xref: /illumos-gate/usr/src/lib/smbsrv/libfksmbsrv/common/fake_lookup.c (revision dd72704bd9e794056c558153663c739e2012d721)
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 /*
23  * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
25  * Copyright 2022 RackTop Systems, Inc.
26  */
27 
28 /*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
29 /*	  All Rights Reserved	*/
30 
31 /*
32  * University Copyright- Copyright (c) 1982, 1986, 1988
33  * The Regents of the University of California
34  * All Rights Reserved
35  *
36  * University Acknowledgment- Portions of this document are derived from
37  * software developed by the University of California, Berkeley, and its
38  * contributors.
39  */
40 
41 #include <sys/types.h>
42 #include <sys/param.h>
43 #include <sys/systm.h>
44 #include <sys/file.h>
45 #include <sys/errno.h>
46 #include <sys/cred.h>
47 #include <sys/user.h>
48 #include <sys/uio.h>
49 #include <sys/vfs.h>
50 #include <sys/vnode.h>
51 #include <sys/pathname.h>
52 #include <sys/proc.h>
53 #include <sys/vtrace.h>
54 #include <sys/sysmacros.h>
55 #include <sys/debug.h>
56 #include <sys/dirent.h>
57 #include <sys/zone.h>
58 #include <sys/dnlc.h>
59 #include <sys/fs/snode.h>
60 
61 int
62 lookupname(
63 	char *fnamep,
64 	enum uio_seg seg,
65 	int followlink,
66 	vnode_t **dirvpp,
67 	vnode_t **compvpp)
68 {
69 	return (lookupnameatcred(fnamep, seg, followlink, dirvpp, compvpp, NULL,
70 	    CRED()));
71 }
72 
73 /*
74  * Lookup the user file name,
75  * Handle allocation and freeing of pathname buffer, return error.
76  */
77 int
78 lookupnameatcred(
79 	char *fnamep,			/* user pathname */
80 	enum uio_seg seg,		/* addr space that name is in */
81 	int followlink,			/* follow sym links */
82 	vnode_t **dirvpp,		/* ret for ptr to parent dir vnode */
83 	vnode_t **compvpp,		/* ret for ptr to component vnode */
84 	vnode_t *startvp,		/* start path search from vp */
85 	cred_t *cr)			/* credential */
86 {
87 	char namebuf[TYPICALMAXPATHLEN];
88 	struct pathname lookpn;
89 	int error;
90 
91 	error = pn_get_buf(fnamep, seg, &lookpn, namebuf, sizeof (namebuf));
92 	if (error == 0) {
93 		error = lookuppnatcred(&lookpn, NULL, followlink,
94 		    dirvpp, compvpp, startvp, cr);
95 	}
96 	if (error == ENAMETOOLONG) {
97 		/*
98 		 * This thread used a pathname > TYPICALMAXPATHLEN bytes long.
99 		 */
100 		if ((error = pn_get(fnamep, seg, &lookpn)) != 0)
101 			return (error);
102 		error = lookuppnatcred(&lookpn, NULL, followlink,
103 		    dirvpp, compvpp, startvp, cr);
104 		pn_free(&lookpn);
105 	}
106 
107 	return (error);
108 }
109 
110 /*
111  * Lookup the user file name from a given vp, using a specific credential.
112  */
113 int
114 lookuppnatcred(
115 	struct pathname *pnp,		/* pathname to lookup */
116 	struct pathname *rpnp,		/* if non-NULL, return resolved path */
117 	int followlink,			/* (don't) follow sym links */
118 	vnode_t **dirvpp,		/* ptr for parent vnode */
119 	vnode_t **compvpp,		/* ptr for entry vnode */
120 	vnode_t *startvp,		/* start search from this vp */
121 	cred_t *cr)			/* user credential */
122 {
123 	vnode_t *vp;	/* current directory vp */
124 	vnode_t *rootvp;
125 
126 	if (pnp->pn_pathlen == 0)
127 		return (ENOENT);
128 
129 	/* Simplified for fake_... */
130 	vp = rootvp = rootdir;
131 
132 	/*
133 	 * Skip over leading slashes
134 	 */
135 	if (pnp->pn_path[0] == '/') {
136 		do {
137 			pnp->pn_path++;
138 			pnp->pn_pathlen--;
139 		} while (pnp->pn_path[0] == '/');
140 	}
141 
142 	return (lookuppnvp(pnp, rpnp, followlink, dirvpp,
143 	    compvpp, rootvp, vp, cr));
144 }
145 
146 /*
147  * Starting at current directory, translate pathname pnp to end.
148  * Leave pathname of final component in pnp, return the vnode
149  * for the final component in *compvpp, and return the vnode
150  * for the parent of the final component in dirvpp.
151  *
152  * This is the central routine in pathname translation and handles
153  * multiple components in pathnames, separating them at /'s.  It also
154  * implements mounted file systems and processes symbolic links.
155  *
156  * vp is the vnode where the directory search should start.
157  *
158  * Reference counts: vp must be held prior to calling this function.  rootvp
159  * should only be held if rootvp != rootdir.
160  */
161 int
162 lookuppnvp(
163 	struct pathname *pnp,		/* pathname to lookup */
164 	struct pathname *rpnp,		/* if non-NULL, return resolved path */
165 	int flags,			/* follow symlinks */
166 	vnode_t **dirvpp,		/* ptr for parent vnode */
167 	vnode_t **compvpp,		/* ptr for entry vnode */
168 	vnode_t *rootvp,		/* rootvp */
169 	vnode_t *vp,			/* directory to start search at */
170 	cred_t *cr)			/* user's credential */
171 {
172 	vnode_t *cvp;	/* current component vp */
173 	vnode_t *tvp;	/* addressable temp ptr */
174 	char component[MAXNAMELEN];	/* buffer for component (incl null) */
175 	int error;
176 	int nlink;
177 	int lookup_flags;
178 	struct pathname presrvd; /* case preserved name */
179 	struct pathname *pp = NULL;
180 	vnode_t *startvp;
181 	int must_be_directory = 0;
182 	boolean_t retry_with_kcred;
183 
184 	nlink = 0;
185 	cvp = NULL;
186 	if (rpnp)
187 		rpnp->pn_pathlen = 0;
188 
189 	lookup_flags = dirvpp ? LOOKUP_DIR : 0;
190 	if (flags & FIGNORECASE) {
191 		lookup_flags |= FIGNORECASE;
192 		pn_alloc(&presrvd);
193 		pp = &presrvd;
194 	}
195 
196 	/*
197 	 * Eliminate any trailing slashes in the pathname.
198 	 * If there are any, we must follow all symlinks.
199 	 * Also, we must guarantee that the last component is a directory.
200 	 */
201 	if (pn_fixslash(pnp)) {
202 		flags |= FOLLOW;
203 		must_be_directory = 1;
204 	}
205 
206 	startvp = vp;
207 next:
208 	retry_with_kcred = B_FALSE;
209 
210 	/*
211 	 * Make sure we have a directory.
212 	 */
213 	if (vp->v_type != VDIR) {
214 		error = ENOTDIR;
215 		goto bad;
216 	}
217 
218 	if (rpnp && VN_CMP(vp, rootvp))
219 		(void) pn_set(rpnp, "/");
220 
221 	/*
222 	 * Process the next component of the pathname.
223 	 */
224 	if ((error = pn_getcomponent(pnp, component)) != 0) {
225 		goto bad;
226 	}
227 
228 	/*
229 	 * Handle "..": two special cases.
230 	 * 1. If we're at the root directory (e.g. after chroot or
231 	 *    zone_enter) then change ".." to "." so we can't get
232 	 *    out of this subtree.
233 	 * 2. If this vnode is the root of a mounted file system,
234 	 *    then replace it with the vnode that was mounted on
235 	 *    so that we take the ".." in the other file system.
236 	 */
237 	if (component[0] == '.' && component[1] == '.' && component[2] == 0) {
238 checkforroot:
239 		if (VN_CMP(vp, rootvp)) {
240 			component[1] = '\0';
241 		} else if (vp->v_flag & VROOT) {
242 			vfs_t *vfsp;
243 			cvp = vp;
244 
245 			/*
246 			 * While we deal with the vfs pointer from the vnode
247 			 * the filesystem could have been forcefully unmounted
248 			 * and the vnode's v_vfsp could have been invalidated
249 			 * by VFS_UNMOUNT. Hence, we cache v_vfsp and use it
250 			 * with vfs_rlock_wait/vfs_unlock.
251 			 * It is safe to use the v_vfsp even it is freed by
252 			 * VFS_UNMOUNT because vfs_rlock_wait/vfs_unlock
253 			 * do not dereference v_vfsp. It is just used as a
254 			 * magic cookie.
255 			 * One more corner case here is the memory getting
256 			 * reused for another vfs structure. In this case
257 			 * lookuppnvp's vfs_rlock_wait will succeed, domount's
258 			 * vfs_lock will fail and domount will bail out with an
259 			 * error (EBUSY).
260 			 */
261 			vfsp = cvp->v_vfsp;
262 
263 			/*
264 			 * This lock is used to synchronize
265 			 * mounts/unmounts and lookups.
266 			 * Threads doing mounts/unmounts hold the
267 			 * writers version vfs_lock_wait().
268 			 */
269 
270 			vfs_rlock_wait(vfsp);
271 
272 			/*
273 			 * If this vnode is on a file system that
274 			 * has been forcibly unmounted,
275 			 * we can't proceed. Cancel this operation
276 			 * and return EIO.
277 			 *
278 			 * vfs_vnodecovered is NULL if unmounted.
279 			 * Currently, nfs uses VFS_UNMOUNTED to
280 			 * check if it's a forced-umount. Keep the
281 			 * same checking here as well even though it
282 			 * may not be needed.
283 			 */
284 			if (((vp = cvp->v_vfsp->vfs_vnodecovered) == NULL) ||
285 			    (cvp->v_vfsp->vfs_flag & VFS_UNMOUNTED)) {
286 				vfs_unlock(vfsp);
287 				VN_RELE(cvp);
288 				if (pp)
289 					pn_free(pp);
290 				return (EIO);
291 			}
292 			VN_HOLD(vp);
293 			vfs_unlock(vfsp);
294 			VN_RELE(cvp);
295 			cvp = NULL;
296 			/*
297 			 * Crossing mount points. For eg: We are doing
298 			 * a lookup of ".." for file systems root vnode
299 			 * mounted here, and VOP_LOOKUP() (with covered vnode)
300 			 * will be on underlying file systems mount point
301 			 * vnode. Set retry_with_kcred flag as we might end
302 			 * up doing VOP_LOOKUP() with kcred if required.
303 			 */
304 			retry_with_kcred = B_TRUE;
305 			goto checkforroot;
306 		}
307 	}
308 
309 	/*
310 	 * Perform a lookup in the current directory.
311 	 */
312 	error = VOP_LOOKUP(vp, component, &tvp, pnp, lookup_flags,
313 	    rootvp, cr, NULL, NULL, pp);
314 
315 	/*
316 	 * Retry with kcred - If crossing mount points & error is EACCES.
317 	 *
318 	 * If we are crossing mount points here and doing ".." lookup,
319 	 * VOP_LOOKUP() might fail if the underlying file systems
320 	 * mount point has no execute permission. In cases like these,
321 	 * we retry VOP_LOOKUP() by giving as much privilage as possible
322 	 * by passing kcred credentials.
323 	 *
324 	 * In case of hierarchical file systems, passing kcred still may
325 	 * or may not work.
326 	 * For eg: UFS FS --> Mount NFS FS --> Again mount UFS on some
327 	 *			directory inside NFS FS.
328 	 */
329 	if ((error == EACCES) && retry_with_kcred)
330 		error = VOP_LOOKUP(vp, component, &tvp, pnp, lookup_flags,
331 		    rootvp, zone_kcred(), NULL, NULL, pp);
332 
333 	cvp = tvp;
334 	if (error) {
335 		cvp = NULL;
336 		/*
337 		 * On error, return hard error if
338 		 * (a) we're not at the end of the pathname yet, or
339 		 * (b) the caller didn't want the parent directory, or
340 		 * (c) we failed for some reason other than a missing entry.
341 		 */
342 		if (pn_pathleft(pnp) || dirvpp == NULL || error != ENOENT)
343 			goto bad;
344 
345 		pn_setlast(pnp);
346 		/*
347 		 * We inform the caller that the desired entry must be
348 		 * a directory by adding a '/' to the component name.
349 		 */
350 		if (must_be_directory && (error = pn_addslash(pnp)) != 0)
351 			goto bad;
352 		*dirvpp = vp;
353 		if (compvpp != NULL)
354 			*compvpp = NULL;
355 		if (rootvp != rootdir)
356 			VN_RELE(rootvp);
357 		if (pp)
358 			pn_free(pp);
359 		return (0);
360 	}
361 
362 	/*
363 	 * Traverse mount points.
364 	 */
365 	if (vn_mountedvfs(cvp) != NULL) {
366 		tvp = cvp;
367 		if ((error = traverse(&tvp)) != 0) {
368 			/*
369 			 * It is required to assign cvp here, because
370 			 * traverse() will return a held vnode which
371 			 * may different than the vnode that was passed
372 			 * in (even in the error case).  If traverse()
373 			 * changes the vnode it releases the original,
374 			 * and holds the new one.
375 			 */
376 			cvp = tvp;
377 			goto bad;
378 		}
379 		cvp = tvp;
380 	}
381 
382 	/*
383 	 * If we hit a symbolic link and there is more path to be
384 	 * translated or this operation does not wish to apply
385 	 * to a link, then place the contents of the link at the
386 	 * front of the remaining pathname.
387 	 */
388 	if (cvp->v_type == VLNK && ((flags & FOLLOW) || pn_pathleft(pnp))) {
389 		struct pathname linkpath;
390 
391 		if (++nlink > MAXSYMLINKS) {
392 			error = ELOOP;
393 			goto bad;
394 		}
395 		pn_alloc(&linkpath);
396 		if ((error = pn_getsymlink(cvp, &linkpath, cr)) != 0) {
397 			pn_free(&linkpath);
398 			goto bad;
399 		}
400 
401 		if (pn_pathleft(&linkpath) == 0)
402 			(void) pn_set(&linkpath, ".");
403 		error = pn_insert(pnp, &linkpath, strlen(component));
404 		pn_free(&linkpath);
405 		if (error)
406 			goto bad;
407 		VN_RELE(cvp);
408 		cvp = NULL;
409 		if (pnp->pn_pathlen == 0) {
410 			error = ENOENT;
411 			goto bad;
412 		}
413 		if (pnp->pn_path[0] == '/') {
414 			do {
415 				pnp->pn_path++;
416 				pnp->pn_pathlen--;
417 			} while (pnp->pn_path[0] == '/');
418 			VN_RELE(vp);
419 			vp = rootvp;
420 			VN_HOLD(vp);
421 		}
422 		if (pn_fixslash(pnp)) {
423 			flags |= FOLLOW;
424 			must_be_directory = 1;
425 		}
426 		goto next;
427 	}
428 
429 	/*
430 	 * If rpnp is non-NULL, remember the resolved path name therein.
431 	 * Do not include "." components.  Collapse occurrences of
432 	 * "previous/..", so long as "previous" is not itself "..".
433 	 * Exhausting rpnp results in error ENAMETOOLONG.
434 	 */
435 	if (rpnp && strcmp(component, ".") != 0) {
436 		size_t len;
437 
438 		if (strcmp(component, "..") == 0 &&
439 		    rpnp->pn_pathlen != 0 &&
440 		    !((rpnp->pn_pathlen > 2 &&
441 		    strncmp(rpnp->pn_path+rpnp->pn_pathlen-3, "/..", 3) == 0) ||
442 		    (rpnp->pn_pathlen == 2 &&
443 		    strncmp(rpnp->pn_path, "..", 2) == 0))) {
444 			while (rpnp->pn_pathlen &&
445 			    rpnp->pn_path[rpnp->pn_pathlen-1] != '/')
446 				rpnp->pn_pathlen--;
447 			if (rpnp->pn_pathlen > 1)
448 				rpnp->pn_pathlen--;
449 			rpnp->pn_path[rpnp->pn_pathlen] = '\0';
450 		} else {
451 			if (rpnp->pn_pathlen != 0 &&
452 			    rpnp->pn_path[rpnp->pn_pathlen-1] != '/')
453 				rpnp->pn_path[rpnp->pn_pathlen++] = '/';
454 			if (flags & FIGNORECASE) {
455 				/*
456 				 * Return the case-preserved name
457 				 * within the resolved path.
458 				 */
459 				error = copystr(pp->pn_buf,
460 				    rpnp->pn_path + rpnp->pn_pathlen,
461 				    rpnp->pn_bufsize - rpnp->pn_pathlen, &len);
462 			} else {
463 				error = copystr(component,
464 				    rpnp->pn_path + rpnp->pn_pathlen,
465 				    rpnp->pn_bufsize - rpnp->pn_pathlen, &len);
466 			}
467 			if (error)	/* copystr() returns ENAMETOOLONG */
468 				goto bad;
469 			rpnp->pn_pathlen += (len - 1);
470 			ASSERT(rpnp->pn_bufsize > rpnp->pn_pathlen);
471 		}
472 	}
473 
474 	/*
475 	 * If no more components, return last directory (if wanted) and
476 	 * last component (if wanted).
477 	 */
478 	if (pn_pathleft(pnp) == 0) {
479 		/*
480 		 * If there was a trailing slash in the pathname,
481 		 * make sure the last component is a directory.
482 		 */
483 		if (must_be_directory && cvp->v_type != VDIR) {
484 			error = ENOTDIR;
485 			goto bad;
486 		}
487 		if (dirvpp != NULL) {
488 			/*
489 			 * Check that we have the real parent and not
490 			 * an alias of the last component.
491 			 */
492 			if (vn_compare(vp, cvp)) {
493 				pn_setlast(pnp);
494 				VN_RELE(vp);
495 				VN_RELE(cvp);
496 				if (rootvp != rootdir)
497 					VN_RELE(rootvp);
498 				if (pp)
499 					pn_free(pp);
500 				return (EINVAL);
501 			}
502 			*dirvpp = vp;
503 		} else
504 			VN_RELE(vp);
505 		if (pnp->pn_path == pnp->pn_buf)
506 			(void) pn_set(pnp, ".");
507 		else
508 			pn_setlast(pnp);
509 		if (rpnp) {
510 			if (VN_CMP(cvp, rootvp))
511 				(void) pn_set(rpnp, "/");
512 			else if (rpnp->pn_pathlen == 0)
513 				(void) pn_set(rpnp, ".");
514 		}
515 
516 		if (compvpp != NULL)
517 			*compvpp = cvp;
518 		else
519 			VN_RELE(cvp);
520 		if (rootvp != rootdir)
521 			VN_RELE(rootvp);
522 		if (pp)
523 			pn_free(pp);
524 		return (0);
525 	}
526 
527 	/*
528 	 * Skip over slashes from end of last component.
529 	 */
530 	while (pnp->pn_path[0] == '/') {
531 		pnp->pn_path++;
532 		pnp->pn_pathlen--;
533 	}
534 
535 	/*
536 	 * Searched through another level of directory:
537 	 * release previous directory handle and save new (result
538 	 * of lookup) as current directory.
539 	 */
540 	VN_RELE(vp);
541 	vp = cvp;
542 	cvp = NULL;
543 	goto next;
544 
545 bad:
546 	/*
547 	 * Error.  Release vnodes and return.
548 	 */
549 	if (cvp)
550 		VN_RELE(cvp);
551 	/*
552 	 * If the error was ESTALE and the current directory to look in
553 	 * was the root for this lookup, the root for a mounted file
554 	 * system, or the starting directory for lookups, then
555 	 * return ENOENT instead of ESTALE.  In this case, no recovery
556 	 * is possible by the higher level.  If ESTALE was returned for
557 	 * some intermediate directory along the path, then recovery
558 	 * is potentially possible and retrying from the higher level
559 	 * will either correct the situation by purging stale cache
560 	 * entries or eventually get back to the point where no recovery
561 	 * is possible.
562 	 */
563 	if (error == ESTALE &&
564 	    (VN_CMP(vp, rootvp) || (vp->v_flag & VROOT) || vp == startvp))
565 		error = ENOENT;
566 	VN_RELE(vp);
567 	if (rootvp != rootdir)
568 		VN_RELE(rootvp);
569 	if (pp)
570 		pn_free(pp);
571 	return (error);
572 }
573 
574 /*
575  * Traverse a mount point.  Routine accepts a vnode pointer as a reference
576  * parameter and performs the indirection, releasing the original vnode.
577  */
578 int
579 traverse(vnode_t **cvpp)
580 {
581 	int error = 0;
582 	vnode_t *cvp;
583 	vnode_t *tvp;
584 	vfs_t *vfsp;
585 
586 	cvp = *cvpp;
587 
588 	/*
589 	 * If this vnode is mounted on, then we transparently indirect
590 	 * to the vnode which is the root of the mounted file system.
591 	 * Before we do this we must check that an unmount is not in
592 	 * progress on this vnode.
593 	 */
594 
595 	for (;;) {
596 		/*
597 		 * Used to try to read lock the vnode here.
598 		 */
599 
600 		/*
601 		 * Reached the end of the mount chain?
602 		 */
603 		vfsp = vn_mountedvfs(cvp);
604 		if (vfsp == NULL) {
605 			break;
606 		}
607 
608 		/*
609 		 * The read lock must be held across the call to VFS_ROOT() to
610 		 * prevent a concurrent unmount from destroying the vfs.
611 		 */
612 		error = VFS_ROOT(vfsp, &tvp);
613 		if (error)
614 			break;
615 
616 		VN_RELE(cvp);
617 
618 		cvp = tvp;
619 	}
620 
621 	*cvpp = cvp;
622 	return (error);
623 }
624 
625 /*
626  * Get the vnode path, relative to the passed rootvp.
627  * Our vncache always fills in v_path, so this is easy.
628  */
629 /* ARGSUSED */
630 int
631 vnodetopath(vnode_t *vrootp, vnode_t *vp, char *buf, size_t buflen, cred_t *cr)
632 {
633 	int len, rvp_len = 0;
634 	const char *p = vp->v_path;
635 
636 	if (vrootp)
637 		rvp_len = strlen(vrootp->v_path);
638 	len = strlen(p);
639 	if (rvp_len < len)
640 		p += rvp_len;
641 	else
642 		p = "/";
643 
644 	(void) strlcpy(buf, p, buflen);
645 	return (0);
646 }
647