svcauthdes.c revision 4321:a8930ec16e52
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26/*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
27/*	  All Rights Reserved  	*/
28
29/*
30 * Portions of this source code were derived from Berkeley 4.3 BSD
31 * under license from the Regents of the University of California.
32 */
33
34#pragma ident	"%Z%%M%	%I%	%E% SMI"
35
36/*
37 * svcauth_des.c, server-side des authentication
38 *
39 * We insure for the service the following:
40 * (1) The timestamp microseconds do not exceed 1 million.
41 * (2) The timestamp plus the window is less than the current time.
42 * (3) The timestamp is not less than the one previously
43 *	seen in the current session.
44 *
45 * It is up to the server to determine if the window size is
46 * too small.
47 */
48
49#include <sys/types.h>
50#include <sys/time.h>
51#include <sys/systm.h>
52#include <sys/param.h>
53#include <sys/stream.h>
54#include <sys/stropts.h>
55#include <sys/strsubr.h>
56#include <sys/tiuser.h>
57#include <sys/tihdr.h>
58#include <sys/t_kuser.h>
59#include <sys/t_lock.h>
60#include <sys/debug.h>
61#include <sys/kmem.h>
62#include <sys/time.h>
63#include <sys/cmn_err.h>
64
65#include <rpc/types.h>
66#include <rpc/xdr.h>
67#include <rpc/auth.h>
68#include <rpc/auth_des.h>
69#include <rpc/rpc_msg.h>
70#include <rpc/svc.h>
71#include <rpc/svc_auth.h>
72#include <rpc/clnt.h>
73#include <rpc/des_crypt.h>
74
75#define	USEC_PER_SEC 1000000
76#define	BEFORE(t1, t2) timercmp(t1, t2, < /* COMMENT HERE TO DEFEAT CSTYLE */)
77
78/*
79 * Cache of conversation keys and some other useful items.
80 * The hash table size is controled via authdes_cachesz variable.
81 * The authdes_cachesz has to be the power of 2.
82 */
83#define	AUTHDES_CACHE_TABLE_SZ 1024
84static int authdes_cachesz = AUTHDES_CACHE_TABLE_SZ;
85#define	HASH(key) ((key) & (authdes_cachesz - 1))
86
87/* low water mark for the number of cache entries */
88static int low_cache_entries = 128;
89
90struct authdes_cache_entry {
91	uint32_t nickname;		/* nick name id */
92	uint32_t window;		/* credential lifetime window */
93	des_block key;			/* conversation key */
94	time_t	ref_time;		/* time referenced previously */
95	char *rname;			/* client's name */
96	caddr_t localcred;		/* generic local credential */
97	struct authdes_cache_entry *prev, *next;  /* hash table linked list */
98	struct authdes_cache_entry *lru_prev, *lru_next; /* LRU linked list */
99	kmutex_t lock;			/* cache entry lock */
100};
101static struct authdes_cache_entry **authdes_cache; /* [authdes_cachesz] */
102static struct authdes_cache_entry *lru_first = NULL;
103static struct authdes_cache_entry *lru_last = NULL;
104static kmutex_t authdes_lock;		/* cache table lock */
105
106static struct kmem_cache *authdes_cache_handle;
107static uint32_t	Nickname = 0;
108
109static struct authdes_cache_entry *authdes_cache_new(char *,
110					des_block *, uint32_t);
111static struct authdes_cache_entry *authdes_cache_get(uint32_t);
112static void authdes_cache_reclaim(void *);
113static void sweep_cache();
114
115/*
116 * After 12 hours, check and delete cache entries that have been
117 * idled for more than 10 hours.
118 */
119static time_t authdes_sweep_interval = 12*60*60;
120static time_t authdes_cache_time = 10*60*60;
121static time_t authdes_last_swept = 0;
122
123/*
124 * cache statistics
125 */
126static int authdes_ncache = 0; /* number of current cached entries */
127static int authdes_ncachehits = 0; /* #times cache hit */
128static int authdes_ncachemisses = 0; /* #times cache missed */
129
130#define	NOT_DEAD(ptr)   ASSERT((((intptr_t)(ptr)) != 0xdeadbeef))
131#define	IS_ALIGNED(ptr) ASSERT((((intptr_t)(ptr)) & 3) == 0)
132
133/*
134 * Service side authenticator for AUTH_DES
135 */
136enum auth_stat
137_svcauth_des(struct svc_req *rqst, struct rpc_msg *msg)
138{
139	int32_t *ixdr;
140	des_block cryptbuf[2];
141	struct authdes_cred *cred;
142	struct authdes_verf verf;
143	int status;
144	des_block *sessionkey;
145	des_block ivec;
146	uint32_t window, winverf, namelen;
147	bool_t nick;
148	struct timeval timestamp, current_time;
149	struct authdes_cache_entry *nick_entry;
150	struct area {
151		struct authdes_cred area_cred;
152		char area_netname[MAXNETNAMELEN+1];
153	} *area;
154	timestruc_t now;
155
156	mutex_enter(&authdes_lock);
157	if (authdes_cache == NULL) {
158		authdes_cache = kmem_zalloc(
159			sizeof (struct authdes_cache_entry *) * authdes_cachesz,
160			KM_SLEEP);
161	}
162	mutex_exit(&authdes_lock);
163
164	/* LINTED pointer alignment */
165	area = (struct area *)rqst->rq_clntcred;
166	cred = (struct authdes_cred *)&area->area_cred;
167
168	/*
169	 * Get the credential
170	 */
171	/* LINTED pointer alignment */
172	ixdr = (int32_t *)msg->rm_call.cb_cred.oa_base;
173	cred->adc_namekind = IXDR_GET_ENUM(ixdr, enum authdes_namekind);
174	switch (cred->adc_namekind) {
175	case ADN_FULLNAME:
176		namelen = IXDR_GET_U_INT32(ixdr);
177		if (namelen > MAXNETNAMELEN)
178			return (AUTH_BADCRED);
179		cred->adc_fullname.name = area->area_netname;
180		bcopy(ixdr, cred->adc_fullname.name, namelen);
181		cred->adc_fullname.name[namelen] = 0;
182		ixdr += (RNDUP(namelen) / BYTES_PER_XDR_UNIT);
183		cred->adc_fullname.key.key.high = (uint32_t)*ixdr++;
184		cred->adc_fullname.key.key.low = (uint32_t)*ixdr++;
185		cred->adc_fullname.window = (uint32_t)*ixdr++;
186		nick = FALSE;
187		break;
188	case ADN_NICKNAME:
189		cred->adc_nickname = (uint32_t)*ixdr++;
190		nick = TRUE;
191		break;
192	default:
193		return (AUTH_BADCRED);
194	}
195
196	/*
197	 * Get the verifier
198	 */
199	/* LINTED pointer alignment */
200	ixdr = (int32_t *)msg->rm_call.cb_verf.oa_base;
201	verf.adv_xtimestamp.key.high = (uint32_t)*ixdr++;
202	verf.adv_xtimestamp.key.low =  (uint32_t)*ixdr++;
203	verf.adv_int_u = (uint32_t)*ixdr++;
204
205
206	/*
207	 * Get the conversation key
208	 */
209	if (!nick) { /* ADN_FULLNAME */
210		sessionkey = &cred->adc_fullname.key;
211		if (key_decryptsession(cred->adc_fullname.name, sessionkey) !=
212		    RPC_SUCCESS) {
213		    return (AUTH_BADCRED); /* key not found */
214		}
215	} else { /* ADN_NICKNAME */
216		mutex_enter(&authdes_lock);
217		if (!(nick_entry = authdes_cache_get(cred->adc_nickname))) {
218		    RPCLOG(1, "_svcauth_des: nickname %d not in the cache\n",
219						cred->adc_nickname);
220		    mutex_exit(&authdes_lock);
221		    return (AUTH_BADCRED);	/* need refresh */
222		}
223		sessionkey = &nick_entry->key;
224		mutex_enter(&nick_entry->lock);
225		mutex_exit(&authdes_lock);
226	}
227
228	/*
229	 * Decrypt the timestamp
230	 */
231	cryptbuf[0] = verf.adv_xtimestamp;
232	if (!nick) { /* ADN_FULLNAME */
233		cryptbuf[1].key.high = cred->adc_fullname.window;
234		cryptbuf[1].key.low = verf.adv_winverf;
235		ivec.key.high = ivec.key.low = 0;
236		status = cbc_crypt((char *)sessionkey, (char *)cryptbuf,
237		    2 * sizeof (des_block), DES_DECRYPT, (char *)&ivec);
238	} else { /* ADN_NICKNAME */
239		status = ecb_crypt((char *)sessionkey, (char *)cryptbuf,
240		    sizeof (des_block), DES_DECRYPT);
241	}
242	if (DES_FAILED(status)) {
243		RPCLOG0(1, "_svcauth_des: decryption failure\n");
244		if (nick) {
245			mutex_exit(&nick_entry->lock);
246		}
247		return (AUTH_FAILED);	/* system error */
248	}
249
250	/*
251	 * XDR the decrypted timestamp
252	 */
253	ixdr = (int32_t *)cryptbuf;
254	timestamp.tv_sec = IXDR_GET_INT32(ixdr);
255	timestamp.tv_usec = IXDR_GET_INT32(ixdr);
256
257	/*
258	 * Check for valid credentials and verifiers.
259	 * They could be invalid because the key was flushed
260	 * out of the cache, and so a new session should begin.
261	 * Be sure and send AUTH_REJECTED{CRED, VERF} if this is the case.
262	 */
263	if (!nick) { /* ADN_FULLNAME */
264		window = IXDR_GET_U_INT32(ixdr);
265		winverf = IXDR_GET_U_INT32(ixdr);
266		if (winverf != window - 1) {
267			RPCLOG(1, "_svcauth_des: window verifier mismatch %d\n",
268				winverf);
269			return (AUTH_BADCRED);	/* garbled credential */
270		}
271	} else { /* ADN_NICKNAME */
272		window = nick_entry->window;
273	}
274
275	if (timestamp.tv_usec >= USEC_PER_SEC) {
276		RPCLOG(1, "_svcauth_des: invalid usecs %ld\n",
277					timestamp.tv_usec);
278		/* cached out (bad key), or garbled verifier */
279		if (nick) {
280			mutex_exit(&nick_entry->lock);
281		}
282		return (nick ? AUTH_REJECTEDVERF : AUTH_BADVERF);
283	}
284
285	gethrestime(&now);
286	current_time.tv_sec = now.tv_sec;
287	current_time.tv_usec = now.tv_nsec / 1000;
288
289	current_time.tv_sec -= window;	/* allow for expiration */
290	if (!BEFORE(&current_time, &timestamp)) {
291		RPCLOG0(1, "_svcauth_des: timestamp expired\n");
292		/* replay, or garbled credential */
293		if (nick) {
294			mutex_exit(&nick_entry->lock);
295		}
296		return (nick ? AUTH_REJECTEDVERF : AUTH_BADCRED);
297	}
298
299	/*
300	 * xdr the timestamp before encrypting
301	 */
302	ixdr = (int32_t *)cryptbuf;
303	IXDR_PUT_INT32(ixdr, timestamp.tv_sec - 1);
304	IXDR_PUT_INT32(ixdr, timestamp.tv_usec);
305
306	/*
307	 * encrypt the timestamp
308	 */
309	status = ecb_crypt((char *)sessionkey, (char *)cryptbuf,
310	    sizeof (des_block), DES_ENCRYPT);
311	if (DES_FAILED(status)) {
312		RPCLOG0(1, "_svcauth_des: encryption failure\n");
313		if (nick) {
314			mutex_exit(&nick_entry->lock);
315		}
316		return (AUTH_FAILED);	/* system error */
317	}
318	verf.adv_xtimestamp = cryptbuf[0];
319
320	/*
321	 * If a ADN_FULLNAME, create a new nickname cache entry.
322	 */
323	if (!nick) {
324	    mutex_enter(&authdes_lock);
325	    if (!(nick_entry = authdes_cache_new(cred->adc_fullname.name,
326					sessionkey, window))) {
327		RPCLOG0(1, "_svcauth_des: can not create new cache entry\n");
328		mutex_exit(&authdes_lock);
329		return (AUTH_FAILED);
330	    }
331	    mutex_enter(&nick_entry->lock);
332	    mutex_exit(&authdes_lock);
333	}
334	verf.adv_nickname = nick_entry->nickname;
335
336	/*
337	 * Serialize the reply verifier, and update rqst
338	 */
339	/* LINTED pointer alignment */
340	ixdr = (int32_t *)msg->rm_call.cb_verf.oa_base;
341	*ixdr++ = (int32_t)verf.adv_xtimestamp.key.high;
342	*ixdr++ = (int32_t)verf.adv_xtimestamp.key.low;
343	*ixdr++ = (int32_t)verf.adv_int_u;
344
345	rqst->rq_xprt->xp_verf.oa_flavor = AUTH_DES;
346	rqst->rq_xprt->xp_verf.oa_base = msg->rm_call.cb_verf.oa_base;
347	rqst->rq_xprt->xp_verf.oa_length =
348	    (uint_t)((char *)ixdr - msg->rm_call.cb_verf.oa_base);
349	if (rqst->rq_xprt->xp_verf.oa_length > MAX_AUTH_BYTES) {
350		RPCLOG0(1, "_svcauth_des: invalid oa length\n");
351		mutex_exit(&nick_entry->lock);
352		return (AUTH_BADVERF);
353	}
354
355	/*
356	 * We succeeded and finish cooking the credential.
357	 * nicknames are cooked into fullnames
358	 */
359	if (!nick) {
360		cred->adc_nickname = nick_entry->nickname;
361		cred->adc_fullname.window = window;
362	} else { /* ADN_NICKNAME */
363		cred->adc_namekind = ADN_FULLNAME;
364		cred->adc_fullname.name = nick_entry->rname;
365		cred->adc_fullname.key = nick_entry->key;
366		cred->adc_fullname.window = nick_entry->window;
367	}
368	mutex_exit(&nick_entry->lock);
369
370	/*
371	 * For every authdes_sweep_interval, delete cache entries that have been
372	 * idled for authdes_cache_time.
373	 */
374	mutex_enter(&authdes_lock);
375	if ((gethrestime_sec() - authdes_last_swept) > authdes_sweep_interval)
376		sweep_cache();
377
378	mutex_exit(&authdes_lock);
379
380	return (AUTH_OK);	/* we made it! */
381}
382
383/*
384 * Initialization upon loading the rpcsec module.
385 */
386void
387svcauthdes_init(void)
388{
389	mutex_init(&authdes_lock, NULL, MUTEX_DEFAULT, NULL);
390	/*
391	 * Allocate des cache handle
392	 */
393	authdes_cache_handle = kmem_cache_create("authdes_cache_handle",
394			sizeof (struct authdes_cache_entry), 0, NULL, NULL,
395			authdes_cache_reclaim, NULL, NULL, 0);
396}
397
398/*
399 * Final actions upon exiting the rpcsec module.
400 */
401void
402svcauthdes_fini(void)
403{
404	mutex_destroy(&authdes_lock);
405	kmem_cache_destroy(authdes_cache_handle);
406}
407
408/*
409 * Local credential handling stuff.
410 * NOTE: bsd unix dependent.
411 * Other operating systems should put something else here.
412 */
413
414struct bsdcred {
415	uid_t uid;		/* cached uid */
416	gid_t gid;		/* cached gid */
417	short valid;		/* valid creds */
418	short grouplen;	/* length of cached groups */
419	gid_t groups[NGROUPS_UMAX];	/* cached groups */
420};
421
422/*
423 * Map a des credential into a unix cred.
424 * We cache the credential here so the application does
425 * not have to make an rpc call every time to interpret
426 * the credential.
427 */
428int
429kauthdes_getucred(const struct authdes_cred *adc, cred_t *cr)
430{
431	uid_t i_uid;
432	gid_t i_gid;
433	int i_grouplen;
434	struct bsdcred *cred;
435	struct authdes_cache_entry *nickentry;
436
437	mutex_enter(&authdes_lock);
438	if (!(nickentry = authdes_cache_get(adc->adc_nickname))) {
439		RPCLOG0(1, "authdes_getucred:  invalid nickname\n");
440		mutex_exit(&authdes_lock);
441		return (0);
442	}
443
444	mutex_enter(&nickentry->lock);
445	mutex_exit(&authdes_lock);
446	/* LINTED pointer alignment */
447	cred = (struct bsdcred *)nickentry->localcred;
448	if (!cred->valid) {
449		/*
450		 * not in cache: lookup
451		 */
452		if (netname2user(adc->adc_fullname.name, &i_uid, &i_gid,
453		    &i_grouplen, &cred->groups[0]) != RPC_SUCCESS) {
454			/*
455			 * Probably a host principal, since at this
456			 * point we have valid keys. Note that later
457			 * if the principal is not in the root list
458			 * for NFS, we will be mapped to that exported
459			 * file system's anonymous user, typically
460			 * NOBODY. keyserv KEY_GETCRED will fail for a
461			 * root-netnames so we assume root here.
462			 * Currently NFS is the only caller of this
463			 * routine. If other RPC services call this
464			 * routine, it is up to that service to
465			 * differentiate between local and remote
466			 * roots.
467			 */
468			i_uid = 0;
469			i_gid = 0;
470			i_grouplen = 0;
471		}
472		RPCLOG0(2, "authdes_getucred:  missed ucred cache\n");
473		cred->uid = i_uid;
474		cred->gid = i_gid;
475		cred->grouplen = (short)i_grouplen;
476		cred->valid = 1;
477	}
478
479	/*
480	 * cached credentials
481	 */
482	if (crsetugid(cr, cred->uid, cred->gid) != 0 ||
483	    crsetgroups(cr, cred->grouplen, &cred->groups[0]) != 0) {
484		mutex_exit(&nickentry->lock);
485		return (0);
486	}
487	mutex_exit(&nickentry->lock);
488	return (1);
489}
490
491/*
492 * Create a new cache_entry and put it in authdes_cache table.
493 * Caller should have already locked the authdes_cache table.
494 */
495struct authdes_cache_entry *
496authdes_cache_new(char *fullname, des_block *sessionkey, uint32_t window) {
497
498	struct authdes_cache_entry *new, *head;
499	struct bsdcred *ucred;
500	int index;
501
502	if (!(new = kmem_cache_alloc(authdes_cache_handle, KM_SLEEP))) {
503		return (NULL);
504	}
505
506	if (!(new->rname = kmem_alloc(strlen(fullname) + 1, KM_NOSLEEP))) {
507		kmem_cache_free(authdes_cache_handle, new);
508		return (NULL);
509	}
510
511	if (!(ucred = (struct bsdcred *)kmem_alloc(sizeof (struct bsdcred),
512			KM_NOSLEEP))) {
513		kmem_free(new->rname, strlen(fullname) + 1);
514		kmem_cache_free(authdes_cache_handle, new);
515		return (NULL);
516	}
517
518	(void) strcpy(new->rname, fullname);
519	ucred->valid = 0;
520	new->localcred = (caddr_t)ucred;
521	new->key = *sessionkey;
522	new->window = window;
523	new->ref_time = gethrestime_sec();
524	new->nickname = Nickname++;
525	mutex_init(&new->lock, NULL, MUTEX_DEFAULT, NULL);
526
527	/* put new into the hash table */
528	index = HASH(new->nickname);
529	head = authdes_cache[index];
530	if ((new->next = head) != NULL) {
531		head->prev = new;
532	}
533	authdes_cache[index] = new;
534	new->prev = NULL;
535
536	/* update the LRU list */
537	new->lru_prev = NULL;
538	if ((new->lru_next = lru_first) != NULL) {
539		lru_first->lru_prev = new;
540	} else {
541		lru_last = new;
542	}
543	lru_first = new;
544
545	authdes_ncache++;
546	return (new);
547}
548
549/*
550 * Get an existing cache entry from authdes_cache table.
551 * The caller should have locked the authdes_cache table.
552 */
553struct authdes_cache_entry *
554authdes_cache_get(uint32_t nickname) {
555
556	struct authdes_cache_entry *cur = NULL;
557	int index = HASH(nickname);
558
559	ASSERT(MUTEX_HELD(&authdes_lock));
560	for (cur = authdes_cache[index]; cur; cur = cur->next) {
561		if ((cur->nickname == nickname)) {
562			/* find it, update the LRU list */
563			if (cur != lru_first) {
564			    cur->lru_prev->lru_next = cur->lru_next;
565			    if (cur->lru_next != NULL) {
566				cur->lru_next->lru_prev = cur->lru_prev;
567			    } else {
568				lru_last = cur->lru_prev;
569			    }
570			    cur->lru_prev = NULL;
571			    cur->lru_next = lru_first;
572			    lru_first->lru_prev = cur;
573			    lru_first = cur;
574			}
575
576			cur->ref_time = gethrestime_sec();
577			authdes_ncachehits++;
578			return (cur);
579		}
580	}
581
582	authdes_ncachemisses++;
583	return (NULL);
584}
585
586/*
587 * authdes_cache_reclaim() is called by the kernel memory allocator
588 * when memory is low. This routine will reclaim 25% of the least recent
589 * used cache entries above the low water mark (low_cache_entries).
590 * If the cache entries have already hit the low water mark, it will
591 * return 1 cache entry.
592 */
593/*ARGSUSED*/
594void
595authdes_cache_reclaim(void *pdata) {
596	struct authdes_cache_entry *p;
597	int n, i;
598
599	mutex_enter(&authdes_lock);
600	n = authdes_ncache - low_cache_entries;
601	n = n > 0 ? n/4 : 1;
602
603	for (i = 0; i < n; i++) {
604		if ((p = lru_last) == lru_first)
605			break;
606
607		/* Update the hash linked list */
608		if (p->prev == NULL) {
609			authdes_cache[HASH(p->nickname)] = p->next;
610		} else {
611			p->prev->next = p->next;
612		}
613		if (p->next != NULL) {
614			p->next->prev = p->prev;
615		}
616
617		/* update the LRU linked list */
618		p->lru_prev->lru_next = NULL;
619		lru_last = p->lru_prev;
620
621		kmem_free(p->rname, strlen(p->rname) + 1);
622		kmem_free(p->localcred, sizeof (struct bsdcred));
623		mutex_destroy(&p->lock);
624		kmem_cache_free(authdes_cache_handle, p);
625
626		authdes_ncache--;
627	}
628	mutex_exit(&authdes_lock);
629	RPCLOG(4, "_svcauth_des: %d cache entries reclaimed...\n",
630				authdes_ncache);
631}
632
633/*
634 *  Walk through the LRU doubly-linked list and delete the cache
635 *  entries that have not been used for more than authdes_cache_time.
636 *
637 *  Caller should have locked the cache table.
638 */
639void
640sweep_cache() {
641	struct authdes_cache_entry *p;
642
643	ASSERT(MUTEX_HELD(&authdes_lock));
644	while ((p = lru_last) != lru_first) {
645		IS_ALIGNED(p);
646		NOT_DEAD(p);
647
648		/*
649		 * If the last LRU entry idled less than authdes_cache_time,
650		 * we are done with the sweeping.
651		 */
652		if (p->ref_time + authdes_cache_time > gethrestime_sec())
653			break;
654
655		/* update the hash linked list */
656		if (p->prev == NULL) {
657			authdes_cache[HASH(p->nickname)] = p->next;
658		} else {
659			p->prev->next = p->next;
660		}
661		if (p->next != NULL) {
662			p->next->prev = p->prev;
663		}
664
665		/* update the LRU linked list */
666		p->lru_prev->lru_next = NULL;
667		lru_last = p->lru_prev;
668
669		kmem_free(p->rname, strlen(p->rname) + 1);
670		kmem_free(p->localcred, sizeof (struct bsdcred));
671		mutex_destroy(&p->lock);
672		kmem_cache_free(authdes_cache_handle, p);
673
674		authdes_ncache--;
675	}
676
677	authdes_last_swept = gethrestime_sec();
678	RPCLOG(4, "_svcauth_des: sweeping cache...#caches left = %d\n",
679				authdes_ncache);
680}
681