Print this page
NEX-2807 Restoring previous versions from snapshots doesn't work with nested folders.
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-9808 SMB3 persistent handles
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-9808 SMB3 persistent handles
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-4538 SMB1 create file should support extended_response format (2)
NEX-6116 Failures in smbtorture raw.open
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Include this commit if upstreaming/backporting any of:
NEX-4540 SMB server declines EA support incorrectly
NEX-4239 smbtorture create failures re. allocation size
(illumos) 6398 SMB should support path names longer than 1024
NEX-5082 panic getting Mac attributes for a 255 character file name
Reviewed by: Bayard Bell <bayard.bell@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
Conflicts:
        usr/src/uts/common/fs/smbsrv/smb_pathname.c
NEX-3611 CLONE NEX-3550 Replace smb2_enable with max_protocol
Reviewed by: Yuri Pankov <Yuri.Pankov@nexenta.com>
SMB-115 Support SMB path names with length > 1024
SMB-100 Internal error if filename is too long
Approved by: Gordon Ross <gwr@nexenta.com>
SMB-136 Snapshots not visible in Windows previous versions
SMB-11 SMB2 message parse & dispatch
SMB-12 SMB2 Negotiate Protocol
SMB-13 SMB2 Session Setup
SMB-14 SMB2 Logoff
SMB-15 SMB2 Tree Connect
SMB-16 SMB2 Tree Disconnect
SMB-17 SMB2 Create
SMB-18 SMB2 Close
SMB-19 SMB2 Flush
SMB-20 SMB2 Read
SMB-21 SMB2 Write
SMB-22 SMB2 Lock/Unlock
SMB-23 SMB2 Ioctl
SMB-24 SMB2 Cancel
SMB-25 SMB2 Echo
SMB-26 SMB2 Query Dir
SMB-27 SMB2 Change Notify
SMB-28 SMB2 Query Info
SMB-29 SMB2 Set Info
SMB-30 SMB2 Oplocks
SMB-53 SMB2 Create Context options
(SMB2 code review cleanup 1, 2, 3)
SMB-65 SMB server in non-global zones (use zone_kcred())
re #13470 rb4432 Sync some SMB differences from illumos
re #6854 FindFirstFile,FindFirstFileEx,... are not working correctly on Nexenta CIFS-shares
re #6813 rb1757 port 2976 Child folder visibility through shares

@@ -18,11 +18,11 @@
  *
  * CDDL HEADER END
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc. All rights reserved.
  */
 
 #include <smbsrv/smb_kproto.h>
 #include <smbsrv/smb_fsops.h>
 #include <sys/pathname.h>

@@ -149,28 +149,30 @@
     smb_node_t          *cur_node,
     smb_node_t          **dir_node,
     char                *last_component)
 {
         smb_node_t      *root_node;
-        pathname_t      ppn;
+        pathname_t      ppn, mnt_pn;
         char            *usepath;
         int             lookup_flags = FOLLOW;
         int             trailing_slash = 0;
         int             err = 0;
         int             len;
-        smb_node_t      *vss_cur_node;
-        smb_node_t      *vss_root_node;
+        smb_node_t      *vss_node;
         smb_node_t      *local_cur_node;
         smb_node_t      *local_root_node;
+        boolean_t       chk_vss;
+        char            *gmttoken;
 
         ASSERT(dir_node);
         ASSERT(last_component);
 
         *dir_node = NULL;
         *last_component = '\0';
-        vss_cur_node = NULL;
-        vss_root_node = NULL;
+        vss_node = NULL;
+        gmttoken = NULL;
+        chk_vss = B_FALSE;
 
         if (sr && sr->tid_tree) {
                 if (STYPE_ISIPC(sr->tid_tree->t_res_type))
                         return (EACCES);
         }

@@ -222,29 +224,32 @@
                         len = strlen(usepath);
                 }
         }
 
         if (sr != NULL) {
-                boolean_t chk_vss;
-                if (sr->session->dialect >= SMB_VERS_2_BASE)
+                if (sr->session->dialect >= SMB_VERS_2_BASE) {
                         chk_vss = sr->arg.open.create_timewarp;
-                else
+                } else {
                         chk_vss = (sr->smb_flg2 &
                             SMB_FLAGS2_REPARSE_PATH) != 0;
+
                 if (chk_vss) {
-                        err = smb_vss_lookup_nodes(sr, root_node, cur_node,
-                            usepath, &vss_cur_node, &vss_root_node);
+                                gmttoken = kmem_alloc(SMB_VSS_GMT_SIZE,
+                                    KM_SLEEP);
+                                err = smb_vss_extract_gmttoken(usepath,
+                                    gmttoken);
                         if (err != 0) {
                                 kmem_free(usepath, SMB_MAXPATHLEN);
+                                        kmem_free(gmttoken, SMB_VSS_GMT_SIZE);
                                 return (err);
                         }
-
                         len = strlen(usepath);
-                        local_cur_node = vss_cur_node;
-                        local_root_node = vss_root_node;
                 }
         }
+                if (chk_vss)
+                        (void) pn_alloc(&mnt_pn);
+        }
 
         if (usepath[len - 1] == '/')
                 trailing_slash = 1;
 
         (void) strcanon(usepath, "/");

