svc_rpcsec_gss.c revision 181344
1/*-
2 * Copyright (c) 2008 Doug Rabson
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 *	$FreeBSD: head/lib/librpcsec_gss/svc_rpcsec_gss.c 181344 2008-08-06 14:02:05Z dfr $
27 */
28/*
29  svc_rpcsec_gss.c
30
31  Copyright (c) 2000 The Regents of the University of Michigan.
32  All rights reserved.
33
34  Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
35  All rights reserved, all wrongs reversed.
36
37  Redistribution and use in source and binary forms, with or without
38  modification, are permitted provided that the following conditions
39  are met:
40
41  1. Redistributions of source code must retain the above copyright
42     notice, this list of conditions and the following disclaimer.
43  2. Redistributions in binary form must reproduce the above copyright
44     notice, this list of conditions and the following disclaimer in the
45     documentation and/or other materials provided with the distribution.
46  3. Neither the name of the University nor the names of its
47     contributors may be used to endorse or promote products derived
48     from this software without specific prior written permission.
49
50  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
51  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
52  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
53  DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
54  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
55  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
56  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
57  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
58  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
59  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
60  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
61
62  $Id: svc_auth_gss.c,v 1.27 2002/01/15 15:43:00 andros Exp $
63 */
64
65#include <stdio.h>
66#include <stdlib.h>
67#include <string.h>
68#include <pwd.h>
69#include <grp.h>
70#include <errno.h>
71#include <unistd.h>
72#include <sys/queue.h>
73#include <rpc/rpc.h>
74#include <rpc/rpcsec_gss.h>
75#include "rpcsec_gss_int.h"
76
77static bool_t	svc_rpc_gss_initialised = FALSE;
78
79static bool_t   svc_rpc_gss_wrap(SVCAUTH *, XDR *, xdrproc_t, caddr_t);
80static bool_t   svc_rpc_gss_unwrap(SVCAUTH *, XDR *, xdrproc_t, caddr_t);
81static enum auth_stat svc_rpc_gss(struct svc_req *, struct rpc_msg *);
82
83static struct svc_auth_ops svc_auth_gss_ops = {
84	svc_rpc_gss_wrap,
85	svc_rpc_gss_unwrap,
86};
87
88struct svc_rpc_gss_callback {
89	SLIST_ENTRY(svc_rpc_gss_callback) cb_link;
90	rpc_gss_callback_t	cb_callback;
91};
92static SLIST_HEAD(svc_rpc_gss_callback_list, svc_rpc_gss_callback)
93	svc_rpc_gss_callbacks = SLIST_HEAD_INITIALIZER(&svc_rpc_gss_callbacks);
94
95struct svc_rpc_gss_svc_name {
96	SLIST_ENTRY(svc_rpc_gss_svc_name) sn_link;
97	char			*sn_principal;
98	gss_OID			sn_mech;
99	u_int			sn_req_time;
100	gss_cred_id_t		sn_cred;
101	u_int			sn_program;
102	u_int			sn_version;
103};
104static SLIST_HEAD(svc_rpc_gss_svc_name_list, svc_rpc_gss_svc_name)
105	svc_rpc_gss_svc_names = SLIST_HEAD_INITIALIZER(&svc_rpc_gss_svc_names);
106
107enum svc_rpc_gss_client_state {
108	CLIENT_NEW,				/* still authenticating */
109	CLIENT_ESTABLISHED,			/* context established */
110	CLIENT_STALE				/* garbage to collect */
111};
112
113#define SVC_RPC_GSS_SEQWINDOW	128
114
115struct svc_rpc_gss_client {
116	TAILQ_ENTRY(svc_rpc_gss_client) cl_link;
117	TAILQ_ENTRY(svc_rpc_gss_client) cl_alllink;
118	uint32_t		cl_id;
119	time_t			cl_expiration;	/* when to gc */
120	enum svc_rpc_gss_client_state cl_state;	/* client state */
121	bool_t			cl_locked;	/* fixed service+qop */
122	gss_ctx_id_t		cl_ctx;		/* context id */
123	gss_cred_id_t		cl_creds;	/* delegated creds */
124	gss_name_t		cl_cname;	/* client name */
125	struct svc_rpc_gss_svc_name *cl_sname;	/* server name used */
126	rpc_gss_rawcred_t	cl_rawcred;	/* raw credentials */
127	rpc_gss_ucred_t		cl_ucred;	/* unix-style credentials */
128	bool_t			cl_done_callback; /* TRUE after call */
129	void			*cl_cookie;	/* user cookie from callback */
130	gid_t			cl_gid_storage[NGRPS];
131	gss_OID			cl_mech;	/* mechanism */
132	gss_qop_t		cl_qop;		/* quality of protection */
133	u_int			cl_seq;		/* current sequence number */
134	u_int			cl_win;		/* sequence window size */
135	u_int			cl_seqlast;	/* sequence window origin */
136	uint32_t		cl_seqmask[SVC_RPC_GSS_SEQWINDOW/32]; /* bitmask of seqnums */
137	gss_buffer_desc		cl_verf;	/* buffer for verf checksum */
138};
139TAILQ_HEAD(svc_rpc_gss_client_list, svc_rpc_gss_client);
140
141#define CLIENT_HASH_SIZE	256
142#define CLIENT_MAX		128
143struct svc_rpc_gss_client_list svc_rpc_gss_client_hash[CLIENT_HASH_SIZE];
144struct svc_rpc_gss_client_list svc_rpc_gss_clients;
145static size_t svc_rpc_gss_client_count;
146static uint32_t svc_rpc_gss_next_clientid = 1;
147
148#ifdef __GNUC__
149static void svc_rpc_gss_init(void) __attribute__ ((constructor));
150#endif
151
152static void
153svc_rpc_gss_init(void)
154{
155	int i;
156
157	if (!svc_rpc_gss_initialised) {
158		for (i = 0; i < CLIENT_HASH_SIZE; i++)
159			TAILQ_INIT(&svc_rpc_gss_client_hash[i]);
160		TAILQ_INIT(&svc_rpc_gss_clients);
161		svc_auth_reg(RPCSEC_GSS, svc_rpc_gss);
162		svc_rpc_gss_initialised = TRUE;
163	}
164}
165
166bool_t
167rpc_gss_set_callback(rpc_gss_callback_t *cb)
168{
169	struct svc_rpc_gss_callback *scb;
170
171	scb = malloc(sizeof(struct svc_rpc_gss_callback));
172	if (!scb) {
173		_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
174		return (FALSE);
175	}
176	scb->cb_callback = *cb;
177	SLIST_INSERT_HEAD(&svc_rpc_gss_callbacks, scb, cb_link);
178
179	return (TRUE);
180}
181
182bool_t
183rpc_gss_set_svc_name(const char *principal, const char *mechanism,
184    u_int req_time, u_int program, u_int version)
185{
186	OM_uint32		maj_stat, min_stat;
187	struct svc_rpc_gss_svc_name *sname;
188	gss_buffer_desc		namebuf;
189	gss_name_t		name;
190	gss_OID			mech_oid;
191	gss_OID_set_desc	oid_set;
192	gss_cred_id_t		cred;
193
194	svc_rpc_gss_init();
195
196	if (!rpc_gss_mech_to_oid(mechanism, &mech_oid))
197		return (FALSE);
198	oid_set.count = 1;
199	oid_set.elements = mech_oid;
200
201	namebuf.value = (void *)(intptr_t) principal;
202	namebuf.length = strlen(principal);
203
204	maj_stat = gss_import_name(&min_stat, &namebuf,
205				   GSS_C_NT_HOSTBASED_SERVICE, &name);
206	if (maj_stat != GSS_S_COMPLETE)
207		return (FALSE);
208
209	maj_stat = gss_acquire_cred(&min_stat, name,
210	    req_time, &oid_set, GSS_C_ACCEPT, &cred, NULL, NULL);
211	if (maj_stat != GSS_S_COMPLETE)
212		return (FALSE);
213
214	gss_release_name(&min_stat, &name);
215
216	sname = malloc(sizeof(struct svc_rpc_gss_svc_name));
217	if (!sname)
218		return (FALSE);
219	sname->sn_principal = strdup(principal);
220	sname->sn_mech = mech_oid;
221	sname->sn_req_time = req_time;
222	sname->sn_cred = cred;
223	sname->sn_program = program;
224	sname->sn_version = version;
225	SLIST_INSERT_HEAD(&svc_rpc_gss_svc_names, sname, sn_link);
226
227	return (TRUE);
228}
229
230bool_t
231rpc_gss_get_principal_name(rpc_gss_principal_t *principal,
232    const char *mech, const char *name, const char *node, const char *domain)
233{
234	OM_uint32		maj_stat, min_stat;
235	gss_OID			mech_oid;
236	size_t			namelen;
237	gss_buffer_desc		buf;
238	gss_name_t		gss_name, gss_mech_name;
239	rpc_gss_principal_t	result;
240
241	svc_rpc_gss_init();
242
243	if (!rpc_gss_mech_to_oid(mech, &mech_oid))
244		return (FALSE);
245
246	/*
247	 * Construct a gss_buffer containing the full name formatted
248	 * as "name/node@domain" where node and domain are optional.
249	 */
250	namelen = strlen(name);
251	if (node) {
252		namelen += strlen(node) + 1;
253	}
254	if (domain) {
255		namelen += strlen(domain) + 1;
256	}
257
258	buf.value = malloc(namelen);
259	buf.length = namelen;
260	strcpy((char *) buf.value, name);
261	if (node) {
262		strcat((char *) buf.value, "/");
263		strcat((char *) buf.value, node);
264	}
265	if (domain) {
266		strcat((char *) buf.value, "@");
267		strcat((char *) buf.value, domain);
268	}
269
270	/*
271	 * Convert that to a gss_name_t and then convert that to a
272	 * mechanism name in the selected mechanism.
273	 */
274	maj_stat = gss_import_name(&min_stat, &buf,
275	    GSS_C_NT_USER_NAME, &gss_name);
276	free(buf.value);
277	if (maj_stat != GSS_S_COMPLETE) {
278		log_status("gss_import_name", mech_oid, maj_stat, min_stat);
279		return (FALSE);
280	}
281	maj_stat = gss_canonicalize_name(&min_stat, gss_name, mech_oid,
282	    &gss_mech_name);
283	if (maj_stat != GSS_S_COMPLETE) {
284		log_status("gss_canonicalize_name", mech_oid, maj_stat,
285		    min_stat);
286		gss_release_name(&min_stat, &gss_name);
287		return (FALSE);
288	}
289	gss_release_name(&min_stat, &gss_name);
290
291	/*
292	 * Export the mechanism name and use that to construct the
293	 * rpc_gss_principal_t result.
294	 */
295	maj_stat = gss_export_name(&min_stat, gss_mech_name, &buf);
296	if (maj_stat != GSS_S_COMPLETE) {
297		log_status("gss_export_name", mech_oid, maj_stat, min_stat);
298		gss_release_name(&min_stat, &gss_mech_name);
299		return (FALSE);
300	}
301	gss_release_name(&min_stat, &gss_mech_name);
302
303	result = malloc(sizeof(int) + buf.length);
304	if (!result) {
305		gss_release_buffer(&min_stat, &buf);
306		return (FALSE);
307	}
308	result->len = buf.length;
309	memcpy(result->name, buf.value, buf.length);
310	gss_release_buffer(&min_stat, &buf);
311
312	*principal = result;
313	return (TRUE);
314}
315
316bool_t
317rpc_gss_getcred(struct svc_req *req, rpc_gss_rawcred_t **rcred,
318    rpc_gss_ucred_t **ucred, void **cookie)
319{
320	struct svc_rpc_gss_client *client;
321
322	if (req->rq_cred.oa_flavor != RPCSEC_GSS)
323		return (FALSE);
324
325	client = req->rq_clntcred;
326	if (rcred)
327		*rcred = &client->cl_rawcred;
328	if (ucred)
329		*ucred = &client->cl_ucred;
330	if (cookie)
331		*cookie = client->cl_cookie;
332	return (TRUE);
333}
334
335int
336rpc_gss_svc_max_data_length(struct svc_req *req, int max_tp_unit_len)
337{
338	struct svc_rpc_gss_client *client = req->rq_clntcred;
339	int			want_conf;
340	OM_uint32		max;
341	OM_uint32		maj_stat, min_stat;
342	int			result;
343
344	switch (client->cl_rawcred.service) {
345	case rpc_gss_svc_none:
346		return (max_tp_unit_len);
347		break;
348
349	case rpc_gss_svc_default:
350	case rpc_gss_svc_integrity:
351		want_conf = FALSE;
352		break;
353
354	case rpc_gss_svc_privacy:
355		want_conf = TRUE;
356		break;
357
358	default:
359		return (0);
360	}
361
362	maj_stat = gss_wrap_size_limit(&min_stat, client->cl_ctx, want_conf,
363	    client->cl_qop, max_tp_unit_len, &max);
364
365	if (maj_stat == GSS_S_COMPLETE) {
366		result = (int) max;
367		if (result < 0)
368			result = 0;
369		return (result);
370	} else {
371		log_status("gss_wrap_size_limit", client->cl_mech,
372		    maj_stat, min_stat);
373		return (0);
374	}
375}
376
377static struct svc_rpc_gss_client *
378svc_rpc_gss_find_client(uint32_t clientid)
379{
380	struct svc_rpc_gss_client *client;
381	struct svc_rpc_gss_client_list *list;
382
383
384	log_debug("in svc_rpc_gss_find_client(%d)", clientid);
385
386	list = &svc_rpc_gss_client_hash[clientid % CLIENT_HASH_SIZE];
387	TAILQ_FOREACH(client, list, cl_link) {
388		if (client->cl_id == clientid) {
389			/*
390			 * Move this client to the front of the LRU
391			 * list.
392			 */
393			TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
394			TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client,
395			    cl_alllink);
396			return client;
397		}
398	}
399
400	return (NULL);
401}
402
403static struct svc_rpc_gss_client *
404svc_rpc_gss_create_client(void)
405{
406	struct svc_rpc_gss_client *client;
407	struct svc_rpc_gss_client_list *list;
408
409	log_debug("in svc_rpc_gss_create_client()");
410
411	client = mem_alloc(sizeof(struct svc_rpc_gss_client));
412	memset(client, 0, sizeof(struct svc_rpc_gss_client));
413	client->cl_id = svc_rpc_gss_next_clientid++;
414	list = &svc_rpc_gss_client_hash[client->cl_id % CLIENT_HASH_SIZE];
415	TAILQ_INSERT_HEAD(list, client, cl_link);
416	TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client, cl_alllink);
417
418	/*
419	 * Start the client off with a short expiration time. We will
420	 * try to get a saner value from the client creds later.
421	 */
422	client->cl_state = CLIENT_NEW;
423	client->cl_locked = FALSE;
424	client->cl_expiration = time(0) + 5*60;
425	svc_rpc_gss_client_count++;
426
427	return (client);
428}
429
430static void
431svc_rpc_gss_destroy_client(struct svc_rpc_gss_client *client)
432{
433	struct svc_rpc_gss_client_list *list;
434	OM_uint32 min_stat;
435
436	log_debug("in svc_rpc_gss_destroy_client()");
437
438	if (client->cl_ctx)
439		gss_delete_sec_context(&min_stat,
440		    &client->cl_ctx, GSS_C_NO_BUFFER);
441
442	if (client->cl_cname)
443		gss_release_name(&min_stat, &client->cl_cname);
444
445	if (client->cl_rawcred.client_principal)
446		free(client->cl_rawcred.client_principal);
447
448	if (client->cl_verf.value)
449		gss_release_buffer(&min_stat, &client->cl_verf);
450
451	list = &svc_rpc_gss_client_hash[client->cl_id % CLIENT_HASH_SIZE];
452	TAILQ_REMOVE(list, client, cl_link);
453	TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
454	svc_rpc_gss_client_count--;
455	mem_free(client, sizeof(*client));
456}
457
458static void
459svc_rpc_gss_timeout_clients(void)
460{
461	struct svc_rpc_gss_client *client;
462	struct svc_rpc_gss_client *nclient;
463	time_t now = time(0);
464
465	log_debug("in svc_rpc_gss_timeout_clients()");
466	/*
467	 * First enforce the max client limit. We keep
468	 * svc_rpc_gss_clients in LRU order.
469	 */
470	while (svc_rpc_gss_client_count > CLIENT_MAX)
471		svc_rpc_gss_destroy_client(TAILQ_LAST(&svc_rpc_gss_clients,
472			    svc_rpc_gss_client_list));
473	TAILQ_FOREACH_SAFE(client, &svc_rpc_gss_clients, cl_alllink, nclient) {
474		if (client->cl_state == CLIENT_STALE
475		    || now > client->cl_expiration) {
476			log_debug("expiring client %p", client);
477			svc_rpc_gss_destroy_client(client);
478		}
479	}
480}
481
482#ifdef DEBUG
483/*
484 * OID<->string routines.  These are uuuuugly.
485 */
486static OM_uint32
487gss_oid_to_str(OM_uint32 *minor_status, gss_OID oid, gss_buffer_t oid_str)
488{
489	char		numstr[128];
490	unsigned long	number;
491	int		numshift;
492	size_t		string_length;
493	size_t		i;
494	unsigned char	*cp;
495	char		*bp;
496
497	/* Decoded according to krb5/gssapi_krb5.c */
498
499	/* First determine the size of the string */
500	string_length = 0;
501	number = 0;
502	numshift = 0;
503	cp = (unsigned char *) oid->elements;
504	number = (unsigned long) cp[0];
505	sprintf(numstr, "%ld ", number/40);
506	string_length += strlen(numstr);
507	sprintf(numstr, "%ld ", number%40);
508	string_length += strlen(numstr);
509	for (i=1; i<oid->length; i++) {
510		if ( (size_t) (numshift+7) < (sizeof(unsigned long)*8)) {
511			number = (number << 7) | (cp[i] & 0x7f);
512			numshift += 7;
513		}
514		else {
515			*minor_status = 0;
516			return(GSS_S_FAILURE);
517		}
518		if ((cp[i] & 0x80) == 0) {
519			sprintf(numstr, "%ld ", number);
520			string_length += strlen(numstr);
521			number = 0;
522			numshift = 0;
523		}
524	}
525	/*
526	 * If we get here, we've calculated the length of "n n n ... n ".  Add 4
527	 * here for "{ " and "}\0".
528	 */
529	string_length += 4;
530	if ((bp = (char *) malloc(string_length))) {
531		strcpy(bp, "{ ");
532		number = (unsigned long) cp[0];
533		sprintf(numstr, "%ld ", number/40);
534		strcat(bp, numstr);
535		sprintf(numstr, "%ld ", number%40);
536		strcat(bp, numstr);
537		number = 0;
538		cp = (unsigned char *) oid->elements;
539		for (i=1; i<oid->length; i++) {
540			number = (number << 7) | (cp[i] & 0x7f);
541			if ((cp[i] & 0x80) == 0) {
542				sprintf(numstr, "%ld ", number);
543				strcat(bp, numstr);
544				number = 0;
545			}
546		}
547		strcat(bp, "}");
548		oid_str->length = strlen(bp)+1;
549		oid_str->value = (void *) bp;
550		*minor_status = 0;
551		return(GSS_S_COMPLETE);
552	}
553	*minor_status = 0;
554	return(GSS_S_FAILURE);
555}
556#endif
557
558static void
559svc_rpc_gss_build_ucred(struct svc_rpc_gss_client *client,
560    const gss_name_t name)
561{
562	OM_uint32		maj_stat, min_stat;
563	char			buf[128];
564	uid_t			uid;
565	struct passwd		pwd, *pw;
566	rpc_gss_ucred_t		*uc = &client->cl_ucred;
567
568	uc->uid = 65534;
569	uc->gid = 65534;
570	uc->gidlen = 0;
571	uc->gidlist = client->cl_gid_storage;
572
573	maj_stat = gss_pname_to_uid(&min_stat, name, client->cl_mech, &uid);
574	if (maj_stat != GSS_S_COMPLETE)
575		return;
576
577	getpwuid_r(uid, &pwd, buf, sizeof(buf), &pw);
578	if (pw) {
579		int len = NGRPS;
580		uc->uid = pw->pw_uid;
581		uc->gid = pw->pw_gid;
582		uc->gidlist = client->cl_gid_storage;
583		getgrouplist(pw->pw_name, pw->pw_gid, uc->gidlist, &len);
584		uc->gidlen = len;
585	}
586}
587
588static bool_t
589svc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client,
590			       struct svc_req *rqst,
591			       struct rpc_gss_init_res *gr,
592			       struct rpc_gss_cred *gc)
593{
594	gss_buffer_desc		recv_tok;
595	gss_OID			mech;
596	OM_uint32		maj_stat = 0, min_stat = 0, ret_flags;
597	OM_uint32		cred_lifetime;
598	struct svc_rpc_gss_svc_name *sname;
599
600	log_debug("in svc_rpc_gss_accept_context()");
601
602	/* Deserialize arguments. */
603	memset(&recv_tok, 0, sizeof(recv_tok));
604
605	if (!svc_getargs(rqst->rq_xprt,
606		(xdrproc_t) xdr_gss_buffer_desc,
607		(caddr_t) &recv_tok)) {
608		client->cl_state = CLIENT_STALE;
609		return (FALSE);
610	}
611
612	/*
613	 * First time round, try all the server names we have until
614	 * one matches. Afterwards, stick with that one.
615	 */
616	if (!client->cl_sname) {
617		SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
618			if (sname->sn_program == rqst->rq_prog
619			    && sname->sn_version == rqst->rq_vers) {
620				gr->gr_major = gss_accept_sec_context(
621					&gr->gr_minor,
622					&client->cl_ctx,
623					sname->sn_cred,
624					&recv_tok,
625					GSS_C_NO_CHANNEL_BINDINGS,
626					&client->cl_cname,
627					&mech,
628					&gr->gr_token,
629					&ret_flags,
630					&cred_lifetime,
631					&client->cl_creds);
632				if (gr->gr_major == GSS_S_COMPLETE
633				    || gr->gr_major == GSS_S_CONTINUE_NEEDED) {
634					client->cl_sname = sname;
635					break;
636				}
637			}
638		}
639	} else {
640		gr->gr_major = gss_accept_sec_context(
641			&gr->gr_minor,
642			&client->cl_ctx,
643			client->cl_sname->sn_cred,
644			&recv_tok,
645			GSS_C_NO_CHANNEL_BINDINGS,
646			&client->cl_cname,
647			&mech,
648			&gr->gr_token,
649			&ret_flags,
650			&cred_lifetime,
651			NULL);
652	}
653
654	xdr_free((xdrproc_t) xdr_gss_buffer_desc, (char *) &recv_tok);
655
656	/*
657	 * If we get an error from gss_accept_sec_context, send the
658	 * reply anyway so that the client gets a chance to see what
659	 * is wrong.
660	 */
661	if (gr->gr_major != GSS_S_COMPLETE &&
662	    gr->gr_major != GSS_S_CONTINUE_NEEDED) {
663		log_status("accept_sec_context", client->cl_mech,
664		    gr->gr_major, gr->gr_minor);
665		client->cl_state = CLIENT_STALE;
666		return (FALSE);
667	}
668
669	gr->gr_handle.value = &client->cl_id;
670	gr->gr_handle.length = sizeof(uint32_t);
671	gr->gr_win = SVC_RPC_GSS_SEQWINDOW;
672
673	/* Save client info. */
674	client->cl_mech = mech;
675	client->cl_qop = GSS_C_QOP_DEFAULT;
676	client->cl_seq = gc->gc_seq;
677	client->cl_win = gr->gr_win;
678	client->cl_done_callback = FALSE;
679
680	if (gr->gr_major == GSS_S_COMPLETE) {
681		gss_buffer_desc	export_name;
682
683		/*
684		 * Change client expiration time to be near when the
685		 * client creds expire (or 24 hours if we can't figure
686		 * that out).
687		 */
688		if (cred_lifetime == GSS_C_INDEFINITE)
689			cred_lifetime = time(0) + 24*60*60;
690
691		client->cl_expiration = time(0) + cred_lifetime;
692
693		/*
694		 * Fill in cred details in the rawcred structure.
695		 */
696		client->cl_rawcred.version = RPCSEC_GSS_VERSION;
697		rpc_gss_oid_to_mech(mech, &client->cl_rawcred.mechanism);
698		maj_stat = gss_export_name(&min_stat, client->cl_cname,
699		    &export_name);
700		if (maj_stat != GSS_S_COMPLETE) {
701			log_status("gss_export_name", client->cl_mech,
702			    maj_stat, min_stat);
703			return (FALSE);
704		}
705		client->cl_rawcred.client_principal =
706			malloc(sizeof(*client->cl_rawcred.client_principal)
707			    + export_name.length);
708		client->cl_rawcred.client_principal->len = export_name.length;
709		memcpy(client->cl_rawcred.client_principal->name,
710		    export_name.value, export_name.length);
711		gss_release_buffer(&min_stat, &export_name);
712		client->cl_rawcred.svc_principal =
713			client->cl_sname->sn_principal;
714		client->cl_rawcred.service = gc->gc_svc;
715
716		/*
717		 * Use gss_pname_to_uid to map to unix creds. For
718		 * kerberos5, this uses krb5_aname_to_localname.
719		 */
720		svc_rpc_gss_build_ucred(client, client->cl_cname);
721
722#ifdef DEBUG
723		{
724			gss_buffer_desc mechname;
725
726			gss_oid_to_str(&min_stat, mech, &mechname);
727
728			log_debug("accepted context for %s with "
729			    "<mech %.*s, qop %d, svc %d>",
730			    client->cl_rawcred.client_principal->name,
731			    mechname.length, (char *)mechname.value,
732			    client->cl_qop, client->rawcred.service);
733
734			gss_release_buffer(&min_stat, &mechname);
735		}
736#endif /* DEBUG */
737	}
738	return (TRUE);
739}
740
741static bool_t
742svc_rpc_gss_validate(struct svc_rpc_gss_client *client, struct rpc_msg *msg,
743	gss_qop_t *qop)
744{
745	struct opaque_auth	*oa;
746	gss_buffer_desc		 rpcbuf, checksum;
747	OM_uint32		 maj_stat, min_stat;
748	gss_qop_t		 qop_state;
749	u_char			 rpchdr[128];
750	int32_t			*buf;
751
752	log_debug("in svc_rpc_gss_validate()");
753
754	memset(rpchdr, 0, sizeof(rpchdr));
755
756	/* Reconstruct RPC header for signing (from xdr_callmsg). */
757	buf = (int32_t *)rpchdr;
758	IXDR_PUT_LONG(buf, msg->rm_xid);
759	IXDR_PUT_ENUM(buf, msg->rm_direction);
760	IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers);
761	IXDR_PUT_LONG(buf, msg->rm_call.cb_prog);
762	IXDR_PUT_LONG(buf, msg->rm_call.cb_vers);
763	IXDR_PUT_LONG(buf, msg->rm_call.cb_proc);
764	oa = &msg->rm_call.cb_cred;
765	IXDR_PUT_ENUM(buf, oa->oa_flavor);
766	IXDR_PUT_LONG(buf, oa->oa_length);
767	if (oa->oa_length) {
768		memcpy((caddr_t)buf, oa->oa_base, oa->oa_length);
769		buf += RNDUP(oa->oa_length) / sizeof(int32_t);
770	}
771	rpcbuf.value = rpchdr;
772	rpcbuf.length = (u_char *)buf - rpchdr;
773
774	checksum.value = msg->rm_call.cb_verf.oa_base;
775	checksum.length = msg->rm_call.cb_verf.oa_length;
776
777	maj_stat = gss_verify_mic(&min_stat, client->cl_ctx, &rpcbuf, &checksum,
778				  &qop_state);
779
780	if (maj_stat != GSS_S_COMPLETE) {
781		log_status("gss_verify_mic", client->cl_mech,
782		    maj_stat, min_stat);
783		client->cl_state = CLIENT_STALE;
784		return (FALSE);
785	}
786	*qop = qop_state;
787	return (TRUE);
788}
789
790static bool_t
791svc_rpc_gss_nextverf(struct svc_rpc_gss_client *client,
792    struct svc_req *rqst, u_int seq)
793{
794	gss_buffer_desc		signbuf;
795	OM_uint32		maj_stat, min_stat;
796	uint32_t		nseq;
797
798	log_debug("in svc_rpc_gss_nextverf()");
799
800	nseq = htonl(seq);
801	signbuf.value = &nseq;
802	signbuf.length = sizeof(nseq);
803
804	if (client->cl_verf.value)
805		gss_release_buffer(&min_stat, &client->cl_verf);
806
807	maj_stat = gss_get_mic(&min_stat, client->cl_ctx, client->cl_qop,
808	    &signbuf, &client->cl_verf);
809
810	if (maj_stat != GSS_S_COMPLETE) {
811		log_status("gss_get_mic", client->cl_mech, maj_stat, min_stat);
812		client->cl_state = CLIENT_STALE;
813		return (FALSE);
814	}
815	rqst->rq_xprt->xp_verf.oa_flavor = RPCSEC_GSS;
816	rqst->rq_xprt->xp_verf.oa_base = (caddr_t)client->cl_verf.value;
817	rqst->rq_xprt->xp_verf.oa_length = (u_int)client->cl_verf.length;
818
819	return (TRUE);
820}
821
822static bool_t
823svc_rpc_gss_callback(struct svc_rpc_gss_client *client, struct svc_req *rqst)
824{
825	struct svc_rpc_gss_callback *scb;
826	rpc_gss_lock_t	lock;
827	void		*cookie;
828	bool_t		cb_res;
829	bool_t		result;
830
831	/*
832	 * See if we have a callback for this guy.
833	 */
834	result = TRUE;
835	SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
836		if (scb->cb_callback.program == rqst->rq_prog
837		    && scb->cb_callback.version == rqst->rq_vers) {
838			/*
839			 * This one matches. Call the callback and see
840			 * if it wants to veto or something.
841			 */
842			lock.locked = FALSE;
843			lock.raw_cred = &client->cl_rawcred;
844			cb_res = scb->cb_callback.callback(rqst,
845			    client->cl_creds,
846			    client->cl_ctx,
847			    &lock,
848			    &cookie);
849
850			if (!cb_res) {
851				client->cl_state = CLIENT_STALE;
852				result = FALSE;
853				break;
854			}
855
856			/*
857			 * The callback accepted the connection - it
858			 * is responsible for freeing client->cl_creds
859			 * now.
860			 */
861			client->cl_creds = GSS_C_NO_CREDENTIAL;
862			client->cl_locked = lock.locked;
863			client->cl_cookie = cookie;
864			return (TRUE);
865		}
866	}
867
868	/*
869	 * Either no callback exists for this program/version or one
870	 * of the callbacks rejected the connection. We just need to
871	 * clean up the delegated client creds, if any.
872	 */
873	if (client->cl_creds) {
874		OM_uint32 min_ver;
875		gss_release_cred(&min_ver, &client->cl_creds);
876	}
877	return (result);
878}
879
880static bool_t
881svc_rpc_gss_check_replay(struct svc_rpc_gss_client *client, uint32_t seq)
882{
883	u_int32_t offset;
884	int word, bit;
885
886	if (seq < client->cl_seqlast) {
887		/*
888		 * The request sequence number is less than
889		 * the largest we have seen so far. If it is
890		 * outside the window or if we have seen a
891		 * request with this sequence before, silently
892		 * discard it.
893		 */
894		offset = client->cl_seqlast - seq;
895		if (offset >= client->cl_win)
896			return (FALSE);
897		word = offset / 32;
898		bit = offset % 32;
899		if (client->cl_seqmask[word] & (1 << bit))
900			return (FALSE);
901		client->cl_seqmask[word] |= (1 << bit);
902	}
903
904	return (TRUE);
905}
906
907static void
908svc_rpc_gss_update_seq(struct svc_rpc_gss_client *client, uint32_t seq)
909{
910	int offset, i;
911	uint32_t carry, newcarry;
912
913	if (seq > client->cl_seqlast) {
914		/*
915		 * This request has a sequence number greater
916		 * than any we have seen so far. Advance the
917		 * seq window and set bit zero of the window
918		 * (which corresponds to the new sequence
919		 * number)
920		 */
921		offset = seq - client->cl_seqlast;
922		while (offset > 32) {
923			for (i = (SVC_RPC_GSS_SEQWINDOW / 32) - 1;
924			     i > 0; i--) {
925				client->cl_seqmask[i] = client->cl_seqmask[i-1];
926			}
927			client->cl_seqmask[0] = 0;
928			offset -= 32;
929		}
930		carry = 0;
931		for (i = 0; i < SVC_RPC_GSS_SEQWINDOW / 32; i++) {
932			newcarry = client->cl_seqmask[i] >> (32 - offset);
933			client->cl_seqmask[i] =
934				(client->cl_seqmask[i] << offset) | carry;
935			carry = newcarry;
936		}
937		client->cl_seqmask[0] |= 1;
938		client->cl_seqlast = seq;
939	}
940}
941
942enum auth_stat
943svc_rpc_gss(struct svc_req *rqst, struct rpc_msg *msg)
944
945{
946	OM_uint32		 min_stat;
947	XDR	 		 xdrs;
948	struct svc_rpc_gss_client *client;
949	struct rpc_gss_cred	 gc;
950	struct rpc_gss_init_res	 gr;
951	gss_qop_t		 qop;
952	int			 call_stat;
953	enum auth_stat		 result;
954
955	log_debug("in svc_rpc_gss()");
956
957	/* Garbage collect old clients. */
958	svc_rpc_gss_timeout_clients();
959
960	/* Initialize reply. */
961	rqst->rq_xprt->xp_verf = _null_auth;
962
963	/* Deserialize client credentials. */
964	if (rqst->rq_cred.oa_length <= 0)
965		return (AUTH_BADCRED);
966
967	memset(&gc, 0, sizeof(gc));
968
969	xdrmem_create(&xdrs, rqst->rq_cred.oa_base,
970	    rqst->rq_cred.oa_length, XDR_DECODE);
971
972	if (!xdr_rpc_gss_cred(&xdrs, &gc)) {
973		XDR_DESTROY(&xdrs);
974		return (AUTH_BADCRED);
975	}
976	XDR_DESTROY(&xdrs);
977
978	/* Check version. */
979	if (gc.gc_version != RPCSEC_GSS_VERSION) {
980		result = AUTH_BADCRED;
981		goto out;
982	}
983
984	/* Check the proc and find the client (or create it) */
985	if (gc.gc_proc == RPCSEC_GSS_INIT) {
986		client = svc_rpc_gss_create_client();
987	} else {
988		if (gc.gc_handle.length != sizeof(uint32_t)) {
989			result = AUTH_BADCRED;
990			goto out;
991		}
992		uint32_t *p = gc.gc_handle.value;
993		client = svc_rpc_gss_find_client(*p);
994		if (!client) {
995			/*
996			 * Can't find the client - we may have
997			 * destroyed it - tell the other side to
998			 * re-authenticate.
999			 */
1000			result = RPCSEC_GSS_CREDPROBLEM;
1001			goto out;
1002		}
1003	}
1004	rqst->rq_clntcred = client;
1005
1006	/*
1007	 * The service and sequence number must be ignored for
1008	 * RPCSEC_GSS_INIT and RPCSEC_GSS_CONTINUE_INIT.
1009	 */
1010	if (gc.gc_proc != RPCSEC_GSS_INIT
1011	    && gc.gc_proc != RPCSEC_GSS_CONTINUE_INIT) {
1012		/*
1013		 * Check for sequence number overflow.
1014		 */
1015		if (gc.gc_seq >= MAXSEQ) {
1016			result = RPCSEC_GSS_CTXPROBLEM;
1017			goto out;
1018		}
1019		client->cl_seq = gc.gc_seq;
1020
1021		/*
1022		 * Check for valid service.
1023		 */
1024		if (gc.gc_svc != rpc_gss_svc_none &&
1025		    gc.gc_svc != rpc_gss_svc_integrity &&
1026		    gc.gc_svc != rpc_gss_svc_privacy) {
1027			result = AUTH_BADCRED;
1028			goto out;
1029		}
1030	}
1031
1032	/* Handle RPCSEC_GSS control procedure. */
1033	switch (gc.gc_proc) {
1034
1035	case RPCSEC_GSS_INIT:
1036	case RPCSEC_GSS_CONTINUE_INIT:
1037		if (rqst->rq_proc != NULLPROC) {
1038			result = AUTH_REJECTEDCRED;
1039			break;
1040		}
1041
1042		memset(&gr, 0, sizeof(gr));
1043		if (!svc_rpc_gss_accept_sec_context(client, rqst, &gr, &gc)) {
1044			result = AUTH_REJECTEDCRED;
1045			break;
1046		}
1047
1048		if (gr.gr_major == GSS_S_COMPLETE) {
1049			if (!svc_rpc_gss_nextverf(client, rqst, gr.gr_win)) {
1050				result = AUTH_REJECTEDCRED;
1051				break;
1052			}
1053		} else {
1054			rqst->rq_xprt->xp_verf.oa_flavor = AUTH_NULL;
1055			rqst->rq_xprt->xp_verf.oa_length = 0;
1056		}
1057
1058		call_stat = svc_sendreply(rqst->rq_xprt,
1059		    (xdrproc_t) xdr_rpc_gss_init_res,
1060		    (caddr_t) &gr);
1061
1062		gss_release_buffer(&min_stat, &gr.gr_token);
1063
1064		if (!call_stat) {
1065			result = AUTH_FAILED;
1066			break;
1067		}
1068
1069		if (gr.gr_major == GSS_S_COMPLETE)
1070			client->cl_state = CLIENT_ESTABLISHED;
1071
1072		result = RPCSEC_GSS_NODISPATCH;
1073		break;
1074
1075	case RPCSEC_GSS_DATA:
1076	case RPCSEC_GSS_DESTROY:
1077		if (!svc_rpc_gss_check_replay(client, gc.gc_seq)) {
1078			result = RPCSEC_GSS_NODISPATCH;
1079			break;
1080		}
1081
1082		if (!svc_rpc_gss_validate(client, msg, &qop)) {
1083			result = RPCSEC_GSS_CREDPROBLEM;
1084			break;
1085		}
1086
1087		if (!svc_rpc_gss_nextverf(client, rqst, gc.gc_seq)) {
1088			result = RPCSEC_GSS_CTXPROBLEM;
1089			break;
1090		}
1091
1092		svc_rpc_gss_update_seq(client, gc.gc_seq);
1093
1094		/*
1095		 * Change the SVCAUTH ops on the transport to point at
1096		 * our own code so that we can unwrap the arguments
1097		 * and wrap the result. The caller will re-set this on
1098		 * every request to point to a set of null wrap/unwrap
1099		 * methods.
1100		 */
1101		SVC_AUTH(rqst->rq_xprt).svc_ah_ops = &svc_auth_gss_ops;
1102		SVC_AUTH(rqst->rq_xprt).svc_ah_private = client;
1103
1104		if (gc.gc_proc == RPCSEC_GSS_DATA) {
1105			/*
1106			 * We might be ready to do a callback to the server to
1107			 * see if it wants to accept/reject the connection.
1108			 */
1109			if (!client->cl_done_callback) {
1110				client->cl_done_callback = TRUE;
1111				client->cl_qop = qop;
1112				client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1113					client->cl_rawcred.mechanism, qop);
1114				if (!svc_rpc_gss_callback(client, rqst)) {
1115					result = AUTH_REJECTEDCRED;
1116					break;
1117				}
1118			}
1119
1120			/*
1121			 * If the server has locked this client to a
1122			 * particular service+qop pair, enforce that
1123			 * restriction now.
1124			 */
1125			if (client->cl_locked) {
1126				if (client->cl_rawcred.service != gc.gc_svc) {
1127					result = AUTH_FAILED;
1128					break;
1129				} else if (client->cl_qop != qop) {
1130					result = AUTH_BADVERF;
1131					break;
1132				}
1133			}
1134
1135			/*
1136			 * If the qop changed, look up the new qop
1137			 * name for rawcred.
1138			 */
1139			if (client->cl_qop != qop) {
1140				client->cl_qop = qop;
1141				client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1142					client->cl_rawcred.mechanism, qop);
1143			}
1144
1145			/*
1146			 * Make sure we use the right service value
1147			 * for unwrap/wrap.
1148			 */
1149			client->cl_rawcred.service = gc.gc_svc;
1150
1151			result = AUTH_OK;
1152		} else {
1153			if (rqst->rq_proc != NULLPROC) {
1154				result = AUTH_REJECTEDCRED;
1155				break;
1156			}
1157
1158			call_stat = svc_sendreply(rqst->rq_xprt,
1159			    (xdrproc_t) xdr_void, (caddr_t) NULL);
1160
1161			if (!call_stat) {
1162				result = AUTH_FAILED;
1163				break;
1164			}
1165
1166			svc_rpc_gss_destroy_client(client);
1167
1168			result = RPCSEC_GSS_NODISPATCH;
1169			break;
1170		}
1171		break;
1172
1173	default:
1174		result = AUTH_BADCRED;
1175		break;
1176	}
1177out:
1178	xdr_free((xdrproc_t) xdr_rpc_gss_cred, (char *) &gc);
1179	return (result);
1180}
1181
1182bool_t
1183svc_rpc_gss_wrap(SVCAUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr)
1184{
1185	struct svc_rpc_gss_client *client;
1186
1187	log_debug("in svc_rpc_gss_wrap()");
1188
1189	client = (struct svc_rpc_gss_client *) auth->svc_ah_private;
1190	if (client->cl_state != CLIENT_ESTABLISHED
1191	    || client->cl_rawcred.service == rpc_gss_svc_none) {
1192		return xdr_func(xdrs, xdr_ptr);
1193	}
1194	return (xdr_rpc_gss_wrap_data(xdrs, xdr_func, xdr_ptr,
1195		client->cl_ctx, client->cl_qop,
1196		client->cl_rawcred.service, client->cl_seq));
1197}
1198
1199bool_t
1200svc_rpc_gss_unwrap(SVCAUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr)
1201{
1202	struct svc_rpc_gss_client *client;
1203
1204	log_debug("in svc_rpc_gss_unwrap()");
1205
1206	client = (struct svc_rpc_gss_client *) auth->svc_ah_private;
1207	if (client->cl_state != CLIENT_ESTABLISHED
1208	    || client->cl_rawcred.service == rpc_gss_svc_none) {
1209		return xdr_func(xdrs, xdr_ptr);
1210	}
1211	return (xdr_rpc_gss_unwrap_data(xdrs, xdr_func, xdr_ptr,
1212		client->cl_ctx, client->cl_qop,
1213		client->cl_rawcred.service, client->cl_seq));
1214}
1215