svc_auth_des.c revision 26221
1
2/*
3 * Copyright (c) 1988 by Sun Microsystems, Inc.
4 */
5
6/*
7 * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
8 * unrestricted use provided that this legend is included on all tape
9 * media and as a part of the software program in whole or part.  Users
10 * may copy or modify Sun RPC without charge, but are not authorized
11 * to license or distribute it to anyone else except as part of a product or
12 * program developed by the user.
13 *
14 * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
15 * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
17 *
18 * Sun RPC is provided with no support and without any obligation on the
19 * part of Sun Microsystems, Inc. to assist in its use, correction,
20 * modification or enhancement.
21 *
22 * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
23 * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
24 * OR ANY PART THEREOF.
25 *
26 * In no event will Sun Microsystems, Inc. be liable for any lost revenue
27 * or profits or other special, indirect and consequential damages, even if
28 * Sun has been advised of the possibility of such damages.
29 *
30 * Sun Microsystems, Inc.
31 * 2550 Garcia Avenue
32 * Mountain View, California  94043
33 */
34
35/*
36 * svcauth_des.c, server-side des authentication
37 *
38 * We insure for the service the following:
39 * (1) The timestamp microseconds do not exceed 1 million.
40 * (2) The timestamp plus the window is less than the current time.
41 * (3) The timestamp is not less than the one previously
42 *     seen in the current session.
43 *
44 * It is up to the server to determine if the window size is
45 * too small .
46 *
47 */
48
49#include <string.h>
50#include <stdlib.h>
51#include <unistd.h>
52#include <rpc/des_crypt.h>
53#include <sys/param.h>
54#include <netinet/in.h>
55#include <rpc/types.h>
56#include <rpc/xdr.h>
57#include <rpc/auth.h>
58#include <rpc/auth_des.h>
59#include <rpc/svc.h>
60#include <rpc/rpc_msg.h>
61#include <rpc/svc_auth.h>
62
63#if defined(LIBC_SCCS) && !defined(lint)
64/* from: static char sccsid[] = 	"@(#)svcauth_des.c	2.3 89/07/11 4.0 RPCSRC; from 1.15 88/02/08 SMI"; */
65static const char rcsid[] = "$Id$";
66#endif
67
68#define debug(msg)	 printf("svcauth_des: %s\n", msg)
69
70#define USEC_PER_SEC ((u_long) 1000000L)
71#define BEFORE(t1, t2) timercmp(t1, t2, <)
72
73/*
74 * LRU cache of conversation keys and some other useful items.
75 */
76#define AUTHDES_CACHESZ 64
77struct cache_entry {
78	des_block key;		/* conversation key */
79	char *rname;		/* client's name */
80	u_int window;		/* credential lifetime window */
81	struct timeval laststamp;	/* detect replays of creds */
82	char *localcred;	/* generic local credential */
83};
84static struct cache_entry *authdes_cache/* [AUTHDES_CACHESZ] */;
85static short *authdes_lru/* [AUTHDES_CACHESZ] */;
86
87static void cache_init();	/* initialize the cache */
88static short cache_spot();	/* find an entry in the cache */
89static void cache_ref(/*short sid*/);	/* note that sid was ref'd */
90
91static void invalidate();	/* invalidate entry in cache */
92
93/*
94 * cache statistics
95 */
96static struct {
97	u_long ncachehits;	/* times cache hit, and is not replay */
98	u_long ncachereplays;	/* times cache hit, and is replay */
99	u_long ncachemisses;	/* times cache missed */
100} svcauthdes_stats;
101
102/*
103 * Service side authenticator for AUTH_DES
104 */
105enum auth_stat
106_svcauth_des(rqst, msg)
107	register struct svc_req *rqst;
108	register struct rpc_msg *msg;
109{
110
111	register long *ixdr;
112	des_block cryptbuf[2];
113	register struct authdes_cred *cred;
114	struct authdes_verf verf;
115	int status;
116	register struct cache_entry *entry;
117	short sid = 0;
118	des_block *sessionkey;
119	des_block ivec;
120	u_int window;
121	struct timeval timestamp;
122	u_long namelen;
123	struct area {
124		struct authdes_cred area_cred;
125		char area_netname[MAXNETNAMELEN+1];
126	} *area;
127
128	if (authdes_cache == NULL) {
129		cache_init();
130	}
131
132	area = (struct area *)rqst->rq_clntcred;
133	cred = (struct authdes_cred *)&area->area_cred;
134
135	/*
136	 * Get the credential
137	 */
138	ixdr = (long *)msg->rm_call.cb_cred.oa_base;
139	cred->adc_namekind = IXDR_GET_ENUM(ixdr, enum authdes_namekind);
140	switch (cred->adc_namekind) {
141	case ADN_FULLNAME:
142		namelen = IXDR_GET_U_LONG(ixdr);
143		if (namelen > MAXNETNAMELEN) {
144			return (AUTH_BADCRED);
145		}
146		cred->adc_fullname.name = area->area_netname;
147		bcopy((char *)ixdr, cred->adc_fullname.name,
148			(u_int)namelen);
149		cred->adc_fullname.name[namelen] = 0;
150		ixdr += (RNDUP(namelen) / BYTES_PER_XDR_UNIT);
151		cred->adc_fullname.key.key.high = (u_long)*ixdr++;
152		cred->adc_fullname.key.key.low = (u_long)*ixdr++;
153		cred->adc_fullname.window = (u_long)*ixdr++;
154		break;
155	case ADN_NICKNAME:
156		cred->adc_nickname = (u_long)*ixdr++;
157		break;
158	default:
159		return (AUTH_BADCRED);
160	}
161
162	/*
163	 * Get the verifier
164	 */
165	ixdr = (long *)msg->rm_call.cb_verf.oa_base;
166	verf.adv_xtimestamp.key.high = (u_long)*ixdr++;
167	verf.adv_xtimestamp.key.low =  (u_long)*ixdr++;
168	verf.adv_int_u = (u_long)*ixdr++;
169
170
171	/*
172	 * Get the conversation key
173 	 */
174	if (cred->adc_namekind == ADN_FULLNAME) {
175		netobj pkey;
176		char pkey_data[1024];
177
178		sessionkey = &cred->adc_fullname.key;
179		if (! getpublickey(cred->adc_fullname.name, pkey_data)) {
180			debug("getpublickey");
181			return(AUTH_BADCRED);
182		}
183		pkey.n_bytes = pkey_data;
184		pkey.n_len = strlen(pkey_data) + 1;
185		if (key_decryptsession_pk(cred->adc_fullname.name, &pkey,
186				       sessionkey) < 0) {
187			debug("decryptsessionkey");
188			return (AUTH_BADCRED); /* key not found */
189		}
190	} else { /* ADN_NICKNAME */
191		sid = (short)cred->adc_nickname;
192		if (sid >= AUTHDES_CACHESZ) {
193			debug("bad nickname");
194			return (AUTH_BADCRED);	/* garbled credential */
195		}
196		sessionkey = &authdes_cache[sid].key;
197	}
198
199
200	/*
201	 * Decrypt the timestamp
202	 */
203	cryptbuf[0] = verf.adv_xtimestamp;
204	if (cred->adc_namekind == ADN_FULLNAME) {
205		cryptbuf[1].key.high = cred->adc_fullname.window;
206		cryptbuf[1].key.low = verf.adv_winverf;
207		ivec.key.high = ivec.key.low = 0;
208		status = cbc_crypt((char *)sessionkey, (char *)cryptbuf,
209			2*sizeof(des_block), DES_DECRYPT | DES_HW,
210			(char *)&ivec);
211	} else {
212		status = ecb_crypt((char *)sessionkey, (char *)cryptbuf,
213			sizeof(des_block), DES_DECRYPT | DES_HW);
214	}
215	if (DES_FAILED(status)) {
216		debug("decryption failure");
217		return (AUTH_FAILED);	/* system error */
218	}
219
220	/*
221	 * XDR the decrypted timestamp
222	 */
223	ixdr = (long *)cryptbuf;
224	timestamp.tv_sec = IXDR_GET_LONG(ixdr);
225	timestamp.tv_usec = IXDR_GET_LONG(ixdr);
226
227	/*
228 	 * Check for valid credentials and verifiers.
229	 * They could be invalid because the key was flushed
230	 * out of the cache, and so a new session should begin.
231	 * Be sure and send AUTH_REJECTED{CRED, VERF} if this is the case.
232	 */
233	{
234		struct timeval current;
235		int nick;
236		int winverf;
237
238		if (cred->adc_namekind == ADN_FULLNAME) {
239			window = IXDR_GET_U_LONG(ixdr);
240			winverf = IXDR_GET_U_LONG(ixdr);
241			if (winverf != window - 1) {
242				debug("window verifier mismatch");
243				return (AUTH_BADCRED);	/* garbled credential */
244			}
245			sid = cache_spot(sessionkey, cred->adc_fullname.name,
246			    &timestamp);
247			if (sid < 0) {
248				debug("replayed credential");
249				return (AUTH_REJECTEDCRED);	/* replay */
250			}
251			nick = 0;
252		} else {	/* ADN_NICKNAME */
253			window = authdes_cache[sid].window;
254			nick = 1;
255		}
256
257		if ((u_long)timestamp.tv_usec >= USEC_PER_SEC) {
258			debug("invalid usecs");
259			/* cached out (bad key), or garbled verifier */
260			return (nick ? AUTH_REJECTEDVERF : AUTH_BADVERF);
261		}
262		if (nick && BEFORE(&timestamp,
263				   &authdes_cache[sid].laststamp)) {
264			debug("timestamp before last seen");
265			return (AUTH_REJECTEDVERF);	/* replay */
266		}
267		(void) gettimeofday(&current, (struct timezone *)NULL);
268		current.tv_sec -= window;	/* allow for expiration */
269		if (!BEFORE(&current, &timestamp)) {
270			debug("timestamp expired");
271			/* replay, or garbled credential */
272			return (nick ? AUTH_REJECTEDVERF : AUTH_BADCRED);
273		}
274	}
275
276	/*
277	 * Set up the reply verifier
278	 */
279	verf.adv_nickname = (u_long)sid;
280
281	/*
282	 * xdr the timestamp before encrypting
283	 */
284	ixdr = (long *)cryptbuf;
285	IXDR_PUT_LONG(ixdr, timestamp.tv_sec - 1);
286	IXDR_PUT_LONG(ixdr, timestamp.tv_usec);
287
288	/*
289	 * encrypt the timestamp
290	 */
291	status = ecb_crypt((char *)sessionkey, (char *)cryptbuf,
292	    sizeof(des_block), DES_ENCRYPT | DES_HW);
293	if (DES_FAILED(status)) {
294		debug("encryption failure");
295		return (AUTH_FAILED);	/* system error */
296	}
297	verf.adv_xtimestamp = cryptbuf[0];
298
299	/*
300	 * Serialize the reply verifier, and update rqst
301	 */
302	ixdr = (long *)msg->rm_call.cb_verf.oa_base;
303	*ixdr++ = (long)verf.adv_xtimestamp.key.high;
304	*ixdr++ = (long)verf.adv_xtimestamp.key.low;
305	*ixdr++ = (long)verf.adv_int_u;
306
307	rqst->rq_xprt->xp_verf.oa_flavor = AUTH_DES;
308	rqst->rq_xprt->xp_verf.oa_base = msg->rm_call.cb_verf.oa_base;
309	rqst->rq_xprt->xp_verf.oa_length =
310		(char *)ixdr - msg->rm_call.cb_verf.oa_base;
311
312	/*
313	 * We succeeded, commit the data to the cache now and
314	 * finish cooking the credential.
315	 */
316	entry = &authdes_cache[sid];
317	entry->laststamp = timestamp;
318	cache_ref(sid);
319	if (cred->adc_namekind == ADN_FULLNAME) {
320		cred->adc_fullname.window = window;
321		cred->adc_nickname = (u_long)sid;	/* save nickname */
322		if (entry->rname != NULL) {
323			mem_free(entry->rname, strlen(entry->rname) + 1);
324		}
325		entry->rname = (char *)mem_alloc((u_int)strlen(cred->adc_fullname.name)
326					 + 1);
327		if (entry->rname != NULL) {
328			(void) strcpy(entry->rname, cred->adc_fullname.name);
329		} else {
330			debug("out of memory");
331		}
332		entry->key = *sessionkey;
333		entry->window = window;
334		invalidate(entry->localcred); /* mark any cached cred invalid */
335	} else { /* ADN_NICKNAME */
336		/*
337		 * nicknames are cooked into fullnames
338		 */
339		cred->adc_namekind = ADN_FULLNAME;
340		cred->adc_fullname.name = entry->rname;
341		cred->adc_fullname.key = entry->key;
342		cred->adc_fullname.window = entry->window;
343	}
344	return (AUTH_OK);	/* we made it!*/
345}
346
347
348/*
349 * Initialize the cache
350 */
351static void
352cache_init()
353{
354	register int i;
355
356	authdes_cache = (struct cache_entry *)
357		mem_alloc(sizeof(struct cache_entry) * AUTHDES_CACHESZ);
358	bzero((char *)authdes_cache,
359		sizeof(struct cache_entry) * AUTHDES_CACHESZ);
360
361	authdes_lru = (short *)mem_alloc(sizeof(short) * AUTHDES_CACHESZ);
362	/*
363	 * Initialize the lru list
364	 */
365	for (i = 0; i < AUTHDES_CACHESZ; i++) {
366		authdes_lru[i] = i;
367	}
368}
369
370
371/*
372 * Find the lru victim
373 */
374static short
375cache_victim()
376{
377	return (authdes_lru[AUTHDES_CACHESZ-1]);
378}
379
380/*
381 * Note that sid was referenced
382 */
383static void
384cache_ref(sid)
385	register short sid;
386{
387	register int i;
388	register short curr;
389	register short prev;
390
391	prev = authdes_lru[0];
392	authdes_lru[0] = sid;
393	for (i = 1; prev != sid; i++) {
394		curr = authdes_lru[i];
395		authdes_lru[i] = prev;
396		prev = curr;
397	}
398}
399
400
401/*
402 * Find a spot in the cache for a credential containing
403 * the items given.  Return -1 if a replay is detected, otherwise
404 * return the spot in the cache.
405 */
406static short
407cache_spot(key, name, timestamp)
408	register des_block *key;
409	char *name;
410	struct timeval *timestamp;
411{
412	register struct cache_entry *cp;
413	register int i;
414	register 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	short uid;		/* cached uid */
446	short gid;		/* cached gid */
447	short grouplen;	/* length of cached groups */
448	short groups[NGROUPS];	/* 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(adc, uid, gid, grouplen, groups)
459	struct authdes_cred *adc;
460	uid_t *uid;
461	gid_t *gid;
462	int *grouplen;
463	register gid_t *groups;
464{
465	unsigned sid;
466	register int i;
467	uid_t i_uid;
468	gid_t i_gid;
469	int i_grouplen;
470	struct bsdcred *cred;
471
472	sid = adc->adc_nickname;
473	if (sid >= AUTHDES_CACHESZ) {
474		debug("invalid nickname");
475		return (0);
476	}
477	cred = (struct bsdcred *)authdes_cache[sid].localcred;
478	if (cred == NULL) {
479		cred = (struct bsdcred *)mem_alloc(sizeof(struct bsdcred));
480		authdes_cache[sid].localcred = (char *)cred;
481		cred->grouplen = INVALID;
482	}
483	if (cred->grouplen == INVALID) {
484		/*
485		 * not in cache: lookup
486		 */
487		if (!netname2user(adc->adc_fullname.name, &i_uid, &i_gid,
488			&i_grouplen, groups))
489		{
490			debug("unknown netname");
491			cred->grouplen = UNKNOWN;	/* mark as lookup up, but not found */
492			return (0);
493		}
494		debug("missed ucred cache");
495		*uid = cred->uid = i_uid;
496		*gid = cred->gid = i_gid;
497		*grouplen = cred->grouplen = i_grouplen;
498		for (i = i_grouplen - 1; i >= 0; i--) {
499			cred->groups[i] = groups[i]; /* int to short */
500		}
501		return (1);
502	} else if (cred->grouplen == UNKNOWN) {
503		/*
504		 * Already lookup up, but no match found
505		 */
506		return (0);
507	}
508
509	/*
510	 * cached credentials
511	 */
512	*uid = cred->uid;
513	*gid = cred->gid;
514	*grouplen = cred->grouplen;
515	for (i = cred->grouplen - 1; i >= 0; i--) {
516		groups[i] = cred->groups[i];	/* short to int */
517	}
518	return (1);
519}
520
521static void
522invalidate(cred)
523	char *cred;
524{
525	if (cred == NULL) {
526		return;
527	}
528	((struct bsdcred *)cred)->grouplen = INVALID;
529}
530#endif
531
532