xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_delete.c (revision 74e7dc986c89efca1f2e4451c7a572e05e4a6e4f)
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_delete.c	1.10	08/08/07 SMI"
27 
28 #include <smbsrv/smb_incl.h>
29 #include <smbsrv/smb_fsops.h>
30 #include <smbsrv/smbinfo.h>
31 #include <sys/nbmlock.h>
32 
33 static uint32_t smb_delete_check(smb_request_t *, smb_node_t *);
34 static boolean_t smb_delete_check_path(smb_request_t *, boolean_t *);
35 
36 /*
37  * smb_com_delete
38  *
39  * The delete file message is sent to delete a data file. The appropriate
40  * Tid and additional pathname are passed. Read only files may not be
41  * deleted, the read-only attribute must be reset prior to file deletion.
42  *
43  * NT supports a hidden permission known as File Delete Child (FDC). If
44  * the user has FullControl access to a directory, the user is permitted
45  * to delete any object in the directory regardless of the permissions
46  * on the object.
47  *
48  * Client Request                     Description
49  * ================================== =================================
50  * UCHAR WordCount;                   Count of parameter words = 1
51  * USHORT SearchAttributes;
52  * USHORT ByteCount;                  Count of data bytes; min = 2
53  * UCHAR BufferFormat;                0x04
54  * STRING FileName[];                 File name
55  *
56  * Multiple files may be deleted in response to a single request as
57  * SMB_COM_DELETE supports wildcards
58  *
59  * SearchAttributes indicates the attributes that the target file(s) must
60  * have. If the attribute is zero then only normal files are deleted. If
61  * the system file or hidden attributes are specified then the delete is
62  * inclusive -both the specified type(s) of files and normal files are
63  * deleted. Attributes are described in the "Attribute Encoding" section
64  * of this document.
65  *
66  * If bit0 of the Flags2 field of the SMB header is set, a pattern is
67  * passed in, and the file has a long name, then the passed pattern  much
68  * match the long file name for the delete to succeed. If bit0 is clear, a
69  * pattern is passed in, and the file has a long name, then the passed
70  * pattern must match the file's short name for the deletion to succeed.
71  *
72  * Server Response                    Description
73  * ================================== =================================
74  * UCHAR WordCount;                   Count of parameter words = 0
75  * USHORT ByteCount;                  Count of data bytes = 0
76  *
77  * 4.2.10.1  Errors
78  *
79  * ERRDOS/ERRbadpath
80  * ERRDOS/ERRbadfile
81  * ERRDOS/ERRnoaccess
82  * ERRDOS/ERRbadshare	# returned by NT for files that are already open
83  * ERRHRD/ERRnowrite
84  * ERRSRV/ERRaccess
85  * ERRSRV/ERRinvdevice
86  * ERRSRV/ERRinvid
87  * ERRSRV/ERRbaduid
88  */
89 smb_sdrc_t
90 smb_pre_delete(smb_request_t *sr)
91 {
92 	struct smb_fqi *fqi = &sr->arg.dirop.fqi;
93 	int rc;
94 
95 	if ((rc = smbsr_decode_vwv(sr, "w", &fqi->srch_attr)) == 0)
96 		rc = smbsr_decode_data(sr, "%S", sr, &fqi->path);
97 
98 	DTRACE_SMB_2(op__Delete__start, smb_request_t *, sr,
99 	    struct smb_fqi *, fqi);
100 
101 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
102 }
103 
104 void
105 smb_post_delete(smb_request_t *sr)
106 {
107 	DTRACE_SMB_1(op__Delete__done, smb_request_t *, sr);
108 }
109 
110 /*
111  * smb_com_delete
112  *
113  * readonly
114  * If a readonly entry is matched the search aborts with status
115  * NT_STATUS_CANNOT_DELETE. Entries found prior to the readonly
116  * entry will have been deleted.
117  *
118  * directories:
119  * smb_com_delete does not delete directories:
120  * A non-wildcard delete that finds a directory should result in
121  * NT_STATUS_FILE_IS_A_DIRECTORY.
122  * A wildcard delete that finds a directory will either:
123  *	- abort with status NT_STATUS_FILE_IS_A_DIRECTORY, if
124  *	  FILE_ATTRIBUTE_DIRECTORY is specified in the search attributes, or
125  *	- skip that entry, if FILE_ATTRIBUTE_DIRECTORY is NOT specified
126  *	  in the search attributes
127  * Entries found prior to the directory entry will have been deleted.
128  *
129  * search attribute not matched
130  * If an entry is found but it is either hidden or system and those
131  * attributes are not specified in the search attributes:
132  *	- if deleting a single file, status NT_STATUS_NO_SUCH_FILE
133  *	- if wildcard delete, skip the entry and continue
134  *
135  * path not found
136  * If smb_rdir_open cannot find the specified path, the error code
137  * is set to NT_STATUS_OBJECT_PATH_NOT_FOUND. If there are wildcards
138  * in the last_component, NT_STATUS_OBJECT_NAME_NOT_FOUND should be set
139  * instead.
140  *
141  * smb_delete_check_path() - checks dot, bad path syntax, wildcards in path
142  */
143 
144 smb_sdrc_t
145 smb_com_delete(smb_request_t *sr)
146 {
147 	struct smb_fqi *fqi = &sr->arg.dirop.fqi;
148 	int rc;
149 	int deleted = 0;
150 	struct smb_node *node = NULL;
151 	smb_odir_context_t *pc;
152 	unsigned short sattr;
153 	boolean_t wildcards;
154 
155 	if (smb_delete_check_path(sr, &wildcards) != B_TRUE)
156 		return (SDRC_ERROR);
157 
158 	/*
159 	 * specify all search attributes so that delete-specific
160 	 * search attribute handling can be performed
161 	 */
162 	sattr = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_HIDDEN |
163 	    FILE_ATTRIBUTE_SYSTEM;
164 
165 	if (smb_rdir_open(sr, fqi->path, sattr) != 0) {
166 		/*
167 		 * If there are wildcards in the last_component,
168 		 * NT_STATUS_OBJECT_NAME_NOT_FOUND
169 		 * should be used in place of NT_STATUS_OBJECT_PATH_NOT_FOUND
170 		 */
171 		if ((wildcards == B_TRUE) &&
172 		    (sr->smb_error.status == NT_STATUS_OBJECT_PATH_NOT_FOUND)) {
173 			smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND,
174 			    ERRDOS, ERROR_FILE_NOT_FOUND);
175 		}
176 
177 		return (SDRC_ERROR);
178 	}
179 
180 	pc = kmem_zalloc(sizeof (*pc), KM_SLEEP);
181 
182 	/*
183 	 * This while loop is meant to deal with wildcards.
184 	 * It is not expected that wildcards will exist for
185 	 * streams.  For the streams case, it is expected
186 	 * that the below loop will be executed only once.
187 	 */
188 
189 	while ((rc = smb_rdir_next(sr, &node, pc)) == 0) {
190 		/* check directory */
191 		if (pc->dc_dattr & FILE_ATTRIBUTE_DIRECTORY) {
192 			smb_node_release(node);
193 			if (wildcards == B_FALSE) {
194 				smbsr_error(sr, NT_STATUS_FILE_IS_A_DIRECTORY,
195 				    ERRDOS, ERROR_ACCESS_DENIED);
196 				goto delete_error;
197 			} else {
198 				if (SMB_SEARCH_DIRECTORY(fqi->srch_attr) != 0)
199 					break;
200 				else
201 					continue;
202 			}
203 		}
204 
205 		/* check readonly */
206 		if (SMB_PATHFILE_IS_READONLY(sr, node)) {
207 			smb_node_release(node);
208 			smbsr_error(sr, NT_STATUS_CANNOT_DELETE,
209 			    ERRDOS, ERROR_ACCESS_DENIED);
210 			goto delete_error;
211 		}
212 
213 		/* check search attributes */
214 		if (((pc->dc_dattr & FILE_ATTRIBUTE_HIDDEN) &&
215 		    !(SMB_SEARCH_HIDDEN(fqi->srch_attr))) ||
216 		    ((pc->dc_dattr & FILE_ATTRIBUTE_SYSTEM) &&
217 		    !(SMB_SEARCH_SYSTEM(fqi->srch_attr)))) {
218 			smb_node_release(node);
219 			if (wildcards == B_FALSE) {
220 				smbsr_error(sr, NT_STATUS_NO_SUCH_FILE,
221 				    ERRDOS, ERROR_FILE_NOT_FOUND);
222 				goto delete_error;
223 			} else {
224 				continue;
225 			}
226 		}
227 
228 		/*
229 		 * NT does not always close a file immediately, which
230 		 * can cause the share and access checking to fail
231 		 * (the node refcnt is greater than one), and the file
232 		 * doesn't get deleted. Breaking the oplock before
233 		 * share and access checking gives the client a chance
234 		 * to close the file.
235 		 */
236 
237 		smb_oplock_break(node);
238 
239 		smb_node_start_crit(node, RW_READER);
240 
241 		if (smb_delete_check(sr, node) != NT_STATUS_SUCCESS) {
242 			smb_node_end_crit(node);
243 			smb_node_release(node);
244 			goto delete_error;
245 		}
246 
247 		/*
248 		 * Use node->od_name so as to skip mangle checks and
249 		 * stream processing (which have already been done in
250 		 * smb_rdir_next()).
251 		 * Use node->dir_snode to obtain the correct parent node
252 		 * (especially for streams).
253 		 */
254 		rc = smb_fsop_remove(sr, sr->user_cr, node->dir_snode,
255 		    node->od_name, 1);
256 
257 		smb_node_end_crit(node);
258 		smb_node_release(node);
259 		node = NULL;
260 
261 		if (rc != 0) {
262 			if (rc != ENOENT) {
263 				smbsr_errno(sr, rc);
264 				goto delete_error;
265 			}
266 		} else {
267 			deleted++;
268 		}
269 	}
270 
271 	if ((rc != 0) && (rc != ENOENT)) {
272 		smbsr_errno(sr, rc);
273 		goto delete_error;
274 	}
275 
276 	if (deleted == 0) {
277 		if (wildcards == B_FALSE)
278 			smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND,
279 			    ERRDOS, ERROR_FILE_NOT_FOUND);
280 		else
281 			smbsr_error(sr, NT_STATUS_NO_SUCH_FILE,
282 			    ERRDOS, ERROR_FILE_NOT_FOUND);
283 		goto delete_error;
284 	}
285 
286 	smb_rdir_close(sr);
287 	kmem_free(pc, sizeof (*pc));
288 
289 	rc = smbsr_encode_empty_result(sr);
290 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
291 
292 delete_error:
293 	smb_rdir_close(sr);
294 	kmem_free(pc, sizeof (*pc));
295 	return (SDRC_ERROR);
296 }
297 
298 /*
299  * smb_delete_check_path
300  *
301  * Perform initial validation on the pathname and last_component.
302  *
303  * dot:
304  * A filename of '.' should result in NT_STATUS_OBJECT_NAME_INVALID
305  * Any wildcard filename that resolves to '.' should result in
306  * NT_STATUS_OBJECT_NAME_INVALID if the search attributes include
307  * FILE_ATTRIBUTE_DIRECTORY, otherwise handled as directory (see above).
308  *
309  * bad path syntax:
310  * On unix .. at the root of a file system links to the root. Thus
311  * an attempt to lookup "/../../.." will be the same as looking up "/"
312  * CIFs clients expect the above to result in
313  * NT_STATUS_OBJECT_PATH_SYNTAX_BAD. It is currently not possible
314  * (and questionable if it's desirable) to deal with all cases
315  * but paths beginning with \\.. are handled. See bad_paths[].
316  * Cases like "\\dir\\..\\.." will still result in "\\" which is
317  * contrary to windows behavior.
318  *
319  * wildcards in path:
320  * Wildcards in the path (excluding the last_component) should result
321  * in NT_STATUS_OBJECT_NAME_INVALID.
322  *
323  * Returns:
324  *	B_TRUE:  path is valid. Sets *wildcard to TRUE if wildcard delete
325  *	         i.e. if wildcards in last component
326  *	B_FALSE: path is invalid. Sets error information in sr.
327  */
328 static boolean_t
329 smb_delete_check_path(smb_request_t *sr, boolean_t *wildcard)
330 {
331 	struct smb_fqi *fqi = &sr->arg.dirop.fqi;
332 	char *p, *last_component;
333 	int i, wildcards;
334 
335 	struct {
336 		char *name;
337 		int len;
338 	} *bad, bad_paths[] = {
339 		{"\\..\0", 4},
340 		{"\\..\\", 4},
341 		{"..\0", 3},
342 		{"..\\", 3}
343 	};
344 
345 	wildcards = smb_convert_unicode_wildcards(fqi->path);
346 
347 	/* find last component, strip trailing '\\' */
348 	p = fqi->path + strlen(fqi->path) - 1;
349 	while (*p == '\\') {
350 		*p = '\0';
351 		--p;
352 	}
353 	if ((p = strrchr(fqi->path, '\\')) == NULL) {
354 		last_component = fqi->path;
355 	} else {
356 		last_component = ++p;
357 
358 		/*
359 		 * Any wildcards in path (excluding last_component) should
360 		 * result in NT_STATUS_OBJECT_NAME_INVALID
361 		 */
362 		if (smb_convert_unicode_wildcards(last_component)
363 		    != wildcards) {
364 			smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
365 			    ERRDOS, ERROR_INVALID_NAME);
366 			return (B_FALSE);
367 		}
368 	}
369 
370 	/*
371 	 * path above the mount point => NT_STATUS_OBJECT_PATH_SYNTAX_BAD
372 	 * This test doesn't cover all cases: e.g. \dir\..\..
373 	 */
374 	for (i = 0; i < sizeof (bad_paths) / sizeof (bad_paths[0]); ++i) {
375 		bad = &bad_paths[i];
376 		if (strncmp(fqi->path, bad->name, bad->len) == 0) {
377 			smbsr_error(sr, NT_STATUS_OBJECT_PATH_SYNTAX_BAD,
378 			    ERRDOS, ERROR_BAD_PATHNAME);
379 			return (B_FALSE);
380 		}
381 	}
382 
383 	/*
384 	 * Any file pattern that resolves to '.' is considered invalid.
385 	 * In the wildcard case, only an error if FILE_ATTRIBUTE_DIRECTORY
386 	 * is specified in search attributes, otherwise skipped (below)
387 	 */
388 	if ((strcmp(last_component, ".") == 0) ||
389 	    (SMB_SEARCH_DIRECTORY(fqi->srch_attr) &&
390 	    (smb_match(last_component, ".")))) {
391 		smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
392 		    ERRDOS, ERROR_INVALID_NAME);
393 		return (B_FALSE);
394 	}
395 
396 	*wildcard = (wildcards != 0);
397 	return (B_TRUE);
398 }
399 
400 /*
401  * For consistency with Windows 2000, the range check should be done
402  * after checking for sharing violations.  Attempting to delete a
403  * locked file will result in sharing violation, which is the same
404  * thing that will happen if you try to delete a non-locked open file.
405  *
406  * Note that windows 2000 rejects lock requests on open files that
407  * have been opened with metadata open modes.  The error is
408  * STATUS_ACCESS_DENIED.
409  */
410 static uint32_t
411 smb_delete_check(smb_request_t *sr, smb_node_t *node)
412 {
413 	uint32_t status;
414 
415 	status = smb_node_delete_check(node);
416 
417 	if (status == NT_STATUS_SHARING_VIOLATION) {
418 		smbsr_error(sr, NT_STATUS_SHARING_VIOLATION,
419 		    ERRDOS, ERROR_SHARING_VIOLATION);
420 		return (status);
421 	}
422 
423 	status = smb_range_check(sr, node, 0, UINT64_MAX, B_TRUE);
424 
425 	if (status != NT_STATUS_SUCCESS) {
426 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
427 		    ERRDOS, ERROR_ACCESS_DENIED);
428 	}
429 
430 	return (status);
431 }
432