diff options
Diffstat (limited to 'source3/smbd/smb2_trans2.c')
-rw-r--r-- | source3/smbd/smb2_trans2.c | 7026 |
1 files changed, 7026 insertions, 0 deletions
diff --git a/source3/smbd/smb2_trans2.c b/source3/smbd/smb2_trans2.c new file mode 100644 index 0000000..8d1e31d --- /dev/null +++ b/source3/smbd/smb2_trans2.c @@ -0,0 +1,7026 @@ +/* + Unix SMB/CIFS implementation. + SMB transaction2 handling + Copyright (C) Jeremy Allison 1994-2007 + Copyright (C) Stefan (metze) Metzmacher 2003 + Copyright (C) Volker Lendecke 2005-2007 + Copyright (C) Steve French 2005 + Copyright (C) James Peach 2006-2007 + + Extensively modified by Andrew Tridgell, 1995 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "ntioctl.h" +#include "system/filesys.h" +#include "lib/util/time_basic.h" +#include "version.h" +#include "smbd/smbd.h" +#include "smbd/globals.h" +#include "../libcli/auth/libcli_auth.h" +#include "../librpc/gen_ndr/xattr.h" +#include "../librpc/gen_ndr/ndr_security.h" +#include "libcli/security/security.h" +#include "trans2.h" +#include "auth.h" +#include "smbprofile.h" +#include "rpc_server/srv_pipe_hnd.h" +#include "printing.h" +#include "lib/util_ea.h" +#include "lib/readdir_attr.h" +#include "messages.h" +#include "libcli/smb/smb2_posix.h" +#include "lib/util/string_wrappers.h" +#include "source3/lib/substitute.h" +#include "source3/lib/adouble.h" + +#define DIR_ENTRY_SAFETY_MARGIN 4096 + +static char *store_file_unix_basic(connection_struct *conn, + char *pdata, + files_struct *fsp, + const SMB_STRUCT_STAT *psbuf); + +static char *store_file_unix_basic_info2(connection_struct *conn, + char *pdata, + files_struct *fsp, + const SMB_STRUCT_STAT *psbuf); + +static uint32_t generate_volume_serial_number( + const struct loadparm_substitution *lp_sub, + int snum); + +/**************************************************************************** + Check if an open file handle is a symlink. +****************************************************************************/ + +NTSTATUS refuse_symlink_fsp(const files_struct *fsp) +{ + + if (!VALID_STAT(fsp->fsp_name->st)) { + return NT_STATUS_ACCESS_DENIED; + } + if (S_ISLNK(fsp->fsp_name->st.st_ex_mode)) { + return NT_STATUS_ACCESS_DENIED; + } + if (fsp_get_pathref_fd(fsp) == -1) { + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; +} + +NTSTATUS check_access_fsp(struct files_struct *fsp, + uint32_t access_mask) +{ + if (!fsp->fsp_flags.is_fsa) { + return smbd_check_access_rights_fsp(fsp->conn->cwd_fsp, + fsp, + false, + access_mask); + } + if (!(fsp->access_mask & access_mask)) { + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; +} + +#if defined(HAVE_POSIX_ACLS) +/**************************************************************************** + Utility function to open a fsp for a POSIX handle operation. +****************************************************************************/ + +static NTSTATUS get_posix_fsp(connection_struct *conn, + struct smb_request *req, + struct smb_filename *smb_fname, + uint32_t access_mask, + files_struct **ret_fsp) +{ + NTSTATUS status; + uint32_t create_disposition = FILE_OPEN; + uint32_t share_access = FILE_SHARE_READ| + FILE_SHARE_WRITE| + FILE_SHARE_DELETE; + struct smb2_create_blobs *posx = NULL; + + /* + * Only FILE_FLAG_POSIX_SEMANTICS matters on existing files, + * but set reasonable defaults. + */ + uint32_t file_attributes = 0664; + uint32_t oplock = NO_OPLOCK; + uint32_t create_options = FILE_NON_DIRECTORY_FILE; + + /* File or directory must exist. */ + if (!VALID_STAT(smb_fname->st)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + /* Cannot be a symlink. */ + if (S_ISLNK(smb_fname->st.st_ex_mode)) { + return NT_STATUS_ACCESS_DENIED; + } + /* Set options correctly for directory open. */ + if (S_ISDIR(smb_fname->st.st_ex_mode)) { + /* + * Only FILE_FLAG_POSIX_SEMANTICS matters on existing + * directories, but set reasonable defaults. + */ + file_attributes = 0775; + create_options = FILE_DIRECTORY_FILE; + } + + status = make_smb2_posix_create_ctx( + talloc_tos(), &posx, file_attributes); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("make_smb2_posix_create_ctx failed: %s\n", + nt_errstr(status)); + goto done; + } + + status = SMB_VFS_CREATE_FILE( + conn, /* conn */ + req, /* req */ + NULL, /* dirfsp */ + smb_fname, /* fname */ + access_mask, /* access_mask */ + share_access, /* share_access */ + create_disposition,/* create_disposition*/ + create_options, /* create_options */ + file_attributes,/* file_attributes */ + oplock, /* oplock_request */ + NULL, /* lease */ + 0, /* allocation_size */ + 0, /* private_flags */ + NULL, /* sd */ + NULL, /* ea_list */ + ret_fsp, /* result */ + NULL, /* pinfo */ + posx, /* in_context */ + NULL); /* out_context */ + +done: + TALLOC_FREE(posx); + return status; +} +#endif + +/******************************************************************** + Roundup a value to the nearest allocation roundup size boundary. + Only do this for Windows clients. +********************************************************************/ + +uint64_t smb_roundup(connection_struct *conn, uint64_t val) +{ + uint64_t rval = lp_allocation_roundup_size(SNUM(conn)); + + /* Only roundup for Windows clients. */ + enum remote_arch_types ra_type = get_remote_arch(); + if (rval && (ra_type != RA_SAMBA) && (ra_type != RA_CIFSFS)) { + val = SMB_ROUNDUP(val,rval); + } + return val; +} + +/**************************************************************************** + Utility functions for dealing with extended attributes. +****************************************************************************/ + +/**************************************************************************** + Refuse to allow clients to overwrite our private xattrs. +****************************************************************************/ + +bool samba_private_attr_name(const char *unix_ea_name) +{ + static const char * const prohibited_ea_names[] = { + SAMBA_POSIX_INHERITANCE_EA_NAME, + SAMBA_XATTR_DOS_ATTRIB, + SAMBA_XATTR_MARKER, + XATTR_NTACL_NAME, + AFPINFO_EA_NETATALK, + NULL + }; + + int i; + + for (i = 0; prohibited_ea_names[i]; i++) { + if (strequal( prohibited_ea_names[i], unix_ea_name)) + return true; + } + if (strncasecmp_m(unix_ea_name, SAMBA_XATTR_DOSSTREAM_PREFIX, + strlen(SAMBA_XATTR_DOSSTREAM_PREFIX)) == 0) { + return true; + } + return false; +} + +/**************************************************************************** + Get one EA value. Fill in a struct ea_struct. +****************************************************************************/ + +NTSTATUS get_ea_value_fsp(TALLOC_CTX *mem_ctx, + files_struct *fsp, + const char *ea_name, + struct ea_struct *pea) +{ + /* Get the value of this xattr. Max size is 64k. */ + size_t attr_size = 256; + char *val = NULL; + ssize_t sizeret; + size_t max_xattr_size = 0; + + if (fsp == NULL) { + return NT_STATUS_INVALID_HANDLE; + } + + max_xattr_size = lp_smbd_max_xattr_size(SNUM(fsp->conn)); + + again: + + val = talloc_realloc(mem_ctx, val, char, attr_size); + if (!val) { + return NT_STATUS_NO_MEMORY; + } + + sizeret = SMB_VFS_FGETXATTR(fsp, ea_name, val, attr_size); + if (sizeret == -1 && errno == ERANGE && attr_size < max_xattr_size) { + attr_size = max_xattr_size; + goto again; + } + + if (sizeret == -1) { + return map_nt_error_from_unix(errno); + } + + DEBUG(10,("get_ea_value: EA %s is of length %u\n", ea_name, (unsigned int)sizeret)); + dump_data(10, (uint8_t *)val, sizeret); + + pea->flags = 0; + if (strnequal(ea_name, "user.", 5)) { + pea->name = talloc_strdup(mem_ctx, &ea_name[5]); + } else { + pea->name = talloc_strdup(mem_ctx, ea_name); + } + if (pea->name == NULL) { + TALLOC_FREE(val); + return NT_STATUS_NO_MEMORY; + } + pea->value.data = (unsigned char *)val; + pea->value.length = (size_t)sizeret; + return NT_STATUS_OK; +} + +NTSTATUS get_ea_names_from_fsp(TALLOC_CTX *mem_ctx, + files_struct *fsp, + char ***pnames, + size_t *pnum_names) +{ + char smallbuf[1024]; + /* Get a list of all xattrs. Max namesize is 64k. */ + size_t ea_namelist_size = 1024; + char *ea_namelist = smallbuf; + char *to_free = NULL; + + char *p; + char **names; + size_t num_names; + ssize_t sizeret = -1; + NTSTATUS status; + + if (pnames) { + *pnames = NULL; + } + *pnum_names = 0; + + if (fsp == NULL) { + /* + * Callers may pass fsp == NULL when passing smb_fname->fsp of a + * symlink. This is ok, handle it here, by just return no EA's + * on a symlink. + */ + return NT_STATUS_OK; + } + + /* should be the case that fsp != NULL */ + SMB_ASSERT(fsp != NULL); + + sizeret = SMB_VFS_FLISTXATTR(fsp, ea_namelist, + ea_namelist_size); + + if ((sizeret == -1) && (errno == ERANGE)) { + ea_namelist_size = 65536; + ea_namelist = talloc_array(mem_ctx, char, ea_namelist_size); + if (ea_namelist == NULL) { + return NT_STATUS_NO_MEMORY; + } + to_free = ea_namelist; + + sizeret = SMB_VFS_FLISTXATTR(fsp, ea_namelist, + ea_namelist_size); + } + + if (sizeret == -1) { + status = map_nt_error_from_unix(errno); + TALLOC_FREE(to_free); + return status; + } + + DBG_DEBUG("ea_namelist size = %zd\n", sizeret); + + if (sizeret == 0) { + TALLOC_FREE(to_free); + return NT_STATUS_OK; + } + + /* + * Ensure the result is 0-terminated + */ + + if (ea_namelist[sizeret-1] != '\0') { + TALLOC_FREE(to_free); + return NT_STATUS_INTERNAL_ERROR; + } + + /* + * count the names + */ + num_names = 0; + + for (p = ea_namelist; p - ea_namelist < sizeret; p += strlen(p)+1) { + num_names += 1; + } + + *pnum_names = num_names; + + if (pnames == NULL) { + TALLOC_FREE(to_free); + return NT_STATUS_OK; + } + + names = talloc_array(mem_ctx, char *, num_names); + if (names == NULL) { + DEBUG(0, ("talloc failed\n")); + TALLOC_FREE(to_free); + return NT_STATUS_NO_MEMORY; + } + + if (ea_namelist == smallbuf) { + ea_namelist = talloc_memdup(names, smallbuf, sizeret); + if (ea_namelist == NULL) { + TALLOC_FREE(names); + return NT_STATUS_NO_MEMORY; + } + } else { + talloc_steal(names, ea_namelist); + + ea_namelist = talloc_realloc(names, ea_namelist, char, + sizeret); + if (ea_namelist == NULL) { + TALLOC_FREE(names); + return NT_STATUS_NO_MEMORY; + } + } + + num_names = 0; + + for (p = ea_namelist; p - ea_namelist < sizeret; p += strlen(p)+1) { + names[num_names++] = p; + } + + *pnames = names; + + return NT_STATUS_OK; +} + +/**************************************************************************** + Return a linked list of the total EA's. Plus the total size +****************************************************************************/ + +static NTSTATUS get_ea_list_from_fsp(TALLOC_CTX *mem_ctx, + files_struct *fsp, + size_t *pea_total_len, + struct ea_list **ea_list) +{ + /* Get a list of all xattrs. Max namesize is 64k. */ + size_t i, num_names; + char **names; + struct ea_list *ea_list_head = NULL; + bool posix_pathnames = false; + NTSTATUS status; + + *pea_total_len = 0; + *ea_list = NULL; + + /* symlink */ + if (fsp == NULL) { + return NT_STATUS_OK; + } + + if (!lp_ea_support(SNUM(fsp->conn))) { + return NT_STATUS_OK; + } + + if (fsp_is_alternate_stream(fsp)) { + return NT_STATUS_INVALID_PARAMETER; + } + + posix_pathnames = (fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH); + + status = get_ea_names_from_fsp(talloc_tos(), + fsp, + &names, + &num_names); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (num_names == 0) { + return NT_STATUS_OK; + } + + for (i=0; i<num_names; i++) { + struct ea_list *listp; + fstring dos_ea_name; + + /* + * POSIX EA names are divided into several namespaces by + * means of string prefixes. Usually, the system controls + * semantics for each namespace, but the 'user' namespace is + * available for arbitrary use, which comes closest to + * Windows EA semantics. Hence, we map POSIX EAs from the + * 'user' namespace to Windows EAs, and just ignore all the + * other namespaces. Also, a few specific names in the 'user' + * namespace are used by Samba internally. Filter them out as + * well, and only present the EAs that are available for + * arbitrary use. + */ + if (!strnequal(names[i], "user.", 5) + || samba_private_attr_name(names[i])) + continue; + + /* + * Filter out any underlying POSIX EA names + * that a Windows client can't handle. + */ + if (!posix_pathnames && + is_invalid_windows_ea_name(names[i])) { + continue; + } + + listp = talloc(mem_ctx, struct ea_list); + if (listp == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = get_ea_value_fsp(listp, + fsp, + names[i], + &listp->ea); + + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(listp); + return status; + } + + if (listp->ea.value.length == 0) { + /* + * We can never return a zero length EA. + * Windows reports the EA's as corrupted. + */ + TALLOC_FREE(listp); + continue; + } else if (listp->ea.value.length > 65536) { + /* + * SMB clients may report error with file + * if large EA is presented to them. + */ + DBG_ERR("EA [%s] on file [%s] exceeds " + "maximum permitted EA size of 64KiB: %zu\n.", + listp->ea.name, fsp_str_dbg(fsp), + listp->ea.value.length); + TALLOC_FREE(listp); + continue; + } + + push_ascii_fstring(dos_ea_name, listp->ea.name); + + *pea_total_len += + 4 + strlen(dos_ea_name) + 1 + listp->ea.value.length; + + DEBUG(10,("get_ea_list_from_file: total_len = %u, %s, val len " + "= %u\n", (unsigned int)*pea_total_len, dos_ea_name, + (unsigned int)listp->ea.value.length)); + + DLIST_ADD_END(ea_list_head, listp); + + } + + /* Add on 4 for total length. */ + if (*pea_total_len) { + *pea_total_len += 4; + } + + DEBUG(10, ("get_ea_list_from_file: total_len = %u\n", + (unsigned int)*pea_total_len)); + + *ea_list = ea_list_head; + return NT_STATUS_OK; +} + +/**************************************************************************** + Fill a qfilepathinfo buffer with EA's. Returns the length of the buffer + that was filled. +****************************************************************************/ + +static unsigned int fill_ea_buffer(TALLOC_CTX *mem_ctx, char *pdata, unsigned int total_data_size, + connection_struct *conn, struct ea_list *ea_list) +{ + unsigned int ret_data_size = 4; + char *p = pdata; + + SMB_ASSERT(total_data_size >= 4); + + if (!lp_ea_support(SNUM(conn))) { + SIVAL(pdata,4,0); + return 4; + } + + for (p = pdata + 4; ea_list; ea_list = ea_list->next) { + size_t dos_namelen; + fstring dos_ea_name; + push_ascii_fstring(dos_ea_name, ea_list->ea.name); + dos_namelen = strlen(dos_ea_name); + if (dos_namelen > 255 || dos_namelen == 0) { + break; + } + if (ea_list->ea.value.length > 65535) { + break; + } + if (4 + dos_namelen + 1 + ea_list->ea.value.length > total_data_size) { + break; + } + + /* We know we have room. */ + SCVAL(p,0,ea_list->ea.flags); + SCVAL(p,1,dos_namelen); + SSVAL(p,2,ea_list->ea.value.length); + strlcpy(p+4, dos_ea_name, dos_namelen+1); + if (ea_list->ea.value.length > 0) { + memcpy(p + 4 + dos_namelen + 1, + ea_list->ea.value.data, + ea_list->ea.value.length); + } + + total_data_size -= 4 + dos_namelen + 1 + ea_list->ea.value.length; + p += 4 + dos_namelen + 1 + ea_list->ea.value.length; + } + + ret_data_size = PTR_DIFF(p, pdata); + DEBUG(10,("fill_ea_buffer: data_size = %u\n", ret_data_size )); + SIVAL(pdata,0,ret_data_size); + return ret_data_size; +} + +static NTSTATUS fill_ea_chained_buffer(TALLOC_CTX *mem_ctx, + char *pdata, + unsigned int total_data_size, + unsigned int *ret_data_size, + connection_struct *conn, + struct ea_list *ea_list) +{ + uint8_t *p = (uint8_t *)pdata; + uint8_t *last_start = NULL; + bool do_store_data = (pdata != NULL); + + *ret_data_size = 0; + + if (!lp_ea_support(SNUM(conn))) { + return NT_STATUS_NO_EAS_ON_FILE; + } + + for (; ea_list; ea_list = ea_list->next) { + size_t dos_namelen; + fstring dos_ea_name; + size_t this_size; + size_t pad = 0; + + if (last_start != NULL && do_store_data) { + SIVAL(last_start, 0, PTR_DIFF(p, last_start)); + } + last_start = p; + + push_ascii_fstring(dos_ea_name, ea_list->ea.name); + dos_namelen = strlen(dos_ea_name); + if (dos_namelen > 255 || dos_namelen == 0) { + return NT_STATUS_INTERNAL_ERROR; + } + if (ea_list->ea.value.length > 65535) { + return NT_STATUS_INTERNAL_ERROR; + } + + this_size = 0x08 + dos_namelen + 1 + ea_list->ea.value.length; + + if (ea_list->next) { + pad = (4 - (this_size % 4)) % 4; + this_size += pad; + } + + if (do_store_data) { + if (this_size > total_data_size) { + return NT_STATUS_INFO_LENGTH_MISMATCH; + } + + /* We know we have room. */ + SIVAL(p, 0x00, 0); /* next offset */ + SCVAL(p, 0x04, ea_list->ea.flags); + SCVAL(p, 0x05, dos_namelen); + SSVAL(p, 0x06, ea_list->ea.value.length); + strlcpy((char *)(p+0x08), dos_ea_name, dos_namelen+1); + memcpy(p + 0x08 + dos_namelen + 1, ea_list->ea.value.data, ea_list->ea.value.length); + if (pad) { + memset(p + 0x08 + dos_namelen + 1 + ea_list->ea.value.length, + '\0', + pad); + } + total_data_size -= this_size; + } + + p += this_size; + } + + *ret_data_size = PTR_DIFF(p, pdata); + DEBUG(10,("fill_ea_chained_buffer: data_size = %u\n", *ret_data_size)); + return NT_STATUS_OK; +} + +unsigned int estimate_ea_size(files_struct *fsp) +{ + size_t total_ea_len = 0; + TALLOC_CTX *mem_ctx; + struct ea_list *ea_list = NULL; + NTSTATUS status; + + /* symlink */ + if (fsp == NULL) { + return 0; + } + + if (!lp_ea_support(SNUM(fsp->conn))) { + return 0; + } + + mem_ctx = talloc_stackframe(); + + /* If this is a stream fsp, then we need to instead find the + * estimated ea len from the main file, not the stream + * (streams cannot have EAs), but the estimate isn't just 0 in + * this case! */ + fsp = metadata_fsp(fsp); + (void)get_ea_list_from_fsp(mem_ctx, + fsp, + &total_ea_len, + &ea_list); + + if(fsp->conn->sconn->using_smb2) { + unsigned int ret_data_size; + /* + * We're going to be using fill_ea_chained_buffer() to + * marshall EA's - this size is significantly larger + * than the SMB1 buffer. Re-calculate the size without + * marshalling. + */ + status = fill_ea_chained_buffer(mem_ctx, + NULL, + 0, + &ret_data_size, + fsp->conn, + ea_list); + if (!NT_STATUS_IS_OK(status)) { + ret_data_size = 0; + } + total_ea_len = ret_data_size; + } + TALLOC_FREE(mem_ctx); + return total_ea_len; +} + +/**************************************************************************** + Ensure the EA name is case insensitive by matching any existing EA name. +****************************************************************************/ + +static void canonicalize_ea_name(files_struct *fsp, + fstring unix_ea_name) +{ + size_t total_ea_len; + TALLOC_CTX *mem_ctx = talloc_tos(); + struct ea_list *ea_list; + NTSTATUS status = get_ea_list_from_fsp(mem_ctx, + fsp, + &total_ea_len, + &ea_list); + if (!NT_STATUS_IS_OK(status)) { + return; + } + + for (; ea_list; ea_list = ea_list->next) { + if (strequal(&unix_ea_name[5], ea_list->ea.name)) { + DEBUG(10,("canonicalize_ea_name: %s -> %s\n", + &unix_ea_name[5], ea_list->ea.name)); + strlcpy(&unix_ea_name[5], ea_list->ea.name, sizeof(fstring)-5); + break; + } + } +} + +/**************************************************************************** + Set or delete an extended attribute. +****************************************************************************/ + +NTSTATUS set_ea(connection_struct *conn, files_struct *fsp, + struct ea_list *ea_list) +{ + NTSTATUS status; + bool posix_pathnames = false; + + if (!lp_ea_support(SNUM(conn))) { + return NT_STATUS_EAS_NOT_SUPPORTED; + } + + if (fsp == NULL) { + return NT_STATUS_INVALID_HANDLE; + } + + posix_pathnames = (fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH); + + status = refuse_symlink_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = check_access_fsp(fsp, FILE_WRITE_EA); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* Setting EAs on streams isn't supported. */ + if (fsp_is_alternate_stream(fsp)) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* + * Filter out invalid Windows EA names - before + * we set *any* of them. + */ + + if (!posix_pathnames && ea_list_has_invalid_name(ea_list)) { + return STATUS_INVALID_EA_NAME; + } + + for (;ea_list; ea_list = ea_list->next) { + int ret; + fstring unix_ea_name; + + /* + * Complementing the forward mapping from POSIX EAs to + * Windows EAs in get_ea_list_from_fsp(), here we map in the + * opposite direction from Windows EAs to the 'user' namespace + * of POSIX EAs. Hence, all POSIX EA names the we set here must + * start with a 'user.' prefix. + */ + fstrcpy(unix_ea_name, "user."); + fstrcat(unix_ea_name, ea_list->ea.name); + + canonicalize_ea_name(fsp, unix_ea_name); + + DEBUG(10,("set_ea: ea_name %s ealen = %u\n", unix_ea_name, (unsigned int)ea_list->ea.value.length)); + + if (samba_private_attr_name(unix_ea_name)) { + DEBUG(10,("set_ea: ea name %s is a private Samba name.\n", unix_ea_name)); + return NT_STATUS_ACCESS_DENIED; + } + + if (ea_list->ea.value.length == 0) { + /* Remove the attribute. */ + DBG_DEBUG("deleting ea name %s on " + "file %s by file descriptor.\n", + unix_ea_name, fsp_str_dbg(fsp)); + ret = SMB_VFS_FREMOVEXATTR(fsp, unix_ea_name); +#ifdef ENOATTR + /* Removing a non existent attribute always succeeds. */ + if (ret == -1 && errno == ENOATTR) { + DEBUG(10,("set_ea: deleting ea name %s didn't exist - succeeding by default.\n", + unix_ea_name)); + ret = 0; + } +#endif + } else { + DEBUG(10,("set_ea: setting ea name %s on file " + "%s by file descriptor.\n", + unix_ea_name, fsp_str_dbg(fsp))); + ret = SMB_VFS_FSETXATTR(fsp, unix_ea_name, + ea_list->ea.value.data, ea_list->ea.value.length, 0); + } + + if (ret == -1) { +#ifdef ENOTSUP + if (errno == ENOTSUP) { + return NT_STATUS_EAS_NOT_SUPPORTED; + } +#endif + return map_nt_error_from_unix(errno); + } + + } + return NT_STATUS_OK; +} + +/**************************************************************************** + Read a list of EA names and data from an incoming data buffer. Create an ea_list with them. +****************************************************************************/ + +struct ea_list *read_ea_list(TALLOC_CTX *ctx, const char *pdata, size_t data_size) +{ + struct ea_list *ea_list_head = NULL; + size_t offset = 0; + size_t bytes_used = 0; + + while (offset < data_size) { + struct ea_list *eal = read_ea_list_entry(ctx, pdata + offset, data_size - offset, &bytes_used); + + if (!eal) { + return NULL; + } + + DLIST_ADD_END(ea_list_head, eal); + offset += bytes_used; + } + + return ea_list_head; +} + +/**************************************************************************** + Count the total EA size needed. +****************************************************************************/ + +static size_t ea_list_size(struct ea_list *ealist) +{ + fstring dos_ea_name; + struct ea_list *listp; + size_t ret = 0; + + for (listp = ealist; listp; listp = listp->next) { + push_ascii_fstring(dos_ea_name, listp->ea.name); + ret += 4 + strlen(dos_ea_name) + 1 + listp->ea.value.length; + } + /* Add on 4 for total length. */ + if (ret) { + ret += 4; + } + + return ret; +} + +/**************************************************************************** + Return a union of EA's from a file list and a list of names. + The TALLOC context for the two lists *MUST* be identical as we steal + memory from one list to add to another. JRA. +****************************************************************************/ + +static struct ea_list *ea_list_union(struct ea_list *name_list, struct ea_list *file_list, size_t *total_ea_len) +{ + struct ea_list *nlistp, *flistp; + + for (nlistp = name_list; nlistp; nlistp = nlistp->next) { + for (flistp = file_list; flistp; flistp = flistp->next) { + if (strequal(nlistp->ea.name, flistp->ea.name)) { + break; + } + } + + if (flistp) { + /* Copy the data from this entry. */ + nlistp->ea.flags = flistp->ea.flags; + nlistp->ea.value = flistp->ea.value; + } else { + /* Null entry. */ + nlistp->ea.flags = 0; + ZERO_STRUCT(nlistp->ea.value); + } + } + + *total_ea_len = ea_list_size(name_list); + return name_list; +} + +/********************************************************* + Routine to check if a given string matches exactly. + as a special case a mask of "." does NOT match. That + is required for correct wildcard semantics + Case can be significant or not. +**********************************************************/ + +static bool exact_match(bool has_wild, + bool case_sensitive, + const char *str, + const char *mask) +{ + if (mask[0] == '.' && mask[1] == 0) { + return false; + } + + if (has_wild) { + return false; + } + + if (case_sensitive) { + return strcmp(str,mask)==0; + } else { + return strcasecmp_m(str,mask) == 0; + } +} + +/**************************************************************************** + Return the filetype for UNIX extensions. +****************************************************************************/ + +static uint32_t unix_filetype(mode_t mode) +{ + if(S_ISREG(mode)) + return UNIX_TYPE_FILE; + else if(S_ISDIR(mode)) + return UNIX_TYPE_DIR; +#ifdef S_ISLNK + else if(S_ISLNK(mode)) + return UNIX_TYPE_SYMLINK; +#endif +#ifdef S_ISCHR + else if(S_ISCHR(mode)) + return UNIX_TYPE_CHARDEV; +#endif +#ifdef S_ISBLK + else if(S_ISBLK(mode)) + return UNIX_TYPE_BLKDEV; +#endif +#ifdef S_ISFIFO + else if(S_ISFIFO(mode)) + return UNIX_TYPE_FIFO; +#endif +#ifdef S_ISSOCK + else if(S_ISSOCK(mode)) + return UNIX_TYPE_SOCKET; +#endif + + DEBUG(0,("unix_filetype: unknown filetype %u\n", (unsigned)mode)); + return UNIX_TYPE_UNKNOWN; +} + +/**************************************************************************** + Map wire perms onto standard UNIX permissions. Obey share restrictions. +****************************************************************************/ + +NTSTATUS unix_perms_from_wire(connection_struct *conn, + const SMB_STRUCT_STAT *psbuf, + uint32_t perms, + enum perm_type ptype, + mode_t *ret_perms) +{ + mode_t ret = 0; + + if (perms == SMB_MODE_NO_CHANGE) { + if (!VALID_STAT(*psbuf)) { + return NT_STATUS_INVALID_PARAMETER; + } else { + *ret_perms = psbuf->st_ex_mode; + return NT_STATUS_OK; + } + } + + ret = wire_perms_to_unix(perms); + + if (ptype == PERM_NEW_FILE) { + /* + * "create mask"/"force create mode" are + * only applied to new files, not existing ones. + */ + ret &= lp_create_mask(SNUM(conn)); + /* Add in force bits */ + ret |= lp_force_create_mode(SNUM(conn)); + } else if (ptype == PERM_NEW_DIR) { + /* + * "directory mask"/"force directory mode" are + * only applied to new directories, not existing ones. + */ + ret &= lp_directory_mask(SNUM(conn)); + /* Add in force bits */ + ret |= lp_force_directory_mode(SNUM(conn)); + } + + *ret_perms = ret; + return NT_STATUS_OK; +} + +/**************************************************************************** + Needed to show the msdfs symlinks as directories. Modifies psbuf + to be a directory if it's a msdfs link. +****************************************************************************/ + +static bool check_msdfs_link(struct files_struct *dirfsp, + struct smb_filename *atname, + struct smb_filename *smb_fname) +{ + int saved_errno = errno; + if(lp_host_msdfs() && + lp_msdfs_root(SNUM(dirfsp->conn)) && + is_msdfs_link(dirfsp, atname)) { + + /* + * Copy the returned stat struct from the relative + * to the full pathname. + */ + smb_fname->st = atname->st; + + DEBUG(5,("check_msdfs_link: Masquerading msdfs link %s " + "as a directory\n", + smb_fname->base_name)); + smb_fname->st.st_ex_mode = + (smb_fname->st.st_ex_mode & 0xFFF) | S_IFDIR; + errno = saved_errno; + return true; + } + errno = saved_errno; + return false; +} + + +/**************************************************************************** + Get a level dependent lanman2 dir entry. +****************************************************************************/ + +struct smbd_dirptr_lanman2_state { + connection_struct *conn; + uint32_t info_level; + bool check_mangled_names; + bool has_wild; + bool got_exact_match; + bool case_sensitive; +}; + +static bool smbd_dirptr_lanman2_match_fn(TALLOC_CTX *ctx, + void *private_data, + const char *dname, + const char *mask, + char **_fname) +{ + struct smbd_dirptr_lanman2_state *state = + (struct smbd_dirptr_lanman2_state *)private_data; + bool ok; + char mangled_name[13]; /* mangled 8.3 name. */ + bool got_match; + const char *fname; + + /* Mangle fname if it's an illegal name. */ + if (mangle_must_mangle(dname, state->conn->params)) { + /* + * Slow path - ensure we can push the original name as UCS2. If + * not, then just don't return this name. + */ + NTSTATUS status; + size_t ret_len = 0; + size_t len = (strlen(dname) + 2) * 4; /* Allow enough space. */ + uint8_t *tmp = talloc_array(talloc_tos(), + uint8_t, + len); + + status = srvstr_push(NULL, + FLAGS2_UNICODE_STRINGS, + tmp, + dname, + len, + STR_TERMINATE, + &ret_len); + + TALLOC_FREE(tmp); + + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + ok = name_to_8_3(dname, mangled_name, + true, state->conn->params); + if (!ok) { + return false; + } + fname = mangled_name; + } else { + fname = dname; + } + + got_match = exact_match(state->has_wild, + state->case_sensitive, + fname, mask); + state->got_exact_match = got_match; + if (!got_match) { + got_match = mask_match(fname, mask, + state->case_sensitive); + } + + if(!got_match && state->check_mangled_names && + !mangle_is_8_3(fname, false, state->conn->params)) { + /* + * It turns out that NT matches wildcards against + * both long *and* short names. This may explain some + * of the wildcard wierdness from old DOS clients + * that some people have been seeing.... JRA. + */ + /* Force the mangling into 8.3. */ + ok = name_to_8_3(fname, mangled_name, + false, state->conn->params); + if (!ok) { + return false; + } + + got_match = exact_match(state->has_wild, + state->case_sensitive, + mangled_name, mask); + state->got_exact_match = got_match; + if (!got_match) { + got_match = mask_match(mangled_name, mask, + state->case_sensitive); + } + } + + if (!got_match) { + return false; + } + + *_fname = talloc_strdup(ctx, fname); + if (*_fname == NULL) { + return false; + } + + return true; +} + +static bool smbd_dirptr_lanman2_mode_fn(TALLOC_CTX *ctx, + void *private_data, + struct files_struct *dirfsp, + struct smb_filename *atname, + struct smb_filename *smb_fname, + bool get_dosmode, + uint32_t *_mode) +{ + struct smbd_dirptr_lanman2_state *state = + (struct smbd_dirptr_lanman2_state *)private_data; + bool ms_dfs_link = false; + + if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) { + if (SMB_VFS_LSTAT(state->conn, smb_fname) != 0) { + DEBUG(5,("smbd_dirptr_lanman2_mode_fn: " + "Couldn't lstat [%s] (%s)\n", + smb_fname_str_dbg(smb_fname), + strerror(errno))); + return false; + } + return true; + } + + if (!VALID_STAT(smb_fname->st) && + SMB_VFS_STAT(state->conn, smb_fname) != 0) { + /* Needed to show the msdfs symlinks as + * directories */ + + ms_dfs_link = check_msdfs_link(dirfsp, + atname, + smb_fname); + if (!ms_dfs_link) { + DEBUG(5,("smbd_dirptr_lanman2_mode_fn: " + "Couldn't stat [%s] (%s)\n", + smb_fname_str_dbg(smb_fname), + strerror(errno))); + return false; + } + + *_mode = dos_mode_msdfs(state->conn, smb_fname); + return true; + } + + if (!get_dosmode) { + return true; + } + + *_mode = fdos_mode(smb_fname->fsp); + smb_fname->st = smb_fname->fsp->fsp_name->st; + + return true; +} + +static NTSTATUS smbd_marshall_dir_entry(TALLOC_CTX *ctx, + connection_struct *conn, + uint16_t flags2, + uint32_t info_level, + struct ea_list *name_list, + bool check_mangled_names, + bool requires_resume_key, + uint32_t mode, + const char *fname, + const struct smb_filename *smb_fname, + int space_remaining, + uint8_t align, + bool do_pad, + char *base_data, + char **ppdata, + char *end_data, + uint64_t *last_entry_off) +{ + char *p, *q, *pdata = *ppdata; + uint32_t reskey=0; + uint64_t file_size = 0; + uint64_t allocation_size = 0; + uint64_t file_id = 0; + size_t len = 0; + struct timespec mdate_ts = {0}; + struct timespec adate_ts = {0}; + struct timespec cdate_ts = {0}; + struct timespec create_date_ts = {0}; + time_t mdate = (time_t)0, adate = (time_t)0, create_date = (time_t)0; + char *nameptr; + char *last_entry_ptr; + bool was_8_3; + int off; + int pad = 0; + NTSTATUS status; + struct readdir_attr_data *readdir_attr_data = NULL; + + if (!(mode & FILE_ATTRIBUTE_DIRECTORY)) { + file_size = get_file_size_stat(&smb_fname->st); + } + allocation_size = SMB_VFS_GET_ALLOC_SIZE(conn, NULL, &smb_fname->st); + + /* + * Skip SMB_VFS_FREADDIR_ATTR if the directory entry is a symlink or + * a DFS symlink. + */ + if (smb_fname->fsp != NULL && + !(mode & FILE_ATTRIBUTE_REPARSE_POINT)) { + status = SMB_VFS_FREADDIR_ATTR(smb_fname->fsp, + ctx, + &readdir_attr_data); + if (!NT_STATUS_IS_OK(status)) { + if (!NT_STATUS_EQUAL(NT_STATUS_NOT_SUPPORTED, + status)) { + return status; + } + } + } + + file_id = SMB_VFS_FS_FILE_ID(conn, &smb_fname->st); + + mdate_ts = smb_fname->st.st_ex_mtime; + adate_ts = smb_fname->st.st_ex_atime; + create_date_ts = get_create_timespec(conn, NULL, smb_fname); + cdate_ts = get_change_timespec(conn, NULL, smb_fname); + + if (lp_dos_filetime_resolution(SNUM(conn))) { + dos_filetime_timespec(&create_date_ts); + dos_filetime_timespec(&mdate_ts); + dos_filetime_timespec(&adate_ts); + dos_filetime_timespec(&cdate_ts); + } + + create_date = convert_timespec_to_time_t(create_date_ts); + mdate = convert_timespec_to_time_t(mdate_ts); + adate = convert_timespec_to_time_t(adate_ts); + + /* align the record */ + SMB_ASSERT(align >= 1); + + off = (int)PTR_DIFF(pdata, base_data); + pad = (off + (align-1)) & ~(align-1); + pad -= off; + + if (pad && pad > space_remaining) { + DEBUG(9,("smbd_marshall_dir_entry: out of space " + "for padding (wanted %u, had %d)\n", + (unsigned int)pad, + space_remaining )); + return STATUS_MORE_ENTRIES; /* Not finished - just out of space */ + } + + off += pad; + /* initialize padding to 0 */ + if (pad) { + memset(pdata, 0, pad); + } + space_remaining -= pad; + + DEBUG(10,("smbd_marshall_dir_entry: space_remaining = %d\n", + space_remaining )); + + pdata += pad; + p = pdata; + last_entry_ptr = p; + + pad = 0; + off = 0; + + switch (info_level) { + case SMB_FIND_INFO_STANDARD: + DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_INFO_STANDARD\n")); + if(requires_resume_key) { + SIVAL(p,0,reskey); + p += 4; + } + srv_put_dos_date2(p,0,create_date); + srv_put_dos_date2(p,4,adate); + srv_put_dos_date2(p,8,mdate); + SIVAL(p,12,(uint32_t)file_size); + SIVAL(p,16,(uint32_t)allocation_size); + SSVAL(p,20,mode); + p += 23; + nameptr = p; + if (flags2 & FLAGS2_UNICODE_STRINGS) { + p += ucs2_align(base_data, p, 0); + } + status = srvstr_push(base_data, flags2, p, + fname, PTR_DIFF(end_data, p), + STR_TERMINATE, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (flags2 & FLAGS2_UNICODE_STRINGS) { + if (len > 2) { + SCVAL(nameptr, -1, len - 2); + } else { + SCVAL(nameptr, -1, 0); + } + } else { + if (len > 1) { + SCVAL(nameptr, -1, len - 1); + } else { + SCVAL(nameptr, -1, 0); + } + } + p += len; + break; + + case SMB_FIND_EA_SIZE: + DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_EA_SIZE\n")); + if (requires_resume_key) { + SIVAL(p,0,reskey); + p += 4; + } + srv_put_dos_date2(p,0,create_date); + srv_put_dos_date2(p,4,adate); + srv_put_dos_date2(p,8,mdate); + SIVAL(p,12,(uint32_t)file_size); + SIVAL(p,16,(uint32_t)allocation_size); + SSVAL(p,20,mode); + { + unsigned int ea_size = estimate_ea_size(smb_fname->fsp); + SIVAL(p,22,ea_size); /* Extended attributes */ + } + p += 27; + nameptr = p - 1; + status = srvstr_push(base_data, flags2, + p, fname, PTR_DIFF(end_data, p), + STR_TERMINATE | STR_NOALIGN, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (flags2 & FLAGS2_UNICODE_STRINGS) { + if (len > 2) { + len -= 2; + } else { + len = 0; + } + } else { + if (len > 1) { + len -= 1; + } else { + len = 0; + } + } + SCVAL(nameptr,0,len); + p += len; + SCVAL(p,0,0); p += 1; /* Extra zero byte ? - why.. */ + break; + + case SMB_FIND_EA_LIST: + { + struct ea_list *file_list = NULL; + size_t ea_len = 0; + + DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_EA_LIST\n")); + if (!name_list) { + return NT_STATUS_INVALID_PARAMETER; + } + if (requires_resume_key) { + SIVAL(p,0,reskey); + p += 4; + } + srv_put_dos_date2(p,0,create_date); + srv_put_dos_date2(p,4,adate); + srv_put_dos_date2(p,8,mdate); + SIVAL(p,12,(uint32_t)file_size); + SIVAL(p,16,(uint32_t)allocation_size); + SSVAL(p,20,mode); + p += 22; /* p now points to the EA area. */ + + status = get_ea_list_from_fsp(ctx, + smb_fname->fsp, + &ea_len, &file_list); + if (!NT_STATUS_IS_OK(status)) { + file_list = NULL; + } + name_list = ea_list_union(name_list, file_list, &ea_len); + + /* We need to determine if this entry will fit in the space available. */ + /* Max string size is 255 bytes. */ + if (PTR_DIFF(p + 255 + ea_len,pdata) > space_remaining) { + DEBUG(9,("smbd_marshall_dir_entry: out of space " + "(wanted %u, had %d)\n", + (unsigned int)PTR_DIFF(p + 255 + ea_len,pdata), + space_remaining )); + return STATUS_MORE_ENTRIES; /* Not finished - just out of space */ + } + + /* Push the ea_data followed by the name. */ + p += fill_ea_buffer(ctx, p, space_remaining, conn, name_list); + nameptr = p; + status = srvstr_push(base_data, flags2, + p + 1, fname, PTR_DIFF(end_data, p+1), + STR_TERMINATE | STR_NOALIGN, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (flags2 & FLAGS2_UNICODE_STRINGS) { + if (len > 2) { + len -= 2; + } else { + len = 0; + } + } else { + if (len > 1) { + len -= 1; + } else { + len = 0; + } + } + SCVAL(nameptr,0,len); + p += len + 1; + SCVAL(p,0,0); p += 1; /* Extra zero byte ? - why.. */ + break; + } + + case SMB_FIND_FILE_BOTH_DIRECTORY_INFO: + DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_BOTH_DIRECTORY_INFO\n")); + was_8_3 = mangle_is_8_3(fname, True, conn->params); + p += 4; + SIVAL(p,0,reskey); p += 4; + put_long_date_full_timespec(conn->ts_res,p,&create_date_ts); p += 8; + put_long_date_full_timespec(conn->ts_res,p,&adate_ts); p += 8; + put_long_date_full_timespec(conn->ts_res,p,&mdate_ts); p += 8; + put_long_date_full_timespec(conn->ts_res,p,&cdate_ts); p += 8; + SOFF_T(p,0,file_size); p += 8; + SOFF_T(p,0,allocation_size); p += 8; + SIVAL(p,0,mode); p += 4; + q = p; p += 4; /* q is placeholder for name length. */ + if (mode & FILE_ATTRIBUTE_REPARSE_POINT) { + SIVAL(p, 0, IO_REPARSE_TAG_DFS); + } else { + unsigned int ea_size = estimate_ea_size(smb_fname->fsp); + SIVAL(p,0,ea_size); /* Extended attributes */ + } + p += 4; + /* Clear the short name buffer. This is + * IMPORTANT as not doing so will trigger + * a Win2k client bug. JRA. + */ + if (!was_8_3 && check_mangled_names) { + char mangled_name[13]; /* mangled 8.3 name. */ + if (!name_to_8_3(fname,mangled_name,True, + conn->params)) { + /* Error - mangle failed ! */ + memset(mangled_name,'\0',12); + } + mangled_name[12] = 0; + status = srvstr_push(base_data, flags2, + p+2, mangled_name, 24, + STR_UPPER|STR_UNICODE, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (len < 24) { + memset(p + 2 + len,'\0',24 - len); + } + SSVAL(p, 0, len); + } else { + memset(p,'\0',26); + } + p += 2 + 24; + status = srvstr_push(base_data, flags2, p, + fname, PTR_DIFF(end_data, p), + STR_TERMINATE_ASCII, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + SIVAL(q,0,len); + p += len; + + len = PTR_DIFF(p, pdata); + pad = (len + (align-1)) & ~(align-1); + /* + * offset to the next entry, the caller + * will overwrite it for the last entry + * that's why we always include the padding + */ + SIVAL(pdata,0,pad); + /* + * set padding to zero + */ + if (do_pad) { + memset(p, 0, pad - len); + p = pdata + pad; + } else { + p = pdata + len; + } + break; + + case SMB_FIND_FILE_DIRECTORY_INFO: + DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_DIRECTORY_INFO\n")); + p += 4; + SIVAL(p,0,reskey); p += 4; + put_long_date_full_timespec(conn->ts_res,p,&create_date_ts); p += 8; + put_long_date_full_timespec(conn->ts_res,p,&adate_ts); p += 8; + put_long_date_full_timespec(conn->ts_res,p,&mdate_ts); p += 8; + put_long_date_full_timespec(conn->ts_res,p,&cdate_ts); p += 8; + SOFF_T(p,0,file_size); p += 8; + SOFF_T(p,0,allocation_size); p += 8; + SIVAL(p,0,mode); p += 4; + status = srvstr_push(base_data, flags2, + p + 4, fname, PTR_DIFF(end_data, p+4), + STR_TERMINATE_ASCII, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + SIVAL(p,0,len); + p += 4 + len; + + len = PTR_DIFF(p, pdata); + pad = (len + (align-1)) & ~(align-1); + /* + * offset to the next entry, the caller + * will overwrite it for the last entry + * that's why we always include the padding + */ + SIVAL(pdata,0,pad); + /* + * set padding to zero + */ + if (do_pad) { + memset(p, 0, pad - len); + p = pdata + pad; + } else { + p = pdata + len; + } + break; + + case SMB_FIND_FILE_FULL_DIRECTORY_INFO: + DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_FULL_DIRECTORY_INFO\n")); + p += 4; + SIVAL(p,0,reskey); p += 4; + put_long_date_full_timespec(conn->ts_res,p,&create_date_ts); p += 8; + put_long_date_full_timespec(conn->ts_res,p,&adate_ts); p += 8; + put_long_date_full_timespec(conn->ts_res,p,&mdate_ts); p += 8; + put_long_date_full_timespec(conn->ts_res,p,&cdate_ts); p += 8; + SOFF_T(p,0,file_size); p += 8; + SOFF_T(p,0,allocation_size); p += 8; + SIVAL(p,0,mode); p += 4; + q = p; p += 4; /* q is placeholder for name length. */ + if (mode & FILE_ATTRIBUTE_REPARSE_POINT) { + SIVAL(p, 0, IO_REPARSE_TAG_DFS); + } else { + unsigned int ea_size = estimate_ea_size(smb_fname->fsp); + SIVAL(p,0,ea_size); /* Extended attributes */ + } + p +=4; + status = srvstr_push(base_data, flags2, p, + fname, PTR_DIFF(end_data, p), + STR_TERMINATE_ASCII, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + SIVAL(q, 0, len); + p += len; + + len = PTR_DIFF(p, pdata); + pad = (len + (align-1)) & ~(align-1); + /* + * offset to the next entry, the caller + * will overwrite it for the last entry + * that's why we always include the padding + */ + SIVAL(pdata,0,pad); + /* + * set padding to zero + */ + if (do_pad) { + memset(p, 0, pad - len); + p = pdata + pad; + } else { + p = pdata + len; + } + break; + + case SMB_FIND_FILE_NAMES_INFO: + DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_NAMES_INFO\n")); + p += 4; + SIVAL(p,0,reskey); p += 4; + p += 4; + /* this must *not* be null terminated or w2k gets in a loop trying to set an + acl on a dir (tridge) */ + status = srvstr_push(base_data, flags2, p, + fname, PTR_DIFF(end_data, p), + STR_TERMINATE_ASCII, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + SIVAL(p, -4, len); + p += len; + + len = PTR_DIFF(p, pdata); + pad = (len + (align-1)) & ~(align-1); + /* + * offset to the next entry, the caller + * will overwrite it for the last entry + * that's why we always include the padding + */ + SIVAL(pdata,0,pad); + /* + * set padding to zero + */ + if (do_pad) { + memset(p, 0, pad - len); + p = pdata + pad; + } else { + p = pdata + len; + } + break; + + case SMB_FIND_ID_FULL_DIRECTORY_INFO: + DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_ID_FULL_DIRECTORY_INFO\n")); + p += 4; + SIVAL(p,0,reskey); p += 4; + put_long_date_full_timespec(conn->ts_res,p,&create_date_ts); p += 8; + put_long_date_full_timespec(conn->ts_res,p,&adate_ts); p += 8; + put_long_date_full_timespec(conn->ts_res,p,&mdate_ts); p += 8; + put_long_date_full_timespec(conn->ts_res,p,&cdate_ts); p += 8; + SOFF_T(p,0,file_size); p += 8; + SOFF_T(p,0,allocation_size); p += 8; + SIVAL(p,0,mode); p += 4; + q = p; p += 4; /* q is placeholder for name length. */ + if (mode & FILE_ATTRIBUTE_REPARSE_POINT) { + SIVAL(p, 0, IO_REPARSE_TAG_DFS); + } else { + unsigned int ea_size = estimate_ea_size(smb_fname->fsp); + SIVAL(p,0,ea_size); /* Extended attributes */ + } + p += 4; + SIVAL(p,0,0); p += 4; /* Unknown - reserved ? */ + SBVAL(p,0,file_id); p += 8; + status = srvstr_push(base_data, flags2, p, + fname, PTR_DIFF(end_data, p), + STR_TERMINATE_ASCII, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + SIVAL(q, 0, len); + p += len; + + len = PTR_DIFF(p, pdata); + pad = (len + (align-1)) & ~(align-1); + /* + * offset to the next entry, the caller + * will overwrite it for the last entry + * that's why we always include the padding + */ + SIVAL(pdata,0,pad); + /* + * set padding to zero + */ + if (do_pad) { + memset(p, 0, pad - len); + p = pdata + pad; + } else { + p = pdata + len; + } + break; + + case SMB_FIND_ID_BOTH_DIRECTORY_INFO: + DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_ID_BOTH_DIRECTORY_INFO\n")); + was_8_3 = mangle_is_8_3(fname, True, conn->params); + p += 4; + SIVAL(p,0,reskey); p += 4; + put_long_date_full_timespec(conn->ts_res,p,&create_date_ts); p += 8; + put_long_date_full_timespec(conn->ts_res,p,&adate_ts); p += 8; + put_long_date_full_timespec(conn->ts_res,p,&mdate_ts); p += 8; + put_long_date_full_timespec(conn->ts_res,p,&cdate_ts); p += 8; + SOFF_T(p,0,file_size); p += 8; + SOFF_T(p,0,allocation_size); p += 8; + SIVAL(p,0,mode); p += 4; + q = p; p += 4; /* q is placeholder for name length */ + if (mode & FILE_ATTRIBUTE_REPARSE_POINT) { + SIVAL(p, 0, IO_REPARSE_TAG_DFS); + } else if (readdir_attr_data && + readdir_attr_data->type == RDATTR_AAPL) { + /* + * OS X specific SMB2 extension negotiated via + * AAPL create context: return max_access in + * ea_size field. + */ + SIVAL(p, 0, readdir_attr_data->attr_data.aapl.max_access); + } else { + unsigned int ea_size = estimate_ea_size(smb_fname->fsp); + SIVAL(p,0,ea_size); /* Extended attributes */ + } + p += 4; + + if (readdir_attr_data && + readdir_attr_data->type == RDATTR_AAPL) { + /* + * OS X specific SMB2 extension negotiated via + * AAPL create context: return resource fork + * length and compressed FinderInfo in + * shortname field. + * + * According to documentation short_name_len + * should be 0, but on the wire behaviour + * shows its set to 24 by clients. + */ + SSVAL(p, 0, 24); + + /* Resourefork length */ + SBVAL(p, 2, readdir_attr_data->attr_data.aapl.rfork_size); + + /* Compressed FinderInfo */ + memcpy(p + 10, &readdir_attr_data->attr_data.aapl.finder_info, 16); + } else if (!was_8_3 && check_mangled_names) { + char mangled_name[13]; /* mangled 8.3 name. */ + if (!name_to_8_3(fname,mangled_name,True, + conn->params)) { + /* Error - mangle failed ! */ + memset(mangled_name,'\0',12); + } + mangled_name[12] = 0; + status = srvstr_push(base_data, flags2, + p+2, mangled_name, 24, + STR_UPPER|STR_UNICODE, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + SSVAL(p, 0, len); + if (len < 24) { + memset(p + 2 + len,'\0',24 - len); + } + SSVAL(p, 0, len); + } else { + /* Clear the short name buffer. This is + * IMPORTANT as not doing so will trigger + * a Win2k client bug. JRA. + */ + memset(p,'\0',26); + } + p += 26; + + /* Reserved ? */ + if (readdir_attr_data && + readdir_attr_data->type == RDATTR_AAPL) { + /* + * OS X specific SMB2 extension negotiated via + * AAPL create context: return UNIX mode in + * reserved field. + */ + uint16_t aapl_mode = (uint16_t)readdir_attr_data->attr_data.aapl.unix_mode; + SSVAL(p, 0, aapl_mode); + } else { + SSVAL(p, 0, 0); + } + p += 2; + + SBVAL(p,0,file_id); p += 8; + status = srvstr_push(base_data, flags2, p, + fname, PTR_DIFF(end_data, p), + STR_TERMINATE_ASCII, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + SIVAL(q,0,len); + p += len; + + len = PTR_DIFF(p, pdata); + pad = (len + (align-1)) & ~(align-1); + /* + * offset to the next entry, the caller + * will overwrite it for the last entry + * that's why we always include the padding + */ + SIVAL(pdata,0,pad); + /* + * set padding to zero + */ + if (do_pad) { + memset(p, 0, pad - len); + p = pdata + pad; + } else { + p = pdata + len; + } + break; + + /* CIFS UNIX Extension. */ + + case SMB_FIND_FILE_UNIX: + case SMB_FIND_FILE_UNIX_INFO2: + p+= 4; + SIVAL(p,0,reskey); p+= 4; /* Used for continuing search. */ + + /* Begin of SMB_QUERY_FILE_UNIX_BASIC */ + + if (info_level == SMB_FIND_FILE_UNIX) { + DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_UNIX\n")); + p = store_file_unix_basic(conn, p, + NULL, &smb_fname->st); + status = srvstr_push(base_data, flags2, p, + fname, PTR_DIFF(end_data, p), + STR_TERMINATE, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } else { + DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_UNIX_INFO2\n")); + p = store_file_unix_basic_info2(conn, p, + NULL, &smb_fname->st); + nameptr = p; + p += 4; + status = srvstr_push(base_data, flags2, p, fname, + PTR_DIFF(end_data, p), 0, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + SIVAL(nameptr, 0, len); + } + + p += len; + + len = PTR_DIFF(p, pdata); + pad = (len + (align-1)) & ~(align-1); + /* + * offset to the next entry, the caller + * will overwrite it for the last entry + * that's why we always include the padding + */ + SIVAL(pdata,0,pad); + /* + * set padding to zero + */ + if (do_pad) { + memset(p, 0, pad - len); + p = pdata + pad; + } else { + p = pdata + len; + } + /* End of SMB_QUERY_FILE_UNIX_BASIC */ + + break; + + default: + return NT_STATUS_INVALID_LEVEL; + } + + if (PTR_DIFF(p,pdata) > space_remaining) { + DEBUG(9,("smbd_marshall_dir_entry: out of space " + "(wanted %u, had %d)\n", + (unsigned int)PTR_DIFF(p,pdata), + space_remaining )); + return STATUS_MORE_ENTRIES; /* Not finished - just out of space */ + } + + /* Setup the last entry pointer, as an offset from base_data */ + *last_entry_off = PTR_DIFF(last_entry_ptr,base_data); + /* Advance the data pointer to the next slot */ + *ppdata = p; + + return NT_STATUS_OK; +} + +NTSTATUS smbd_dirptr_lanman2_entry(TALLOC_CTX *ctx, + connection_struct *conn, + struct dptr_struct *dirptr, + uint16_t flags2, + const char *path_mask, + uint32_t dirtype, + int info_level, + int requires_resume_key, + bool dont_descend, + bool ask_sharemode, + bool get_dosmode, + uint8_t align, + bool do_pad, + char **ppdata, + char *base_data, + char *end_data, + int space_remaining, + struct smb_filename **_smb_fname, + bool *got_exact_match, + int *_last_entry_off, + struct ea_list *name_list, + struct file_id *file_id) +{ + const char *p; + const char *mask = NULL; + long prev_dirpos = 0; + uint32_t mode = 0; + char *fname = NULL; + struct smb_filename *smb_fname = NULL; + struct smbd_dirptr_lanman2_state state; + bool ok; + uint64_t last_entry_off = 0; + NTSTATUS status; + enum mangled_names_options mangled_names; + bool marshall_with_83_names; + + mangled_names = lp_mangled_names(conn->params); + + ZERO_STRUCT(state); + state.conn = conn; + state.info_level = info_level; + if (mangled_names != MANGLED_NAMES_NO) { + state.check_mangled_names = true; + } + state.has_wild = dptr_has_wild(dirptr); + state.got_exact_match = false; + state.case_sensitive = dptr_case_sensitive(dirptr); + + *got_exact_match = false; + + p = strrchr_m(path_mask,'/'); + if(p != NULL) { + if(p[1] == '\0') { + mask = "*.*"; + } else { + mask = p+1; + } + } else { + mask = path_mask; + } + + ok = smbd_dirptr_get_entry(ctx, + dirptr, + mask, + dirtype, + dont_descend, + ask_sharemode, + get_dosmode, + smbd_dirptr_lanman2_match_fn, + smbd_dirptr_lanman2_mode_fn, + &state, + &fname, + &smb_fname, + &mode, + &prev_dirpos); + if (!ok) { + return NT_STATUS_END_OF_FILE; + } + + *got_exact_match = state.got_exact_match; + + marshall_with_83_names = (mangled_names == MANGLED_NAMES_YES); + + status = smbd_marshall_dir_entry(ctx, + conn, + flags2, + info_level, + name_list, + marshall_with_83_names, + requires_resume_key, + mode, + fname, + smb_fname, + space_remaining, + align, + do_pad, + base_data, + ppdata, + end_data, + &last_entry_off); + if (NT_STATUS_EQUAL(status, NT_STATUS_ILLEGAL_CHARACTER)) { + DEBUG(1,("Conversion error: illegal character: %s\n", + smb_fname_str_dbg(smb_fname))); + } + + if (file_id != NULL) { + *file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st); + } + + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) + { + TALLOC_FREE(smb_fname); + TALLOC_FREE(fname); + return status; + } + + if (_smb_fname != NULL) { + /* + * smb_fname is already talloc'ed off ctx. + * We just need to make sure we don't return + * any stream_name, and replace base_name + * with fname in case base_name got mangled. + * This allows us to preserve any smb_fname->fsp + * for asynchronous handle lookups. + */ + TALLOC_FREE(smb_fname->stream_name); + TALLOC_FREE(smb_fname->base_name); + smb_fname->base_name = talloc_strdup(smb_fname, fname); + + if (smb_fname->base_name == NULL) { + TALLOC_FREE(smb_fname); + TALLOC_FREE(fname); + return NT_STATUS_NO_MEMORY; + } + *_smb_fname = smb_fname; + } else { + TALLOC_FREE(smb_fname); + } + TALLOC_FREE(fname); + + if (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) { + dptr_SeekDir(dirptr, prev_dirpos); + return status; + } + + *_last_entry_off = last_entry_off; + return NT_STATUS_OK; +} + +unsigned char *create_volume_objectid(connection_struct *conn, unsigned char objid[16]) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + E_md4hash(lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),objid); + return objid; +} + +static void samba_extended_info_version(struct smb_extended_info *extended_info) +{ + SMB_ASSERT(extended_info != NULL); + + extended_info->samba_magic = SAMBA_EXTENDED_INFO_MAGIC; + extended_info->samba_version = ((SAMBA_VERSION_MAJOR & 0xff) << 24) + | ((SAMBA_VERSION_MINOR & 0xff) << 16) + | ((SAMBA_VERSION_RELEASE & 0xff) << 8); +#ifdef SAMBA_VERSION_REVISION + extended_info->samba_version |= (tolower(*SAMBA_VERSION_REVISION) - 'a' + 1) & 0xff; +#endif + extended_info->samba_subversion = 0; +#ifdef SAMBA_VERSION_RC_RELEASE + extended_info->samba_subversion |= (SAMBA_VERSION_RC_RELEASE & 0xff) << 24; +#else +#ifdef SAMBA_VERSION_PRE_RELEASE + extended_info->samba_subversion |= (SAMBA_VERSION_PRE_RELEASE & 0xff) << 16; +#endif +#endif +#ifdef SAMBA_VERSION_VENDOR_PATCH + extended_info->samba_subversion |= (SAMBA_VERSION_VENDOR_PATCH & 0xffff); +#endif + extended_info->samba_gitcommitdate = 0; +#ifdef SAMBA_VERSION_COMMIT_TIME + unix_to_nt_time(&extended_info->samba_gitcommitdate, SAMBA_VERSION_COMMIT_TIME); +#endif + + memset(extended_info->samba_version_string, 0, + sizeof(extended_info->samba_version_string)); + + snprintf (extended_info->samba_version_string, + sizeof(extended_info->samba_version_string), + "%s", samba_version_string()); +} + +NTSTATUS smbd_do_qfsinfo(struct smbXsrv_connection *xconn, + connection_struct *conn, + TALLOC_CTX *mem_ctx, + uint16_t info_level, + uint16_t flags2, + unsigned int max_data_bytes, + size_t *fixed_portion, + struct smb_filename *fname, + char **ppdata, + int *ret_data_len) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + char *pdata, *end_data; + int data_len = 0; + size_t len = 0; + const char *vname = volume_label(talloc_tos(), SNUM(conn)); + int snum = SNUM(conn); + const char *fstype = lp_fstype(SNUM(conn)); + const char *filename = NULL; + const uint64_t bytes_per_sector = 512; + uint32_t additional_flags = 0; + struct smb_filename smb_fname; + SMB_STRUCT_STAT st; + NTSTATUS status = NT_STATUS_OK; + uint64_t df_ret; + uint32_t serial; + + if (fname == NULL || fname->base_name == NULL) { + filename = "."; + } else { + filename = fname->base_name; + } + + if (IS_IPC(conn)) { + if (info_level != SMB_QUERY_CIFS_UNIX_INFO) { + DEBUG(0,("smbd_do_qfsinfo: not an allowed " + "info level (0x%x) on IPC$.\n", + (unsigned int)info_level)); + return NT_STATUS_ACCESS_DENIED; + } + } + + DEBUG(3,("smbd_do_qfsinfo: level = %d\n", info_level)); + + smb_fname = (struct smb_filename) { + .base_name = discard_const_p(char, filename), + .flags = fname ? fname->flags : 0, + .twrp = fname ? fname->twrp : 0, + }; + + if(info_level != SMB_FS_QUOTA_INFORMATION + && SMB_VFS_STAT(conn, &smb_fname) != 0) { + DEBUG(2,("stat of . failed (%s)\n", strerror(errno))); + return map_nt_error_from_unix(errno); + } + + st = smb_fname.st; + + if (max_data_bytes + DIR_ENTRY_SAFETY_MARGIN < max_data_bytes) { + return NT_STATUS_INVALID_PARAMETER; + } + + *ppdata = (char *)SMB_REALLOC( + *ppdata, max_data_bytes + DIR_ENTRY_SAFETY_MARGIN); + if (*ppdata == NULL) { + return NT_STATUS_NO_MEMORY; + } + + pdata = *ppdata; + memset((char *)pdata,'\0',max_data_bytes + DIR_ENTRY_SAFETY_MARGIN); + end_data = pdata + max_data_bytes + DIR_ENTRY_SAFETY_MARGIN - 1; + + *fixed_portion = 0; + + switch (info_level) { + case SMB_INFO_ALLOCATION: + { + uint64_t dfree,dsize,bsize,block_size,sectors_per_unit; + data_len = 18; + df_ret = get_dfree_info(conn, &smb_fname, &bsize, + &dfree, &dsize); + if (df_ret == (uint64_t)-1) { + return map_nt_error_from_unix(errno); + } + + block_size = lp_block_size(snum); + if (bsize < block_size) { + uint64_t factor = block_size/bsize; + bsize = block_size; + dsize /= factor; + dfree /= factor; + } + if (bsize > block_size) { + uint64_t factor = bsize/block_size; + bsize = block_size; + dsize *= factor; + dfree *= factor; + } + sectors_per_unit = bsize/bytes_per_sector; + + DEBUG(5,("smbd_do_qfsinfo : SMB_INFO_ALLOCATION id=%x, bsize=%u, cSectorUnit=%u, \ +cBytesSector=%u, cUnitTotal=%u, cUnitAvail=%d\n", (unsigned int)st.st_ex_dev, (unsigned int)bsize, (unsigned int)sectors_per_unit, + (unsigned int)bytes_per_sector, (unsigned int)dsize, (unsigned int)dfree)); + + /* + * For large drives, return max values and not modulo. + */ + dsize = MIN(dsize, UINT32_MAX); + dfree = MIN(dfree, UINT32_MAX); + + SIVAL(pdata,l1_idFileSystem,st.st_ex_dev); + SIVAL(pdata,l1_cSectorUnit,sectors_per_unit); + SIVAL(pdata,l1_cUnit,dsize); + SIVAL(pdata,l1_cUnitAvail,dfree); + SSVAL(pdata,l1_cbSector,bytes_per_sector); + break; + } + + case SMB_INFO_VOLUME: + /* Return volume name */ + /* + * Add volume serial number - hash of a combination of + * the called hostname and the service name. + */ + serial = generate_volume_serial_number(lp_sub, snum); + SIVAL(pdata,0,serial); + /* + * Win2k3 and previous mess this up by sending a name length + * one byte short. I believe only older clients (OS/2 Win9x) use + * this call so try fixing this by adding a terminating null to + * the pushed string. The change here was adding the STR_TERMINATE. JRA. + */ + status = srvstr_push( + pdata, flags2, + pdata+l2_vol_szVolLabel, vname, + PTR_DIFF(end_data, pdata+l2_vol_szVolLabel), + STR_NOALIGN|STR_TERMINATE, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + SCVAL(pdata,l2_vol_cch,len); + data_len = l2_vol_szVolLabel + len; + DEBUG(5,("smbd_do_qfsinfo : time = %x, namelen = %u, " + "name = %s serial = 0x%04"PRIx32"\n", + (unsigned)convert_timespec_to_time_t(st.st_ex_ctime), + (unsigned)len, vname, serial)); + break; + + case SMB_QUERY_FS_ATTRIBUTE_INFO: + case SMB_FS_ATTRIBUTE_INFORMATION: + + additional_flags = 0; +#if defined(HAVE_SYS_QUOTAS) + additional_flags |= FILE_VOLUME_QUOTAS; +#endif + + if(lp_nt_acl_support(SNUM(conn))) { + additional_flags |= FILE_PERSISTENT_ACLS; + } + + /* Capabilities are filled in at connection time through STATVFS call */ + additional_flags |= conn->fs_capabilities; + additional_flags |= lp_parm_int(conn->params->service, + "share", "fake_fscaps", + 0); + + SIVAL(pdata,0,FILE_CASE_PRESERVED_NAMES|FILE_CASE_SENSITIVE_SEARCH| + FILE_SUPPORTS_OBJECT_IDS|FILE_UNICODE_ON_DISK| + additional_flags); /* FS ATTRIBUTES */ + + SIVAL(pdata,4,255); /* Max filename component length */ + /* NOTE! the fstype must *not* be null terminated or win98 won't recognise it + and will think we can't do long filenames */ + status = srvstr_push(pdata, flags2, pdata+12, fstype, + PTR_DIFF(end_data, pdata+12), + STR_UNICODE, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + SIVAL(pdata,8,len); + data_len = 12 + len; + if (max_data_bytes >= 16 && data_len > max_data_bytes) { + /* the client only requested a portion of the + file system name */ + data_len = max_data_bytes; + status = STATUS_BUFFER_OVERFLOW; + } + *fixed_portion = 16; + break; + + case SMB_QUERY_FS_LABEL_INFO: + case SMB_FS_LABEL_INFORMATION: + status = srvstr_push(pdata, flags2, pdata+4, vname, + PTR_DIFF(end_data, pdata+4), 0, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + data_len = 4 + len; + SIVAL(pdata,0,len); + break; + + case SMB_QUERY_FS_VOLUME_INFO: + case SMB_FS_VOLUME_INFORMATION: + put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER, + pdata, &st.st_ex_btime); + /* + * Add volume serial number - hash of a combination of + * the called hostname and the service name. + */ + serial = generate_volume_serial_number(lp_sub, snum); + SIVAL(pdata,8,serial); + + /* Max label len is 32 characters. */ + status = srvstr_push(pdata, flags2, pdata+18, vname, + PTR_DIFF(end_data, pdata+18), + STR_UNICODE, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + SIVAL(pdata,12,len); + data_len = 18+len; + + DEBUG(5,("smbd_do_qfsinfo : SMB_QUERY_FS_VOLUME_INFO " + "namelen = %d, vol=%s serv=%s " + "serial=0x%04"PRIx32"\n", + (int)strlen(vname),vname, + lp_servicename(talloc_tos(), lp_sub, snum), + serial)); + if (max_data_bytes >= 24 && data_len > max_data_bytes) { + /* the client only requested a portion of the + volume label */ + data_len = max_data_bytes; + status = STATUS_BUFFER_OVERFLOW; + } + *fixed_portion = 24; + break; + + case SMB_QUERY_FS_SIZE_INFO: + case SMB_FS_SIZE_INFORMATION: + { + uint64_t dfree,dsize,bsize,block_size,sectors_per_unit; + data_len = 24; + df_ret = get_dfree_info(conn, &smb_fname, &bsize, + &dfree, &dsize); + if (df_ret == (uint64_t)-1) { + return map_nt_error_from_unix(errno); + } + block_size = lp_block_size(snum); + if (bsize < block_size) { + uint64_t factor = block_size/bsize; + bsize = block_size; + dsize /= factor; + dfree /= factor; + } + if (bsize > block_size) { + uint64_t factor = bsize/block_size; + bsize = block_size; + dsize *= factor; + dfree *= factor; + } + sectors_per_unit = bsize/bytes_per_sector; + DEBUG(5,("smbd_do_qfsinfo : SMB_QUERY_FS_SIZE_INFO bsize=%u, cSectorUnit=%u, \ +cBytesSector=%u, cUnitTotal=%u, cUnitAvail=%d\n", (unsigned int)bsize, (unsigned int)sectors_per_unit, + (unsigned int)bytes_per_sector, (unsigned int)dsize, (unsigned int)dfree)); + SBIG_UINT(pdata,0,dsize); + SBIG_UINT(pdata,8,dfree); + SIVAL(pdata,16,sectors_per_unit); + SIVAL(pdata,20,bytes_per_sector); + *fixed_portion = 24; + break; + } + + case SMB_FS_FULL_SIZE_INFORMATION: + { + uint64_t dfree,dsize,bsize,block_size,sectors_per_unit; + data_len = 32; + df_ret = get_dfree_info(conn, &smb_fname, &bsize, + &dfree, &dsize); + if (df_ret == (uint64_t)-1) { + return map_nt_error_from_unix(errno); + } + block_size = lp_block_size(snum); + if (bsize < block_size) { + uint64_t factor = block_size/bsize; + bsize = block_size; + dsize /= factor; + dfree /= factor; + } + if (bsize > block_size) { + uint64_t factor = bsize/block_size; + bsize = block_size; + dsize *= factor; + dfree *= factor; + } + sectors_per_unit = bsize/bytes_per_sector; + DEBUG(5,("smbd_do_qfsinfo : SMB_QUERY_FS_FULL_SIZE_INFO bsize=%u, cSectorUnit=%u, \ +cBytesSector=%u, cUnitTotal=%u, cUnitAvail=%d\n", (unsigned int)bsize, (unsigned int)sectors_per_unit, + (unsigned int)bytes_per_sector, (unsigned int)dsize, (unsigned int)dfree)); + SBIG_UINT(pdata,0,dsize); /* Total Allocation units. */ + SBIG_UINT(pdata,8,dfree); /* Caller available allocation units. */ + SBIG_UINT(pdata,16,dfree); /* Actual available allocation units. */ + SIVAL(pdata,24,sectors_per_unit); /* Sectors per allocation unit. */ + SIVAL(pdata,28,bytes_per_sector); /* Bytes per sector. */ + *fixed_portion = 32; + break; + } + + case SMB_QUERY_FS_DEVICE_INFO: + case SMB_FS_DEVICE_INFORMATION: + { + uint32_t characteristics = FILE_DEVICE_IS_MOUNTED; + + if (!CAN_WRITE(conn)) { + characteristics |= FILE_READ_ONLY_DEVICE; + } + data_len = 8; + SIVAL(pdata,0,FILE_DEVICE_DISK); /* dev type */ + SIVAL(pdata,4,characteristics); + *fixed_portion = 8; + break; + } + +#ifdef HAVE_SYS_QUOTAS + case SMB_FS_QUOTA_INFORMATION: + /* + * what we have to send --metze: + * + * Unknown1: 24 NULL bytes + * Soft Quota Treshold: 8 bytes seems like uint64_t or so + * Hard Quota Limit: 8 bytes seems like uint64_t or so + * Quota Flags: 2 byte : + * Unknown3: 6 NULL bytes + * + * 48 bytes total + * + * details for Quota Flags: + * + * 0x0020 Log Limit: log if the user exceeds his Hard Quota + * 0x0010 Log Warn: log if the user exceeds his Soft Quota + * 0x0002 Deny Disk: deny disk access when the user exceeds his Hard Quota + * 0x0001 Enable Quotas: enable quota for this fs + * + */ + { + /* we need to fake up a fsp here, + * because its not send in this call + */ + files_struct fsp; + SMB_NTQUOTA_STRUCT quotas; + + ZERO_STRUCT(fsp); + ZERO_STRUCT(quotas); + + fsp.conn = conn; + fsp.fnum = FNUM_FIELD_INVALID; + + /* access check */ + if (get_current_uid(conn) != 0) { + DEBUG(0,("get_user_quota: access_denied " + "service [%s] user [%s]\n", + lp_servicename(talloc_tos(), lp_sub, SNUM(conn)), + conn->session_info->unix_info->unix_name)); + return NT_STATUS_ACCESS_DENIED; + } + + status = vfs_get_ntquota(&fsp, SMB_USER_FS_QUOTA_TYPE, + NULL, "as); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("vfs_get_ntquota() failed for service [%s]\n",lp_servicename(talloc_tos(), lp_sub, SNUM(conn)))); + return status; + } + + data_len = 48; + + DEBUG(10,("SMB_FS_QUOTA_INFORMATION: for service [%s]\n", + lp_servicename(talloc_tos(), lp_sub, SNUM(conn)))); + + /* Unknown1 24 NULL bytes*/ + SBIG_UINT(pdata,0,(uint64_t)0); + SBIG_UINT(pdata,8,(uint64_t)0); + SBIG_UINT(pdata,16,(uint64_t)0); + + /* Default Soft Quota 8 bytes */ + SBIG_UINT(pdata,24,quotas.softlim); + + /* Default Hard Quota 8 bytes */ + SBIG_UINT(pdata,32,quotas.hardlim); + + /* Quota flag 2 bytes */ + SSVAL(pdata,40,quotas.qflags); + + /* Unknown3 6 NULL bytes */ + SSVAL(pdata,42,0); + SIVAL(pdata,44,0); + + break; + } +#endif /* HAVE_SYS_QUOTAS */ + case SMB_FS_OBJECTID_INFORMATION: + { + unsigned char objid[16]; + struct smb_extended_info extended_info; + memcpy(pdata,create_volume_objectid(conn, objid),16); + samba_extended_info_version (&extended_info); + SIVAL(pdata,16,extended_info.samba_magic); + SIVAL(pdata,20,extended_info.samba_version); + SIVAL(pdata,24,extended_info.samba_subversion); + SBIG_UINT(pdata,28,extended_info.samba_gitcommitdate); + memcpy(pdata+36,extended_info.samba_version_string,28); + data_len = 64; + break; + } + + case SMB_FS_SECTOR_SIZE_INFORMATION: + { + data_len = 28; + /* + * These values match a physical Windows Server 2012 + * share backed by NTFS atop spinning rust. + */ + DEBUG(5, ("SMB_FS_SECTOR_SIZE_INFORMATION:")); + /* logical_bytes_per_sector */ + SIVAL(pdata, 0, bytes_per_sector); + /* phys_bytes_per_sector_atomic */ + SIVAL(pdata, 4, bytes_per_sector); + /* phys_bytes_per_sector_perf */ + SIVAL(pdata, 8, bytes_per_sector); + /* fs_effective_phys_bytes_per_sector_atomic */ + SIVAL(pdata, 12, bytes_per_sector); + /* flags */ + SIVAL(pdata, 16, SSINFO_FLAGS_ALIGNED_DEVICE + | SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE); + /* byte_off_sector_align */ + SIVAL(pdata, 20, 0); + /* byte_off_partition_align */ + SIVAL(pdata, 24, 0); + *fixed_portion = 28; + break; + } + + +#if defined(WITH_SMB1SERVER) + /* + * Query the version and capabilities of the CIFS UNIX extensions + * in use. + */ + + case SMB_QUERY_CIFS_UNIX_INFO: + { + bool large_write = lp_min_receive_file_size() && + !smb1_srv_is_signing_active(xconn); + bool large_read = !smb1_srv_is_signing_active(xconn); + int encrypt_caps = 0; + + if (!lp_smb1_unix_extensions()) { + return NT_STATUS_INVALID_LEVEL; + } + + switch (conn->encrypt_level) { + case SMB_SIGNING_OFF: + encrypt_caps = 0; + break; + case SMB_SIGNING_DESIRED: + case SMB_SIGNING_IF_REQUIRED: + case SMB_SIGNING_DEFAULT: + encrypt_caps = CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP; + break; + case SMB_SIGNING_REQUIRED: + encrypt_caps = CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP| + CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP; + large_write = false; + large_read = false; + break; + } + + data_len = 12; + SSVAL(pdata,0,CIFS_UNIX_MAJOR_VERSION); + SSVAL(pdata,2,CIFS_UNIX_MINOR_VERSION); + + /* We have POSIX ACLs, pathname, encryption, + * large read/write, and locking capability. */ + + SBIG_UINT(pdata,4,((uint64_t)( + CIFS_UNIX_POSIX_ACLS_CAP| + CIFS_UNIX_POSIX_PATHNAMES_CAP| + CIFS_UNIX_FCNTL_LOCKS_CAP| + CIFS_UNIX_EXTATTR_CAP| + CIFS_UNIX_POSIX_PATH_OPERATIONS_CAP| + encrypt_caps| + (large_read ? CIFS_UNIX_LARGE_READ_CAP : 0) | + (large_write ? + CIFS_UNIX_LARGE_WRITE_CAP : 0)))); + break; + } +#endif + + case SMB_QUERY_POSIX_FS_INFO: + { + int rc; + struct vfs_statvfs_struct svfs; + + if (!lp_smb1_unix_extensions()) { + return NT_STATUS_INVALID_LEVEL; + } + + rc = SMB_VFS_STATVFS(conn, &smb_fname, &svfs); + + if (!rc) { + data_len = 56; + SIVAL(pdata,0,svfs.OptimalTransferSize); + SIVAL(pdata,4,svfs.BlockSize); + SBIG_UINT(pdata,8,svfs.TotalBlocks); + SBIG_UINT(pdata,16,svfs.BlocksAvail); + SBIG_UINT(pdata,24,svfs.UserBlocksAvail); + SBIG_UINT(pdata,32,svfs.TotalFileNodes); + SBIG_UINT(pdata,40,svfs.FreeFileNodes); + SBIG_UINT(pdata,48,svfs.FsIdentifier); + DEBUG(5,("smbd_do_qfsinfo : SMB_QUERY_POSIX_FS_INFO succsessful\n")); +#ifdef EOPNOTSUPP + } else if (rc == EOPNOTSUPP) { + return NT_STATUS_INVALID_LEVEL; +#endif /* EOPNOTSUPP */ + } else { + DEBUG(0,("vfs_statvfs() failed for service [%s]\n",lp_servicename(talloc_tos(), lp_sub, SNUM(conn)))); + return NT_STATUS_DOS(ERRSRV, ERRerror); + } + break; + } + + case SMB_QUERY_POSIX_WHOAMI: + { + uint32_t flags = 0; + uint32_t sid_bytes; + uint32_t i; + + if (!lp_smb1_unix_extensions()) { + return NT_STATUS_INVALID_LEVEL; + } + + if (max_data_bytes < 40) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + if (security_session_user_level(conn->session_info, NULL) < SECURITY_USER) { + flags |= SMB_WHOAMI_GUEST; + } + + /* NOTE: 8 bytes for UID/GID, irrespective of native + * platform size. This matches + * SMB_QUERY_FILE_UNIX_BASIC and friends. + */ + data_len = 4 /* flags */ + + 4 /* flag mask */ + + 8 /* uid */ + + 8 /* gid */ + + 4 /* ngroups */ + + 4 /* num_sids */ + + 4 /* SID bytes */ + + 4 /* pad/reserved */ + + (conn->session_info->unix_token->ngroups * 8) + /* groups list */ + + (conn->session_info->security_token->num_sids * + SID_MAX_SIZE) + /* SID list */; + + SIVAL(pdata, 0, flags); + SIVAL(pdata, 4, SMB_WHOAMI_MASK); + SBIG_UINT(pdata, 8, + (uint64_t)conn->session_info->unix_token->uid); + SBIG_UINT(pdata, 16, + (uint64_t)conn->session_info->unix_token->gid); + + + if (data_len >= max_data_bytes) { + /* Potential overflow, skip the GIDs and SIDs. */ + + SIVAL(pdata, 24, 0); /* num_groups */ + SIVAL(pdata, 28, 0); /* num_sids */ + SIVAL(pdata, 32, 0); /* num_sid_bytes */ + SIVAL(pdata, 36, 0); /* reserved */ + + data_len = 40; + break; + } + + SIVAL(pdata, 24, conn->session_info->unix_token->ngroups); + SIVAL(pdata, 28, conn->session_info->security_token->num_sids); + + /* We walk the SID list twice, but this call is fairly + * infrequent, and I don't expect that it's performance + * sensitive -- jpeach + */ + for (i = 0, sid_bytes = 0; + i < conn->session_info->security_token->num_sids; ++i) { + sid_bytes += ndr_size_dom_sid( + &conn->session_info->security_token->sids[i], + 0); + } + + /* SID list byte count */ + SIVAL(pdata, 32, sid_bytes); + + /* 4 bytes pad/reserved - must be zero */ + SIVAL(pdata, 36, 0); + data_len = 40; + + /* GID list */ + for (i = 0; i < conn->session_info->unix_token->ngroups; ++i) { + SBIG_UINT(pdata, data_len, + (uint64_t)conn->session_info->unix_token->groups[i]); + data_len += 8; + } + + /* SID list */ + for (i = 0; + i < conn->session_info->security_token->num_sids; ++i) { + int sid_len = ndr_size_dom_sid( + &conn->session_info->security_token->sids[i], + 0); + + sid_linearize((uint8_t *)(pdata + data_len), + sid_len, + &conn->session_info->security_token->sids[i]); + data_len += sid_len; + } + + break; + } + + case SMB_MAC_QUERY_FS_INFO: + /* + * Thursby MAC extension... ONLY on NTFS filesystems + * once we do streams then we don't need this + */ + if (strequal(lp_fstype(SNUM(conn)),"NTFS")) { + data_len = 88; + SIVAL(pdata,84,0x100); /* Don't support mac... */ + break; + } + + FALL_THROUGH; + default: + return NT_STATUS_INVALID_LEVEL; + } + + *ret_data_len = data_len; + return status; +} + +NTSTATUS smb_set_fsquota(connection_struct *conn, + struct smb_request *req, + files_struct *fsp, + const DATA_BLOB *qdata) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + NTSTATUS status; + SMB_NTQUOTA_STRUCT quotas; + + ZERO_STRUCT(quotas); + + /* access check */ + if ((get_current_uid(conn) != 0) || !CAN_WRITE(conn)) { + DEBUG(3, ("set_fsquota: access_denied service [%s] user [%s]\n", + lp_servicename(talloc_tos(), lp_sub, SNUM(conn)), + conn->session_info->unix_info->unix_name)); + return NT_STATUS_ACCESS_DENIED; + } + + if (!check_fsp_ntquota_handle(conn, req, + fsp)) { + DEBUG(1, ("set_fsquota: no valid QUOTA HANDLE\n")); + return NT_STATUS_INVALID_HANDLE; + } + + /* note: normally there're 48 bytes, + * but we didn't use the last 6 bytes for now + * --metze + */ + if (qdata->length < 42) { + DEBUG(0,("set_fsquota: requires total_data(%u) >= 42 bytes!\n", + (unsigned int)qdata->length)); + return NT_STATUS_INVALID_PARAMETER; + } + + /* unknown_1 24 NULL bytes in pdata*/ + + /* the soft quotas 8 bytes (uint64_t)*/ + quotas.softlim = BVAL(qdata->data,24); + + /* the hard quotas 8 bytes (uint64_t)*/ + quotas.hardlim = BVAL(qdata->data,32); + + /* quota_flags 2 bytes **/ + quotas.qflags = SVAL(qdata->data,40); + + /* unknown_2 6 NULL bytes follow*/ + + /* now set the quotas */ + if (vfs_set_ntquota(fsp, SMB_USER_FS_QUOTA_TYPE, NULL, "as)!=0) { + DEBUG(1, ("vfs_set_ntquota() failed for service [%s]\n", + lp_servicename(talloc_tos(), lp_sub, SNUM(conn)))); + status = map_nt_error_from_unix(errno); + } else { + status = NT_STATUS_OK; + } + return status; +} + +NTSTATUS smbd_do_setfsinfo(connection_struct *conn, + struct smb_request *req, + TALLOC_CTX *mem_ctx, + uint16_t info_level, + files_struct *fsp, + const DATA_BLOB *pdata) +{ + switch (info_level) { + case SMB_FS_QUOTA_INFORMATION: + { + return smb_set_fsquota(conn, + req, + fsp, + pdata); + } + + default: + break; + } + return NT_STATUS_INVALID_LEVEL; +} + +#if defined(HAVE_POSIX_ACLS) +/**************************************************************************** + Utility function to count the number of entries in a POSIX acl. +****************************************************************************/ + +static unsigned int count_acl_entries(connection_struct *conn, SMB_ACL_T posix_acl) +{ + unsigned int ace_count = 0; + int entry_id = SMB_ACL_FIRST_ENTRY; + SMB_ACL_ENTRY_T entry; + + while ( posix_acl && (sys_acl_get_entry(posix_acl, entry_id, &entry) == 1)) { + /* get_next... */ + if (entry_id == SMB_ACL_FIRST_ENTRY) { + entry_id = SMB_ACL_NEXT_ENTRY; + } + ace_count++; + } + return ace_count; +} + +/**************************************************************************** + Utility function to marshall a POSIX acl into wire format. +****************************************************************************/ + +static bool marshall_posix_acl(connection_struct *conn, char *pdata, SMB_STRUCT_STAT *pst, SMB_ACL_T posix_acl) +{ + int entry_id = SMB_ACL_FIRST_ENTRY; + SMB_ACL_ENTRY_T entry; + + while ( posix_acl && (sys_acl_get_entry(posix_acl, entry_id, &entry) == 1)) { + SMB_ACL_TAG_T tagtype; + SMB_ACL_PERMSET_T permset; + unsigned char perms = 0; + unsigned int own_grp; + + /* get_next... */ + if (entry_id == SMB_ACL_FIRST_ENTRY) { + entry_id = SMB_ACL_NEXT_ENTRY; + } + + if (sys_acl_get_tag_type(entry, &tagtype) == -1) { + DEBUG(0,("marshall_posix_acl: SMB_VFS_SYS_ACL_GET_TAG_TYPE failed.\n")); + return False; + } + + if (sys_acl_get_permset(entry, &permset) == -1) { + DEBUG(0,("marshall_posix_acl: SMB_VFS_SYS_ACL_GET_PERMSET failed.\n")); + return False; + } + + perms |= (sys_acl_get_perm(permset, SMB_ACL_READ) ? SMB_POSIX_ACL_READ : 0); + perms |= (sys_acl_get_perm(permset, SMB_ACL_WRITE) ? SMB_POSIX_ACL_WRITE : 0); + perms |= (sys_acl_get_perm(permset, SMB_ACL_EXECUTE) ? SMB_POSIX_ACL_EXECUTE : 0); + + SCVAL(pdata,1,perms); + + switch (tagtype) { + case SMB_ACL_USER_OBJ: + SCVAL(pdata,0,SMB_POSIX_ACL_USER_OBJ); + own_grp = (unsigned int)pst->st_ex_uid; + SIVAL(pdata,2,own_grp); + SIVAL(pdata,6,0); + break; + case SMB_ACL_USER: + { + uid_t *puid = (uid_t *)sys_acl_get_qualifier(entry); + if (!puid) { + DEBUG(0,("marshall_posix_acl: SMB_VFS_SYS_ACL_GET_QUALIFIER failed.\n")); + return False; + } + own_grp = (unsigned int)*puid; + SCVAL(pdata,0,SMB_POSIX_ACL_USER); + SIVAL(pdata,2,own_grp); + SIVAL(pdata,6,0); + break; + } + case SMB_ACL_GROUP_OBJ: + SCVAL(pdata,0,SMB_POSIX_ACL_GROUP_OBJ); + own_grp = (unsigned int)pst->st_ex_gid; + SIVAL(pdata,2,own_grp); + SIVAL(pdata,6,0); + break; + case SMB_ACL_GROUP: + { + gid_t *pgid= (gid_t *)sys_acl_get_qualifier(entry); + if (!pgid) { + DEBUG(0,("marshall_posix_acl: SMB_VFS_SYS_ACL_GET_QUALIFIER failed.\n")); + return False; + } + own_grp = (unsigned int)*pgid; + SCVAL(pdata,0,SMB_POSIX_ACL_GROUP); + SIVAL(pdata,2,own_grp); + SIVAL(pdata,6,0); + break; + } + case SMB_ACL_MASK: + SCVAL(pdata,0,SMB_POSIX_ACL_MASK); + SIVAL(pdata,2,0xFFFFFFFF); + SIVAL(pdata,6,0xFFFFFFFF); + break; + case SMB_ACL_OTHER: + SCVAL(pdata,0,SMB_POSIX_ACL_OTHER); + SIVAL(pdata,2,0xFFFFFFFF); + SIVAL(pdata,6,0xFFFFFFFF); + break; + default: + DEBUG(0,("marshall_posix_acl: unknown tagtype.\n")); + return False; + } + pdata += SMB_POSIX_ACL_ENTRY_SIZE; + } + + return True; +} +#endif + +/**************************************************************************** + Store the FILE_UNIX_BASIC info. +****************************************************************************/ + +static char *store_file_unix_basic(connection_struct *conn, + char *pdata, + files_struct *fsp, + const SMB_STRUCT_STAT *psbuf) +{ + dev_t devno; + + DEBUG(10,("store_file_unix_basic: SMB_QUERY_FILE_UNIX_BASIC\n")); + DEBUG(4,("store_file_unix_basic: st_mode=%o\n",(int)psbuf->st_ex_mode)); + + SOFF_T(pdata,0,get_file_size_stat(psbuf)); /* File size 64 Bit */ + pdata += 8; + + SOFF_T(pdata,0,SMB_VFS_GET_ALLOC_SIZE(conn,fsp,psbuf)); /* Number of bytes used on disk - 64 Bit */ + pdata += 8; + + put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER, pdata, &psbuf->st_ex_ctime); /* Change Time 64 Bit */ + put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER ,pdata+8, &psbuf->st_ex_atime); /* Last access time 64 Bit */ + put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER, pdata+16, &psbuf->st_ex_mtime); /* Last modification time 64 Bit */ + pdata += 24; + + SIVAL(pdata,0,psbuf->st_ex_uid); /* user id for the owner */ + SIVAL(pdata,4,0); + pdata += 8; + + SIVAL(pdata,0,psbuf->st_ex_gid); /* group id of owner */ + SIVAL(pdata,4,0); + pdata += 8; + + SIVAL(pdata,0,unix_filetype(psbuf->st_ex_mode)); + pdata += 4; + + if (S_ISBLK(psbuf->st_ex_mode) || S_ISCHR(psbuf->st_ex_mode)) { + devno = psbuf->st_ex_rdev; + } else { + devno = psbuf->st_ex_dev; + } + + SIVAL(pdata,0,unix_dev_major(devno)); /* Major device number if type is device */ + SIVAL(pdata,4,0); + pdata += 8; + + SIVAL(pdata,0,unix_dev_minor(devno)); /* Minor device number if type is device */ + SIVAL(pdata,4,0); + pdata += 8; + + SINO_T_VAL(pdata, 0, psbuf->st_ex_ino); /* inode number */ + pdata += 8; + + SIVAL(pdata,0, unix_perms_to_wire(psbuf->st_ex_mode)); /* Standard UNIX file permissions */ + SIVAL(pdata,4,0); + pdata += 8; + + SIVAL(pdata,0,psbuf->st_ex_nlink); /* number of hard links */ + SIVAL(pdata,4,0); + pdata += 8; + + return pdata; +} + +/* Forward and reverse mappings from the UNIX_INFO2 file flags field and + * the chflags(2) (or equivalent) flags. + * + * XXX: this really should be behind the VFS interface. To do this, we would + * need to alter SMB_STRUCT_STAT so that it included a flags and a mask field. + * Each VFS module could then implement its own mapping as appropriate for the + * platform. We would then pass the SMB flags into SMB_VFS_CHFLAGS. + */ +static const struct {unsigned stat_fflag; unsigned smb_fflag;} + info2_flags_map[] = +{ +#ifdef UF_NODUMP + { UF_NODUMP, EXT_DO_NOT_BACKUP }, +#endif + +#ifdef UF_IMMUTABLE + { UF_IMMUTABLE, EXT_IMMUTABLE }, +#endif + +#ifdef UF_APPEND + { UF_APPEND, EXT_OPEN_APPEND_ONLY }, +#endif + +#ifdef UF_HIDDEN + { UF_HIDDEN, EXT_HIDDEN }, +#endif + + /* Do not remove. We need to guarantee that this array has at least one + * entry to build on HP-UX. + */ + { 0, 0 } + +}; + +static void map_info2_flags_from_sbuf(const SMB_STRUCT_STAT *psbuf, + uint32_t *smb_fflags, uint32_t *smb_fmask) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(info2_flags_map); ++i) { + *smb_fmask |= info2_flags_map[i].smb_fflag; + if (psbuf->st_ex_flags & info2_flags_map[i].stat_fflag) { + *smb_fflags |= info2_flags_map[i].smb_fflag; + } + } +} + +static bool map_info2_flags_to_sbuf(const SMB_STRUCT_STAT *psbuf, + const uint32_t smb_fflags, + const uint32_t smb_fmask, + int *stat_fflags) +{ + uint32_t max_fmask = 0; + size_t i; + + *stat_fflags = psbuf->st_ex_flags; + + /* For each flags requested in smb_fmask, check the state of the + * corresponding flag in smb_fflags and set or clear the matching + * stat flag. + */ + + for (i = 0; i < ARRAY_SIZE(info2_flags_map); ++i) { + max_fmask |= info2_flags_map[i].smb_fflag; + if (smb_fmask & info2_flags_map[i].smb_fflag) { + if (smb_fflags & info2_flags_map[i].smb_fflag) { + *stat_fflags |= info2_flags_map[i].stat_fflag; + } else { + *stat_fflags &= ~info2_flags_map[i].stat_fflag; + } + } + } + + /* If smb_fmask is asking to set any bits that are not supported by + * our flag mappings, we should fail. + */ + if ((smb_fmask & max_fmask) != smb_fmask) { + return False; + } + + return True; +} + + +/* Just like SMB_QUERY_FILE_UNIX_BASIC, but with the addition + * of file flags and birth (create) time. + */ +static char *store_file_unix_basic_info2(connection_struct *conn, + char *pdata, + files_struct *fsp, + const SMB_STRUCT_STAT *psbuf) +{ + uint32_t file_flags = 0; + uint32_t flags_mask = 0; + + pdata = store_file_unix_basic(conn, pdata, fsp, psbuf); + + /* Create (birth) time 64 bit */ + put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER,pdata, &psbuf->st_ex_btime); + pdata += 8; + + map_info2_flags_from_sbuf(psbuf, &file_flags, &flags_mask); + SIVAL(pdata, 0, file_flags); /* flags */ + SIVAL(pdata, 4, flags_mask); /* mask */ + pdata += 8; + + return pdata; +} + +static NTSTATUS marshall_stream_info(unsigned int num_streams, + const struct stream_struct *streams, + char *data, + unsigned int max_data_bytes, + unsigned int *data_size) +{ + unsigned int i; + unsigned int ofs = 0; + + if (max_data_bytes < 32) { + return NT_STATUS_INFO_LENGTH_MISMATCH; + } + + for (i = 0; i < num_streams; i++) { + unsigned int next_offset; + size_t namelen; + smb_ucs2_t *namebuf; + + if (!push_ucs2_talloc(talloc_tos(), &namebuf, + streams[i].name, &namelen) || + namelen <= 2) + { + return NT_STATUS_INVALID_PARAMETER; + } + + /* + * name_buf is now null-terminated, we need to marshall as not + * terminated + */ + + namelen -= 2; + + /* + * We cannot overflow ... + */ + if ((ofs + 24 + namelen) > max_data_bytes) { + DEBUG(10, ("refusing to overflow reply at stream %u\n", + i)); + TALLOC_FREE(namebuf); + return STATUS_BUFFER_OVERFLOW; + } + + SIVAL(data, ofs+4, namelen); + SOFF_T(data, ofs+8, streams[i].size); + SOFF_T(data, ofs+16, streams[i].alloc_size); + memcpy(data+ofs+24, namebuf, namelen); + TALLOC_FREE(namebuf); + + next_offset = ofs + 24 + namelen; + + if (i == num_streams-1) { + SIVAL(data, ofs, 0); + } + else { + unsigned int align = ndr_align_size(next_offset, 8); + + if ((next_offset + align) > max_data_bytes) { + DEBUG(10, ("refusing to overflow align " + "reply at stream %u\n", + i)); + TALLOC_FREE(namebuf); + return STATUS_BUFFER_OVERFLOW; + } + + memset(data+next_offset, 0, align); + next_offset += align; + + SIVAL(data, ofs, next_offset - ofs); + ofs = next_offset; + } + + ofs = next_offset; + } + + DEBUG(10, ("max_data: %u, data_size: %u\n", max_data_bytes, ofs)); + + *data_size = ofs; + + return NT_STATUS_OK; +} + +static NTSTATUS smb_unix_read_symlink(connection_struct *conn, + struct smb_request *req, + struct smb_filename *smb_fname, + char *pdata, + unsigned int data_size_in, + unsigned int *pdata_size_out) +{ + NTSTATUS status; + size_t len = 0; + int link_len = 0; + struct smb_filename *parent_fname = NULL; + struct smb_filename *base_name = NULL; + + char *buffer = talloc_array(talloc_tos(), char, PATH_MAX+1); + + if (!buffer) { + return NT_STATUS_NO_MEMORY; + } + + DBG_DEBUG("SMB_QUERY_FILE_UNIX_LINK for file %s\n", + smb_fname_str_dbg(smb_fname)); + + if(!S_ISLNK(smb_fname->st.st_ex_mode)) { + TALLOC_FREE(buffer); + return NT_STATUS_DOS(ERRSRV, ERRbadlink); + } + + status = parent_pathref(talloc_tos(), + conn->cwd_fsp, + smb_fname, + &parent_fname, + &base_name); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(buffer); + return status; + } + + link_len = SMB_VFS_READLINKAT(conn, + parent_fname->fsp, + base_name, + buffer, + PATH_MAX); + + TALLOC_FREE(parent_fname); + + if (link_len == -1) { + TALLOC_FREE(buffer); + return map_nt_error_from_unix(errno); + } + + buffer[link_len] = 0; + status = srvstr_push(pdata, + req->flags2, + pdata, + buffer, + data_size_in, + STR_TERMINATE, + &len); + TALLOC_FREE(buffer); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + *pdata_size_out = len; + + return NT_STATUS_OK; +} + +#if defined(HAVE_POSIX_ACLS) +static NTSTATUS smb_query_posix_acl(connection_struct *conn, + struct smb_request *req, + files_struct *fsp, + struct smb_filename *smb_fname, + char *pdata, + unsigned int data_size_in, + unsigned int *pdata_size_out) +{ + SMB_ACL_T file_acl = NULL; + SMB_ACL_T def_acl = NULL; + uint16_t num_file_acls = 0; + uint16_t num_def_acls = 0; + unsigned int size_needed = 0; + NTSTATUS status; + bool ok; + bool close_fsp = false; + + /* + * Ensure we always operate on a file descriptor, not just + * the filename. + */ + if (fsp == NULL || !fsp->fsp_flags.is_fsa) { + uint32_t access_mask = SEC_STD_READ_CONTROL| + FILE_READ_ATTRIBUTES| + FILE_WRITE_ATTRIBUTES; + + status = get_posix_fsp(conn, + req, + smb_fname, + access_mask, + &fsp); + + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + close_fsp = true; + } + + SMB_ASSERT(fsp != NULL); + + status = refuse_symlink_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + file_acl = SMB_VFS_SYS_ACL_GET_FD(fsp, SMB_ACL_TYPE_ACCESS, + talloc_tos()); + + if (file_acl == NULL && no_acl_syscall_error(errno)) { + DBG_INFO("ACLs not implemented on " + "filesystem containing %s\n", + fsp_str_dbg(fsp)); + status = NT_STATUS_NOT_IMPLEMENTED; + goto out; + } + + if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) { + /* + * We can only have default POSIX ACLs on + * directories. + */ + if (!fsp->fsp_flags.is_directory) { + DBG_INFO("Non-directory open %s\n", + fsp_str_dbg(fsp)); + status = NT_STATUS_INVALID_HANDLE; + goto out; + } + def_acl = SMB_VFS_SYS_ACL_GET_FD(fsp, + SMB_ACL_TYPE_DEFAULT, + talloc_tos()); + def_acl = free_empty_sys_acl(conn, def_acl); + } + + num_file_acls = count_acl_entries(conn, file_acl); + num_def_acls = count_acl_entries(conn, def_acl); + + /* Wrap checks. */ + if (num_file_acls + num_def_acls < num_file_acls) { + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + size_needed = num_file_acls + num_def_acls; + + /* + * (size_needed * SMB_POSIX_ACL_ENTRY_SIZE) must be less + * than UINT_MAX, so check by division. + */ + if (size_needed > (UINT_MAX/SMB_POSIX_ACL_ENTRY_SIZE)) { + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + size_needed = size_needed*SMB_POSIX_ACL_ENTRY_SIZE; + if (size_needed + SMB_POSIX_ACL_HEADER_SIZE < size_needed) { + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + size_needed += SMB_POSIX_ACL_HEADER_SIZE; + + if ( data_size_in < size_needed) { + DBG_INFO("data_size too small (%u) need %u\n", + data_size_in, + size_needed); + status = NT_STATUS_BUFFER_TOO_SMALL; + goto out; + } + + SSVAL(pdata,0,SMB_POSIX_ACL_VERSION); + SSVAL(pdata,2,num_file_acls); + SSVAL(pdata,4,num_def_acls); + pdata += SMB_POSIX_ACL_HEADER_SIZE; + + ok = marshall_posix_acl(conn, + pdata, + &fsp->fsp_name->st, + file_acl); + if (!ok) { + status = NT_STATUS_INTERNAL_ERROR; + goto out; + } + pdata += (num_file_acls*SMB_POSIX_ACL_ENTRY_SIZE); + + ok = marshall_posix_acl(conn, + pdata, + &fsp->fsp_name->st, + def_acl); + if (!ok) { + status = NT_STATUS_INTERNAL_ERROR; + goto out; + } + + *pdata_size_out = size_needed; + status = NT_STATUS_OK; + + out: + + if (close_fsp) { + /* + * Ensure the stat struct in smb_fname is up to + * date. Structure copy. + */ + smb_fname->st = fsp->fsp_name->st; + (void)close_file_free(req, &fsp, NORMAL_CLOSE); + } + + TALLOC_FREE(file_acl); + TALLOC_FREE(def_acl); + return status; +} +#endif + +NTSTATUS smbd_do_qfilepathinfo(connection_struct *conn, + TALLOC_CTX *mem_ctx, + struct smb_request *req, + uint16_t info_level, + files_struct *fsp, + struct smb_filename *smb_fname, + bool delete_pending, + struct timespec write_time_ts, + struct ea_list *ea_list, + int lock_data_count, + char *lock_data, + uint16_t flags2, + unsigned int max_data_bytes, + size_t *fixed_portion, + char **ppdata, + unsigned int *pdata_size) +{ + char *pdata = *ppdata; + char *dstart, *dend; + unsigned int data_size; + struct timespec create_time_ts, mtime_ts, atime_ts, ctime_ts; + time_t create_time, mtime, atime, c_time; + SMB_STRUCT_STAT *psbuf = NULL; + SMB_STRUCT_STAT *base_sp = NULL; + char *p; + char *base_name; + char *dos_fname; + int mode; + int nlink; + NTSTATUS status; + uint64_t file_size = 0; + uint64_t pos = 0; + uint64_t allocation_size = 0; + uint64_t file_id = 0; + uint32_t access_mask = 0; + size_t len = 0; + + if (INFO_LEVEL_IS_UNIX(info_level)) { + if (!lp_smb1_unix_extensions()) { + return NT_STATUS_INVALID_LEVEL; + } + if (!req->posix_pathnames) { + return NT_STATUS_INVALID_LEVEL; + } + } + + DEBUG(5,("smbd_do_qfilepathinfo: %s (%s) level=%d max_data=%u\n", + smb_fname_str_dbg(smb_fname), + fsp_fnum_dbg(fsp), + info_level, max_data_bytes)); + + /* + * In case of querying a symlink in POSIX context, + * fsp will be NULL. fdos_mode() deals with it. + */ + if (fsp != NULL) { + smb_fname = fsp->fsp_name; + } + mode = fdos_mode(fsp); + psbuf = &smb_fname->st; + + if (fsp != NULL) { + base_sp = fsp->base_fsp ? + &fsp->base_fsp->fsp_name->st : + &fsp->fsp_name->st; + } else { + base_sp = &smb_fname->st; + } + + nlink = psbuf->st_ex_nlink; + + if (nlink && (mode&FILE_ATTRIBUTE_DIRECTORY)) { + nlink = 1; + } + + if ((nlink > 0) && delete_pending) { + nlink -= 1; + } + + if (max_data_bytes + DIR_ENTRY_SAFETY_MARGIN < max_data_bytes) { + return NT_STATUS_INVALID_PARAMETER; + } + + data_size = max_data_bytes + DIR_ENTRY_SAFETY_MARGIN; + *ppdata = (char *)SMB_REALLOC(*ppdata, data_size); + if (*ppdata == NULL) { + return NT_STATUS_NO_MEMORY; + } + pdata = *ppdata; + dstart = pdata; + dend = dstart + data_size - 1; + + if (!is_omit_timespec(&write_time_ts) && + !INFO_LEVEL_IS_UNIX(info_level)) + { + update_stat_ex_mtime(psbuf, write_time_ts); + } + + create_time_ts = get_create_timespec(conn, fsp, smb_fname); + mtime_ts = psbuf->st_ex_mtime; + atime_ts = psbuf->st_ex_atime; + ctime_ts = get_change_timespec(conn, fsp, smb_fname); + + if (lp_dos_filetime_resolution(SNUM(conn))) { + dos_filetime_timespec(&create_time_ts); + dos_filetime_timespec(&mtime_ts); + dos_filetime_timespec(&atime_ts); + dos_filetime_timespec(&ctime_ts); + } + + create_time = convert_timespec_to_time_t(create_time_ts); + mtime = convert_timespec_to_time_t(mtime_ts); + atime = convert_timespec_to_time_t(atime_ts); + c_time = convert_timespec_to_time_t(ctime_ts); + + p = strrchr_m(smb_fname->base_name,'/'); + if (!p) + base_name = smb_fname->base_name; + else + base_name = p+1; + + /* NT expects the name to be in an exact form of the *full* + filename. See the trans2 torture test */ + if (ISDOT(base_name)) { + dos_fname = talloc_strdup(mem_ctx, "\\"); + if (!dos_fname) { + return NT_STATUS_NO_MEMORY; + } + } else { + dos_fname = talloc_asprintf(mem_ctx, + "\\%s", + smb_fname->base_name); + if (!dos_fname) { + return NT_STATUS_NO_MEMORY; + } + if (is_named_stream(smb_fname)) { + dos_fname = talloc_asprintf(dos_fname, "%s", + smb_fname->stream_name); + if (!dos_fname) { + return NT_STATUS_NO_MEMORY; + } + } + + string_replace(dos_fname, '/', '\\'); + } + + allocation_size = SMB_VFS_GET_ALLOC_SIZE(conn, fsp, psbuf); + + if (fsp == NULL || !fsp->fsp_flags.is_fsa) { + /* Do we have this path open ? */ + files_struct *fsp1; + struct file_id fileid = vfs_file_id_from_sbuf(conn, psbuf); + fsp1 = file_find_di_first(conn->sconn, fileid, true); + if (fsp1 && fsp1->initial_allocation_size) { + allocation_size = SMB_VFS_GET_ALLOC_SIZE(conn, fsp1, psbuf); + } + } + + if (!(mode & FILE_ATTRIBUTE_DIRECTORY)) { + file_size = get_file_size_stat(psbuf); + } + + if (fsp) { + pos = fh_get_position_information(fsp->fh); + } + + if (fsp) { + access_mask = fsp->access_mask; + } else { + /* GENERIC_EXECUTE mapping from Windows */ + access_mask = 0x12019F; + } + + /* This should be an index number - looks like + dev/ino to me :-) + + I think this causes us to fail the IFSKIT + BasicFileInformationTest. -tpot */ + file_id = SMB_VFS_FS_FILE_ID(conn, base_sp); + + *fixed_portion = 0; + + switch (info_level) { + case SMB_INFO_STANDARD: + DEBUG(10,("smbd_do_qfilepathinfo: SMB_INFO_STANDARD\n")); + data_size = 22; + srv_put_dos_date2(pdata,l1_fdateCreation,create_time); + srv_put_dos_date2(pdata,l1_fdateLastAccess,atime); + srv_put_dos_date2(pdata,l1_fdateLastWrite,mtime); /* write time */ + SIVAL(pdata,l1_cbFile,(uint32_t)file_size); + SIVAL(pdata,l1_cbFileAlloc,(uint32_t)allocation_size); + SSVAL(pdata,l1_attrFile,mode); + break; + + case SMB_INFO_QUERY_EA_SIZE: + { + unsigned int ea_size = + estimate_ea_size(smb_fname->fsp); + DEBUG(10,("smbd_do_qfilepathinfo: SMB_INFO_QUERY_EA_SIZE\n")); + data_size = 26; + srv_put_dos_date2(pdata,0,create_time); + srv_put_dos_date2(pdata,4,atime); + srv_put_dos_date2(pdata,8,mtime); /* write time */ + SIVAL(pdata,12,(uint32_t)file_size); + SIVAL(pdata,16,(uint32_t)allocation_size); + SSVAL(pdata,20,mode); + SIVAL(pdata,22,ea_size); + break; + } + + case SMB_INFO_IS_NAME_VALID: + DEBUG(10,("smbd_do_qfilepathinfo: SMB_INFO_IS_NAME_VALID\n")); + if (fsp) { + /* os/2 needs this ? really ?*/ + return NT_STATUS_DOS(ERRDOS, ERRbadfunc); + } + /* This is only reached for qpathinfo */ + data_size = 0; + break; + + case SMB_INFO_QUERY_EAS_FROM_LIST: + { + size_t total_ea_len = 0; + struct ea_list *ea_file_list = NULL; + DEBUG(10,("smbd_do_qfilepathinfo: SMB_INFO_QUERY_EAS_FROM_LIST\n")); + + status = + get_ea_list_from_fsp(mem_ctx, + smb_fname->fsp, + &total_ea_len, &ea_file_list); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ea_list = ea_list_union(ea_list, ea_file_list, &total_ea_len); + + if (!ea_list || (total_ea_len > data_size)) { + data_size = 4; + SIVAL(pdata,0,4); /* EA List Length must be set to 4 if no EA's. */ + break; + } + + data_size = fill_ea_buffer(mem_ctx, pdata, data_size, conn, ea_list); + break; + } + + case SMB_INFO_QUERY_ALL_EAS: + { + /* We have data_size bytes to put EA's into. */ + size_t total_ea_len = 0; + DEBUG(10,("smbd_do_qfilepathinfo: SMB_INFO_QUERY_ALL_EAS\n")); + + status = get_ea_list_from_fsp(mem_ctx, + smb_fname->fsp, + &total_ea_len, &ea_list); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!ea_list || (total_ea_len > data_size)) { + data_size = 4; + SIVAL(pdata,0,4); /* EA List Length must be set to 4 if no EA's. */ + break; + } + + data_size = fill_ea_buffer(mem_ctx, pdata, data_size, conn, ea_list); + break; + } + + case SMB2_FILE_FULL_EA_INFORMATION: + { + /* We have data_size bytes to put EA's into. */ + size_t total_ea_len = 0; + struct ea_list *ea_file_list = NULL; + + DEBUG(10,("smbd_do_qfilepathinfo: SMB2_INFO_QUERY_ALL_EAS\n")); + + /*TODO: add filtering and index handling */ + + status = + get_ea_list_from_fsp(mem_ctx, + smb_fname->fsp, + &total_ea_len, &ea_file_list); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!ea_file_list) { + return NT_STATUS_NO_EAS_ON_FILE; + } + + status = fill_ea_chained_buffer(mem_ctx, + pdata, + data_size, + &data_size, + conn, ea_file_list); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + break; + } + + case SMB_FILE_BASIC_INFORMATION: + case SMB_QUERY_FILE_BASIC_INFO: + + if (info_level == SMB_QUERY_FILE_BASIC_INFO) { + DEBUG(10,("smbd_do_qfilepathinfo: SMB_QUERY_FILE_BASIC_INFO\n")); + data_size = 36; /* w95 returns 40 bytes not 36 - why ?. */ + } else { + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_BASIC_INFORMATION\n")); + data_size = 40; + SIVAL(pdata,36,0); + } + put_long_date_full_timespec(conn->ts_res,pdata,&create_time_ts); + put_long_date_full_timespec(conn->ts_res,pdata+8,&atime_ts); + put_long_date_full_timespec(conn->ts_res,pdata+16,&mtime_ts); /* write time */ + put_long_date_full_timespec(conn->ts_res,pdata+24,&ctime_ts); /* change time */ + SIVAL(pdata,32,mode); + + DEBUG(5,("SMB_QFBI - ")); + DEBUG(5,("create: %s ", ctime(&create_time))); + DEBUG(5,("access: %s ", ctime(&atime))); + DEBUG(5,("write: %s ", ctime(&mtime))); + DEBUG(5,("change: %s ", ctime(&c_time))); + DEBUG(5,("mode: %x\n", mode)); + *fixed_portion = data_size; + break; + + case SMB_FILE_STANDARD_INFORMATION: + case SMB_QUERY_FILE_STANDARD_INFO: + + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_STANDARD_INFORMATION\n")); + data_size = 24; + SOFF_T(pdata,0,allocation_size); + SOFF_T(pdata,8,file_size); + SIVAL(pdata,16,nlink); + SCVAL(pdata,20,delete_pending?1:0); + SCVAL(pdata,21,(mode&FILE_ATTRIBUTE_DIRECTORY)?1:0); + SSVAL(pdata,22,0); /* Padding. */ + *fixed_portion = 24; + break; + + case SMB_FILE_EA_INFORMATION: + case SMB_QUERY_FILE_EA_INFO: + { + unsigned int ea_size = + estimate_ea_size(smb_fname->fsp); + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_EA_INFORMATION\n")); + data_size = 4; + *fixed_portion = 4; + SIVAL(pdata,0,ea_size); + break; + } + + /* Get the 8.3 name - used if NT SMB was negotiated. */ + case SMB_QUERY_FILE_ALT_NAME_INFO: + case SMB_FILE_ALTERNATE_NAME_INFORMATION: + { + char mangled_name[13]; + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_ALTERNATE_NAME_INFORMATION\n")); + if (!name_to_8_3(base_name,mangled_name, + True,conn->params)) { + return NT_STATUS_NO_MEMORY; + } + status = srvstr_push(dstart, flags2, + pdata+4, mangled_name, + PTR_DIFF(dend, pdata+4), + STR_UNICODE, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + data_size = 4 + len; + SIVAL(pdata,0,len); + *fixed_portion = 8; + break; + } + + case SMB_QUERY_FILE_NAME_INFO: + { + /* + this must be *exactly* right for ACLs on mapped drives to work + */ + status = srvstr_push(dstart, flags2, + pdata+4, dos_fname, + PTR_DIFF(dend, pdata+4), + STR_UNICODE, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + DEBUG(10,("smbd_do_qfilepathinfo: SMB_QUERY_FILE_NAME_INFO\n")); + data_size = 4 + len; + SIVAL(pdata,0,len); + break; + } + + case SMB_FILE_NORMALIZED_NAME_INFORMATION: + { + char *nfname = NULL; + + if (fsp == NULL || !fsp->conn->sconn->using_smb2) { + return NT_STATUS_INVALID_LEVEL; + } + + nfname = talloc_strdup(mem_ctx, smb_fname->base_name); + if (nfname == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (ISDOT(nfname)) { + nfname[0] = '\0'; + } + string_replace(nfname, '/', '\\'); + + if (fsp_is_alternate_stream(fsp)) { + const char *s = smb_fname->stream_name; + const char *e = NULL; + size_t n; + + SMB_ASSERT(s[0] != '\0'); + + /* + * smb_fname->stream_name is in form + * of ':StrEam:$DATA', but we should only + * append ':StrEam' here. + */ + + e = strchr(&s[1], ':'); + if (e == NULL) { + n = strlen(s); + } else { + n = PTR_DIFF(e, s); + } + nfname = talloc_strndup_append(nfname, s, n); + if (nfname == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + status = srvstr_push(dstart, flags2, + pdata+4, nfname, + PTR_DIFF(dend, pdata+4), + STR_UNICODE, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_NORMALIZED_NAME_INFORMATION\n")); + data_size = 4 + len; + SIVAL(pdata,0,len); + *fixed_portion = 8; + break; + } + + case SMB_FILE_ALLOCATION_INFORMATION: + case SMB_QUERY_FILE_ALLOCATION_INFO: + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_ALLOCATION_INFORMATION\n")); + data_size = 8; + SOFF_T(pdata,0,allocation_size); + break; + + case SMB_FILE_END_OF_FILE_INFORMATION: + case SMB_QUERY_FILE_END_OF_FILEINFO: + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_END_OF_FILE_INFORMATION\n")); + data_size = 8; + SOFF_T(pdata,0,file_size); + break; + + case SMB_QUERY_FILE_ALL_INFO: + case SMB_FILE_ALL_INFORMATION: + { + unsigned int ea_size = + estimate_ea_size(smb_fname->fsp); + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_ALL_INFORMATION\n")); + put_long_date_full_timespec(conn->ts_res,pdata,&create_time_ts); + put_long_date_full_timespec(conn->ts_res,pdata+8,&atime_ts); + put_long_date_full_timespec(conn->ts_res,pdata+16,&mtime_ts); /* write time */ + put_long_date_full_timespec(conn->ts_res,pdata+24,&ctime_ts); /* change time */ + SIVAL(pdata,32,mode); + SIVAL(pdata,36,0); /* padding. */ + pdata += 40; + SOFF_T(pdata,0,allocation_size); + SOFF_T(pdata,8,file_size); + SIVAL(pdata,16,nlink); + SCVAL(pdata,20,delete_pending); + SCVAL(pdata,21,(mode&FILE_ATTRIBUTE_DIRECTORY)?1:0); + SSVAL(pdata,22,0); + pdata += 24; + SIVAL(pdata,0,ea_size); + pdata += 4; /* EA info */ + status = srvstr_push(dstart, flags2, + pdata+4, dos_fname, + PTR_DIFF(dend, pdata+4), + STR_UNICODE, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + SIVAL(pdata,0,len); + pdata += 4 + len; + data_size = PTR_DIFF(pdata,(*ppdata)); + *fixed_portion = 10; + break; + } + + case SMB2_FILE_ALL_INFORMATION: + { + unsigned int ea_size = + estimate_ea_size(smb_fname->fsp); + DEBUG(10,("smbd_do_qfilepathinfo: SMB2_FILE_ALL_INFORMATION\n")); + put_long_date_full_timespec(conn->ts_res,pdata+0x00,&create_time_ts); + put_long_date_full_timespec(conn->ts_res,pdata+0x08,&atime_ts); + put_long_date_full_timespec(conn->ts_res,pdata+0x10,&mtime_ts); /* write time */ + put_long_date_full_timespec(conn->ts_res,pdata+0x18,&ctime_ts); /* change time */ + SIVAL(pdata, 0x20, mode); + SIVAL(pdata, 0x24, 0); /* padding. */ + SBVAL(pdata, 0x28, allocation_size); + SBVAL(pdata, 0x30, file_size); + SIVAL(pdata, 0x38, nlink); + SCVAL(pdata, 0x3C, delete_pending); + SCVAL(pdata, 0x3D, (mode&FILE_ATTRIBUTE_DIRECTORY)?1:0); + SSVAL(pdata, 0x3E, 0); /* padding */ + SBVAL(pdata, 0x40, file_id); + SIVAL(pdata, 0x48, ea_size); + SIVAL(pdata, 0x4C, access_mask); + SBVAL(pdata, 0x50, pos); + SIVAL(pdata, 0x58, mode); /*TODO: mode != mode fix this!!! */ + SIVAL(pdata, 0x5C, 0); /* No alignment needed. */ + + pdata += 0x60; + + status = srvstr_push(dstart, flags2, + pdata+4, dos_fname, + PTR_DIFF(dend, pdata+4), + STR_UNICODE, &len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + SIVAL(pdata,0,len); + pdata += 4 + len; + data_size = PTR_DIFF(pdata,(*ppdata)); + *fixed_portion = 104; + break; + } + case SMB_FILE_INTERNAL_INFORMATION: + + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_INTERNAL_INFORMATION\n")); + SBVAL(pdata, 0, file_id); + data_size = 8; + *fixed_portion = 8; + break; + + case SMB_FILE_ACCESS_INFORMATION: + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_ACCESS_INFORMATION\n")); + SIVAL(pdata, 0, access_mask); + data_size = 4; + *fixed_portion = 4; + break; + + case SMB_FILE_NAME_INFORMATION: + /* Pathname with leading '\'. */ + { + size_t byte_len; + byte_len = dos_PutUniCode(pdata+4,dos_fname,(size_t)max_data_bytes,False); + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_NAME_INFORMATION\n")); + SIVAL(pdata,0,byte_len); + data_size = 4 + byte_len; + break; + } + + case SMB_FILE_DISPOSITION_INFORMATION: + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_DISPOSITION_INFORMATION\n")); + data_size = 1; + SCVAL(pdata,0,delete_pending); + *fixed_portion = 1; + break; + + case SMB_FILE_POSITION_INFORMATION: + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_POSITION_INFORMATION\n")); + data_size = 8; + SOFF_T(pdata,0,pos); + *fixed_portion = 8; + break; + + case SMB_FILE_MODE_INFORMATION: + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_MODE_INFORMATION\n")); + SIVAL(pdata,0,mode); + data_size = 4; + *fixed_portion = 4; + break; + + case SMB_FILE_ALIGNMENT_INFORMATION: + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_ALIGNMENT_INFORMATION\n")); + SIVAL(pdata,0,0); /* No alignment needed. */ + data_size = 4; + *fixed_portion = 4; + break; + + /* + * NT4 server just returns "invalid query" to this - if we try + * to answer it then NTws gets a BSOD! (tridge). W2K seems to + * want this. JRA. + */ + /* The first statement above is false - verified using Thursby + * client against NT4 -- gcolley. + */ + case SMB_QUERY_FILE_STREAM_INFO: + case SMB_FILE_STREAM_INFORMATION: { + unsigned int num_streams = 0; + struct stream_struct *streams = NULL; + + DEBUG(10,("smbd_do_qfilepathinfo: " + "SMB_FILE_STREAM_INFORMATION\n")); + + if (is_ntfs_stream_smb_fname(smb_fname)) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = vfs_fstreaminfo(fsp, + mem_ctx, + &num_streams, + &streams); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("could not get stream info: %s\n", + nt_errstr(status))); + return status; + } + + status = marshall_stream_info(num_streams, streams, + pdata, max_data_bytes, + &data_size); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("marshall_stream_info failed: %s\n", + nt_errstr(status))); + TALLOC_FREE(streams); + return status; + } + + TALLOC_FREE(streams); + + *fixed_portion = 32; + + break; + } + case SMB_QUERY_COMPRESSION_INFO: + case SMB_FILE_COMPRESSION_INFORMATION: + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_COMPRESSION_INFORMATION\n")); + SOFF_T(pdata,0,file_size); + SIVAL(pdata,8,0); /* ??? */ + SIVAL(pdata,12,0); /* ??? */ + data_size = 16; + *fixed_portion = 16; + break; + + case SMB_FILE_NETWORK_OPEN_INFORMATION: + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_NETWORK_OPEN_INFORMATION\n")); + put_long_date_full_timespec(conn->ts_res,pdata,&create_time_ts); + put_long_date_full_timespec(conn->ts_res,pdata+8,&atime_ts); + put_long_date_full_timespec(conn->ts_res,pdata+16,&mtime_ts); /* write time */ + put_long_date_full_timespec(conn->ts_res,pdata+24,&ctime_ts); /* change time */ + SOFF_T(pdata,32,allocation_size); + SOFF_T(pdata,40,file_size); + SIVAL(pdata,48,mode); + SIVAL(pdata,52,0); /* ??? */ + data_size = 56; + *fixed_portion = 56; + break; + + case SMB_FILE_ATTRIBUTE_TAG_INFORMATION: + DEBUG(10,("smbd_do_qfilepathinfo: SMB_FILE_ATTRIBUTE_TAG_INFORMATION\n")); + SIVAL(pdata,0,mode); + SIVAL(pdata,4,0); + data_size = 8; + *fixed_portion = 8; + break; + + /* + * CIFS UNIX Extensions. + */ + + case SMB_QUERY_FILE_UNIX_BASIC: + + pdata = store_file_unix_basic(conn, pdata, fsp, psbuf); + data_size = PTR_DIFF(pdata,(*ppdata)); + + DEBUG(4,("smbd_do_qfilepathinfo: " + "SMB_QUERY_FILE_UNIX_BASIC\n")); + dump_data(4, (uint8_t *)(*ppdata), data_size); + + break; + + case SMB_QUERY_FILE_UNIX_INFO2: + + pdata = store_file_unix_basic_info2(conn, pdata, fsp, psbuf); + data_size = PTR_DIFF(pdata,(*ppdata)); + + { + int i; + DEBUG(4,("smbd_do_qfilepathinfo: SMB_QUERY_FILE_UNIX_INFO2 ")); + + for (i=0; i<100; i++) + DEBUG(4,("%d=%x, ",i, (*ppdata)[i])); + DEBUG(4,("\n")); + } + + break; + + case SMB_QUERY_FILE_UNIX_LINK: + { + status = smb_unix_read_symlink(conn, + req, + smb_fname, + pdata, + data_size, + &data_size); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + break; + } + +#if defined(HAVE_POSIX_ACLS) + case SMB_QUERY_POSIX_ACL: + { + status = smb_query_posix_acl(conn, + req, + fsp, + smb_fname, + pdata, + data_size, + &data_size); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + break; + } +#endif + + + case SMB_QUERY_POSIX_LOCK: + { + uint64_t count; + uint64_t offset; + uint64_t smblctx; + enum brl_type lock_type; + + /* We need an open file with a real fd for this. */ + if (fsp == NULL || + fsp->fsp_flags.is_pathref || + fsp_get_io_fd(fsp) == -1) + { + return NT_STATUS_INVALID_LEVEL; + } + + if (lock_data_count != POSIX_LOCK_DATA_SIZE) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (SVAL(pdata, POSIX_LOCK_TYPE_OFFSET)) { + case POSIX_LOCK_TYPE_READ: + lock_type = READ_LOCK; + break; + case POSIX_LOCK_TYPE_WRITE: + lock_type = WRITE_LOCK; + break; + case POSIX_LOCK_TYPE_UNLOCK: + default: + /* There's no point in asking for an unlock... */ + return NT_STATUS_INVALID_PARAMETER; + } + + smblctx = (uint64_t)IVAL(pdata, POSIX_LOCK_PID_OFFSET); + offset = BVAL(pdata,POSIX_LOCK_START_OFFSET); + count = BVAL(pdata,POSIX_LOCK_LEN_OFFSET); + + status = query_lock(fsp, + &smblctx, + &count, + &offset, + &lock_type, + POSIX_LOCK); + + if (ERROR_WAS_LOCK_DENIED(status)) { + /* Here we need to report who has it locked... */ + data_size = POSIX_LOCK_DATA_SIZE; + + SSVAL(pdata, POSIX_LOCK_TYPE_OFFSET, lock_type); + SSVAL(pdata, POSIX_LOCK_FLAGS_OFFSET, 0); + SIVAL(pdata, POSIX_LOCK_PID_OFFSET, (uint32_t)smblctx); + SBVAL(pdata, POSIX_LOCK_START_OFFSET, offset); + SBVAL(pdata, POSIX_LOCK_LEN_OFFSET, count); + + } else if (NT_STATUS_IS_OK(status)) { + /* For success we just return a copy of what we sent + with the lock type set to POSIX_LOCK_TYPE_UNLOCK. */ + data_size = POSIX_LOCK_DATA_SIZE; + memcpy(pdata, lock_data, POSIX_LOCK_DATA_SIZE); + SSVAL(pdata, POSIX_LOCK_TYPE_OFFSET, POSIX_LOCK_TYPE_UNLOCK); + } else { + return status; + } + break; + } + + default: + return NT_STATUS_INVALID_LEVEL; + } + + *pdata_size = data_size; + return NT_STATUS_OK; +} + +/**************************************************************************** + Set a hard link (called by UNIX extensions and by NT rename with HARD link + code. +****************************************************************************/ + +NTSTATUS hardlink_internals(TALLOC_CTX *ctx, + connection_struct *conn, + struct smb_request *req, + bool overwrite_if_exists, + struct files_struct *old_dirfsp, + const struct smb_filename *smb_fname_old, + struct files_struct *new_dirfsp, + struct smb_filename *smb_fname_new) +{ + NTSTATUS status = NT_STATUS_OK; + int ret; + bool ok; + struct smb_filename *parent_fname_old = NULL; + struct smb_filename *base_name_old = NULL; + struct smb_filename *parent_fname_new = NULL; + struct smb_filename *base_name_new = NULL; + + /* source must already exist. */ + if (!VALID_STAT(smb_fname_old->st)) { + status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto out; + } + + /* No links from a directory. */ + if (S_ISDIR(smb_fname_old->st.st_ex_mode)) { + status = NT_STATUS_FILE_IS_A_DIRECTORY; + goto out; + } + + /* Setting a hardlink to/from a stream isn't currently supported. */ + ok = is_ntfs_stream_smb_fname(smb_fname_old); + if (ok) { + DBG_DEBUG("Old name has streams\n"); + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + ok = is_ntfs_stream_smb_fname(smb_fname_new); + if (ok) { + DBG_DEBUG("New name has streams\n"); + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + status = parent_pathref(talloc_tos(), + conn->cwd_fsp, + smb_fname_old, + &parent_fname_old, + &base_name_old); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + status = parent_pathref(talloc_tos(), + conn->cwd_fsp, + smb_fname_new, + &parent_fname_new, + &base_name_new); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + if (VALID_STAT(smb_fname_new->st)) { + if (overwrite_if_exists) { + if (S_ISDIR(smb_fname_new->st.st_ex_mode)) { + status = NT_STATUS_FILE_IS_A_DIRECTORY; + goto out; + } + status = unlink_internals(conn, + req, + FILE_ATTRIBUTE_NORMAL, + NULL, /* new_dirfsp */ + smb_fname_new); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + } else { + /* Disallow if newname already exists. */ + status = NT_STATUS_OBJECT_NAME_COLLISION; + goto out; + } + } + + DEBUG(10,("hardlink_internals: doing hard link %s -> %s\n", + smb_fname_old->base_name, smb_fname_new->base_name)); + + ret = SMB_VFS_LINKAT(conn, + parent_fname_old->fsp, + base_name_old, + parent_fname_new->fsp, + base_name_new, + 0); + + if (ret != 0) { + status = map_nt_error_from_unix(errno); + DEBUG(3,("hardlink_internals: Error %s hard link %s -> %s\n", + nt_errstr(status), smb_fname_old->base_name, + smb_fname_new->base_name)); + } + + out: + + TALLOC_FREE(parent_fname_old); + TALLOC_FREE(parent_fname_new); + return status; +} + +/**************************************************************************** + Deal with setting the time from any of the setfilepathinfo functions. + NOTE !!!! The check for FILE_WRITE_ATTRIBUTES access must be done *before* + calling this function. +****************************************************************************/ + +NTSTATUS smb_set_file_time(connection_struct *conn, + files_struct *fsp, + struct smb_filename *smb_fname, + struct smb_file_time *ft, + bool setting_write_time) +{ + struct files_struct *set_fsp = NULL; + struct timeval_buf tbuf[4]; + uint32_t action = + FILE_NOTIFY_CHANGE_LAST_ACCESS + |FILE_NOTIFY_CHANGE_LAST_WRITE + |FILE_NOTIFY_CHANGE_CREATION; + int ret; + + if (!VALID_STAT(smb_fname->st)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (fsp == NULL) { + /* A symlink */ + return NT_STATUS_OK; + } + + set_fsp = metadata_fsp(fsp); + + /* get some defaults (no modifications) if any info is zero or -1. */ + if (is_omit_timespec(&ft->create_time)) { + action &= ~FILE_NOTIFY_CHANGE_CREATION; + } + + if (is_omit_timespec(&ft->atime)) { + action &= ~FILE_NOTIFY_CHANGE_LAST_ACCESS; + } + + if (is_omit_timespec(&ft->mtime)) { + action &= ~FILE_NOTIFY_CHANGE_LAST_WRITE; + } + + if (!setting_write_time) { + /* ft->mtime comes from change time, not write time. */ + action &= ~FILE_NOTIFY_CHANGE_LAST_WRITE; + } + + /* Ensure the resolution is the correct for + * what we can store on this filesystem. */ + + round_timespec(conn->ts_res, &ft->create_time); + round_timespec(conn->ts_res, &ft->ctime); + round_timespec(conn->ts_res, &ft->atime); + round_timespec(conn->ts_res, &ft->mtime); + + DBG_DEBUG("smb_set_filetime: actime: %s\n ", + timespec_string_buf(&ft->atime, true, &tbuf[0])); + DBG_DEBUG("smb_set_filetime: modtime: %s\n ", + timespec_string_buf(&ft->mtime, true, &tbuf[1])); + DBG_DEBUG("smb_set_filetime: ctime: %s\n ", + timespec_string_buf(&ft->ctime, true, &tbuf[2])); + DBG_DEBUG("smb_set_file_time: createtime: %s\n ", + timespec_string_buf(&ft->create_time, true, &tbuf[3])); + + if (setting_write_time) { + /* + * This was a Windows setfileinfo on an open file. + * NT does this a lot. We also need to + * set the time here, as it can be read by + * FindFirst/FindNext and with the patch for bug #2045 + * in smbd/fileio.c it ensures that this timestamp is + * kept sticky even after a write. We save the request + * away and will set it on file close and after a write. JRA. + */ + + DBG_DEBUG("setting pending modtime to %s\n", + timespec_string_buf(&ft->mtime, true, &tbuf[0])); + + if (set_fsp != NULL) { + set_sticky_write_time_fsp(set_fsp, ft->mtime); + } else { + set_sticky_write_time_path( + vfs_file_id_from_sbuf(conn, &smb_fname->st), + ft->mtime); + } + } + + DEBUG(10,("smb_set_file_time: setting utimes to modified values.\n")); + + ret = file_ntimes(conn, set_fsp, ft); + if (ret != 0) { + return map_nt_error_from_unix(errno); + } + + notify_fname(conn, NOTIFY_ACTION_MODIFIED, action, + smb_fname->base_name); + return NT_STATUS_OK; +} + +/**************************************************************************** + Deal with setting the dosmode from any of the setfilepathinfo functions. + NB. The check for FILE_WRITE_ATTRIBUTES access on this path must have been + done before calling this function. +****************************************************************************/ + +static NTSTATUS smb_set_file_dosmode(connection_struct *conn, + struct files_struct *fsp, + uint32_t dosmode) +{ + struct files_struct *dos_fsp = NULL; + uint32_t current_dosmode; + int ret; + + if (!VALID_STAT(fsp->fsp_name->st)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + dos_fsp = metadata_fsp(fsp); + + if (dosmode != 0) { + if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) { + dosmode |= FILE_ATTRIBUTE_DIRECTORY; + } else { + dosmode &= ~FILE_ATTRIBUTE_DIRECTORY; + } + } + + DBG_DEBUG("dosmode: 0x%" PRIx32 "\n", dosmode); + + /* check the mode isn't different, before changing it */ + if (dosmode == 0) { + return NT_STATUS_OK; + } + current_dosmode = fdos_mode(dos_fsp); + if (dosmode == current_dosmode) { + return NT_STATUS_OK; + } + + DBG_DEBUG("file %s : setting dos mode 0x%" PRIx32 "\n", + fsp_str_dbg(dos_fsp), dosmode); + + ret = file_set_dosmode(conn, dos_fsp->fsp_name, dosmode, NULL, false); + if (ret != 0) { + DBG_WARNING("file_set_dosmode of %s failed: %s\n", + fsp_str_dbg(dos_fsp), strerror(errno)); + return map_nt_error_from_unix(errno); + } + + return NT_STATUS_OK; +} + +/**************************************************************************** + Deal with setting the size from any of the setfilepathinfo functions. +****************************************************************************/ + +static NTSTATUS smb_set_file_size(connection_struct *conn, + struct smb_request *req, + files_struct *fsp, + struct smb_filename *smb_fname, + const SMB_STRUCT_STAT *psbuf, + off_t size, + bool fail_after_createfile) +{ + NTSTATUS status = NT_STATUS_OK; + files_struct *new_fsp = NULL; + + if (!VALID_STAT(*psbuf)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + DBG_INFO("size: %"PRIu64", file_size_stat=%"PRIu64"\n", + (uint64_t)size, + get_file_size_stat(psbuf)); + + if (size == get_file_size_stat(psbuf)) { + if (fsp == NULL) { + return NT_STATUS_OK; + } + if (!fsp->fsp_flags.modified) { + return NT_STATUS_OK; + } + trigger_write_time_update_immediate(fsp); + return NT_STATUS_OK; + } + + DEBUG(10,("smb_set_file_size: file %s : setting new size to %.0f\n", + smb_fname_str_dbg(smb_fname), (double)size)); + + if (fsp && + !fsp->fsp_flags.is_pathref && + fsp_get_io_fd(fsp) != -1) + { + /* Handle based call. */ + if (!(fsp->access_mask & FILE_WRITE_DATA)) { + return NT_STATUS_ACCESS_DENIED; + } + + if (vfs_set_filelen(fsp, size) == -1) { + return map_nt_error_from_unix(errno); + } + trigger_write_time_update_immediate(fsp); + return NT_STATUS_OK; + } + + status = SMB_VFS_CREATE_FILE( + conn, /* conn */ + req, /* req */ + NULL, /* dirfsp */ + smb_fname, /* fname */ + FILE_WRITE_DATA, /* 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 */ + 0, /* oplock_request */ + NULL, /* lease */ + 0, /* allocation_size */ + 0, /* private_flags */ + NULL, /* sd */ + NULL, /* ea_list */ + &new_fsp, /* result */ + NULL, /* pinfo */ + NULL, NULL); /* create context */ + + if (!NT_STATUS_IS_OK(status)) { + /* NB. We check for open_was_deferred in the caller. */ + return status; + } + + /* See RAW-SFILEINFO-END-OF-FILE */ + if (fail_after_createfile) { + close_file_free(req, &new_fsp, NORMAL_CLOSE); + return NT_STATUS_INVALID_LEVEL; + } + + if (vfs_set_filelen(new_fsp, size) == -1) { + status = map_nt_error_from_unix(errno); + close_file_free(req, &new_fsp, NORMAL_CLOSE); + return status; + } + + trigger_write_time_update_immediate(new_fsp); + close_file_free(req, &new_fsp, NORMAL_CLOSE); + return NT_STATUS_OK; +} + +/**************************************************************************** + Deal with SMB_INFO_SET_EA. +****************************************************************************/ + +static NTSTATUS smb_info_set_ea(connection_struct *conn, + const char *pdata, + int total_data, + files_struct *fsp, + struct smb_filename *smb_fname) +{ + struct ea_list *ea_list = NULL; + TALLOC_CTX *ctx = NULL; + NTSTATUS status = NT_STATUS_OK; + + if (total_data < 10) { + + /* OS/2 workplace shell seems to send SET_EA requests of "null" + length. They seem to have no effect. Bug #3212. JRA */ + + if ((total_data == 4) && (IVAL(pdata,0) == 4)) { + /* We're done. We only get EA info in this call. */ + return NT_STATUS_OK; + } + + return NT_STATUS_INVALID_PARAMETER; + } + + if (IVAL(pdata,0) > total_data) { + DEBUG(10,("smb_info_set_ea: bad total data size (%u) > %u\n", + IVAL(pdata,0), (unsigned int)total_data)); + return NT_STATUS_INVALID_PARAMETER; + } + + ctx = talloc_tos(); + ea_list = read_ea_list(ctx, pdata + 4, total_data - 4); + if (!ea_list) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (fsp == NULL) { + /* + * The only way fsp can be NULL here is if + * smb_fname points at a symlink and + * and we're in POSIX context. + * Ensure this is the case. + * + * In this case we cannot set the EA. + */ + SMB_ASSERT(smb_fname->flags & SMB_FILENAME_POSIX_PATH); + return NT_STATUS_ACCESS_DENIED; + } + + status = set_ea(conn, fsp, ea_list); + + return status; +} + +/**************************************************************************** + Deal with SMB_FILE_FULL_EA_INFORMATION set. +****************************************************************************/ + +static NTSTATUS smb_set_file_full_ea_info(connection_struct *conn, + const char *pdata, + int total_data, + files_struct *fsp) +{ + struct ea_list *ea_list = NULL; + NTSTATUS status; + + if (fsp == NULL) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!lp_ea_support(SNUM(conn))) { + DEBUG(10, ("smb_set_file_full_ea_info - ea_len = %u but " + "EA's not supported.\n", + (unsigned int)total_data)); + return NT_STATUS_EAS_NOT_SUPPORTED; + } + + if (total_data < 10) { + DEBUG(10, ("smb_set_file_full_ea_info - ea_len = %u " + "too small.\n", + (unsigned int)total_data)); + return NT_STATUS_INVALID_PARAMETER; + } + + ea_list = read_nttrans_ea_list(talloc_tos(), + pdata, + total_data); + + if (!ea_list) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = set_ea(conn, fsp, ea_list); + + DEBUG(10, ("smb_set_file_full_ea_info on file %s returned %s\n", + smb_fname_str_dbg(fsp->fsp_name), + nt_errstr(status) )); + + return status; +} + + +/**************************************************************************** + Deal with SMB_SET_FILE_DISPOSITION_INFO. +****************************************************************************/ + +static NTSTATUS smb_set_file_disposition_info(connection_struct *conn, + const char *pdata, + int total_data, + files_struct *fsp, + struct smb_filename *smb_fname) +{ + NTSTATUS status = NT_STATUS_OK; + bool delete_on_close; + uint32_t dosmode = 0; + + if (total_data < 1) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (fsp == NULL) { + return NT_STATUS_INVALID_HANDLE; + } + + delete_on_close = (CVAL(pdata,0) ? True : False); + dosmode = fdos_mode(fsp); + + DEBUG(10,("smb_set_file_disposition_info: file %s, dosmode = %u, " + "delete_on_close = %u\n", + smb_fname_str_dbg(smb_fname), + (unsigned int)dosmode, + (unsigned int)delete_on_close )); + + if (delete_on_close) { + status = can_set_delete_on_close(fsp, dosmode); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + /* The set is across all open files on this dev/inode pair. */ + if (!set_delete_on_close(fsp, delete_on_close, + conn->session_info->security_token, + conn->session_info->unix_token)) { + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; +} + +/**************************************************************************** + Deal with SMB_FILE_POSITION_INFORMATION. +****************************************************************************/ + +static NTSTATUS smb_file_position_information(connection_struct *conn, + const char *pdata, + int total_data, + files_struct *fsp) +{ + uint64_t position_information; + + if (total_data < 8) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (fsp == NULL) { + /* Ignore on pathname based set. */ + return NT_STATUS_OK; + } + + position_information = (uint64_t)IVAL(pdata,0); + position_information |= (((uint64_t)IVAL(pdata,4)) << 32); + + DEBUG(10,("smb_file_position_information: Set file position " + "information for file %s to %.0f\n", fsp_str_dbg(fsp), + (double)position_information)); + fh_set_position_information(fsp->fh, position_information); + return NT_STATUS_OK; +} + +/**************************************************************************** + Deal with SMB_FILE_MODE_INFORMATION. +****************************************************************************/ + +static NTSTATUS smb_file_mode_information(connection_struct *conn, + const char *pdata, + int total_data) +{ + uint32_t mode; + + if (total_data < 4) { + return NT_STATUS_INVALID_PARAMETER; + } + mode = IVAL(pdata,0); + if (mode != 0 && mode != 2 && mode != 4 && mode != 6) { + return NT_STATUS_INVALID_PARAMETER; + } + return NT_STATUS_OK; +} + +/**************************************************************************** + Deal with SMB_SET_FILE_UNIX_LINK (create a UNIX symlink). +****************************************************************************/ + +static NTSTATUS smb_set_file_unix_link(connection_struct *conn, + struct smb_request *req, + const char *pdata, + int total_data, + struct smb_filename *new_smb_fname) +{ + char *link_target = NULL; + struct smb_filename target_fname; + TALLOC_CTX *ctx = talloc_tos(); + NTSTATUS status; + int ret; + struct smb_filename *parent_fname = NULL; + struct smb_filename *base_name = NULL; + + /* Set a symbolic link. */ + /* Don't allow this if follow links is false. */ + + if (total_data == 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!lp_follow_symlinks(SNUM(conn))) { + return NT_STATUS_ACCESS_DENIED; + } + + srvstr_pull_talloc(ctx, pdata, req->flags2, &link_target, pdata, + total_data, STR_TERMINATE); + + if (!link_target) { + return NT_STATUS_INVALID_PARAMETER; + } + + target_fname = (struct smb_filename) { + .base_name = link_target, + }; + + /* Removes @GMT tokens if any */ + status = canonicalize_snapshot_path(&target_fname, UCF_GMT_PATHNAME, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(10,("smb_set_file_unix_link: SMB_SET_FILE_UNIX_LINK doing symlink %s -> %s\n", + new_smb_fname->base_name, link_target )); + + status = parent_pathref(talloc_tos(), + conn->cwd_fsp, + new_smb_fname, + &parent_fname, + &base_name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ret = SMB_VFS_SYMLINKAT(conn, + &target_fname, + parent_fname->fsp, + base_name); + if (ret != 0) { + TALLOC_FREE(parent_fname); + return map_nt_error_from_unix(errno); + } + + TALLOC_FREE(parent_fname); + return NT_STATUS_OK; +} + +/**************************************************************************** + Deal with SMB_SET_FILE_UNIX_HLINK (create a UNIX hard link). +****************************************************************************/ + +static NTSTATUS smb_set_file_unix_hlink(connection_struct *conn, + struct smb_request *req, + const char *pdata, int total_data, + struct smb_filename *smb_fname_new) +{ + char *oldname = NULL; + struct files_struct *src_dirfsp = NULL; + struct smb_filename *smb_fname_old = NULL; + uint32_t ucf_flags = ucf_flags_from_smb_request(req); + NTTIME old_twrp = 0; + TALLOC_CTX *ctx = talloc_tos(); + NTSTATUS status = NT_STATUS_OK; + + /* Set a hard link. */ + if (total_data == 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (req->posix_pathnames) { + srvstr_get_path_posix(ctx, + pdata, + req->flags2, + &oldname, + pdata, + total_data, + STR_TERMINATE, + &status); + } else { + srvstr_get_path(ctx, + pdata, + req->flags2, + &oldname, + pdata, + total_data, + STR_TERMINATE, + &status); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(10,("smb_set_file_unix_hlink: SMB_SET_FILE_UNIX_LINK doing hard link %s -> %s\n", + smb_fname_str_dbg(smb_fname_new), oldname)); + + if (ucf_flags & UCF_GMT_PATHNAME) { + extract_snapshot_token(oldname, &old_twrp); + } + status = filename_convert_dirfsp(ctx, + conn, + oldname, + ucf_flags, + old_twrp, + &src_dirfsp, + &smb_fname_old); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return hardlink_internals(ctx, + conn, + req, + false, + src_dirfsp, + smb_fname_old, + NULL, /* new_dirfsp */ + smb_fname_new); +} + +/**************************************************************************** + Deal with SMB2_FILE_RENAME_INFORMATION_INTERNAL +****************************************************************************/ + +static NTSTATUS smb2_file_rename_information(connection_struct *conn, + struct smb_request *req, + const char *pdata, + int total_data, + files_struct *fsp, + struct smb_filename *smb_fname_src) +{ + bool overwrite; + uint32_t len; + char *newname = NULL; + struct files_struct *dst_dirfsp = NULL; + struct smb_filename *smb_fname_dst = NULL; + const char *dst_original_lcomp = NULL; + uint32_t ucf_flags = ucf_flags_from_smb_request(req); + NTTIME dst_twrp = 0; + NTSTATUS status = NT_STATUS_OK; + bool is_dfs = (req->flags2 & FLAGS2_DFS_PATHNAMES); + TALLOC_CTX *ctx = talloc_tos(); + + if (!fsp) { + return NT_STATUS_INVALID_HANDLE; + } + + if (total_data < 20) { + return NT_STATUS_INVALID_PARAMETER; + } + + overwrite = (CVAL(pdata,0) ? True : False); + len = IVAL(pdata,16); + + if (len > (total_data - 20) || (len == 0)) { + return NT_STATUS_INVALID_PARAMETER; + } + + (void)srvstr_pull_talloc(ctx, + pdata, + req->flags2, + &newname, + &pdata[20], + len, + STR_TERMINATE); + + if (newname == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + status = check_path_syntax_smb2(newname, is_dfs); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(10,("smb2_file_rename_information: got name |%s|\n", + newname)); + + if (newname[0] == ':') { + /* Create an smb_fname to call rename_internals_fsp() with. */ + smb_fname_dst = synthetic_smb_fname(talloc_tos(), + fsp->base_fsp->fsp_name->base_name, + newname, + NULL, + fsp->base_fsp->fsp_name->twrp, + fsp->base_fsp->fsp_name->flags); + if (smb_fname_dst == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + } else { + if (ucf_flags & UCF_GMT_PATHNAME) { + extract_snapshot_token(newname, &dst_twrp); + } + status = filename_convert_dirfsp(ctx, + conn, + newname, + ucf_flags, + dst_twrp, + &dst_dirfsp, + &smb_fname_dst); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + } + + /* + * Set the original last component, since + * rename_internals_fsp() requires it. + */ + dst_original_lcomp = get_original_lcomp(smb_fname_dst, + conn, + newname, + ucf_flags); + if (dst_original_lcomp == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + DEBUG(10,("smb2_file_rename_information: " + "SMB_FILE_RENAME_INFORMATION (%s) %s -> %s\n", + fsp_fnum_dbg(fsp), fsp_str_dbg(fsp), + smb_fname_str_dbg(smb_fname_dst))); + status = rename_internals_fsp(conn, + fsp, + NULL, /* dst_dirfsp */ + smb_fname_dst, + dst_original_lcomp, + (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM), + overwrite); + + out: + TALLOC_FREE(smb_fname_dst); + return status; +} + +static NTSTATUS smb_file_link_information(connection_struct *conn, + struct smb_request *req, + const char *pdata, + int total_data, + files_struct *fsp, + struct smb_filename *smb_fname_src) +{ + bool overwrite; + uint32_t len; + char *newname = NULL; + struct files_struct *dst_dirfsp = NULL; + struct smb_filename *smb_fname_dst = NULL; + NTSTATUS status = NT_STATUS_OK; + uint32_t ucf_flags = ucf_flags_from_smb_request(req); + NTTIME dst_twrp = 0; + TALLOC_CTX *ctx = talloc_tos(); + + if (!fsp) { + return NT_STATUS_INVALID_HANDLE; + } + + if (total_data < 20) { + return NT_STATUS_INVALID_PARAMETER; + } + + overwrite = (CVAL(pdata,0) ? true : false); + len = IVAL(pdata,16); + + if (len > (total_data - 20) || (len == 0)) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (smb_fname_src->flags & SMB_FILENAME_POSIX_PATH) { + srvstr_get_path_posix(ctx, + pdata, + req->flags2, + &newname, + &pdata[20], + len, + STR_TERMINATE, + &status); + ucf_flags |= UCF_POSIX_PATHNAMES; + } else { + srvstr_get_path(ctx, + pdata, + req->flags2, + &newname, + &pdata[20], + len, + STR_TERMINATE, + &status); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(10,("smb_file_link_information: got name |%s|\n", + newname)); + + if (ucf_flags & UCF_GMT_PATHNAME) { + extract_snapshot_token(newname, &dst_twrp); + } + status = filename_convert_dirfsp(ctx, + conn, + newname, + ucf_flags, + dst_twrp, + &dst_dirfsp, + &smb_fname_dst); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (fsp->base_fsp) { + /* No stream names. */ + return NT_STATUS_NOT_SUPPORTED; + } + + DEBUG(10,("smb_file_link_information: " + "SMB_FILE_LINK_INFORMATION (%s) %s -> %s\n", + fsp_fnum_dbg(fsp), fsp_str_dbg(fsp), + smb_fname_str_dbg(smb_fname_dst))); + status = hardlink_internals(ctx, + conn, + req, + overwrite, + NULL, /* src_dirfsp */ + fsp->fsp_name, + dst_dirfsp, /* dst_dirfsp */ + smb_fname_dst); + + TALLOC_FREE(smb_fname_dst); + return status; +} + +/**************************************************************************** + Deal with SMB_FILE_RENAME_INFORMATION. +****************************************************************************/ + +static NTSTATUS smb_file_rename_information(connection_struct *conn, + struct smb_request *req, + const char *pdata, + int total_data, + files_struct *fsp, + struct smb_filename *smb_fname_src) +{ + bool overwrite; + uint32_t root_fid; + uint32_t len; + char *newname = NULL; + struct files_struct *dst_dirfsp = NULL; + struct smb_filename *smb_fname_dst = NULL; + const char *dst_original_lcomp = NULL; + NTSTATUS status = NT_STATUS_OK; + char *p; + TALLOC_CTX *ctx = talloc_tos(); + + if (total_data < 13) { + return NT_STATUS_INVALID_PARAMETER; + } + + overwrite = (CVAL(pdata,0) != 0); + root_fid = IVAL(pdata,4); + len = IVAL(pdata,8); + + if (len > (total_data - 12) || (len == 0) || (root_fid != 0)) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (req->posix_pathnames) { + srvstr_get_path_posix(ctx, + pdata, + req->flags2, + &newname, + &pdata[12], + len, + 0, + &status); + } else { + srvstr_get_path(ctx, + pdata, + req->flags2, + &newname, + &pdata[12], + len, + 0, + &status); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(10,("smb_file_rename_information: got name |%s|\n", + newname)); + + /* Check the new name has no '/' characters. */ + if (strchr_m(newname, '/')) { + return NT_STATUS_NOT_SUPPORTED; + } + + if (fsp && fsp->base_fsp) { + /* newname must be a stream name. */ + if (newname[0] != ':') { + return NT_STATUS_NOT_SUPPORTED; + } + + /* Create an smb_fname to call rename_internals_fsp() with. */ + smb_fname_dst = synthetic_smb_fname(talloc_tos(), + fsp->base_fsp->fsp_name->base_name, + newname, + NULL, + fsp->base_fsp->fsp_name->twrp, + fsp->base_fsp->fsp_name->flags); + if (smb_fname_dst == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + /* + * Get the original last component, since + * rename_internals_fsp() requires it. + */ + dst_original_lcomp = get_original_lcomp(smb_fname_dst, + conn, + newname, + 0); + if (dst_original_lcomp == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + } else { + /* + * Build up an smb_fname_dst based on the filename passed in. + * We basically just strip off the last component, and put on + * the newname instead. + */ + char *base_name = NULL; + uint32_t ucf_flags = ucf_flags_from_smb_request(req); + NTTIME dst_twrp = 0; + + /* newname must *not* be a stream name. */ + if (newname[0] == ':') { + return NT_STATUS_NOT_SUPPORTED; + } + + /* + * Strip off the last component (filename) of the path passed + * in. + */ + base_name = talloc_strdup(ctx, smb_fname_src->base_name); + if (!base_name) { + return NT_STATUS_NO_MEMORY; + } + p = strrchr_m(base_name, '/'); + if (p) { + p[1] = '\0'; + } else { + base_name = talloc_strdup(ctx, ""); + if (!base_name) { + return NT_STATUS_NO_MEMORY; + } + } + /* Append the new name. */ + base_name = talloc_asprintf_append(base_name, + "%s", + newname); + if (!base_name) { + return NT_STATUS_NO_MEMORY; + } + + if (ucf_flags & UCF_GMT_PATHNAME) { + extract_snapshot_token(base_name, &dst_twrp); + } + status = filename_convert_dirfsp(ctx, + conn, + base_name, + ucf_flags, + dst_twrp, + &dst_dirfsp, + &smb_fname_dst); + + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + dst_original_lcomp = get_original_lcomp(smb_fname_dst, + conn, + newname, + ucf_flags); + if (dst_original_lcomp == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + } + + if (fsp != NULL && fsp->fsp_flags.is_fsa) { + DEBUG(10,("smb_file_rename_information: " + "SMB_FILE_RENAME_INFORMATION (%s) %s -> %s\n", + fsp_fnum_dbg(fsp), fsp_str_dbg(fsp), + smb_fname_str_dbg(smb_fname_dst))); + status = rename_internals_fsp(conn, + fsp, + dst_dirfsp, + smb_fname_dst, + dst_original_lcomp, + 0, + overwrite); + } else { + DEBUG(10,("smb_file_rename_information: " + "SMB_FILE_RENAME_INFORMATION %s -> %s\n", + smb_fname_str_dbg(smb_fname_src), + smb_fname_str_dbg(smb_fname_dst))); + status = rename_internals(ctx, + conn, + req, + NULL, /* src_dirfsp */ + smb_fname_src, + dst_dirfsp, + smb_fname_dst, + dst_original_lcomp, + 0, + overwrite, + FILE_WRITE_ATTRIBUTES); + } + out: + TALLOC_FREE(smb_fname_dst); + return status; +} + +/**************************************************************************** + Deal with SMB_SET_POSIX_ACL. +****************************************************************************/ + +#if defined(HAVE_POSIX_ACLS) +static NTSTATUS smb_set_posix_acl(connection_struct *conn, + struct smb_request *req, + const char *pdata, + int total_data_in, + files_struct *fsp, + struct smb_filename *smb_fname) +{ + uint16_t posix_acl_version; + uint16_t num_file_acls; + uint16_t num_def_acls; + bool valid_file_acls = true; + bool valid_def_acls = true; + NTSTATUS status; + unsigned int size_needed; + unsigned int total_data; + bool close_fsp = false; + + if (total_data_in < 0) { + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + total_data = total_data_in; + + if (total_data < SMB_POSIX_ACL_HEADER_SIZE) { + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + posix_acl_version = SVAL(pdata,0); + num_file_acls = SVAL(pdata,2); + num_def_acls = SVAL(pdata,4); + + if (num_file_acls == SMB_POSIX_IGNORE_ACE_ENTRIES) { + valid_file_acls = false; + num_file_acls = 0; + } + + if (num_def_acls == SMB_POSIX_IGNORE_ACE_ENTRIES) { + valid_def_acls = false; + num_def_acls = 0; + } + + if (posix_acl_version != SMB_POSIX_ACL_VERSION) { + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + /* Wrap checks. */ + if (num_file_acls + num_def_acls < num_file_acls) { + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + size_needed = num_file_acls + num_def_acls; + + /* + * (size_needed * SMB_POSIX_ACL_ENTRY_SIZE) must be less + * than UINT_MAX, so check by division. + */ + if (size_needed > (UINT_MAX/SMB_POSIX_ACL_ENTRY_SIZE)) { + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + size_needed = size_needed*SMB_POSIX_ACL_ENTRY_SIZE; + if (size_needed + SMB_POSIX_ACL_HEADER_SIZE < size_needed) { + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + size_needed += SMB_POSIX_ACL_HEADER_SIZE; + + if (total_data < size_needed) { + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + /* + * Ensure we always operate on a file descriptor, not just + * the filename. + */ + if (fsp == NULL || !fsp->fsp_flags.is_fsa) { + uint32_t access_mask = SEC_STD_WRITE_OWNER| + SEC_STD_WRITE_DAC| + SEC_STD_READ_CONTROL| + FILE_READ_ATTRIBUTES| + FILE_WRITE_ATTRIBUTES; + + status = get_posix_fsp(conn, + req, + smb_fname, + access_mask, + &fsp); + + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + close_fsp = true; + } + + /* Here we know fsp != NULL */ + SMB_ASSERT(fsp != NULL); + + status = refuse_symlink_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + /* If we have a default acl, this *must* be a directory. */ + if (valid_def_acls && !fsp->fsp_flags.is_directory) { + DBG_INFO("Can't set default acls on " + "non-directory %s\n", + fsp_str_dbg(fsp)); + return NT_STATUS_INVALID_HANDLE; + } + + DBG_DEBUG("file %s num_file_acls = %"PRIu16", " + "num_def_acls = %"PRIu16"\n", + fsp_str_dbg(fsp), + num_file_acls, + num_def_acls); + + /* Move pdata to the start of the file ACL entries. */ + pdata += SMB_POSIX_ACL_HEADER_SIZE; + + if (valid_file_acls) { + status = set_unix_posix_acl(conn, + fsp, + num_file_acls, + pdata); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + } + + /* Move pdata to the start of the default ACL entries. */ + pdata += (num_file_acls*SMB_POSIX_ACL_ENTRY_SIZE); + + if (valid_def_acls) { + status = set_unix_posix_default_acl(conn, + fsp, + num_def_acls, + pdata); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + } + + status = NT_STATUS_OK; + + out: + + if (close_fsp) { + (void)close_file_free(req, &fsp, NORMAL_CLOSE); + } + return status; +} +#endif + +/**************************************************************************** + Deal with SMB_SET_FILE_BASIC_INFO. +****************************************************************************/ + +static NTSTATUS smb_set_file_basic_info(connection_struct *conn, + const char *pdata, + int total_data, + files_struct *fsp, + struct smb_filename *smb_fname) +{ + /* Patch to do this correctly from Paul Eggert <eggert@twinsun.com>. */ + struct smb_file_time ft; + uint32_t dosmode = 0; + NTSTATUS status = NT_STATUS_OK; + + init_smb_file_time(&ft); + + if (total_data < 36) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (fsp == NULL) { + return NT_STATUS_INVALID_HANDLE; + } + + status = check_access_fsp(fsp, FILE_WRITE_ATTRIBUTES); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* Set the attributes */ + dosmode = IVAL(pdata,32); + status = smb_set_file_dosmode(conn, fsp, dosmode); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* create time */ + ft.create_time = pull_long_date_full_timespec(pdata); + + /* access time */ + ft.atime = pull_long_date_full_timespec(pdata+8); + + /* write time. */ + ft.mtime = pull_long_date_full_timespec(pdata+16); + + /* change time. */ + ft.ctime = pull_long_date_full_timespec(pdata+24); + + DEBUG(10, ("smb_set_file_basic_info: file %s\n", + smb_fname_str_dbg(smb_fname))); + + status = smb_set_file_time(conn, fsp, smb_fname, &ft, true); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (fsp->fsp_flags.modified) { + trigger_write_time_update_immediate(fsp); + } + return NT_STATUS_OK; +} + +/**************************************************************************** + Deal with SMB_INFO_STANDARD. +****************************************************************************/ + +static NTSTATUS smb_set_info_standard(connection_struct *conn, + const char *pdata, + int total_data, + files_struct *fsp, + struct smb_filename *smb_fname) +{ + NTSTATUS status; + struct smb_file_time ft; + + init_smb_file_time(&ft); + + if (total_data < 12) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (fsp == NULL) { + return NT_STATUS_INVALID_HANDLE; + } + + /* create time */ + ft.create_time = time_t_to_full_timespec(srv_make_unix_date2(pdata)); + /* access time */ + ft.atime = time_t_to_full_timespec(srv_make_unix_date2(pdata+4)); + /* write time */ + ft.mtime = time_t_to_full_timespec(srv_make_unix_date2(pdata+8)); + + DEBUG(10,("smb_set_info_standard: file %s\n", + smb_fname_str_dbg(smb_fname))); + + status = check_access_fsp(fsp, FILE_WRITE_ATTRIBUTES); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = smb_set_file_time(conn, fsp, smb_fname, &ft, true); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (fsp->fsp_flags.modified) { + trigger_write_time_update_immediate(fsp); + } + return NT_STATUS_OK; +} + +/**************************************************************************** + Deal with SMB_SET_FILE_ALLOCATION_INFO. +****************************************************************************/ + +static NTSTATUS smb_set_file_allocation_info(connection_struct *conn, + struct smb_request *req, + const char *pdata, + int total_data, + files_struct *fsp, + struct smb_filename *smb_fname) +{ + uint64_t allocation_size = 0; + NTSTATUS status = NT_STATUS_OK; + files_struct *new_fsp = NULL; + + if (!VALID_STAT(smb_fname->st)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (total_data < 8) { + return NT_STATUS_INVALID_PARAMETER; + } + + allocation_size = (uint64_t)IVAL(pdata,0); + allocation_size |= (((uint64_t)IVAL(pdata,4)) << 32); + DEBUG(10,("smb_set_file_allocation_info: Set file allocation info for " + "file %s to %.0f\n", smb_fname_str_dbg(smb_fname), + (double)allocation_size)); + + if (allocation_size) { + allocation_size = smb_roundup(conn, allocation_size); + } + + DEBUG(10,("smb_set_file_allocation_info: file %s : setting new " + "allocation size to %.0f\n", smb_fname_str_dbg(smb_fname), + (double)allocation_size)); + + if (fsp && + !fsp->fsp_flags.is_pathref && + fsp_get_io_fd(fsp) != -1) + { + /* Open file handle. */ + if (!(fsp->access_mask & FILE_WRITE_DATA)) { + return NT_STATUS_ACCESS_DENIED; + } + + /* Only change if needed. */ + if (allocation_size != get_file_size_stat(&smb_fname->st)) { + if (vfs_allocate_file_space(fsp, allocation_size) == -1) { + return map_nt_error_from_unix(errno); + } + } + /* But always update the time. */ + /* + * This is equivalent to a write. Ensure it's seen immediately + * if there are no pending writes. + */ + trigger_write_time_update_immediate(fsp); + return NT_STATUS_OK; + } + + /* Pathname or stat or directory file. */ + status = SMB_VFS_CREATE_FILE( + conn, /* conn */ + req, /* req */ + NULL, /* dirfsp */ + smb_fname, /* fname */ + FILE_WRITE_DATA, /* 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 */ + 0, /* oplock_request */ + NULL, /* lease */ + 0, /* allocation_size */ + 0, /* private_flags */ + NULL, /* sd */ + NULL, /* ea_list */ + &new_fsp, /* result */ + NULL, /* pinfo */ + NULL, NULL); /* create context */ + + if (!NT_STATUS_IS_OK(status)) { + /* NB. We check for open_was_deferred in the caller. */ + return status; + } + + /* Only change if needed. */ + if (allocation_size != get_file_size_stat(&smb_fname->st)) { + if (vfs_allocate_file_space(new_fsp, allocation_size) == -1) { + status = map_nt_error_from_unix(errno); + close_file_free(req, &new_fsp, NORMAL_CLOSE); + return status; + } + } + + /* Changing the allocation size should set the last mod time. */ + /* + * This is equivalent to a write. Ensure it's seen immediately + * if there are no pending writes. + */ + trigger_write_time_update_immediate(new_fsp); + close_file_free(req, &new_fsp, NORMAL_CLOSE); + return NT_STATUS_OK; +} + +/**************************************************************************** + Deal with SMB_SET_FILE_END_OF_FILE_INFO. +****************************************************************************/ + +static NTSTATUS smb_set_file_end_of_file_info(connection_struct *conn, + struct smb_request *req, + const char *pdata, + int total_data, + files_struct *fsp, + struct smb_filename *smb_fname, + bool fail_after_createfile) +{ + off_t size; + + if (total_data < 8) { + return NT_STATUS_INVALID_PARAMETER; + } + + size = IVAL(pdata,0); + size |= (((off_t)IVAL(pdata,4)) << 32); + DEBUG(10,("smb_set_file_end_of_file_info: Set end of file info for " + "file %s to %.0f\n", smb_fname_str_dbg(smb_fname), + (double)size)); + + return smb_set_file_size(conn, req, + fsp, + smb_fname, + &smb_fname->st, + size, + fail_after_createfile); +} + +/**************************************************************************** + Allow a UNIX info mknod. +****************************************************************************/ + +static NTSTATUS smb_unix_mknod(connection_struct *conn, + const char *pdata, + int total_data, + const struct smb_filename *smb_fname) +{ + uint32_t file_type = IVAL(pdata,56); +#if defined(HAVE_MAKEDEV) + uint32_t dev_major = IVAL(pdata,60); + uint32_t dev_minor = IVAL(pdata,68); +#endif + SMB_DEV_T dev = (SMB_DEV_T)0; + uint32_t raw_unixmode = IVAL(pdata,84); + NTSTATUS status; + mode_t unixmode; + int ret; + struct smb_filename *parent_fname = NULL; + struct smb_filename *base_name = NULL; + + if (total_data < 100) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = unix_perms_from_wire(conn, &smb_fname->st, raw_unixmode, + PERM_NEW_FILE, &unixmode); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + +#if defined(HAVE_MAKEDEV) + dev = makedev(dev_major, dev_minor); +#endif + + switch (file_type) { + /* We can't create other objects here. */ + case UNIX_TYPE_FILE: + case UNIX_TYPE_DIR: + case UNIX_TYPE_SYMLINK: + return NT_STATUS_ACCESS_DENIED; +#if defined(S_IFIFO) + case UNIX_TYPE_FIFO: + unixmode |= S_IFIFO; + break; +#endif +#if defined(S_IFSOCK) + case UNIX_TYPE_SOCKET: + unixmode |= S_IFSOCK; + break; +#endif +#if defined(S_IFCHR) + case UNIX_TYPE_CHARDEV: + /* This is only allowed for root. */ + if (get_current_uid(conn) != sec_initial_uid()) { + return NT_STATUS_ACCESS_DENIED; + } + unixmode |= S_IFCHR; + break; +#endif +#if defined(S_IFBLK) + case UNIX_TYPE_BLKDEV: + if (get_current_uid(conn) != sec_initial_uid()) { + return NT_STATUS_ACCESS_DENIED; + } + unixmode |= S_IFBLK; + break; +#endif + default: + return NT_STATUS_INVALID_PARAMETER; + } + + DEBUG(10,("smb_unix_mknod: SMB_SET_FILE_UNIX_BASIC doing mknod dev " + "%.0f mode 0%o for file %s\n", (double)dev, + (unsigned int)unixmode, smb_fname_str_dbg(smb_fname))); + + status = parent_pathref(talloc_tos(), + conn->cwd_fsp, + smb_fname, + &parent_fname, + &base_name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* Ok - do the mknod. */ + ret = SMB_VFS_MKNODAT(conn, + parent_fname->fsp, + base_name, + unixmode, + dev); + + if (ret != 0) { + TALLOC_FREE(parent_fname); + return map_nt_error_from_unix(errno); + } + + /* If any of the other "set" calls fail we + * don't want to end up with a half-constructed mknod. + */ + + if (lp_inherit_permissions(SNUM(conn))) { + inherit_access_posix_acl(conn, + parent_fname->fsp, + smb_fname, + unixmode); + } + TALLOC_FREE(parent_fname); + + return NT_STATUS_OK; +} + +/**************************************************************************** + Deal with SMB_SET_FILE_UNIX_BASIC. +****************************************************************************/ + +static NTSTATUS smb_set_file_unix_basic(connection_struct *conn, + struct smb_request *req, + const char *pdata, + int total_data, + files_struct *fsp, + struct smb_filename *smb_fname) +{ + struct smb_file_time ft; + uint32_t raw_unixmode; + mode_t unixmode; + off_t size = 0; + uid_t set_owner = (uid_t)SMB_UID_NO_CHANGE; + gid_t set_grp = (uid_t)SMB_GID_NO_CHANGE; + NTSTATUS status = NT_STATUS_OK; + enum perm_type ptype; + files_struct *all_fsps = NULL; + bool modify_mtime = true; + struct file_id id; + SMB_STRUCT_STAT sbuf; + + init_smb_file_time(&ft); + + if (total_data < 100) { + return NT_STATUS_INVALID_PARAMETER; + } + + if(IVAL(pdata, 0) != SMB_SIZE_NO_CHANGE_LO && + IVAL(pdata, 4) != SMB_SIZE_NO_CHANGE_HI) { + size=IVAL(pdata,0); /* first 8 Bytes are size */ + size |= (((off_t)IVAL(pdata,4)) << 32); + } + + ft.atime = pull_long_date_full_timespec(pdata+24); /* access_time */ + ft.mtime = pull_long_date_full_timespec(pdata+32); /* modification_time */ + set_owner = (uid_t)IVAL(pdata,40); + set_grp = (gid_t)IVAL(pdata,48); + raw_unixmode = IVAL(pdata,84); + + if (VALID_STAT(smb_fname->st)) { + if (S_ISDIR(smb_fname->st.st_ex_mode)) { + ptype = PERM_EXISTING_DIR; + } else { + ptype = PERM_EXISTING_FILE; + } + } else { + ptype = PERM_NEW_FILE; + } + + status = unix_perms_from_wire(conn, &smb_fname->st, raw_unixmode, + ptype, &unixmode); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(10,("smb_set_file_unix_basic: SMB_SET_FILE_UNIX_BASIC: name = " + "%s size = %.0f, uid = %u, gid = %u, raw perms = 0%o\n", + smb_fname_str_dbg(smb_fname), (double)size, + (unsigned int)set_owner, (unsigned int)set_grp, + (int)raw_unixmode)); + + sbuf = smb_fname->st; + + if (!VALID_STAT(sbuf)) { + /* + * The only valid use of this is to create character and block + * devices, and named pipes. This is deprecated (IMHO) and + * a new info level should be used for mknod. JRA. + */ + + return smb_unix_mknod(conn, + pdata, + total_data, + smb_fname); + } + +#if 1 + /* Horrible backwards compatibility hack as an old server bug + * allowed a CIFS client bug to remain unnoticed :-(. JRA. + * */ + + if (!size) { + size = get_file_size_stat(&sbuf); + } +#endif + + /* + * Deal with the UNIX specific mode set. + */ + + if (raw_unixmode != SMB_MODE_NO_CHANGE) { + int ret; + + if (fsp == NULL || S_ISLNK(smb_fname->st.st_ex_mode)) { + DBG_WARNING("Can't set mode on symlink %s\n", + smb_fname_str_dbg(smb_fname)); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + DEBUG(10,("smb_set_file_unix_basic: SMB_SET_FILE_UNIX_BASIC " + "setting mode 0%o for file %s\n", + (unsigned int)unixmode, + smb_fname_str_dbg(smb_fname))); + ret = SMB_VFS_FCHMOD(fsp, unixmode); + if (ret != 0) { + return map_nt_error_from_unix(errno); + } + } + + /* + * Deal with the UNIX specific uid set. + */ + + if ((set_owner != (uid_t)SMB_UID_NO_CHANGE) && + (sbuf.st_ex_uid != set_owner)) { + int ret; + + DEBUG(10,("smb_set_file_unix_basic: SMB_SET_FILE_UNIX_BASIC " + "changing owner %u for path %s\n", + (unsigned int)set_owner, + smb_fname_str_dbg(smb_fname))); + + if (fsp && + !fsp->fsp_flags.is_pathref && + fsp_get_io_fd(fsp) != -1) + { + ret = SMB_VFS_FCHOWN(fsp, set_owner, (gid_t)-1); + } else { + /* + * UNIX extensions calls must always operate + * on symlinks. + */ + ret = SMB_VFS_LCHOWN(conn, smb_fname, + set_owner, (gid_t)-1); + } + + if (ret != 0) { + status = map_nt_error_from_unix(errno); + return status; + } + } + + /* + * Deal with the UNIX specific gid set. + */ + + if ((set_grp != (uid_t)SMB_GID_NO_CHANGE) && + (sbuf.st_ex_gid != set_grp)) { + int ret; + + DEBUG(10,("smb_set_file_unix_basic: SMB_SET_FILE_UNIX_BASIC " + "changing group %u for file %s\n", + (unsigned int)set_grp, + smb_fname_str_dbg(smb_fname))); + if (fsp && + !fsp->fsp_flags.is_pathref && + fsp_get_io_fd(fsp) != -1) + { + ret = SMB_VFS_FCHOWN(fsp, (uid_t)-1, set_grp); + } else { + /* + * UNIX extensions calls must always operate + * on symlinks. + */ + ret = SMB_VFS_LCHOWN(conn, smb_fname, (uid_t)-1, + set_grp); + } + if (ret != 0) { + status = map_nt_error_from_unix(errno); + return status; + } + } + + /* Deal with any size changes. */ + + if (S_ISREG(sbuf.st_ex_mode)) { + status = smb_set_file_size(conn, req, + fsp, + smb_fname, + &sbuf, + size, + false); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + /* Deal with any time changes. */ + if (is_omit_timespec(&ft.mtime) && is_omit_timespec(&ft.atime)) { + /* No change, don't cancel anything. */ + return status; + } + + id = vfs_file_id_from_sbuf(conn, &sbuf); + for(all_fsps = file_find_di_first(conn->sconn, id, true); all_fsps; + all_fsps = file_find_di_next(all_fsps, true)) { + /* + * We're setting the time explicitly for UNIX. + * Cancel any pending changes over all handles. + */ + all_fsps->fsp_flags.update_write_time_on_close = false; + TALLOC_FREE(all_fsps->update_write_time_event); + } + + /* + * Override the "setting_write_time" + * parameter here as it almost does what + * we need. Just remember if we modified + * mtime and send the notify ourselves. + */ + if (is_omit_timespec(&ft.mtime)) { + modify_mtime = false; + } + + status = smb_set_file_time(conn, + fsp, + smb_fname, + &ft, + false); + if (modify_mtime) { + notify_fname(conn, NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_LAST_WRITE, smb_fname->base_name); + } + return status; +} + +/**************************************************************************** + Deal with SMB_SET_FILE_UNIX_INFO2. +****************************************************************************/ + +static NTSTATUS smb_set_file_unix_info2(connection_struct *conn, + struct smb_request *req, + const char *pdata, + int total_data, + files_struct *fsp, + struct smb_filename *smb_fname) +{ + NTSTATUS status; + uint32_t smb_fflags; + uint32_t smb_fmask; + + if (total_data < 116) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* Start by setting all the fields that are common between UNIX_BASIC + * and UNIX_INFO2. + */ + status = smb_set_file_unix_basic(conn, req, pdata, total_data, + fsp, smb_fname); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + smb_fflags = IVAL(pdata, 108); + smb_fmask = IVAL(pdata, 112); + + /* NB: We should only attempt to alter the file flags if the client + * sends a non-zero mask. + */ + if (smb_fmask != 0) { + int stat_fflags = 0; + + if (!map_info2_flags_to_sbuf(&smb_fname->st, smb_fflags, + smb_fmask, &stat_fflags)) { + /* Client asked to alter a flag we don't understand. */ + return NT_STATUS_INVALID_PARAMETER; + } + + if (fsp == NULL || S_ISLNK(smb_fname->st.st_ex_mode)) { + DBG_WARNING("Can't change flags on symlink %s\n", + smb_fname_str_dbg(smb_fname)); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + if (SMB_VFS_FCHFLAGS(fsp, stat_fflags) != 0) { + return map_nt_error_from_unix(errno); + } + } + + /* XXX: need to add support for changing the create_time here. You + * can do this for paths on Darwin with setattrlist(2). The right way + * to hook this up is probably by extending the VFS utimes interface. + */ + + return NT_STATUS_OK; +} + +/**************************************************************************** + Create a directory with POSIX semantics. +****************************************************************************/ + +static NTSTATUS smb_posix_mkdir(connection_struct *conn, + struct smb_request *req, + char **ppdata, + int total_data, + struct smb_filename *smb_fname, + int *pdata_return_size) +{ + NTSTATUS status = NT_STATUS_OK; + uint32_t raw_unixmode = 0; + mode_t unixmode = (mode_t)0; + files_struct *fsp = NULL; + uint16_t info_level_return = 0; + int info; + char *pdata = *ppdata; + struct smb2_create_blobs *posx = NULL; + + if (total_data < 18) { + return NT_STATUS_INVALID_PARAMETER; + } + + raw_unixmode = IVAL(pdata,8); + /* Next 4 bytes are not yet defined. */ + + status = unix_perms_from_wire(conn, &smb_fname->st, raw_unixmode, + PERM_NEW_DIR, &unixmode); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = make_smb2_posix_create_ctx(talloc_tos(), &posx, unixmode); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("make_smb2_posix_create_ctx failed: %s\n", + nt_errstr(status)); + return status; + } + + DEBUG(10,("smb_posix_mkdir: file %s, mode 0%o\n", + smb_fname_str_dbg(smb_fname), (unsigned int)unixmode)); + + status = SMB_VFS_CREATE_FILE( + conn, /* conn */ + req, /* req */ + NULL, /* dirfsp */ + smb_fname, /* fname */ + FILE_READ_ATTRIBUTES, /* access_mask */ + FILE_SHARE_NONE, /* share_access */ + FILE_CREATE, /* create_disposition*/ + FILE_DIRECTORY_FILE, /* create_options */ + 0, /* file_attributes */ + 0, /* oplock_request */ + NULL, /* lease */ + 0, /* allocation_size */ + 0, /* private_flags */ + NULL, /* sd */ + NULL, /* ea_list */ + &fsp, /* result */ + &info, /* pinfo */ + posx, /* in_context_blobs */ + NULL); /* out_context_blobs */ + + TALLOC_FREE(posx); + + if (NT_STATUS_IS_OK(status)) { + close_file_free(req, &fsp, NORMAL_CLOSE); + } + + info_level_return = SVAL(pdata,16); + + if (info_level_return == SMB_QUERY_FILE_UNIX_BASIC) { + *pdata_return_size = 12 + SMB_FILE_UNIX_BASIC_SIZE; + } else if (info_level_return == SMB_QUERY_FILE_UNIX_INFO2) { + *pdata_return_size = 12 + SMB_FILE_UNIX_INFO2_SIZE; + } else { + *pdata_return_size = 12; + } + + /* Realloc the data size */ + *ppdata = (char *)SMB_REALLOC(*ppdata,*pdata_return_size); + if (*ppdata == NULL) { + *pdata_return_size = 0; + return NT_STATUS_NO_MEMORY; + } + pdata = *ppdata; + + SSVAL(pdata,0,NO_OPLOCK_RETURN); + SSVAL(pdata,2,0); /* No fnum. */ + SIVAL(pdata,4,info); /* Was directory created. */ + + switch (info_level_return) { + case SMB_QUERY_FILE_UNIX_BASIC: + SSVAL(pdata,8,SMB_QUERY_FILE_UNIX_BASIC); + SSVAL(pdata,10,0); /* Padding. */ + store_file_unix_basic(conn, pdata + 12, fsp, + &smb_fname->st); + break; + case SMB_QUERY_FILE_UNIX_INFO2: + SSVAL(pdata,8,SMB_QUERY_FILE_UNIX_INFO2); + SSVAL(pdata,10,0); /* Padding. */ + store_file_unix_basic_info2(conn, pdata + 12, fsp, + &smb_fname->st); + break; + default: + SSVAL(pdata,8,SMB_NO_INFO_LEVEL_RETURNED); + SSVAL(pdata,10,0); /* Padding. */ + break; + } + + return status; +} + +/**************************************************************************** + Open/Create a file with POSIX semantics. +****************************************************************************/ + +#define SMB_O_RDONLY_MAPPING (FILE_READ_DATA|FILE_READ_ATTRIBUTES|FILE_READ_EA) +#define SMB_O_WRONLY_MAPPING (FILE_WRITE_DATA|FILE_WRITE_ATTRIBUTES|FILE_WRITE_EA) + +static NTSTATUS smb_posix_open(connection_struct *conn, + struct smb_request *req, + char **ppdata, + int total_data, + struct smb_filename *smb_fname, + int *pdata_return_size) +{ + bool extended_oplock_granted = False; + char *pdata = *ppdata; + uint32_t flags = 0; + uint32_t wire_open_mode = 0; + uint32_t raw_unixmode = 0; + uint32_t attributes = 0; + uint32_t create_disp = 0; + uint32_t access_mask = 0; + uint32_t create_options = FILE_NON_DIRECTORY_FILE; + NTSTATUS status = NT_STATUS_OK; + mode_t unixmode = (mode_t)0; + files_struct *fsp = NULL; + int oplock_request = 0; + int info = 0; + uint16_t info_level_return = 0; + struct smb2_create_blobs *posx = NULL; + + if (total_data < 18) { + return NT_STATUS_INVALID_PARAMETER; + } + + flags = IVAL(pdata,0); + oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0; + if (oplock_request) { + oplock_request |= (flags & REQUEST_BATCH_OPLOCK) ? BATCH_OPLOCK : 0; + } + + wire_open_mode = IVAL(pdata,4); + + if (wire_open_mode == (SMB_O_CREAT|SMB_O_DIRECTORY)) { + return smb_posix_mkdir(conn, req, + ppdata, + total_data, + smb_fname, + pdata_return_size); + } + + switch (wire_open_mode & SMB_ACCMODE) { + case SMB_O_RDONLY: + access_mask = SMB_O_RDONLY_MAPPING; + break; + case SMB_O_WRONLY: + access_mask = SMB_O_WRONLY_MAPPING; + break; + case SMB_O_RDWR: + access_mask = (SMB_O_RDONLY_MAPPING| + SMB_O_WRONLY_MAPPING); + break; + default: + DEBUG(5,("smb_posix_open: invalid open mode 0x%x\n", + (unsigned int)wire_open_mode )); + return NT_STATUS_INVALID_PARAMETER; + } + + wire_open_mode &= ~SMB_ACCMODE; + + /* First take care of O_CREAT|O_EXCL interactions. */ + switch (wire_open_mode & (SMB_O_CREAT | SMB_O_EXCL)) { + case (SMB_O_CREAT | SMB_O_EXCL): + /* File exists fail. File not exist create. */ + create_disp = FILE_CREATE; + break; + case SMB_O_CREAT: + /* File exists open. File not exist create. */ + create_disp = FILE_OPEN_IF; + break; + case SMB_O_EXCL: + /* O_EXCL on its own without O_CREAT is undefined. + We deliberately ignore it as some versions of + Linux CIFSFS can send a bare O_EXCL on the + wire which other filesystems in the kernel + ignore. See bug 9519 for details. */ + + /* Fallthrough. */ + + case 0: + /* File exists open. File not exist fail. */ + create_disp = FILE_OPEN; + break; + default: + DEBUG(5,("smb_posix_open: invalid create mode 0x%x\n", + (unsigned int)wire_open_mode )); + return NT_STATUS_INVALID_PARAMETER; + } + + /* Next factor in the effects of O_TRUNC. */ + wire_open_mode &= ~(SMB_O_CREAT | SMB_O_EXCL); + + if (wire_open_mode & SMB_O_TRUNC) { + switch (create_disp) { + case FILE_CREATE: + /* (SMB_O_CREAT | SMB_O_EXCL | O_TRUNC) */ + /* Leave create_disp alone as + (O_CREAT|O_EXCL|O_TRUNC) == (O_CREAT|O_EXCL) + */ + /* File exists fail. File not exist create. */ + break; + case FILE_OPEN_IF: + /* SMB_O_CREAT | SMB_O_TRUNC */ + /* File exists overwrite. File not exist create. */ + create_disp = FILE_OVERWRITE_IF; + break; + case FILE_OPEN: + /* SMB_O_TRUNC */ + /* File exists overwrite. File not exist fail. */ + create_disp = FILE_OVERWRITE; + break; + default: + /* Cannot get here. */ + smb_panic("smb_posix_open: logic error"); + return NT_STATUS_INVALID_PARAMETER; + } + } + + raw_unixmode = IVAL(pdata,8); + /* Next 4 bytes are not yet defined. */ + + status = unix_perms_from_wire(conn, &smb_fname->st, raw_unixmode, + (VALID_STAT(smb_fname->st) ? + PERM_EXISTING_FILE : PERM_NEW_FILE), + &unixmode); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = make_smb2_posix_create_ctx(talloc_tos(), &posx, unixmode); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("make_smb2_posix_create_ctx failed: %s\n", + nt_errstr(status)); + return status; + } + + if (wire_open_mode & SMB_O_SYNC) { + create_options |= FILE_WRITE_THROUGH; + } + if (wire_open_mode & SMB_O_APPEND) { + access_mask |= FILE_APPEND_DATA; + } + if (wire_open_mode & SMB_O_DIRECT) { + attributes |= FILE_FLAG_NO_BUFFERING; + } + + if ((wire_open_mode & SMB_O_DIRECTORY) || + VALID_STAT_OF_DIR(smb_fname->st)) { + if (access_mask != SMB_O_RDONLY_MAPPING) { + return NT_STATUS_FILE_IS_A_DIRECTORY; + } + create_options &= ~FILE_NON_DIRECTORY_FILE; + create_options |= FILE_DIRECTORY_FILE; + } + + DEBUG(10,("smb_posix_open: file %s, smb_posix_flags = %u, mode 0%o\n", + smb_fname_str_dbg(smb_fname), + (unsigned int)wire_open_mode, + (unsigned int)unixmode )); + + status = SMB_VFS_CREATE_FILE( + conn, /* conn */ + req, /* req */ + NULL, /* dirfsp */ + smb_fname, /* fname */ + access_mask, /* access_mask */ + (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */ + FILE_SHARE_DELETE), + create_disp, /* create_disposition*/ + create_options, /* create_options */ + attributes, /* file_attributes */ + oplock_request, /* oplock_request */ + NULL, /* lease */ + 0, /* allocation_size */ + 0, /* private_flags */ + NULL, /* sd */ + NULL, /* ea_list */ + &fsp, /* result */ + &info, /* pinfo */ + posx, /* in_context_blobs */ + NULL); /* out_context_blobs */ + + TALLOC_FREE(posx); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (oplock_request && lp_fake_oplocks(SNUM(conn))) { + extended_oplock_granted = True; + } + + if(oplock_request && EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) { + extended_oplock_granted = True; + } + + info_level_return = SVAL(pdata,16); + + /* Allocate the correct return size. */ + + if (info_level_return == SMB_QUERY_FILE_UNIX_BASIC) { + *pdata_return_size = 12 + SMB_FILE_UNIX_BASIC_SIZE; + } else if (info_level_return == SMB_QUERY_FILE_UNIX_INFO2) { + *pdata_return_size = 12 + SMB_FILE_UNIX_INFO2_SIZE; + } else { + *pdata_return_size = 12; + } + + /* Realloc the data size */ + *ppdata = (char *)SMB_REALLOC(*ppdata,*pdata_return_size); + if (*ppdata == NULL) { + close_file_free(req, &fsp, ERROR_CLOSE); + *pdata_return_size = 0; + return NT_STATUS_NO_MEMORY; + } + pdata = *ppdata; + + if (extended_oplock_granted) { + if (flags & REQUEST_BATCH_OPLOCK) { + SSVAL(pdata,0, BATCH_OPLOCK_RETURN); + } else { + SSVAL(pdata,0, EXCLUSIVE_OPLOCK_RETURN); + } + } else if (fsp->oplock_type == LEVEL_II_OPLOCK) { + SSVAL(pdata,0, LEVEL_II_OPLOCK_RETURN); + } else { + SSVAL(pdata,0,NO_OPLOCK_RETURN); + } + + SSVAL(pdata,2,fsp->fnum); + SIVAL(pdata,4,info); /* Was file created etc. */ + + switch (info_level_return) { + case SMB_QUERY_FILE_UNIX_BASIC: + SSVAL(pdata,8,SMB_QUERY_FILE_UNIX_BASIC); + SSVAL(pdata,10,0); /* padding. */ + store_file_unix_basic(conn, pdata + 12, fsp, + &smb_fname->st); + break; + case SMB_QUERY_FILE_UNIX_INFO2: + SSVAL(pdata,8,SMB_QUERY_FILE_UNIX_INFO2); + SSVAL(pdata,10,0); /* padding. */ + store_file_unix_basic_info2(conn, pdata + 12, fsp, + &smb_fname->st); + break; + default: + SSVAL(pdata,8,SMB_NO_INFO_LEVEL_RETURNED); + SSVAL(pdata,10,0); /* padding. */ + break; + } + return NT_STATUS_OK; +} + +/**************************************************************************** + Delete a file with POSIX semantics. +****************************************************************************/ + +static NTSTATUS smb_posix_unlink(connection_struct *conn, + struct smb_request *req, + const char *pdata, + int total_data, + struct smb_filename *smb_fname) +{ + NTSTATUS status = NT_STATUS_OK; + files_struct *fsp = NULL; + uint16_t flags = 0; + char del = 1; + int info = 0; + int create_options = 0; + struct share_mode_lock *lck = NULL; + bool other_nonposix_opens; + struct smb2_create_blobs *posx = NULL; + + if (total_data < 2) { + return NT_STATUS_INVALID_PARAMETER; + } + + flags = SVAL(pdata,0); + + if (!VALID_STAT(smb_fname->st)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if ((flags == SMB_POSIX_UNLINK_DIRECTORY_TARGET) && + !VALID_STAT_OF_DIR(smb_fname->st)) { + return NT_STATUS_NOT_A_DIRECTORY; + } + + DEBUG(10,("smb_posix_unlink: %s %s\n", + (flags == SMB_POSIX_UNLINK_DIRECTORY_TARGET) ? "directory" : "file", + smb_fname_str_dbg(smb_fname))); + + if (VALID_STAT_OF_DIR(smb_fname->st)) { + create_options |= FILE_DIRECTORY_FILE; + } + + status = make_smb2_posix_create_ctx(talloc_tos(), &posx, 0777); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("make_smb2_posix_create_ctx failed: %s\n", + nt_errstr(status)); + return status; + } + + status = SMB_VFS_CREATE_FILE( + conn, /* conn */ + req, /* req */ + NULL, /* dirfsp */ + smb_fname, /* fname */ + DELETE_ACCESS, /* access_mask */ + (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */ + FILE_SHARE_DELETE), + FILE_OPEN, /* create_disposition*/ + create_options, /* create_options */ + 0, /* file_attributes */ + 0, /* oplock_request */ + NULL, /* lease */ + 0, /* allocation_size */ + 0, /* private_flags */ + NULL, /* sd */ + NULL, /* ea_list */ + &fsp, /* result */ + &info, /* pinfo */ + posx, /* in_context_blobs */ + NULL); /* out_context_blobs */ + + TALLOC_FREE(posx); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * Don't lie to client. If we can't really delete due to + * non-POSIX opens return SHARING_VIOLATION. + */ + + lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id); + if (lck == NULL) { + DEBUG(0, ("smb_posix_unlink: Could not get share mode " + "lock for file %s\n", fsp_str_dbg(fsp))); + close_file_free(req, &fsp, NORMAL_CLOSE); + return NT_STATUS_INVALID_PARAMETER; + } + + other_nonposix_opens = has_other_nonposix_opens(lck, fsp); + if (other_nonposix_opens) { + /* Fail with sharing violation. */ + TALLOC_FREE(lck); + close_file_free(req, &fsp, NORMAL_CLOSE); + return NT_STATUS_SHARING_VIOLATION; + } + + /* + * Set the delete on close. + */ + status = smb_set_file_disposition_info(conn, + &del, + 1, + fsp, + smb_fname); + + TALLOC_FREE(lck); + + if (!NT_STATUS_IS_OK(status)) { + close_file_free(req, &fsp, NORMAL_CLOSE); + return status; + } + return close_file_free(req, &fsp, NORMAL_CLOSE); +} + +static NTSTATUS smbd_do_posix_setfilepathinfo(struct connection_struct *conn, + struct smb_request *req, + TALLOC_CTX *mem_ctx, + uint16_t info_level, + struct smb_filename *smb_fname, + files_struct *fsp, + char **ppdata, + int total_data, + int *ret_data_size) +{ + char *pdata = *ppdata; + NTSTATUS status = NT_STATUS_OK; + int data_return_size = 0; + + *ret_data_size = 0; + + if (!CAN_WRITE(conn)) { + /* Allow POSIX opens. The open path will deny + * any non-readonly opens. */ + if (info_level != SMB_POSIX_PATH_OPEN) { + return NT_STATUS_DOS(ERRSRV, ERRaccess); + } + } + + DBG_DEBUG("file=%s (%s) info_level=%d totdata=%d\n", + smb_fname_str_dbg(smb_fname), + fsp_fnum_dbg(fsp), + info_level, + total_data); + + switch (info_level) { + case SMB_SET_FILE_UNIX_BASIC: + { + status = smb_set_file_unix_basic(conn, req, + pdata, + total_data, + fsp, + smb_fname); + break; + } + + case SMB_SET_FILE_UNIX_INFO2: + { + status = smb_set_file_unix_info2(conn, req, + pdata, + total_data, + fsp, + smb_fname); + break; + } + + case SMB_SET_FILE_UNIX_LINK: + { + if (smb_fname == NULL) { + /* We must have a pathname for this. */ + return NT_STATUS_INVALID_LEVEL; + } + status = smb_set_file_unix_link(conn, req, pdata, + total_data, smb_fname); + break; + } + + case SMB_SET_FILE_UNIX_HLINK: + { + if (smb_fname == NULL) { + /* We must have a pathname for this. */ + return NT_STATUS_INVALID_LEVEL; + } + status = smb_set_file_unix_hlink(conn, req, + pdata, total_data, + smb_fname); + break; + } + +#if defined(HAVE_POSIX_ACLS) + case SMB_SET_POSIX_ACL: + { + status = smb_set_posix_acl(conn, + req, + pdata, + total_data, + fsp, + smb_fname); + break; + } +#endif + +#if defined(WITH_SMB1SERVER) + case SMB_SET_POSIX_LOCK: + { + if (fsp == NULL) { + return NT_STATUS_INVALID_LEVEL; + } + status = smb_set_posix_lock(conn, req, + pdata, total_data, fsp); + break; + } +#endif + + case SMB_POSIX_PATH_OPEN: + { + if (smb_fname == NULL) { + /* We must have a pathname for this. */ + return NT_STATUS_INVALID_LEVEL; + } + + status = smb_posix_open(conn, req, + ppdata, + total_data, + smb_fname, + &data_return_size); + break; + } + + case SMB_POSIX_PATH_UNLINK: + { + if (smb_fname == NULL) { + /* We must have a pathname for this. */ + return NT_STATUS_INVALID_LEVEL; + } + + status = smb_posix_unlink(conn, req, + pdata, + total_data, + smb_fname); + break; + } + + default: + return NT_STATUS_INVALID_LEVEL; + } + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *ret_data_size = data_return_size; + return NT_STATUS_OK; +} + +NTSTATUS smbd_do_setfilepathinfo(connection_struct *conn, + struct smb_request *req, + TALLOC_CTX *mem_ctx, + uint16_t info_level, + files_struct *fsp, + struct smb_filename *smb_fname, + char **ppdata, int total_data, + int *ret_data_size) +{ + char *pdata = *ppdata; + NTSTATUS status = NT_STATUS_OK; + int data_return_size = 0; + + if (INFO_LEVEL_IS_UNIX(info_level)) { + if (!lp_smb1_unix_extensions()) { + return NT_STATUS_INVALID_LEVEL; + } + if (!req->posix_pathnames) { + return NT_STATUS_INVALID_LEVEL; + } + status = smbd_do_posix_setfilepathinfo(conn, + req, + req, + info_level, + smb_fname, + fsp, + ppdata, + total_data, + &data_return_size); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + *ret_data_size = data_return_size; + return NT_STATUS_OK; + } + + *ret_data_size = 0; + + DEBUG(3,("smbd_do_setfilepathinfo: %s (%s) info_level=%d " + "totdata=%d\n", smb_fname_str_dbg(smb_fname), + fsp_fnum_dbg(fsp), + info_level, total_data)); + + switch (info_level) { + + case SMB_INFO_STANDARD: + { + status = smb_set_info_standard(conn, + pdata, + total_data, + fsp, + smb_fname); + break; + } + + case SMB_INFO_SET_EA: + { + status = smb_info_set_ea(conn, + pdata, + total_data, + fsp, + smb_fname); + break; + } + + case SMB_SET_FILE_BASIC_INFO: + case SMB_FILE_BASIC_INFORMATION: + { + status = smb_set_file_basic_info(conn, + pdata, + total_data, + fsp, + smb_fname); + break; + } + + case SMB_FILE_ALLOCATION_INFORMATION: + case SMB_SET_FILE_ALLOCATION_INFO: + { + status = smb_set_file_allocation_info(conn, req, + pdata, + total_data, + fsp, + smb_fname); + break; + } + + case SMB_FILE_END_OF_FILE_INFORMATION: + case SMB_SET_FILE_END_OF_FILE_INFO: + { + /* + * XP/Win7 both fail after the createfile with + * SMB_SET_FILE_END_OF_FILE_INFO but not + * SMB_FILE_END_OF_FILE_INFORMATION (pass-through). + * The level is known here, so pass it down + * appropriately. + */ + bool should_fail = + (info_level == SMB_SET_FILE_END_OF_FILE_INFO); + + status = smb_set_file_end_of_file_info(conn, req, + pdata, + total_data, + fsp, + smb_fname, + should_fail); + break; + } + + case SMB_FILE_DISPOSITION_INFORMATION: + case SMB_SET_FILE_DISPOSITION_INFO: /* Set delete on close for open file. */ + { +#if 0 + /* JRA - We used to just ignore this on a path ? + * Shouldn't this be invalid level on a pathname + * based call ? + */ + if (tran_call != TRANSACT2_SETFILEINFO) { + return ERROR_NT(NT_STATUS_INVALID_LEVEL); + } +#endif + status = smb_set_file_disposition_info(conn, + pdata, + total_data, + fsp, + smb_fname); + break; + } + + case SMB_FILE_POSITION_INFORMATION: + { + status = smb_file_position_information(conn, + pdata, + total_data, + fsp); + break; + } + + case SMB_FILE_FULL_EA_INFORMATION: + { + status = smb_set_file_full_ea_info(conn, + pdata, + total_data, + fsp); + break; + } + + /* From tridge Samba4 : + * MODE_INFORMATION in setfileinfo (I have no + * idea what "mode information" on a file is - it takes a value of 0, + * 2, 4 or 6. What could it be?). + */ + + case SMB_FILE_MODE_INFORMATION: + { + status = smb_file_mode_information(conn, + pdata, + total_data); + break; + } + + /* [MS-SMB2] 3.3.5.21.1 states we MUST fail with STATUS_NOT_SUPPORTED. */ + case SMB_FILE_VALID_DATA_LENGTH_INFORMATION: + case SMB_FILE_SHORT_NAME_INFORMATION: + return NT_STATUS_NOT_SUPPORTED; + + case SMB_FILE_RENAME_INFORMATION: + { + status = smb_file_rename_information(conn, req, + pdata, total_data, + fsp, smb_fname); + break; + } + + case SMB2_FILE_RENAME_INFORMATION_INTERNAL: + { + /* SMB2 rename information. */ + status = smb2_file_rename_information(conn, req, + pdata, total_data, + fsp, smb_fname); + break; + } + + case SMB_FILE_LINK_INFORMATION: + { + status = smb_file_link_information(conn, req, + pdata, total_data, + fsp, smb_fname); + break; + } + + default: + return NT_STATUS_INVALID_LEVEL; + } + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *ret_data_size = data_return_size; + return NT_STATUS_OK; +} + +static uint32_t generate_volume_serial_number( + const struct loadparm_substitution *lp_sub, + int snum) +{ + int serial = lp_volume_serial_number(snum); + return serial != -1 ? serial: + str_checksum(lp_servicename(talloc_tos(), lp_sub, snum)) ^ + (str_checksum(get_local_machine_name())<<16); +} |