r3447: more include/system/XXX.h include files
[jelmer/samba4-debian.git] / source / ntvfs / posix / pvfs_resolve.c
index 5c535c98801c428a2a4693f0307a6efa7be3b402..551b05b248a32dc9cc12ee1d76485be43b43472d 100644 (file)
 
 #include "include/includes.h"
 #include "vfs_posix.h"
+#include "system/dir.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(comp, name);
+
+       if (ret != 0) {
+               char *shortname = pvfs_short_name_component(pvfs, name);
+               if (shortname) {
+                       ret = StrCaseCmp(comp, shortname);
+                       talloc_free(shortname);
+               }
+       }
+
+       return ret;
 }
 
 /*
@@ -72,6 +85,9 @@ static NTSTATUS pvfs_case_search(struct pvfs_state *pvfs, struct pvfs_filename *
                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]);
@@ -85,6 +101,13 @@ static NTSTATUS pvfs_case_search(struct pvfs_state *pvfs, struct pvfs_filename *
                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) {
@@ -94,7 +117,7 @@ static NTSTATUS pvfs_case_search(struct pvfs_state *pvfs, struct pvfs_filename *
                /* 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;
@@ -103,6 +126,18 @@ static NTSTATUS pvfs_case_search(struct pvfs_state *pvfs, struct pvfs_filename *
                        }
                        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) {
@@ -110,7 +145,7 @@ static NTSTATUS pvfs_case_search(struct pvfs_state *pvfs, struct pvfs_filename *
                }
 
                while ((de = readdir(dir))) {
-                       if (component_compare(components[i], de->d_name) == 0) {
+                       if (component_compare(pvfs, components[i], de->d_name) == 0) {
                                break;
                        }
                }
@@ -118,7 +153,7 @@ static NTSTATUS pvfs_case_search(struct pvfs_state *pvfs, struct pvfs_filename *
                if (!de) {
                        if (i < num_components-1) {
                                closedir(dir);
-                               return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+                               return NT_STATUS_OBJECT_PATH_NOT_FOUND;
                        }
                } else {
                        components[i] = talloc_strdup(name, de->d_name);
@@ -158,16 +193,24 @@ static NTSTATUS pvfs_case_search(struct pvfs_state *pvfs, struct pvfs_filename *
 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;
 
        name->original_name = talloc_strdup(name, cifs_name);
        name->stream_name = NULL;
        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;
@@ -177,15 +220,23 @@ static NTSTATUS pvfs_unix_path(struct pvfs_state *pvfs, const char *cifs_name,
 
        /* 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(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] == 0) {
+                               *p = 0;
+                       } else {
+                               *p = '/';
+                       }
                        break;
                case ':':
                        if (!(flags & PVFS_RESOLVE_STREAMS)) {
@@ -203,14 +254,33 @@ static NTSTATUS pvfs_unix_path(struct pvfs_state *pvfs, const char *cifs_name,
                case '?':
                case '"':
                        if (flags & PVFS_RESOLVE_NO_WILDCARD) {
-                               return NT_STATUS_ILLEGAL_CHARACTER;
+                               return NT_STATUS_OBJECT_NAME_INVALID;
                        }
                        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;
@@ -219,6 +289,115 @@ static NTSTATUS pvfs_unix_path(struct pvfs_state *pvfs, const char *cifs_name,
 }
 
 
+/*
+  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, 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(p, &c_size);
+               if (c == '\\') num_components++;
+       }
+
+       components = talloc_array_p(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(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 (strcmp(components[i], ".") == 0 || err_count) {
+                       err_count++;
+               }
+       }
+       if (err_count) {
+               if (!(flags & PVFS_RESOLVE_NO_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--;
+               }
+               if (strcmp(components[i], "..") == 0) {
+                       if (i < 1) return NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+                       memmove(&components[i-1], &components[i+1], 
+                               sizeof(char *)*(num_components-(i+1)));
+                       i -= 2;
+               }
+       }
+
+       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(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'
@@ -227,7 +406,6 @@ static NTSTATUS pvfs_unix_path(struct pvfs_state *pvfs, const char *cifs_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,
@@ -246,6 +424,16 @@ NTSTATUS pvfs_resolve_name(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
        /* 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, &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;
        }
@@ -261,12 +449,6 @@ NTSTATUS pvfs_resolve_name(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
                return pvfs_fill_dos_info(pvfs, *name);
        }
 
-       /* the filesystem might be case insensitive, in which
-          case a search is pointless */
-       if (pvfs->flags & PVFS_FLAG_CI_FILESYSTEM) {
-               return NT_STATUS_OK;
-       }
-
        /* search for a matching filename */
        status = pvfs_case_search(pvfs, *name);
 
@@ -321,8 +503,33 @@ NTSTATUS pvfs_resolve_partial(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
 NTSTATUS pvfs_resolve_name_fd(struct pvfs_state *pvfs, int fd,
                              struct pvfs_filename *name)
 {
-       if (fstat(fd, &name->st) == -1) {
-               return NT_STATUS_INVALID_HANDLE;
+       dev_t device;
+       ino_t inode;
+
+       if (name->exists) {
+               device = name->st.st_dev;
+               inode = name->st.st_ino;
+       }
+
+       if (name->exists && (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)) {
+               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 resole - failing\n",
+                        name->full_name));
+               return NT_STATUS_UNEXPECTED_IO_ERROR;
        }
 
        name->exists = True;