/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include /* * NT_RENAME InformationLevels: * * SMB_NT_RENAME_MOVE_CLUSTER_INFO Server returns invalid parameter. * SMB_NT_RENAME_SET_LINK_INFO Create a hard link to a file. * SMB_NT_RENAME_RENAME_FILE In-place rename of a file. * SMB_NT_RENAME_MOVE_FILE Move (rename) a file. */ #define SMB_NT_RENAME_MOVE_CLUSTER_INFO 0x0102 #define SMB_NT_RENAME_SET_LINK_INFO 0x0103 #define SMB_NT_RENAME_RENAME_FILE 0x0104 #define SMB_NT_RENAME_MOVE_FILE 0x0105 static int smb_do_rename(smb_request_t *, smb_fqi_t *, smb_fqi_t *); static int smb_make_link(smb_request_t *, smb_fqi_t *, smb_fqi_t *); static int smb_rename_check_attr(smb_request_t *, smb_node_t *, uint16_t); static void smb_rename_set_error(smb_request_t *, int); /* * smb_com_rename * * Rename a file. Files OldFileName must exist and NewFileName must not. * Both pathnames must be relative to the Tid specified in the request. * Open files may be renamed. * * Multiple files may be renamed in response to a single request as Rename * File supports wildcards in the file name (last component of the path). * NOTE: we don't support rename with wildcards. * * SearchAttributes indicates the attributes that the target file(s) must * have. If SearchAttributes is zero then only normal files are renamed. * If the system file or hidden attributes are specified then the rename * is inclusive - both the specified type(s) of files and normal files are * renamed. The encoding of SearchAttributes is described in section 3.10 * - File Attribute Encoding. */ smb_sdrc_t smb_pre_rename(smb_request_t *sr) { smb_fqi_t *src_fqi = &sr->arg.dirop.fqi; smb_fqi_t *dst_fqi = &sr->arg.dirop.dst_fqi; int rc; if ((rc = smbsr_decode_vwv(sr, "w", &src_fqi->fq_sattr)) == 0) { rc = smbsr_decode_data(sr, "%SS", sr, &src_fqi->fq_path.pn_path, &dst_fqi->fq_path.pn_path); dst_fqi->fq_sattr = 0; } DTRACE_SMB_2(op__Rename__start, smb_request_t *, sr, struct dirop *, &sr->arg.dirop); return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR); } void smb_post_rename(smb_request_t *sr) { DTRACE_SMB_1(op__Rename__done, smb_request_t *, sr); } smb_sdrc_t smb_com_rename(smb_request_t *sr) { smb_fqi_t *src_fqi = &sr->arg.dirop.fqi; smb_fqi_t *dst_fqi = &sr->arg.dirop.dst_fqi; int rc; if (!STYPE_ISDSK(sr->tid_tree->t_res_type)) { smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRDOS, ERROR_ACCESS_DENIED); return (SDRC_ERROR); } rc = smb_do_rename(sr, src_fqi, dst_fqi); if (rc != 0) { smb_rename_set_error(sr, rc); return (SDRC_ERROR); } rc = smbsr_encode_empty_result(sr); return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR); } /* * smb_do_rename * * Common code for renaming a file. * * If the source and destination are identical, we go through all * the checks but we don't actually do the rename. If the source * and destination files differ only in case, we do a case-sensitive * rename. Otherwise, we do a full case-insensitive rename. * * Returns errno values. */ static int smb_do_rename(smb_request_t *sr, smb_fqi_t *src_fqi, smb_fqi_t *dst_fqi) { smb_node_t *src_node; char *dstname; DWORD status; int rc; int count; if ((rc = smbd_fs_query(sr, src_fqi, FQM_PATH_MUST_EXIST)) != 0) return (rc); src_node = src_fqi->fq_fnode; if ((rc = smb_rename_check_attr(sr, src_node, src_fqi->fq_sattr)) != 0) goto rename_cleanup_nodes; /* * Break the oplock before access checks. If a client * has a file open, this will force a flush or close, * which may affect the outcome of any share checking. */ (void) smb_oplock_break(src_node, sr->session, B_FALSE); for (count = 0; count <= 3; count++) { if (count) { smb_node_end_crit(src_node); delay(MSEC_TO_TICK(400)); } smb_node_start_crit(src_node, RW_READER); status = smb_node_rename_check(src_node); if (status != NT_STATUS_SHARING_VIOLATION) break; } if (status == NT_STATUS_SHARING_VIOLATION) { smb_node_end_crit(src_node); rc = EPIPE; /* = ERRbadshare */ goto rename_cleanup_nodes; } status = smb_range_check(sr, src_node, 0, UINT64_MAX, B_TRUE); if (status != NT_STATUS_SUCCESS) { smb_node_end_crit(src_node); rc = EACCES; goto rename_cleanup_nodes; } if (utf8_strcasecmp(src_fqi->fq_path.pn_path, dst_fqi->fq_path.pn_path) == 0) { if ((rc = smbd_fs_query(sr, dst_fqi, 0)) != 0) { smb_node_end_crit(src_node); goto rename_cleanup_nodes; } /* * Because the fqm parameter to smbd_fs_query() was 0, * dst_fqi->fq_fnode may be NULL. */ if (dst_fqi->fq_fnode) smb_node_release(dst_fqi->fq_fnode); rc = strcmp(src_fqi->fq_od_name, dst_fqi->fq_last_comp); if (rc == 0) { smb_node_end_crit(src_node); goto rename_cleanup_nodes; } rc = smb_fsop_rename(sr, sr->user_cr, src_fqi->fq_dnode, src_fqi->fq_od_name, dst_fqi->fq_dnode, dst_fqi->fq_last_comp); smb_node_end_crit(src_node); if (rc == 0) smb_node_notify_change(dst_fqi->fq_dnode); goto rename_cleanup_nodes; } rc = smbd_fs_query(sr, dst_fqi, FQM_PATH_MUST_NOT_EXIST); if (rc != 0) { smb_node_end_crit(src_node); goto rename_cleanup_nodes; } /* * On success of FQM_PATH_MUST_NOT_EXIST only dst_fqi->fq_dnode * is valid (dst_fqi->fq_fnode is NULL). */ /* * If the source name is mangled but the source and destination * on-disk names are identical, we'll use the on-disk name. */ if ((smb_maybe_mangled_name(src_fqi->fq_last_comp)) && (strcmp(src_fqi->fq_last_comp, dst_fqi->fq_last_comp) == 0)) { dstname = src_fqi->fq_od_name; } else { dstname = dst_fqi->fq_last_comp; } rc = smb_fsop_rename(sr, sr->user_cr, src_fqi->fq_dnode, src_fqi->fq_od_name, dst_fqi->fq_dnode, dstname); smb_node_end_crit(src_node); if (rc == 0) smb_node_notify_change(dst_fqi->fq_dnode); rename_cleanup_nodes: smb_node_release(src_node); smb_node_release(src_fqi->fq_dnode); if (dst_fqi->fq_dnode) smb_node_release(dst_fqi->fq_dnode); SMB_NULL_FQI_NODES(*src_fqi); SMB_NULL_FQI_NODES(*dst_fqi); return (rc); } /* * smb_com_nt_rename * * Rename a file. Files OldFileName must exist and NewFileName must not. * Both pathnames must be relative to the Tid specified in the request. * Open files may be renamed. * * Multiple files may be renamed in response to a single request as Rename * File supports wildcards in the file name (last component of the path). * NOTE: we don't support rename with wildcards. * * SearchAttributes indicates the attributes that the target file(s) must * have. If SearchAttributes is zero then only normal files are renamed. * If the system file or hidden attributes are specified then the rename * is inclusive - both the specified type(s) of files and normal files are * renamed. The encoding of SearchAttributes is described in section 3.10 * - File Attribute Encoding. * * Client Request Description * ================================= ================================== * UCHAR WordCount; Count of parameter words = 4 * USHORT SearchAttributes; * USHORT InformationLevel; 0x0103 Create a hard link * 0x0104 In-place rename * 0x0105 Move (rename) a file * ULONG ClusterCount Servers should ignore this value * USHORT ByteCount; Count of data bytes; min = 4 * UCHAR Buffer[]; Buffer containing: * UCHAR BufferFormat1 0x04 * UCHAR OldFileName[] OldFileName * UCHAR BufferFormat1 0x04 * UCHAR OldFileName[] NewFileName * * Server Response Description * ================================= ================================== * UCHAR WordCount; Count of parameter words = 0 * UCHAR ByteCount; Count of data bytes = 0 */ smb_sdrc_t smb_pre_nt_rename(smb_request_t *sr) { smb_fqi_t *src_fqi = &sr->arg.dirop.fqi; smb_fqi_t *dst_fqi = &sr->arg.dirop.dst_fqi; uint32_t clusters; int rc; rc = smbsr_decode_vwv(sr, "wwl", &src_fqi->fq_sattr, &sr->arg.dirop.info_level, &clusters); if (rc == 0) { rc = smbsr_decode_data(sr, "%SS", sr, &src_fqi->fq_path.pn_path, &dst_fqi->fq_path.pn_path); dst_fqi->fq_sattr = 0; } DTRACE_SMB_2(op__NtRename__start, smb_request_t *, sr, struct dirop *, &sr->arg.dirop); return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR); } void smb_post_nt_rename(smb_request_t *sr) { DTRACE_SMB_1(op__NtRename__done, smb_request_t *, sr); } smb_sdrc_t smb_com_nt_rename(smb_request_t *sr) { smb_fqi_t *src_fqi = &sr->arg.dirop.fqi; smb_fqi_t *dst_fqi = &sr->arg.dirop.dst_fqi; int rc; if (!STYPE_ISDSK(sr->tid_tree->t_res_type)) { smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRDOS, ERROR_ACCESS_DENIED); return (SDRC_ERROR); } if (smb_convert_wildcards(src_fqi->fq_path.pn_path) != 0) { smbsr_error(sr, NT_STATUS_OBJECT_PATH_SYNTAX_BAD, ERRDOS, ERROR_BAD_PATHNAME); return (SDRC_ERROR); } switch (sr->arg.dirop.info_level) { case SMB_NT_RENAME_SET_LINK_INFO: rc = smb_make_link(sr, src_fqi, dst_fqi); break; case SMB_NT_RENAME_RENAME_FILE: case SMB_NT_RENAME_MOVE_FILE: rc = smb_do_rename(sr, src_fqi, dst_fqi); break; case SMB_NT_RENAME_MOVE_CLUSTER_INFO: rc = EINVAL; break; default: rc = EACCES; break; } if (rc != 0) { smb_rename_set_error(sr, rc); return (SDRC_ERROR); } rc = smbsr_encode_empty_result(sr); return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR); } /* * smb_make_link * * Common code for creating a hard link (adding an additional name * for a file. * * If the source and destination are identical, we go through all * the checks but we don't create a link. * * Returns errno values. */ static int smb_make_link(smb_request_t *sr, smb_fqi_t *src_fqi, smb_fqi_t *dst_fqi) { smb_node_t *src_fnode; DWORD status; int rc; int count; if ((rc = smbd_fs_query(sr, src_fqi, FQM_PATH_MUST_EXIST)) != 0) return (rc); src_fnode = src_fqi->fq_fnode; if ((rc = smb_rename_check_attr(sr, src_fnode, src_fqi->fq_sattr)) != 0) goto link_cleanup_nodes; /* * Break the oplock before access checks. If a client * has a file open, this will force a flush or close, * which may affect the outcome of any share checking. */ (void) smb_oplock_break(src_fnode, sr->session, B_FALSE); for (count = 0; count <= 3; count++) { if (count) { smb_node_end_crit(src_fnode); delay(MSEC_TO_TICK(400)); } smb_node_start_crit(src_fnode, RW_READER); status = smb_node_rename_check(src_fnode); if (status != NT_STATUS_SHARING_VIOLATION) break; } if (status == NT_STATUS_SHARING_VIOLATION) { smb_node_end_crit(src_fnode); rc = EPIPE; /* = ERRbadshare */ goto link_cleanup_nodes; } status = smb_range_check(sr, src_fnode, 0, UINT64_MAX, B_TRUE); if (status != NT_STATUS_SUCCESS) { smb_node_end_crit(src_fnode); rc = EACCES; goto link_cleanup_nodes; } if (utf8_strcasecmp(src_fqi->fq_path.pn_path, dst_fqi->fq_path.pn_path) == 0) { smb_node_end_crit(src_fnode); rc = 0; goto link_cleanup_nodes; } rc = smbd_fs_query(sr, dst_fqi, FQM_PATH_MUST_NOT_EXIST); if (rc != 0) { smb_node_end_crit(src_fnode); goto link_cleanup_nodes; } /* * On success of FQM_PATH_MUST_NOT_EXIST only dst_fqi->fq_dnode * is valid (dst_fqi->fq_fnode is NULL). */ rc = smb_fsop_link(sr, sr->user_cr, dst_fqi->fq_dnode, src_fnode, dst_fqi->fq_last_comp); smb_node_end_crit(src_fnode); if (rc == 0) smb_node_notify_change(dst_fqi->fq_dnode); link_cleanup_nodes: smb_node_release(src_fnode); smb_node_release(src_fqi->fq_dnode); if (dst_fqi->fq_dnode) smb_node_release(dst_fqi->fq_dnode); SMB_NULL_FQI_NODES(*src_fqi); SMB_NULL_FQI_NODES(*dst_fqi); return (rc); } static int smb_rename_check_attr(smb_request_t *sr, smb_node_t *node, uint16_t sattr) { smb_attr_t attr; if (smb_node_getattr(sr, node, &attr) != 0) return (EIO); if ((attr.sa_dosattr & FILE_ATTRIBUTE_HIDDEN) && !(SMB_SEARCH_HIDDEN(sattr))) return (ESRCH); if ((attr.sa_dosattr & FILE_ATTRIBUTE_SYSTEM) && !(SMB_SEARCH_SYSTEM(sattr))) return (ESRCH); return (0); } /* * The following values are based on observed WFWG, Windows 9x, Windows NT * and Windows 2000 behaviour. * * ERROR_FILE_EXISTS doesn't work for Windows 98 clients. * * Windows 95 clients don't see the problem because the target is deleted * before the rename request. */ static void smb_rename_set_error(smb_request_t *sr, int errnum) { static struct { int errnum; uint16_t errcode; uint32_t status32; } rc_map[] = { { EEXIST, ERROR_ALREADY_EXISTS, NT_STATUS_OBJECT_NAME_COLLISION }, { EPIPE, ERROR_SHARING_VIOLATION, NT_STATUS_SHARING_VIOLATION }, { ENOENT, ERROR_FILE_NOT_FOUND, NT_STATUS_OBJECT_NAME_NOT_FOUND }, { ESRCH, ERROR_FILE_NOT_FOUND, NT_STATUS_NO_SUCH_FILE }, { EINVAL, ERROR_INVALID_PARAMETER, NT_STATUS_INVALID_PARAMETER }, { EACCES, ERROR_ACCESS_DENIED, NT_STATUS_ACCESS_DENIED }, { EIO, ERROR_INTERNAL_ERROR, NT_STATUS_INTERNAL_ERROR } }; int i; if (errnum == 0) return; for (i = 0; i < sizeof (rc_map)/sizeof (rc_map[0]); ++i) { if (rc_map[i].errnum == errnum) { smbsr_error(sr, rc_map[i].status32, ERRDOS, rc_map[i].errcode); return; } } smbsr_errno(sr, errnum); }