@@ -252,44 +257,101 @@
         (void) pn_alloc_sz(&ppn, SMB_MAXPATHLEN);
 
         if ((err = pn_set(&ppn, usepath)) != 0) {
                 (void) pn_free(&ppn);
                 kmem_free(usepath, SMB_MAXPATHLEN);
-                if (vss_cur_node != NULL)
-                        (void) smb_node_release(vss_cur_node);
-                if (vss_root_node != NULL)
-                        (void) smb_node_release(vss_root_node);
+                if (chk_vss)
+                        (void) pn_free(&mnt_pn);
+                if (gmttoken != NULL)
+                        kmem_free(gmttoken, SMB_VSS_GMT_SIZE);
                 return (err);
         }
 
         /*
          * If a path does not have a trailing slash, strip off the
          * last component.  (We only need to return an smb_node for
          * the second to last component; a name is returned for the
          * last component.)
+         *
+         * For VSS requests, the last component might be a filesystem of its
+         * own, and we need to discover that before exiting this function,
+         * so allow the lookup to happen on the last component.
+         * We'll correct this later when we convert to the snapshot.
          */
 
+        if (!chk_vss) {
         if (trailing_slash) {
                 (void) strlcpy(last_component, ".", MAXNAMELEN);
         } else {
                 (void) pn_setlast(&ppn);
                 (void) strlcpy(last_component, ppn.pn_path, MAXNAMELEN);
                 ppn.pn_path[0] = '\0';
         }
+        }
 
         if ((strcmp(ppn.pn_buf, "/") == 0) || (ppn.pn_buf[0] == '\0')) {
                 smb_node_ref(local_cur_node);
                 *dir_node = local_cur_node;
         } else {
                 err = smb_pathname(sr, ppn.pn_buf, lookup_flags,
-                    local_root_node, local_cur_node, NULL, dir_node, cred);
+                    local_root_node, local_cur_node, NULL, dir_node, cred,
+                    chk_vss ? &mnt_pn : NULL);
         }
 
         (void) pn_free(&ppn);
         kmem_free(usepath, SMB_MAXPATHLEN);
 
         /*
+         * We need to try and convert to snapshots, even on error.
+         * This is to handle the following cases:
+         * - We're on the lowest level filesystem, but a directory got renamed
+         *   on the live version. We'll get ENOENT, but can still find it in
+         *   the snapshot.
+         * - The last component was actually a file. We need to leave the last
+         *   component in in case it is, itself, a mountpoint, but that means
+         *   we might get ENOTDIR if it's not actually a directory.
+         *
+         * Note that if you change the share-relative name of a mountpoint,
+         * you won't be able to access previous versions of files under it.
+         */
+        if (chk_vss && *dir_node != NULL) {
+                if ((err = smb_vss_lookup_nodes(sr, *dir_node, &vss_node,
+                    gmttoken)) == 0) {
+                        char *p = mnt_pn.pn_path;
+                        size_t pathleft;
+
+                        smb_node_release(*dir_node);
+                        *dir_node = NULL;
+                        pathleft = pn_pathleft(&mnt_pn);
+
+                        if (pathleft == 0 || trailing_slash) {
+                                (void) strlcpy(last_component, ".", MAXNAMELEN);
+                        } else {
+                                (void) pn_setlast(&mnt_pn);
+                                (void) strlcpy(last_component, mnt_pn.pn_path,
+                                    MAXNAMELEN);
+                                mnt_pn.pn_path[0] = '\0';
+                                pathleft -= strlen(last_component);
+                        }
+
+                        if (pathleft != 0) {
+                                err = smb_pathname(sr, p, lookup_flags,
+                                    vss_node, vss_node, NULL, dir_node, cred,
+                                    NULL);
+                        } else {
+                                *dir_node = vss_node;
+                                vss_node = NULL;
+                        }
+                }
+        }
+
+        if (chk_vss)
+                (void) pn_free(&mnt_pn);
+        if (gmttoken != NULL)
+                kmem_free(gmttoken, SMB_VSS_GMT_SIZE);
+
+        /*
          * Prevent traversal to another file system if mount point
          * traversal is disabled.
          *
          * Note that we disregard whether the traversal of the path went
          * outside of the file system and then came back (say via a link).

@@ -316,15 +378,12 @@
                         *dir_node = NULL;
                 }
                 *last_component = 0;
         }
 
-        if (vss_cur_node != NULL)
-                (void) smb_node_release(vss_cur_node);
-        if (vss_root_node != NULL)
-                (void) smb_node_release(vss_root_node);
-
+        if (vss_node != NULL)
+                (void) smb_node_release(vss_node);
         return (err);
 }
 
 /*
  * smb_pathname()

@@ -355,23 +414,24 @@
  */
 
 int
 smb_pathname(smb_request_t *sr, char *path, int flags,
     smb_node_t *root_node, smb_node_t *cur_node, smb_node_t **dir_node,
-    smb_node_t **ret_node, cred_t *cred)
+    smb_node_t **ret_node, cred_t *cred, pathname_t *mnt_pn)
 {
         char            *component, *real_name, *namep;
         pathname_t      pn, rpn, upn, link_pn;
-        smb_node_t      *dnode, *fnode;
+        smb_node_t      *dnode, *fnode, *mnt_node;
         smb_attr_t      attr;
         vnode_t         *rootvp, *vp;
         size_t          pathleft;
         int             err = 0;
         int             nlink = 0;
         int             local_flags;
         uint32_t        abe_flag = 0;
         char            namebuf[MAXNAMELEN];
+        vnode_t *fsrootvp = NULL;
 
         if (path == NULL)
                 return (EINVAL);
 
         ASSERT(root_node);

@@ -388,19 +448,29 @@
         if ((err = pn_set(&upn, path)) != 0) {
                 (void) pn_free(&upn);
                 return (err);
         }
 
+        if (mnt_pn != NULL && (err = pn_set(mnt_pn, path) != 0)) {
+                (void) pn_free(&upn);
+                return (err);
+        }
+
         if (SMB_TREE_SUPPORTS_ABE(sr))
                 abe_flag = SMB_ABE;
 
         (void) pn_alloc(&pn);
         (void) pn_alloc(&rpn);
 
         component = kmem_alloc(MAXNAMELEN, KM_SLEEP);
         real_name = kmem_alloc(MAXNAMELEN, KM_SLEEP);
 
+        if (mnt_pn != NULL) {
+                mnt_node = cur_node;
+                smb_node_ref(cur_node);
+        } else
+                mnt_node = NULL;
         fnode = NULL;
         dnode = cur_node;
         smb_node_ref(dnode);
         rootvp = root_node->vp;
 

@@ -421,10 +491,14 @@
                 }
 
                 if ((err = pn_set(&pn, namep)) != 0)
                         break;
 
