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