xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_pathname.c (revision 8b2cc8ac894f2d58f38cf2fb7c3ac778f4c57c09)
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 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <smbsrv/smb_incl.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 
36 uint32_t
37 smb_is_executable(char *path)
38 {
39 	char	extension[5];
40 	int	len = strlen(path);
41 
42 	if ((len >= 4) && (path[len - 4] == '.')) {
43 		(void) strcpy(extension, &path[len - 3]);
44 		(void) utf8_strupr(extension);
45 
46 		if (strcmp(extension, "EXE") == 0)
47 			return (NODE_FLAGS_EXECUTABLE);
48 
49 		if (strcmp(extension, "COM") == 0)
50 			return (NODE_FLAGS_EXECUTABLE);
51 
52 		if (strcmp(extension, "DLL") == 0)
53 			return (NODE_FLAGS_EXECUTABLE);
54 
55 		if (strcmp(extension, "SYM") == 0)
56 			return (NODE_FLAGS_EXECUTABLE);
57 	}
58 
59 	return (0);
60 }
61 
62 /*
63  * smbd_fs_query
64  *
65  * Upon success, the caller will need to call smb_node_release() on
66  * fqi.last_snode (if it isn't already set to NULL by this routine) and
67  * and fqi.dir_snode.  These pointers will not be used after the caller
68  * is done with them and should be released immediately.  (The position
69  * of smb_fqi in a union in the smb_request structure makes it difficult
70  * to free these pointers at smb_request deallocation time.)
71  *
72  * If smbd_fs_query() returns error, no smb_nodes will need to be released
73  * by callers as a result of references taken in this routine, and
74  * fqi.last_snode and fqi.dir_snode will be set to NULL.
75  */
76 
77 int
78 smbd_fs_query(smb_request_t *sr, smb_fqi_t *fqi, int fqm)
79 {
80 	int rc;
81 
82 	fqi->last_comp_was_found = 0;
83 
84 	rc = smb_pathname_reduce(sr, sr->user_cr, fqi->path,
85 	    sr->tid_tree->t_snode, sr->tid_tree->t_snode, &fqi->dir_snode,
86 	    fqi->last_comp);
87 
88 	if (rc)
89 		return (rc);
90 
91 	rc = smb_fsop_lookup(sr, sr->user_cr, SMB_FOLLOW_LINKS,
92 	    sr->tid_tree->t_snode, fqi->dir_snode, fqi->last_comp,
93 	    &fqi->last_snode, &fqi->last_attr);
94 
95 	if (rc == 0) {
96 		fqi->last_comp_was_found = 1;
97 		(void) strcpy(fqi->last_comp_od,
98 		    fqi->last_snode->od_name);
99 
100 		if (fqm == FQM_PATH_MUST_NOT_EXIST) {
101 			smb_node_release(fqi->dir_snode);
102 			smb_node_release(fqi->last_snode);
103 			SMB_NULL_FQI_NODES(*fqi);
104 			return (EEXIST);
105 		}
106 
107 		return (0);
108 	}
109 
110 	if (fqm == FQM_PATH_MUST_EXIST) {
111 		smb_node_release(fqi->dir_snode);
112 		SMB_NULL_FQI_NODES(*fqi);
113 		return (rc);
114 	}
115 
116 	if (rc == ENOENT) {
117 		fqi->last_snode = NULL;
118 		return (0);
119 	}
120 
121 	smb_node_release(fqi->dir_snode);
122 	SMB_NULL_FQI_NODES(*fqi);
123 
124 	return (rc);
125 }
126 
127 /*
128  * smb_pathname_reduce
129  *
130  * smb_pathname_reduce() takes a path and returns the smb_node for the
131  * second-to-last component of the path.  It also returns the name of the last
132  * component.  Pointers for both of these fields must be supplied by the caller.
133  *
134  * Upon success, 0 is returned.
135  *
136  * Upon error, *dir_node will be set to 0.
137  *
138  * *sr (in)
139  * ---
140  * smb_request structure pointer
141  *
142  * *cred (in)
143  * -----
144  * credential
145  *
146  * *path (in)
147  * -----
148  * pathname to be looked up
149  *
150  * *share_root_node (in)
151  * ----------------
152  * File operations which are share-relative should pass sr->tid_tree->t_snode.
153  * If the call is not for a share-relative operation, this parameter must be 0
154  * (e.g. the call from smbsr_setup_share()).  (Such callers will have path
155  * operations done using root_smb_node.)  This parameter is used to determine
156  * whether mount points can be crossed.
157  *
158  * share_root_node should have at least one reference on it.  This reference
159  * will stay intact throughout this routine.
160  *
161  * *cur_node (in)
162  * ---------
163  * The smb_node for the current directory (for relative paths).
164  * cur_node should have at least one reference on it.
165  * This reference will stay intact throughout this routine.
166  *
167  * **dir_node (out)
168  * ----------
169  * Directory for the penultimate component of the original path.
170  * (Note that this is not the same as the parent directory of the ultimate
171  * target in the case of a link.)
172  *
173  * The directory smb_node is returned held.  The caller will need to release
174  * the hold or otherwise make sure it will get released (e.g. in a destroy
175  * routine if made part of a global structure).
176  *
177  * last_component (out)
178  * --------------
179  * The last component of the path.  (This may be different from the name of any
180  * link target to which the last component may resolve.)
181  *
182  *
183  * ____________________________
184  *
185  * The CIFS server lookup path needs to have logic equivalent to that of
186  * smb_fsop_lookup(), smb_vop_lookup() and other smb_vop_*() routines in the
187  * following areas:
188  *
189  *	- non-traversal of child mounts		(handled by smb_pathname_reduce)
190  *	- unmangling 				(handled in smb_pathname)
191  *	- "chroot" behavior of share root 	(handled by lookuppnvp)
192  *
193  * In addition, it needs to replace backslashes with forward slashes.  It also
194  * ensures that link processing is done correctly, and that directory
195  * information requested by the caller is correctly returned (i.e. for paths
196  * with a link in the last component, the directory information of the
197  * link and not the target needs to be returned).
198  */
199 
200 int
201 smb_pathname_reduce(
202     smb_request_t	*sr,
203     cred_t		*cred,
204     const char		*path,
205     smb_node_t		*share_root_node,
206     smb_node_t		*cur_node,
207     smb_node_t		**dir_node,
208     char		*last_component)
209 {
210 	smb_node_t	*root_node;
211 	pathname_t	ppn;
212 	char		*usepath;
213 	int		lookup_flags = FOLLOW;
214 	int 		trailing_slash = 0;
215 	int		err = 0;
216 	int		len;
217 	smb_node_t	*vss_cur_node;
218 	smb_node_t	*vss_root_node;
219 	smb_node_t	*local_cur_node;
220 	smb_node_t	*local_root_node;
221 
222 	ASSERT(dir_node);
223 	ASSERT(last_component);
224 
225 	*dir_node = NULL;
226 	*last_component = '\0';
227 	vss_cur_node = NULL;
228 	vss_root_node = NULL;
229 
230 	if (sr && sr->tid_tree) {
231 		if (!STYPE_ISDSK(sr->tid_tree->t_res_type))
232 			return (EACCES);
233 	}
234 
235 	if (SMB_TREE_IS_CASEINSENSITIVE(sr))
236 		lookup_flags |= FIGNORECASE;
237 
238 	if (path == NULL)
239 		return (EINVAL);
240 
241 	if (*path == '\0')
242 		return (ENOENT);
243 
244 	usepath = kmem_alloc(MAXPATHLEN, KM_SLEEP);
245 
246 	if ((len = strlcpy(usepath, path, MAXPATHLEN)) >= MAXPATHLEN) {
247 		kmem_free(usepath, MAXPATHLEN);
248 		return (ENAMETOOLONG);
249 	}
250 
251 	(void) strsubst(usepath, '\\', '/');
252 
253 	if (share_root_node)
254 		root_node = share_root_node;
255 	else
256 		root_node = sr->sr_server->si_root_smb_node;
257 
258 	if (cur_node == NULL)
259 		cur_node = root_node;
260 
261 	local_cur_node = cur_node;
262 	local_root_node = root_node;
263 
264 	if (sr && (sr->smb_flg2 & SMB_FLAGS2_REPARSE_PATH)) {
265 		err = smb_vss_lookup_nodes(sr, root_node, cur_node,
266 		    usepath, &vss_cur_node, &vss_root_node);
267 
268 		if (err != 0) {
269 			kmem_free(usepath, MAXPATHLEN);
270 			return (err);
271 		}
272 
273 		len = strlen(usepath);
274 		local_cur_node = vss_cur_node;
275 		local_root_node = vss_root_node;
276 	}
277 
278 	if (usepath[len - 1] == '/')
279 		trailing_slash = 1;
280 
281 	(void) strcanon(usepath, "/");
282 
283 	(void) pn_alloc(&ppn);
284 
285 	if ((err = pn_set(&ppn, usepath)) != 0) {
286 		(void) pn_free(&ppn);
287 		kmem_free(usepath, MAXPATHLEN);
288 		if (vss_cur_node != NULL)
289 			(void) smb_node_release(vss_cur_node);
290 		if (vss_root_node != NULL)
291 			(void) smb_node_release(vss_root_node);
292 		return (err);
293 	}
294 
295 	/*
296 	 * If a path does not have a trailing slash, strip off the
297 	 * last component.  (We only need to return an smb_node for
298 	 * the second to last component; a name is returned for the
299 	 * last component.)
300 	 */
301 
302 	if (trailing_slash) {
303 		(void) strlcpy(last_component, ".", MAXNAMELEN);
304 	} else {
305 		(void) pn_setlast(&ppn);
306 		(void) strlcpy(last_component, ppn.pn_path, MAXNAMELEN);
307 		ppn.pn_path[0] = '\0';
308 	}
309 
310 	if ((strcmp(ppn.pn_buf, "/") == 0) || (ppn.pn_buf[0] == '\0')) {
311 		smb_node_ref(local_cur_node);
312 		*dir_node = local_cur_node;
313 	} else {
314 		err = smb_pathname(sr, ppn.pn_buf, lookup_flags,
315 		    local_root_node, local_cur_node, NULL, dir_node, cred);
316 	}
317 
318 	(void) pn_free(&ppn);
319 	kmem_free(usepath, MAXPATHLEN);
320 
321 	/*
322 	 * Prevent access to anything outside of the share root, except
323 	 * when mapping a share because that may require traversal from
324 	 * / to a mounted file system.  share_root_node is NULL when
325 	 * mapping a share.
326 	 *
327 	 * Note that we disregard whether the traversal of the path went
328 	 * outside of the file system and then came back (say via a link).
329 	 */
330 
331 	if ((err == 0) && share_root_node) {
332 		if (share_root_node->vp->v_vfsp != (*dir_node)->vp->v_vfsp)
333 			err = EACCES;
334 	}
335 
336 	if (err) {
337 		if (*dir_node) {
338 			(void) smb_node_release(*dir_node);
339 			*dir_node = NULL;
340 		}
341 		*last_component = 0;
342 	}
343 
344 	if (vss_cur_node != NULL)
345 		(void) smb_node_release(vss_cur_node);
346 	if (vss_root_node != NULL)
347 		(void) smb_node_release(vss_root_node);
348 
349 	return (err);
350 }
351 
352 /*
353  * smb_pathname()
354  * wrapper to lookuppnvp().  Handles name unmangling.
355  *
356  * *dir_node is the true directory of the target *node.
357  *
358  * If any component but the last in the path is not found, ENOTDIR instead of
359  * ENOENT will be returned.
360  *
361  * Path components are processed one at a time so that smb_nodes can be
362  * created for each component.  This allows the dir_snode field in the
363  * smb_node to be properly populated.
364  *
365  * Because of the above, links are also processed in this routine
366  * (i.e., we do not pass the FOLLOW flag to lookuppnvp()).  This
367  * will allow smb_nodes to be created for each component of a link.
368  *
369  * Mangle checking is per component. If a name is mangled, when the
370  * unmangled name is passed to smb_pathname_lookup() do not pass
371  * FIGNORECASE, since the unmangled name is the real on-disk name.
372  * Otherwise pass FIGNORECASE if it's set in flags. This will cause the
373  * file system to return "first match" in the event of a case collision.
374  *
375  * If CATIA character translation is enabled it is applied to each
376  * component before passing the component to smb_pathname_lookup().
377  * After smb_pathname_lookup() the reverse translation is applied.
378  */
379 
380 int
381 smb_pathname(smb_request_t *sr, char *path, int flags,
382     smb_node_t *root_node, smb_node_t *cur_node, smb_node_t **dir_node,
383     smb_node_t **ret_node, cred_t *cred)
384 {
385 	char		*component, *real_name, *namep;
386 	pathname_t	pn, rpn, upn, link_pn;
387 	smb_node_t	*dnode, *fnode;
388 	vnode_t		*rootvp, *vp;
389 	smb_attr_t	attr;
390 	size_t		pathleft;
391 	int		err = 0;
392 	int		nlink = 0;
393 	int		local_flags;
394 	char		namebuf[MAXNAMELEN];
395 
396 	if (path == NULL)
397 		return (EINVAL);
398 
399 	ASSERT(root_node);
400 	ASSERT(cur_node);
401 	ASSERT(ret_node);
402 
403 	*ret_node = NULL;
404 
405 	if (dir_node)
406 		*dir_node = NULL;
407 
408 	(void) pn_alloc(&upn);
409 
410 	if ((err = pn_set(&upn, path)) != 0) {
411 		(void) pn_free(&upn);
412 		return (err);
413 	}
414 
415 	(void) pn_alloc(&pn);
416 	(void) pn_alloc(&rpn);
417 
418 	component = kmem_alloc(MAXNAMELEN, KM_SLEEP);
419 	real_name = kmem_alloc(MAXNAMELEN, KM_SLEEP);
420 
421 	fnode = NULL;
422 	dnode = cur_node;
423 	smb_node_ref(dnode);
424 	rootvp = root_node->vp;
425 
426 	while ((pathleft = pn_pathleft(&upn)) != 0) {
427 		if (fnode) {
428 			smb_node_release(dnode);
429 			dnode = fnode;
430 			fnode = NULL;
431 		}
432 
433 		if ((err = pn_getcomponent(&upn, component)) != 0)
434 			break;
435 
436 		if ((namep = smb_pathname_catia_v5tov4(sr, component,
437 		    namebuf, sizeof (namebuf))) == NULL) {
438 			err = EILSEQ;
439 			break;
440 		}
441 
442 		if ((err = pn_set(&pn, namep)) != 0)
443 			break;
444 
445 		local_flags = flags & FIGNORECASE;
446 		err = smb_pathname_lookup(&pn, &rpn, local_flags,
447 		    &vp, rootvp, dnode->vp, cred);
448 
449 		if (err) {
450 			if (smb_maybe_mangled_name(component) == 0)
451 				break;
452 
453 			if ((err = smb_unmangle_name(dnode, component,
454 			    real_name, MAXNAMELEN)) != 0)
455 				break;
456 
457 			if ((namep = smb_pathname_catia_v5tov4(sr, real_name,
458 			    namebuf, sizeof (namebuf))) == NULL) {
459 				err = EILSEQ;
460 				break;
461 			}
462 
463 			if ((err = pn_set(&pn, namep)) != 0)
464 				break;
465 
466 			local_flags = 0;
467 			err = smb_pathname_lookup(&pn, &rpn, local_flags,
468 			    &vp, rootvp, dnode->vp, cred);
469 			if (err)
470 				break;
471 		}
472 
473 		if ((vp->v_type == VLNK) &&
474 		    ((flags & FOLLOW) || pn_pathleft(&upn))) {
475 
476 			if (++nlink > MAXSYMLINKS) {
477 				err = ELOOP;
478 				VN_RELE(vp);
479 				break;
480 			}
481 
482 			(void) pn_alloc(&link_pn);
483 			err = pn_getsymlink(vp, &link_pn, cred);
484 			VN_RELE(vp);
485 
486 			if (err == 0) {
487 				if (pn_pathleft(&link_pn) == 0)
488 					(void) pn_set(&link_pn, ".");
489 				err = pn_insert(&upn, &link_pn,
490 				    strlen(component));
491 			}
492 			pn_free(&link_pn);
493 
494 			if (err)
495 				break;
496 
497 			if (upn.pn_pathlen == 0) {
498 				err = ENOENT;
499 				break;
500 			}
501 
502 			if (upn.pn_path[0] == '/') {
503 				fnode = root_node;
504 				smb_node_ref(fnode);
505 			}
506 
507 			if (pn_fixslash(&upn))
508 				flags |= FOLLOW;
509 
510 		} else {
511 			if (flags & FIGNORECASE) {
512 				if (strcmp(rpn.pn_path, "/") != 0)
513 					pn_setlast(&rpn);
514 				namep = rpn.pn_path;
515 			} else {
516 				namep = pn.pn_path;
517 			}
518 
519 			namep = smb_pathname_catia_v4tov5(sr, namep,
520 			    namebuf, sizeof (namebuf));
521 
522 			fnode = smb_node_lookup(sr, NULL, cred, vp, namep,
523 			    dnode, NULL, &attr);
524 			VN_RELE(vp);
525 
526 			if (fnode == NULL) {
527 				err = ENOMEM;
528 				break;
529 			}
530 		}
531 
532 		while (upn.pn_path[0] == '/') {
533 			upn.pn_path++;
534 			upn.pn_pathlen--;
535 		}
536 
537 	}
538 
539 	if ((pathleft) && (err == ENOENT))
540 		err = ENOTDIR;
541 
542 	if (err) {
543 		if (fnode)
544 			smb_node_release(fnode);
545 		if (dnode)
546 			smb_node_release(dnode);
547 	} else {
548 		*ret_node = fnode;
549 
550 		if (dir_node)
551 			*dir_node = dnode;
552 		else
553 			smb_node_release(dnode);
554 	}
555 
556 	kmem_free(component, MAXNAMELEN);
557 	kmem_free(real_name, MAXNAMELEN);
558 	(void) pn_free(&pn);
559 	(void) pn_free(&rpn);
560 	(void) pn_free(&upn);
561 
562 	return (err);
563 }
564 
565 /*
566  * Holds on dvp and rootvp (if not rootdir) are required by lookuppnvp()
567  * and will be released within lookuppnvp().
568  */
569 static int
570 smb_pathname_lookup(pathname_t *pn, pathname_t *rpn, int flags,
571     vnode_t **vp, vnode_t *rootvp, vnode_t *dvp, cred_t *cred)
572 {
573 	int err;
574 
575 	*vp = NULL;
576 	VN_HOLD(dvp);
577 	if (rootvp != rootdir)
578 		VN_HOLD(rootvp);
579 
580 	err = lookuppnvp(pn, rpn, flags, NULL, vp, rootvp, dvp, cred);
581 	return (err);
582 }
583 
584 /*
585  * CATIA Translation of a pathname component prior to passing it to lookuppnvp
586  *
587  * If the translated component name contains a '/' NULL is returned.
588  * The caller should treat this as error EILSEQ. It is not valid to
589  * have a directory name with a '/'.
590  */
591 static char *
592 smb_pathname_catia_v5tov4(smb_request_t *sr, char *name,
593     char *namebuf, int buflen)
594 {
595 	char *namep;
596 
597 	if (SMB_TREE_SUPPORTS_CATIA(sr)) {
598 		namep = smb_vop_catia_v5tov4(name, namebuf, buflen);
599 		if (strchr(namep, '/') != NULL)
600 			return (NULL);
601 		return (namep);
602 	}
603 
604 	return (name);
605 }
606 
607 /*
608  * CATIA translation of a pathname component after returning from lookuppnvp
609  */
610 static char *
611 smb_pathname_catia_v4tov5(smb_request_t *sr, char *name,
612     char *namebuf, int buflen)
613 {
614 	if (SMB_TREE_SUPPORTS_CATIA(sr)) {
615 		smb_vop_catia_v4tov5(name, namebuf, buflen);
616 		return (namebuf);
617 	}
618 
619 	return (name);
620 }
621 
622 /*
623  * sr - needed to check for case sense
624  * path - non mangled path needed to be looked up from the startvp
625  * startvp - the vnode to start the lookup from
626  * rootvp - the vnode of the root of the filesystem
627  * returns the vnode found when starting at startvp and using the path
628  *
629  * Finds a vnode starting at startvp and parsing the non mangled path
630  */
631 
632 vnode_t *
633 smb_lookuppathvptovp(smb_request_t *sr, char *path, vnode_t *startvp,
634     vnode_t *rootvp)
635 {
636 	pathname_t pn;
637 	vnode_t *vp = NULL;
638 	int lookup_flags = FOLLOW;
639 
640 	if (SMB_TREE_IS_CASEINSENSITIVE(sr))
641 		lookup_flags |= FIGNORECASE;
642 
643 	(void) pn_alloc(&pn);
644 
645 	if (pn_set(&pn, path) == 0) {
646 		VN_HOLD(rootvp);
647 		if (rootvp != rootdir)
648 			VN_HOLD(rootvp);
649 
650 		/* lookuppnvp should release the holds */
651 		if (lookuppnvp(&pn, NULL, lookup_flags, NULL, &vp,
652 		    rootvp, startvp, kcred) != 0) {
653 			pn_free(&pn);
654 			return (NULL);
655 		}
656 	}
657 
658 	pn_free(&pn);
659 	return (vp);
660 }
661