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