xref: /titanic_52/usr/src/uts/common/rpc/sec/svcauthdes.c (revision d8d4fa8f33f737d82c7dcd7ddd11b4342ce4fbca)
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
84 static 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 */
88 static int low_cache_entries = 128;
89 
90 struct 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 };
101 static struct authdes_cache_entry **authdes_cache; /* [authdes_cachesz] */
102 static struct authdes_cache_entry *lru_first = NULL;
103 static struct authdes_cache_entry *lru_last = NULL;
104 static kmutex_t authdes_lock;		/* cache table lock */
105 
106 static struct kmem_cache *authdes_cache_handle;
107 static uint32_t	Nickname = 0;
108 
109 static struct authdes_cache_entry *authdes_cache_new(char *,
110 					des_block *, uint32_t);
111 static struct authdes_cache_entry *authdes_cache_get(uint32_t);
112 static void authdes_cache_reclaim(void *);
113 static 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  */
119 static time_t authdes_sweep_interval = 12*60*60;
120 static time_t authdes_cache_time = 10*60*60;
121 static time_t authdes_last_swept = 0;
122 
123 /*
124  * cache statistics
125  */
126 static int authdes_ncache = 0; /* number of current cached entries */
127 static int authdes_ncachehits = 0; /* #times cache hit */
128 static 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  */
136 enum 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  */
386 void
387 svcauthdes_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  */
401 void
402 svcauthdes_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 
414 struct 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  */
428 int
429 kauthdes_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  */
495 struct authdes_cache_entry *
496 authdes_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  */
553 struct authdes_cache_entry *
554 authdes_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*/
594 void
595 authdes_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  */
639 void
640 sweep_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