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(¤t_time, ×tamp)) { 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