xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_delete.c (revision ddcb637016537d200651b05908187af72d7babd3)
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_kproto.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 *);
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 *, char *, int);
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. intialize, pre-process and validate pathname
119  *
120  * 2. process the path to get directory node & last_comp,
121  *    store these in fqi
122  *    - If smb_pathname_reduce cannot find the specified path,
123  *      the error (ENOTDIR) is translated to NT_STATUS_OBJECT_PATH_NOT_FOUND
124  *      if the target is a single file (no wildcards).  If there are
125  *      wildcards in the last_comp, NT_STATUS_OBJECT_NAME_NOT_FOUND is
126  *      used instead.
127  *    - If the directory node is the mount point and the last component
128  *      is ".." NT_STATUS_OBJECT_PATH_SYNTAX_BAD is returned.
129  *
130  * 3. check access permissions
131  *
132  * 4. invoke the appropriate deletion routine to find and remove
133  *    the specified file(s).
134  *    - if target is a single file (no wildcards) - smb_delete_single_file
135  *    - if the target contains wildcards - smb_delete_multiple_files
136  *
137  * Returns: SDRC_SUCCESS or SDRC_ERROR
138  */
139 smb_sdrc_t
140 smb_com_delete(smb_request_t *sr)
141 {
142 	int rc;
143 	smb_error_t err;
144 	uint32_t status;
145 	boolean_t wildcards = B_FALSE;
146 	smb_fqi_t *fqi;
147 	smb_pathname_t *pn;
148 
149 	fqi = &sr->arg.dirop.fqi;
150 	pn = &fqi->fq_path;
151 
152 	smb_pathname_init(sr, pn, pn->pn_path);
153 	if (!smb_pathname_validate(sr, pn))
154 		return (SDRC_ERROR);
155 	if (smb_delete_check_path(sr) != 0)
156 		return (SDRC_ERROR);
157 
158 	wildcards = smb_contains_wildcards(pn->pn_fname);
159 
160 	rc = smb_pathname_reduce(sr, sr->user_cr, fqi->fq_path.pn_path,
161 	    sr->tid_tree->t_snode, sr->tid_tree->t_snode,
162 	    &fqi->fq_dnode, fqi->fq_last_comp);
163 	if (rc == 0) {
164 		if (fqi->fq_dnode->vp->v_type != VDIR) {
165 			smb_node_release(fqi->fq_dnode);
166 			rc = ENOTDIR;
167 		}
168 	}
169 	if (rc != 0) {
170 		if (rc == ENOTDIR) {
171 			if (wildcards)
172 				status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
173 			else
174 				status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
175 			smbsr_error(sr, status, ERRDOS, ERROR_FILE_NOT_FOUND);
176 		} else {
177 			smbsr_errno(sr, rc);
178 		}
179 
180 		return (SDRC_ERROR);
181 	}
182 
183 	if ((fqi->fq_dnode == sr->tid_tree->t_snode) &&
184 	    (strcmp(fqi->fq_last_comp, "..") == 0)) {
185 		smb_node_release(fqi->fq_dnode);
186 		smbsr_error(sr, NT_STATUS_OBJECT_PATH_SYNTAX_BAD,
187 		    ERRDOS, ERROR_BAD_PATHNAME);
188 		return (SDRC_ERROR);
189 	}
190 
191 	rc = smb_fsop_access(sr, sr->user_cr, fqi->fq_dnode,
192 	    FILE_LIST_DIRECTORY);
193 	if (rc != 0) {
194 		smb_node_release(fqi->fq_dnode);
195 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
196 		    ERRDOS, ERROR_ACCESS_DENIED);
197 		return (SDRC_ERROR);
198 	}
199 
200 	if (wildcards)
201 		rc = smb_delete_multiple_files(sr, &err);
202 	else
203 		rc = smb_delete_single_file(sr, &err);
204 
205 	smb_node_release(fqi->fq_dnode);
206 
207 	if (rc != 0)
208 		smbsr_set_error(sr, &err);
209 	else
210 		rc = smbsr_encode_empty_result(sr);
211 
212 	return (rc == 0 ? SDRC_SUCCESS : SDRC_ERROR);
213 }
214 
215 /*
216  * smb_delete_single_file
217  *
218  * Find the specified file and, if its attributes match the search
219  * criteria, delete it.
220  *
221  * Returns 0 - success (file deleted)
222  *        -1 - error, err is populated with error details
223  */
224 static int
225 smb_delete_single_file(smb_request_t *sr, smb_error_t *err)
226 {
227 	smb_fqi_t *fqi;
228 	smb_pathname_t *pn;
229 
230 	fqi = &sr->arg.dirop.fqi;
231 	pn = &fqi->fq_path;
232 
233 	/* pn already initialized and validated */
234 	if (!smb_validate_object_name(sr, pn)) {
235 		smb_delete_error(err, sr->smb_error.status,
236 		    ERRDOS, ERROR_INVALID_NAME);
237 		return (-1);
238 	}
239 
240 	if (smb_fsop_lookup_name(sr, sr->user_cr, 0, sr->tid_tree->t_snode,
241 	    fqi->fq_dnode, fqi->fq_last_comp, &fqi->fq_fnode) != 0) {
242 		smb_delete_error(err, NT_STATUS_OBJECT_NAME_NOT_FOUND,
243 		    ERRDOS, ERROR_FILE_NOT_FOUND);
244 		return (-1);
245 	}
246 
247 	if (smb_delete_check_dosattr(sr, err) != 0) {
248 		smb_node_release(fqi->fq_fnode);
249 		return (-1);
250 	}
251 
252 	if (smb_delete_remove_file(sr, err) != 0) {
253 		smb_node_release(fqi->fq_fnode);
254 		return (-1);
255 	}
256 
257 	smb_node_release(fqi->fq_fnode);
258 	return (0);
259 }
260 
261 /*
262  * smb_delete_multiple_files
263  *
264  * For each matching file found by smb_delete_find_fname:
265  * 1. lookup file
266  * 2. check the file's attributes
267  *    - The search ends with an error if a readonly file
268  *      (NT_STATUS_CANNOT_DELETE) is matched.
269  *    - The search ends (but not an error) if a directory is
270  *      matched and the request's search did not include
271  *      directories.
272  *    - Otherwise, if smb_delete_check_dosattr fails the file
273  *      is skipped and the search continues (at step 1)
274  * 3. delete the file
275  *
276  * Returns 0 - success
277  *        -1 - error, err is populated with error details
278  */
279 static int
280 smb_delete_multiple_files(smb_request_t *sr, smb_error_t *err)
281 {
282 	int rc, deleted = 0;
283 	smb_fqi_t *fqi;
284 	uint16_t odid;
285 	smb_odir_t *od;
286 	char namebuf[MAXNAMELEN];
287 
288 	fqi = &sr->arg.dirop.fqi;
289 
290 	/*
291 	 * Specify all search attributes (SMB_SEARCH_ATTRIBUTES) so that
292 	 * delete-specific checking can be done (smb_delete_check_dosattr).
293 	 */
294 	odid = smb_odir_open(sr, fqi->fq_path.pn_path,
295 	    SMB_SEARCH_ATTRIBUTES, 0);
296 	if (odid == 0)
297 		return (-1);
298 
299 	if ((od = smb_tree_lookup_odir(sr->tid_tree, odid)) == NULL)
300 		return (-1);
301 
302 	for (;;) {
303 		rc = smb_delete_find_fname(sr, od, namebuf, MAXNAMELEN);
304 		if (rc != 0)
305 			break;
306 
307 		rc = smb_fsop_lookup_name(sr, sr->user_cr, 0,
308 		    sr->tid_tree->t_snode, fqi->fq_dnode,
309 		    namebuf, &fqi->fq_fnode);
310 		if (rc != 0)
311 			break;
312 
313 		if (smb_delete_check_dosattr(sr, err) != 0) {
314 			smb_node_release(fqi->fq_fnode);
315 			if (err->status == NT_STATUS_CANNOT_DELETE) {
316 				smb_odir_close(od);
317 				smb_odir_release(od);
318 				return (-1);
319 			}
320 			if ((err->status == NT_STATUS_FILE_IS_A_DIRECTORY) &&
321 			    (SMB_SEARCH_DIRECTORY(fqi->fq_sattr) != 0))
322 				break;
323 			continue;
324 		}
325 
326 		if (smb_delete_remove_file(sr, err) == 0) {
327 			++deleted;
328 			smb_node_release(fqi->fq_fnode);
329 			continue;
330 		}
331 		if (err->status == NT_STATUS_OBJECT_NAME_NOT_FOUND) {
332 			smb_node_release(fqi->fq_fnode);
333 			continue;
334 		}
335 
336 		smb_odir_close(od);
337 		smb_odir_release(od);
338 		smb_node_release(fqi->fq_fnode);
339 		return (-1);
340 	}
341 
342 	smb_odir_close(od);
343 	smb_odir_release(od);
344 
345 	if ((rc != 0) && (rc != ENOENT)) {
346 		smbsr_map_errno(rc, err);
347 		return (-1);
348 	}
349 
350 	if (deleted == 0) {
351 		smb_delete_error(err, NT_STATUS_NO_SUCH_FILE,
352 		    ERRDOS, ERROR_FILE_NOT_FOUND);
353 		return (-1);
354 	}
355 
356 	return (0);
357 }
358 
359 /*
360  * smb_delete_find_fname
361  *
362  * Find next filename that matches search pattern and return it
363  * in namebuf.
364  *
365  * Case insensitivity note:
366  * If the tree is case insensitive and there's a case conflict
367  * with the name returned from smb_odir_read, smb_delete_find_fname
368  * performs case conflict name mangling to produce a unique filename.
369  * This ensures that any subsequent smb_fsop_lookup, (which will
370  * find the first case insensitive match) will find the correct file.
371  *
372  * Returns: 0 - success
373  *          errno
374  */
375 static int
376 smb_delete_find_fname(smb_request_t *sr, smb_odir_t *od, char *namebuf, int len)
377 {
378 	int		rc;
379 	smb_odirent_t	*odirent;
380 	boolean_t	eos;
381 	char		*name;
382 	char		shortname[SMB_SHORTNAMELEN];
383 	char		name83[SMB_SHORTNAMELEN];
384 
385 	odirent = kmem_alloc(sizeof (smb_odirent_t), KM_SLEEP);
386 
387 	rc = smb_odir_read(sr, od, odirent, &eos);
388 	if (rc != 0) {
389 		kmem_free(odirent, sizeof (smb_odirent_t));
390 		return (rc);
391 	}
392 	if (eos) {
393 		kmem_free(odirent, sizeof (smb_odirent_t));
394 		return (ENOENT);
395 	}
396 
397 	/* if case conflict, force mangle and use shortname */
398 	if ((od->d_flags & SMB_ODIR_FLAG_IGNORE_CASE) &&
399 	    (odirent->od_eflags & ED_CASE_CONFLICT)) {
400 		(void) smb_mangle_name(odirent->od_ino, odirent->od_name,
401 		    shortname, name83, 1);
402 		name = shortname;
403 	} else {
404 		name = odirent->od_name;
405 	}
406 	(void) strlcpy(namebuf, name, len);
407 
408 	kmem_free(odirent, sizeof (smb_odirent_t));
409 	return (0);
410 }
411 
412 /*
413  * smb_delete_check_dosattr
414  *
415  * Check file's dos atributes to ensure that
416  * 1. the file is not a directory - NT_STATUS_FILE_IS_A_DIRECTORY
417  * 2. the file is not readonly - NT_STATUS_CANNOT_DELETE
418  * 3. the file's dos attributes comply with the specified search attributes
419  *     If the file is either hidden or system and those attributes
420  *     are not specified in the search attributes - NT_STATUS_NO_SUCH_FILE
421  *
422  * Returns: 0 - file's attributes pass all checks
423  *         -1 - err populated with error details
424  */
425 static int
426 smb_delete_check_dosattr(smb_request_t *sr, smb_error_t *err)
427 {
428 	smb_fqi_t *fqi;
429 	smb_node_t *node;
430 	smb_attr_t attr;
431 	uint16_t sattr;
432 
433 	fqi = &sr->arg.dirop.fqi;
434 	sattr = fqi->fq_sattr;
435 	node = fqi->fq_fnode;
436 
437 	if (smb_node_getattr(sr, node, &attr) != 0) {
438 		smb_delete_error(err, NT_STATUS_INTERNAL_ERROR,
439 		    ERRDOS, ERROR_INTERNAL_ERROR);
440 		return (-1);
441 	}
442 
443 	if (attr.sa_dosattr & FILE_ATTRIBUTE_DIRECTORY) {
444 		smb_delete_error(err, NT_STATUS_FILE_IS_A_DIRECTORY,
445 		    ERRDOS, ERROR_ACCESS_DENIED);
446 		return (-1);
447 	}
448 
449 	if (SMB_PATHFILE_IS_READONLY(sr, node)) {
450 		smb_delete_error(err, NT_STATUS_CANNOT_DELETE,
451 		    ERRDOS, ERROR_ACCESS_DENIED);
452 		return (-1);
453 	}
454 
455 	if ((attr.sa_dosattr & FILE_ATTRIBUTE_HIDDEN) &&
456 	    !(SMB_SEARCH_HIDDEN(sattr))) {
457 		smb_delete_error(err, NT_STATUS_NO_SUCH_FILE,
458 		    ERRDOS, ERROR_FILE_NOT_FOUND);
459 		return (-1);
460 	}
461 
462 	if ((attr.sa_dosattr & FILE_ATTRIBUTE_SYSTEM) &&
463 	    !(SMB_SEARCH_SYSTEM(sattr))) {
464 		smb_delete_error(err, NT_STATUS_NO_SUCH_FILE,
465 		    ERRDOS, ERROR_FILE_NOT_FOUND);
466 		return (-1);
467 	}
468 
469 	return (0);
470 }
471 
472 /*
473  * smb_delete_remove_file
474  *
475  * For consistency with Windows 2000, the range check should be done
476  * after checking for sharing violations.  Attempting to delete a
477  * locked file will result in sharing violation, which is the same
478  * thing that will happen if you try to delete a non-locked open file.
479  *
480  * Note that windows 2000 rejects lock requests on open files that
481  * have been opened with metadata open modes.  The error is
482  * STATUS_ACCESS_DENIED.
483  *
484  * NT does not always close a file immediately, which can cause the
485  * share and access checking to fail (the node refcnt is greater
486  * than one), and the file doesn't get deleted. Breaking the oplock
487  * before share and access checking gives the client a chance to
488  * close the file.
489  *
490  * Returns: 0 - success
491  *         -1 - error, err populated with error details
492  */
493 static int
494 smb_delete_remove_file(smb_request_t *sr, smb_error_t *err)
495 {
496 	int rc;
497 	uint32_t status;
498 	smb_fqi_t *fqi;
499 	smb_node_t *node;
500 	uint32_t flags = 0;
501 
502 	fqi = &sr->arg.dirop.fqi;
503 	node = fqi->fq_fnode;
504 
505 	(void) smb_oplock_break(node, sr->session, B_FALSE);
506 
507 	smb_node_start_crit(node, RW_READER);
508 
509 	status = smb_node_delete_check(node);
510 	if (status != NT_STATUS_SUCCESS) {
511 		smb_delete_error(err, NT_STATUS_SHARING_VIOLATION,
512 		    ERRDOS, ERROR_SHARING_VIOLATION);
513 		smb_node_end_crit(node);
514 		return (-1);
515 	}
516 
517 	status = smb_range_check(sr, node, 0, UINT64_MAX, B_TRUE);
518 	if (status != NT_STATUS_SUCCESS) {
519 		smb_delete_error(err, NT_STATUS_ACCESS_DENIED,
520 		    ERRDOS, ERROR_ACCESS_DENIED);
521 		smb_node_end_crit(node);
522 		return (-1);
523 	}
524 
525 	if (SMB_TREE_SUPPORTS_CATIA(sr))
526 		flags |= SMB_CATIA;
527 
528 	rc = smb_fsop_remove(sr, sr->user_cr, node->n_dnode,
529 	    node->od_name, flags);
530 	if (rc != 0) {
531 		if (rc == ENOENT)
532 			smb_delete_error(err, NT_STATUS_OBJECT_NAME_NOT_FOUND,
533 			    ERRDOS, ERROR_FILE_NOT_FOUND);
534 		else
535 			smbsr_map_errno(rc, err);
536 
537 		smb_node_end_crit(node);
538 		return (-1);
539 	}
540 
541 	smb_node_end_crit(node);
542 	return (0);
543 }
544 
545 
546 /*
547  * smb_delete_check_path
548  *
549  * smb_pathname_validate() should already have been used to
550  * perform initial validation on the pathname. Additional
551  * request specific validation of the filename is performed
552  * here.
553  *
554  * - pn->pn_fname is NULL should result in NT_STATUS_FILE_IS_A_DIRECTORY
555  *
556  * - Any wildcard filename that resolves to '.' should result in
557  *   NT_STATUS_OBJECT_NAME_INVALID if the search attributes include
558  *   FILE_ATTRIBUTE_DIRECTORY
559  *
560  * Returns:
561  *   0: path is valid.
562  *  -1: path is invalid. Sets error information in sr.
563  */
564 static int
565 smb_delete_check_path(smb_request_t *sr)
566 {
567 	smb_fqi_t *fqi = &sr->arg.dirop.fqi;
568 	smb_pathname_t *pn = &fqi->fq_path;
569 
570 	if (pn->pn_fname == NULL) {
571 		smbsr_error(sr, NT_STATUS_FILE_IS_A_DIRECTORY,
572 		    ERRDOS, ERROR_ACCESS_DENIED);
573 		return (-1);
574 	}
575 
576 	/* fname component is, or resolves to, '.' (dot) */
577 	if ((strcmp(pn->pn_fname, ".") == 0) ||
578 	    (SMB_SEARCH_DIRECTORY(fqi->fq_sattr) &&
579 	    (smb_match(pn->pn_fname, ".")))) {
580 		smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
581 		    ERRDOS, ERROR_INVALID_NAME);
582 		return (-1);
583 	}
584 
585 	return (0);
586 }
587 
588 /*
589  * smb_delete_error
590  */
591 static void
592 smb_delete_error(smb_error_t *err,
593     uint32_t status, uint16_t errcls, uint16_t errcode)
594 {
595 	err->severity = ERROR_SEVERITY_ERROR;
596 	err->status = status;
597 	err->errcls = errcls;
598 	err->errcode = errcode;
599 }
600