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