1
2/*
3 * Copyright (c) 1988 by Sun Microsystems, Inc.
4 */
5
6/*-
7 * Copyright (c) 2009, Sun Microsystems, Inc.
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions are met:
12 * - Redistributions of source code must retain the above copyright notice,
13 *   this list of conditions and the following disclaimer.
14 * - Redistributions in binary form must reproduce the above copyright notice,
15 *   this list of conditions and the following disclaimer in the documentation
16 *   and/or other materials provided with the distribution.
17 * - Neither the name of Sun Microsystems, Inc. nor the names of its
18 *   contributors may be used to endorse or promote products derived
19 *   from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGE.
32 */
33
34/*
35 * svcauth_des.c, server-side des authentication
36 *
37 * We insure for the service the following:
38 * (1) The timestamp microseconds do not exceed 1 million.
39 * (2) The timestamp plus the window is less than the current time.
40 * (3) The timestamp is not less than the one previously
41 *     seen in the current session.
42 *
43 * It is up to the server to determine if the window size is
44 * too small .
45 *
46 */
47
48#include "namespace.h"
49#include "reentrant.h"
50#include <string.h>
51#include <stdlib.h>
52#include <stdio.h>
53#include <unistd.h>
54#include <rpc/des_crypt.h>
55#include <sys/param.h>
56#include <netinet/in.h>
57#include <rpc/types.h>
58#include <rpc/xdr.h>
59#include <rpc/auth.h>
60#include <rpc/auth_des.h>
61#include <rpc/svc.h>
62#include <rpc/rpc_msg.h>
63#include <rpc/svc_auth.h>
64#include "libc_private.h"
65
66#if defined(LIBC_SCCS) && !defined(lint)
67static char sccsid[] = 	"@(#)svcauth_des.c	2.3 89/07/11 4.0 RPCSRC; from 1.15 88/02/08 SMI";
68#endif
69#include <sys/cdefs.h>
70__FBSDID("$FreeBSD$");
71
72extern int key_decryptsession_pk(const char *, netobj *, des_block *);
73
74#define debug(msg)	 printf("svcauth_des: %s\n", msg)
75
76#define USEC_PER_SEC ((u_long) 1000000L)
77#define BEFORE(t1, t2) timercmp(t1, t2, <)
78
79/*
80 * LRU cache of conversation keys and some other useful items.
81 */
82#define AUTHDES_CACHESZ 64
83struct cache_entry {
84	des_block key;		/* conversation key */
85	char *rname;		/* client's name */
86	u_int window;		/* credential lifetime window */
87	struct timeval laststamp;	/* detect replays of creds */
88	char *localcred;	/* generic local credential */
89};
90static struct cache_entry *authdes_cache/* [AUTHDES_CACHESZ] */;
91static short *authdes_lru/* [AUTHDES_CACHESZ] */;
92
93static void cache_init(void);	/* initialize the cache */
94static short cache_spot(des_block *, char *, struct timeval *); /* find an entry in the cache */
95static void cache_ref(short sid);	/* note that sid was ref'd */
96
97static void invalidate(char *);	/* invalidate entry in cache */
98
99/*
100 * cache statistics
101 */
102static struct {
103	u_long ncachehits;	/* times cache hit, and is not replay */
104	u_long ncachereplays;	/* times cache hit, and is replay */
105	u_long ncachemisses;	/* times cache missed */
106} svcauthdes_stats;
107
108/*
109 * Service side authenticator for AUTH_DES
110 */
111enum auth_stat
112_svcauth_des(struct svc_req *rqst, struct rpc_msg *msg)
113{
114
115	long *ixdr;
116	des_block cryptbuf[2];
117	struct authdes_cred *cred;
118	struct authdes_verf verf;
119	int status;
120	struct cache_entry *entry;
121	short sid = 0;
122	des_block *sessionkey;
123	des_block ivec;
124	u_int window;
125	struct timeval timestamp;
126	u_long namelen;
127	struct area {
128		struct authdes_cred area_cred;
129		char area_netname[MAXNETNAMELEN+1];
130	} *area;
131
132	if (authdes_cache == NULL) {
133		cache_init();
134	}
135
136	area = (struct area *)rqst->rq_clntcred;
137	cred = (struct authdes_cred *)&area->area_cred;
138
139	/*
140	 * Get the credential
141	 */
142	ixdr = (long *)msg->rm_call.cb_cred.oa_base;
143	cred->adc_namekind = IXDR_GET_ENUM(ixdr, enum authdes_namekind);
144	switch (cred->adc_namekind) {
145	case ADN_FULLNAME:
146		namelen = IXDR_GET_U_LONG(ixdr);
147		if (namelen > MAXNETNAMELEN) {
148			return (AUTH_BADCRED);
149		}
150		cred->adc_fullname.name = area->area_netname;
151		bcopy((char *)ixdr, cred->adc_fullname.name,
152			(u_int)namelen);
153		cred->adc_fullname.name[namelen] = 0;
154		ixdr += (RNDUP(namelen) / BYTES_PER_XDR_UNIT);
155		cred->adc_fullname.key.key.high = (u_long)*ixdr++;
156		cred->adc_fullname.key.key.low = (u_long)*ixdr++;
157		cred->adc_fullname.window = (u_long)*ixdr++;
158		break;
159	case ADN_NICKNAME:
160		cred->adc_nickname = (u_long)*ixdr++;
161		break;
162	default:
163		return (AUTH_BADCRED);
164	}
165
166	/*
167	 * Get the verifier
168	 */
169	ixdr = (long *)msg->rm_call.cb_verf.oa_base;
170	verf.adv_xtimestamp.key.high = (u_long)*ixdr++;
171	verf.adv_xtimestamp.key.low =  (u_long)*ixdr++;
172	verf.adv_int_u = (u_long)*ixdr++;
173
174
175	/*
176	 * Get the conversation key
177 	 */
178	if (cred->adc_namekind == ADN_FULLNAME) {
179		netobj pkey;
180		char pkey_data[1024];
181
182		sessionkey = &cred->adc_fullname.key;
183		if (! getpublickey(cred->adc_fullname.name, pkey_data)) {
184			debug("getpublickey");
185			return(AUTH_BADCRED);
186		}
187		pkey.n_bytes = pkey_data;
188		pkey.n_len = strlen(pkey_data) + 1;
189		if (key_decryptsession_pk(cred->adc_fullname.name, &pkey,
190				       sessionkey) < 0) {
191			debug("decryptsessionkey");
192			return (AUTH_BADCRED); /* key not found */
193		}
194	} else { /* ADN_NICKNAME */
195		sid = (short)cred->adc_nickname;
196		if (sid < 0 || sid >= AUTHDES_CACHESZ) {
197			debug("bad nickname");
198			return (AUTH_BADCRED);	/* garbled credential */
199		}
200		sessionkey = &authdes_cache[sid].key;
201	}
202
203
204	/*
205	 * Decrypt the timestamp
206	 */
207	cryptbuf[0] = verf.adv_xtimestamp;
208	if (cred->adc_namekind == ADN_FULLNAME) {
209		cryptbuf[1].key.high = cred->adc_fullname.window;
210		cryptbuf[1].key.low = verf.adv_winverf;
211		ivec.key.high = ivec.key.low = 0;
212		status = cbc_crypt((char *)sessionkey, (char *)cryptbuf,
213			2*sizeof(des_block), DES_DECRYPT | DES_HW,
214			(char *)&ivec);
215	} else {
216		status = ecb_crypt((char *)sessionkey, (char *)cryptbuf,
217			sizeof(des_block), DES_DECRYPT | DES_HW);
218	}
219	if (DES_FAILED(status)) {
220		debug("decryption failure");
221		return (AUTH_FAILED);	/* system error */
222	}
223
224	/*
225	 * XDR the decrypted timestamp
226	 */
227	ixdr = (long *)cryptbuf;
228	timestamp.tv_sec = IXDR_GET_LONG(ixdr);
229	timestamp.tv_usec = IXDR_GET_LONG(ixdr);
230
231	/*
232 	 * Check for valid credentials and verifiers.
233	 * They could be invalid because the key was flushed
234	 * out of the cache, and so a new session should begin.
235	 * Be sure and send AUTH_REJECTED{CRED, VERF} if this is the case.
236	 */
237	{
238		struct timeval current;
239		int nick;
240		int winverf;
241
242		if (cred->adc_namekind == ADN_FULLNAME) {
243			window = IXDR_GET_U_LONG(ixdr);
244			winverf = IXDR_GET_U_LONG(ixdr);
245			if (winverf != window - 1) {
246				debug("window verifier mismatch");
247				return (AUTH_BADCRED);	/* garbled credential */
248			}
249			sid = cache_spot(sessionkey, cred->adc_fullname.name,
250			    &timestamp);
251			if (sid < 0) {
252				debug("replayed credential");
253				return (AUTH_REJECTEDCRED);	/* replay */
254			}
255			nick = 0;
256		} else {	/* ADN_NICKNAME */
257			window = authdes_cache[sid].window;
258			nick = 1;
259		}
260
261		if ((u_long)timestamp.tv_usec >= USEC_PER_SEC) {
262			debug("invalid usecs");
263			/* cached out (bad key), or garbled verifier */
264			return (nick ? AUTH_REJECTEDVERF : AUTH_BADVERF);
265		}
266		if (nick && BEFORE(&timestamp,
267				   &authdes_cache[sid].laststamp)) {
268			debug("timestamp before last seen");
269			return (AUTH_REJECTEDVERF);	/* replay */
270		}
271		(void)gettimeofday(&current, NULL);
272		current.tv_sec -= window;	/* allow for expiration */
273		if (!BEFORE(&current, &timestamp)) {
274			debug("timestamp expired");
275			/* replay, or garbled credential */
276			return (nick ? AUTH_REJECTEDVERF : AUTH_BADCRED);
277		}
278	}
279
280	/*
281	 * Set up the reply verifier
282	 */
283	verf.adv_nickname = (u_long)sid;
284
285	/*
286	 * xdr the timestamp before encrypting
287	 */
288	ixdr = (long *)cryptbuf;
289	IXDR_PUT_LONG(ixdr, timestamp.tv_sec - 1);
290	IXDR_PUT_LONG(ixdr, timestamp.tv_usec);
291
292	/*
293	 * encrypt the timestamp
294	 */
295	status = ecb_crypt((char *)sessionkey, (char *)cryptbuf,
296	    sizeof(des_block), DES_ENCRYPT | DES_HW);
297	if (DES_FAILED(status)) {
298		debug("encryption failure");
299		return (AUTH_FAILED);	/* system error */
300	}
301	verf.adv_xtimestamp = cryptbuf[0];
302
303	/*
304	 * Serialize the reply verifier, and update rqst
305	 */
306	ixdr = (long *)msg->rm_call.cb_verf.oa_base;
307	*ixdr++ = (long)verf.adv_xtimestamp.key.high;
308	*ixdr++ = (long)verf.adv_xtimestamp.key.low;
309	*ixdr++ = (long)verf.adv_int_u;
310
311	rqst->rq_xprt->xp_verf.oa_flavor = AUTH_DES;
312	rqst->rq_xprt->xp_verf.oa_base = msg->rm_call.cb_verf.oa_base;
313	rqst->rq_xprt->xp_verf.oa_length =
314		(char *)ixdr - msg->rm_call.cb_verf.oa_base;
315
316	/*
317	 * We succeeded, commit the data to the cache now and
318	 * finish cooking the credential.
319	 */
320	entry = &authdes_cache[sid];
321	entry->laststamp = timestamp;
322	cache_ref(sid);
323	if (cred->adc_namekind == ADN_FULLNAME) {
324		cred->adc_fullname.window = window;
325		cred->adc_nickname = (u_long)sid;	/* save nickname */
326		if (entry->rname != NULL) {
327			mem_free(entry->rname, strlen(entry->rname) + 1);
328		}
329		entry->rname = (char *)mem_alloc((u_int)strlen(cred->adc_fullname.name)
330					 + 1);
331		if (entry->rname != NULL) {
332			(void) strcpy(entry->rname, cred->adc_fullname.name);
333		} else {
334			debug("out of memory");
335		}
336		entry->key = *sessionkey;
337		entry->window = window;
338		invalidate(entry->localcred); /* mark any cached cred invalid */
339	} else { /* ADN_NICKNAME */
340		/*
341		 * nicknames are cooked into fullnames
342		 */
343		cred->adc_namekind = ADN_FULLNAME;
344		cred->adc_fullname.name = entry->rname;
345		cred->adc_fullname.key = entry->key;
346		cred->adc_fullname.window = entry->window;
347	}
348	return (AUTH_OK);	/* we made it!*/
349}
350
351
352/*
353 * Initialize the cache
354 */
355static void
356cache_init(void)
357{
358	int i;
359
360	authdes_cache = (struct cache_entry *)
361		mem_alloc(sizeof(struct cache_entry) * AUTHDES_CACHESZ);
362	bzero((char *)authdes_cache,
363		sizeof(struct cache_entry) * AUTHDES_CACHESZ);
364
365	authdes_lru = (short *)mem_alloc(sizeof(short) * AUTHDES_CACHESZ);
366	/*
367	 * Initialize the lru list
368	 */
369	for (i = 0; i < AUTHDES_CACHESZ; i++) {
370		authdes_lru[i] = i;
371	}
372}
373
374
375/*
376 * Find the lru victim
377 */
378static short
379cache_victim(void)
380{
381	return (authdes_lru[AUTHDES_CACHESZ-1]);
382}
383
384/*
385 * Note that sid was referenced
386 */
387static void
388cache_ref(short sid)
389{
390	int i;
391	short curr;
392	short prev;
393
394	prev = authdes_lru[0];
395	authdes_lru[0] = sid;
396	for (i = 1; prev != sid; i++) {
397		curr = authdes_lru[i];
398		authdes_lru[i] = prev;
399		prev = curr;
400	}
401}
402
403
404/*
405 * Find a spot in the cache for a credential containing
406 * the items given.  Return -1 if a replay is detected, otherwise
407 * return the spot in the cache.
408 */
409static short
410cache_spot(des_block *key, char *name, struct timeval *timestamp)
411{
412	struct cache_entry *cp;
413	int i;
414	u_long hi;
415
416	hi = key->key.high;
417	for (cp = authdes_cache, i = 0; i < AUTHDES_CACHESZ; i++, cp++) {
418		if (cp->key.key.high == hi &&
419		    cp->key.key.low == key->key.low &&
420		    cp->rname != NULL &&
421		    bcmp(cp->rname, name, strlen(name) + 1) == 0) {
422			if (BEFORE(timestamp, &cp->laststamp)) {
423				svcauthdes_stats.ncachereplays++;
424				return (-1); /* replay */
425			}
426			svcauthdes_stats.ncachehits++;
427			return (i);	/* refresh */
428		}
429	}
430	svcauthdes_stats.ncachemisses++;
431	return (cache_victim());	/* new credential */
432}
433
434
435#if (defined(sun) || defined(vax) || defined(__FreeBSD__))
436/*
437 * Local credential handling stuff.
438 * NOTE: bsd unix dependent.
439 * Other operating systems should put something else here.
440 */
441#define UNKNOWN 	-2	/* grouplen, if cached cred is unknown user */
442#define INVALID		-1 	/* grouplen, if cache entry is invalid */
443
444struct bsdcred {
445	uid_t uid;		/* cached uid */
446	gid_t gid;		/* cached gid */
447	int grouplen;	/* length of cached groups */
448	gid_t groups[NGRPS];	/* cached groups */
449};
450
451/*
452 * Map a des credential into a unix cred.
453 * We cache the credential here so the application does
454 * not have to make an rpc call every time to interpret
455 * the credential.
456 */
457int
458authdes_getucred(struct authdes_cred *adc, uid_t *uid, gid_t *gid,
459    int *grouplen, gid_t *groups)
460{
461	unsigned sid;
462	int i;
463	uid_t i_uid;
464	gid_t i_gid;
465	int i_grouplen;
466	struct bsdcred *cred;
467
468	sid = adc->adc_nickname;
469	if (sid >= AUTHDES_CACHESZ) {
470		debug("invalid nickname");
471		return (0);
472	}
473	cred = (struct bsdcred *)authdes_cache[sid].localcred;
474	if (cred == NULL) {
475		cred = (struct bsdcred *)mem_alloc(sizeof(struct bsdcred));
476		authdes_cache[sid].localcred = (char *)cred;
477		cred->grouplen = INVALID;
478	}
479	if (cred->grouplen == INVALID) {
480		/*
481		 * not in cache: lookup
482		 */
483		if (!netname2user(adc->adc_fullname.name, &i_uid, &i_gid,
484			&i_grouplen, groups))
485		{
486			debug("unknown netname");
487			cred->grouplen = UNKNOWN;	/* mark as lookup up, but not found */
488			return (0);
489		}
490		debug("missed ucred cache");
491		*uid = cred->uid = i_uid;
492		*gid = cred->gid = i_gid;
493		*grouplen = cred->grouplen = i_grouplen;
494		for (i = i_grouplen - 1; i >= 0; i--) {
495			cred->groups[i] = groups[i]; /* int to short */
496		}
497		return (1);
498	} else if (cred->grouplen == UNKNOWN) {
499		/*
500		 * Already lookup up, but no match found
501		 */
502		return (0);
503	}
504
505	/*
506	 * cached credentials
507	 */
508	*uid = cred->uid;
509	*gid = cred->gid;
510	*grouplen = cred->grouplen;
511	for (i = cred->grouplen - 1; i >= 0; i--) {
512		groups[i] = cred->groups[i];	/* short to int */
513	}
514	return (1);
515}
516
517static void
518invalidate(char *cred)
519{
520	if (cred == NULL) {
521		return;
522	}
523	((struct bsdcred *)cred)->grouplen = INVALID;
524}
525#endif
526
527