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 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 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 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 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 * 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 * 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 * 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 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 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 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 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 * 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 * 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 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 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 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 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 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 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 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 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