/* 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 . */ /* 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" #include "source3/smbd/dir.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) == '\\')) NTSTATUS check_path_syntax(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; } } else if (IS_SMBD_TMPNAME(s, NULL)) { return NT_STATUS_OBJECT_NAME_INVALID; } } 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 ch_size; /* Get the size of the next MB character. */ next_codepoint(s,&ch_size); switch(ch_size) { 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: DBG_ERR("character length assumptions invalid !\n"); *d = '\0'; return NT_STATUS_INVALID_PARAMETER; } } start_of_name_component = False; } *d = '\0'; return ret; } /**************************************************************************** SMB2-only code to strip an MSDFS prefix from an incoming pathname. ****************************************************************************/ NTSTATUS smb2_strip_dfs_path(const char *in_path, const char **out_path) { const char *path = in_path; /* Match the Windows 2022 behavior for an empty DFS pathname. */ if (*path == '\0') { return NT_STATUS_INVALID_PARAMETER; } /* Strip any leading '\\' characters - MacOSX client behavior. */ while (*path == '\\') { path++; } /* We should now be pointing at the server name. Go past it. */ for (;;) { if (*path == '\0') { /* End of complete path. Exit OK. */ goto out; } if (*path == '\\') { /* End of server name. Go past and break. */ path++; break; } path++; /* Continue looking for end of server name or string. */ } /* We should now be pointing at the share name. Go past it. */ for (;;) { if (*path == '\0') { /* End of complete path. Exit OK. */ goto out; } if (*path == '\\') { /* End of share name. Go past and break. */ path++; break; } if (*path == ':') { /* Only invalid character in sharename. */ return NT_STATUS_OBJECT_NAME_INVALID; } path++; /* Continue looking for end of share name or string. */ } /* path now points at the start of the real filename (if any). */ out: /* We have stripped the DFS path prefix (if any). */ *out_path = path; return NT_STATUS_OK; } /**************************************************************************** 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: *err = check_path_syntax(dst, posix_pathnames); 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 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 ((fsp == NULL) || (conn == NULL)) { return false; } if ((conn != fsp->conn) || (req->vuid != fsp->vuid)) { 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)) { 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 guaranteed 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)) { 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 | FILE_OPEN_REPARSE_POINT, /* 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) { NTSTATUS status; if (fsp->fsp_name->twrp != 0) { /* Get the error right, this is what Windows returns. */ return NT_STATUS_NOT_SAME_DEVICE; } 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; } } status = check_any_access_fsp(fsp, DELETE_ACCESS | FILE_WRITE_ATTRIBUTES); if (!NT_STATUS_IS_OK(status)) { return status; } return NT_STATUS_OK; } /**************************************************************************** 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; 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; bool ok; /* 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; ok = fsp_set_smb_fname(fsp, smb_fname_dst); if (ok) { 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 . ****************************************************************************/ 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(struct connection_struct *conn, struct files_struct *fsp, const struct smb_filename *smb_fname_src, const struct smb_filename *smb_fname_dst) { bool is_dir = fsp->fsp_flags.is_directory; 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, NULL); notify_fname(conn, NOTIFY_ACTION_NEW_NAME | NOTIFY_ACTION_DIRLEASE_BREAK, mask, smb_fname_dst, fsp_get_smb2_lease(fsp)); } else { notify_fname(conn, NOTIFY_ACTION_REMOVED | NOTIFY_ACTION_DIRLEASE_BREAK, mask, smb_fname_src, fsp_get_smb2_lease(fsp)); notify_fname(conn, NOTIFY_ACTION_ADDED | NOTIFY_ACTION_DIRLEASE_BREAK, mask, smb_fname_dst, fsp_get_smb2_lease(fsp)); } /* 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, NULL); } out: TALLOC_FREE(parent_dir_src); TALLOC_FREE(parent_dir_dst); } struct rename_check_open_state { struct files_struct *dst_fsp; struct file_id fileid; }; static struct files_struct *rename_check_open_fn(struct files_struct *fsp, void *private_data) { struct rename_check_open_state *state = private_data; if (fsp == state->dst_fsp) { return NULL; } if (!fsp->fsp_flags.is_fsa) { return NULL; } if (!file_id_equal(&fsp->file_id, &state->fileid)) { return NULL; } return fsp; } /**************************************************************************** Rename an open file - given an fsp. ****************************************************************************/ NTSTATUS rename_internals_fsp(connection_struct *conn, struct files_struct *src_dirfsp, files_struct *fsp, struct smb_filename *smb_fname_src_rel, struct share_mode_lock **_lck, uint32_t attrs, const char *newname, bool replace_if_exists) { TALLOC_CTX *ctx = talloc_stackframe(); struct smb_filename *smb_fname_src = fsp->fsp_name; struct files_struct *dst_dirfsp = NULL; struct smb_filename *smb_fname_dst = NULL; struct smb_filename *smb_fname_dst_rel = NULL; NTSTATUS status = NT_STATUS_OK; struct share_mode_lock *lck = NULL; int ret; bool case_preserve = fsp->fsp_flags.posix_open || conn->case_preserve; struct vfs_rename_how rhow = { .flags = 0, }; if (file_has_open_streams(fsp)) { status = NT_STATUS_ACCESS_DENIED; goto out; } if (fsp_is_alternate_stream(fsp)) { if (newname[0] != ':') { status = NT_STATUS_INVALID_PARAMETER; goto out; } smb_fname_dst = synthetic_smb_fname(ctx, smb_fname_src->base_name, newname, NULL, 0, smb_fname_src->flags); if (smb_fname_dst == NULL) { status = NT_STATUS_NO_MEMORY; goto out; } if (is_ntfs_default_stream_smb_fname(smb_fname_dst)) { status = replace_if_exists ? NT_STATUS_NOT_SUPPORTED : NT_STATUS_OBJECT_NAME_COLLISION; goto out; } ret = SMB_VFS_RENAME_STREAM(fsp->conn, fsp, newname, replace_if_exists); goto inform_others; } status = filename_convert_dirfsp(ctx, conn, newname, fsp->fsp_flags.posix_open ? UCF_POSIX_PATHNAMES | UCF_LCOMP_LNK_OK : 0, smb_fname_src->twrp, &dst_dirfsp, &smb_fname_dst); if (!NT_STATUS_IS_OK(status)) { goto out; } if (is_ntfs_stream_smb_fname(smb_fname_dst)) { /* * We handled streams above */ status = NT_STATUS_OBJECT_NAME_INVALID; goto out; } { /* * Get the lcomp as the client sent it. For a pure * case change smb_fname_dst exists, but * filename_convert_dirfsp has removed the client's * intent by doing the case-insensitive lookup. */ char *lcomp = get_original_lcomp(ctx, conn, newname, 0); if (lcomp == NULL) { status = NT_STATUS_NO_MEMORY; goto out; } smb_fname_dst_rel = synthetic_smb_fname( ctx, lcomp, NULL, NULL, 0, 0); TALLOC_FREE(lcomp); if (smb_fname_dst_rel == NULL) { status = NT_STATUS_NO_MEMORY; goto out; } } if (!case_preserve && strequal(smb_fname_src->base_name, smb_fname_dst->base_name)) { /* * src and dst are the same except for the * case. Because we don't preserve the case, we can * just return success. Streams handled above. */ status = NT_STATUS_OK; goto out; } if (strcsequal(src_dirfsp->fsp_name->base_name, dst_dirfsp->fsp_name->base_name)) { /* * Same directory */ if (strcsequal(smb_fname_src_rel->base_name, smb_fname_dst_rel->base_name)) { /* * No file name change */ status = NT_STATUS_OK; goto out; } if (strequal(smb_fname_src_rel->base_name, smb_fname_dst_rel->base_name)) { /* * Change the case of a file, mark the dst as * nonexisting. */ SET_STAT_INVALID(smb_fname_dst->st); } } if (VALID_STAT(smb_fname_dst->st)) { struct rename_check_open_state check_state = { .dst_fsp = smb_fname_dst->fsp, }; struct files_struct *found_open = NULL; if (!replace_if_exists) { DBG_NOTICE("dest exists doing rename " "%s -> %s\n", smb_fname_str_dbg(smb_fname_src), smb_fname_str_dbg(smb_fname_dst)); status = NT_STATUS_OBJECT_NAME_COLLISION; goto out; } check_state.fileid = vfs_file_id_from_sbuf(conn, &smb_fname_dst->st); found_open = files_forall(conn->sconn, rename_check_open_fn, &check_state); if (found_open != NULL) { DBG_NOTICE("Target file open\n"); status = NT_STATUS_ACCESS_DENIED; goto out; } } status = can_rename(conn, fsp, attrs); if (!NT_STATUS_IS_OK(status)) { DBG_NOTICE("Error %s rename %s -> %s\n", nt_errstr(status), fsp_str_dbg(fsp), 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; } status = check_parent_access_fsp(dst_dirfsp, S_ISDIR(fsp->fsp_name->st.st_ex_mode) ? SEC_DIR_ADD_SUBDIR : SEC_DIR_ADD_FILE); 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; } ret = SMB_VFS_RENAMEAT(conn, src_dirfsp, smb_fname_src_rel, dst_dirfsp, smb_fname_dst_rel, &rhow); inform_others: if (_lck != NULL) { lck = talloc_move(talloc_tos(), _lck); } else { 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); if (ret == 0) { struct smb_filename *old_fname = NULL; DBG_NOTICE("succeeded doing rename on " "%s -> %s\n", smb_fname_str_dbg(fsp->fsp_name), smb_fname_str_dbg(smb_fname_dst)); old_fname = cp_smb_filename(talloc_tos(), fsp->fsp_name); if (old_fname == NULL) { status = NT_STATUS_NO_MEMORY; TALLOC_FREE(lck); goto out; } rename_open_files(conn, lck, fsp->file_id, fsp->name_hash, smb_fname_dst); if (!fsp->fsp_flags.is_directory && (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); } } TALLOC_FREE(lck); notify_rename(conn, fsp, old_fname, smb_fname_dst); TALLOC_FREE(old_fname); 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); } DBG_NOTICE("Error %s rename %s -> %s\n", nt_errstr(status), smb_fname_str_dbg(fsp->fsp_name), smb_fname_str_dbg(smb_fname_dst)); out: TALLOC_FREE(ctx); 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 smb_filename *smb_fname_src_rel, uint32_t attrs, const char *newname, bool replace_if_exists, uint32_t access_mask) { NTSTATUS status = NT_STATUS_OK; int create_options = FILE_OPEN_REPARSE_POINT; 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 || conn->case_sensitive; bool case_preserve = posix_pathname || conn->case_preserve; bool short_case_preserve = posix_pathname || 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\n", case_sensitive, case_preserve, short_case_preserve, smb_fname_str_dbg(smb_fname_src), newname); 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; } /* * If no pathnames are open below this directory, allow the rename. */ if (have_file_open_below(fsp)) { status = NT_STATUS_ACCESS_DENIED; close_file_free(req, &fsp, NORMAL_CLOSE); goto out; } status = rename_internals_fsp(conn, src_dirfsp, fsp, smb_fname_src_rel, NULL, attrs, newname, 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), newname); 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 = 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*/ FILE_NON_DIRECTORY_FILE, /* 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; } static void smbd_do_unlocking_fn(struct share_mode_lock *lck, struct byte_range_lock *br_lck, void *private_data) { struct smbd_do_locks_state *state = private_data; struct files_struct *fsp = brl_fsp(br_lck); uint16_t i; /* * The caller has checked fsp->fsp_flags.can_lock and lp_locking so * br_lck has to be there! */ SMB_ASSERT(br_lck != NULL); for (i = 0; i < state->num_locks; i++) { struct smbd_lock_element *e = &state->locks[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( br_lck, 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; } } share_mode_wakeup_waiters(fsp->file_id); } NTSTATUS smbd_do_unlocking(struct smb_request *req, files_struct *fsp, uint16_t num_ulocks, struct smbd_lock_element *ulocks) { struct smbd_do_locks_state state = { .num_locks = num_ulocks, .locks = ulocks, }; NTSTATUS status; DBG_NOTICE("%s num_ulocks=%"PRIu16"\n", fsp_fnum_dbg(fsp), num_ulocks); if (!fsp->fsp_flags.can_lock) { if (fsp->fsp_flags.is_directory) { return NT_STATUS_INVALID_DEVICE_REQUEST; } return NT_STATUS_INVALID_HANDLE; } if (!lp_locking(fsp->conn->params)) { return NT_STATUS_OK; } status = share_mode_do_locked_brl(fsp, smbd_do_unlocking_fn, &state); if (!NT_STATUS_IS_OK(status)) { DBG_DEBUG("share_mode_do_locked_brl 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; }