/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"@(#)smb_delete.c	1.10	08/08/07 SMI"

#include <smbsrv/smb_incl.h>
#include <smbsrv/smb_fsops.h>
#include <smbsrv/smbinfo.h>
#include <sys/nbmlock.h>

static uint32_t smb_delete_check(smb_request_t *, smb_node_t *);
static boolean_t smb_delete_check_path(smb_request_t *, boolean_t *);

/*
 * smb_com_delete
 *
 * The delete file message is sent to delete a data file. The appropriate
 * Tid and additional pathname are passed. Read only files may not be
 * deleted, the read-only attribute must be reset prior to file deletion.
 *
 * NT supports a hidden permission known as File Delete Child (FDC). If
 * the user has FullControl access to a directory, the user is permitted
 * to delete any object in the directory regardless of the permissions
 * on the object.
 *
 * Client Request                     Description
 * ================================== =================================
 * UCHAR WordCount;                   Count of parameter words = 1
 * USHORT SearchAttributes;
 * USHORT ByteCount;                  Count of data bytes; min = 2
 * UCHAR BufferFormat;                0x04
 * STRING FileName[];                 File name
 *
 * Multiple files may be deleted in response to a single request as
 * SMB_COM_DELETE supports wildcards
 *
 * SearchAttributes indicates the attributes that the target file(s) must
 * have. If the attribute is zero then only normal files are deleted. If
 * the system file or hidden attributes are specified then the delete is
 * inclusive -both the specified type(s) of files and normal files are
 * deleted. Attributes are described in the "Attribute Encoding" section
 * of this document.
 *
 * If bit0 of the Flags2 field of the SMB header is set, a pattern is
 * passed in, and the file has a long name, then the passed pattern  much
 * match the long file name for the delete to succeed. If bit0 is clear, a
 * pattern is passed in, and the file has a long name, then the passed
 * pattern must match the file's short name for the deletion to succeed.
 *
 * Server Response                    Description
 * ================================== =================================
 * UCHAR WordCount;                   Count of parameter words = 0
 * USHORT ByteCount;                  Count of data bytes = 0
 *
 * 4.2.10.1  Errors
 *
 * ERRDOS/ERRbadpath
 * ERRDOS/ERRbadfile
 * ERRDOS/ERRnoaccess
 * ERRDOS/ERRbadshare	# returned by NT for files that are already open
 * ERRHRD/ERRnowrite
 * ERRSRV/ERRaccess
 * ERRSRV/ERRinvdevice
 * ERRSRV/ERRinvid
 * ERRSRV/ERRbaduid
 */
smb_sdrc_t
smb_pre_delete(smb_request_t *sr)
{
	struct smb_fqi *fqi = &sr->arg.dirop.fqi;
	int rc;

	if ((rc = smbsr_decode_vwv(sr, "w", &fqi->srch_attr)) == 0)
		rc = smbsr_decode_data(sr, "%S", sr, &fqi->path);

	DTRACE_SMB_2(op__Delete__start, smb_request_t *, sr,
	    struct smb_fqi *, fqi);

	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
}

void
smb_post_delete(smb_request_t *sr)
{
	DTRACE_SMB_1(op__Delete__done, smb_request_t *, sr);
}

/*
 * smb_com_delete
 *
 * readonly
 * If a readonly entry is matched the search aborts with status
 * NT_STATUS_CANNOT_DELETE. Entries found prior to the readonly
 * entry will have been deleted.
 *
 * directories:
 * smb_com_delete does not delete directories:
 * A non-wildcard delete that finds a directory should result in
 * NT_STATUS_FILE_IS_A_DIRECTORY.
 * A wildcard delete that finds a directory will either:
 *	- abort with status NT_STATUS_FILE_IS_A_DIRECTORY, if
 *	  FILE_ATTRIBUTE_DIRECTORY is specified in the search attributes, or
 *	- skip that entry, if FILE_ATTRIBUTE_DIRECTORY is NOT specified
 *	  in the search attributes
 * Entries found prior to the directory entry will have been deleted.
 *
 * search attribute not matched
 * If an entry is found but it is either hidden or system and those
 * attributes are not specified in the search attributes:
 *	- if deleting a single file, status NT_STATUS_NO_SUCH_FILE
 *	- if wildcard delete, skip the entry and continue
 *
 * path not found
 * If smb_rdir_open cannot find the specified path, the error code
 * is set to NT_STATUS_OBJECT_PATH_NOT_FOUND. If there are wildcards
 * in the last_component, NT_STATUS_OBJECT_NAME_NOT_FOUND should be set
 * instead.
 *
 * smb_delete_check_path() - checks dot, bad path syntax, wildcards in path
 */

