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