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