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 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <smbsrv/smb_incl.h> 27 #include <smbsrv/smb_fsops.h> 28 #include <smbsrv/smbinfo.h> 29 #include <sys/nbmlock.h> 30 31 static int smb_delete_check_path(smb_request_t *, boolean_t *); 32 static int smb_delete_single_file(smb_request_t *, smb_error_t *); 33 static int smb_delete_multiple_files(smb_request_t *, smb_error_t *); 34 static int smb_delete_find_fname(smb_request_t *, smb_odir_t *); 35 static int smb_delete_check_dosattr(smb_request_t *, smb_error_t *); 36 static int smb_delete_remove_file(smb_request_t *, smb_error_t *); 37 38 static void smb_delete_error(smb_error_t *, uint32_t, uint16_t, uint16_t); 39 40 /* 41 * smb_com_delete 42 * 43 * The delete file message is sent to delete a data file. The appropriate 44 * Tid and additional pathname are passed. Read only files may not be 45 * deleted, the read-only attribute must be reset prior to file deletion. 46 * 47 * NT supports a hidden permission known as File Delete Child (FDC). If 48 * the user has FullControl access to a directory, the user is permitted 49 * to delete any object in the directory regardless of the permissions 50 * on the object. 51 * 52 * Client Request Description 53 * ================================== ================================= 54 * UCHAR WordCount; Count of parameter words = 1 55 * USHORT SearchAttributes; 56 * USHORT ByteCount; Count of data bytes; min = 2 57 * UCHAR BufferFormat; 0x04 58 * STRING FileName[]; File name 59 * 60 * Multiple files may be deleted in response to a single request as 61 * SMB_COM_DELETE supports wildcards 62 * 63 * SearchAttributes indicates the attributes that the target file(s) must 64 * have. If the attribute is zero then only normal files are deleted. If 65 * the system file or hidden attributes are specified then the delete is 66 * inclusive -both the specified type(s) of files and normal files are 67 * deleted. Attributes are described in the "Attribute Encoding" section 68 * of this document. 69 * 70 * If bit0 of the Flags2 field of the SMB header is set, a pattern is 71 * passed in, and the file has a long name, then the passed pattern much 72 * match the long file name for the delete to succeed. If bit0 is clear, a 73 * pattern is passed in, and the file has a long name, then the passed 74 * pattern must match the file's short name for the deletion to succeed. 75 * 76 * Server Response Description 77 * ================================== ================================= 78 * UCHAR WordCount; Count of parameter words = 0 79 * USHORT ByteCount; Count of data bytes = 0 80 * 81 * 4.2.10.1 Errors 82 * 83 * ERRDOS/ERRbadpath 84 * ERRDOS/ERRbadfile 85 * ERRDOS/ERRnoaccess 86 * ERRDOS/ERRbadshare # returned by NT for files that are already open 87 * ERRHRD/ERRnowrite 88 * ERRSRV/ERRaccess 89 * ERRSRV/ERRinvdevice 90 * ERRSRV/ERRinvid 91 * ERRSRV/ERRbaduid 92 */ 93 smb_sdrc_t 94 smb_pre_delete(smb_request_t *sr) 95 { 96 int rc; 97 smb_fqi_t *fqi; 98 99 fqi = &sr->arg.dirop.fqi; 100 101 if ((rc = smbsr_decode_vwv(sr, "w", &fqi->fq_sattr)) == 0) 102 rc = smbsr_decode_data(sr, "%S", sr, &fqi->fq_path.pn_path); 103 104 DTRACE_SMB_2(op__Delete__start, smb_request_t *, sr, smb_fqi_t *, fqi); 105 106 return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR); 107 } 108 109 void 110 smb_post_delete(smb_request_t *sr) 111 { 112 DTRACE_SMB_1(op__Delete__done, smb_request_t *, sr); 113 } 114 115 /* 116 * smb_com_delete 117 * 118 * 1. pre-process pathname - smb_delete_check_path() 119 * checks dot, bad path syntax, wildcards in path 120 * 121 * 2. process the path to get directory node & last_comp, 122 * store these in fqi 123 * - If smb_pathname_reduce cannot find the specified path, 124 * the error (ENOTDIR) is translated to NT_STATUS_OBJECT_PATH_NOT_FOUND 125 * if the target is a single file (no wildcards). If there are 126 * wildcards in the last_comp, NT_STATUS_OBJECT_NAME_NOT_FOUND is 127 * used instead. 128 * - If the directory node is the mount point and the last component 129 * is ".." NT_STATUS_OBJECT_PATH_SYNTAX_BAD is returned. 130 * 131 * 3. check access permissions 132 * 133 * 4. invoke the appropriate deletion routine to find and remove 134 * the specified file(s). 135 * - if target is a single file (no wildcards) - smb_delete_single_file 136 * - if the target contains wildcards - smb_delete_multiple_files 137 * 138 * Returns: SDRC_SUCCESS or SDRC_ERROR 139 */ 140 smb_sdrc_t 141 smb_com_delete(smb_request_t *sr) 142 { 143 int rc; 144 smb_error_t err; 145 uint32_t status; 146 boolean_t wildcards; 147 smb_fqi_t *fqi; 148 149 fqi = &sr->arg.dirop.fqi; 150 151 if (smb_delete_check_path(sr, &wildcards) != 0) 152 return (SDRC_ERROR); 153 154 rc = smb_pathname_reduce(sr, sr->user_cr, fqi->fq_path.pn_path, 155 sr->tid_tree->t_snode, sr->tid_tree->t_snode, 156 &fqi->fq_dnode, fqi->fq_last_comp); 157 if (rc == 0) { 158 if (fqi->fq_dnode->vp->v_type != VDIR) { 159 smb_node_release(fqi->fq_dnode); 160 rc = ENOTDIR; 161 } 162 } 163 if (rc != 0) { 164 if (rc == ENOTDIR) { 165 if (wildcards) 166 status = NT_STATUS_OBJECT_NAME_NOT_FOUND; 167 else 168 status = NT_STATUS_OBJECT_PATH_NOT_FOUND; 169 smbsr_error(sr, status, ERRDOS, ERROR_FILE_NOT_FOUND); 170 } else { 171 smbsr_errno(sr, rc); 172 } 173 174 return (SDRC_ERROR); 175 } 176 177 if ((fqi->fq_dnode == sr->tid_tree->t_snode) && 178 (strcmp(fqi->fq_last_comp, "..") == 0)) { 179 smb_node_release(fqi->fq_dnode); 180 smbsr_error(sr, NT_STATUS_OBJECT_PATH_SYNTAX_BAD, 181 ERRDOS, ERROR_BAD_PATHNAME); 182 return (SDRC_ERROR); 183 } 184 185 rc = smb_fsop_access(sr, sr->user_cr, fqi->fq_dnode, 186 FILE_LIST_DIRECTORY); 187 if (rc != 0) { 188 smb_node_release(fqi->fq_dnode); 189 smbsr_error(sr, NT_STATUS_ACCESS_DENIED, 190 ERRDOS, ERROR_ACCESS_DENIED); 191 return (SDRC_ERROR); 192 } 193 194 if (wildcards) 195 rc = smb_delete_multiple_files(sr, &err); 196 else 197 rc = smb_delete_single_file(sr, &err); 198 199 smb_node_release(fqi->fq_dnode); 200 201 if (rc != 0) 202 smbsr_set_error(sr, &err); 203 else 204 rc = smbsr_encode_empty_result(sr); 205 206 return (rc == 0 ? SDRC_SUCCESS : SDRC_ERROR); 207 } 208 209 /* 210 * smb_delete_single_file 211 * 212 * Find the specified file and, if its attributes match the search 213 * criteria, delete it. 214 * 215 * Returns 0 - success (file deleted) 216 * -1 - error, err is populated with error details 217 */ 218 static int 219 smb_delete_single_file(smb_request_t *sr, smb_error_t *err) 220 { 221 smb_fqi_t *fqi; 222 uint32_t status; 223 224 fqi = &sr->arg.dirop.fqi; 225 226 smb_pathname_setup(sr, &fqi->fq_path); 227 status = smb_validate_object_name(&fqi->fq_path); 228 if (status != NT_STATUS_SUCCESS) { 229 smb_delete_error(err, status, ERRDOS, ERROR_INVALID_NAME); 230 return (-1); 231 } 232 233 if (smb_fsop_lookup_name(sr, sr->user_cr, 0, sr->tid_tree->t_snode, 234 fqi->fq_dnode, fqi->fq_last_comp, &fqi->fq_fnode) != 0) { 235 smb_delete_error(err, NT_STATUS_OBJECT_NAME_NOT_FOUND, 236 ERRDOS, ERROR_FILE_NOT_FOUND); 237 return (-1); 238 } 239 240 if (smb_delete_check_dosattr(sr, err) != 0) { 241 smb_node_release(fqi->fq_fnode); 242 return (-1); 243 } 244 245 if (smb_delete_remove_file(sr, err) != 0) { 246 smb_node_release(fqi->fq_fnode); 247 return (-1); 248 } 249 250 smb_node_release(fqi->fq_fnode); 251 return (0); 252 } 253 254 /* 255 * smb_delete_multiple_files 256 * 257 * For each matching file found by smb_delete_find_fname: 258 * 1. lookup file 259 * 2. check the file's attributes 260 * - The search ends with an error if a readonly file 261 * (NT_STATUS_CANNOT_DELETE) is matched. 262 * - The search ends (but not an error) if a directory is 263 * matched and the request's search did not include 264 * directories. 265 * - Otherwise, if smb_delete_check_dosattr fails the file 266 * is skipped and the search continues (at step 1) 267 * 3. delete the file 268 * 269 * Returns 0 - success 270 * -1 - error, err is populated with error details 271 */ 272 static int 273 smb_delete_multiple_files(smb_request_t *sr, smb_error_t *err) 274 { 275 int rc, deleted = 0; 276 smb_fqi_t *fqi; 277 uint16_t odid; 278 smb_odir_t *od; 279 280 fqi = &sr->arg.dirop.fqi; 281 282 /* 283 * Specify all search attributes (SMB_SEARCH_ATTRIBUTES) so that 284 * delete-specific checking can be done (smb_delete_check_dosattr). 285 */ 286 odid = smb_odir_open(sr, fqi->fq_path.pn_path, 287 SMB_SEARCH_ATTRIBUTES, 0); 288 if (odid == 0) 289 return (-1); 290 291 if ((od = smb_tree_lookup_odir(sr->tid_tree, odid)) == NULL) 292 return (-1); 293 294 for (;;) { 295 rc = smb_delete_find_fname(sr, od); 296 if (rc != 0) 297 break; 298 299 rc = smb_fsop_lookup_name(sr, sr->user_cr, 0, 300 sr->tid_tree->t_snode, fqi->fq_dnode, 301 fqi->fq_od_name, &fqi->fq_fnode); 302 if (rc != 0) 303 break; 304 305 if (smb_delete_check_dosattr(sr, err) != 0) { 306 smb_node_release(fqi->fq_fnode); 307 if (err->status == NT_STATUS_CANNOT_DELETE) { 308 smb_odir_close(od); 309 smb_odir_release(od); 310 return (-1); 311 } 312 if ((err->status == NT_STATUS_FILE_IS_A_DIRECTORY) && 313 (SMB_SEARCH_DIRECTORY(fqi->fq_sattr) != 0)) 314 break; 315 continue; 316 } 317 318 if (smb_delete_remove_file(sr, err) == 0) { 319 ++deleted; 320 smb_node_release(fqi->fq_fnode); 321 continue; 322 } 323 if (err->status == NT_STATUS_OBJECT_NAME_NOT_FOUND) { 324 smb_node_release(fqi->fq_fnode); 325 continue; 326 } 327 328 smb_odir_close(od); 329 smb_odir_release(od); 330 smb_node_release(fqi->fq_fnode); 331 return (-1); 332 } 333 334 smb_odir_close(od); 335 smb_odir_release(od); 336 337 if ((rc != 0) && (rc != ENOENT)) { 338 smbsr_map_errno(rc, err); 339 return (-1); 340 } 341 342 if (deleted == 0) { 343 smb_delete_error(err, NT_STATUS_NO_SUCH_FILE, 344 ERRDOS, ERROR_FILE_NOT_FOUND); 345 return (-1); 346 } 347 348 return (0); 349 } 350 351 /* 352 * smb_delete_find_fname 353 * 354 * Find next filename that matches search pattern (fqi->fq_last_comp) 355 * and save it in fqi->fq_od_name. 356 * 357 * Case insensitivity note: 358 * If the tree is case insensitive and there's a case conflict 359 * with the name returned from smb_odir_read, smb_delete_find_fname 360 * performs case conflict name mangling to produce a unique filename. 361 * This ensures that any subsequent smb_fsop_lookup, (which will 362 * find the first case insensitive match) will find the correct file. 363 * 364 * Returns: 0 - success 365 * errno 366 */ 367 static int 368 smb_delete_find_fname(smb_request_t *sr, smb_odir_t *od) 369 { 370 int rc; 371 smb_odirent_t *odirent; 372 boolean_t eos; 373 char *name; 374 char shortname[SMB_SHORTNAMELEN]; 375 char name83[SMB_SHORTNAMELEN]; 376 smb_fqi_t *fqi; 377 378 fqi = &sr->arg.dirop.fqi; 379 odirent = kmem_alloc(sizeof (smb_odirent_t), KM_SLEEP); 380 381 rc = smb_odir_read(sr, od, odirent, &eos); 382 if (rc != 0) { 383 kmem_free(odirent, sizeof (smb_odirent_t)); 384 return (rc); 385 } 386 if (eos) { 387 kmem_free(odirent, sizeof (smb_odirent_t)); 388 return (ENOENT); 389 } 390 391 /* if case conflict, force mangle and use shortname */ 392 if ((od->d_flags & SMB_ODIR_FLAG_IGNORE_CASE) && 393 (odirent->od_eflags & ED_CASE_CONFLICT)) { 394 (void) smb_mangle_name(odirent->od_ino, odirent->od_name, 395 shortname, name83, 1); 396 name = shortname; 397 } else { 398 name = odirent->od_name; 399 } 400 (void) strlcpy(fqi->fq_od_name, name, sizeof (fqi->fq_od_name)); 401 402 kmem_free(odirent, sizeof (smb_odirent_t)); 403 return (0); 404 } 405 406 /* 407 * smb_delete_check_dosattr 408 * 409 * Check file's dos atributes to ensure that 410 * 1. the file is not a directory - NT_STATUS_FILE_IS_A_DIRECTORY 411 * 2. the file is not readonly - NT_STATUS_CANNOT_DELETE 412 * 3. the file's dos attributes comply with the specified search attributes 413 * If the file is either hidden or system and those attributes 414 * are not specified in the search attributes - NT_STATUS_NO_SUCH_FILE 415 * 416 * Returns: 0 - file's attributes pass all checks 417 * -1 - err populated with error details 418 */ 419 static int 420 smb_delete_check_dosattr(smb_request_t *sr, smb_error_t *err) 421 { 422 smb_fqi_t *fqi; 423 smb_node_t *node; 424 smb_attr_t attr; 425 uint16_t sattr; 426 427 fqi = &sr->arg.dirop.fqi; 428 sattr = fqi->fq_sattr; 429 node = fqi->fq_fnode; 430 431 if (smb_node_getattr(sr, node, &attr) != 0) { 432 smb_delete_error(err, NT_STATUS_INTERNAL_ERROR, 433 ERRDOS, ERROR_INTERNAL_ERROR); 434 return (-1); 435 } 436 437 if (attr.sa_dosattr & FILE_ATTRIBUTE_DIRECTORY) { 438 smb_delete_error(err, NT_STATUS_FILE_IS_A_DIRECTORY, 439 ERRDOS, ERROR_ACCESS_DENIED); 440 return (-1); 441 } 442 443 if (SMB_PATHFILE_IS_READONLY(sr, node)) { 444 smb_delete_error(err, NT_STATUS_CANNOT_DELETE, 445 ERRDOS, ERROR_ACCESS_DENIED); 446 return (-1); 447 } 448 449 if ((attr.sa_dosattr & FILE_ATTRIBUTE_HIDDEN) && 450 !(SMB_SEARCH_HIDDEN(sattr))) { 451 smb_delete_error(err, NT_STATUS_NO_SUCH_FILE, 452 ERRDOS, ERROR_FILE_NOT_FOUND); 453 return (-1); 454 } 455 456 if ((attr.sa_dosattr & FILE_ATTRIBUTE_SYSTEM) && 457 !(SMB_SEARCH_SYSTEM(sattr))) { 458 smb_delete_error(err, NT_STATUS_NO_SUCH_FILE, 459 ERRDOS, ERROR_FILE_NOT_FOUND); 460 return (-1); 461 } 462 463 return (0); 464 } 465 466 /* 467 * smb_delete_remove_file 468 * 469 * For consistency with Windows 2000, the range check should be done 470 * after checking for sharing violations. Attempting to delete a 471 * locked file will result in sharing violation, which is the same 472 * thing that will happen if you try to delete a non-locked open file. 473 * 474 * Note that windows 2000 rejects lock requests on open files that 475 * have been opened with metadata open modes. The error is 476 * STATUS_ACCESS_DENIED. 477 * 478 * NT does not always close a file immediately, which can cause the 479 * share and access checking to fail (the node refcnt is greater 480 * than one), and the file doesn't get deleted. Breaking the oplock 481 * before share and access checking gives the client a chance to 482 * close the file. 483 * 484 * Returns: 0 - success 485 * -1 - error, err populated with error details 486 */ 487 static int 488 smb_delete_remove_file(smb_request_t *sr, smb_error_t *err) 489 { 490 int rc; 491 uint32_t status; 492 smb_fqi_t *fqi; 493 smb_node_t *node; 494 uint32_t flags = 0; 495 496 fqi = &sr->arg.dirop.fqi; 497 node = fqi->fq_fnode; 498 499 (void) smb_oplock_break(node, sr->session, B_FALSE); 500 501 smb_node_start_crit(node, RW_READER); 502 503 status = smb_node_delete_check(node); 504 if (status != NT_STATUS_SUCCESS) { 505 smb_delete_error(err, NT_STATUS_SHARING_VIOLATION, 506 ERRDOS, ERROR_SHARING_VIOLATION); 507 smb_node_end_crit(node); 508 return (-1); 509 } 510 511 status = smb_range_check(sr, node, 0, UINT64_MAX, B_TRUE); 512 if (status != NT_STATUS_SUCCESS) { 513 smb_delete_error(err, NT_STATUS_ACCESS_DENIED, 514 ERRDOS, ERROR_ACCESS_DENIED); 515 smb_node_end_crit(node); 516 return (-1); 517 } 518 519 if (SMB_TREE_SUPPORTS_CATIA(sr)) 520 flags |= SMB_CATIA; 521 522 rc = smb_fsop_remove(sr, sr->user_cr, node->dir_snode, 523 node->od_name, flags); 524 if (rc != 0) { 525 if (rc == ENOENT) 526 smb_delete_error(err, NT_STATUS_OBJECT_NAME_NOT_FOUND, 527 ERRDOS, ERROR_FILE_NOT_FOUND); 528 else 529 smbsr_map_errno(rc, err); 530 531 smb_node_end_crit(node); 532 return (-1); 533 } 534 535 smb_node_end_crit(node); 536 return (0); 537 } 538 539 540 /* 541 * smb_delete_check_path 542 * 543 * Perform initial validation on the pathname and last_comp. 544 * 545 * wildcards in path: 546 * Wildcards in the path (excluding the last_comp) should result 547 * in NT_STATUS_OBJECT_NAME_INVALID. 548 * 549 * bad path syntax: 550 * On unix .. at the root of a file system links to the root. Thus 551 * an attempt to lookup "/../../.." will be the same as looking up "/" 552 * CIFs clients expect the above to result in 553 * NT_STATUS_OBJECT_PATH_SYNTAX_BAD. It is currently not possible 554 * (and questionable if it's desirable) to deal with all cases 555 * but paths beginning with \\.. are handled. See bad_paths[]. 556 * Cases like "\\dir\\..\\.." will be caught and handled after the 557 * pnreduce. Cases like "\\dir\\..\\..\\filename" will still result 558 * in "\\filename" which is contrary to windows behavior. 559 * 560 * dot: 561 * A filename of '.' should result in NT_STATUS_OBJECT_NAME_INVALID 562 * Any wildcard filename that resolves to '.' should result in 563 * NT_STATUS_OBJECT_NAME_INVALID if the search attributes include 564 * FILE_ATTRIBUTE_DIRECTORY 565 * 566 * Returns: 567 * 0: path is valid. Sets *wildcard to TRUE if wildcard delete 568 * i.e. if wildcards in last component 569 * -1: path is invalid. Sets error information in sr. 570 */ 571 static int 572 smb_delete_check_path(smb_request_t *sr, boolean_t *wildcard) 573 { 574 smb_fqi_t *fqi = &sr->arg.dirop.fqi; 575 char *p, *last_comp; 576 int i, wildcards; 577 smb_pathname_t *pn = &fqi->fq_path; 578 579 struct { 580 char *name; 581 int len; 582 } *bad, bad_paths[] = { 583 {"\\..\0", 4}, 584 {"\\..\\", 4}, 585 {"..\0", 3}, 586 {"..\\", 3} 587 }; 588 589 /* find last component, strip trailing '\\' */ 590 p = pn->pn_path + strlen(pn->pn_path) - 1; 591 while (*p == '\\') { 592 *p = '\0'; 593 --p; 594 } 595 596 if ((p = strrchr(pn->pn_path, '\\')) == NULL) 597 last_comp = pn->pn_path; 598 else 599 last_comp = ++p; 600 601 wildcards = smb_convert_wildcards(last_comp); 602 603 if (last_comp != pn->pn_path) { 604 /* 605 * Wildcards are only allowed in the last component. 606 * Check for additional wildcards in the path. 607 */ 608 if (smb_convert_wildcards(pn->pn_path) != wildcards) { 609 smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID, 610 ERRDOS, ERROR_INVALID_NAME); 611 return (-1); 612 } 613 } 614 615 /* path above the mount point */ 616 for (i = 0; i < sizeof (bad_paths) / sizeof (bad_paths[0]); ++i) { 617 bad = &bad_paths[i]; 618 if (strncmp(pn->pn_path, bad->name, bad->len) == 0) { 619 smbsr_error(sr, NT_STATUS_OBJECT_PATH_SYNTAX_BAD, 620 ERRDOS, ERROR_BAD_PATHNAME); 621 return (-1); 622 } 623 } 624 625 /* last component is, or resolves to, '.' (dot) */ 626 if ((strcmp(last_comp, ".") == 0) || 627 (SMB_SEARCH_DIRECTORY(fqi->fq_sattr) && 628 (smb_match(last_comp, ".")))) { 629 smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID, 630 ERRDOS, ERROR_INVALID_NAME); 631 return (-1); 632 } 633 634 *wildcard = (wildcards != 0); 635 return (0); 636 } 637 638 /* 639 * smb_delete_error 640 */ 641 static void 642 smb_delete_error(smb_error_t *err, 643 uint32_t status, uint16_t errcls, uint16_t errcode) 644 { 645 err->severity = ERROR_SEVERITY_ERROR; 646 err->status = status; 647 err->errcls = errcls; 648 err->errcode = errcode; 649 } 650