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 2 of the License, or
+ 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,
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, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
*/
-#include "include/includes.h"
+#include "includes.h"
#include "vfs_posix.h"
+#include "system/dir.h"
+#include "param/param.h"
-/*
+/**
compare two filename components. This is where the name mangling hook will go
*/
-static int component_compare(const char *c1, const char *c2)
+static int component_compare(struct pvfs_state *pvfs, const char *comp, const char *name)
{
- return StrCaseCmp(c1, c2);
+ int ret;
+
+ ret = strcasecmp_m(comp, name);
+
+ if (ret != 0) {
+ char *shortname = pvfs_short_name_component(pvfs, name);
+ if (shortname) {
+ ret = strcasecmp_m(comp, shortname);
+ talloc_free(shortname);
+ }
+ }
+
+ return ret;
}
/*
}
}
- components = talloc_array_p(name, char *, num_components);
+ components = talloc_array(name, char *, num_components);
p = name->full_name + strlen(pvfs->base_directory);
*p++ = 0;
components[i] = p;
p = strchr(p, '/');
if (p) *p++ = 0;
+ if (pvfs_is_reserved_name(pvfs, components[i])) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
}
partial_name = talloc_strdup(name, components[0]);
char *test_name;
DIR *dir;
struct dirent *de;
+ char *long_component;
+
+ /* possibly remap from the short name cache */
+ long_component = pvfs_mangled_lookup(pvfs, name, components[i]);
+ if (long_component) {
+ components[i] = long_component;
+ }
test_name = talloc_asprintf(name, "%s/%s", partial_name, components[i]);
if (!test_name) {
/* check if this component exists as-is */
if (stat(test_name, &name->st) == 0) {
if (i<num_components-1 && !S_ISDIR(name->st.st_mode)) {
- return NT_STATUS_NOT_A_DIRECTORY;
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
}
talloc_free(partial_name);
partial_name = test_name;
if (i == num_components - 1) {
- name->exists = True;
+ name->exists = true;
}
continue;
}
+
+ /* the filesystem might be case insensitive, in which
+ case a search is pointless unless the name is
+ mangled */
+ if ((pvfs->flags & PVFS_FLAG_CI_FILESYSTEM) &&
+ !pvfs_is_mangled_component(pvfs, components[i])) {
+ if (i < num_components-1) {
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ partial_name = test_name;
+ continue;
+ }
dir = opendir(partial_name);
if (!dir) {
}
while ((de = readdir(dir))) {
- if (component_compare(components[i], de->d_name) == 0) {
+ if (component_compare(pvfs, components[i], de->d_name) == 0) {
break;
}
}
if (!de) {
- closedir(dir);
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ if (i < num_components-1) {
+ closedir(dir);
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ } else {
+ components[i] = talloc_strdup(name, de->d_name);
}
-
- components[i] = talloc_strdup(name, de->d_name);
test_name = talloc_asprintf(name, "%s/%s", partial_name, components[i]);
talloc_free(partial_name);
partial_name = test_name;
if (!name->exists) {
if (stat(partial_name, &name->st) == 0) {
- name->exists = True;
+ name->exists = true;
}
}
talloc_free(name->full_name);
name->full_name = partial_name;
+ if (name->exists) {
+ return pvfs_fill_dos_info(pvfs, name, -1);
+ }
+
return NT_STATUS_OK;
}
+/*
+ parse a alternate data stream name
+*/
+static NTSTATUS parse_stream_name(struct pvfs_filename *name, const char *s)
+{
+ char *p;
+ name->stream_name = talloc_strdup(name, s+1);
+ if (name->stream_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ p = strchr_m(name->stream_name, ':');
+ if (p == NULL) {
+ name->stream_id = pvfs_name_hash(name->stream_name,
+ strlen(name->stream_name));
+ return NT_STATUS_OK;
+ }
+ if (strcasecmp_m(p, ":$DATA") != 0) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ *p = 0;
+ if (strcmp(name->stream_name, "") == 0) {
+ name->stream_name = NULL;
+ name->stream_id = 0;
+ } else {
+ name->stream_id = pvfs_name_hash(name->stream_name,
+ strlen(name->stream_name));
+ }
+
+ return NT_STATUS_OK;
+}
+
/*
convert a CIFS pathname to a unix pathname. Note that this does NOT
static NTSTATUS pvfs_unix_path(struct pvfs_state *pvfs, const char *cifs_name,
uint_t flags, struct pvfs_filename *name)
{
- char *ret, *p;
+ char *ret, *p, *p_start;
+ NTSTATUS status;
- name->original_name = cifs_name;
+ name->original_name = talloc_strdup(name, cifs_name);
name->stream_name = NULL;
- name->has_wildcard = False;
+ name->stream_id = 0;
+ name->has_wildcard = false;
- if (*cifs_name == '\\') {
+ while (*cifs_name == '\\') {
cifs_name++;
}
+ if (*cifs_name == 0) {
+ name->full_name = talloc_asprintf(name, "%s/.", pvfs->base_directory);
+ if (name->full_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+ }
+
ret = talloc_asprintf(name, "%s/%s", pvfs->base_directory, cifs_name);
if (ret == NULL) {
return NT_STATUS_NO_MEMORY;
/* now do an in-place conversion of '\' to '/', checking
for legal characters */
- for (;*p;p++) {
- switch (*p) {
+ p_start = p;
+
+ while (*p) {
+ size_t c_size;
+ codepoint_t c = next_codepoint(lp_iconv_convenience(pvfs->ntvfs->ctx->lp_ctx), p, &c_size);
+ switch (c) {
case '\\':
if (name->has_wildcard) {
/* wildcards are only allowed in the last part
of a name */
return NT_STATUS_ILLEGAL_CHARACTER;
}
- *p = '/';
+ if (p > p_start && (p[1] == '\\' || p[1] == '\0')) {
+ /* see if it is definately a "\\" or
+ * a trailing "\". If it is then fail here,
+ * and let the next layer up try again after
+ * pvfs_reduce_name() if it wants to. This is
+ * much more efficient on average than always
+ * scanning for these separately
+ */
+ return NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+ } else {
+ *p = '/';
+ }
break;
case ':':
if (!(flags & PVFS_RESOLVE_STREAMS)) {
return NT_STATUS_ILLEGAL_CHARACTER;
}
- name->stream_name = talloc_strdup(name, p+1);
- if (name->stream_name == NULL) {
- return NT_STATUS_NO_MEMORY;
+ if (name->has_wildcard) {
+ return NT_STATUS_ILLEGAL_CHARACTER;
+ }
+ status = parse_stream_name(name, p);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
}
*p-- = 0;
break;
case '<':
case '?':
case '"':
- if (flags & PVFS_RESOLVE_NO_WILDCARD) {
- return NT_STATUS_ILLEGAL_CHARACTER;
+ if (!(flags & PVFS_RESOLVE_WILDCARD)) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
}
- name->has_wildcard = True;
+ name->has_wildcard = true;
break;
case '/':
case '|':
return NT_STATUS_ILLEGAL_CHARACTER;
+ case '.':
+ /* see if it is definately a .. or
+ . component. If it is then fail here, and
+ let the next layer up try again after
+ pvfs_reduce_name() if it wants to. This is
+ much more efficient on average than always
+ scanning for these separately */
+ if (p[1] == '.' &&
+ (p[2] == 0 || p[2] == '\\') &&
+ (p == p_start || p[-1] == '/')) {
+ return NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+ }
+ if ((p[1] == 0 || p[1] == '\\') &&
+ (p == p_start || p[-1] == '/')) {
+ return NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+ }
+ break;
}
+
+ p += c_size;
}
name->full_name = ret;
}
+/*
+ reduce a name that contains .. components or repeated \ separators
+ return NULL if it can't be reduced
+*/
+static NTSTATUS pvfs_reduce_name(TALLOC_CTX *mem_ctx,
+ struct smb_iconv_convenience *iconv_convenience,
+ const char **fname, uint_t flags)
+{
+ codepoint_t c;
+ size_t c_size, len;
+ int i, num_components, err_count;
+ char **components;
+ char *p, *s, *ret;
+
+ s = talloc_strdup(mem_ctx, *fname);
+ if (s == NULL) return NT_STATUS_NO_MEMORY;
+
+ for (num_components=1, p=s; *p; p += c_size) {
+ c = next_codepoint(iconv_convenience, p, &c_size);
+ if (c == '\\') num_components++;
+ }
+
+ components = talloc_array(s, char *, num_components+1);
+ if (components == NULL) {
+ talloc_free(s);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ components[0] = s;
+ for (i=0, p=s; *p; p += c_size) {
+ c = next_codepoint(iconv_convenience, p, &c_size);
+ if (c == '\\') {
+ *p = 0;
+ components[++i] = p+1;
+ }
+ }
+ components[i+1] = NULL;
+
+ /*
+ rather bizarre!
+
+ '.' components are not allowed, but the rules for what error
+ code to give don't seem to make sense. This is a close
+ approximation.
+ */
+ for (err_count=i=0;components[i];i++) {
+ if (strcmp(components[i], "") == 0) {
+ continue;
+ }
+ if (ISDOT(components[i]) || err_count) {
+ err_count++;
+ }
+ }
+ if (err_count) {
+ if (flags & PVFS_RESOLVE_WILDCARD) err_count--;
+
+ if (err_count==1) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ } else {
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ }
+
+ /* remove any null components */
+ for (i=0;components[i];i++) {
+ if (strcmp(components[i], "") == 0) {
+ memmove(&components[i], &components[i+1],
+ sizeof(char *)*(num_components-i));
+ i--;
+ continue;
+ }
+ if (ISDOTDOT(components[i])) {
+ if (i < 1) return NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+ memmove(&components[i-1], &components[i+1],
+ sizeof(char *)*(num_components-i));
+ i -= 2;
+ continue;
+ }
+ }
+
+ if (components[0] == NULL) {
+ talloc_free(s);
+ *fname = talloc_strdup(mem_ctx, "\\");
+ return NT_STATUS_OK;
+ }
+
+ for (len=i=0;components[i];i++) {
+ len += strlen(components[i]) + 1;
+ }
+
+ /* rebuild the name */
+ ret = talloc_size(mem_ctx, len+1);
+ if (ret == NULL) {
+ talloc_free(s);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (len=0,i=0;components[i];i++) {
+ size_t len1 = strlen(components[i]);
+ ret[len] = '\\';
+ memcpy(ret+len+1, components[i], len1);
+ len += len1 + 1;
+ }
+ ret[len] = 0;
+
+ talloc_free(s);
+
+ *fname = ret;
+
+ return NT_STATUS_OK;
+}
+
+
/*
resolve a name from relative client format to a struct pvfs_filename
the memory for the filename is made as a talloc child of 'name'
PVFS_RESOLVE_NO_WILDCARD = wildcards are considered illegal characters
PVFS_RESOLVE_STREAMS = stream names are allowed
- TODO: add reserved name checking (for things like LPT1)
TODO: ../ collapsing, and outside share checking
*/
NTSTATUS pvfs_resolve_name(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
{
NTSTATUS status;
- *name = talloc_p(mem_ctx, struct pvfs_filename);
+ *name = talloc(mem_ctx, struct pvfs_filename);
if (*name == NULL) {
return NT_STATUS_NO_MEMORY;
}
- (*name)->exists = False;
+ (*name)->exists = false;
+ (*name)->stream_exists = false;
+
+ if (!(pvfs->fs_attribs & FS_ATTR_NAMED_STREAMS)) {
+ flags &= ~PVFS_RESOLVE_STREAMS;
+ }
/* do the basic conversion to a unix formatted path,
also checking for allowable characters */
status = pvfs_unix_path(pvfs, cifs_name, flags, *name);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD)) {
+ /* it might contain .. components which need to be reduced */
+ status = pvfs_reduce_name(*name, lp_iconv_convenience(pvfs->ntvfs->ctx->lp_ctx), &cifs_name, flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = pvfs_unix_path(pvfs, cifs_name, flags, *name);
+ }
+
if (!NT_STATUS_IS_OK(status)) {
return status;
}
- /* if it has a wildcard then no point doing a stat() */
+ /* if it has a wildcard then no point doing a stat() of the
+ full name. Instead We need check if the directory exists
+ */
if ((*name)->has_wildcard) {
+ const char *p;
+ char *dir_name, *saved_name;
+ p = strrchr((*name)->full_name, '/');
+ if (p == NULL) {
+ /* root directory wildcard is OK */
+ return NT_STATUS_OK;
+ }
+ dir_name = talloc_strndup(*name, (*name)->full_name, (p-(*name)->full_name));
+ if (stat(dir_name, &(*name)->st) == 0) {
+ talloc_free(dir_name);
+ return NT_STATUS_OK;
+ }
+ /* we need to search for a matching name */
+ saved_name = (*name)->full_name;
+ (*name)->full_name = dir_name;
+ status = pvfs_case_search(pvfs, *name);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* the directory doesn't exist */
+ (*name)->full_name = saved_name;
+ return status;
+ }
+ /* it does exist, but might need a case change */
+ if (dir_name != (*name)->full_name) {
+ (*name)->full_name = talloc_asprintf(*name, "%s%s",
+ (*name)->full_name, p);
+ NT_STATUS_HAVE_NO_MEMORY((*name)->full_name);
+ } else {
+ (*name)->full_name = saved_name;
+ talloc_free(dir_name);
+ }
return NT_STATUS_OK;
}
/* if we can stat() the full name now then we are done */
if (stat((*name)->full_name, &(*name)->st) == 0) {
- (*name)->exists = True;
- return NT_STATUS_OK;
- }
-
- /* the filesystem might be case insensitive, in which
- case a search is pointless */
- if (pvfs->flags & PVFS_FLAG_CI_FILESYSTEM) {
- return NT_STATUS_OK;
+ (*name)->exists = true;
+ return pvfs_fill_dos_info(pvfs, *name, -1);
}
/* search for a matching filename */
const char *unix_dir, const char *fname,
struct pvfs_filename **name)
{
- *name = talloc_p(mem_ctx, struct pvfs_filename);
+ NTSTATUS status;
+
+ *name = talloc(mem_ctx, struct pvfs_filename);
if (*name == NULL) {
return NT_STATUS_NO_MEMORY;
}
- (*name)->full_name = talloc_asprintf(mem_ctx, "%s/%s", unix_dir, fname);
+ (*name)->full_name = talloc_asprintf(*name, "%s/%s", unix_dir, fname);
if ((*name)->full_name == NULL) {
return NT_STATUS_NO_MEMORY;
}
return NT_STATUS_OBJECT_NAME_NOT_FOUND;
}
- (*name)->exists = True;
- (*name)->has_wildcard = False;
- (*name)->original_name = fname;
+ (*name)->exists = true;
+ (*name)->stream_exists = true;
+ (*name)->has_wildcard = false;
+ (*name)->original_name = talloc_strdup(*name, fname);
(*name)->stream_name = NULL;
+ (*name)->stream_id = 0;
- return NT_STATUS_OK;
+ status = pvfs_fill_dos_info(pvfs, *name, -1);
+
+ return status;
+}
+
+
+/*
+ fill in the pvfs_filename info for an open file, given the current
+ info for a (possibly) non-open file. This is used by places that need
+ to update the pvfs_filename stat information, and by pvfs_open()
+*/
+NTSTATUS pvfs_resolve_name_fd(struct pvfs_state *pvfs, int fd,
+ struct pvfs_filename *name)
+{
+ dev_t device = (dev_t)0;
+ ino_t inode = 0;
+
+ if (name->exists) {
+ device = name->st.st_dev;
+ inode = name->st.st_ino;
+ }
+
+ if (fd == -1) {
+ if (stat(name->full_name, &name->st) == -1) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ } else {
+ if (fstat(fd, &name->st) == -1) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ }
+
+ if (name->exists &&
+ (device != name->st.st_dev || inode != name->st.st_ino)) {
+ /* the file we are looking at has changed! this could
+ be someone trying to exploit a race
+ condition. Certainly we don't want to continue
+ operating on this file */
+ DEBUG(0,("pvfs: WARNING: file '%s' changed during resolve - failing\n",
+ name->full_name));
+ return NT_STATUS_UNEXPECTED_IO_ERROR;
+ }
+
+ name->exists = true;
+
+ return pvfs_fill_dos_info(pvfs, name, fd);
+}
+
+
+/*
+ resolve the parent of a given name
+*/
+NTSTATUS pvfs_resolve_parent(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
+ const struct pvfs_filename *child,
+ struct pvfs_filename **name)
+{
+ NTSTATUS status;
+ char *p;
+
+ *name = talloc(mem_ctx, struct pvfs_filename);
+ if (*name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ (*name)->full_name = talloc_strdup(*name, child->full_name);
+ if ((*name)->full_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ p = strrchr_m((*name)->full_name, '/');
+ if (p == NULL) {
+ return NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+ }
+
+ /* this handles the root directory */
+ if (p == (*name)->full_name) {
+ p[1] = 0;
+ } else {
+ p[0] = 0;
+ }
+
+ if (stat((*name)->full_name, &(*name)->st) == -1) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ (*name)->exists = true;
+ (*name)->stream_exists = true;
+ (*name)->has_wildcard = false;
+ /* we can't get the correct 'original_name', but for the purposes
+ of this call this is close enough */
+ (*name)->original_name = talloc_reference(*name, child->original_name);
+ (*name)->stream_name = NULL;
+ (*name)->stream_id = 0;
+
+ status = pvfs_fill_dos_info(pvfs, *name, -1);
+
+ return status;
}