s3-auth: Split out get_user_sid_info3_and_extra() from create_local_nt_token_from_info3()
[amitay/samba.git] / source3 / modules / vfs_shadow_copy2.c
index 6896a16353000107d5efed08dfd2cfe32e4d0082..2afc5954e7d26f5a4cc8b681c378e8f87d89e263 100644 (file)
@@ -35,6 +35,7 @@
 #include "system/filesys.h"
 #include "include/ntioctl.h"
 #include "util_tdb.h"
+#include "lib/util_path.h"
 
 struct shadow_copy2_config {
        char *gmt_format;
@@ -74,6 +75,11 @@ struct shadow_copy2_snaplist_info {
 struct shadow_copy2_private {
        struct shadow_copy2_config *config;
        struct shadow_copy2_snaplist_info *snaps;
+       char *shadow_cwd; /* Absolute $cwd path. */
+       /* Absolute connectpath - can vary depending on $cwd. */
+       char *shadow_connectpath;
+       /* malloc'ed realpath return. */
+       char *shadow_realpath;
 };
 
 static int shadow_copy2_get_shadow_copy_data(
@@ -404,16 +410,180 @@ static char *shadow_copy2_snapshot_path(TALLOC_CTX *mem_ctx,
        return result;
 }
 
+static char *make_path_absolute(TALLOC_CTX *mem_ctx,
+                               struct shadow_copy2_private *priv,
+                               const char *name)
+{
+       char *newpath = NULL;
+       char *abs_path = NULL;
+
+       if (name[0] != '/') {
+               newpath = talloc_asprintf(mem_ctx,
+                                       "%s/%s",
+                                       priv->shadow_cwd,
+                                       name);
+               if (newpath == NULL) {
+                       return NULL;
+               }
+               name = newpath;
+       }
+       abs_path = canonicalize_absolute_path(mem_ctx, name);
+       TALLOC_FREE(newpath);
+       return abs_path;
+}
+
+/* Return a $cwd-relative path. */
+static bool make_relative_path(const char *cwd, char *abs_path)
+{
+       size_t cwd_len = strlen(cwd);
+       size_t abs_len = strlen(abs_path);
+
+       if (abs_len < cwd_len) {
+               return false;
+       }
+       if (memcmp(abs_path, cwd, cwd_len) != 0) {
+               return false;
+       }
+       if (abs_path[cwd_len] != '/' && abs_path[cwd_len] != '\0') {
+               return false;
+       }
+       if (abs_path[cwd_len] == '/') {
+               cwd_len++;
+       }
+       memmove(abs_path, &abs_path[cwd_len], abs_len + 1 - cwd_len);
+       return true;
+}
+
+static bool shadow_copy2_snapshot_to_gmt(vfs_handle_struct *handle,
+                                       const char *name,
+                                       char *gmt, size_t gmt_len);
+
+/*
+ * Check if an incoming filename is already a snapshot converted pathname.
+ *
+ * If so, it returns the pathname truncated at the snapshot point which
+ * will be used as the connectpath.
+ */
+
+static int check_for_converted_path(TALLOC_CTX *mem_ctx,
+                               struct vfs_handle_struct *handle,
+                               struct shadow_copy2_private *priv,
+                               char *abs_path,
+                               bool *ppath_already_converted,
+                               char **pconnectpath)
+{
+       size_t snapdirlen = 0;
+       char *p = strstr_m(abs_path, priv->config->snapdir);
+       char *q = NULL;
+       char *connect_path = NULL;
+       char snapshot[GMT_NAME_LEN+1];
+
+       *ppath_already_converted = false;
+
+       if (p == NULL) {
+               /* Must at least contain shadow:snapdir. */
+               return 0;
+       }
+
+       if (priv->config->snapdir[0] == '/' &&
+                       p != abs_path) {
+               /* Absolute shadow:snapdir must be at the start. */
+               return 0;
+       }
+
+       snapdirlen = strlen(priv->config->snapdir);
+       if (p[snapdirlen] != '/') {
+               /* shadow:snapdir must end as a separate component. */
+               return 0;
+       }
+
+       if (p > abs_path && p[-1] != '/') {
+               /* shadow:snapdir must start as a separate component. */
+               return 0;
+       }
+
+       p += snapdirlen;
+       p++; /* Move past the / */
+
+       /*
+        * Need to return up to the next path
+        * component after the time.
+        * This will be used as the connectpath.
+        */
+       q = strchr(p, '/');
+       if (q == NULL) {
+               /*
+                * No next path component.
+                * Use entire string.
+                */
+               connect_path = talloc_strdup(mem_ctx,
+                                       abs_path);
+       } else {
+               connect_path = talloc_strndup(mem_ctx,
+                                       abs_path,
+                                       q - abs_path);
+       }
+       if (connect_path == NULL) {
+               return ENOMEM;
+       }
+
+       /*
+        * Point p at the same offset in connect_path as
+        * it is in abs_path.
+        */
+
+       p = &connect_path[p - abs_path];
+
+       /*
+        * Now ensure there is a time string at p.
+        * The SMB-format @GMT-token string is returned
+        * in snapshot.
+        */
+
+       if (!shadow_copy2_snapshot_to_gmt(handle,
+                               p,
+                               snapshot,
+                               sizeof(snapshot))) {
+               TALLOC_FREE(connect_path);
+               return 0;
+       }
+
+       if (pconnectpath != NULL) {
+               *pconnectpath = connect_path;
+       }
+
+       *ppath_already_converted = true;
+
+       DBG_DEBUG("path |%s| is already converted. "
+               "connect path = |%s|\n",
+               abs_path,
+               connect_path);
+
+       return 0;
+}
+
 /**
- * Strip a snapshot component from a filename as
- * handed in via the smb layer.
- * Returns the parsed timestamp and the stripped filename.
+ * This function does two things.
+ *
+ * 1). Checks if an incoming filename is already a
+ * snapshot converted pathname.
+ *     If so, it returns the pathname truncated
+ *     at the snapshot point which will be used
+ *     as the connectpath, and then does an early return.
+ *
+ * 2). Checks if an incoming filename contains an
+ * SMB-layer @GMT- style timestamp.
+ *     If so, it strips the timestamp, and returns
+ *     both the timestamp and the stripped path
+ *     (making it cwd-relative).
  */
-static bool shadow_copy2_strip_snapshot(TALLOC_CTX *mem_ctx,
+
+static bool shadow_copy2_strip_snapshot_internal(TALLOC_CTX *mem_ctx,
                                        struct vfs_handle_struct *handle,
-                                       const char *name,
+                                       const char *orig_name,
                                        time_t *ptimestamp,
-                                       char **pstripped)
+                                       char **pstripped,
+                                       char **psnappath)
 {
        struct tm tm;
        time_t timestamp = 0;
@@ -422,61 +592,72 @@ static bool shadow_copy2_strip_snapshot(TALLOC_CTX *mem_ctx,
        char *stripped = NULL;
        size_t rest_len, dst_len;
        struct shadow_copy2_private *priv;
-       const char *snapdir;
-       ssize_t snapdirlen;
        ptrdiff_t len_before_gmt;
+       const char *name = orig_name;
+       char *abs_path = NULL;
+       bool ret = true;
+       bool already_converted = false;
+       int err = 0;
 
        SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
                                return false);
 
        DEBUG(10, (__location__ ": enter path '%s'\n", name));
 
+       abs_path = make_path_absolute(mem_ctx, priv, name);
+       if (abs_path == NULL) {
+               ret = false;
+               goto out;
+       }
+       name = abs_path;
+
+       DEBUG(10, (__location__ ": abs path '%s'\n", name));
+
+       err = check_for_converted_path(mem_ctx,
+                                       handle,
+                                       priv,
+                                       abs_path,
+                                       &already_converted,
+                                       psnappath);
+       if (err != 0) {
+               /* error in conversion. */
+               ret = false;
+               goto out;
+       }
+
+       if (already_converted) {
+               goto out;
+       }
+
+       /*
+        * From here we're only looking to strip an
+        * SMB-layer @GMT- token.
+        */
+
        p = strstr_m(name, "@GMT-");
        if (p == NULL) {
                DEBUG(11, ("@GMT not found\n"));
-               goto no_snapshot;
+               goto out;
        }
        if ((p > name) && (p[-1] != '/')) {
                /* the GMT-token does not start a path-component */
                DEBUG(10, ("not at start, p=%p, name=%p, p[-1]=%d\n",
                           p, name, (int)p[-1]));
-               goto no_snapshot;
+               goto out;
        }
 
-       /*
-        * Figure out whether we got an already converted string. One
-        * case where this happens is in a smb2 create call with the
-        * mxac create blob set. We do the get_acl call on
-        * fsp->fsp_name, which is already converted. We are converted
-        * if we got a file name of the form ".snapshots/@GMT-",
-        * i.e. ".snapshots/" precedes "p".
-        */
-
-       snapdir = lp_parm_const_string(SNUM(handle->conn), "shadow", "snapdir",
-                                      ".snapshots");
-       snapdirlen = strlen(snapdir);
        len_before_gmt = p - name;
 
-       if ((len_before_gmt >= (snapdirlen + 1)) && (p[-1] == '/')) {
-               const char *parent_snapdir = p - (snapdirlen+1);
-
-               DEBUG(10, ("parent_snapdir = %s\n", parent_snapdir));
-
-               if (strncmp(parent_snapdir, snapdir, snapdirlen) == 0) {
-                       DEBUG(10, ("name=%s is already converted\n", name));
-                       goto no_snapshot;
-               }
-       }
        q = strptime(p, GMT_FORMAT, &tm);
        if (q == NULL) {
                DEBUG(10, ("strptime failed\n"));
-               goto no_snapshot;
+               goto out;
        }
        tm.tm_isdst = -1;
        timestamp = timegm(&tm);
        if (timestamp == (time_t)-1) {
                DEBUG(10, ("timestamp==-1\n"));
-               goto no_snapshot;
+               goto out;
        }
        if (q[0] == '\0') {
                /*
@@ -496,12 +677,24 @@ static bool shadow_copy2_strip_snapshot(TALLOC_CTX *mem_ctx,
                        stripped = talloc_strndup(mem_ctx, name,
                                        len_before_gmt);
                        if (stripped == NULL) {
-                               return false;
+                               ret = false;
+                               goto out;
+                       }
+                       if (orig_name[0] != '/') {
+                               if (make_relative_path(priv->shadow_cwd,
+                                               stripped) == false) {
+                                       DEBUG(10, (__location__ ": path '%s' "
+                                               "doesn't start with cwd '%s\n",
+                                               stripped, priv->shadow_cwd));
+                                               ret = false;
+                                       errno = ENOENT;
+                                       goto out;
+                               }
                        }
                        *pstripped = stripped;
                }
                *ptimestamp = timestamp;
-               return true;
+               goto out;
        }
        if (q[0] != '/') {
                /*
@@ -509,75 +702,18 @@ static bool shadow_copy2_strip_snapshot(TALLOC_CTX *mem_ctx,
                 * component continues after the gmt-token.
                 */
                DEBUG(10, ("q[0] = %d\n", (int)q[0]));
-               goto no_snapshot;
+               goto out;
        }
        q += 1;
 
        rest_len = strlen(q);
        dst_len = len_before_gmt + rest_len;
 
-       if (priv->config->snapdirseverywhere) {
-               char *insert;
-               bool have_insert;
-               insert = shadow_copy2_insert_string(talloc_tos(), handle,
-                                                   timestamp);
-               if (insert == NULL) {
-                       errno = ENOMEM;
-                       return false;
-               }
-
-               DEBUG(10, (__location__ ": snapdirseverywhere mode.\n"
-                          "path '%s'.\n"
-                          "insert string '%s'\n", name, insert));
-
-               have_insert = (strstr(name, insert+1) != NULL);
-               DEBUG(10, ("have_insert=%d, name=%s, insert+1=%s\n",
-                          (int)have_insert, name, insert+1));
-               if (have_insert) {
-                       DEBUG(10, (__location__ ": insert string '%s' found in "
-                                  "path '%s' found in snapdirseverywhere mode "
-                                  "==> already converted\n", insert, name));
-                       TALLOC_FREE(insert);
-                       goto no_snapshot;
-               }
-               TALLOC_FREE(insert);
-       } else {
-               char *snapshot_path;
-               char *s;
-
-               snapshot_path = shadow_copy2_snapshot_path(talloc_tos(),
-                                                          handle,
-                                                          timestamp);
-               if (snapshot_path == NULL) {
-                       errno = ENOMEM;
-                       return false;
-               }
-
-               DEBUG(10, (__location__ " path: '%s'.\n"
-                          "snapshot path: '%s'\n", name, snapshot_path));
-
-               s = strstr(name, snapshot_path);
-               if (s == name) {
-                       /*
-                        * this starts with "snapshot_basepath/GMT-Token"
-                        * so it is already a converted absolute
-                        * path. Don't process further.
-                        */
-                       DEBUG(10, (__location__ ": path '%s' starts with "
-                                  "snapshot path '%s' (not in "
-                                  "snapdirseverywhere mode) ==> "
-                                  "already converted\n", name, snapshot_path));
-                       talloc_free(snapshot_path);
-                       goto no_snapshot;
-               }
-               talloc_free(snapshot_path);
-       }
-
        if (pstripped != NULL) {
                stripped = talloc_array(mem_ctx, char, dst_len+1);
                if (stripped == NULL) {
-                       errno = ENOMEM;
-                       return false;
+                       ret = false;
+                       goto out;
                }
                if (p > name) {
                        memcpy(stripped, name, len_before_gmt);
@@ -586,13 +722,39 @@ static bool shadow_copy2_strip_snapshot(TALLOC_CTX *mem_ctx,
                        memcpy(stripped + len_before_gmt, q, rest_len);
                }
                stripped[dst_len] = '\0';
+               if (orig_name[0] != '/') {
+                       if (make_relative_path(priv->shadow_cwd,
+                                       stripped) == false) {
+                               DEBUG(10, (__location__ ": path '%s' "
+                                       "doesn't start with cwd '%s\n",
+                                       stripped, priv->shadow_cwd));
+                               ret = false;
+                               errno = ENOENT;
+                               goto out;
+                       }
+               }
                *pstripped = stripped;
        }
        *ptimestamp = timestamp;
-       return true;
-no_snapshot:
-       *ptimestamp = 0;
-       return true;
+       ret = true;
+
+  out:
+       TALLOC_FREE(abs_path);
+       return ret;
+}
+
+static bool shadow_copy2_strip_snapshot(TALLOC_CTX *mem_ctx,
+                                       struct vfs_handle_struct *handle,
+                                       const char *orig_name,
+                                       time_t *ptimestamp,
+                                       char **pstripped)
+{
+       return shadow_copy2_strip_snapshot_internal(mem_ctx,
+                                       handle,
+                                       orig_name,
+                                       ptimestamp,
+                                       pstripped,
+                                       NULL);
 }
 
 static char *shadow_copy2_find_mount_point(TALLOC_CTX *mem_ctx,
@@ -646,7 +808,8 @@ static char *shadow_copy2_do_convert(TALLOC_CTX *mem_ctx,
        char *insert = NULL;
        char *converted = NULL;
        size_t insertlen, connectlen = 0;
-       int i, saved_errno;
+       int saved_errno = 0;
+       int i;
        size_t min_offset;
        struct shadow_copy2_config *config;
        struct shadow_copy2_private *priv;
@@ -832,12 +995,16 @@ static char *shadow_copy2_do_convert(TALLOC_CTX *mem_ctx,
                errno = ENOENT;
        }
 fail:
-       saved_errno = errno;
+       if (result == NULL) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(converted);
        TALLOC_FREE(insert);
        TALLOC_FREE(slashes);
        TALLOC_FREE(path);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return result;
 }
 
@@ -896,7 +1063,7 @@ static DIR *shadow_copy2_opendir(vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        DIR *ret;
-       int saved_errno;
+       int saved_errno = 0;
        char *conv;
        struct smb_filename *conv_smb_fname = NULL;
 
@@ -925,10 +1092,14 @@ static DIR *shadow_copy2_opendir(vfs_handle_struct *handle,
                return NULL;
        }
        ret = SMB_VFS_NEXT_OPENDIR(handle, conv_smb_fname, mask, attr);
-       saved_errno = errno;
+       if (ret == NULL) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
        TALLOC_FREE(conv_smb_fname);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -938,15 +1109,17 @@ static int shadow_copy2_rename(vfs_handle_struct *handle,
 {
        time_t timestamp_src = 0;
        time_t timestamp_dst = 0;
+       char *snappath_src = NULL;
+       char *snappath_dst = NULL;
 
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
+       if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle,
                                         smb_fname_src->base_name,
-                                        &timestamp_src, NULL)) {
+                                        &timestamp_src, NULL, &snappath_src)) {
                return -1;
        }
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
+       if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle,
                                         smb_fname_dst->base_name,
-                                        &timestamp_dst, NULL)) {
+                                        &timestamp_dst, NULL, &snappath_dst)) {
                return -1;
        }
        if (timestamp_src != 0) {
@@ -957,6 +1130,17 @@ static int shadow_copy2_rename(vfs_handle_struct *handle,
                errno = EROFS;
                return -1;
        }
+       /*
+        * Don't allow rename on already converted paths.
+        */
+       if (snappath_src != NULL) {
+               errno = EXDEV;
+               return -1;
+       }
+       if (snappath_dst != NULL) {
+               errno = EROFS;
+               return -1;
+       }
        return SMB_VFS_NEXT_RENAME(handle, smb_fname_src, smb_fname_dst);
 }
 
@@ -965,19 +1149,28 @@ static int shadow_copy2_symlink(vfs_handle_struct *handle,
 {
        time_t timestamp_old = 0;
        time_t timestamp_new = 0;
+       char *snappath_old = NULL;
+       char *snappath_new = NULL;
 
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, oldname,
-                                        &timestamp_old, NULL)) {
+       if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle, oldname,
+                                        &timestamp_old, NULL, &snappath_old)) {
                return -1;
        }
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, newname,
-                                        &timestamp_new, NULL)) {
+       if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle, newname,
+                                        &timestamp_new, NULL, &snappath_new)) {
                return -1;
        }
        if ((timestamp_old != 0) || (timestamp_new != 0)) {
                errno = EROFS;
                return -1;
        }
