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