/* 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 . */ #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; iea); 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 . */ 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); }