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