1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-27 03:21:53 +03:00
samba-mirror/source3/smbd/filename.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1284 lines
29 KiB
C
Raw Normal View History

/*
Unix SMB/CIFS implementation.
filename handling routines
Copyright (C) Andrew Tridgell 1992-1998
Copyright (C) Jeremy Allison 1999-2007
Copyright (C) Ying Chen 2000
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/>.
*/
/*
* New hash table stat cache code added by Ying Chen.
*/
#include "includes.h"
#include "system/filesys.h"
#include "fake_file.h"
#include "smbd/smbd.h"
#include "smbd/globals.h"
#include "libcli/smb/reparse.h"
#include "source3/smbd/dir.h"
uint32_t ucf_flags_from_smb_request(struct smb_request *req)
{
uint32_t ucf_flags = 0;
if (req == NULL) {
return 0;
}
if (req->posix_pathnames) {
ucf_flags |= UCF_POSIX_PATHNAMES;
if (!conn_using_smb2(req->sconn)) {
ucf_flags |= UCF_LCOMP_LNK_OK;
}
}
if (req->flags2 & FLAGS2_DFS_PATHNAMES) {
ucf_flags |= UCF_DFS_PATHNAME;
}
if (req->flags2 & FLAGS2_REPARSE_PATH) {
ucf_flags |= UCF_GMT_PATHNAME;
}
return ucf_flags;
}
uint32_t filename_create_ucf_flags(struct smb_request *req, uint32_t create_disposition)
{
uint32_t ucf_flags = 0;
ucf_flags |= ucf_flags_from_smb_request(req);
switch (create_disposition) {
case FILE_OPEN:
case FILE_OVERWRITE:
break;
case FILE_SUPERSEDE:
case FILE_CREATE:
case FILE_OPEN_IF:
case FILE_OVERWRITE_IF:
ucf_flags |= UCF_PREP_CREATEFILE;
break;
}
return ucf_flags;
}
/****************************************************************************
Mangle the 2nd name and check if it is then equal to the first name.
****************************************************************************/
static bool mangled_equal(const char *name1,
const char *name2,
const struct share_params *p)
{
char mname[13];
if (!name_to_8_3(name2, mname, False, p)) {
return False;
}
return strequal(name1, mname);
}
/*
* Strip a valid @GMT-token from any incoming filename path,
* adding any NTTIME encoded in the pathname into the
* twrp field of the passed in smb_fname.
*
* Valid @GMT-tokens look like @GMT-YYYY-MM-DD-HH-MM-SS
* at the *start* of a pathname component.
*
* If twrp is passed in then smb_fname->twrp is set to that
* value, and the @GMT-token part of the filename is removed
* and does not change the stored smb_fname->twrp.
*
*/
NTSTATUS canonicalize_snapshot_path(struct smb_filename *smb_fname,
uint32_t ucf_flags,
NTTIME twrp)
{
bool found;
if (twrp != 0) {
smb_fname->twrp = twrp;
}
if (!(ucf_flags & UCF_GMT_PATHNAME)) {
return NT_STATUS_OK;
}
found = extract_snapshot_token(smb_fname->base_name, &twrp);
if (!found) {
return NT_STATUS_OK;
}
if (smb_fname->twrp == 0) {
smb_fname->twrp = twrp;
}
return NT_STATUS_OK;
}
static bool strnorm(char *s, int case_default)
{
if (case_default == CASE_UPPER)
return strupper_m(s);
else
return strlower_m(s);
}
/*
* Utility function to normalize case on an incoming client filename
* if required on this connection struct.
* Performs an in-place case conversion guaranteed to stay the same size.
*/
static NTSTATUS normalize_filename_case(connection_struct *conn,
char *filename,
uint32_t ucf_flags)
{
bool ok;
if (ucf_flags & UCF_POSIX_PATHNAMES) {
/*
* POSIX never normalizes filename case.
*/
return NT_STATUS_OK;
}
if (!conn->case_sensitive) {
return NT_STATUS_OK;
}
if (conn->case_preserve) {
return NT_STATUS_OK;
}
if (conn->short_case_preserve) {
return NT_STATUS_OK;
}
ok = strnorm(filename, lp_default_case(SNUM(conn)));
if (!ok) {
return NT_STATUS_INVALID_PARAMETER;
}
return NT_STATUS_OK;
}
/****************************************************************************
Check if two filenames are equal.
This needs to be careful about whether we are case sensitive.
****************************************************************************/
static bool fname_equal(const char *name1, const char *name2,
bool case_sensitive)
{
/* Normal filename handling */
if (case_sensitive) {
return(strcmp(name1,name2) == 0);
}
return(strequal(name1,name2));
}
static bool sname_equal(const char *name1, const char *name2,
bool case_sensitive)
{
bool match;
const char *s1 = NULL;
const char *s2 = NULL;
size_t n1;
size_t n2;
const char *e1 = NULL;
const char *e2 = NULL;
char *c1 = NULL;
char *c2 = NULL;
match = fname_equal(name1, name2, case_sensitive);
if (match) {
return true;
}
if (name1[0] != ':') {
return false;
}
if (name2[0] != ':') {
return false;
}
s1 = &name1[1];
e1 = strchr(s1, ':');
if (e1 == NULL) {
n1 = strlen(s1);
} else {
n1 = PTR_DIFF(e1, s1);
}
s2 = &name2[1];
e2 = strchr(s2, ':');
if (e2 == NULL) {
n2 = strlen(s2);
} else {
n2 = PTR_DIFF(e2, s2);
}
/* Normal filename handling */
if (case_sensitive) {
return (strncmp(s1, s2, n1) == 0);
}
/*
* We can't use strnequal() here
* as it takes the number of codepoints
* and not the number of bytes.
*
* So we make a copy before calling
* strequal().
*
* Note that we TALLOC_FREE() in reverse order
* in order to avoid memory fragmentation.
*/
c1 = talloc_strndup(talloc_tos(), s1, n1);
c2 = talloc_strndup(talloc_tos(), s2, n2);
if (c1 == NULL || c2 == NULL) {
TALLOC_FREE(c2);
TALLOC_FREE(c1);
return (strncmp(s1, s2, n1) == 0);
}
match = strequal(c1, c2);
TALLOC_FREE(c2);
TALLOC_FREE(c1);
return match;
}
/****************************************************************************
Scan a directory to find a filename, matching without case sensitivity.
If the name looks like a mangled name then try via the mangling functions
****************************************************************************/
NTSTATUS get_real_filename_full_scan_at(struct files_struct *dirfsp,
const char *name,
bool mangled,
TALLOC_CTX *mem_ctx,
char **found_name)
{
struct connection_struct *conn = dirfsp->conn;
struct smb_Dir *cur_dir = NULL;
const char *dname = NULL;
char *talloced = NULL;
char *unmangled_name = NULL;
NTSTATUS status;
/* If we have a case-sensitive filesystem, it doesn't do us any
* good to search for a name. If a case variation of the name was
* there, then the original stat(2) would have found it.
*/
if (!mangled && !(conn->fs_capabilities & FILE_CASE_SENSITIVE_SEARCH)) {
return NT_STATUS_OBJECT_NAME_NOT_FOUND;
}
/*
* The incoming name can be mangled, and if we de-mangle it
* here it will not compare correctly against the filename (name2)
* read from the directory and then mangled by the name_to_8_3()
* call. We need to mangle both names or neither.
* (JRA).
*
* Fix for bug found by Dina Fine. If in case sensitive mode then
* the mangle cache is no good (3 letter extension could be wrong
* case - so don't demangle in this case - leave as mangled and
* allow the mangling of the directory entry read (which is done
* case insensitively) to match instead. This will lead to more
* false positive matches but we fail completely without it. JRA.
*/
if (mangled && !conn->case_sensitive) {
mangled = !mangle_lookup_name_from_8_3(talloc_tos(), name,
&unmangled_name,
conn->params);
if (!mangled) {
/* Name is now unmangled. */
name = unmangled_name;
}
}
/* open the directory */
status = OpenDir_from_pathref(talloc_tos(), dirfsp, NULL, 0, &cur_dir);
if (!NT_STATUS_IS_OK(status)) {
DBG_NOTICE("scan dir didn't open dir [%s]: %s\n",
fsp_str_dbg(dirfsp),
nt_errstr(status));
TALLOC_FREE(unmangled_name);
return status;
}
/* now scan for matching names */
while ((dname = ReadDirName(cur_dir, &talloced))) {
/* Is it dot or dot dot. */
if (ISDOT(dname) || ISDOTDOT(dname)) {
TALLOC_FREE(talloced);
continue;
}
/*
* At this point dname is the unmangled name.
* name is either mangled or not, depending on the state
* of the "mangled" variable. JRA.
*/
/*
* Check mangled name against mangled name, or unmangled name
* against unmangled name.
*/
if ((mangled && mangled_equal(name,dname,conn->params)) ||
fname_equal(name, dname, conn->case_sensitive)) {
/* we've found the file, change it's name and return */
*found_name = talloc_strdup(mem_ctx, dname);
TALLOC_FREE(unmangled_name);
TALLOC_FREE(cur_dir);
if (!*found_name) {
TALLOC_FREE(talloced);
return NT_STATUS_NO_MEMORY;
}
TALLOC_FREE(talloced);
return NT_STATUS_OK;
}
TALLOC_FREE(talloced);
}
TALLOC_FREE(unmangled_name);
TALLOC_FREE(cur_dir);
return NT_STATUS_OBJECT_NAME_NOT_FOUND;
}
/****************************************************************************
Wrapper around the vfs get_real_filename and the full directory scan
fallback.
****************************************************************************/
NTSTATUS get_real_filename_at(struct files_struct *dirfsp,
const char *name,
TALLOC_CTX *mem_ctx,
char **found_name)
{
struct connection_struct *conn = dirfsp->conn;
NTSTATUS status;
bool mangled;
mangled = mangle_is_mangled(name, conn->params);
if (mangled) {
status = get_real_filename_full_scan_at(
dirfsp, name, mangled, mem_ctx, found_name);
return status;
}
/* Try the vfs first to take advantage of case-insensitive stat. */
status = SMB_VFS_GET_REAL_FILENAME_AT(
dirfsp->conn, dirfsp, name, mem_ctx, found_name);
/*
* If the case-insensitive stat was successful, or returned an error
* other than EOPNOTSUPP then there is no need to fall back on the
* full directory scan.
*/
if (NT_STATUS_IS_OK(status) ||
!NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
return status;
}
status = get_real_filename_full_scan_at(
dirfsp, name, mangled, mem_ctx, found_name);
return status;
}
/*
* Lightweight function to just get last component
* for rename / enumerate directory calls.
*/
char *get_original_lcomp(TALLOC_CTX *ctx,
connection_struct *conn,
const char *filename_in,
uint32_t ucf_flags)
{
char *last_slash = NULL;
char *orig_lcomp;
NTSTATUS status;
last_slash = strrchr(filename_in, '/');
if (last_slash != NULL) {
orig_lcomp = talloc_strdup(ctx, last_slash+1);
} else {
orig_lcomp = talloc_strdup(ctx, filename_in);
}
if (orig_lcomp == NULL) {
return NULL;
}
status = normalize_filename_case(conn, orig_lcomp, ucf_flags);
if (!NT_STATUS_IS_OK(status)) {
TALLOC_FREE(orig_lcomp);
return NULL;
}
return orig_lcomp;
}
/*
* Get the correct capitalized stream name hanging off
* base_fsp. Equivalent of get_real_filename(), but for streams.
*/
static NTSTATUS get_real_stream_name(
TALLOC_CTX *mem_ctx,
struct files_struct *base_fsp,
const char *stream_name,
char **_found)
{
unsigned int i, num_streams = 0;
struct stream_struct *streams = NULL;
NTSTATUS status;
status = vfs_fstreaminfo(
base_fsp, talloc_tos(), &num_streams, &streams);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
for (i=0; i<num_streams; i++) {
bool equal = sname_equal(stream_name, streams[i].name, false);
DBG_DEBUG("comparing [%s] and [%s]: %sequal\n",
stream_name,
streams[i].name,
equal ? "" : "not ");
if (equal) {
*_found = talloc_move(mem_ctx, &streams[i].name);
TALLOC_FREE(streams);
return NT_STATUS_OK;
}
}
TALLOC_FREE(streams);
return NT_STATUS_OBJECT_NAME_NOT_FOUND;
}
static bool filename_split_lcomp(
TALLOC_CTX *mem_ctx,
const char *name_in,
bool posix,
char **_dirname,
const char **_fname_rel,
const char **_streamname)
{
const char *lcomp = NULL;
const char *fname_rel = NULL;
const char *streamname = NULL;
char *dirname = NULL;
if (name_in[0] == '\0') {
fname_rel = ".";
dirname = talloc_strdup(mem_ctx, "");
if (dirname == NULL) {
return false;
}
goto done;
}
lcomp = strrchr_m(name_in, '/');
if (lcomp != NULL) {
fname_rel = lcomp+1;
dirname = talloc_strndup(mem_ctx, name_in, lcomp - name_in);
if (dirname == NULL) {
return false;
}
goto find_stream;
}
/*
* No slash, dir is empty
*/
dirname = talloc_strdup(mem_ctx, "");
if (dirname == NULL) {
return false;
}
if (!posix && (name_in[0] == ':')) {
/*
* Special case for stream on root directory
*/
fname_rel = ".";
streamname = name_in;
goto done;
}
fname_rel = name_in;
find_stream:
if (!posix) {
streamname = strchr_m(fname_rel, ':');
if (streamname != NULL) {
fname_rel = talloc_strndup(
mem_ctx,
fname_rel,
streamname - fname_rel);
if (fname_rel == NULL) {
TALLOC_FREE(dirname);
return false;
}
}
}
done:
*_dirname = dirname;
*_fname_rel = fname_rel;
*_streamname = streamname;
return true;
}
/*
* Create the correct capitalization of a file name to be created.
*/
static NTSTATUS filename_convert_normalize_new(
TALLOC_CTX *mem_ctx,
struct connection_struct *conn,
char *name_in,
char **_normalized)
{
char *name = name_in;
*_normalized = NULL;
if (!conn->case_preserve ||
(mangle_is_8_3(name, false,
conn->params) &&
!conn->short_case_preserve)) {
char *normalized = talloc_strdup(mem_ctx, name);
if (normalized == NULL) {
return NT_STATUS_NO_MEMORY;
}
strnorm(normalized, lp_default_case(SNUM(conn)));
name = normalized;
}
if (mangle_is_mangled(name, conn->params)) {
bool found;
char *unmangled = NULL;
found = mangle_lookup_name_from_8_3(
mem_ctx, name, &unmangled, conn->params);
if (found) {
name = unmangled;
}
}
if (name != name_in) {
*_normalized = name;
}
return NT_STATUS_OK;
}
static const char *previous_slash(const char *name_in, const char *slash)
{
const char *prev = NULL;
SMB_ASSERT((name_in <= slash) && (slash[0] == '/'));
prev = strchr_m(name_in, '/');
if (prev == slash) {
/* No previous slash */
return NULL;
}
while (true) {
const char *next = strchr_m(prev + 1, '/');
if (next == slash) {
return prev;
}
prev = next;
}
return NULL; /* unreachable */
}
static char *symlink_target_path(
TALLOC_CTX *mem_ctx,
const char *name_in,
const char *substitute,
size_t unparsed)
{
size_t name_in_len = strlen(name_in);
const char *p_unparsed = NULL;
const char *parent = NULL;
char *ret;
SMB_ASSERT(unparsed <= name_in_len);
p_unparsed = name_in + (name_in_len - unparsed);
if (substitute[0] == '/') {
ret = talloc_asprintf(mem_ctx, "%s%s", substitute, p_unparsed);
return ret;
}
if (unparsed == 0) {
parent = strrchr_m(name_in, '/');
} else {
parent = previous_slash(name_in, p_unparsed);
}
if (parent == NULL) {
ret = talloc_asprintf(mem_ctx, "%s%s", substitute, p_unparsed);
} else {
ret = talloc_asprintf(mem_ctx,
"%.*s/%s%s",
(int)(parent - name_in),
name_in,
substitute,
p_unparsed);
}
return ret;
}
NTSTATUS safe_symlink_target_path(TALLOC_CTX *mem_ctx,
const char *connectpath,
const char *dir,
const char *target,
size_t unparsed,
char **_relative)
{
char *abs_target = NULL;
char *abs_target_canon = NULL;
const char *relative = NULL;
bool in_share;
NTSTATUS status = NT_STATUS_NO_MEMORY;
DBG_DEBUG("connectpath [%s] target [%s] unparsed [%zu]\n",
connectpath, target, unparsed);
if (target[0] == '/') {
abs_target = talloc_strdup(mem_ctx, target);
} else if (dir == NULL) {
abs_target = talloc_asprintf(mem_ctx,
"%s/%s",
connectpath,
target);
} else if (dir[0] == '/') {
abs_target = talloc_asprintf(mem_ctx,
"%s/%s",
dir,
target);
} else {
abs_target = talloc_asprintf(mem_ctx,
"%s/%s/%s",
connectpath,
dir,
target);
}
if (abs_target == NULL) {
goto fail;
}
abs_target_canon = canonicalize_absolute_path(abs_target, abs_target);
if (abs_target_canon == NULL) {
goto fail;
}
DBG_DEBUG("abs_target_canon=%s\n", abs_target_canon);
in_share = subdir_of(
connectpath, strlen(connectpath), abs_target_canon, &relative);
if (!in_share) {
DBG_DEBUG("wide link to %s\n", abs_target_canon);
status = (unparsed != 0) ? NT_STATUS_OBJECT_PATH_NOT_FOUND
: NT_STATUS_OBJECT_NAME_NOT_FOUND;
goto fail;
}
*_relative = talloc_strdup(mem_ctx, relative);
if (*_relative == NULL) {
goto fail;
}
status = NT_STATUS_OK;
fail:
TALLOC_FREE(abs_target);
return status;
}
/*
* Split up name_in as sent by the client into a directory pathref fsp
* and a relative smb_filename.
*/
static NTSTATUS filename_convert_dirfsp_nosymlink(
TALLOC_CTX *mem_ctx,
connection_struct *conn,
const char *name_in,
uint32_t ucf_flags,
NTTIME twrp,
struct files_struct **_dirfsp,
struct smb_filename **_smb_fname,
struct reparse_data_buffer **_symlink_err)
{
struct smb_filename *smb_dirname = NULL;
struct smb_filename *smb_fname_rel = NULL;
struct smb_filename *smb_fname = NULL;
struct reparse_data_buffer *symlink_err = NULL;
const bool posix = (ucf_flags & UCF_POSIX_PATHNAMES);
char *dirname = NULL;
const char *fname_rel = NULL;
const char *streamname = NULL;
char *saved_streamname = NULL;
struct files_struct *base_fsp = NULL;
bool ok;
NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
SMB_ASSERT(!(ucf_flags & UCF_DFS_PATHNAME));
if (is_fake_file_path(name_in)) {
smb_fname = synthetic_smb_fname_split(mem_ctx, name_in, posix);
if (smb_fname == NULL) {
return NT_STATUS_NO_MEMORY;
}
smb_fname->st = (SMB_STRUCT_STAT){
.st_ex_nlink = 1,
.st_ex_mode = S_IFREG | 0644,
};
smb_fname->st.st_ex_btime =
(struct timespec){0, SAMBA_UTIME_OMIT};
smb_fname->st.st_ex_atime =
(struct timespec){0, SAMBA_UTIME_OMIT};
smb_fname->st.st_ex_mtime =
(struct timespec){0, SAMBA_UTIME_OMIT};
smb_fname->st.st_ex_ctime =
(struct timespec){0, SAMBA_UTIME_OMIT};
*_dirfsp = conn->cwd_fsp;
*_smb_fname = smb_fname;
return NT_STATUS_OK;
}
/*
* Catch an invalid path of "." before we
* call filename_split_lcomp(). We need to
* do this as filename_split_lcomp() will
* use "." for the missing relative component
* when an empty name_in path is sent by
* the client.
*/
if (ISDOT(name_in)) {
status = NT_STATUS_OBJECT_NAME_INVALID;
goto fail;
}
ok = filename_split_lcomp(
talloc_tos(),
name_in,
posix,
&dirname,
&fname_rel,
&streamname);
if (!ok) {
status = NT_STATUS_NO_MEMORY;
goto fail;
}
if ((streamname != NULL) &&
((conn->fs_capabilities & FILE_NAMED_STREAMS) == 0)) {
status = NT_STATUS_OBJECT_NAME_INVALID;
goto fail;
}
if (!posix) {
bool name_has_wild = ms_has_wild(dirname);
name_has_wild |= ms_has_wild(fname_rel);
if (name_has_wild) {
status = NT_STATUS_OBJECT_NAME_INVALID;
goto fail;
}
}
if (dirname[0] == '\0') {
status = synthetic_pathref(
mem_ctx,
conn->cwd_fsp,
".",
NULL,
NULL,
0,
posix ? SMB_FILENAME_POSIX_PATH : 0,
&smb_dirname);
} else {
status = normalize_filename_case(conn, dirname, ucf_flags);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("normalize_filename_case %s failed: %s\n",
dirname,
nt_errstr(status));
goto fail;
}
status = openat_pathref_fsp_nosymlink(mem_ctx,
conn,
conn->cwd_fsp,
dirname,
twrp,
posix,
&smb_dirname,
&symlink_err);
if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
struct symlink_reparse_struct
*lnk = &symlink_err->parsed.lnk;
size_t unparsed = lnk->unparsed_path_length;
size_t name_in_len, dirname_len;
name_in_len = strlen(name_in);
dirname_len = strlen(dirname);
SMB_ASSERT(name_in_len >= dirname_len);
unparsed += (name_in_len - dirname_len);
if (unparsed > UINT16_MAX) {
status = NT_STATUS_BUFFER_OVERFLOW;
goto fail;
}
lnk->unparsed_path_length = unparsed;
*_symlink_err = symlink_err;
goto fail;
}
}
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("opening directory %s failed: %s\n",
dirname,
nt_errstr(status));
TALLOC_FREE(dirname);
if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
/*
* Except ACCESS_DENIED, everything else leads
* to PATH_NOT_FOUND.
*/
status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
}
goto fail;
}
if (!VALID_STAT_OF_DIR(smb_dirname->st)) {
status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
goto fail;
}
smb_dirname->fsp->fsp_flags.is_directory = true;
/*
* Only look at bad last component values
* once we know we have a valid directory. That
* way we won't confuse error messages from
* opening the directory path with error
* messages from a bad last component.
*/
/* Relative filename can't be empty */
if (fname_rel[0] == '\0') {
status = NT_STATUS_OBJECT_NAME_INVALID;
goto fail;
}
/* Relative filename can't be ".." */
if (ISDOTDOT(fname_rel)) {
status = NT_STATUS_OBJECT_NAME_INVALID;
goto fail;
}
/* Relative name can only be dot if directory is empty. */
if (ISDOT(fname_rel) && dirname[0] != '\0') {
status = NT_STATUS_OBJECT_NAME_INVALID;
goto fail;
}
TALLOC_FREE(dirname);
smb_fname_rel = synthetic_smb_fname(
mem_ctx,
fname_rel,
streamname,
NULL,
twrp,
posix ? SMB_FILENAME_POSIX_PATH : 0);
if (smb_fname_rel == NULL) {
status = NT_STATUS_NO_MEMORY;
goto fail;
}
if ((conn->fs_capabilities & FILE_NAMED_STREAMS) &&
is_named_stream(smb_fname_rel)) {
/*
* Find the base_fsp first without the stream.
*/
saved_streamname = smb_fname_rel->stream_name;
smb_fname_rel->stream_name = NULL;
}
status = normalize_filename_case(
conn, smb_fname_rel->base_name, ucf_flags);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("normalize_filename_case %s failed: %s\n",
smb_fname_rel->base_name,
nt_errstr(status));
goto fail;
}
smbd: Simplify openat_pathref_fsp_case_insensitive() This is more lines of code, but it's still a simplification. With this patch we don't call the full openat_pathref_fsp() anymore when looking up the last component in filename_convert_dirfsp(), instead we do the direct SMB_VFS_OPENAT(). We don't need the whole complexity of non_widelink_open() for this case, we do know that we have a real non-cwd dirfsp. The other big change that is not obvious just from looking at the patch: This removes the special case for looking up posix symlinks. Before this patch, filename_convert_dirfsp() returned a proper smb_filename but without an attached fsp when a smb1 posix client hits a symlink. This caused all sorts of special case code everywhere. For example smbd_do_qfilepathinfo() needs to cover both cases just for the smb1 posix symlink case. This special-case handling can go now. We can do the path lookup in the smb1-only qpathinfo code and call into the common code with a proper fsp. When hitting a symlink and with O_PATH available, we'll get the symlink opened with an O_PATH fd. Without O_PATH we obviously can't do that, there we get fd=-1 and an indication that we don't have the procfd fallback around. Why all this? I want to present FIFOs (and eventually symlinks) as reparse points as the very next step. Without this patch, there is no real unified way to get the file attributes from disk. Now we can use the proper logic of fdos_mode() everywhere and not rely on special cases for fsp==NULL. This patch also changes some error codes for smb1 posix extensions. I chose to just change the test instead of going after each and every change. As long as we do get an error, I'm willing to accept that we slightly change error path behaviour for this deprecated code. And, I tried to split this up into smaller patches but I failed. Signed-off-by: Volker Lendecke <vl@samba.org> Reviewed-by: Ralph Boehme <slow@samba.org>
2023-10-18 12:50:20 +03:00
status = openat_pathref_fsp_lcomp(smb_dirname->fsp,
smb_fname_rel,
ucf_flags);
smbd: Simplify openat_pathref_fsp_case_insensitive() This is more lines of code, but it's still a simplification. With this patch we don't call the full openat_pathref_fsp() anymore when looking up the last component in filename_convert_dirfsp(), instead we do the direct SMB_VFS_OPENAT(). We don't need the whole complexity of non_widelink_open() for this case, we do know that we have a real non-cwd dirfsp. The other big change that is not obvious just from looking at the patch: This removes the special case for looking up posix symlinks. Before this patch, filename_convert_dirfsp() returned a proper smb_filename but without an attached fsp when a smb1 posix client hits a symlink. This caused all sorts of special case code everywhere. For example smbd_do_qfilepathinfo() needs to cover both cases just for the smb1 posix symlink case. This special-case handling can go now. We can do the path lookup in the smb1-only qpathinfo code and call into the common code with a proper fsp. When hitting a symlink and with O_PATH available, we'll get the symlink opened with an O_PATH fd. Without O_PATH we obviously can't do that, there we get fd=-1 and an indication that we don't have the procfd fallback around. Why all this? I want to present FIFOs (and eventually symlinks) as reparse points as the very next step. Without this patch, there is no real unified way to get the file attributes from disk. Now we can use the proper logic of fdos_mode() everywhere and not rely on special cases for fsp==NULL. This patch also changes some error codes for smb1 posix extensions. I chose to just change the test instead of going after each and every change. As long as we do get an error, I'm willing to accept that we slightly change error path behaviour for this deprecated code. And, I tried to split this up into smaller patches but I failed. Signed-off-by: Volker Lendecke <vl@samba.org> Reviewed-by: Ralph Boehme <slow@samba.org>
2023-10-18 12:50:20 +03:00
if (NT_STATUS_IS_OK(status) && S_ISLNK(smb_fname_rel->st.st_ex_mode)) {
/*
smbd: Simplify openat_pathref_fsp_case_insensitive() This is more lines of code, but it's still a simplification. With this patch we don't call the full openat_pathref_fsp() anymore when looking up the last component in filename_convert_dirfsp(), instead we do the direct SMB_VFS_OPENAT(). We don't need the whole complexity of non_widelink_open() for this case, we do know that we have a real non-cwd dirfsp. The other big change that is not obvious just from looking at the patch: This removes the special case for looking up posix symlinks. Before this patch, filename_convert_dirfsp() returned a proper smb_filename but without an attached fsp when a smb1 posix client hits a symlink. This caused all sorts of special case code everywhere. For example smbd_do_qfilepathinfo() needs to cover both cases just for the smb1 posix symlink case. This special-case handling can go now. We can do the path lookup in the smb1-only qpathinfo code and call into the common code with a proper fsp. When hitting a symlink and with O_PATH available, we'll get the symlink opened with an O_PATH fd. Without O_PATH we obviously can't do that, there we get fd=-1 and an indication that we don't have the procfd fallback around. Why all this? I want to present FIFOs (and eventually symlinks) as reparse points as the very next step. Without this patch, there is no real unified way to get the file attributes from disk. Now we can use the proper logic of fdos_mode() everywhere and not rely on special cases for fsp==NULL. This patch also changes some error codes for smb1 posix extensions. I chose to just change the test instead of going after each and every change. As long as we do get an error, I'm willing to accept that we slightly change error path behaviour for this deprecated code. And, I tried to split this up into smaller patches but I failed. Signed-off-by: Volker Lendecke <vl@samba.org> Reviewed-by: Ralph Boehme <slow@samba.org>
2023-10-18 12:50:20 +03:00
* Upper layers might need the link target. Here we
* still have the relname around, get the symlink err.
*/
status = read_symlink_reparse(mem_ctx,
smb_dirname->fsp,
smb_fname_rel,
&symlink_err);
smbd: Simplify openat_pathref_fsp_case_insensitive() This is more lines of code, but it's still a simplification. With this patch we don't call the full openat_pathref_fsp() anymore when looking up the last component in filename_convert_dirfsp(), instead we do the direct SMB_VFS_OPENAT(). We don't need the whole complexity of non_widelink_open() for this case, we do know that we have a real non-cwd dirfsp. The other big change that is not obvious just from looking at the patch: This removes the special case for looking up posix symlinks. Before this patch, filename_convert_dirfsp() returned a proper smb_filename but without an attached fsp when a smb1 posix client hits a symlink. This caused all sorts of special case code everywhere. For example smbd_do_qfilepathinfo() needs to cover both cases just for the smb1 posix symlink case. This special-case handling can go now. We can do the path lookup in the smb1-only qpathinfo code and call into the common code with a proper fsp. When hitting a symlink and with O_PATH available, we'll get the symlink opened with an O_PATH fd. Without O_PATH we obviously can't do that, there we get fd=-1 and an indication that we don't have the procfd fallback around. Why all this? I want to present FIFOs (and eventually symlinks) as reparse points as the very next step. Without this patch, there is no real unified way to get the file attributes from disk. Now we can use the proper logic of fdos_mode() everywhere and not rely on special cases for fsp==NULL. This patch also changes some error codes for smb1 posix extensions. I chose to just change the test instead of going after each and every change. As long as we do get an error, I'm willing to accept that we slightly change error path behaviour for this deprecated code. And, I tried to split this up into smaller patches but I failed. Signed-off-by: Volker Lendecke <vl@samba.org> Reviewed-by: Ralph Boehme <slow@samba.org>
2023-10-18 12:50:20 +03:00
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("Could not read symlink for %s: %s\n",
smb_fname_str_dbg(
smb_fname_rel->fsp->fsp_name),
nt_errstr(status));
goto fail;
}
}
if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) &&
!VALID_STAT(smb_fname_rel->st)) {
char *normalized = NULL;
/*
* Creating a new file
*/
status = filename_convert_normalize_new(
smb_fname_rel,
conn,
smb_fname_rel->base_name,
&normalized);
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("filename_convert_normalize_new failed: "
"%s\n",
nt_errstr(status));
goto fail;
}
if (normalized != NULL) {
smb_fname_rel->base_name = normalized;
}
smb_fname_rel->stream_name = saved_streamname;
smb_fname = full_path_from_dirfsp_atname(
mem_ctx, smb_dirname->fsp, smb_fname_rel);
if (smb_fname == NULL) {
status = NT_STATUS_NO_MEMORY;
goto fail;
}
goto done;
}
if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_OPEN_RESTRICTION)) {
/* A vetoed file, pretend it's not there */
status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
}
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
if (saved_streamname == NULL) {
/* smb_fname must be allocated off mem_ctx. */
smb_fname = cp_smb_filename(mem_ctx,
smb_fname_rel->fsp->fsp_name);
if (smb_fname == NULL) {
goto fail;
}
status = move_smb_fname_fsp_link(smb_fname, smb_fname_rel);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
goto done;
}
base_fsp = smb_fname_rel->fsp;
smb_fname_fsp_unlink(smb_fname_rel);
SET_STAT_INVALID(smb_fname_rel->st);
smb_fname_rel->stream_name = saved_streamname;
status = open_stream_pathref_fsp(&base_fsp, smb_fname_rel);
if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) &&
!conn->case_sensitive) {
char *found = NULL;
status = get_real_stream_name(
smb_fname_rel,
base_fsp,
smb_fname_rel->stream_name,
&found);
if (NT_STATUS_IS_OK(status)) {
smb_fname_rel->stream_name = found;
found = NULL;
status = open_stream_pathref_fsp(
&base_fsp, smb_fname_rel);
}
}
if (NT_STATUS_IS_OK(status)) {
/* smb_fname must be allocated off mem_ctx. */
smb_fname = cp_smb_filename(mem_ctx,
smb_fname_rel->fsp->fsp_name);
if (smb_fname == NULL) {
goto fail;
}
status = move_smb_fname_fsp_link(smb_fname, smb_fname_rel);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
goto done;
}
if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
/*
* Creating a new stream
*
* We should save the already-open base fsp for
* create_file_unixpath() somehow.
*/
smb_fname = full_path_from_dirfsp_atname(
mem_ctx, smb_dirname->fsp, smb_fname_rel);
if (smb_fname == NULL) {
status = NT_STATUS_NO_MEMORY;
goto fail;
}
/*
* When open_stream_pathref_fsp() returns
* NT_STATUS_OBJECT_NAME_NOT_FOUND, smb_fname_rel->fsp
* has been set to NULL, so we must free base_fsp separately
* to prevent fd-leaks when opening a stream that doesn't
* exist.
*/
fd_close(base_fsp);
file_free(NULL, base_fsp);
base_fsp = NULL;
goto done;
}
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
done:
*_dirfsp = smb_dirname->fsp;
*_smb_fname = smb_fname;
smbd: Simplify openat_pathref_fsp_case_insensitive() This is more lines of code, but it's still a simplification. With this patch we don't call the full openat_pathref_fsp() anymore when looking up the last component in filename_convert_dirfsp(), instead we do the direct SMB_VFS_OPENAT(). We don't need the whole complexity of non_widelink_open() for this case, we do know that we have a real non-cwd dirfsp. The other big change that is not obvious just from looking at the patch: This removes the special case for looking up posix symlinks. Before this patch, filename_convert_dirfsp() returned a proper smb_filename but without an attached fsp when a smb1 posix client hits a symlink. This caused all sorts of special case code everywhere. For example smbd_do_qfilepathinfo() needs to cover both cases just for the smb1 posix symlink case. This special-case handling can go now. We can do the path lookup in the smb1-only qpathinfo code and call into the common code with a proper fsp. When hitting a symlink and with O_PATH available, we'll get the symlink opened with an O_PATH fd. Without O_PATH we obviously can't do that, there we get fd=-1 and an indication that we don't have the procfd fallback around. Why all this? I want to present FIFOs (and eventually symlinks) as reparse points as the very next step. Without this patch, there is no real unified way to get the file attributes from disk. Now we can use the proper logic of fdos_mode() everywhere and not rely on special cases for fsp==NULL. This patch also changes some error codes for smb1 posix extensions. I chose to just change the test instead of going after each and every change. As long as we do get an error, I'm willing to accept that we slightly change error path behaviour for this deprecated code. And, I tried to split this up into smaller patches but I failed. Signed-off-by: Volker Lendecke <vl@samba.org> Reviewed-by: Ralph Boehme <slow@samba.org>
2023-10-18 12:50:20 +03:00
*_symlink_err = symlink_err;
smb_fname_fsp_unlink(smb_fname_rel);
TALLOC_FREE(smb_fname_rel);
return NT_STATUS_OK;
fail:
/*
* If open_stream_pathref_fsp() returns an error, smb_fname_rel->fsp
* has been set to NULL, so we must free base_fsp separately
* to prevent fd-leaks when opening a stream that doesn't
* exist.
*/
if (base_fsp != NULL) {
fd_close(base_fsp);
file_free(NULL, base_fsp);
base_fsp = NULL;
}
TALLOC_FREE(dirname);
TALLOC_FREE(smb_dirname);
TALLOC_FREE(smb_fname_rel);
return status;
}
NTSTATUS filename_convert_dirfsp(
TALLOC_CTX *mem_ctx,
connection_struct *conn,
const char *name_in,
uint32_t ucf_flags,
NTTIME twrp,
struct files_struct **_dirfsp,
struct smb_filename **_smb_fname)
{
struct reparse_data_buffer *symlink_err = NULL;
struct symlink_reparse_struct *lnk = NULL;
NTSTATUS status;
char *target = NULL;
char *safe_target = NULL;
size_t symlink_redirects = 0;
next:
if (symlink_redirects > 40) {
return NT_STATUS_OBJECT_PATH_NOT_FOUND;
}
status = filename_convert_dirfsp_nosymlink(mem_ctx,
conn,
name_in,
ucf_flags,
twrp,
_dirfsp,
_smb_fname,
&symlink_err);
smbd: Simplify openat_pathref_fsp_case_insensitive() This is more lines of code, but it's still a simplification. With this patch we don't call the full openat_pathref_fsp() anymore when looking up the last component in filename_convert_dirfsp(), instead we do the direct SMB_VFS_OPENAT(). We don't need the whole complexity of non_widelink_open() for this case, we do know that we have a real non-cwd dirfsp. The other big change that is not obvious just from looking at the patch: This removes the special case for looking up posix symlinks. Before this patch, filename_convert_dirfsp() returned a proper smb_filename but without an attached fsp when a smb1 posix client hits a symlink. This caused all sorts of special case code everywhere. For example smbd_do_qfilepathinfo() needs to cover both cases just for the smb1 posix symlink case. This special-case handling can go now. We can do the path lookup in the smb1-only qpathinfo code and call into the common code with a proper fsp. When hitting a symlink and with O_PATH available, we'll get the symlink opened with an O_PATH fd. Without O_PATH we obviously can't do that, there we get fd=-1 and an indication that we don't have the procfd fallback around. Why all this? I want to present FIFOs (and eventually symlinks) as reparse points as the very next step. Without this patch, there is no real unified way to get the file attributes from disk. Now we can use the proper logic of fdos_mode() everywhere and not rely on special cases for fsp==NULL. This patch also changes some error codes for smb1 posix extensions. I chose to just change the test instead of going after each and every change. As long as we do get an error, I'm willing to accept that we slightly change error path behaviour for this deprecated code. And, I tried to split this up into smaller patches but I failed. Signed-off-by: Volker Lendecke <vl@samba.org> Reviewed-by: Ralph Boehme <slow@samba.org>
2023-10-18 12:50:20 +03:00
if (NT_STATUS_IS_OK(status) && S_ISLNK((*_smb_fname)->st.st_ex_mode)) {
/*
* lcomp is a symlink
*/
if (ucf_flags & UCF_LCOMP_LNK_OK) {
TALLOC_FREE(symlink_err);
return NT_STATUS_OK;
}
close_file_free(NULL, _dirfsp, ERROR_CLOSE);
status = NT_STATUS_STOPPED_ON_SYMLINK;
}
if (!NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
return status;
}
lnk = &symlink_err->parsed.lnk;
smbd: Simplify openat_pathref_fsp_case_insensitive() This is more lines of code, but it's still a simplification. With this patch we don't call the full openat_pathref_fsp() anymore when looking up the last component in filename_convert_dirfsp(), instead we do the direct SMB_VFS_OPENAT(). We don't need the whole complexity of non_widelink_open() for this case, we do know that we have a real non-cwd dirfsp. The other big change that is not obvious just from looking at the patch: This removes the special case for looking up posix symlinks. Before this patch, filename_convert_dirfsp() returned a proper smb_filename but without an attached fsp when a smb1 posix client hits a symlink. This caused all sorts of special case code everywhere. For example smbd_do_qfilepathinfo() needs to cover both cases just for the smb1 posix symlink case. This special-case handling can go now. We can do the path lookup in the smb1-only qpathinfo code and call into the common code with a proper fsp. When hitting a symlink and with O_PATH available, we'll get the symlink opened with an O_PATH fd. Without O_PATH we obviously can't do that, there we get fd=-1 and an indication that we don't have the procfd fallback around. Why all this? I want to present FIFOs (and eventually symlinks) as reparse points as the very next step. Without this patch, there is no real unified way to get the file attributes from disk. Now we can use the proper logic of fdos_mode() everywhere and not rely on special cases for fsp==NULL. This patch also changes some error codes for smb1 posix extensions. I chose to just change the test instead of going after each and every change. As long as we do get an error, I'm willing to accept that we slightly change error path behaviour for this deprecated code. And, I tried to split this up into smaller patches but I failed. Signed-off-by: Volker Lendecke <vl@samba.org> Reviewed-by: Ralph Boehme <slow@samba.org>
2023-10-18 12:50:20 +03:00
/*
* If we're on an MSDFS share, see if this is
* an MSDFS link.
*/
if (lp_host_msdfs() && lp_msdfs_root(SNUM(conn)) &&
strnequal(lnk->substitute_name, "msdfs:", 6))
smbd: Simplify openat_pathref_fsp_case_insensitive() This is more lines of code, but it's still a simplification. With this patch we don't call the full openat_pathref_fsp() anymore when looking up the last component in filename_convert_dirfsp(), instead we do the direct SMB_VFS_OPENAT(). We don't need the whole complexity of non_widelink_open() for this case, we do know that we have a real non-cwd dirfsp. The other big change that is not obvious just from looking at the patch: This removes the special case for looking up posix symlinks. Before this patch, filename_convert_dirfsp() returned a proper smb_filename but without an attached fsp when a smb1 posix client hits a symlink. This caused all sorts of special case code everywhere. For example smbd_do_qfilepathinfo() needs to cover both cases just for the smb1 posix symlink case. This special-case handling can go now. We can do the path lookup in the smb1-only qpathinfo code and call into the common code with a proper fsp. When hitting a symlink and with O_PATH available, we'll get the symlink opened with an O_PATH fd. Without O_PATH we obviously can't do that, there we get fd=-1 and an indication that we don't have the procfd fallback around. Why all this? I want to present FIFOs (and eventually symlinks) as reparse points as the very next step. Without this patch, there is no real unified way to get the file attributes from disk. Now we can use the proper logic of fdos_mode() everywhere and not rely on special cases for fsp==NULL. This patch also changes some error codes for smb1 posix extensions. I chose to just change the test instead of going after each and every change. As long as we do get an error, I'm willing to accept that we slightly change error path behaviour for this deprecated code. And, I tried to split this up into smaller patches but I failed. Signed-off-by: Volker Lendecke <vl@samba.org> Reviewed-by: Ralph Boehme <slow@samba.org>
2023-10-18 12:50:20 +03:00
{
TALLOC_FREE(*_smb_fname);
TALLOC_FREE(symlink_err);
return NT_STATUS_PATH_NOT_COVERED;
}
if (!lp_follow_symlinks(SNUM(conn))) {
status = (lnk->unparsed_path_length == 0)
? NT_STATUS_OBJECT_NAME_NOT_FOUND
: NT_STATUS_OBJECT_PATH_NOT_FOUND;
TALLOC_FREE(symlink_err);
return status;
}
/*
* Right now, SMB2 and SMB1 always traverse symlinks
* within the share. SMB1+POSIX traverses non-terminal
* symlinks within the share.
*
* When we add SMB2+POSIX we need to return
* a NT_STATUS_STOPPED_ON_SYMLINK error here, using the
* symlink target data read below if SMB2+POSIX has
* UCF_POSIX_PATHNAMES set to cause the client to
* resolve all symlinks locally.
*/
target = symlink_target_path(mem_ctx,
name_in,
lnk->substitute_name,
lnk->unparsed_path_length);
if (target == NULL) {
return NT_STATUS_NO_MEMORY;
}
status = safe_symlink_target_path(mem_ctx,
conn->connectpath,
NULL,
target,
lnk->unparsed_path_length,
&safe_target);
TALLOC_FREE(symlink_err);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
name_in = safe_target;
symlink_redirects += 1;
goto next;
}
char *full_path_from_dirfsp_at_basename(TALLOC_CTX *mem_ctx,
const struct files_struct *dirfsp,
const char *at_base_name)
{
char *path = NULL;
if (dirfsp == dirfsp->conn->cwd_fsp ||
ISDOT(dirfsp->fsp_name->base_name) || at_base_name[0] == '/') {
path = talloc_strdup(mem_ctx, at_base_name);
} else {
path = talloc_asprintf(mem_ctx,
"%s/%s",
dirfsp->fsp_name->base_name,
at_base_name);
}
return path;
}
/*
* Build the full path from a dirfsp and dirfsp relative name
*/
struct smb_filename *
full_path_from_dirfsp_atname(TALLOC_CTX *mem_ctx,
const struct files_struct *dirfsp,
const struct smb_filename *atname)
{
struct smb_filename *fname = NULL;
char *path = NULL;
path = full_path_from_dirfsp_at_basename(mem_ctx,
dirfsp,
atname->base_name);
if (path == NULL) {
return NULL;
}
fname = synthetic_smb_fname(mem_ctx,
path,
atname->stream_name,
&atname->st,
atname->twrp,
atname->flags);
TALLOC_FREE(path);
if (fname == NULL) {
return NULL;
}
return fname;
}