+                /* We want the DOS attributes. */
+                bzero(&attr, sizeof (attr));
+                attr.sa_mask = SMB_AT_DOSATTR;
+
                 local_flags = flags & FIGNORECASE;
                 err = smb_pathname_lookup(&pn, &rpn, local_flags,
                     &vp, rootvp, dnode->vp, &attr, cred);
 
                 if (err) {

@@ -525,22 +599,56 @@
                 while (upn.pn_path[0] == '/') {
                         upn.pn_path++;
                         upn.pn_pathlen--;
                 }
 
+                /*
+                 * If the node we looked up is the root of a filesystem,
+                 * snapshot the lookup so we can replay this after discovering
+                 * the lowest mounted filesystem.
+                 */
+                if (mnt_pn != NULL &&
+                    (err = VFS_ROOT(fnode->vp->v_vfsp, &fsrootvp)) == 0) {
+                        if (fsrootvp == fnode->vp) {
+                                mnt_pn->pn_pathlen = pn_pathleft(&upn);
+                                mnt_pn->pn_path = mnt_pn->pn_buf +
+                                    ((ptrdiff_t)upn.pn_path -
+                                    (ptrdiff_t)upn.pn_buf);
+
+                                smb_node_ref(fnode);
+                                if (mnt_node != NULL)
+                                        smb_node_release(mnt_node);
+                                mnt_node = fnode;
+
         }
+                        VN_RELE(fsrootvp);
+                }
+        }
 
         if ((pathleft) && (err == ENOENT))
                 err = ENOTDIR;
 
