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