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