-        if (err) {
+        if (mnt_node == NULL)
+                mnt_pn = NULL;
+
+        /*
+         * We always want to return a node when we're doing VSS
+         * (mnt_pn != NULL)
+         */
+        if (mnt_pn == NULL && err != 0) {
                 if (fnode)
                         smb_node_release(fnode);
                 if (dnode)
                         smb_node_release(dnode);
         } else {
+                if (mnt_pn != NULL) {
+                        *ret_node = mnt_node;
+                        if (fnode != NULL)
+                                smb_node_release(fnode);
+                } else {
                 *ret_node = fnode;
+                }
 
                 if (dir_node)
                         *dir_node = dnode;
                 else
                         smb_node_release(dnode);

@@ -905,14 +1013,14 @@
                 smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
                     ERRDOS, ERROR_INVALID_NAME);
                 return (B_FALSE);
         }
 
-        /* If fname is "." -> INVALID_OBJECT_NAME */
+        /* If fname is "." -> OBJECT_NAME_INVALID */
         if (pn->pn_fname && (strcmp(pn->pn_fname, ".") == 0)) {
                 smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
-                    ERRDOS, ERROR_PATH_NOT_FOUND);
+                    ERRDOS, ERROR_INVALID_NAME);
                 return (B_FALSE);
         }
 
         return (B_TRUE);
 }

@@ -982,36 +1090,52 @@
 /*
  * smb_stream_parse_name
  *
  * smb_stream_parse_name should only be called for a path that
  * contains a valid named stream.  Path validation should have
- * been performed before this function is called.
+ * been performed before this function is called, typically by
+ * calling smb_is_stream_name() just before this.
  *
  * Find the last component of path and split it into filename
  * and stream name.
  *
  * On return the named stream type will be present.  The stream
  * type defaults to ":$DATA", if it has not been defined
- * For exmaple, 'stream' contains :<sname>:$DATA
+ * For example, 'stream' contains :<sname>:$DATA
+ *
+ * Output args: filename, stream both MAXNAMELEN
  */
 void
 smb_stream_parse_name(char *path, char *filename, char *stream)
 {
         char *fname, *sname, *stype;
+        size_t flen, slen;
 
         ASSERT(path);
         ASSERT(filename);
         ASSERT(stream);
 
         fname = strrchr(path, '\\');
         fname = (fname == NULL) ? path : fname + 1;
-        (void) strlcpy(filename, fname, MAXNAMELEN);
+        sname = strchr(fname, ':');
+        /* Caller makes sure there is a ':' in path. */
+        VERIFY(sname != NULL);
+        /* LINTED: possible ptrdiff_t overflow */
+        flen = sname - fname;
+        slen = strlen(sname);
 
-        sname = strchr(filename, ':');
-        (void) strlcpy(stream, sname, MAXNAMELEN);
-        *sname = '\0';
+        if (flen > (MAXNAMELEN-1))
+                flen = (MAXNAMELEN-1);
+        (void) strncpy(filename, fname, flen);
+        filename[flen] = '\0';
 
+        if (slen > (MAXNAMELEN-1))
+                slen = (MAXNAMELEN-1);
+        (void) strncpy(stream, sname, slen);
+        stream[slen] = '\0';
+
+        /* Add a "stream type" if there isn't one. */
         stype = strchr(stream + 1, ':');
         if (stype == NULL)
                 (void) strlcat(stream, ":$DATA", MAXNAMELEN);
         else
                 (void) smb_strupr(stype);

@@ -1048,10 +1172,31 @@
 
         return (B_TRUE);
 }
 
 /*
+ * Is this stream node a "restricted" type?
+ */
+boolean_t
+smb_strname_restricted(char *strname)
+{
+        char *stype;
+
+        stype = strrchr(strname, ':');
+        if (stype == NULL)
+                return (B_FALSE);
+
+        /*
+         * Only ":$CA" is restricted (for now).
+         */
+        if (strcmp(stype, ":$CA") == 0)
+                return (B_TRUE);
+
+        return (B_FALSE);
+}
+
+/*
  * smb_validate_stream_name
  *
  * B_FALSE will be returned, and the error status ser in the sr, if:
  * - the path is not a stream name
  * - a path is specified but the fname is ommitted.

@@ -1061,10 +1206,11 @@
  */
 boolean_t
 smb_validate_stream_name(smb_request_t *sr, smb_pathname_t *pn)
 {
         static char *strmtype[] = {
+                "$CA",
                 "$DATA",
                 "$INDEX_ALLOCATION"
         };
         int i;