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