smb_sdrc_t
smb_com_delete(smb_request_t *sr)
{
	struct smb_fqi *fqi = &sr->arg.dirop.fqi;
	int rc;
	int deleted = 0;
	struct smb_node *node = NULL;
	smb_odir_context_t *pc;
	unsigned short sattr;
	boolean_t wildcards;

	if (smb_delete_check_path(sr, &wildcards) != B_TRUE)
		return (SDRC_ERROR);

	/*
	 * specify all search attributes so that delete-specific
	 * search attribute handling can be performed
	 */
	sattr = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_HIDDEN |
	    FILE_ATTRIBUTE_SYSTEM;

	if (smb_rdir_open(sr, fqi->path, sattr) != 0) {
		/*
		 * If there are wildcards in the last_component,
		 * NT_STATUS_OBJECT_NAME_NOT_FOUND
		 * should be used in place of NT_STATUS_OBJECT_PATH_NOT_FOUND
		 */
		if ((wildcards == B_TRUE) &&
		    (sr->smb_error.status == NT_STATUS_OBJECT_PATH_NOT_FOUND)) {
			smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND,
			    ERRDOS, ERROR_FILE_NOT_FOUND);
		}

		return (SDRC_ERROR);
	}

	pc = kmem_zalloc(sizeof (*pc), KM_SLEEP);

	/*
	 * This while loop is meant to deal with wildcards.
	 * It is not expected that wildcards will exist for
	 * streams.  For the streams case, it is expected
	 * that the below loop will be executed only once.
	 */

	while ((rc = smb_rdir_next(sr, &node, pc)) == 0) {
		/* check directory */
		if (pc->dc_dattr & FILE_ATTRIBUTE_DIRECTORY) {
			smb_node_release(node);
			if (wildcards == B_FALSE) {
				smbsr_error(sr, NT_STATUS_FILE_IS_A_DIRECTORY,
				    ERRDOS, ERROR_ACCESS_DENIED);
				goto delete_error;
			} else {
				if (SMB_SEARCH_DIRECTORY(fqi->srch_attr) != 0)
					break;
				else
					continue;
			}
		}

		/* check readonly */
		if (SMB_PATHFILE_IS_READONLY(sr, node)) {
			smb_node_release(node);
			smbsr_error(sr, NT_STATUS_CANNOT_DELETE,
			    ERRDOS, ERROR_ACCESS_DENIED);
			goto delete_error;
		}

		/* check search attributes */
		if (((pc->dc_dattr & FILE_ATTRIBUTE_HIDDEN) &&
		    !(SMB_SEARCH_HIDDEN(fqi->srch_attr))) ||
		    ((pc->dc_dattr & FILE_ATTRIBUTE_SYSTEM) &&
		    !(SMB_SEARCH_SYSTEM(fqi->srch_attr)))) {
			smb_node_release(node);
			if (wildcards == B_FALSE) {
				smbsr_error(sr, NT_STATUS_NO_SUCH_FILE,
				    ERRDOS, ERROR_FILE_NOT_FOUND);
				goto delete_error;
			} else {
				continue;
			}
		}

		/*
		 * NT does not always close a file immediately, which
		 * can cause the share and access checking to fail
		 * (the node refcnt is greater than one), and the file
		 * doesn't get deleted. Breaking the oplock before
		 * share and access checking gives the client a chance
		 * to close the file.
		 */

		smb_oplock_break(node);

		smb_node_start_crit(node, RW_READER);

		if (smb_delete_check(sr, node) != NT_STATUS_SUCCESS) {
			smb_node_end_crit(node);
			smb_node_release(node);
			goto delete_error;
		}

		/*
		 * Use node->od_name so as to skip mangle checks and
		 * stream processing (which have already been done in
		 * smb_rdir_next()).
		 * Use node->dir_snode to obtain the correct parent node
		 * (especially for streams).
		 */
		rc = smb_fsop_remove(sr, sr->user_cr, node->dir_snode,
		    node->od_name, 1);

		smb_node_end_crit(node);
		smb_node_release(node);
		node = NULL;

		if (rc != 0) {
			if (rc != ENOENT) {
				smbsr_errno(sr, rc);
				goto delete_error;
			}
		} else {
			deleted++;
		}
	}

	if ((rc != 0) && (rc != ENOENT)) {
		smbsr_errno(sr, rc);
		goto delete_error;
	}

	if (deleted == 0) {
		if (wildcards == B_FALSE)
			smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND,
			    ERRDOS, ERROR_FILE_NOT_FOUND);
		else
			smbsr_error(sr, NT_STATUS_NO_SUCH_FILE,
			    ERRDOS, ERROR_FILE_NOT_FOUND);
		goto delete_error;
	}

	smb_rdir_close(sr);
	kmem_free(pc, sizeof (*pc));

	rc = smbsr_encode_empty_result(sr);
	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);

delete_error:
	smb_rdir_close(sr);
	kmem_free(pc, sizeof (*pc));
	return (SDRC_ERROR);
}