+       /*
+        * Don't allow symlinks on already converted paths.
+        */
+       if ((snappath_old != NULL) || (snappath_new != NULL)) {
+               errno = EROFS;
+               return -1;
+       }
        return SMB_VFS_NEXT_SYMLINK(handle, oldname, newname);
 }
 
@@ -986,19 +1179,28 @@ static int shadow_copy2_link(vfs_handle_struct *handle,
 {
        time_t timestamp_old = 0;
        time_t timestamp_new = 0;
+       char *snappath_old = NULL;
+       char *snappath_new = NULL;
 
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, oldname,
-                                        &timestamp_old, NULL)) {
+       if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle, oldname,
+                                        &timestamp_old, NULL, &snappath_old)) {
                return -1;
        }
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, newname,
-                                        &timestamp_new, NULL)) {
+       if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle, newname,
+                                        &timestamp_new, NULL, &snappath_new)) {
                return -1;
        }
        if ((timestamp_old != 0) || (timestamp_new != 0)) {
                errno = EROFS;
                return -1;
        }
+       /*
+        * Don't allow links on already converted paths.
+        */
+       if ((snappath_old != NULL) || (snappath_new != NULL)) {
+               errno = EROFS;
+               return -1;
+       }
        return SMB_VFS_NEXT_LINK(handle, oldname, newname);
 }
 
