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 #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 *, uint32_t *); 35 static int smb_delete_check_attr(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->srch_attr)) == 0) 102 rc = smbsr_decode_data(sr, "%S", sr, &fqi->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->path, 155 sr->tid_tree->t_snode, sr->tid_tree->t_snode, 156 &fqi->dir_snode, fqi->last_comp); 157 if (rc == 0) { 158 if (fqi->dir_snode->vp->v_type != VDIR) { 159 smb_node_release(fqi->dir_snode); 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->dir_snode == sr->tid_tree->t_snode) && 178 (strcmp(fqi->last_comp, "..") == 0)) { 179 smb_node_release(fqi->dir_snode); 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->dir_snode, 186 FILE_LIST_DIRECTORY); 187 if (rc != 0) { 188 smb_node_release(fqi->dir_snode); 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 if (rc != 0) 200 smbsr_set_error(sr, &err); 201 else 202 rc = smbsr_encode_empty_result(sr); 203 204 return (rc == 0 ? SDRC_SUCCESS : SDRC_ERROR); 205 } 206 207 /* 208 * smb_delete_single_file 209 * 210 * Find the specified file and, if its attributes match the search 211 * criteria, delete it. 212 * 213 * Returns 0 - success (file deleted) 214 * -1 - error, err is populated with error details 215 */ 216 static int 217 smb_delete_single_file(smb_request_t *sr, smb_error_t *err) 218 { 219 smb_fqi_t *fqi; 220 smb_attr_t ret_attr; 221 222 fqi = &sr->arg.dirop.fqi; 223 224 if (smb_fsop_lookup_name(sr, sr->user_cr, 0, sr->tid_tree->t_snode, 225 fqi->dir_snode, fqi->last_comp, &fqi->last_snode, &ret_attr) != 0) { 226 smb_delete_error(err, NT_STATUS_OBJECT_NAME_NOT_FOUND, 227 ERRDOS, ERROR_FILE_NOT_FOUND); 228 return (-1); 229 } 230 231 if (smb_delete_check_attr(sr, err) != 0) { 232 smb_node_release(fqi->last_snode); 233 return (-1); 234 } 235 236 if (smb_delete_remove_file(sr, err) != 0) { 237 smb_node_release(fqi->last_snode); 238 return (-1); 239 } 240 241 smb_node_release(fqi->last_snode); 242 return (0); 243 } 244 245 /* 246 * smb_delete_multiple_files 247 * 248 * For each matching file found by smb_delete_find_name: 249 * 1. lookup file 250 * 2. check the file's attributes 251 * - The search ends with an error if a readonly file 252 * (NT_STATUS_CANNOT_DELETE) is matched. 253 * - The search ends (but not an error) if a directory is 254 * matched and the request's search did not include 255 * directories. 256 * - Otherwise, if smb_delete_check_attr fails the file 257 * is skipped and the search continues (at step 1) 258 * 3. delete the file 259 * 260 * Returns 0 - success 261 * -1 - error, err is populated with error details 262 */ 263 static int 264 smb_delete_multiple_files(smb_request_t *sr, smb_error_t *err) 265 { 266 int rc, deleted = 0; 267 uint32_t cookie = 0; 268 smb_fqi_t *fqi; 269 smb_attr_t ret_attr; 270 271 fqi = &sr->arg.dirop.fqi; 272 273 for (;;) { 274 rc = smb_delete_find_fname(sr, &cookie); 275 if (rc != 0) 276 break; 277 278 rc = smb_fsop_lookup_name(sr, sr->user_cr, 0, 279 sr->tid_tree->t_snode, fqi->dir_snode, 280 fqi->last_comp_od, &fqi->last_snode, &ret_attr); 281 if (rc != 0) 282 break; 283 284 if (smb_delete_check_attr(sr, err) != 0) { 285 smb_node_release(fqi->last_snode); 286 if (err->status == NT_STATUS_CANNOT_DELETE) { 287 return (-1); 288 } 289 if ((err->status == NT_STATUS_FILE_IS_A_DIRECTORY) && 290 (SMB_SEARCH_DIRECTORY(fqi->srch_attr) != 0)) 291 break; 292 continue; 293 } 294 295 if (smb_delete_remove_file(sr, err) == 0) { 296 ++deleted; 297 smb_node_release(fqi->last_snode); 298 continue; 299 } 300 if (err->status == NT_STATUS_OBJECT_NAME_NOT_FOUND) { 301 smb_node_release(fqi->last_snode); 302 continue; 303 } 304 305 smb_node_release(fqi->last_snode); 306 return (-1); 307 } 308 309 310 if ((rc != 0) && (rc != ENOENT)) { 311 smbsr_map_errno(rc, err); 312 return (-1); 313 } 314 315 if (deleted == 0) { 316 smb_delete_error(err, NT_STATUS_NO_SUCH_FILE, 317 ERRDOS, ERROR_FILE_NOT_FOUND); 318 return (-1); 319 } 320 321 return (0); 322 } 323 324 /* 325 * smb_delete_find_fname 326 * 327 * Find next filename that matches search pattern (fqi->last_comp) 328 * and save it in fqi->last_comp_od. 329 * 330 * Returns: 0 - success 331 * errno 332 */ 333 static int 334 smb_delete_find_fname(smb_request_t *sr, uint32_t *cookie) 335 { 336 int rc, n_name; 337 ino64_t fileid; 338 smb_fqi_t *fqi; 339 char name83[SMB_SHORTNAMELEN]; 340 char shortname[SMB_SHORTNAMELEN]; 341 boolean_t ignore_case; 342 343 fqi = &sr->arg.dirop.fqi; 344 345 ignore_case = SMB_TREE_IS_CASEINSENSITIVE(sr); 346 347 for (;;) { 348 n_name = sizeof (fqi->last_comp_od) - 1; 349 350 rc = smb_fsop_readdir(sr, sr->user_cr, fqi->dir_snode, cookie, 351 fqi->last_comp_od, &n_name, &fileid, NULL, NULL, NULL); 352 353 if (rc != 0) 354 return (rc); 355 356 /* check for EOF */ 357 if (n_name == 0) 358 return (ENOENT); 359 360 fqi->last_comp_od[n_name] = '\0'; 361 362 if (smb_match_name(fileid, fqi->last_comp_od, shortname, name83, 363 fqi->last_comp, ignore_case)) 364 return (0); 365 } 366 } 367 368 /* 369 * smb_delete_check_attr 370 * 371 * Check file's dos atributes to ensure that 372 * 1. the file is not a directory - NT_STATUS_FILE_IS_A_DIRECTORY 373 * 2. the file is not readonly - NT_STATUS_CANNOT_DELETE 374 * 3. the file's dos attributes comply with the specified search attributes 375 * If the file is either hidden or system and those attributes 376 * are not specified in the search attributes - NT_STATUS_NO_SUCH_FILE 377 * 378 * Returns: 0 - file's attributes pass all checks 379 * -1 - err populated with error details 380 */ 381 static int 382 smb_delete_check_attr(smb_request_t *sr, smb_error_t *err) 383 { 384 smb_fqi_t *fqi; 385 smb_node_t *node; 386 uint16_t dosattr, sattr; 387 388 fqi = &sr->arg.dirop.fqi; 389 sattr = fqi->srch_attr; 390 node = fqi->last_snode; 391 dosattr = smb_node_get_dosattr(node); 392 393 if (dosattr & FILE_ATTRIBUTE_DIRECTORY) { 394 smb_delete_error(err, NT_STATUS_FILE_IS_A_DIRECTORY, 395 ERRDOS, ERROR_ACCESS_DENIED); 396 return (-1); 397 } 398 399 if (SMB_PATHFILE_IS_READONLY(sr, node)) { 400 smb_delete_error(err, NT_STATUS_CANNOT_DELETE, 401 ERRDOS, ERROR_ACCESS_DENIED); 402 return (-1); 403 } 404 405 if ((dosattr & FILE_ATTRIBUTE_HIDDEN) && !(SMB_SEARCH_HIDDEN(sattr))) { 406 smb_delete_error(err, NT_STATUS_NO_SUCH_FILE, 407 ERRDOS, ERROR_FILE_NOT_FOUND); 408 return (-1); 409 } 410 411 if ((dosattr & FILE_ATTRIBUTE_SYSTEM) && !(SMB_SEARCH_SYSTEM(sattr))) { 412 smb_delete_error(err, NT_STATUS_NO_SUCH_FILE, 413 ERRDOS, ERROR_FILE_NOT_FOUND); 414 return (-1); 415 } 416 417 return (0); 418 } 419 420 /* 421 * smb_delete_remove_file 422 * 423 * For consistency with Windows 2000, the range check should be done 424 * after checking for sharing violations. Attempting to delete a 425 * locked file will result in sharing violation, which is the same 426 * thing that will happen if you try to delete a non-locked open file. 427 * 428 * Note that windows 2000 rejects lock requests on open files that 429 * have been opened with metadata open modes. The error is 430 * STATUS_ACCESS_DENIED. 431 * 432 * NT does not always close a file immediately, which can cause the 433 * share and access checking to fail (the node refcnt is greater 434 * than one), and the file doesn't get deleted. Breaking the oplock 435 * before share and access checking gives the client a chance to 436 * close the file. 437 * 438 * Returns: 0 - success 439 * -1 - error, err populated with error details 440 */ 441 static int 442 smb_delete_remove_file(smb_request_t *sr, smb_error_t *err) 443 { 444 int rc; 445 uint32_t status; 446 smb_fqi_t *fqi; 447 smb_node_t *node; 448 449 fqi = &sr->arg.dirop.fqi; 450 node = fqi->last_snode; 451 452 smb_oplock_break(node); 453 454 smb_node_start_crit(node, RW_READER); 455 456 status = smb_node_delete_check(node); 457 if (status != NT_STATUS_SUCCESS) { 458 smb_delete_error(err, NT_STATUS_SHARING_VIOLATION, 459 ERRDOS, ERROR_SHARING_VIOLATION); 460 smb_node_end_crit(node); 461 return (-1); 462 } 463 464 status = smb_range_check(sr, node, 0, UINT64_MAX, B_TRUE); 465 if (status != NT_STATUS_SUCCESS) { 466 smb_delete_error(err, NT_STATUS_ACCESS_DENIED, 467 ERRDOS, ERROR_ACCESS_DENIED); 468 smb_node_end_crit(node); 469 return (-1); 470 } 471 472 rc = smb_fsop_remove(sr, sr->user_cr, node->dir_snode, 473 node->od_name, 1); 474 if (rc != 0) { 475 if (rc == ENOENT) 476 smb_delete_error(err, NT_STATUS_OBJECT_NAME_NOT_FOUND, 477 ERRDOS, ERROR_FILE_NOT_FOUND); 478 else 479 smbsr_map_errno(rc, err); 480 481 smb_node_end_crit(node); 482 return (-1); 483 } 484 485 smb_node_end_crit(node); 486 return (0); 487 } 488 489 490 /* 491 * smb_delete_check_path 492 * 493 * Perform initial validation on the pathname and last_comp. 494 * 495 * wildcards in path: 496 * Wildcards in the path (excluding the last_comp) should result 497 * in NT_STATUS_OBJECT_NAME_INVALID. 498 * 499 * bad path syntax: 500 * On unix .. at the root of a file system links to the root. Thus 501 * an attempt to lookup "/../../.." will be the same as looking up "/" 502 * CIFs clients expect the above to result in 503 * NT_STATUS_OBJECT_PATH_SYNTAX_BAD. It is currently not possible 504 * (and questionable if it's desirable) to deal with all cases 505 * but paths beginning with \\.. are handled. See bad_paths[]. 506 * Cases like "\\dir\\..\\.." will be caught and handled after the 507 * pnreduce. Cases like "\\dir\\..\\..\\filename" will still result 508 * in "\\filename" which is contrary to windows behavior. 509 * 510 * dot: 511 * A filename of '.' should result in NT_STATUS_OBJECT_NAME_INVALID 512 * Any wildcard filename that resolves to '.' should result in 513 * NT_STATUS_OBJECT_NAME_INVALID if the search attributes include 514 * FILE_ATTRIBUTE_DIRECTORY 515 * 516 * Returns: 517 * 0: path is valid. Sets *wildcard to TRUE if wildcard delete 518 * i.e. if wildcards in last component 519 * -1: path is invalid. Sets error information in sr. 520 */ 521 static int 522 smb_delete_check_path(smb_request_t *sr, boolean_t *wildcard) 523 { 524 smb_fqi_t *fqi = &sr->arg.dirop.fqi; 525 char *p, *last_comp; 526 int i, wildcards; 527 528 struct { 529 char *name; 530 int len; 531 } *bad, bad_paths[] = { 532 {"\\..\0", 4}, 533 {"\\..\\", 4}, 534 {"..\0", 3}, 535 {"..\\", 3} 536 }; 537 538 /* check for wildcards in path */ 539 wildcards = smb_convert_unicode_wildcards(fqi->path); 540 541 /* find last component, strip trailing '\\' */ 542 p = fqi->path + strlen(fqi->path) - 1; 543 while (*p == '\\') { 544 *p = '\0'; 545 --p; 546 } 547 if ((p = strrchr(fqi->path, '\\')) == NULL) { 548 last_comp = fqi->path; 549 } else { 550 last_comp = ++p; 551 552 /* wildcards in path > wildcards in last_comp */ 553 if (smb_convert_unicode_wildcards(last_comp) != wildcards) { 554 smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID, 555 ERRDOS, ERROR_INVALID_NAME); 556 return (-1); 557 } 558 } 559 560 /* path above the mount point */ 561 for (i = 0; i < sizeof (bad_paths) / sizeof (bad_paths[0]); ++i) { 562 bad = &bad_paths[i]; 563 if (strncmp(fqi->path, bad->name, bad->len) == 0) { 564 smbsr_error(sr, NT_STATUS_OBJECT_PATH_SYNTAX_BAD, 565 ERRDOS, ERROR_BAD_PATHNAME); 566 return (-1); 567 } 568 } 569 570 /* last component is, or resolves to, '.' (dot) */ 571 if ((strcmp(last_comp, ".") == 0) || 572 (SMB_SEARCH_DIRECTORY(fqi->srch_attr) && 573 (smb_match(last_comp, ".")))) { 574 smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID, 575 ERRDOS, ERROR_INVALID_NAME); 576 return (-1); 577 } 578 579 *wildcard = (wildcards != 0); 580 return (0); 581 } 582 583 /* 584 * smb_delete_error 585 */ 586 static void 587 smb_delete_error(smb_error_t *err, 588 uint32_t status, uint16_t errcls, uint16_t errcode) 589 { 590 err->severity = ERROR_SEVERITY_ERROR; 591 err->status = status; 592 err->errcls = errcls; 593 err->errcode = errcode; 594 } 595