diff options
Diffstat (limited to '')
-rw-r--r-- | source3/smbd/smb2_reply.c | 2232 |
1 files changed, 2232 insertions, 0 deletions
diff --git a/source3/smbd/smb2_reply.c b/source3/smbd/smb2_reply.c new file mode 100644 index 0000000..16d132e --- /dev/null +++ b/source3/smbd/smb2_reply.c @@ -0,0 +1,2232 @@ +/* + Unix SMB/CIFS implementation. + Main SMB reply routines + Copyright (C) Andrew Tridgell 1992-1998 + Copyright (C) Andrew Bartlett 2001 + Copyright (C) Jeremy Allison 1992-2007. + Copyright (C) Volker Lendecke 2007 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +/* + This file handles most of the reply_ calls that the server + makes to handle specific protocols +*/ + +#include "includes.h" +#include "libsmb/namequery.h" +#include "system/filesys.h" +#include "printing.h" +#include "locking/share_mode_lock.h" +#include "smbd/smbd.h" +#include "smbd/globals.h" +#include "smbd/smbXsrv_open.h" +#include "fake_file.h" +#include "rpc_client/rpc_client.h" +#include "../librpc/gen_ndr/ndr_spoolss_c.h" +#include "rpc_client/cli_spoolss.h" +#include "rpc_client/init_spoolss.h" +#include "rpc_server/rpc_ncacn_np.h" +#include "libcli/security/security.h" +#include "libsmb/nmblib.h" +#include "auth.h" +#include "smbprofile.h" +#include "../lib/tsocket/tsocket.h" +#include "lib/util/tevent_ntstatus.h" +#include "libcli/smb/smb_signing.h" +#include "lib/util/sys_rw_data.h" +#include "librpc/gen_ndr/open_files.h" +#include "libcli/smb/smb2_posix.h" +#include "lib/util/string_wrappers.h" +#include "source3/printing/rap_jobid.h" +#include "source3/lib/substitute.h" + +/**************************************************************************** + Ensure we check the path in *exactly* the same way as W2K for a findfirst/findnext + path or anything including wildcards. + We're assuming here that '/' is not the second byte in any multibyte char + set (a safe assumption). '\\' *may* be the second byte in a multibyte char + set. +****************************************************************************/ + +/* Custom version for processing POSIX paths. */ +#define IS_PATH_SEP(c,posix_only) ((c) == '/' || (!(posix_only) && (c) == '\\')) + +static NTSTATUS check_path_syntax_internal(char *path, + bool posix_path) +{ + char *d = path; + const char *s = path; + NTSTATUS ret = NT_STATUS_OK; + bool start_of_name_component = True; + bool stream_started = false; + bool last_component_contains_wcard = false; + + while (*s) { + if (stream_started) { + switch (*s) { + case '/': + case '\\': + return NT_STATUS_OBJECT_NAME_INVALID; + case ':': + if (s[1] == '\0') { + return NT_STATUS_OBJECT_NAME_INVALID; + } + if (strchr_m(&s[1], ':')) { + return NT_STATUS_OBJECT_NAME_INVALID; + } + break; + } + } + + if ((*s == ':') && !posix_path && !stream_started) { + if (last_component_contains_wcard) { + return NT_STATUS_OBJECT_NAME_INVALID; + } + /* Stream names allow more characters than file names. + We're overloading posix_path here to allow a wider + range of characters. If stream_started is true this + is still a Windows path even if posix_path is true. + JRA. + */ + stream_started = true; + start_of_name_component = false; + posix_path = true; + + if (s[1] == '\0') { + return NT_STATUS_OBJECT_NAME_INVALID; + } + } + + if (!stream_started && IS_PATH_SEP(*s,posix_path)) { + /* + * Safe to assume is not the second part of a mb char + * as this is handled below. + */ + /* Eat multiple '/' or '\\' */ + while (IS_PATH_SEP(*s,posix_path)) { + s++; + } + if ((d != path) && (*s != '\0')) { + /* We only care about non-leading or trailing '/' or '\\' */ + *d++ = '/'; + } + + start_of_name_component = True; + /* New component. */ + last_component_contains_wcard = false; + continue; + } + + if (start_of_name_component) { + if ((s[0] == '.') && (s[1] == '.') && (IS_PATH_SEP(s[2],posix_path) || s[2] == '\0')) { + /* Uh oh - "/../" or "\\..\\" or "/..\0" or "\\..\0" ! */ + + /* + * No mb char starts with '.' so we're safe checking the directory separator here. + */ + + /* If we just added a '/' - delete it */ + if ((d > path) && (*(d-1) == '/')) { + *(d-1) = '\0'; + d--; + } + + /* Are we at the start ? Can't go back further if so. */ + if (d <= path) { + ret = NT_STATUS_OBJECT_PATH_SYNTAX_BAD; + break; + } + /* Go back one level... */ + /* We know this is safe as '/' cannot be part of a mb sequence. */ + /* NOTE - if this assumption is invalid we are not in good shape... */ + /* Decrement d first as d points to the *next* char to write into. */ + for (d--; d > path; d--) { + if (*d == '/') + break; + } + s += 2; /* Else go past the .. */ + /* We're still at the start of a name component, just the previous one. */ + continue; + + } else if ((s[0] == '.') && ((s[1] == '\0') || IS_PATH_SEP(s[1],posix_path))) { + if (posix_path) { + /* Eat the '.' */ + s++; + continue; + } + } + + } + + if (!(*s & 0x80)) { + if (!posix_path) { + if (*s <= 0x1f || *s == '|') { + return NT_STATUS_OBJECT_NAME_INVALID; + } + switch (*s) { + case '*': + case '?': + case '<': + case '>': + case '"': + last_component_contains_wcard = true; + break; + default: + break; + } + } + *d++ = *s++; + } else { + size_t siz; + /* Get the size of the next MB character. */ + next_codepoint(s,&siz); + switch(siz) { + case 5: + *d++ = *s++; + FALL_THROUGH; + case 4: + *d++ = *s++; + FALL_THROUGH; + case 3: + *d++ = *s++; + FALL_THROUGH; + case 2: + *d++ = *s++; + FALL_THROUGH; + case 1: + *d++ = *s++; + break; + default: + DEBUG(0,("check_path_syntax_internal: character length assumptions invalid !\n")); + *d = '\0'; + return NT_STATUS_INVALID_PARAMETER; + } + } + start_of_name_component = False; + } + + *d = '\0'; + + return ret; +} + +/**************************************************************************** + Ensure we check the path in *exactly* the same way as W2K for regular pathnames. + No wildcards allowed. +****************************************************************************/ + +NTSTATUS check_path_syntax(char *path) +{ + return check_path_syntax_internal(path, false); +} + +/**************************************************************************** + Check the path for a POSIX client. + We're assuming here that '/' is not the second byte in any multibyte char + set (a safe assumption). +****************************************************************************/ + +NTSTATUS check_path_syntax_posix(char *path) +{ + return check_path_syntax_internal(path, true); +} + +/**************************************************************************** + Check the path for an SMB2 DFS path. + SMB2 DFS paths look like hostname\share (followed by a possible \extrapath. + Path returned from here must look like: + hostname/share (followed by a possible /extrapath). +****************************************************************************/ + +static NTSTATUS check_path_syntax_smb2_msdfs(char *path) +{ + char *share = NULL; + char *remaining_path = NULL; + /* No SMB2 names can start with '\\' */ + if (path[0] == '\\') { + return NT_STATUS_OBJECT_NAME_INVALID; + } + /* + * smbclient libraries sometimes set the DFS flag and send + * local pathnames. Cope with this by just calling + * check_path_syntax() on the whole path if it doesn't + * look like a DFS path, similar to what parse_dfs_path() does. + */ + /* servername should be at path[0] */ + share = strchr(path, '\\'); + if (share == NULL) { + return check_path_syntax(path); + } + *share++ = '/'; + remaining_path = strchr(share, '\\'); + if (remaining_path == NULL) { + /* Only hostname\share. We're done. */ + return NT_STATUS_OK; + } + *remaining_path++ = '/'; + return check_path_syntax(remaining_path); +} + +NTSTATUS check_path_syntax_smb2(char *path, bool dfs_path) +{ + if (dfs_path) { + return check_path_syntax_smb2_msdfs(path); + } else { + return check_path_syntax(path); + } +} + +/**************************************************************************** + Pull a string and check the path allowing a wildcard - provide for error return. + Passes in posix flag. +****************************************************************************/ + +static size_t srvstr_get_path_internal(TALLOC_CTX *ctx, + const char *base_ptr, + uint16_t smb_flags2, + char **pp_dest, + const char *src, + size_t src_len, + int flags, + bool posix_pathnames, + NTSTATUS *err) +{ + size_t ret; + char *dst = NULL; + + *pp_dest = NULL; + + ret = srvstr_pull_talloc(ctx, base_ptr, smb_flags2, pp_dest, src, + src_len, flags); + + if (!*pp_dest) { + *err = NT_STATUS_INVALID_PARAMETER; + return ret; + } + + dst = *pp_dest; + + if (smb_flags2 & FLAGS2_DFS_PATHNAMES) { + /* + * A valid DFS path looks either like + * /server/share + * \server\share + * (there may be more components after). + * Either way it must have at least two separators. + * + * Ensure we end up as /server/share + * so we don't need to special case + * separator characters elsewhere in + * the code. + */ + char *server = NULL; + char *share = NULL; + char *remaining_path = NULL; + char path_sep = 0; + char *p = NULL; + + if (posix_pathnames && (dst[0] == '/')) { + path_sep = dst[0]; + } else if (dst[0] == '\\') { + path_sep = dst[0]; + } + + if (path_sep == 0) { + goto local_path; + } + /* + * May be a DFS path. + * We need some heuristics here, + * as clients differ on what constitutes + * a well-formed DFS path. If the path + * appears malformed, just fall back to + * processing as a local path. + */ + server = dst; + + /* + * Cosmetic fix for Linux-only DFS clients. + * The Linux kernel SMB1 client has a bug - it sends + * DFS pathnames as: + * + * \\server\share\path + * + * Causing us to mis-parse server,share,remaining_path here + * and jump into 'goto local_path' at 'share\path' instead + * of 'path'. + * + * This doesn't cause an error as the limits on share names + * are similar to those on pathnames. + * + * parse_dfs_path() which we call before filename parsing + * copes with this by calling trim_char on the leading '\' + * characters before processing. + * Do the same here so logging of pathnames looks better. + */ + if (server[1] == path_sep) { + trim_char(&server[1], path_sep, '\0'); + } + + /* + * Look to see if we also have /share following. + */ + share = strchr(server+1, path_sep); + if (share == NULL) { + goto local_path; + } + /* + * Ensure the server name does not contain + * any possible path components by converting + * them to _'s. + */ + for (p = server + 1; p < share; p++) { + if (*p == '/' || *p == '\\') { + *p = '_'; + } + } + /* + * It's a well formed DFS path with + * at least server and share components. + * Replace the slashes with '/' and + * pass the remainder to local_path. + */ + *server = '/'; + *share = '/'; + /* + * Skip past share so we don't pass the + * sharename into check_path_syntax(). + */ + remaining_path = strchr(share+1, path_sep); + if (remaining_path == NULL) { + /* + * Ensure the share name does not contain + * any possible path components by converting + * them to _'s. + */ + for (p = share + 1; *p; p++) { + if (*p == '/' || *p == '\\') { + *p = '_'; + } + } + /* + * If no remaining path this was + * a bare /server/share path. Just return. + */ + *err = NT_STATUS_OK; + return ret; + } + /* + * Ensure the share name does not contain + * any possible path components by converting + * them to _'s. + */ + for (p = share + 1; p < remaining_path; p++) { + if (*p == '/' || *p == '\\') { + *p = '_'; + } + } + *remaining_path = '/'; + dst = remaining_path + 1; + /* dst now points at any following components. */ + } + + local_path: + + if (posix_pathnames) { + *err = check_path_syntax_posix(dst); + } else { + *err = check_path_syntax(dst); + } + + return ret; +} + +/**************************************************************************** + Pull a string and check the path - provide for error return. +****************************************************************************/ + +size_t srvstr_get_path(TALLOC_CTX *ctx, + const char *base_ptr, + uint16_t smb_flags2, + char **pp_dest, + const char *src, + size_t src_len, + int flags, + NTSTATUS *err) +{ + return srvstr_get_path_internal(ctx, + base_ptr, + smb_flags2, + pp_dest, + src, + src_len, + flags, + false, + err); +} + +/**************************************************************************** + Pull a string and check the path - provide for error return. + posix_pathnames version. +****************************************************************************/ + +size_t srvstr_get_path_posix(TALLOC_CTX *ctx, + const char *base_ptr, + uint16_t smb_flags2, + char **pp_dest, + const char *src, + size_t src_len, + int flags, + NTSTATUS *err) +{ + return srvstr_get_path_internal(ctx, + base_ptr, + smb_flags2, + pp_dest, + src, + src_len, + flags, + true, + err); +} + + +size_t srvstr_get_path_req(TALLOC_CTX *mem_ctx, struct smb_request *req, + char **pp_dest, const char *src, int flags, + NTSTATUS *err) +{ + ssize_t bufrem = smbreq_bufrem(req, src); + + if (bufrem == 0) { + *err = NT_STATUS_INVALID_PARAMETER; + return 0; + } + + if (req->posix_pathnames) { + return srvstr_get_path_internal(mem_ctx, + (const char *)req->inbuf, + req->flags2, + pp_dest, + src, + bufrem, + flags, + true, + err); + } else { + return srvstr_get_path_internal(mem_ctx, + (const char *)req->inbuf, + req->flags2, + pp_dest, + src, + bufrem, + flags, + false, + err); + } +} + +/** + * pull a string from the smb_buf part of a packet. In this case the + * string can either be null terminated or it can be terminated by the + * end of the smbbuf area + */ +size_t srvstr_pull_req_talloc(TALLOC_CTX *ctx, struct smb_request *req, + char **dest, const uint8_t *src, int flags) +{ + ssize_t bufrem = smbreq_bufrem(req, src); + + if (bufrem == 0) { + *dest = NULL; + return 0; + } + + return pull_string_talloc(ctx, req->inbuf, req->flags2, dest, src, + bufrem, flags); +} + +/**************************************************************************** + Check if we have a correct fsp pointing to a file. Basic check for open fsp. +****************************************************************************/ + +bool check_fsp_open(connection_struct *conn, struct smb_request *req, + files_struct *fsp) +{ + if ((fsp == NULL) || (conn == NULL)) { + reply_nterror(req, NT_STATUS_INVALID_HANDLE); + return False; + } + if ((conn != fsp->conn) || (req->vuid != fsp->vuid)) { + reply_nterror(req, NT_STATUS_INVALID_HANDLE); + return False; + } + return True; +} + +/**************************************************************************** + Check if we have a correct fsp pointing to a file. +****************************************************************************/ + +bool check_fsp(connection_struct *conn, struct smb_request *req, + files_struct *fsp) +{ + if (!check_fsp_open(conn, req, fsp)) { + return False; + } + if (fsp->fsp_flags.is_directory) { + reply_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST); + return False; + } + if (fsp_get_pathref_fd(fsp) == -1) { + reply_nterror(req, NT_STATUS_ACCESS_DENIED); + return False; + } + fsp->num_smb_operations++; + return True; +} + +/**************************************************************************** + Check if we have a correct fsp pointing to a quota fake file. Replacement for + the CHECK_NTQUOTA_HANDLE_OK macro. +****************************************************************************/ + +bool check_fsp_ntquota_handle(connection_struct *conn, struct smb_request *req, + files_struct *fsp) +{ + if (!check_fsp_open(conn, req, fsp)) { + return false; + } + + if (fsp->fsp_flags.is_directory) { + return false; + } + + if (fsp->fake_file_handle == NULL) { + return false; + } + + if (fsp->fake_file_handle->type != FAKE_FILE_TYPE_QUOTA) { + return false; + } + + if (fsp->fake_file_handle->private_data == NULL) { + return false; + } + + return true; +} + +/**************************************************************************** + Return the port number we've bound to on a socket. +****************************************************************************/ + +static int get_socket_port(int fd) +{ + struct samba_sockaddr saddr = { + .sa_socklen = sizeof(struct sockaddr_storage), + }; + + if (fd == -1) { + return -1; + } + + if (getsockname(fd, &saddr.u.sa, &saddr.sa_socklen) < 0) { + int level = (errno == ENOTCONN) ? 2 : 0; + DEBUG(level, ("getsockname failed. Error was %s\n", + strerror(errno))); + return -1; + } + +#if defined(HAVE_IPV6) + if (saddr.u.sa.sa_family == AF_INET6) { + return ntohs(saddr.u.in6.sin6_port); + } +#endif + if (saddr.u.sa.sa_family == AF_INET) { + return ntohs(saddr.u.in.sin_port); + } + return -1; +} + +static bool netbios_session_retarget(struct smbXsrv_connection *xconn, + const char *name, int name_type) +{ + char *trim_name; + char *trim_name_type; + const char *retarget_parm; + char *retarget; + char *p; + int retarget_type = 0x20; + int retarget_port = NBT_SMB_PORT; + struct sockaddr_storage retarget_addr; + struct sockaddr_in *in_addr; + bool ret = false; + uint8_t outbuf[10]; + + if (get_socket_port(xconn->transport.sock) != NBT_SMB_PORT) { + return false; + } + + trim_name = talloc_strdup(talloc_tos(), name); + if (trim_name == NULL) { + goto fail; + } + trim_char(trim_name, ' ', ' '); + + trim_name_type = talloc_asprintf(trim_name, "%s#%2.2x", trim_name, + name_type); + if (trim_name_type == NULL) { + goto fail; + } + + retarget_parm = lp_parm_const_string(-1, "netbios retarget", + trim_name_type, NULL); + if (retarget_parm == NULL) { + retarget_parm = lp_parm_const_string(-1, "netbios retarget", + trim_name, NULL); + } + if (retarget_parm == NULL) { + goto fail; + } + + retarget = talloc_strdup(trim_name, retarget_parm); + if (retarget == NULL) { + goto fail; + } + + DEBUG(10, ("retargeting %s to %s\n", trim_name_type, retarget)); + + p = strchr(retarget, ':'); + if (p != NULL) { + *p++ = '\0'; + retarget_port = atoi(p); + } + + p = strchr_m(retarget, '#'); + if (p != NULL) { + *p++ = '\0'; + if (sscanf(p, "%x", &retarget_type) != 1) { + goto fail; + } + } + + ret = resolve_name(retarget, &retarget_addr, retarget_type, false); + if (!ret) { + DEBUG(10, ("could not resolve %s\n", retarget)); + goto fail; + } + + if (retarget_addr.ss_family != AF_INET) { + DEBUG(10, ("Retarget target not an IPv4 addr\n")); + goto fail; + } + + in_addr = (struct sockaddr_in *)(void *)&retarget_addr; + + _smb_setlen(outbuf, 6); + SCVAL(outbuf, 0, 0x84); + *(uint32_t *)(outbuf+4) = in_addr->sin_addr.s_addr; + *(uint16_t *)(outbuf+8) = htons(retarget_port); + + if (!smb1_srv_send(xconn, (char *)outbuf, false, 0, false, + NULL)) { + exit_server_cleanly("netbios_session_retarget: smb1_srv_send " + "failed."); + } + + ret = true; + fail: + TALLOC_FREE(trim_name); + return ret; +} + +static void reply_called_name_not_present(char *outbuf) +{ + smb_setlen(outbuf, 1); + SCVAL(outbuf, 0, 0x83); + SCVAL(outbuf, 4, 0x82); +} + +/**************************************************************************** + Reply to a (netbios-level) special message. +****************************************************************************/ + +void reply_special(struct smbXsrv_connection *xconn, char *inbuf, size_t inbuf_size) +{ + struct smbd_server_connection *sconn = xconn->client->sconn; + int msg_type = CVAL(inbuf,0); + int msg_flags = CVAL(inbuf,1); + /* + * We only really use 4 bytes of the outbuf, but for the smb_setlen + * calculation & friends (smb1_srv_send uses that) we need the full smb + * header. + */ + char outbuf[smb_size]; + + memset(outbuf, '\0', sizeof(outbuf)); + + smb_setlen(outbuf,0); + + switch (msg_type) { + case NBSSrequest: /* session request */ + { + /* inbuf_size is guarenteed to be at least 4. */ + fstring name1,name2; + int name_type1, name_type2; + int name_len1, name_len2; + + *name1 = *name2 = 0; + + if (xconn->transport.nbt.got_session) { + exit_server_cleanly("multiple session request not permitted"); + } + + SCVAL(outbuf,0,NBSSpositive); + SCVAL(outbuf,3,0); + + /* inbuf_size is guaranteed to be at least 4. */ + name_len1 = name_len((unsigned char *)(inbuf+4),inbuf_size - 4); + if (name_len1 <= 0 || name_len1 > inbuf_size - 4) { + DEBUG(0,("Invalid name length in session request\n")); + reply_called_name_not_present(outbuf); + break; + } + name_len2 = name_len((unsigned char *)(inbuf+4+name_len1),inbuf_size - 4 - name_len1); + if (name_len2 <= 0 || name_len2 > inbuf_size - 4 - name_len1) { + DEBUG(0,("Invalid name length in session request\n")); + reply_called_name_not_present(outbuf); + break; + } + + name_type1 = name_extract((unsigned char *)inbuf, + inbuf_size,(unsigned int)4,name1); + name_type2 = name_extract((unsigned char *)inbuf, + inbuf_size,(unsigned int)(4 + name_len1),name2); + + if (name_type1 == -1 || name_type2 == -1) { + DEBUG(0,("Invalid name type in session request\n")); + reply_called_name_not_present(outbuf); + break; + } + + DEBUG(2,("netbios connect: name1=%s0x%x name2=%s0x%x\n", + name1, name_type1, name2, name_type2)); + + if (netbios_session_retarget(xconn, name1, name_type1)) { + exit_server_cleanly("retargeted client"); + } + + /* + * Windows NT/2k uses "*SMBSERVER" and XP uses + * "*SMBSERV" arrggg!!! + */ + if (strequal(name1, "*SMBSERVER ") + || strequal(name1, "*SMBSERV ")) { + char *raddr; + + raddr = tsocket_address_inet_addr_string(sconn->remote_address, + talloc_tos()); + if (raddr == NULL) { + exit_server_cleanly("could not allocate raddr"); + } + + fstrcpy(name1, raddr); + } + + set_local_machine_name(name1, True); + set_remote_machine_name(name2, True); + + if (is_ipaddress(sconn->remote_hostname)) { + char *p = discard_const_p(char, sconn->remote_hostname); + + talloc_free(p); + + sconn->remote_hostname = talloc_strdup(sconn, + get_remote_machine_name()); + if (sconn->remote_hostname == NULL) { + exit_server_cleanly("could not copy remote name"); + } + xconn->remote_hostname = sconn->remote_hostname; + } + + DEBUG(2,("netbios connect: local=%s remote=%s, name type = %x\n", + get_local_machine_name(), get_remote_machine_name(), + name_type2)); + + if (name_type2 == 'R') { + /* We are being asked for a pathworks session --- + no thanks! */ + reply_called_name_not_present(outbuf); + break; + } + + reload_services(sconn, conn_snum_used, true); + reopen_logs(); + + xconn->transport.nbt.got_session = true; + break; + } + + case 0x89: /* session keepalive request + (some old clients produce this?) */ + SCVAL(outbuf,0,NBSSkeepalive); + SCVAL(outbuf,3,0); + break; + + case NBSSpositive: /* positive session response */ + case NBSSnegative: /* negative session response */ + case NBSSretarget: /* retarget session response */ + DEBUG(0,("Unexpected session response\n")); + break; + + case NBSSkeepalive: /* session keepalive */ + default: + return; + } + + DEBUG(5,("init msg_type=0x%x msg_flags=0x%x\n", + msg_type, msg_flags)); + + if (!smb1_srv_send(xconn, outbuf, false, 0, false, NULL)) { + exit_server_cleanly("reply_special: smb1_srv_send failed."); + } + + if (CVAL(outbuf, 0) != 0x82) { + exit_server_cleanly("invalid netbios session"); + } + return; +} + +/******************************************************************* + * unlink a file with all relevant access checks + *******************************************************************/ + +NTSTATUS unlink_internals(connection_struct *conn, + struct smb_request *req, + uint32_t dirtype, + struct files_struct *dirfsp, + struct smb_filename *smb_fname) +{ + uint32_t fattr; + files_struct *fsp; + uint32_t dirtype_orig = dirtype; + NTSTATUS status; + int ret; + struct smb2_create_blobs *posx = NULL; + + if (dirtype == 0) { + dirtype = FILE_ATTRIBUTE_NORMAL; + } + + DBG_DEBUG("%s, dirtype = %d\n", + smb_fname_str_dbg(smb_fname), + dirtype); + + if (!CAN_WRITE(conn)) { + return NT_STATUS_MEDIA_WRITE_PROTECTED; + } + + ret = vfs_stat(conn, smb_fname); + if (ret != 0) { + return map_nt_error_from_unix(errno); + } + + fattr = fdos_mode(smb_fname->fsp); + + if (dirtype & FILE_ATTRIBUTE_NORMAL) { + dirtype = FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY; + } + + dirtype &= (FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM); + if (!dirtype) { + return NT_STATUS_NO_SUCH_FILE; + } + + if (!dir_check_ftype(fattr, dirtype)) { + if (fattr & FILE_ATTRIBUTE_DIRECTORY) { + return NT_STATUS_FILE_IS_A_DIRECTORY; + } + return NT_STATUS_NO_SUCH_FILE; + } + + if (dirtype_orig & 0x8000) { + /* These will never be set for POSIX. */ + return NT_STATUS_NO_SUCH_FILE; + } + +#if 0 + if ((fattr & dirtype) & FILE_ATTRIBUTE_DIRECTORY) { + return NT_STATUS_FILE_IS_A_DIRECTORY; + } + + if ((fattr & ~dirtype) & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM)) { + return NT_STATUS_NO_SUCH_FILE; + } + + if (dirtype & 0xFF00) { + /* These will never be set for POSIX. */ + return NT_STATUS_NO_SUCH_FILE; + } + + dirtype &= 0xFF; + if (!dirtype) { + return NT_STATUS_NO_SUCH_FILE; + } + + /* Can't delete a directory. */ + if (fattr & FILE_ATTRIBUTE_DIRECTORY) { + return NT_STATUS_FILE_IS_A_DIRECTORY; + } +#endif + +#if 0 /* JRATEST */ + else if (dirtype & FILE_ATTRIBUTE_DIRECTORY) /* Asked for a directory and it isn't. */ + return NT_STATUS_OBJECT_NAME_INVALID; +#endif /* JRATEST */ + + if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) { + 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; + } + } + + /* On open checks the open itself will check the share mode, so + don't do it here as we'll get it wrong. */ + + status = SMB_VFS_CREATE_FILE + (conn, /* conn */ + req, /* req */ + dirfsp, /* dirfsp */ + smb_fname, /* fname */ + DELETE_ACCESS, /* access_mask */ + FILE_SHARE_NONE, /* share_access */ + FILE_OPEN, /* create_disposition*/ + FILE_NON_DIRECTORY_FILE, /* create_options */ + FILE_ATTRIBUTE_NORMAL, /* file_attributes */ + 0, /* oplock_request */ + NULL, /* lease */ + 0, /* allocation_size */ + 0, /* private_flags */ + NULL, /* sd */ + NULL, /* ea_list */ + &fsp, /* result */ + NULL, /* pinfo */ + posx, /* in_context_blobs */ + NULL); /* out_context_blobs */ + + TALLOC_FREE(posx); + + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("SMB_VFS_CREATEFILE failed: %s\n", + nt_errstr(status)); + return status; + } + + status = can_set_delete_on_close(fsp, fattr); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("can_set_delete_on_close for file %s - " + "(%s)\n", + smb_fname_str_dbg(smb_fname), + nt_errstr(status)); + close_file_free(req, &fsp, NORMAL_CLOSE); + return status; + } + + /* The set is across all open files on this dev/inode pair. */ + if (!set_delete_on_close(fsp, True, + conn->session_info->security_token, + conn->session_info->unix_token)) { + close_file_free(req, &fsp, NORMAL_CLOSE); + return NT_STATUS_ACCESS_DENIED; + } + + return close_file_free(req, &fsp, NORMAL_CLOSE); +} + +/**************************************************************************** + Fake (read/write) sendfile. Returns -1 on read or write fail. +****************************************************************************/ + +ssize_t fake_sendfile(struct smbXsrv_connection *xconn, files_struct *fsp, + off_t startpos, size_t nread) +{ + size_t bufsize; + size_t tosend = nread; + char *buf; + + if (nread == 0) { + return 0; + } + + bufsize = MIN(nread, 65536); + + if (!(buf = SMB_MALLOC_ARRAY(char, bufsize))) { + return -1; + } + + while (tosend > 0) { + ssize_t ret; + size_t cur_read; + + cur_read = MIN(tosend, bufsize); + ret = read_file(fsp,buf,startpos,cur_read); + if (ret == -1) { + SAFE_FREE(buf); + return -1; + } + + /* If we had a short read, fill with zeros. */ + if (ret < cur_read) { + memset(buf + ret, '\0', cur_read - ret); + } + + ret = write_data(xconn->transport.sock, buf, cur_read); + if (ret != cur_read) { + int saved_errno = errno; + /* + * Try and give an error message saying what + * client failed. + */ + DEBUG(0, ("write_data failed for client %s. " + "Error %s\n", + smbXsrv_connection_dbg(xconn), + strerror(saved_errno))); + SAFE_FREE(buf); + errno = saved_errno; + return -1; + } + tosend -= cur_read; + startpos += cur_read; + } + + SAFE_FREE(buf); + return (ssize_t)nread; +} + +/**************************************************************************** + Deal with the case of sendfile reading less bytes from the file than + requested. Fill with zeros (all we can do). Returns 0 on success +****************************************************************************/ + +ssize_t sendfile_short_send(struct smbXsrv_connection *xconn, + files_struct *fsp, + ssize_t nread, + size_t headersize, + size_t smb_maxcnt) +{ +#define SHORT_SEND_BUFSIZE 1024 + if (nread < headersize) { + DEBUG(0,("sendfile_short_send: sendfile failed to send " + "header for file %s (%s). Terminating\n", + fsp_str_dbg(fsp), strerror(errno))); + return -1; + } + + nread -= headersize; + + if (nread < smb_maxcnt) { + char buf[SHORT_SEND_BUFSIZE] = { 0 }; + + DEBUG(0,("sendfile_short_send: filling truncated file %s " + "with zeros !\n", fsp_str_dbg(fsp))); + + while (nread < smb_maxcnt) { + /* + * We asked for the real file size and told sendfile + * to not go beyond the end of the file. But it can + * happen that in between our fstat call and the + * sendfile call the file was truncated. This is very + * bad because we have already announced the larger + * number of bytes to the client. + * + * The best we can do now is to send 0-bytes, just as + * a read from a hole in a sparse file would do. + * + * This should happen rarely enough that I don't care + * about efficiency here :-) + */ + size_t to_write; + ssize_t ret; + + to_write = MIN(SHORT_SEND_BUFSIZE, smb_maxcnt - nread); + ret = write_data(xconn->transport.sock, buf, to_write); + if (ret != to_write) { + int saved_errno = errno; + /* + * Try and give an error message saying what + * client failed. + */ + DEBUG(0, ("write_data failed for client %s. " + "Error %s\n", + smbXsrv_connection_dbg(xconn), + strerror(saved_errno))); + errno = saved_errno; + return -1; + } + nread += to_write; + } + } + + return 0; +} + +/******************************************************************* + Check if a user is allowed to rename a file. +********************************************************************/ + +static NTSTATUS can_rename(connection_struct *conn, files_struct *fsp, + uint16_t dirtype) +{ + if (!CAN_WRITE(conn)) { + return NT_STATUS_MEDIA_WRITE_PROTECTED; + } + + if ((dirtype & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) != + (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) { + /* Only bother to read the DOS attribute if we might deny the + rename on the grounds of attribute mismatch. */ + uint32_t fmode = fdos_mode(fsp); + if ((fmode & ~dirtype) & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) { + return NT_STATUS_NO_SUCH_FILE; + } + } + + if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) { + if (fsp->posix_flags & FSP_POSIX_FLAGS_RENAME) { + return NT_STATUS_OK; + } + + /* If no pathnames are open below this + directory, allow the rename. */ + + if (lp_strict_rename(SNUM(conn))) { + /* + * Strict rename, check open file db. + */ + if (have_file_open_below(fsp->conn, fsp->fsp_name)) { + return NT_STATUS_ACCESS_DENIED; + } + } else if (file_find_subpath(fsp)) { + /* + * No strict rename, just look in local process. + */ + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; + } + + if (fsp->access_mask & (DELETE_ACCESS|FILE_WRITE_ATTRIBUTES)) { + return NT_STATUS_OK; + } + + return NT_STATUS_ACCESS_DENIED; +} + +/**************************************************************************** + Ensure open files have their names updated. Updated to notify other smbd's + asynchronously. +****************************************************************************/ + +static void rename_open_files(connection_struct *conn, + struct share_mode_lock *lck, + struct file_id id, + uint32_t orig_name_hash, + const struct smb_filename *smb_fname_dst) +{ + files_struct *fsp; + bool did_rename = False; + NTSTATUS status; + uint32_t new_name_hash = 0; + + for(fsp = file_find_di_first(conn->sconn, id, false); fsp; + fsp = file_find_di_next(fsp, false)) { + SMB_STRUCT_STAT fsp_orig_sbuf; + struct file_id_buf idbuf; + /* fsp_name is a relative path under the fsp. To change this for other + sharepaths we need to manipulate relative paths. */ + /* TODO - create the absolute path and manipulate the newname + relative to the sharepath. */ + if (!strequal(fsp->conn->connectpath, conn->connectpath)) { + continue; + } + if (fsp->name_hash != orig_name_hash) { + continue; + } + DBG_DEBUG("renaming file %s " + "(file_id %s) from %s -> %s\n", + fsp_fnum_dbg(fsp), + file_id_str_buf(fsp->file_id, &idbuf), + fsp_str_dbg(fsp), + smb_fname_str_dbg(smb_fname_dst)); + + /* + * The incoming smb_fname_dst here has an + * invalid stat struct (it must not have + * existed for the rename to succeed). + * Preserve the existing stat from the + * open fsp after fsp_set_smb_fname() + * overwrites with the invalid stat. + * + * We will do an fstat before returning + * any of this metadata to the client anyway. + */ + fsp_orig_sbuf = fsp->fsp_name->st; + status = fsp_set_smb_fname(fsp, smb_fname_dst); + if (NT_STATUS_IS_OK(status)) { + did_rename = True; + new_name_hash = fsp->name_hash; + /* Restore existing stat. */ + fsp->fsp_name->st = fsp_orig_sbuf; + } + } + + if (!did_rename) { + struct file_id_buf idbuf; + DBG_DEBUG("no open files on file_id %s " + "for %s\n", + file_id_str_buf(id, &idbuf), + smb_fname_str_dbg(smb_fname_dst)); + } + + /* Send messages to all smbd's (not ourself) that the name has changed. */ + rename_share_filename(conn->sconn->msg_ctx, lck, id, conn->connectpath, + orig_name_hash, new_name_hash, + smb_fname_dst); + +} + +/**************************************************************************** + We need to check if the source path is a parent directory of the destination + (ie. a rename of /foo/bar/baz -> /foo/bar/baz/bibble/bobble. If so we must + refuse the rename with a sharing violation. Under UNIX the above call can + *succeed* if /foo/bar/baz is a symlink to another area in the share. We + probably need to check that the client is a Windows one before disallowing + this as a UNIX client (one with UNIX extensions) can know the source is a + symlink and make this decision intelligently. Found by an excellent bug + report from <AndyLiebman@aol.com>. +****************************************************************************/ + +static bool rename_path_prefix_equal(const struct smb_filename *smb_fname_src, + const struct smb_filename *smb_fname_dst) +{ + const char *psrc = smb_fname_src->base_name; + const char *pdst = smb_fname_dst->base_name; + size_t slen; + + if (psrc[0] == '.' && psrc[1] == '/') { + psrc += 2; + } + if (pdst[0] == '.' && pdst[1] == '/') { + pdst += 2; + } + if ((slen = strlen(psrc)) > strlen(pdst)) { + return False; + } + return ((memcmp(psrc, pdst, slen) == 0) && pdst[slen] == '/'); +} + +/* + * Do the notify calls from a rename + */ + +static void notify_rename(connection_struct *conn, bool is_dir, + const struct smb_filename *smb_fname_src, + const struct smb_filename *smb_fname_dst) +{ + char *parent_dir_src = NULL; + char *parent_dir_dst = NULL; + uint32_t mask; + + mask = is_dir ? FILE_NOTIFY_CHANGE_DIR_NAME + : FILE_NOTIFY_CHANGE_FILE_NAME; + + if (!parent_dirname(talloc_tos(), smb_fname_src->base_name, + &parent_dir_src, NULL) || + !parent_dirname(talloc_tos(), smb_fname_dst->base_name, + &parent_dir_dst, NULL)) { + goto out; + } + + if (strcmp(parent_dir_src, parent_dir_dst) == 0) { + notify_fname(conn, NOTIFY_ACTION_OLD_NAME, mask, + smb_fname_src->base_name); + notify_fname(conn, NOTIFY_ACTION_NEW_NAME, mask, + smb_fname_dst->base_name); + } + else { + notify_fname(conn, NOTIFY_ACTION_REMOVED, mask, + smb_fname_src->base_name); + notify_fname(conn, NOTIFY_ACTION_ADDED, mask, + smb_fname_dst->base_name); + } + + /* this is a strange one. w2k3 gives an additional event for + CHANGE_ATTRIBUTES and CHANGE_CREATION on the new file when renaming + files, but not directories */ + if (!is_dir) { + notify_fname(conn, NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_ATTRIBUTES + |FILE_NOTIFY_CHANGE_CREATION, + smb_fname_dst->base_name); + } + out: + TALLOC_FREE(parent_dir_src); + TALLOC_FREE(parent_dir_dst); +} + +/**************************************************************************** + Returns an error if the parent directory for a filename is open in an + incompatible way. +****************************************************************************/ + +static NTSTATUS parent_dirname_compatible_open(connection_struct *conn, + const struct smb_filename *smb_fname_dst_in) +{ + struct smb_filename *smb_fname_parent = NULL; + struct file_id id; + files_struct *fsp = NULL; + int ret; + NTSTATUS status; + + status = SMB_VFS_PARENT_PATHNAME(conn, + talloc_tos(), + smb_fname_dst_in, + &smb_fname_parent, + NULL); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ret = vfs_stat(conn, smb_fname_parent); + if (ret == -1) { + return map_nt_error_from_unix(errno); + } + + /* + * We're only checking on this smbd here, mostly good + * enough.. and will pass tests. + */ + + id = vfs_file_id_from_sbuf(conn, &smb_fname_parent->st); + for (fsp = file_find_di_first(conn->sconn, id, true); fsp; + fsp = file_find_di_next(fsp, true)) { + if (fsp->access_mask & DELETE_ACCESS) { + return NT_STATUS_SHARING_VIOLATION; + } + } + return NT_STATUS_OK; +} + +/**************************************************************************** + Rename an open file - given an fsp. +****************************************************************************/ + +NTSTATUS rename_internals_fsp(connection_struct *conn, + files_struct *fsp, + struct files_struct *dst_dirfsp, + struct smb_filename *smb_fname_dst_in, + const char *dst_original_lcomp, + uint32_t attrs, + bool replace_if_exists) +{ + TALLOC_CTX *ctx = talloc_tos(); + struct smb_filename *parent_dir_fname_dst = NULL; + struct smb_filename *parent_dir_fname_dst_atname = NULL; + struct smb_filename *parent_dir_fname_src = NULL; + struct smb_filename *parent_dir_fname_src_atname = NULL; + struct smb_filename *smb_fname_dst = NULL; + NTSTATUS status = NT_STATUS_OK; + struct share_mode_lock *lck = NULL; + uint32_t access_mask = SEC_DIR_ADD_FILE; + bool dst_exists, old_is_stream, new_is_stream; + int ret; + bool case_sensitive = (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) ? + true : conn->case_sensitive; + bool case_preserve = (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) ? + true : conn->case_preserve; + + status = parent_dirname_compatible_open(conn, smb_fname_dst_in); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (file_has_open_streams(fsp)) { + return NT_STATUS_ACCESS_DENIED; + } + + /* Make a copy of the dst smb_fname structs */ + + smb_fname_dst = cp_smb_filename(ctx, smb_fname_dst_in); + if (smb_fname_dst == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + /* + * Check for special case with case preserving and not + * case sensitive. If the new last component differs from the original + * last component only by case, then we should allow + * the rename (user is trying to change the case of the + * filename). + */ + if (!case_sensitive && case_preserve && + strequal(fsp->fsp_name->base_name, smb_fname_dst->base_name) && + strequal(fsp->fsp_name->stream_name, smb_fname_dst->stream_name)) { + char *fname_dst_parent = NULL; + const char *fname_dst_lcomp = NULL; + char *orig_lcomp_path = NULL; + char *orig_lcomp_stream = NULL; + bool ok = true; + + /* + * Split off the last component of the processed + * destination name. We will compare this to + * the split components of dst_original_lcomp. + */ + if (!parent_dirname(ctx, + smb_fname_dst->base_name, + &fname_dst_parent, + &fname_dst_lcomp)) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + /* + * The dst_original_lcomp component contains + * the last_component of the path + stream + * name (if a stream exists). + * + * Split off the stream name so we + * can check them separately. + */ + + if (fsp->posix_flags & FSP_POSIX_FLAGS_PATHNAMES) { + /* POSIX - no stream component. */ + orig_lcomp_path = talloc_strdup(ctx, + dst_original_lcomp); + if (orig_lcomp_path == NULL) { + ok = false; + } + } else { + ok = split_stream_filename(ctx, + dst_original_lcomp, + &orig_lcomp_path, + &orig_lcomp_stream); + } + + if (!ok) { + TALLOC_FREE(fname_dst_parent); + status = NT_STATUS_NO_MEMORY; + goto out; + } + + /* If the base names only differ by case, use original. */ + if(!strcsequal(fname_dst_lcomp, orig_lcomp_path)) { + char *tmp; + /* + * Replace the modified last component with the + * original. + */ + if (!ISDOT(fname_dst_parent)) { + tmp = talloc_asprintf(smb_fname_dst, + "%s/%s", + fname_dst_parent, + orig_lcomp_path); + } else { + tmp = talloc_strdup(smb_fname_dst, + orig_lcomp_path); + } + if (tmp == NULL) { + status = NT_STATUS_NO_MEMORY; + TALLOC_FREE(fname_dst_parent); + TALLOC_FREE(orig_lcomp_path); + TALLOC_FREE(orig_lcomp_stream); + goto out; + } + TALLOC_FREE(smb_fname_dst->base_name); + smb_fname_dst->base_name = tmp; + } + + /* If the stream_names only differ by case, use original. */ + if(!strcsequal(smb_fname_dst->stream_name, + orig_lcomp_stream)) { + /* Use the original stream. */ + char *tmp = talloc_strdup(smb_fname_dst, + orig_lcomp_stream); + if (tmp == NULL) { + status = NT_STATUS_NO_MEMORY; + TALLOC_FREE(fname_dst_parent); + TALLOC_FREE(orig_lcomp_path); + TALLOC_FREE(orig_lcomp_stream); + goto out; + } + TALLOC_FREE(smb_fname_dst->stream_name); + smb_fname_dst->stream_name = tmp; + } + TALLOC_FREE(fname_dst_parent); + TALLOC_FREE(orig_lcomp_path); + TALLOC_FREE(orig_lcomp_stream); + } + + /* + * If the src and dest names are identical - including case, + * don't do the rename, just return success. + */ + + if (strcsequal(fsp->fsp_name->base_name, smb_fname_dst->base_name) && + strcsequal(fsp->fsp_name->stream_name, + smb_fname_dst->stream_name)) { + DEBUG(3, ("rename_internals_fsp: identical names in rename %s " + "- returning success\n", + smb_fname_str_dbg(smb_fname_dst))); + status = NT_STATUS_OK; + goto out; + } + + old_is_stream = is_ntfs_stream_smb_fname(fsp->fsp_name); + new_is_stream = is_ntfs_stream_smb_fname(smb_fname_dst); + + /* Return the correct error code if both names aren't streams. */ + if (!old_is_stream && new_is_stream) { + status = NT_STATUS_OBJECT_NAME_INVALID; + goto out; + } + + if (old_is_stream && !new_is_stream) { + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + dst_exists = vfs_stat(conn, smb_fname_dst) == 0; + + if(!replace_if_exists && dst_exists) { + DEBUG(3, ("rename_internals_fsp: dest exists doing rename " + "%s -> %s\n", smb_fname_str_dbg(fsp->fsp_name), + smb_fname_str_dbg(smb_fname_dst))); + status = NT_STATUS_OBJECT_NAME_COLLISION; + goto out; + } + + /* + * Drop the pathref fsp on the destination otherwise we trip upon in in + * the below check for open files check. + */ + if (smb_fname_dst_in->fsp != NULL) { + fd_close(smb_fname_dst_in->fsp); + file_free(NULL, smb_fname_dst_in->fsp); + SMB_ASSERT(smb_fname_dst_in->fsp == NULL); + } + + if (dst_exists) { + struct file_id fileid = vfs_file_id_from_sbuf(conn, + &smb_fname_dst->st); + files_struct *dst_fsp = file_find_di_first(conn->sconn, + fileid, true); + /* The file can be open when renaming a stream */ + if (dst_fsp && !new_is_stream) { + DEBUG(3, ("rename_internals_fsp: Target file open\n")); + status = NT_STATUS_ACCESS_DENIED; + goto out; + } + } + + /* Ensure we have a valid stat struct for the source. */ + status = vfs_stat_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + status = can_rename(conn, fsp, attrs); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("rename_internals_fsp: Error %s rename %s -> %s\n", + nt_errstr(status), smb_fname_str_dbg(fsp->fsp_name), + smb_fname_str_dbg(smb_fname_dst))); + if (NT_STATUS_EQUAL(status,NT_STATUS_SHARING_VIOLATION)) + status = NT_STATUS_ACCESS_DENIED; + goto out; + } + + if (rename_path_prefix_equal(fsp->fsp_name, smb_fname_dst)) { + status = NT_STATUS_ACCESS_DENIED; + goto out; + } + + /* Do we have rights to move into the destination ? */ + if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) { + /* We're moving a directory. */ + access_mask = SEC_DIR_ADD_SUBDIR; + } + + /* + * Get a pathref on the destination parent directory, so + * we can call check_parent_access_fsp(). + */ + status = parent_pathref(ctx, + conn->cwd_fsp, + smb_fname_dst, + &parent_dir_fname_dst, + &parent_dir_fname_dst_atname); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + status = check_parent_access_fsp(parent_dir_fname_dst->fsp, + access_mask); + if (!NT_STATUS_IS_OK(status)) { + DBG_INFO("check_parent_access_fsp on " + "dst %s returned %s\n", + smb_fname_str_dbg(smb_fname_dst), + nt_errstr(status)); + goto out; + } + + /* + * If the target existed, make sure the destination + * atname has the same stat struct. + */ + parent_dir_fname_dst_atname->st = smb_fname_dst->st; + + /* + * It's very common that source and + * destination directories are the same. + * Optimize by not opening the + * second parent_pathref if we know + * this is the case. + */ + + status = SMB_VFS_PARENT_PATHNAME(conn, + ctx, + fsp->fsp_name, + &parent_dir_fname_src, + &parent_dir_fname_src_atname); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + /* + * We do a case-sensitive string comparison. We want to be *sure* + * this is the same path. The worst that can happen if + * the case doesn't match is we lose out on the optimization, + * the code still works. + * + * We can ignore twrp fields here. Rename is not allowed on + * shadow copy handles. + */ + + if (strcmp(parent_dir_fname_src->base_name, + parent_dir_fname_dst->base_name) == 0) { + /* + * parent directory is the same for source + * and destination. + */ + /* Reparent the src_atname to the parent_dir_dest fname. */ + parent_dir_fname_src_atname = talloc_move( + parent_dir_fname_dst, + &parent_dir_fname_src_atname); + /* Free the unneeded duplicate parent name. */ + TALLOC_FREE(parent_dir_fname_src); + /* + * And make the source parent name a copy of the + * destination parent name. + */ + parent_dir_fname_src = parent_dir_fname_dst; + } else { + /* + * source and destination parent directories are + * different. + * + * Get a pathref on the source parent directory, so + * we can do a relative rename. + */ + TALLOC_FREE(parent_dir_fname_src); + status = parent_pathref(ctx, + conn->cwd_fsp, + fsp->fsp_name, + &parent_dir_fname_src, + &parent_dir_fname_src_atname); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + } + + /* + * Some modules depend on the source smb_fname having a valid stat. + * The parent_dir_fname_src_atname is the relative name of the + * currently open file, so just copy the stat from the open fsp. + */ + parent_dir_fname_src_atname->st = fsp->fsp_name->st; + + lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id); + + /* + * We have the file open ourselves, so not being able to get the + * corresponding share mode lock is a fatal error. + */ + + SMB_ASSERT(lck != NULL); + + ret = SMB_VFS_RENAMEAT(conn, + parent_dir_fname_src->fsp, + parent_dir_fname_src_atname, + parent_dir_fname_dst->fsp, + parent_dir_fname_dst_atname); + if (ret == 0) { + uint32_t create_options = fh_get_private_options(fsp->fh); + + DEBUG(3, ("rename_internals_fsp: succeeded doing rename on " + "%s -> %s\n", smb_fname_str_dbg(fsp->fsp_name), + smb_fname_str_dbg(smb_fname_dst))); + + notify_rename(conn, + fsp->fsp_flags.is_directory, + fsp->fsp_name, + smb_fname_dst); + + rename_open_files(conn, lck, fsp->file_id, fsp->name_hash, + smb_fname_dst); + + if (!fsp->fsp_flags.is_directory && + !(fsp->posix_flags & FSP_POSIX_FLAGS_PATHNAMES) && + (lp_map_archive(SNUM(conn)) || + lp_store_dos_attributes(SNUM(conn)))) + { + /* + * We must set the archive bit on the newly renamed + * file. + */ + status = vfs_stat_fsp(fsp); + if (NT_STATUS_IS_OK(status)) { + uint32_t old_dosmode; + old_dosmode = fdos_mode(fsp); + /* + * We can use fsp->fsp_name here as it has + * already been changed to the new name. + */ + SMB_ASSERT(fsp->fsp_name->fsp == fsp); + file_set_dosmode(conn, + fsp->fsp_name, + old_dosmode | FILE_ATTRIBUTE_ARCHIVE, + NULL, + true); + } + } + + /* + * A rename acts as a new file create w.r.t. allowing an initial delete + * on close, probably because in Windows there is a new handle to the + * new file. If initial delete on close was requested but not + * originally set, we need to set it here. This is probably not 100% correct, + * but will work for the CIFSFS client which in non-posix mode + * depends on these semantics. JRA. + */ + + if (create_options & FILE_DELETE_ON_CLOSE) { + status = can_set_delete_on_close(fsp, 0); + + if (NT_STATUS_IS_OK(status)) { + /* Note that here we set the *initial* delete on close flag, + * not the regular one. The magic gets handled in close. */ + fsp->fsp_flags.initial_delete_on_close = true; + } + } + TALLOC_FREE(lck); + status = NT_STATUS_OK; + goto out; + } + + TALLOC_FREE(lck); + + if (errno == ENOTDIR || errno == EISDIR) { + status = NT_STATUS_OBJECT_NAME_COLLISION; + } else { + status = map_nt_error_from_unix(errno); + } + + DEBUG(3, ("rename_internals_fsp: Error %s rename %s -> %s\n", + nt_errstr(status), smb_fname_str_dbg(fsp->fsp_name), + smb_fname_str_dbg(smb_fname_dst))); + + out: + + /* + * parent_dir_fname_src may be a copy of parent_dir_fname_dst. + * See the optimization for same source and destination directory + * above. Only free one in that case. + */ + if (parent_dir_fname_src != parent_dir_fname_dst) { + TALLOC_FREE(parent_dir_fname_src); + } + TALLOC_FREE(parent_dir_fname_dst); + TALLOC_FREE(smb_fname_dst); + + return status; +} + +/**************************************************************************** + The guts of the rename command, split out so it may be called by the NT SMB + code. +****************************************************************************/ + +NTSTATUS rename_internals(TALLOC_CTX *ctx, + connection_struct *conn, + struct smb_request *req, + struct files_struct *src_dirfsp, + struct smb_filename *smb_fname_src, + struct files_struct *dst_dirfsp, + struct smb_filename *smb_fname_dst, + const char *dst_original_lcomp, + uint32_t attrs, + bool replace_if_exists, + uint32_t access_mask) +{ + NTSTATUS status = NT_STATUS_OK; + int create_options = 0; + struct smb2_create_blobs *posx = NULL; + struct files_struct *fsp = NULL; + bool posix_pathname = (smb_fname_src->flags & SMB_FILENAME_POSIX_PATH); + bool case_sensitive = posix_pathname ? true : conn->case_sensitive; + bool case_preserve = posix_pathname ? true : conn->case_preserve; + bool short_case_preserve = posix_pathname ? true : + conn->short_case_preserve; + + if (posix_pathname) { + 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)); + goto out; + } + } + + DBG_NOTICE("case_sensitive = %d, " + "case_preserve = %d, short case preserve = %d, " + "directory = %s, newname = %s, " + "last_component_dest = %s\n", + case_sensitive, case_preserve, + short_case_preserve, + smb_fname_str_dbg(smb_fname_src), + smb_fname_str_dbg(smb_fname_dst), + dst_original_lcomp); + + ZERO_STRUCT(smb_fname_src->st); + + status = openat_pathref_fsp(conn->cwd_fsp, smb_fname_src); + if (!NT_STATUS_IS_OK(status)) { + if (!NT_STATUS_EQUAL(status, + NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + goto out; + } + /* + * Possible symlink src. + */ + if (!(smb_fname_src->flags & SMB_FILENAME_POSIX_PATH)) { + goto out; + } + if (!S_ISLNK(smb_fname_src->st.st_ex_mode)) { + goto out; + } + } + + if (S_ISDIR(smb_fname_src->st.st_ex_mode)) { + create_options |= FILE_DIRECTORY_FILE; + } + + status = SMB_VFS_CREATE_FILE( + conn, /* conn */ + req, /* req */ + src_dirfsp, /* dirfsp */ + smb_fname_src, /* fname */ + access_mask, /* access_mask */ + (FILE_SHARE_READ | /* share_access */ + FILE_SHARE_WRITE), + 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 */ + NULL, /* pinfo */ + posx, /* in_context_blobs */ + NULL); /* out_context_blobs */ + + if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("Could not open rename source %s: %s\n", + smb_fname_str_dbg(smb_fname_src), + nt_errstr(status)); + goto out; + } + + status = rename_internals_fsp(conn, + fsp, + dst_dirfsp, + smb_fname_dst, + dst_original_lcomp, + attrs, + replace_if_exists); + + close_file_free(req, &fsp, NORMAL_CLOSE); + + DBG_NOTICE("Error %s rename %s -> %s\n", + nt_errstr(status), smb_fname_str_dbg(smb_fname_src), + smb_fname_str_dbg(smb_fname_dst)); + + out: + TALLOC_FREE(posx); + return status; +} + +/******************************************************************* + Copy a file as part of a reply_copy. +******************************************************************/ + +/* + * TODO: check error codes on all callers + */ + +NTSTATUS copy_file(TALLOC_CTX *ctx, + connection_struct *conn, + struct smb_filename *smb_fname_src, + struct smb_filename *smb_fname_dst, + uint32_t new_create_disposition) +{ + struct smb_filename *smb_fname_dst_tmp = NULL; + off_t ret=-1; + files_struct *fsp1,*fsp2; + uint32_t dosattrs; + NTSTATUS status; + + + smb_fname_dst_tmp = cp_smb_filename(ctx, smb_fname_dst); + if (smb_fname_dst_tmp == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = vfs_file_exist(conn, smb_fname_src); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + status = openat_pathref_fsp(conn->cwd_fsp, smb_fname_src); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + /* Open the src file for reading. */ + status = SMB_VFS_CREATE_FILE( + conn, /* conn */ + NULL, /* req */ + NULL, /* dirfsp */ + smb_fname_src, /* fname */ + FILE_GENERIC_READ, /* access_mask */ + FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */ + FILE_OPEN, /* create_disposition*/ + 0, /* create_options */ + FILE_ATTRIBUTE_NORMAL, /* file_attributes */ + INTERNAL_OPEN_ONLY, /* oplock_request */ + NULL, /* lease */ + 0, /* allocation_size */ + 0, /* private_flags */ + NULL, /* sd */ + NULL, /* ea_list */ + &fsp1, /* result */ + NULL, /* psbuf */ + NULL, NULL); /* create context */ + + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + dosattrs = fdos_mode(fsp1); + + if (SMB_VFS_STAT(conn, smb_fname_dst_tmp) == -1) { + ZERO_STRUCTP(&smb_fname_dst_tmp->st); + } + + status = openat_pathref_fsp(conn->cwd_fsp, smb_fname_dst); + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) + { + goto out; + } + + /* Open the dst file for writing. */ + status = SMB_VFS_CREATE_FILE( + conn, /* conn */ + NULL, /* req */ + NULL, /* dirfsp */ + smb_fname_dst, /* fname */ + FILE_GENERIC_WRITE, /* access_mask */ + FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */ + new_create_disposition, /* create_disposition*/ + 0, /* create_options */ + dosattrs, /* file_attributes */ + INTERNAL_OPEN_ONLY, /* oplock_request */ + NULL, /* lease */ + 0, /* allocation_size */ + 0, /* private_flags */ + NULL, /* sd */ + NULL, /* ea_list */ + &fsp2, /* result */ + NULL, /* psbuf */ + NULL, NULL); /* create context */ + + if (!NT_STATUS_IS_OK(status)) { + close_file_free(NULL, &fsp1, ERROR_CLOSE); + goto out; + } + + /* Do the actual copy. */ + if (smb_fname_src->st.st_ex_size) { + ret = vfs_transfer_file(fsp1, fsp2, smb_fname_src->st.st_ex_size); + } else { + ret = 0; + } + + close_file_free(NULL, &fsp1, NORMAL_CLOSE); + + /* Ensure the modtime is set correctly on the destination file. */ + set_close_write_time(fsp2, smb_fname_src->st.st_ex_mtime); + + /* + * As we are opening fsp1 read-only we only expect + * an error on close on fsp2 if we are out of space. + * Thus we don't look at the error return from the + * close of fsp1. + */ + status = close_file_free(NULL, &fsp2, NORMAL_CLOSE); + + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + if (ret != (off_t)smb_fname_src->st.st_ex_size) { + status = NT_STATUS_DISK_FULL; + goto out; + } + + status = NT_STATUS_OK; + + out: + TALLOC_FREE(smb_fname_dst_tmp); + return status; +} + +/**************************************************************************** + Get a lock offset, dealing with large offset requests. +****************************************************************************/ + +uint64_t get_lock_offset(const uint8_t *data, int data_offset, + bool large_file_format) +{ + uint64_t offset = 0; + + if(!large_file_format) { + offset = (uint64_t)IVAL(data,SMB_LKOFF_OFFSET(data_offset)); + } else { + /* + * No BVAL, this is reversed! + */ + offset = (((uint64_t) IVAL(data,SMB_LARGE_LKOFF_OFFSET_HIGH(data_offset))) << 32) | + ((uint64_t) IVAL(data,SMB_LARGE_LKOFF_OFFSET_LOW(data_offset))); + } + + return offset; +} + +struct smbd_do_unlocking_state { + struct files_struct *fsp; + uint16_t num_ulocks; + struct smbd_lock_element *ulocks; + NTSTATUS status; +}; + +static void smbd_do_unlocking_fn( + const uint8_t *buf, + size_t buflen, + bool *pmodified_dependent, + void *private_data) +{ + struct smbd_do_unlocking_state *state = private_data; + struct files_struct *fsp = state->fsp; + uint16_t i; + + for (i = 0; i < state->num_ulocks; i++) { + struct smbd_lock_element *e = &state->ulocks[i]; + + DBG_DEBUG("unlock start=%"PRIu64", len=%"PRIu64" for " + "pid %"PRIu64", file %s\n", + e->offset, + e->count, + e->smblctx, + fsp_str_dbg(fsp)); + + if (e->brltype != UNLOCK_LOCK) { + /* this can only happen with SMB2 */ + state->status = NT_STATUS_INVALID_PARAMETER; + return; + } + + state->status = do_unlock( + fsp, e->smblctx, e->count, e->offset, e->lock_flav); + + DBG_DEBUG("do_unlock returned %s\n", + nt_errstr(state->status)); + + if (!NT_STATUS_IS_OK(state->status)) { + return; + } + } + + *pmodified_dependent = true; +} + +NTSTATUS smbd_do_unlocking(struct smb_request *req, + files_struct *fsp, + uint16_t num_ulocks, + struct smbd_lock_element *ulocks) +{ + struct smbd_do_unlocking_state state = { + .fsp = fsp, + .num_ulocks = num_ulocks, + .ulocks = ulocks, + }; + NTSTATUS status; + + DBG_NOTICE("%s num_ulocks=%"PRIu16"\n", fsp_fnum_dbg(fsp), num_ulocks); + + status = share_mode_do_locked( + fsp->file_id, smbd_do_unlocking_fn, &state); + + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("share_mode_do_locked failed: %s\n", + nt_errstr(status)); + return status; + } + if (!NT_STATUS_IS_OK(state.status)) { + DBG_DEBUG("smbd_do_unlocking_fn failed: %s\n", + nt_errstr(status)); + return state.status; + } + + return NT_STATUS_OK; +} |