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