xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_delete.c (revision 1128e05efc1f8d851258698732d30c54ae0fcb69)
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