s4-pvfs: change the handling of access checking on create
[ira/wip.git] / source4 / ntvfs / posix / pvfs_resolve.c
index c129038572208f5351577e8101f86dda7cf433d9..8c5806d93ffceac1047b268cfbd65622ae702f18 100644 (file)
@@ -57,7 +57,9 @@ static int component_compare(struct pvfs_state *pvfs, const char *comp, const ch
   TODO: add a cache for previously resolved case-insensitive names
   TODO: add mangled name support
 */
-static NTSTATUS pvfs_case_search(struct pvfs_state *pvfs, struct pvfs_filename *name)
+static NTSTATUS pvfs_case_search(struct pvfs_state *pvfs,
+                                struct pvfs_filename *name,
+                                uint_t flags)
 {
        /* break into a series of components */
        int num_components;
@@ -175,7 +177,7 @@ static NTSTATUS pvfs_case_search(struct pvfs_state *pvfs, struct pvfs_filename *
        name->full_name = partial_name;
 
        if (name->exists) {
-               return pvfs_fill_dos_info(pvfs, name, -1);
+               return pvfs_fill_dos_info(pvfs, name, flags, -1);
        }
 
        return NT_STATUS_OK;
@@ -184,25 +186,57 @@ static NTSTATUS pvfs_case_search(struct pvfs_state *pvfs, struct pvfs_filename *
 /*
   parse a alternate data stream name
 */
-static NTSTATUS parse_stream_name(struct pvfs_filename *name, const char *s)
+static NTSTATUS parse_stream_name(struct smb_iconv_convenience *ic,
+                                 struct pvfs_filename *name,
+                                 const char *s)
 {
-       char *p;
-       name->stream_name = talloc_strdup(name, s+1);
+       char *p, *stream_name;
+       if (s[1] == '\0') {
+               return NT_STATUS_OBJECT_NAME_INVALID;
+       }
+       name->stream_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 = stream_name;
+
+       while (*p) {
+               size_t c_size;
+               codepoint_t c = next_codepoint_convenience(ic, p, &c_size);
+
+               switch (c) {
+               case '/':
+               case '\\':
+                       return NT_STATUS_OBJECT_NAME_INVALID;
+               case ':':
+                       *p= 0;
+                       p++;
+                       if (*p == '\0') {
+                               return NT_STATUS_OBJECT_NAME_INVALID;
+                       }
+                       if (strcasecmp_m(p, "$DATA") != 0) {
+                               if (strchr_m(p, ':')) {
+                                       return NT_STATUS_OBJECT_NAME_INVALID;
+                               }
+                               return NT_STATUS_INVALID_PARAMETER;
+                       }
+                       c_size = 0;
+                       p--;
+                       break;
+               }
+
+               p += c_size;
        }
-       *p = 0;
+
        if (strcmp(name->stream_name, "") == 0) {
-               name->stream_name = NULL;
+               /*
+                * we don't set stream_name to NULL, here
+                * as this would be wrong for directories
+                *
+                * pvfs_fill_dos_info() will set it to NULL
+                * if it's not a directory.
+                */
                name->stream_id = 0;
        } else {
                name->stream_id = pvfs_name_hash(name->stream_name, 
@@ -225,6 +259,7 @@ static NTSTATUS pvfs_unix_path(struct pvfs_state *pvfs, const char *cifs_name,
                               uint_t flags, struct pvfs_filename *name)
 {
        char *ret, *p, *p_start;
+       struct smb_iconv_convenience *ic = NULL;
        NTSTATUS status;
 
        name->original_name = talloc_strdup(name, cifs_name);
@@ -255,30 +290,43 @@ static NTSTATUS pvfs_unix_path(struct pvfs_state *pvfs, const char *cifs_name,
           for legal characters */
        p_start = p;
 
+       ic = lp_iconv_convenience(pvfs->ntvfs->ctx->lp_ctx);
        while (*p) {
                size_t c_size;
-               codepoint_t c = next_codepoint(lp_iconv_convenience(pvfs->ntvfs->ctx->lp_ctx), p, &c_size);
+               codepoint_t c = next_codepoint_convenience(ic, p, &c_size);
+
+               if (c <= 0x1F) {
+                       return NT_STATUS_OBJECT_NAME_INVALID;
+               }
+
                switch (c) {
                case '\\':
                        if (name->has_wildcard) {
                                /* wildcards are only allowed in the last part
                                   of a name */
-                               return NT_STATUS_ILLEGAL_CHARACTER;
+                               return NT_STATUS_OBJECT_NAME_INVALID;
                        }
-                       if (p > p_start && p[1] == 0) {
-                               *p = 0;
+                       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;
+                               return NT_STATUS_OBJECT_NAME_INVALID;
                        }
                        if (name->has_wildcard) {
-                               return NT_STATUS_ILLEGAL_CHARACTER;
+                               return NT_STATUS_OBJECT_NAME_INVALID;
                        }
-                       status = parse_stream_name(name, p);
+                       status = parse_stream_name(ic, name, p);
                        if (!NT_STATUS_IS_OK(status)) {
                                return status;
                        }
@@ -296,7 +344,7 @@ static NTSTATUS pvfs_unix_path(struct pvfs_state *pvfs, const char *cifs_name,
                        break;
                case '/':
                case '|':
-                       return NT_STATUS_ILLEGAL_CHARACTER;
+                       return NT_STATUS_OBJECT_NAME_INVALID;
                case '.':
                        /* see if it is definately a .. or
                           . component. If it is then fail here, and
@@ -343,7 +391,7 @@ static NTSTATUS pvfs_reduce_name(TALLOC_CTX *mem_ctx,
        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);
+               c = next_codepoint_convenience(iconv_convenience, p, &c_size);
                if (c == '\\') num_components++;
        }
 
@@ -355,7 +403,7 @@ static NTSTATUS pvfs_reduce_name(TALLOC_CTX *mem_ctx,
 
        components[0] = s;
        for (i=0, p=s; *p; p += c_size) {
-               c = next_codepoint(iconv_convenience, p, &c_size);
+               c = next_codepoint_convenience(iconv_convenience, p, &c_size);
                if (c == '\\') {
                        *p = 0;
                        components[++i] = p+1;
@@ -416,7 +464,7 @@ static NTSTATUS pvfs_reduce_name(TALLOC_CTX *mem_ctx,
        }
 
        /* rebuild the name */
-       ret = talloc_size(mem_ctx, len+1);
+       ret = talloc_array(mem_ctx, char, len+1);
        if (ret == NULL) {
                talloc_free(s);
                return NT_STATUS_NO_MEMORY;
@@ -430,6 +478,8 @@ static NTSTATUS pvfs_reduce_name(TALLOC_CTX *mem_ctx,
        }       
        ret[len] = 0;
 
+       talloc_set_name_const(ret, ret);
+
        talloc_free(s);
 
        *fname = ret;
@@ -448,13 +498,14 @@ static NTSTATUS pvfs_reduce_name(TALLOC_CTX *mem_ctx,
 
      TODO: ../ collapsing, and outside share checking
 */
-NTSTATUS pvfs_resolve_name(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
+NTSTATUS pvfs_resolve_name(struct pvfs_state *pvfs, 
+                          struct ntvfs_request *req,
                           const char *cifs_name,
                           uint_t flags, struct pvfs_filename **name)
 {
        NTSTATUS status;
 
-       *name = talloc(mem_ctx, struct pvfs_filename);
+       *name = talloc(req, struct pvfs_filename);
        if (*name == NULL) {
                return NT_STATUS_NO_MEMORY;
        }
@@ -466,6 +517,12 @@ NTSTATUS pvfs_resolve_name(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
                flags &= ~PVFS_RESOLVE_STREAMS;
        }
 
+       /* SMB2 doesn't allow a leading slash */
+       if (req->ctx->protocol == PROTOCOL_SMB2 &&
+           *cifs_name == '\\') {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
        /* do the basic conversion to a unix formatted path,
           also checking for allowable characters */
        status = pvfs_unix_path(pvfs, cifs_name, flags, *name);
@@ -502,7 +559,7 @@ NTSTATUS pvfs_resolve_name(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
                /* we need to search for a matching name */
                saved_name = (*name)->full_name;
                (*name)->full_name = dir_name;
-               status = pvfs_case_search(pvfs, *name);
+               status = pvfs_case_search(pvfs, *name, flags);
                if (!NT_STATUS_IS_OK(status)) {
                        /* the directory doesn't exist */
                        (*name)->full_name = saved_name;
@@ -523,11 +580,11 @@ NTSTATUS pvfs_resolve_name(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
        /* if we can stat() the full name now then we are done */
        if (stat((*name)->full_name, &(*name)->st) == 0) {
                (*name)->exists = true;
-               return pvfs_fill_dos_info(pvfs, *name, -1);
+               return pvfs_fill_dos_info(pvfs, *name, flags, -1);
        }
 
        /* search for a matching filename */
-       status = pvfs_case_search(pvfs, *name);
+       status = pvfs_case_search(pvfs, *name, flags);
 
        return status;
 }
@@ -543,7 +600,7 @@ NTSTATUS pvfs_resolve_name(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
 */
 NTSTATUS pvfs_resolve_partial(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
                              const char *unix_dir, const char *fname,
-                             struct pvfs_filename **name)
+                             uint_t flags, struct pvfs_filename **name)
 {
        NTSTATUS status;
 
@@ -568,7 +625,7 @@ NTSTATUS pvfs_resolve_partial(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
        (*name)->stream_name = NULL;
        (*name)->stream_id = 0;
 
-       status = pvfs_fill_dos_info(pvfs, *name, -1);
+       status = pvfs_fill_dos_info(pvfs, *name, flags, -1);
 
        return status;
 }
@@ -580,7 +637,7 @@ NTSTATUS pvfs_resolve_partial(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
   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)
+                             struct pvfs_filename *name, uint_t flags)
 {
        dev_t device = (dev_t)0;
        ino_t inode = 0;
@@ -613,7 +670,95 @@ NTSTATUS pvfs_resolve_name_fd(struct pvfs_state *pvfs, int fd,
 
        name->exists = true;
        
-       return pvfs_fill_dos_info(pvfs, name, fd);
+       return pvfs_fill_dos_info(pvfs, name, flags, fd);
+}
+
+/*
+  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 the path
+  after a possible rename on a different handle.
+*/
+NTSTATUS pvfs_resolve_name_handle(struct pvfs_state *pvfs,
+                                 struct pvfs_file_handle *h)
+{
+       NTSTATUS status;
+
+       if (h->have_opendb_entry) {
+               struct odb_lock *lck;
+               const char *name = NULL;
+
+               lck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key);
+               if (lck == NULL) {
+                       DEBUG(0,("%s: failed to lock file '%s' in opendb\n",
+                                __FUNCTION__, h->name->full_name));
+                       /* we were supposed to do a blocking lock, so something
+                          is badly wrong! */
+                       return NT_STATUS_INTERNAL_DB_CORRUPTION;
+               }
+
+               status = odb_get_path(lck, &name);
+               if (NT_STATUS_IS_OK(status)) {
+                       /*
+                        * This relies an the fact that
+                        * renames of open files are only
+                        * allowed by setpathinfo() and setfileinfo()
+                        * and there're only renames within the same
+                        * directory supported
+                        */
+                       if (strcmp(h->name->full_name, name) != 0) {
+                               const char *orig_dir;
+                               const char *new_file;
+                               const char *new_orig;
+                               char *delim;
+
+                               delim = strrchr(name, '/');
+                               if (!delim) {
+                                       talloc_free(lck);
+                                       return NT_STATUS_INTERNAL_ERROR;
+                               }
+
+                               new_file = delim + 1;
+                               delim = strrchr(h->name->original_name, '\\');
+                               if (delim) {
+                                       delim[0] = '\0';
+                                       orig_dir = h->name->original_name;
+                                       new_orig = talloc_asprintf(h->name, "%s\\%s",
+                                                                  orig_dir, new_file);
+                                       if (!new_orig) {
+                                               talloc_free(lck);
+                                               return NT_STATUS_NO_MEMORY;
+                                       }
+                               } else {
+                                       new_orig = talloc_strdup(h->name, new_file);
+                                       if (!new_orig) {
+                                               talloc_free(lck);
+                                               return NT_STATUS_NO_MEMORY;
+                                       }
+                               }
+
+                               talloc_free(h->name->original_name);
+                               talloc_free(h->name->full_name);
+                               h->name->full_name = talloc_steal(h->name, name);
+                               h->name->original_name = new_orig;
+                       }
+               }
+
+               talloc_free(lck);
+       }
+
+       /*
+        * TODO: pass PVFS_RESOLVE_NO_OPENDB and get
+        *       the write time from odb_lock() above.
+        */
+       status = pvfs_resolve_name_fd(pvfs, h->fd, h->name, 0);
+       NT_STATUS_NOT_OK_RETURN(status);
+
+       if (!null_nttime(h->write_time.close_time)) {
+               h->name->dos.write_time = h->write_time.close_time;
+       }
+
+       return NT_STATUS_OK;
 }
 
 
@@ -658,11 +803,14 @@ NTSTATUS pvfs_resolve_parent(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
        (*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)->original_name = talloc_strdup(*name, child->original_name);
+       if ((*name)->original_name == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
        (*name)->stream_name = NULL;
        (*name)->stream_id = 0;
 
-       status = pvfs_fill_dos_info(pvfs, *name, -1);
+       status = pvfs_fill_dos_info(pvfs, *name, PVFS_RESOLVE_NO_OPENDB, -1);
 
        return status;
 }