xref: /illumos-gate/usr/src/uts/common/fs/nfs/nfs_auth.c (revision b31b5de1357c915fe7dab4d9646d9d84f9fe69bc)
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 #include <sys/param.h>
27 #include <sys/errno.h>
28 #include <sys/vfs.h>
29 #include <sys/vnode.h>
30 #include <sys/cred.h>
31 #include <sys/cmn_err.h>
32 #include <sys/systm.h>
33 #include <sys/kmem.h>
34 #include <sys/pathname.h>
35 #include <sys/utsname.h>
36 #include <sys/debug.h>
37 #include <sys/door.h>
38 #include <sys/sdt.h>
39 
40 #include <rpc/types.h>
41 #include <rpc/auth.h>
42 #include <rpc/clnt.h>
43 
44 #include <nfs/nfs.h>
45 #include <nfs/export.h>
46 #include <nfs/nfs_clnt.h>
47 #include <nfs/auth.h>
48 
49 #define	EQADDR(a1, a2)  \
50 	(bcmp((char *)(a1)->buf, (char *)(a2)->buf, (a1)->len) == 0 && \
51 	(a1)->len == (a2)->len)
52 
53 static struct knetconfig auth_knconf;
54 static servinfo_t svp;
55 static clinfo_t ci;
56 
57 static struct kmem_cache *exi_cache_handle;
58 static void exi_cache_reclaim(void *);
59 static void exi_cache_trim(struct exportinfo *exi);
60 
61 int nfsauth_cache_hit;
62 int nfsauth_cache_miss;
63 int nfsauth_cache_reclaim;
64 
65 /*
66  * Number of seconds to wait for an NFSAUTH upcall.
67  */
68 static int nfsauth_timeout = 20;
69 
70 /*
71  * mountd is a server-side only daemon. This will need to be
72  * revisited if the NFS server is ever made zones-aware.
73  */
74 kmutex_t	mountd_lock;
75 door_handle_t   mountd_dh;
76 
77 void
78 mountd_args(uint_t did)
79 {
80 	mutex_enter(&mountd_lock);
81 	if (mountd_dh)
82 		door_ki_rele(mountd_dh);
83 	mountd_dh = door_ki_lookup(did);
84 	mutex_exit(&mountd_lock);
85 }
86 
87 void
88 nfsauth_init(void)
89 {
90 	/*
91 	 * mountd can be restarted by smf(5). We need to make sure
92 	 * the updated door handle will safely make it to mountd_dh
93 	 */
94 	mutex_init(&mountd_lock, NULL, MUTEX_DEFAULT, NULL);
95 
96 	/*
97 	 * Allocate nfsauth cache handle
98 	 */
99 	exi_cache_handle = kmem_cache_create("exi_cache_handle",
100 	    sizeof (struct auth_cache), 0, NULL, NULL,
101 	    exi_cache_reclaim, NULL, NULL, 0);
102 }
103 
104 /*
105  * Finalization routine for nfsauth. It is important to call this routine
106  * before destroying the exported_lock.
107  */
108 void
109 nfsauth_fini(void)
110 {
111 	/*
112 	 * Deallocate nfsauth cache handle
113 	 */
114 	kmem_cache_destroy(exi_cache_handle);
115 }
116 
117 /*
118  * Convert the address in a netbuf to
119  * a hash index for the auth_cache table.
120  */
121 static int
122 hash(struct netbuf *a)
123 {
124 	int i, h = 0;
125 
126 	for (i = 0; i < a->len; i++)
127 		h ^= a->buf[i];
128 
129 	return (h & (AUTH_TABLESIZE - 1));
130 }
131 
132 /*
133  * Mask out the components of an
134  * address that do not identify
135  * a host. For socket addresses the
136  * masking gets rid of the port number.
137  */
138 static void
139 addrmask(struct netbuf *addr, struct netbuf *mask)
140 {
141 	int i;
142 
143 	for (i = 0; i < addr->len; i++)
144 		addr->buf[i] &= mask->buf[i];
145 }
146 
147 /*
148  * nfsauth4_access is used for NFS V4 auth checking. Besides doing
149  * the common nfsauth_access(), it will check if the client can
150  * have a limited access to this vnode even if the security flavor
151  * used does not meet the policy.
152  */
153 int
154 nfsauth4_access(struct exportinfo *exi, vnode_t *vp, struct svc_req *req)
155 {
156 	int access;
157 
158 	access = nfsauth_access(exi, req);
159 
160 	/*
161 	 * There are cases that the server needs to allow the client
162 	 * to have a limited view.
163 	 *
164 	 * e.g.
165 	 * /export is shared as "sec=sys,rw=dfs-test-4,sec=krb5,rw"
166 	 * /export/home is shared as "sec=sys,rw"
167 	 *
168 	 * When the client mounts /export with sec=sys, the client
169 	 * would get a limited view with RO access on /export to see
170 	 * "home" only because the client is allowed to access
171 	 * /export/home with auth_sys.
172 	 */
173 	if (access & NFSAUTH_DENIED || access & NFSAUTH_WRONGSEC) {
174 		/*
175 		 * Allow ro permission with LIMITED view if there is a
176 		 * sub-dir exported under vp.
177 		 */
178 		if (has_visible(exi, vp))
179 			return (NFSAUTH_LIMITED);
180 	}
181 
182 	return (access);
183 }
184 
185 static void
186 sys_log(const char *msg)
187 {
188 	static time_t	tstamp = 0;
189 	time_t		now;
190 
191 	/*
192 	 * msg is shown (at most) once per minute
193 	 */
194 	now = gethrestime_sec();
195 	if ((tstamp + 60) < now) {
196 		tstamp = now;
197 		cmn_err(CE_WARN, msg);
198 	}
199 }
200 
201 /*
202  * Get the access information from the cache or callup to the mountd
203  * to get and cache the access information in the kernel.
204  */
205 int
206 nfsauth_cache_get(struct exportinfo *exi, struct svc_req *req, int flavor)
207 {
208 	struct netbuf		  addr;
209 	struct netbuf		 *claddr;
210 	struct auth_cache	**head;
211 	struct auth_cache	 *ap;
212 	int			  access;
213 	varg_t			  varg = {0};
214 	nfsauth_res_t		  res = {0};
215 	XDR			  xdrs_a;
216 	XDR			  xdrs_r;
217 	size_t			  absz;
218 	caddr_t			  abuf;
219 	size_t			  rbsz = (size_t)(BYTES_PER_XDR_UNIT * 2);
220 	char			  result[BYTES_PER_XDR_UNIT * 2] = {0};
221 	caddr_t			  rbuf = (caddr_t)&result;
222 	int			  last = 0;
223 	door_arg_t		  da;
224 	door_info_t		  di;
225 	door_handle_t		  dh;
226 	uint_t			  ntries = 0;
227 
228 	/*
229 	 * Now check whether this client already
230 	 * has an entry for this flavor in the cache
231 	 * for this export.
232 	 * Get the caller's address, mask off the
233 	 * parts of the address that do not identify
234 	 * the host (port number, etc), and then hash
235 	 * it to find the chain of cache entries.
236 	 */
237 
238 	claddr = svc_getrpccaller(req->rq_xprt);
239 	addr = *claddr;
240 	addr.buf = kmem_alloc(addr.len, KM_SLEEP);
241 	bcopy(claddr->buf, addr.buf, claddr->len);
242 	addrmask(&addr, svc_getaddrmask(req->rq_xprt));
243 	head = &exi->exi_cache[hash(&addr)];
244 
245 	rw_enter(&exi->exi_cache_lock, RW_READER);
246 	for (ap = *head; ap; ap = ap->auth_next) {
247 		if (EQADDR(&addr, &ap->auth_addr) && flavor == ap->auth_flavor)
248 			break;
249 	}
250 	if (ap) {				/* cache hit */
251 		access = ap->auth_access;
252 		ap->auth_time = gethrestime_sec();
253 		nfsauth_cache_hit++;
254 	}
255 
256 	rw_exit(&exi->exi_cache_lock);
257 
258 	if (ap) {
259 		kmem_free(addr.buf, addr.len);
260 		return (access);
261 	}
262 
263 	nfsauth_cache_miss++;
264 
265 	/*
266 	 * No entry in the cache for this client/flavor
267 	 * so we need to call the nfsauth service in the
268 	 * mount daemon.
269 	 */
270 retry:
271 	mutex_enter(&mountd_lock);
272 	dh = mountd_dh;
273 	if (dh)
274 		door_ki_hold(dh);
275 	mutex_exit(&mountd_lock);
276 
277 	if (dh == NULL) {
278 		/*
279 		 * The rendezvous point has not been established yet !
280 		 * This could mean that either mountd(1m) has not yet
281 		 * been started or that _this_ routine nuked the door
282 		 * handle after receiving an EINTR for a REVOKED door.
283 		 *
284 		 * Returning NFSAUTH_DROP will cause the NFS client
285 		 * to retransmit the request, so let's try to be more
286 		 * rescillient and attempt for ntries before we bail.
287 		 */
288 		if (++ntries % NFSAUTH_DR_TRYCNT) {
289 			delay(hz);
290 			goto retry;
291 		}
292 		sys_log("nfsauth: mountd has not established door");
293 		kmem_free(addr.buf, addr.len);
294 		return (NFSAUTH_DROP);
295 	}
296 	ntries = 0;
297 	varg.vers = V_PROTO;
298 	varg.arg_u.arg.cmd = NFSAUTH_ACCESS;
299 	varg.arg_u.arg.areq.req_client.n_len = addr.len;
300 	varg.arg_u.arg.areq.req_client.n_bytes = addr.buf;
301 	varg.arg_u.arg.areq.req_netid = svc_getnetid(req->rq_xprt);
302 	varg.arg_u.arg.areq.req_path = exi->exi_export.ex_path;
303 	varg.arg_u.arg.areq.req_flavor = flavor;
304 
305 	/*
306 	 * Setup the XDR stream for encoding the arguments. Notice that
307 	 * in addition to the args having variable fields (req_netid and
308 	 * req_path), the argument data structure is itself versioned,
309 	 * so we need to make sure we can size the arguments buffer
310 	 * appropriately to encode all the args. If we can't get sizing
311 	 * info _or_ properly encode the arguments, there's really no
312 	 * point in continuting, so we fail the request.
313 	 */
314 	DTRACE_PROBE1(nfsserv__func__nfsauth__varg, varg_t *, &varg);
315 	if ((absz = xdr_sizeof(xdr_varg, (void *)&varg)) == 0) {
316 		door_ki_rele(dh);
317 		kmem_free(addr.buf, addr.len);
318 		return (NFSAUTH_DENIED);
319 	}
320 	abuf = (caddr_t)kmem_alloc(absz, KM_SLEEP);
321 	xdrmem_create(&xdrs_a, abuf, absz, XDR_ENCODE);
322 	if (!xdr_varg(&xdrs_a, &varg)) {
323 		door_ki_rele(dh);
324 		goto fail;
325 	}
326 	XDR_DESTROY(&xdrs_a);
327 
328 	/*
329 	 * The result (nfsauth_res_t) is always two int's, so we don't
330 	 * have to dynamically size (or allocate) the results buffer.
331 	 * Now that we've got what we need, we prep the door arguments
332 	 * and place the call.
333 	 */
334 	da.data_ptr = (char *)abuf;
335 	da.data_size = absz;
336 	da.desc_ptr = NULL;
337 	da.desc_num = 0;
338 	da.rbuf = (char *)rbuf;
339 	da.rsize = rbsz;
340 
341 	switch (door_ki_upcall_limited(dh, &da, NULL, SIZE_MAX, 0)) {
342 		case 0:				/* Success */
343 			if (da.data_ptr != da.rbuf && da.data_size == 0) {
344 				/*
345 				 * The door_return that contained the data
346 				 * failed ! We're here because of the 2nd
347 				 * door_return (w/o data) such that we can
348 				 * get control of the thread (and exit
349 				 * gracefully).
350 				 */
351 				DTRACE_PROBE1(nfsserv__func__nfsauth__door__nil,
352 				    door_arg_t *, &da);
353 				door_ki_rele(dh);
354 				goto fail;
355 
356 			} else if (rbuf != da.rbuf) {
357 				/*
358 				 * The only time this should be true
359 				 * is iff userland wanted to hand us
360 				 * a bigger response than what we
361 				 * expect; that should not happen
362 				 * (nfsauth_res_t is only 2 int's),
363 				 * but we check nevertheless.
364 				 */
365 				rbuf = da.rbuf;
366 				rbsz = da.rsize;
367 
368 			} else if (rbsz > da.data_size) {
369 				/*
370 				 * We were expecting two int's; but if
371 				 * userland fails in encoding the XDR
372 				 * stream, we detect that here, since
373 				 * the mountd forces down only one byte
374 				 * in such scenario.
375 				 */
376 				door_ki_rele(dh);
377 				goto fail;
378 			}
379 			door_ki_rele(dh);
380 			break;
381 
382 		case EAGAIN:
383 			/*
384 			 * Server out of resources; back off for a bit
385 			 */
386 			door_ki_rele(dh);
387 			kmem_free(abuf, absz);
388 			delay(hz);
389 			goto retry;
390 			/* NOTREACHED */
391 
392 		case EINTR:
393 			if (!door_ki_info(dh, &di)) {
394 				if (di.di_attributes & DOOR_REVOKED) {
395 					/*
396 					 * The server barfed and revoked
397 					 * the (existing) door on us; we
398 					 * want to wait to give smf(5) a
399 					 * chance to restart mountd(1m)
400 					 * and establish a new door handle.
401 					 */
402 					mutex_enter(&mountd_lock);
403 					if (dh == mountd_dh)
404 						mountd_dh = NULL;
405 					mutex_exit(&mountd_lock);
406 					door_ki_rele(dh);
407 					kmem_free(abuf, absz);
408 					delay(hz);
409 					goto retry;
410 				}
411 				/*
412 				 * If the door was _not_ revoked on us,
413 				 * then more than likely we took an INTR,
414 				 * so we need to fail the operation.
415 				 */
416 				door_ki_rele(dh);
417 				goto fail;
418 			}
419 			/*
420 			 * The only failure that can occur from getting
421 			 * the door info is EINVAL, so we let the code
422 			 * below handle it.
423 			 */
424 			/* FALLTHROUGH */
425 
426 		case EBADF:
427 		case EINVAL:
428 		default:
429 			/*
430 			 * If we have a stale door handle, give smf a last
431 			 * chance to start it by sleeping for a little bit.
432 			 * If we're still hosed, we'll fail the call.
433 			 *
434 			 * Since we're going to reacquire the door handle
435 			 * upon the retry, we opt to sleep for a bit and
436 			 * _not_ to clear mountd_dh. If mountd restarted
437 			 * and was able to set mountd_dh, we should see
438 			 * the new instance; if not, we won't get caught
439 			 * up in the retry/DELAY loop.
440 			 */
441 			door_ki_rele(dh);
442 			if (!last) {
443 				delay(hz);
444 				last++;
445 				goto retry;
446 			}
447 			sys_log("nfsauth: stale mountd door handle");
448 			goto fail;
449 	}
450 
451 	/*
452 	 * No door errors encountered; setup the XDR stream for decoding
453 	 * the results. If we fail to decode the results, we've got no
454 	 * other recourse than to fail the request.
455 	 */
456 	xdrmem_create(&xdrs_r, rbuf, rbsz, XDR_DECODE);
457 	if (!xdr_nfsauth_res(&xdrs_r, &res))
458 		goto fail;
459 	XDR_DESTROY(&xdrs_r);
460 
461 	DTRACE_PROBE1(nfsserv__func__nfsauth__results, nfsauth_res_t *, &res);
462 	switch (res.stat) {
463 		case NFSAUTH_DR_OKAY:
464 			access = res.ares.auth_perm;
465 			kmem_free(abuf, absz);
466 			break;
467 
468 		case NFSAUTH_DR_EFAIL:
469 		case NFSAUTH_DR_DECERR:
470 		case NFSAUTH_DR_BADCMD:
471 		default:
472 fail:
473 			kmem_free(addr.buf, addr.len);
474 			kmem_free(abuf, absz);
475 			return (NFSAUTH_DENIED);
476 			/* NOTREACHED */
477 	}
478 
479 	/*
480 	 * Now cache the result on the cache chain
481 	 * for this export (if there's enough memory)
482 	 */
483 	ap = kmem_cache_alloc(exi_cache_handle, KM_NOSLEEP);
484 	if (ap) {
485 		ap->auth_addr = addr;
486 		ap->auth_flavor = flavor;
487 		ap->auth_access = access;
488 		ap->auth_time = gethrestime_sec();
489 		rw_enter(&exi->exi_cache_lock, RW_WRITER);
490 		ap->auth_next = *head;
491 		*head = ap;
492 		rw_exit(&exi->exi_cache_lock);
493 	} else {
494 		kmem_free(addr.buf, addr.len);
495 	}
496 
497 	return (access);
498 }
499 
500 /*
501  * Check if the requesting client has access to the filesystem with
502  * a given nfs flavor number which is an explicitly shared flavor.
503  */
504 int
505 nfsauth4_secinfo_access(struct exportinfo *exi, struct svc_req *req,
506 			int flavor, int perm)
507 {
508 	int access;
509 
510 	if (! (perm & M_4SEC_EXPORTED)) {
511 		return (NFSAUTH_DENIED);
512 	}
513 
514 	/*
515 	 * Optimize if there are no lists
516 	 */
517 	if ((perm & (M_ROOT|M_NONE)) == 0) {
518 		perm &= ~M_4SEC_EXPORTED;
519 		if (perm == M_RO)
520 			return (NFSAUTH_RO);
521 		if (perm == M_RW)
522 			return (NFSAUTH_RW);
523 	}
524 
525 	access = nfsauth_cache_get(exi, req, flavor);
526 
527 	return (access);
528 }
529 
530 int
531 nfsauth_access(struct exportinfo *exi, struct svc_req *req)
532 {
533 	int access, mapaccess;
534 	struct secinfo *sp;
535 	int i, flavor, perm;
536 	int authnone_entry = -1;
537 
538 	/*
539 	 *  Get the nfs flavor number from xprt.
540 	 */
541 	flavor = (int)(uintptr_t)req->rq_xprt->xp_cookie;
542 
543 	/*
544 	 * First check the access restrictions on the filesystem.  If
545 	 * there are no lists associated with this flavor then there's no
546 	 * need to make an expensive call to the nfsauth service or to
547 	 * cache anything.
548 	 */
549 
550 	sp = exi->exi_export.ex_secinfo;
551 	for (i = 0; i < exi->exi_export.ex_seccnt; i++) {
552 		if (flavor != sp[i].s_secinfo.sc_nfsnum) {
553 			if (sp[i].s_secinfo.sc_nfsnum == AUTH_NONE)
554 				authnone_entry = i;
555 			continue;
556 		}
557 		break;
558 	}
559 
560 	mapaccess = 0;
561 
562 	if (i >= exi->exi_export.ex_seccnt) {
563 		/*
564 		 * Flavor not found, but use AUTH_NONE if it exists
565 		 */
566 		if (authnone_entry == -1)
567 			return (NFSAUTH_DENIED);
568 		flavor = AUTH_NONE;
569 		mapaccess = NFSAUTH_MAPNONE;
570 		i = authnone_entry;
571 	}
572 
573 	/*
574 	 * If the flavor is in the ex_secinfo list, but not an explicitly
575 	 * shared flavor by the user, it is a result of the nfsv4 server
576 	 * namespace setup. We will grant an RO permission similar for
577 	 * a pseudo node except that this node is a shared one.
578 	 *
579 	 * e.g. flavor in (flavor) indicates that it is not explictly
580 	 *	shared by the user:
581 	 *
582 	 *		/	(sys, krb5)
583 	 *		|
584 	 *		export  #share -o sec=sys (krb5)
585 	 *		|
586 	 *		secure  #share -o sec=krb5
587 	 *
588 	 *	In this case, when a krb5 request coming in to access
589 	 *	/export, RO permission is granted.
590 	 */
591 	if (!(sp[i].s_flags & M_4SEC_EXPORTED))
592 		return (mapaccess | NFSAUTH_RO);
593 
594 	/*
595 	 * Optimize if there are no lists
596 	 */
597 	perm = sp[i].s_flags;
598 	if ((perm & (M_ROOT|M_NONE)) == 0) {
599 		perm &= ~M_4SEC_EXPORTED;
600 		if (perm == M_RO)
601 			return (mapaccess | NFSAUTH_RO);
602 		if (perm == M_RW)
603 			return (mapaccess | NFSAUTH_RW);
604 	}
605 
606 	access = nfsauth_cache_get(exi, req, flavor);
607 
608 	/*
609 	 * Client's security flavor doesn't match with "ro" or
610 	 * "rw" list. Try again using AUTH_NONE if present.
611 	 */
612 	if ((access & NFSAUTH_WRONGSEC) && (flavor != AUTH_NONE)) {
613 		/*
614 		 * Have we already encountered AUTH_NONE ?
615 		 */
616 		if (authnone_entry != -1) {
617 			mapaccess = NFSAUTH_MAPNONE;
618 			access = nfsauth_cache_get(exi, req, AUTH_NONE);
619 		} else {
620 			/*
621 			 * Check for AUTH_NONE presence.
622 			 */
623 			for (; i < exi->exi_export.ex_seccnt; i++) {
624 				if (sp[i].s_secinfo.sc_nfsnum == AUTH_NONE) {
625 					mapaccess = NFSAUTH_MAPNONE;
626 					access = nfsauth_cache_get(exi, req,
627 					    AUTH_NONE);
628 					break;
629 				}
630 			}
631 		}
632 	}
633 
634 	if (access & NFSAUTH_DENIED)
635 		access = NFSAUTH_DENIED;
636 
637 	return (access | mapaccess);
638 }
639 
640 /*
641  * Free the nfsauth cache for a given export
642  */
643 void
644 nfsauth_cache_free(struct exportinfo *exi)
645 {
646 	int i;
647 	struct auth_cache *p, *next;
648 
649 	for (i = 0; i < AUTH_TABLESIZE; i++) {
650 		for (p = exi->exi_cache[i]; p; p = next) {
651 			kmem_free(p->auth_addr.buf, p->auth_addr.len);
652 			next = p->auth_next;
653 			kmem_cache_free(exi_cache_handle, (void *)p);
654 		}
655 	}
656 }
657 
658 /*
659  * Called by the kernel memory allocator when
660  * memory is low. Free unused cache entries.
661  * If that's not enough, the VM system will
662  * call again for some more.
663  */
664 /*ARGSUSED*/
665 void
666 exi_cache_reclaim(void *cdrarg)
667 {
668 	int i;
669 	struct exportinfo *exi;
670 
671 	rw_enter(&exported_lock, RW_READER);
672 
673 	for (i = 0; i < EXPTABLESIZE; i++) {
674 		for (exi = exptable[i]; exi; exi = exi->exi_hash) {
675 			exi_cache_trim(exi);
676 		}
677 	}
678 	nfsauth_cache_reclaim++;
679 
680 	rw_exit(&exported_lock);
681 }
682 
683 /*
684  * Don't reclaim entries until they've been
685  * in the cache for at least exi_cache_time
686  * seconds.
687  */
688 time_t exi_cache_time = 60 * 60;
689 
690 void
691 exi_cache_trim(struct exportinfo *exi)
692 {
693 	struct auth_cache *p;
694 	struct auth_cache *prev, *next;
695 	int i;
696 	time_t stale_time;
697 
698 	stale_time = gethrestime_sec() - exi_cache_time;
699 
700 	rw_enter(&exi->exi_cache_lock, RW_WRITER);
701 
702 	for (i = 0; i < AUTH_TABLESIZE; i++) {
703 
704 		/*
705 		 * Free entries that have not been
706 		 * used for exi_cache_time seconds.
707 		 */
708 		prev = NULL;
709 		for (p = exi->exi_cache[i]; p; p = next) {
710 			next = p->auth_next;
711 			if (p->auth_time > stale_time) {
712 				prev = p;
713 				continue;
714 			}
715 
716 			kmem_free(p->auth_addr.buf, p->auth_addr.len);
717 			kmem_cache_free(exi_cache_handle, (void *)p);
718 			if (prev == NULL)
719 				exi->exi_cache[i] = next;
720 			else
721 				prev->auth_next = next;
722 		}
723 	}
724 
725 	rw_exit(&exi->exi_cache_lock);
726 }
727