@@ -1008,7 +1210,8 @@ static int shadow_copy2_stat(vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        char *tmp;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
                                         smb_fname->base_name,
@@ -1030,7 +1233,9 @@ static int shadow_copy2_stat(vfs_handle_struct *handle,
        }
 
        ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
 
        TALLOC_FREE(smb_fname->base_name);
        smb_fname->base_name = tmp;
@@ -1038,7 +1243,9 @@ static int shadow_copy2_stat(vfs_handle_struct *handle,
        if (ret == 0) {
                convert_sbuf(handle, smb_fname->base_name, &smb_fname->st);
        }
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1048,7 +1255,8 @@ static int shadow_copy2_lstat(vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        char *tmp;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
                                         smb_fname->base_name,
@@ -1070,7 +1278,9 @@ static int shadow_copy2_lstat(vfs_handle_struct *handle,
        }
 
        ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
 
        TALLOC_FREE(smb_fname->base_name);
        smb_fname->base_name = tmp;
@@ -1078,7 +1288,9 @@ static int shadow_copy2_lstat(vfs_handle_struct *handle,
        if (ret == 0) {
                convert_sbuf(handle, smb_fname->base_name, &smb_fname->st);
        }
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1110,7 +1322,8 @@ static int shadow_copy2_open(vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        char *tmp;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
                                         smb_fname->base_name,
@@ -1132,12 +1345,16 @@ static int shadow_copy2_open(vfs_handle_struct *handle,
        }
 
        ret = SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
 
        TALLOC_FREE(smb_fname->base_name);
        smb_fname->base_name = tmp;
 
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1146,7 +1363,8 @@ static int shadow_copy2_unlink(vfs_handle_struct *handle,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        struct smb_filename *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
@@ -1169,9 +1387,13 @@ static int shadow_copy2_unlink(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_UNLINK(handle, conv);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1181,7 +1403,8 @@ static int shadow_copy2_chmod(vfs_handle_struct *handle,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        char *conv = NULL;
        struct smb_filename *conv_smb_fname;
 
@@ -1213,10 +1436,14 @@ static int shadow_copy2_chmod(vfs_handle_struct *handle,
        }
 
        ret = SMB_VFS_NEXT_CHMOD(handle, conv_smb_fname, mode);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
        TALLOC_FREE(conv_smb_fname);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1227,7 +1454,8 @@ static int shadow_copy2_chown(vfs_handle_struct *handle,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        char *conv = NULL;
        struct smb_filename *conv_smb_fname = NULL;
 
@@ -1257,37 +1485,96 @@ static int shadow_copy2_chown(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_CHOWN(handle, conv_smb_fname, uid, gid);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
        TALLOC_FREE(conv_smb_fname);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
+static void store_cwd_data(vfs_handle_struct *handle,
+                               const char *connectpath)
+{
+       struct shadow_copy2_private *priv = NULL;
+       char *cwd = NULL;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+                               return);
+
+       TALLOC_FREE(priv->shadow_cwd);
+       cwd = SMB_VFS_NEXT_GETWD(handle);
+       if (cwd == NULL) {
+               smb_panic("getwd failed\n");
+       }
+       DBG_DEBUG("shadow cwd = %s\n", cwd);
+       priv->shadow_cwd = talloc_strdup(priv, cwd);
+       SAFE_FREE(cwd);
+       if (priv->shadow_cwd == NULL) {
+               smb_panic("talloc failed\n");
+       }
+       TALLOC_FREE(priv->shadow_connectpath);
+       if (connectpath) {
+               DBG_DEBUG("shadow conectpath = %s\n", connectpath);
+               priv->shadow_connectpath = talloc_strdup(priv, connectpath);
+               if (priv->shadow_connectpath == NULL) {
+                       smb_panic("talloc failed\n");
+               }
+       }
+}
+
 static int shadow_copy2_chdir(vfs_handle_struct *handle,
                              const char *fname)
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
-       char *conv;
+       char *snappath = NULL;
+       int ret = -1;
+       int saved_errno = 0;
+       char *conv = NULL;
+       size_t rootpath_len = 0;
 
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
-                                        &timestamp, &stripped)) {
+       if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle, fname,
+                                       &timestamp, &stripped, &snappath)) {
                return -1;
        }
-       if (timestamp == 0) {
-               return SMB_VFS_NEXT_CHDIR(handle, fname);
+       if (stripped != NULL) {
+               conv = shadow_copy2_do_convert(talloc_tos(),
+                                               handle,
+                                               stripped,
+                                               timestamp,
+                                               &rootpath_len);
+               TALLOC_FREE(stripped);
+               if (conv == NULL) {
+                       return -1;
+               }
+               fname = conv;
        }
-       conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp);
-       TALLOC_FREE(stripped);
-       if (conv == NULL) {
-               return -1;
+
+       ret = SMB_VFS_NEXT_CHDIR(handle, fname);
+       if (ret == -1) {
+               saved_errno = errno;
+       }
+
+       if (ret == 0) {
+               if (conv != NULL && rootpath_len != 0) {
+                       conv[rootpath_len] = '\0';
+               } else if (snappath != 0) {
+                       TALLOC_FREE(conv);
+                       conv = snappath;
+               }
+               store_cwd_data(handle, conv);
        }
-       ret = SMB_VFS_NEXT_CHDIR(handle, conv);
-       saved_errno = errno;
+
+       TALLOC_FREE(stripped);
        TALLOC_FREE(conv);
-       errno = saved_errno;
+
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1297,7 +1584,8 @@ static int shadow_copy2_ntimes(vfs_handle_struct *handle,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        struct smb_filename *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
@@ -1320,9 +1608,13 @@ static int shadow_copy2_ntimes(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_NTIMES(handle, conv, ft);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1331,7 +1623,8 @@ static int shadow_copy2_readlink(vfs_handle_struct *handle,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        char *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
@@ -1347,9 +1640,13 @@ static int shadow_copy2_readlink(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_READLINK(handle, conv, buf, bufsiz);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1358,7 +1655,8 @@ static int shadow_copy2_mknod(vfs_handle_struct *handle,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        char *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
@@ -1374,9 +1672,13 @@ static int shadow_copy2_mknod(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_MKNOD(handle, conv, mode, dev);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1387,7 +1689,7 @@ static char *shadow_copy2_realpath(vfs_handle_struct *handle,
        char *stripped = NULL;
        char *tmp = NULL;
        char *result = NULL;
-       int saved_errno;
+       int saved_errno = 0;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
                                         &timestamp, &stripped)) {
@@ -1405,10 +1707,14 @@ static char *shadow_copy2_realpath(vfs_handle_struct *handle,
        result = SMB_VFS_NEXT_REALPATH(handle, tmp);
 
 done:
-       saved_errno = errno;
+       if (result == NULL) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(tmp);
        TALLOC_FREE(stripped);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return result;
 }
 
@@ -1567,6 +1873,9 @@ static bool shadow_copy2_snapshot_to_gmt(vfs_handle_struct *handle,
                }
                /* Extract the prefix */
                tmp = strstr(tmpstr, priv->config->delimiter);
+               if (tmp == NULL) {
+                       goto done;
+               }
                *tmp = '\0';
 
                /* Parse regex */
@@ -1899,7 +2208,8 @@ static int shadow_copy2_mkdir(vfs_handle_struct *handle,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        char *conv;
        struct smb_filename *conv_smb_fname = NULL;
 
@@ -1928,10 +2238,14 @@ static int shadow_copy2_mkdir(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_MKDIR(handle, conv_smb_fname, mode);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
        TALLOC_FREE(conv_smb_fname);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1940,7 +2254,8 @@ static int shadow_copy2_rmdir(vfs_handle_struct *handle,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        char *conv;
        struct smb_filename *conv_smb_fname = NULL;
 
@@ -1969,10 +2284,14 @@ static int shadow_copy2_rmdir(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_RMDIR(handle, conv_smb_fname);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv_smb_fname);
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1981,7 +2300,8 @@ static int shadow_copy2_chflags(vfs_handle_struct *handle, const char *fname,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        char *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
@@ -1997,9 +2317,13 @@ static int shadow_copy2_chflags(vfs_handle_struct *handle, const char *fname,
                return -1;
        }
        ret = SMB_VFS_NEXT_CHFLAGS(handle, conv, flags);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -2010,7 +2334,7 @@ static ssize_t shadow_copy2_getxattr(vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        ssize_t ret;
-       int saved_errno;
+       int saved_errno = 0;
        char *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
@@ -2027,9 +2351,13 @@ static ssize_t shadow_copy2_getxattr(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_GETXATTR(handle, conv, aname, value, size);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -2040,7 +2368,7 @@ static ssize_t shadow_copy2_listxattr(struct vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        ssize_t ret;
-       int saved_errno;
+       int saved_errno = 0;
        char *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
@@ -2056,9 +2384,13 @@ static ssize_t shadow_copy2_listxattr(struct vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_LISTXATTR(handle, conv, list, size);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -2067,7 +2399,8 @@ static int shadow_copy2_removexattr(vfs_handle_struct *handle,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        char *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
@@ -2083,9 +2416,13 @@ static int shadow_copy2_removexattr(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_REMOVEXATTR(handle, conv, aname);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -2097,7 +2434,7 @@ static int shadow_copy2_setxattr(struct vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        ssize_t ret;
-       int saved_errno;
+       int saved_errno = 0;
        char *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
@@ -2114,9 +2451,13 @@ static int shadow_copy2_setxattr(struct vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_SETXATTR(handle, conv, aname, value, size, flags);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -2127,7 +2468,7 @@ static int shadow_copy2_chmod_acl(vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        ssize_t ret;
-       int saved_errno;
+       int saved_errno = 0;
        char *conv = NULL;
        struct smb_filename *conv_smb_fname = NULL;
 
@@ -2157,10 +2498,14 @@ static int shadow_copy2_chmod_acl(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_CHMOD_ACL(handle, conv_smb_fname, mode);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
        TALLOC_FREE(conv_smb_fname);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -2173,7 +2518,7 @@ static int shadow_copy2_get_real_filename(struct vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        ssize_t ret;
-       int saved_errno;
+       int saved_errno = 0;
        char *conv;
 
        DEBUG(10, ("shadow_copy2_get_real_filename called for path=[%s], "
@@ -2200,9 +2545,13 @@ static int shadow_copy2_get_real_filename(struct vfs_handle_struct *handle,
        ret = SMB_VFS_NEXT_GET_REAL_FILENAME(handle, conv, name,
                                             mem_ctx, found_name);
        DEBUG(10, ("NEXT_REAL_FILE_NAME returned %d\n", (int)ret));
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -2214,11 +2563,21 @@ static const char *shadow_copy2_connectpath(struct vfs_handle_struct *handle,
        char *tmp = NULL;
        char *result = NULL;
        char *parent_dir = NULL;
-       int saved_errno;
+       int saved_errno = 0;
        size_t rootpath_len = 0;
+       struct shadow_copy2_private *priv = NULL;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+                               return NULL);
 
        DBG_DEBUG("Calc connect path for [%s]\n", fname);
 
+       if (priv->shadow_connectpath != NULL) {
+               DBG_DEBUG("cached connect path is [%s]\n",
+                       priv->shadow_connectpath);
+               return priv->shadow_connectpath;
+       }
+
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
                                         &timestamp, &stripped)) {
                goto done;
@@ -2269,14 +2628,25 @@ static const char *shadow_copy2_connectpath(struct vfs_handle_struct *handle,
                goto done;
        }
 
+       /*
+        * SMB_VFS_NEXT_REALPATH returns a malloc'ed string.
+        * Don't leak memory.
+        */
+       SAFE_FREE(priv->shadow_realpath);
+       priv->shadow_realpath = result;
+
        DBG_DEBUG("connect path is [%s]\n", result);
 
 done:
-       saved_errno = errno;
+       if (result == NULL) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(tmp);
        TALLOC_FREE(stripped);
        TALLOC_FREE(parent_dir);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return result;
 }
 
@@ -2287,7 +2657,7 @@ static uint64_t shadow_copy2_disk_free(vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        ssize_t ret;
-       int saved_errno;
+       int saved_errno = 0;
        char *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, path,
@@ -2307,9 +2677,13 @@ static uint64_t shadow_copy2_disk_free(vfs_handle_struct *handle,
 
        ret = SMB_VFS_NEXT_DISK_FREE(handle, conv, bsize, dfree, dsize);
 
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
 
        return ret;
 }
@@ -2321,7 +2695,7 @@ static int shadow_copy2_get_quota(vfs_handle_struct *handle, const char *path,
        time_t timestamp = 0;
        char *stripped = NULL;
        int ret;
-       int saved_errno;
+       int saved_errno = 0;
        char *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, path, &timestamp,
@@ -2340,13 +2714,23 @@ static int shadow_copy2_get_quota(vfs_handle_struct *handle, const char *path,
 
        ret = SMB_VFS_NEXT_GET_QUOTA(handle, conv, qtype, id, dq);
 
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
 
        return ret;
 }
 
+static int shadow_copy2_private_destructor(struct shadow_copy2_private *priv)
+{
+       SAFE_FREE(priv->shadow_realpath);
+       return 0;
+}
+
 static int shadow_copy2_connect(struct vfs_handle_struct *handle,
                                const char *service, const char *user)
 {
@@ -2378,6 +2762,8 @@ static int shadow_copy2_connect(struct vfs_handle_struct *handle,
                return -1;
        }
 
+       talloc_set_destructor(priv, shadow_copy2_private_destructor);
+
        priv->snaps = talloc_zero(priv, struct shadow_copy2_snaplist_info);
        if (priv->snaps == NULL) {
                DBG_ERR("talloc_zero() failed\n");
@@ -2404,6 +2790,15 @@ static int shadow_copy2_connect(struct vfs_handle_struct *handle,
                return -1;
        }
 
+       /* config->gmt_format must not contain a path separator. */
+       if (strchr(config->gmt_format, '/') != NULL) {
+               DEBUG(0, ("shadow:format %s must not contain a /"
+                       "character. Unable to initialize module.\n",
+                       config->gmt_format));
+               errno = EINVAL;
+               return -1;
+       }
+
        config->use_sscanf = lp_parm_bool(SNUM(handle->conn),
                                          "shadow", "sscanf", false);