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