xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_pathname.c (revision c43efa7f6b109f90d7f4962df8c0e1a94008d2d1)
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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
23  * Copyright 2019 Nexenta by DDN, Inc. All rights reserved.
24  * Copyright 2021-2023 RackTop Systems, Inc.
25  */
26 
27 #include <smbsrv/smb_kproto.h>
28 #include <smbsrv/smb_fsops.h>
29 #include <sys/pathname.h>
30 #include <sys/priv_const.h>
31 #include <sys/policy.h>
32 #include <sys/sdt.h>
33 
34 static char *smb_pathname_catia_v5tov4(smb_request_t *, char *, char *, int);
35 static char *smb_pathname_catia_v4tov5(smb_request_t *, char *, char *, int);
36 static int smb_pathname_lookup(pathname_t *, pathname_t *, int,
37     vnode_t **, vnode_t *, vnode_t *, smb_attr_t *attr, cred_t *);
38 static char *smb_pathname_strdup(smb_request_t *, const char *);
39 static char *smb_pathname_strcat(smb_request_t *, char *, const char *);
40 static void smb_pathname_preprocess(smb_request_t *, smb_pathname_t *);
41 static void smb_pathname_preprocess_quota(smb_request_t *, smb_pathname_t *);
42 static int smb_pathname_dfs_preprocess(smb_request_t *, char *, size_t);
43 static void smb_pathname_preprocess_adminshare(smb_request_t *,
44     smb_pathname_t *);
45 
46 
47 uint32_t
48 smb_is_executable(char *path)
49 {
50 	char	extension[5];
51 	int	len = strlen(path);
52 
53 	if ((len >= 4) && (path[len - 4] == '.')) {
54 		(void) strcpy(extension, &path[len - 3]);
55 		(void) smb_strupr(extension);
56 
57 		if (strcmp(extension, "EXE") == 0)
58 			return (NODE_FLAGS_EXECUTABLE);
59 
60 		if (strcmp(extension, "COM") == 0)
61 			return (NODE_FLAGS_EXECUTABLE);
62 
63 		if (strcmp(extension, "DLL") == 0)
64 			return (NODE_FLAGS_EXECUTABLE);
65 
66 		if (strcmp(extension, "SYM") == 0)
67 			return (NODE_FLAGS_EXECUTABLE);
68 	}
69 
70 	return (0);
71 }
72 
73 /*
74  * smb_pathname_reduce
75  *
76  * smb_pathname_reduce() takes a path and returns the smb_node for the
77  * second-to-last component of the path.  It also returns the name of the last
78  * component.  Pointers for both of these fields must be supplied by the caller.
79  *
80  * Upon success, 0 is returned.
81  *
82  * Upon error, *dir_node will be set to 0.
83  *
84  * *sr (in)
85  * ---
86  * smb_request structure pointer
87  *
88  * *cred (in)
89  * -----
90  * credential
91  *
92  * *path (in)
93  * -----
94  * pathname to be looked up
95  *
96  * *share_root_node (in)
97  * ----------------
98  * File operations which are share-relative should pass sr->tid_tree->t_snode.
99  * If the call is not for a share-relative operation, this parameter must be 0
100  * (e.g. the call from smbsr_setup_share()).  (Such callers will have path
101  * operations done using root_smb_node.)  This parameter is used to determine
102  * whether mount points can be crossed.
103  *
104  * share_root_node should have at least one reference on it.  This reference
105  * will stay intact throughout this routine.
106  *
107  * *cur_node (in)
108  * ---------
109  * The smb_node for the current directory (for relative paths).
110  * cur_node should have at least one reference on it.
111  * This reference will stay intact throughout this routine.
112  *
113  * **dir_node (out)
114  * ----------
115  * Directory for the penultimate component of the original path.
116  * (Note that this is not the same as the parent directory of the ultimate
117  * target in the case of a link.)
118  *
119  * The directory smb_node is returned held.  The caller will need to release
120  * the hold or otherwise make sure it will get released (e.g. in a destroy
121  * routine if made part of a global structure).
122  *
123  * last_component (out)
124  * --------------
125  * The last component of the path.  (This may be different from the name of any
126  * link target to which the last component may resolve.)
127  *
128  *
129  * ____________________________
130  *
131  * The CIFS server lookup path needs to have logic equivalent to that of
132  * smb_fsop_lookup(), smb_vop_lookup() and other smb_vop_*() routines in the
133  * following areas:
134  *
135  *	- traversal of child mounts (handled by smb_pathname_reduce)
136  *	- unmangling                (handled in smb_pathname)
137  *	- "chroot" behavior of share root (handled by lookuppnvp)
138  *
139  * In addition, it needs to replace backslashes with forward slashes.  It also
140  * ensures that link processing is done correctly, and that directory
141  * information requested by the caller is correctly returned (i.e. for paths
142  * with a link in the last component, the directory information of the
143  * link and not the target needs to be returned).
144  */
145 
146 int
147 smb_pathname_reduce(
148     smb_request_t	*sr,
149     cred_t		*cred,
150     const char		*path,
151     smb_node_t		*share_root_node,
152     smb_node_t		*cur_node,
153     smb_node_t		**dir_node,
154     char		*last_component)
155 {
156 	smb_node_t	*root_node;
157 	pathname_t	ppn = {0};
158 	pathname_t	mnt_pn = {0};
159 	char		*usepath;
160 	int		lookup_flags = FOLLOW;
161 	int		trailing_slash = 0;
162 	int		err = 0;
163 	int		len;
164 	smb_node_t	*vss_node;
165 	smb_node_t	*local_cur_node;
166 	smb_node_t	*local_root_node;
167 	boolean_t	chk_vss;
168 	char		*gmttoken;
169 
170 	ASSERT(dir_node);
171 	ASSERT(last_component);
172 
173 	*dir_node = NULL;
174 	*last_component = '\0';
175 	vss_node = NULL;
176 	gmttoken = NULL;
177 	chk_vss = B_FALSE;
178 
179 	if (sr && sr->tid_tree) {
180 		if (STYPE_ISIPC(sr->tid_tree->t_res_type))
181 			return (EACCES);
182 	}
183 
184 	if (SMB_TREE_IS_CASEINSENSITIVE(sr))
185 		lookup_flags |= FIGNORECASE;
186 
187 	if (path == NULL)
188 		return (EINVAL);
189 
190 	if (*path == '\0')
191 		return (ENOENT);
192 
193 	usepath = kmem_alloc(SMB_MAXPATHLEN, KM_SLEEP);
194 
195 	len = strlcpy(usepath, path, SMB_MAXPATHLEN);
196 	if (len >= SMB_MAXPATHLEN) {
197 		kmem_free(usepath, SMB_MAXPATHLEN);
198 		return (ENAMETOOLONG);
199 	}
200 
201 	(void) strsubst(usepath, '\\', '/');
202 
203 	if (share_root_node)
204 		root_node = share_root_node;
205 	else
206 		root_node = sr->sr_server->si_root_smb_node;
207 
208 	if (cur_node == NULL)
209 		cur_node = root_node;
210 
211 	local_cur_node = cur_node;
212 	local_root_node = root_node;
213 
214 	if (SMB_TREE_IS_DFSROOT(sr)) {
215 		int is_dfs;
216 		if (sr->session->dialect >= SMB_VERS_2_BASE)
217 			is_dfs = sr->smb2_hdr_flags &
218 			    SMB2_FLAGS_DFS_OPERATIONS;
219 		else
220 			is_dfs = sr->smb_flg2 & SMB_FLAGS2_DFS;
221 		if (is_dfs != 0) {
222 			err = smb_pathname_dfs_preprocess(sr, usepath,
223 			    SMB_MAXPATHLEN);
224 			if (err != 0) {
225 				kmem_free(usepath, SMB_MAXPATHLEN);
226 				return (err);
227 			}
228 			len = strlen(usepath);
229 		}
230 	}
231 
232 	if (sr != NULL) {
233 		if (sr->session->dialect >= SMB_VERS_2_BASE) {
234 			chk_vss = sr->arg.open.create_timewarp;
235 		} else {
236 			chk_vss = (sr->smb_flg2 &
237 			    SMB_FLAGS2_REPARSE_PATH) != 0;
238 
239 			if (chk_vss) {
240 				gmttoken = kmem_alloc(SMB_VSS_GMT_SIZE,
241 				    KM_SLEEP);
242 				err = smb_vss_extract_gmttoken(usepath,
243 				    gmttoken);
244 				if (err != 0) {
245 					kmem_free(usepath, SMB_MAXPATHLEN);
246 					kmem_free(gmttoken, SMB_VSS_GMT_SIZE);
247 					return (err);
248 				}
249 				len = strlen(usepath);
250 			}
251 		}
252 		if (chk_vss)
253 			(void) pn_alloc(&mnt_pn);
254 	}
255 
256 	if (usepath[len - 1] == '/')
257 		trailing_slash = 1;
258 
259 	(void) strcanon(usepath, "/");
260 
261 	(void) pn_alloc_sz(&ppn, SMB_MAXPATHLEN);
262 
263 	if ((err = pn_set(&ppn, usepath)) != 0) {
264 		(void) pn_free(&ppn);
265 		kmem_free(usepath, SMB_MAXPATHLEN);
266 		if (chk_vss)
267 			(void) pn_free(&mnt_pn);
268 		if (gmttoken != NULL)
269 			kmem_free(gmttoken, SMB_VSS_GMT_SIZE);
270 		return (err);
271 	}
272 
273 	/*
274 	 * If a path does not have a trailing slash, strip off the
275 	 * last component.  (We only need to return an smb_node for
276 	 * the second to last component; a name is returned for the
277 	 * last component.)
278 	 *
279 	 * For VSS requests, the last component might be a filesystem of its
280 	 * own, and we need to discover that before exiting this function,
281 	 * so allow the lookup to happen on the last component.
282 	 * We'll correct this later when we convert to the snapshot.
283 	 */
284 
285 	if (!chk_vss) {
286 		if (trailing_slash) {
287 			(void) strlcpy(last_component, ".", MAXNAMELEN);
288 		} else {
289 			(void) pn_setlast(&ppn);
290 			if (ppn.pn_pathlen >= MAXNAMELEN) {
291 				err = ENAMETOOLONG;
292 				goto end_not_vss;
293 			}
294 			(void) strlcpy(last_component, ppn.pn_path, MAXNAMELEN);
295 			ppn.pn_path[0] = '\0';
296 		}
297 	}
298 
299 	if ((strcmp(ppn.pn_buf, "/") == 0) || (ppn.pn_buf[0] == '\0')) {
300 		smb_node_ref(local_cur_node);
301 		*dir_node = local_cur_node;
302 	} else {
303 		err = smb_pathname(sr, ppn.pn_buf, lookup_flags,
304 		    local_root_node, local_cur_node, NULL, dir_node, cred,
305 		    chk_vss ? &mnt_pn : NULL);
306 	}
307 
308 end_not_vss:
309 	(void) pn_free(&ppn);
310 	kmem_free(usepath, SMB_MAXPATHLEN);
311 
312 	/*
313 	 * We need to try and convert to snapshots, even on error.
314 	 * This is to handle the following cases:
315 	 * - We're on the lowest level filesystem, but a directory got renamed
316 	 *   on the live version. We'll get ENOENT, but can still find it in
317 	 *   the snapshot.
318 	 * - The last component was actually a file. We need to leave the last
319 	 *   component in in case it is, itself, a mountpoint, but that means
320 	 *   we might get ENOTDIR if it's not actually a directory.
321 	 *
322 	 * Note that if you change the share-relative name of a mountpoint,
323 	 * you won't be able to access previous versions of files under it.
324 	 */
325 	if (chk_vss && *dir_node != NULL) {
326 		if ((err = smb_vss_lookup_nodes(sr, *dir_node, &vss_node,
327 		    gmttoken)) == 0) {
328 			char *p = mnt_pn.pn_path;
329 			size_t pathleft;
330 
331 			smb_node_release(*dir_node);
332 			*dir_node = NULL;
333 			pathleft = pn_pathleft(&mnt_pn);
334 
335 			if (pathleft == 0 || trailing_slash) {
336 				(void) strlcpy(last_component, ".", MAXNAMELEN);
337 			} else {
338 				(void) pn_setlast(&mnt_pn);
339 				if (ppn.pn_pathlen >= MAXNAMELEN) {
340 					err = ENAMETOOLONG;
341 					goto end_chk_vss;
342 				}
343 				(void) strlcpy(last_component, mnt_pn.pn_path,
344 				    MAXNAMELEN);
345 				mnt_pn.pn_path[0] = '\0';
346 				pathleft -= strlen(last_component);
347 			}
348 
349 			if (pathleft != 0) {
350 				err = smb_pathname(sr, p, lookup_flags,
351 				    vss_node, vss_node, NULL, dir_node, cred,
352 				    NULL);
353 			} else {
354 				*dir_node = vss_node;
355 				vss_node = NULL;
356 			}
357 		}
358 	}
359 
360 end_chk_vss:
361 	if (chk_vss)
362 		(void) pn_free(&mnt_pn);
363 	if (gmttoken != NULL)
364 		kmem_free(gmttoken, SMB_VSS_GMT_SIZE);
365 
366 	/*
367 	 * Prevent traversal to another file system if mount point
368 	 * traversal is disabled.
369 	 *
370 	 * Note that we disregard whether the traversal of the path went
371 	 * outside of the file system and then came back (say via a link).
372 	 * This means that only symlinks that are expressed relatively to
373 	 * the share root work.
374 	 *
375 	 * share_root_node is NULL when mapping a share, so we disregard
376 	 * that case.
377 	 */
378 
379 	if ((err == 0) && share_root_node) {
380 		if (share_root_node->vp->v_vfsp != (*dir_node)->vp->v_vfsp) {
381 			err = EACCES;
382 			if ((sr) && (sr)->tid_tree &&
383 			    smb_tree_has_feature((sr)->tid_tree,
384 			    SMB_TREE_TRAVERSE_MOUNTS))
385 				err = 0;
386 		}
387 	}
388 
389 	if (err) {
390 		if (*dir_node) {
391 			(void) smb_node_release(*dir_node);
392 			*dir_node = NULL;
393 		}
394 		*last_component = 0;
395 	}
396 
397 	if (vss_node != NULL)
398 		(void) smb_node_release(vss_node);
399 	return (err);
400 }
401 
402 /*
403  * smb_pathname()
404  * wrapper to lookuppnvp().  Handles name unmangling.
405  *
406  * *dir_node is the true directory of the target *node.
407  *
408  * If any component but the last in the path is not found, ENOTDIR instead of
409  * ENOENT will be returned.
410  *
411  * Path components are processed one at a time so that smb_nodes can be
412  * created for each component.  This allows the n_dnode field in the
413  * smb_node to be properly populated.
414  *
415  * Because of the above, links are also processed in this routine
416  * (i.e., we do not pass the FOLLOW flag to lookuppnvp()).  This
417  * will allow smb_nodes to be created for each component of a link.
418  *
419  * Mangle checking is per component. If a name is mangled, when the
420  * unmangled name is passed to smb_pathname_lookup() do not pass
421  * FIGNORECASE, since the unmangled name is the real on-disk name.
422  * Otherwise pass FIGNORECASE if it's set in flags. This will cause the
423  * file system to return "first match" in the event of a case collision.
424  *
425  * If CATIA character translation is enabled it is applied to each
426  * component before passing the component to smb_pathname_lookup().
427  * After smb_pathname_lookup() the reverse translation is applied.
428  */
429 
430 int
431 smb_pathname(smb_request_t *sr, char *path, int flags,
432     smb_node_t *root_node, smb_node_t *cur_node, smb_node_t **dir_node,
433     smb_node_t **ret_node, cred_t *cred, pathname_t *mnt_pn)
434 {
435 	char		*component, *real_name, *namep;
436 	pathname_t	pn, rpn, upn, link_pn;
437 	smb_node_t	*dnode, *fnode, *mnt_node;
438 	smb_attr_t	attr;
439 	vnode_t		*rootvp, *vp;
440 	size_t		pathleft;
441 	int		err = 0;
442 	int		nlink = 0;
443 	int		local_flags;
444 	uint32_t	abe_flag = 0;
445 	char		namebuf[MAXNAMELEN];
446 	vnode_t *fsrootvp = NULL;
447 
448 	if (path == NULL)
449 		return (EINVAL);
450 
451 	ASSERT(root_node);
452 	ASSERT(cur_node);
453 	ASSERT(ret_node);
454 
455 	*ret_node = NULL;
456 
457 	if (dir_node)
458 		*dir_node = NULL;
459 
460 	(void) pn_alloc_sz(&upn, SMB_MAXPATHLEN);
461 
462 	if ((err = pn_set(&upn, path)) != 0) {
463 		(void) pn_free(&upn);
464 		return (err);
465 	}
466 
467 	if (mnt_pn != NULL && (err = pn_set(mnt_pn, path) != 0)) {
468 		(void) pn_free(&upn);
469 		return (err);
470 	}
471 
472 	if (SMB_TREE_SUPPORTS_ABE(sr))
473 		abe_flag = SMB_ABE;
474 
475 	(void) pn_alloc(&pn);
476 	(void) pn_alloc(&rpn);
477 
478 	component = kmem_alloc(MAXNAMELEN, KM_SLEEP);
479 	real_name = kmem_alloc(MAXNAMELEN, KM_SLEEP);
480 
481 	if (mnt_pn != NULL) {
482 		mnt_node = cur_node;
483 		smb_node_ref(cur_node);
484 	} else
485 		mnt_node = NULL;
486 	fnode = NULL;
487 	dnode = cur_node;
488 	smb_node_ref(dnode);
489 	rootvp = root_node->vp;
490 
491 	while ((pathleft = pn_pathleft(&upn)) != 0) {
492 		if (fnode) {
493 			smb_node_release(dnode);
494 			dnode = fnode;
495 			fnode = NULL;
496 		}
497 
498 		if ((err = pn_getcomponent(&upn, component)) != 0)
499 			break;
500 
501 		if ((namep = smb_pathname_catia_v5tov4(sr, component,
502 		    namebuf, sizeof (namebuf))) == NULL) {
503 			err = EILSEQ;
504 			break;
505 		}
506 
507 		if ((err = pn_set(&pn, namep)) != 0)
508 			break;
509 
510 		/* We want the DOS attributes. */
511 		bzero(&attr, sizeof (attr));
512 		attr.sa_mask = SMB_AT_DOSATTR;
513 
514 		local_flags = flags & FIGNORECASE;
515 		err = smb_pathname_lookup(&pn, &rpn, local_flags,
516 		    &vp, rootvp, dnode->vp, &attr, cred);
517 
518 		if (err) {
519 			if (!SMB_TREE_SUPPORTS_SHORTNAMES(sr) ||
520 			    !smb_maybe_mangled(component))
521 				break;
522 
523 			if ((err = smb_unmangle(dnode, component,
524 			    real_name, MAXNAMELEN, abe_flag)) != 0)
525 				break;
526 
527 			if ((namep = smb_pathname_catia_v5tov4(sr, real_name,
528 			    namebuf, sizeof (namebuf))) == NULL) {
529 				err = EILSEQ;
530 				break;
531 			}
532 
533 			if ((err = pn_set(&pn, namep)) != 0)
534 				break;
535 
536 			local_flags = 0;
537 			err = smb_pathname_lookup(&pn, &rpn, local_flags,
538 			    &vp, rootvp, dnode->vp, &attr, cred);
539 			if (err)
540 				break;
541 		}
542 
543 		/*
544 		 * This check MUST be done before symlink check
545 		 * since a reparse point is of type VLNK but should
546 		 * not be handled like a regular symlink.
547 		 */
548 		if (attr.sa_dosattr & FILE_ATTRIBUTE_REPARSE_POINT) {
549 			err = EREMOTE;
550 			VN_RELE(vp);
551 			break;
552 		}
553 
554 		if ((vp->v_type == VLNK) &&
555 		    ((flags & FOLLOW) || pn_pathleft(&upn))) {
556 
557 			if (++nlink > MAXSYMLINKS) {
558 				err = ELOOP;
559 				VN_RELE(vp);
560 				break;
561 			}
562 
563 			(void) pn_alloc(&link_pn);
564 			err = pn_getsymlink(vp, &link_pn, cred);
565 			VN_RELE(vp);
566 
567 			if (err == 0) {
568 				if (pn_pathleft(&link_pn) == 0)
569 					(void) pn_set(&link_pn, ".");
570 				err = pn_insert(&upn, &link_pn,
571 				    strlen(component));
572 			}
573 			pn_free(&link_pn);
574 
575 			if (err)
576 				break;
577 
578 			if (upn.pn_pathlen == 0) {
579 				err = ENOENT;
580 				break;
581 			}
582 
583 			if (upn.pn_path[0] == '/') {
584 				fnode = root_node;
585 				smb_node_ref(fnode);
586 			}
587 
588 			if (pn_fixslash(&upn))
589 				flags |= FOLLOW;
590 
591 		} else {
592 			if (flags & FIGNORECASE) {
593 				if (strcmp(rpn.pn_path, "/") != 0)
594 					pn_setlast(&rpn);
595 				namep = rpn.pn_path;
596 			} else {
597 				namep = pn.pn_path;
598 			}
599 
600 			namep = smb_pathname_catia_v4tov5(sr, namep,
601 			    namebuf, sizeof (namebuf));
602 
603 			fnode = smb_node_lookup(sr, NULL, cred, vp, namep,
604 			    dnode, NULL);
605 			VN_RELE(vp);
606 
607 			if (fnode == NULL) {
608 				err = ENOMEM;
609 				break;
610 			}
611 		}
612 
613 		while (upn.pn_path[0] == '/') {
614 			upn.pn_path++;
615 			upn.pn_pathlen--;
616 		}
617 
618 		/*
619 		 * If the node we looked up is the root of a filesystem,
620 		 * snapshot the lookup so we can replay this after discovering
621 		 * the lowest mounted filesystem.
622 		 */
623 		if (mnt_pn != NULL &&
624 		    fnode != NULL &&
625 		    (err = VFS_ROOT(fnode->vp->v_vfsp, &fsrootvp)) == 0) {
626 			if (fsrootvp == fnode->vp) {
627 				mnt_pn->pn_pathlen = pn_pathleft(&upn);
628 				mnt_pn->pn_path = mnt_pn->pn_buf +
629 				    ((ptrdiff_t)upn.pn_path -
630 				    (ptrdiff_t)upn.pn_buf);
631 
632 				smb_node_ref(fnode);
633 				if (mnt_node != NULL)
634 					smb_node_release(mnt_node);
635 				mnt_node = fnode;
636 
637 			}
638 			VN_RELE(fsrootvp);
639 		}
640 	}
641 
642 	if ((pathleft) && (err == ENOENT))
643 		err = ENOTDIR;
644 
645 	if (mnt_node == NULL)
646 		mnt_pn = NULL;
647 
648 	/*
649 	 * We always want to return a node when we're doing VSS
650 	 * (mnt_pn != NULL)
651 	 */
652 	if (mnt_pn == NULL && err != 0) {
653 		if (fnode)
654 			smb_node_release(fnode);
655 		if (dnode)
656 			smb_node_release(dnode);
657 	} else {
658 		if (mnt_pn != NULL) {
659 			*ret_node = mnt_node;
660 			if (fnode != NULL)
661 				smb_node_release(fnode);
662 		} else {
663 			*ret_node = fnode;
664 		}
665 
666 		if (dir_node)
667 			*dir_node = dnode;
668 		else
669 			smb_node_release(dnode);
670 	}
671 
672 	kmem_free(component, MAXNAMELEN);
673 	kmem_free(real_name, MAXNAMELEN);
674 	(void) pn_free(&pn);
675 	(void) pn_free(&rpn);
676 	(void) pn_free(&upn);
677 
678 	return (err);
679 }
680 
681 /*
682  * Holds on dvp and rootvp (if not rootdir) are required by lookuppnvp()
683  * and will be released within lookuppnvp().
684  */
685 static int
686 smb_pathname_lookup(pathname_t *pn, pathname_t *rpn, int flags,
687     vnode_t **vp, vnode_t *rootvp, vnode_t *dvp, smb_attr_t *attr, cred_t *cred)
688 {
689 	int err;
690 
691 	*vp = NULL;
692 	VN_HOLD(dvp);
693 	if (rootvp != rootdir)
694 		VN_HOLD(rootvp);
695 
696 #ifdef _KERNEL
697 	/*
698 	 * When BYPASS_TRAVERSE_CHECKING is enabled, avoid EXECUTE access
699 	 * checks. See: smb_vop_lookup().
700 	 */
701 	if (smb_vop_priv_check(cred, PRIV_FILE_DAC_SEARCH, B_FALSE, dvp))
702 		flags |= LOOKUP_NOACLCHECK;
703 #endif
704 
705 	err = lookuppnvp(pn, rpn, flags, NULL, vp, rootvp, dvp, cred);
706 	if ((err == 0) && (attr != NULL))
707 		(void) smb_vop_getattr(*vp, NULL, attr, 0, zone_kcred());
708 
709 	return (err);
710 }
711 
712 /*
713  * CATIA Translation of a pathname component prior to passing it to lookuppnvp
714  *
715  * If the translated component name contains a '/' NULL is returned.
716  * The caller should treat this as error EILSEQ. It is not valid to
717  * have a directory name with a '/'.
718  */
719 static char *
720 smb_pathname_catia_v5tov4(smb_request_t *sr, char *name,
721     char *namebuf, int buflen)
722 {
723 	char *namep;
724 
725 	if (SMB_TREE_SUPPORTS_CATIA(sr)) {
726 		namep = smb_vop_catia_v5tov4(name, namebuf, buflen);
727 		if (strchr(namep, '/') != NULL)
728 			return (NULL);
729 		return (namep);
730 	}
731 
732 	return (name);
733 }
734 
735 /*
736  * CATIA translation of a pathname component after returning from lookuppnvp
737  */
738 static char *
739 smb_pathname_catia_v4tov5(smb_request_t *sr, char *name,
740     char *namebuf, int buflen)
741 {
742 	if (SMB_TREE_SUPPORTS_CATIA(sr)) {
743 		smb_vop_catia_v4tov5(name, namebuf, buflen);
744 		return (namebuf);
745 	}
746 
747 	return (name);
748 }
749 
750 /*
751  * sr - needed to check for case sense
752  * path - non mangled path needed to be looked up from the startvp
753  * startvp - the vnode to start the lookup from
754  * rootvp - the vnode of the root of the filesystem
755  * returns the vnode found when starting at startvp and using the path
756  *
757  * Finds a vnode starting at startvp and parsing the non mangled path
758  */
759 
760 vnode_t *
761 smb_lookuppathvptovp(smb_request_t *sr, char *path, vnode_t *startvp,
762     vnode_t *rootvp)
763 {
764 	pathname_t pn;
765 	vnode_t *vp = NULL;
766 	int lookup_flags = FOLLOW;
767 
768 	if (SMB_TREE_IS_CASEINSENSITIVE(sr))
769 		lookup_flags |= FIGNORECASE;
770 
771 	(void) pn_alloc(&pn);
772 
773 	if (pn_set(&pn, path) == 0) {
774 		VN_HOLD(startvp);
775 		if (rootvp != rootdir)
776 			VN_HOLD(rootvp);
777 
778 		/* lookuppnvp should release the holds */
779 		if (lookuppnvp(&pn, NULL, lookup_flags, NULL, &vp,
780 		    rootvp, startvp, zone_kcred()) != 0) {
781 			pn_free(&pn);
782 			return (NULL);
783 		}
784 	}
785 
786 	pn_free(&pn);
787 	return (vp);
788 }
789 
790 /*
791  * smb_pathname_init
792  * Parse path: pname\\fname:sname:stype
793  *
794  * Elements of the smb_pathname_t structure are allocated using request
795  * specific storage and will be free'd when the sr is destroyed.
796  *
797  * Populate pn structure elements with the individual elements
798  * of pn->pn_path. pn->pn_sname will contain the whole stream name
799  * including the stream type and preceding colon: :sname:%DATA
800  * pn_stype will point to the stream type within pn_sname.
801  *
802  * If the pname element is missing pn_pname will be set to NULL.
803  * If any other element is missing the pointer in pn will be NULL.
804  */
805 void
806 smb_pathname_init(smb_request_t *sr, smb_pathname_t *pn, char *path)
807 {
808 	char *pname, *fname, *sname;
809 	int len;
810 
811 	bzero(pn, sizeof (smb_pathname_t));
812 	pn->pn_path = smb_pathname_strdup(sr, path);
813 
814 	smb_pathname_preprocess(sr, pn);
815 
816 	/* parse pn->pn_path into its constituent parts */
817 	pname = pn->pn_path;
818 
819 	/*
820 	 * Split the string between the directory and filename.
821 	 * Either part may be empty.
822 	 *
823 	 * Fill in pn->pn_pname (the path name)
824 	 */
825 	fname = strrchr(pname, '\\');
826 	if (fname != NULL) {
827 		if (fname == pname) {
828 			/*
829 			 * Last '/' is at start of string.
830 			 * No directory part (dir is root)
831 			 */
832 			pn->pn_pname = NULL;
833 		} else {
834 			/*
835 			 * Directory part ends at the last '/'
836 			 * Temporarily truncate and copy
837 			 */
838 			*fname = '\0';
839 			pn->pn_pname =
840 			    smb_pathname_strdup(sr, pname);
841 			*fname = '\\';
842 		}
843 		++fname;
844 		/* fname is just after the '/' */
845 	} else {
846 		/*
847 		 * No '/' at all in the string.
848 		 * It's all filename
849 		 */
850 		fname = pname;
851 		pn->pn_pname = NULL;
852 	}
853 
854 	/*
855 	 * Find end of the filename part of the string,
856 	 * which may be the null terminator, or may be
857 	 * the start of the optional :sname suffix.
858 	 */
859 	sname = strchr(fname, ':');
860 	if (sname == NULL) {
861 		/*
862 		 * No :sname suffix.  We're done.
863 		 */
864 		pn->pn_fname = smb_pathname_strdup(sr, fname);
865 		return;
866 	}
867 
868 	/*
869 	 * We have a stream name, and maybe a stream type.
870 	 * Can't use smb_is_stream_name(fname) here because
871 	 * we need to allow sname="::$DATA"
872 	 */
873 	if (sname == fname) {
874 		/*
875 		 * The ":sname" part is at the start of
876 		 * the file name, which means that the
877 		 * file name is "" and this pathname
878 		 * refers to a stream on the directory.
879 		 */
880 		pn->pn_fname = NULL;
881 	} else {
882 		/*
883 		 * The filename part ends at the ':'
884 		 * Temporarily truncate and copy
885 		 */
886 		*sname = '\0';
887 		pn->pn_fname = smb_pathname_strdup(sr, fname);
888 		*sname = ':';
889 	}
890 
891 	/*
892 	 * Special case "::$DATA" which "points to"
893 	 * the "unnamed" stream (the file itself).
894 	 * Basically ignore the "::$DATA"
895 	 */
896 	if (strcasecmp(sname, "::$DATA") == 0) {
897 		ASSERT(sname >= pname &&
898 		    sname < (pname + strlen(pname)));
899 		*sname = '\0';
900 		return;
901 	}
902 
903 	/*
904 	 * sname points to ":sname:stype" in pn_path
905 	 * If ":stype" is missing, add it, then set
906 	 * pn_stype to point after the 2nd ':'
907 	 *
908 	 * Caller knows pn_stype is NOT allocated.
909 	 * Allocations here are free'd via smb_srm_fini
910 	 */
911 	pn->pn_sname = smb_pathname_strdup(sr, sname);
912 	pn->pn_stype = strchr(pn->pn_sname + 1, ':');
913 	if (pn->pn_stype) {
914 		(void) smb_strupr(pn->pn_stype);
915 	} else {
916 		len = strlen(pn->pn_sname);
917 		pn->pn_sname = smb_pathname_strcat(sr, pn->pn_sname, ":$DATA");
918 		pn->pn_stype = pn->pn_sname + len;
919 	}
920 	++pn->pn_stype;
921 }
922 
923 /*
924  * smb_pathname_preprocess
925  *
926  * Perform common pre-processing of pn->pn_path:
927  * - if the pn_path is blank, set it to '\\'
928  * - perform unicode wildcard converstion.
929  * - convert any '/' to '\\'
930  * - eliminate duplicate slashes
931  * - remove trailing slashes
932  * - quota directory specific pre-processing
933  */
934 static void
935 smb_pathname_preprocess(smb_request_t *sr, smb_pathname_t *pn)
936 {
937 	char *p;
938 
939 	/* treat empty path as "\\" */
940 	if (strlen(pn->pn_path) == 0) {
941 		pn->pn_path = smb_pathname_strdup(sr, "\\");
942 		return;
943 	}
944 
945 	if (sr->session->dialect < NT_LM_0_12)
946 		smb_convert_wildcards(pn->pn_path);
947 
948 	/* treat '/' as '\\' */
949 	(void) strsubst(pn->pn_path, '/', '\\');
950 
951 	(void) strcanon(pn->pn_path, "\\");
952 
953 	/* remove trailing '\\' */
954 	p = pn->pn_path + strlen(pn->pn_path) - 1;
955 	if ((p != pn->pn_path) && (*p == '\\'))
956 		*p = '\0';
957 
958 	smb_pathname_preprocess_quota(sr, pn);
959 	smb_pathname_preprocess_adminshare(sr, pn);
960 }
961 
962 /*
963  * smb_pathname_preprocess_quota
964  *
965  * There is a special file required by windows so that the quota
966  * tab will be displayed by windows clients. This is created in
967  * a special directory, $EXTEND, at the root of the shared file
968  * system. To hide this directory prepend a '.' (dot).
969  */
970 static void
971 smb_pathname_preprocess_quota(smb_request_t *sr, smb_pathname_t *pn)
972 {
973 	char *name = "$EXTEND";
974 	char *new_name = ".$EXTEND";
975 	char *p, *slash;
976 	int len;
977 
978 	if (!smb_node_is_vfsroot(sr->tid_tree->t_snode))
979 		return;
980 
981 	p = pn->pn_path;
982 
983 	/* ignore any initial "\\" */
984 	p += strspn(p, "\\");
985 	if (smb_strcasecmp(p, name, strlen(name)) != 0)
986 		return;
987 
988 	p += strlen(name);
989 	if ((*p != ':') && (*p != '\\') && (*p != '\0'))
990 		return;
991 
992 	slash = (pn->pn_path[0] == '\\') ? "\\" : "";
993 	len = strlen(pn->pn_path) + 2;
994 	pn->pn_path = smb_srm_alloc(sr, len);
995 	(void) snprintf(pn->pn_path, len, "%s%s%s", slash, new_name, p);
996 	(void) smb_strupr(pn->pn_path);
997 }
998 
999 /*
1000  * smb_pathname_preprocess_adminshare
1001  *
1002  * Convert any path with share name "C$" or "c$" (Admin share) in to lower case.
1003  */
1004 static void
1005 smb_pathname_preprocess_adminshare(smb_request_t *sr, smb_pathname_t *pn)
1006 {
1007 	if (strcasecmp(sr->tid_tree->t_sharename, "c$") == 0)
1008 		(void) smb_strlwr(pn->pn_path);
1009 }
1010 
1011 /*
1012  * smb_pathname_strdup
1013  *
1014  * Duplicate NULL terminated string s.
1015  *
1016  * The new string is allocated using request specific storage and will
1017  * be free'd when the sr is destroyed.
1018  */
1019 static char *
1020 smb_pathname_strdup(smb_request_t *sr, const char *s)
1021 {
1022 	char *s2;
1023 	size_t n;
1024 
1025 	n = strlen(s) + 1;
1026 	s2 = smb_srm_zalloc(sr, n);
1027 	(void) strlcpy(s2, s, n);
1028 	return (s2);
1029 }
1030 
1031 /*
1032  * smb_pathname_strcat
1033  *
1034  * Reallocate NULL terminated string s1 to accommodate
1035  * concatenating  NULL terminated string s2.
1036  * Append s2 and return resulting NULL terminated string.
1037  *
1038  * The string buffer is reallocated using request specific
1039  * storage and will be free'd when the sr is destroyed.
1040  */
1041 static char *
1042 smb_pathname_strcat(smb_request_t *sr, char *s1, const char *s2)
1043 {
1044 	size_t n;
1045 
1046 	n = strlen(s1) + strlen(s2) + 1;
1047 	s1 = smb_srm_rezalloc(sr, s1, n);
1048 	(void) strlcat(s1, s2, n);
1049 	return (s1);
1050 }
1051 
1052 /*
1053  * smb_pathname_validate
1054  *
1055  * Perform basic validation of pn:
1056  * - If first component of pn->path is ".." -> PATH_SYNTAX_BAD
1057  * - If there are wildcards in pn->pn_pname -> OBJECT_NAME_INVALID
1058  * - If fname is "." -> INVALID_OBJECT_NAME
1059  *
1060  * On unix .. at the root of a file system links to the root. Thus
1061  * an attempt to lookup "/../../.." will be the same as looking up "/"
1062  * CIFs clients expect the above to result in
1063  * NT_STATUS_OBJECT_PATH_SYNTAX_BAD. It is currently not possible
1064  * (and questionable if it's desirable) to deal with all cases
1065  * but paths beginning with \\.. are handled.
1066  *
1067  * Returns: B_TRUE if pn is valid,
1068  *          otherwise returns B_FALSE and sets error status in sr.
1069  *
1070  * XXX: Get rid of smbsr_error calls for SMB2
1071  */
1072 boolean_t
1073 smb_pathname_validate(smb_request_t *sr, smb_pathname_t *pn)
1074 {
1075 	char *path = pn->pn_path;
1076 
1077 	/* ignore any initial "\\" */
1078 	path += strspn(path, "\\");
1079 
1080 	/* If first component of path is ".." -> PATH_SYNTAX_BAD */
1081 	if ((strcmp(path, "..") == 0) || (strncmp(path, "..\\", 3) == 0)) {
1082 		smbsr_error(sr, NT_STATUS_OBJECT_PATH_SYNTAX_BAD,
1083 		    ERRDOS, ERROR_BAD_PATHNAME);
1084 		return (B_FALSE);
1085 	}
1086 
1087 	/* If there are wildcards in pn->pn_pname -> OBJECT_NAME_INVALID */
1088 	if (pn->pn_pname && smb_contains_wildcards(pn->pn_pname)) {
1089 		smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
1090 		    ERRDOS, ERROR_INVALID_NAME);
1091 		return (B_FALSE);
1092 	}
1093 
1094 	/* If fname is "." -> OBJECT_NAME_INVALID */
1095 	if (pn->pn_fname && (strcmp(pn->pn_fname, ".") == 0)) {
1096 		smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
1097 		    ERRDOS, ERROR_INVALID_NAME);
1098 		return (B_FALSE);
1099 	}
1100 
1101 	return (B_TRUE);
1102 }
1103 
1104 /*
1105  * smb_validate_dirname
1106  *
1107  * smb_pathname_validate() should have already been performed on pn.
1108  *
1109  * Very basic directory name validation:  checks for colons in a path.
1110  * Need to skip the drive prefix since it contains a colon.
1111  *
1112  * Returns: B_TRUE if the name is valid,
1113  *          otherwise returns B_FALSE and sets error status in sr.
1114  */
1115 boolean_t
1116 smb_validate_dirname(smb_request_t *sr, smb_pathname_t *pn)
1117 {
1118 	char *name;
1119 	char *path = pn->pn_path;
1120 
1121 	if ((name = path) != 0) {
1122 		name += strspn(name, "\\");
1123 
1124 		if (strchr(name, ':') != 0) {
1125 			smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY,
1126 			    ERRDOS, ERROR_INVALID_NAME);
1127 			return (B_FALSE);
1128 		}
1129 	}
1130 
1131 	if (pn->pn_sname)
1132 		return (smb_validate_stream_name(sr, pn));
1133 
1134 	return (B_TRUE);
1135 }
1136 
1137 /*
1138  * smb_validate_object_name
1139  *
1140  * smb_pathname_validate() should have already been pertformed on pn.
1141  *
1142  * Very basic file name validation.
1143  * For filenames, we check for names of the form "AAAn:". Names that
1144  * contain three characters, a single digit and a colon (:) are reserved
1145  * as DOS device names, i.e. "COM1:".
1146  * Stream name validation is handed off to smb_validate_stream_name
1147  *
1148  * Returns: B_TRUE if pn->pn_fname is valid,
1149  *          otherwise returns B_FALSE and sets error status in sr.
1150  */
1151 boolean_t
1152 smb_validate_object_name(smb_request_t *sr, smb_pathname_t *pn)
1153 {
1154 	if (pn->pn_fname &&
1155 	    strlen(pn->pn_fname) == 5 &&
1156 	    smb_isdigit(pn->pn_fname[3]) &&
1157 	    pn->pn_fname[4] == ':') {
1158 		smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
1159 		    ERRDOS, ERROR_INVALID_NAME);
1160 		return (B_FALSE);
1161 	}
1162 
1163 	if (pn->pn_sname)
1164 		return (smb_validate_stream_name(sr, pn));
1165 
1166 	return (B_TRUE);
1167 }
1168 
1169 /*
1170  * smb_stream_parse_name
1171  *
1172  * smb_stream_parse_name should only be called for a path that
1173  * contains a valid named stream.  Path validation should have
1174  * been performed before this function is called, typically by
1175  * calling smb_is_stream_name() just before this.
1176  *
1177  * Find the last component of path and split it into filename
1178  * and stream name.
1179  *
1180  * On return the named stream type will be present.  The stream
1181  * type defaults to ":$DATA", if it has not been defined
1182  * For example, 'stream' contains :<sname>:$DATA
1183  *
1184  * Output args: filename, stream both MAXNAMELEN
1185  */
1186 void
1187 smb_stream_parse_name(char *path, char *filename, char *stream)
1188 {
1189 	char *fname, *sname, *stype;
1190 	size_t flen, slen;
1191 
1192 	ASSERT(path);
1193 	ASSERT(filename);
1194 	ASSERT(stream);
1195 
1196 	fname = strrchr(path, '\\');
1197 	fname = (fname == NULL) ? path : fname + 1;
1198 	sname = strchr(fname, ':');
1199 	/* Caller makes sure there is a ':' in path. */
1200 	VERIFY(sname != NULL);
1201 	/* LINTED: possible ptrdiff_t overflow */
1202 	flen = sname - fname;
1203 	slen = strlen(sname);
1204 
1205 	if (flen > (MAXNAMELEN-1))
1206 		flen = (MAXNAMELEN-1);
1207 	(void) strncpy(filename, fname, flen);
1208 	filename[flen] = '\0';
1209 
1210 	if (slen > (MAXNAMELEN-1))
1211 		slen = (MAXNAMELEN-1);
1212 	(void) strncpy(stream, sname, slen);
1213 	stream[slen] = '\0';
1214 
1215 	/* Add a "stream type" if there isn't one. */
1216 	stype = strchr(stream + 1, ':');
1217 	if (stype == NULL)
1218 		(void) strlcat(stream, ":$DATA", MAXNAMELEN);
1219 	else
1220 		(void) smb_strupr(stype);
1221 }
1222 
1223 /*
1224  * smb_is_stream_name
1225  *
1226  * Determines if 'path' specifies a named stream.
1227  *
1228  * path is a NULL terminated string which could be a stream path.
1229  * [pathname/]fname[:stream_name[:stream_type]]
1230  *
1231  * - If there is no colon in the path or it's the last char
1232  *   then it's not a stream name
1233  *
1234  * - '::' is a non-stream and is commonly used by Windows to designate
1235  *   the unamed stream in the form "::$DATA"
1236  */
1237 boolean_t
1238 smb_is_stream_name(char *path)
1239 {
1240 	char *colonp;
1241 
1242 	if (path == NULL)
1243 		return (B_FALSE);
1244 
1245 	colonp = strchr(path, ':');
1246 	if ((colonp == NULL) || (*(colonp+1) == '\0'))
1247 		return (B_FALSE);
1248 
1249 	if (strstr(path, "::"))
1250 		return (B_FALSE);
1251 
1252 	return (B_TRUE);
1253 }
1254 
1255 /*
1256  * Is this stream node a "restricted" type?
1257  */
1258 boolean_t
1259 smb_strname_restricted(char *strname)
1260 {
1261 	char *stype;
1262 
1263 	stype = strrchr(strname, ':');
1264 	if (stype == NULL)
1265 		return (B_FALSE);
1266 
1267 	/*
1268 	 * Only ":$CA" is restricted (for now).
1269 	 */
1270 	if (strcmp(stype, ":$CA") == 0)
1271 		return (B_TRUE);
1272 
1273 	return (B_FALSE);
1274 }
1275 
1276 /*
1277  * smb_validate_stream_name
1278  *
1279  * B_FALSE will be returned, and the error status ser in the sr, if:
1280  * - the path is not a stream name
1281  * - a path is specified but the fname is ommitted.
1282  * - the stream_type is specified but not valid.
1283  *
1284  * Note: the stream type is case-insensitive.
1285  */
1286 boolean_t
1287 smb_validate_stream_name(smb_request_t *sr, smb_pathname_t *pn)
1288 {
1289 	static char *strmtype[] = {
1290 		"$CA",
1291 		"$DATA",
1292 		"$INDEX_ALLOCATION"
1293 	};
1294 	int i;
1295 
1296 	ASSERT(pn);
1297 	ASSERT(pn->pn_sname);
1298 
1299 	if (pn->pn_stype != NULL) {
1300 		for (i = 0; i < sizeof (strmtype) / sizeof (strmtype[0]); ++i) {
1301 			if (strcasecmp(pn->pn_stype, strmtype[i]) == 0)
1302 				return (B_TRUE);
1303 		}
1304 
1305 		smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
1306 		    ERRDOS, ERROR_INVALID_NAME);
1307 		return (B_FALSE);
1308 	}
1309 
1310 	return (B_TRUE);
1311 }
1312 
1313 /*
1314  * valid DFS I/O path:
1315  *
1316  * \server-or-domain\share
1317  * \server-or-domain\share\path
1318  *
1319  * All the returned errors by this function needs to be
1320  * checked against Windows.
1321  */
1322 static int
1323 smb_pathname_dfs_preprocess(smb_request_t *sr, char *path, size_t pathsz)
1324 {
1325 	smb_unc_t unc;
1326 	char *linkpath;
1327 	int rc;
1328 
1329 	if (sr->tid_tree == NULL)
1330 		return (0);
1331 
1332 	if ((rc = smb_unc_init(path, &unc)) != 0)
1333 		return (rc);
1334 
1335 	if (smb_strcasecmp(unc.unc_share, sr->tid_tree->t_sharename, 0)) {
1336 		smb_unc_free(&unc);
1337 		return (EINVAL);
1338 	}
1339 
1340 	linkpath = unc.unc_path;
1341 	(void) snprintf(path, pathsz, "/%s", (linkpath) ? linkpath : "");
1342 
1343 	smb_unc_free(&unc);
1344 	return (0);
1345 }
1346