/* Unix SMB/CIFS implementation. SMB NT transaction handling Copyright (C) Jeremy Allison 1994-2007 Copyright (C) Stefan (metze) Metzmacher 2003 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "includes.h" #include "system/filesys.h" #include "smbd/smbd.h" #include "smbd/globals.h" #include "fake_file.h" #include "../libcli/security/security.h" #include "../librpc/gen_ndr/ndr_security.h" #include "passdb/lookup_sid.h" #include "auth.h" #include "smbprofile.h" #include "libsmb/libsmb.h" #include "lib/util_ea.h" #include "librpc/gen_ndr/ndr_quota.h" #include "librpc/gen_ndr/ndr_security.h" extern const struct generic_mapping file_generic_mapping; /********************************************************************* Windows seems to do canonicalization of inheritance bits. Do the same. *********************************************************************/ static void canonicalize_inheritance_bits(struct files_struct *fsp, struct security_descriptor *psd) { bool set_auto_inherited = false; /* * We need to filter out the * SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_AUTO_INHERIT_REQ * bits. If both are set we store SEC_DESC_DACL_AUTO_INHERITED * as this alters whether SEC_ACE_FLAG_INHERITED_ACE is set * when an ACE is inherited. Otherwise we zero these bits out. * See: * * http://social.msdn.microsoft.com/Forums/eu/os_fileservices/thread/11f77b68-731e-407d-b1b3-064750716531 * * for details. */ if (!lp_acl_flag_inherited_canonicalization(SNUM(fsp->conn))) { psd->type &= ~SEC_DESC_DACL_AUTO_INHERIT_REQ; return; } if ((psd->type & (SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_AUTO_INHERIT_REQ)) == (SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_AUTO_INHERIT_REQ)) { set_auto_inherited = true; } psd->type &= ~(SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_AUTO_INHERIT_REQ); if (set_auto_inherited) { psd->type |= SEC_DESC_DACL_AUTO_INHERITED; } } /**************************************************************************** Internal fn to set security descriptors. ****************************************************************************/ NTSTATUS set_sd(files_struct *fsp, struct security_descriptor *psd, uint32_t security_info_sent) { files_struct *sd_fsp = NULL; NTSTATUS status; if (!CAN_WRITE(fsp->conn)) { return NT_STATUS_ACCESS_DENIED; } if (!lp_nt_acl_support(SNUM(fsp->conn))) { return NT_STATUS_OK; } status = refuse_symlink_fsp(fsp); if (!NT_STATUS_IS_OK(status)) { DBG_DEBUG("ACL set on symlink %s denied.\n", fsp_str_dbg(fsp)); return status; } if (psd->owner_sid == NULL) { security_info_sent &= ~SECINFO_OWNER; } if (psd->group_sid == NULL) { security_info_sent &= ~SECINFO_GROUP; } /* Ensure we have at least one thing set. */ if ((security_info_sent & (SECINFO_OWNER|SECINFO_GROUP|SECINFO_DACL|SECINFO_SACL)) == 0) { /* Just like W2K3 */ return NT_STATUS_OK; } /* Ensure we have the rights to do this. */ if (security_info_sent & SECINFO_OWNER) { if (!(fsp->access_mask & SEC_STD_WRITE_OWNER)) { return NT_STATUS_ACCESS_DENIED; } } if (security_info_sent & SECINFO_GROUP) { if (!(fsp->access_mask & SEC_STD_WRITE_OWNER)) { return NT_STATUS_ACCESS_DENIED; } } if (security_info_sent & SECINFO_DACL) { if (!(fsp->access_mask & SEC_STD_WRITE_DAC)) { return NT_STATUS_ACCESS_DENIED; } /* Convert all the generic bits. */ if (psd->dacl) { security_acl_map_generic(psd->dacl, &file_generic_mapping); } } if (security_info_sent & SECINFO_SACL) { if (!(fsp->access_mask & SEC_FLAG_SYSTEM_SECURITY)) { return NT_STATUS_ACCESS_DENIED; } /* * Setting a SACL also requires WRITE_DAC. * See the smbtorture3 SMB2-SACL test. */ if (!(fsp->access_mask & SEC_STD_WRITE_DAC)) { return NT_STATUS_ACCESS_DENIED; } /* Convert all the generic bits. */ if (psd->sacl) { security_acl_map_generic(psd->sacl, &file_generic_mapping); } } canonicalize_inheritance_bits(fsp, psd); if (DEBUGLEVEL >= 10) { DEBUG(10,("set_sd for file %s\n", fsp_str_dbg(fsp))); NDR_PRINT_DEBUG(security_descriptor, psd); } sd_fsp = metadata_fsp(fsp); status = SMB_VFS_FSET_NT_ACL(sd_fsp, security_info_sent, psd); TALLOC_FREE(psd); return status; } /**************************************************************************** Internal fn to set security descriptors from a data blob. ****************************************************************************/ NTSTATUS set_sd_blob(files_struct *fsp, uint8_t *data, uint32_t sd_len, uint32_t security_info_sent) { struct security_descriptor *psd = NULL; NTSTATUS status; if (sd_len == 0) { return NT_STATUS_INVALID_PARAMETER; } status = unmarshall_sec_desc(talloc_tos(), data, sd_len, &psd); if (!NT_STATUS_IS_OK(status)) { return status; } return set_sd(fsp, psd, security_info_sent); } /**************************************************************************** Copy a file. ****************************************************************************/ NTSTATUS copy_internals(TALLOC_CTX *ctx, connection_struct *conn, struct smb_request *req, struct files_struct *src_dirfsp, struct smb_filename *smb_fname_src, struct files_struct *dst_dirfsp, struct smb_filename *smb_fname_dst, uint32_t attrs) { files_struct *fsp1,*fsp2; uint32_t fattr; int info; off_t ret=-1; NTSTATUS status = NT_STATUS_OK; struct smb_filename *parent = NULL; struct smb_filename *pathref = NULL; if (!CAN_WRITE(conn)) { status = NT_STATUS_MEDIA_WRITE_PROTECTED; goto out; } /* Source must already exist. */ if (!VALID_STAT(smb_fname_src->st)) { status = NT_STATUS_OBJECT_NAME_NOT_FOUND; goto out; } /* Ensure attributes match. */ fattr = fdos_mode(smb_fname_src->fsp); if ((fattr & ~attrs) & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) { status = NT_STATUS_NO_SUCH_FILE; goto out; } /* Disallow if dst file already exists. */ if (VALID_STAT(smb_fname_dst->st)) { status = NT_STATUS_OBJECT_NAME_COLLISION; goto out; } /* No links from a directory. */ if (S_ISDIR(smb_fname_src->st.st_ex_mode)) { status = NT_STATUS_FILE_IS_A_DIRECTORY; goto out; } DEBUG(10,("copy_internals: doing file copy %s to %s\n", smb_fname_str_dbg(smb_fname_src), smb_fname_str_dbg(smb_fname_dst))); status = SMB_VFS_CREATE_FILE( conn, /* conn */ req, /* req */ src_dirfsp, /* dirfsp */ smb_fname_src, /* fname */ FILE_READ_DATA|FILE_READ_ATTRIBUTES| FILE_READ_EA, /* access_mask */ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */ FILE_SHARE_DELETE), FILE_OPEN, /* create_disposition*/ 0, /* create_options */ FILE_ATTRIBUTE_NORMAL, /* file_attributes */ NO_OPLOCK, /* oplock_request */ NULL, /* lease */ 0, /* allocation_size */ 0, /* private_flags */ NULL, /* sd */ NULL, /* ea_list */ &fsp1, /* result */ &info, /* pinfo */ NULL, NULL); /* create context */ if (!NT_STATUS_IS_OK(status)) { goto out; } status = SMB_VFS_CREATE_FILE( conn, /* conn */ req, /* req */ dst_dirfsp, /* dirfsp */ smb_fname_dst, /* fname */ FILE_WRITE_DATA|FILE_WRITE_ATTRIBUTES| FILE_WRITE_EA, /* access_mask */ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */ FILE_SHARE_DELETE), FILE_CREATE, /* create_disposition*/ 0, /* create_options */ fattr, /* file_attributes */ NO_OPLOCK, /* oplock_request */ NULL, /* lease */ 0, /* allocation_size */ 0, /* private_flags */ NULL, /* sd */ NULL, /* ea_list */ &fsp2, /* result */ &info, /* pinfo */ NULL, NULL); /* create context */ if (!NT_STATUS_IS_OK(status)) { close_file_free(NULL, &fsp1, ERROR_CLOSE); goto out; } if (smb_fname_src->st.st_ex_size) { ret = vfs_transfer_file(fsp1, fsp2, smb_fname_src->st.st_ex_size); } /* * As we are opening fsp1 read-only we only expect * an error on close on fsp2 if we are out of space. * Thus we don't look at the error return from the * close of fsp1. */ close_file_free(NULL, &fsp1, NORMAL_CLOSE); /* Ensure the modtime is set correctly on the destination file. */ set_close_write_time(fsp2, smb_fname_src->st.st_ex_mtime); status = close_file_free(NULL, &fsp2, NORMAL_CLOSE); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("close_file_free() failed: %s\n", nt_errstr(status)); /* * We can't do much but leak the fsp */ goto out; } /* Grrr. We have to do this as open_file_ntcreate adds FILE_ATTRIBUTE_ARCHIVE when it creates the file. This isn't the correct thing to do in the copy case. JRA */ status = SMB_VFS_PARENT_PATHNAME(conn, talloc_tos(), smb_fname_dst, &parent, NULL); if (!NT_STATUS_IS_OK(status)) { goto out; } if (smb_fname_dst->fsp == NULL) { status = synthetic_pathref(parent, conn->cwd_fsp, smb_fname_dst->base_name, smb_fname_dst->stream_name, NULL, smb_fname_dst->twrp, smb_fname_dst->flags, &pathref); /* should we handle NT_STATUS_OBJECT_NAME_NOT_FOUND specially here ???? */ if (!NT_STATUS_IS_OK(status)) { TALLOC_FREE(parent); goto out; } file_set_dosmode(conn, pathref, fattr, parent, false); smb_fname_dst->st.st_ex_mode = pathref->st.st_ex_mode; } else { file_set_dosmode(conn, smb_fname_dst, fattr, parent, false); } TALLOC_FREE(parent); if (ret < (off_t)smb_fname_src->st.st_ex_size) { status = NT_STATUS_DISK_FULL; goto out; } out: if (!NT_STATUS_IS_OK(status)) { DEBUG(3,("copy_internals: Error %s copy file %s to %s\n", nt_errstr(status), smb_fname_str_dbg(smb_fname_src), smb_fname_str_dbg(smb_fname_dst))); } return status; } /****************************************************************************** Fake up a completely empty SD. *******************************************************************************/ static NTSTATUS get_null_nt_acl(TALLOC_CTX *mem_ctx, struct security_descriptor **ppsd) { size_t sd_size; *ppsd = make_standard_sec_desc( mem_ctx, &global_sid_World, &global_sid_World, NULL, &sd_size); if(!*ppsd) { DEBUG(0,("get_null_nt_acl: Unable to malloc space for security descriptor.\n")); return NT_STATUS_NO_MEMORY; } return NT_STATUS_OK; } /**************************************************************************** Get a security descriptor from the file system, normalize for components requested. ****************************************************************************/ static NTSTATUS smbd_fetch_security_desc(connection_struct *conn, TALLOC_CTX *mem_ctx, files_struct *fsp, uint32_t security_info_wanted, struct security_descriptor **ppsd) { NTSTATUS status; struct security_descriptor *psd = NULL; bool need_to_read_sd = false; /* * Get the permissions to return. */ if ((security_info_wanted & SECINFO_SACL) && !(fsp->access_mask & SEC_FLAG_SYSTEM_SECURITY)) { DEBUG(10, ("Access to SACL denied.\n")); return NT_STATUS_ACCESS_DENIED; } if ((security_info_wanted & (SECINFO_DACL|SECINFO_OWNER|SECINFO_GROUP)) && !(fsp->access_mask & SEC_STD_READ_CONTROL)) { DEBUG(10, ("Access to DACL, OWNER, or GROUP denied.\n")); return NT_STATUS_ACCESS_DENIED; } status = refuse_symlink_fsp(fsp); if (!NT_STATUS_IS_OK(status)) { DBG_DEBUG("ACL get on symlink %s denied.\n", fsp_str_dbg(fsp)); return status; } if (security_info_wanted & (SECINFO_DACL|SECINFO_OWNER| SECINFO_GROUP|SECINFO_SACL)) { /* Don't return SECINFO_LABEL if anything else was requested. See bug #8458. */ security_info_wanted &= ~SECINFO_LABEL; /* * Only query the file system SD if the caller asks * for any bits. This allows a caller to open without * READ_CONTROL but still issue a query sd. See * smb2.sdread test. */ need_to_read_sd = true; } if (lp_nt_acl_support(SNUM(conn)) && ((security_info_wanted & SECINFO_LABEL) == 0) && need_to_read_sd) { files_struct *sd_fsp = metadata_fsp(fsp); status = SMB_VFS_FGET_NT_ACL( sd_fsp, security_info_wanted, mem_ctx, &psd); } else { status = get_null_nt_acl(mem_ctx, &psd); } if (!NT_STATUS_IS_OK(status)) { return status; } if (!(security_info_wanted & SECINFO_OWNER)) { psd->owner_sid = NULL; } if (!(security_info_wanted & SECINFO_GROUP)) { psd->group_sid = NULL; } if (!(security_info_wanted & SECINFO_DACL)) { psd->type &= ~SEC_DESC_DACL_PRESENT; psd->dacl = NULL; } if (!(security_info_wanted & SECINFO_SACL)) { psd->type &= ~SEC_DESC_SACL_PRESENT; psd->sacl = NULL; } /* If the SACL/DACL is NULL, but was requested, we mark that it is * present in the reply to match Windows behavior */ if (psd->sacl == NULL && security_info_wanted & SECINFO_SACL) psd->type |= SEC_DESC_SACL_PRESENT; if (psd->dacl == NULL && security_info_wanted & SECINFO_DACL) psd->type |= SEC_DESC_DACL_PRESENT; if (security_info_wanted & SECINFO_LABEL) { /* Like W2K3 return a null object. */ psd->owner_sid = NULL; psd->group_sid = NULL; psd->dacl = NULL; psd->sacl = NULL; psd->type &= ~(SEC_DESC_DACL_PRESENT|SEC_DESC_SACL_PRESENT); } *ppsd = psd; return NT_STATUS_OK; } /**************************************************************************** Write a securty descriptor into marshalled format. ****************************************************************************/ static NTSTATUS smbd_marshall_security_desc(TALLOC_CTX *mem_ctx, files_struct *fsp, struct security_descriptor *psd, uint32_t max_data_count, uint8_t **ppmarshalled_sd, size_t *psd_size) { *psd_size = ndr_size_security_descriptor(psd, 0); DBG_NOTICE("sd_size = %zu.\n", *psd_size); if (DEBUGLEVEL >= 10) { DBG_DEBUG("security desc for file %s\n", fsp_str_dbg(fsp)); NDR_PRINT_DEBUG(security_descriptor, psd); } if (max_data_count < *psd_size) { return NT_STATUS_BUFFER_TOO_SMALL; } return marshall_sec_desc(mem_ctx, psd, ppmarshalled_sd, psd_size); } /**************************************************************************** Reply to query a security descriptor. Callable from SMB1 and SMB2. If it returns NT_STATUS_BUFFER_TOO_SMALL, psd_size is initialized with the required size. ****************************************************************************/ NTSTATUS smbd_do_query_security_desc(connection_struct *conn, TALLOC_CTX *mem_ctx, files_struct *fsp, uint32_t security_info_wanted, uint32_t max_data_count, uint8_t **ppmarshalled_sd, size_t *psd_size) { NTSTATUS status; struct security_descriptor *psd = NULL; /* * Get the permissions to return. */ status = smbd_fetch_security_desc(conn, mem_ctx, fsp, security_info_wanted, &psd); if (!NT_STATUS_IS_OK(status)) { return status; } status = smbd_marshall_security_desc(mem_ctx, fsp, psd, max_data_count, ppmarshalled_sd, psd_size); TALLOC_FREE(psd); return status; } #ifdef HAVE_SYS_QUOTAS static enum ndr_err_code fill_qtlist_from_sids(TALLOC_CTX *mem_ctx, struct files_struct *fsp, SMB_NTQUOTA_HANDLE *qt_handle, struct dom_sid *sids, uint32_t elems) { uint32_t i; TALLOC_CTX *list_ctx = NULL; list_ctx = talloc_init("quota_sid_list"); if (list_ctx == NULL) { DBG_ERR("failed to allocate\n"); return NDR_ERR_ALLOC; } if (qt_handle->quota_list!=NULL) { free_ntquota_list(&(qt_handle->quota_list)); } for (i = 0; i < elems; i++) { SMB_NTQUOTA_STRUCT qt; SMB_NTQUOTA_LIST *list_item; bool ok; if (!NT_STATUS_IS_OK(vfs_get_ntquota(fsp, SMB_USER_QUOTA_TYPE, &sids[i], &qt))) { /* non fatal error, return empty item in result */ ZERO_STRUCT(qt); continue; } list_item = talloc_zero(list_ctx, SMB_NTQUOTA_LIST); if (list_item == NULL) { DBG_ERR("failed to allocate\n"); return NDR_ERR_ALLOC; } ok = sid_to_uid(&sids[i], &list_item->uid); if (!ok) { struct dom_sid_buf buf; DBG_WARNING("Could not convert SID %s to uid\n", dom_sid_str_buf(&sids[i], &buf)); /* No idea what to return here... */ return NDR_ERR_INVALID_POINTER; } list_item->quotas = talloc_zero(list_item, SMB_NTQUOTA_STRUCT); if (list_item->quotas == NULL) { DBG_ERR("failed to allocate\n"); return NDR_ERR_ALLOC; } *list_item->quotas = qt; list_item->mem_ctx = list_ctx; DLIST_ADD(qt_handle->quota_list, list_item); } qt_handle->tmp_list = qt_handle->quota_list; return NDR_ERR_SUCCESS; } static enum ndr_err_code extract_sids_from_buf(TALLOC_CTX *mem_ctx, uint32_t sidlistlength, DATA_BLOB *sid_buf, struct dom_sid **sids, uint32_t *num) { DATA_BLOB blob; uint32_t i = 0; enum ndr_err_code err; struct sid_list_elem { struct sid_list_elem *prev, *next; struct dom_sid sid; }; struct sid_list_elem *sid_list = NULL; struct sid_list_elem *iter = NULL; TALLOC_CTX *list_ctx = talloc_init("sid_list"); if (!list_ctx) { DBG_ERR("OOM\n"); err = NDR_ERR_ALLOC; goto done; } *num = 0; *sids = NULL; if (sidlistlength) { uint32_t offset = 0; struct ndr_pull *ndr_pull = NULL; if (sidlistlength > sid_buf->length) { DBG_ERR("sid_list_length 0x%x exceeds " "available bytes %zx\n", sidlistlength, sid_buf->length); err = NDR_ERR_OFFSET; goto done; } while (true) { struct file_get_quota_info info; struct sid_list_elem *item = NULL; uint32_t new_offset = 0; blob.data = sid_buf->data + offset; blob.length = sidlistlength - offset; ndr_pull = ndr_pull_init_blob(&blob, list_ctx); if (!ndr_pull) { DBG_ERR("OOM\n"); err = NDR_ERR_ALLOC; goto done; } err = ndr_pull_file_get_quota_info(ndr_pull, NDR_SCALARS | NDR_BUFFERS, &info); if (!NDR_ERR_CODE_IS_SUCCESS(err)) { DBG_ERR("Failed to pull file_get_quota_info " "from sidlist buffer\n"); goto done; } item = talloc_zero(list_ctx, struct sid_list_elem); if (!item) { DBG_ERR("OOM\n"); err = NDR_ERR_ALLOC; goto done; } item->sid = info.sid; DLIST_ADD(sid_list, item); i++; if (i == UINT32_MAX) { DBG_ERR("Integer overflow\n"); err = NDR_ERR_ARRAY_SIZE; goto done; } new_offset = info.next_entry_offset; /* if new_offset == 0 no more sid(s) to read. */ if (new_offset == 0) { break; } /* Integer wrap? */ if ((offset + new_offset) < offset) { DBG_ERR("Integer wrap while adding " "new_offset 0x%x to current " "buffer offset 0x%x\n", new_offset, offset); err = NDR_ERR_OFFSET; goto done; } offset += new_offset; /* check if new offset is outside buffer boundry. */ if (offset >= sidlistlength) { DBG_ERR("bufsize 0x%x exceeded by " "new offset 0x%x)\n", sidlistlength, offset); err = NDR_ERR_OFFSET; goto done; } } *sids = talloc_zero_array(mem_ctx, struct dom_sid, i); if (*sids == NULL) { DBG_ERR("OOM\n"); err = NDR_ERR_ALLOC; goto done; } *num = i; for (iter = sid_list, i = 0; iter; iter = iter->next, i++) { struct dom_sid_buf buf; (*sids)[i] = iter->sid; DBG_DEBUG("quota SID[%u] %s\n", (unsigned int)i, dom_sid_str_buf(&iter->sid, &buf)); } } err = NDR_ERR_SUCCESS; done: TALLOC_FREE(list_ctx); return err; } NTSTATUS smbd_do_query_getinfo_quota(TALLOC_CTX *mem_ctx, files_struct *fsp, bool restart_scan, bool return_single, uint32_t sid_list_length, DATA_BLOB *sid_buf, uint32_t max_data_count, uint8_t **p_data, uint32_t *p_data_size) { NTSTATUS status; SMB_NTQUOTA_HANDLE *qt_handle = NULL; SMB_NTQUOTA_LIST *qt_list = NULL; DATA_BLOB blob = data_blob_null; enum ndr_err_code err; qt_handle = (SMB_NTQUOTA_HANDLE *)fsp->fake_file_handle->private_data; if (sid_list_length ) { struct dom_sid *sids; uint32_t elems = 0; /* * error check pulled offsets and lengths for wrap and * exceeding available bytes. */ if (sid_list_length > sid_buf->length) { DBG_ERR("sid_list_length 0x%x exceeds " "available bytes %zx\n", sid_list_length, sid_buf->length); return NT_STATUS_INVALID_PARAMETER; } err = extract_sids_from_buf(mem_ctx, sid_list_length, sid_buf, &sids, &elems); if (!NDR_ERR_CODE_IS_SUCCESS(err) || elems == 0) { return NT_STATUS_INVALID_PARAMETER; } err = fill_qtlist_from_sids(mem_ctx, fsp, qt_handle, sids, elems); if (!NDR_ERR_CODE_IS_SUCCESS(err)) { return NT_STATUS_INVALID_PARAMETER; } } else if (restart_scan) { if (vfs_get_user_ntquota_list(fsp, &(qt_handle->quota_list))!=0) { return NT_STATUS_INTERNAL_ERROR; } } else { if (qt_handle->quota_list!=NULL && qt_handle->tmp_list==NULL) { free_ntquota_list(&(qt_handle->quota_list)); } } if (restart_scan !=0 ) { qt_list = qt_handle->quota_list; } else { qt_list = qt_handle->tmp_list; } status = fill_quota_buffer(mem_ctx, qt_list, return_single != 0, max_data_count, &blob, &qt_handle->tmp_list); if (!NT_STATUS_IS_OK(status)) { return status; } if (blob.length > max_data_count) { return NT_STATUS_BUFFER_TOO_SMALL; } *p_data = blob.data; *p_data_size = blob.length; return NT_STATUS_OK; } #endif /* HAVE_SYS_QUOTAS */