/*
 * smb_delete_check_path
 *
 * Perform initial validation on the pathname and last_component.
 *
 * dot:
 * A filename of '.' should result in NT_STATUS_OBJECT_NAME_INVALID
 * Any wildcard filename that resolves to '.' should result in
 * NT_STATUS_OBJECT_NAME_INVALID if the search attributes include
 * FILE_ATTRIBUTE_DIRECTORY, otherwise handled as directory (see above).
 *
 * bad path syntax:
 * On unix .. at the root of a file system links to the root. Thus
 * an attempt to lookup "/../../.." will be the same as looking up "/"
 * CIFs clients expect the above to result in
 * NT_STATUS_OBJECT_PATH_SYNTAX_BAD. It is currently not possible
 * (and questionable if it's desirable) to deal with all cases
 * but paths beginning with \\.. are handled. See bad_paths[].
 * Cases like "\\dir\\..\\.." will still result in "\\" which is
 * contrary to windows behavior.
 *
 * wildcards in path:
 * Wildcards in the path (excluding the last_component) should result
 * in NT_STATUS_OBJECT_NAME_INVALID.
 *
 * Returns:
 *	B_TRUE:  path is valid. Sets *wildcard to TRUE if wildcard delete
 *	         i.e. if wildcards in last component
 *	B_FALSE: path is invalid. Sets error information in sr.
 */
static boolean_t
smb_delete_check_path(smb_request_t *sr, boolean_t *wildcard)
{
	struct smb_fqi *fqi = &sr->arg.dirop.fqi;
	char *p, *last_component;
	int i, wildcards;

	struct {
		char *name;
		int len;
	} *bad, bad_paths[] = {
		{"\\..\0", 4},
		{"\\..\\", 4},
		{"..\0", 3},
		{"..\\", 3}
	};

	wildcards = smb_convert_unicode_wildcards(fqi->path);

	/* find last component, strip trailing '\\' */
	p = fqi->path + strlen(fqi->path) - 1;
	while (*p == '\\') {
		*p = '\0';
		--p;
	}
	if ((p = strrchr(fqi->path, '\\')) == NULL) {
		last_component = fqi->path;
	} else {
		last_component = ++p;

		/*
		 * Any wildcards in path (excluding last_component) should
		 * result in NT_STATUS_OBJECT_NAME_INVALID
		 */
		if (smb_convert_unicode_wildcards(last_component)
		    != wildcards) {
			smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
			    ERRDOS, ERROR_INVALID_NAME);
			return (B_FALSE);
		}
	}

	/*
	 * path above the mount point => NT_STATUS_OBJECT_PATH_SYNTAX_BAD
	 * This test doesn't cover all cases: e.g. \dir\..\..
	 */
	for (i = 0; i < sizeof (bad_paths) / sizeof (bad_paths[0]); ++i) {
		bad = &bad_paths[i];
		if (strncmp(fqi->path, bad->name, bad->len) == 0) {
			smbsr_error(sr, NT_STATUS_OBJECT_PATH_SYNTAX_BAD,
			    ERRDOS, ERROR_BAD_PATHNAME);
			return (B_FALSE);
		}
	}

	/*
	 * Any file pattern that resolves to '.' is considered invalid.
	 * In the wildcard case, only an error if FILE_ATTRIBUTE_DIRECTORY
	 * is specified in search attributes, otherwise skipped (below)
	 */
	if ((strcmp(last_component, ".") == 0) ||
	    (SMB_SEARCH_DIRECTORY(fqi->srch_attr) &&
	    (smb_match(last_component, ".")))) {
		smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
		    ERRDOS, ERROR_INVALID_NAME);
		return (B_FALSE);
	}

	*wildcard = (wildcards != 0);
	return (B_TRUE);
}

/*
 * For consistency with Windows 2000, the range check should be done
 * after checking for sharing violations.  Attempting to delete a
 * locked file will result in sharing violation, which is the same
 * thing that will happen if you try to delete a non-locked open file.
 *
 * Note that windows 2000 rejects lock requests on open files that
 * have been opened with metadata open modes.  The error is
 * STATUS_ACCESS_DENIED.
 */
static uint32_t
smb_delete_check(smb_request_t *sr, smb_node_t *node)
{
	uint32_t status;

	status = smb_node_delete_check(node);

	if (status == NT_STATUS_SHARING_VIOLATION) {
		smbsr_error(sr, NT_STATUS_SHARING_VIOLATION,
		    ERRDOS, ERROR_SHARING_VIOLATION);
		return (status);
	}

	status = smb_range_check(sr, node, 0, UINT64_MAX, B_TRUE);

	if (status != NT_STATUS_SUCCESS) {
		smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
		    ERRDOS, ERROR_ACCESS_DENIED);
	}

	return (status);
}