Print this page
NEX-19378 Access problem with SMB server
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
NEX-19152 MacOS HighSierra Finder crashes...
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-13644 File access audit logging
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
Reviewed by: Saso Kiselkov <saso.kiselkov@nexenta.com>
Reviewed by: Rick McNeal <rick.mcneal@nexenta.com>
Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com>
NEX-17779 Creating named streams on existing files is not quite right
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
NEX-17289 Minimal SMB 3.0.2 support
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
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-15069 smtorture smb2.create.blob is failed
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-15069 smtorture smb2.create.blob is failed
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-13653 Obsolete SMB server work-around for ZFS read-only
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-6276 SMB sparse file support
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-5844 want SMB2 ioctl FSCTL_SRV_COPYCHUNK
NEX-6124 smb_fsop_read/write should allow file != sr->fid_ofile
NEX-6125 smbtorture invalid response with smb2.ioctl
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
NEX-6041 Should pass the smbtorture lock tests
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
NEX-4474 SMB open with access=MAXIMUM_ALLOWED fails after NEX-3232
Reviewed by: Bayard Bell <bayard.bell@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
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-58 smbsrv should be immune to its own FEM hooks
SMB-50 User-mode SMB server
 Includes work by these authors:
 Thomas Keiser <thomas.keiser@nexenta.com>
 Albert Lee <trisk@nexenta.com>
SMB-65 SMB server in non-global zones (use zone_kcred())
SMB-63 taskq_create_proc ... TQ_DYNAMIC puts tasks in p0
re #11974 CIFS Share - Tree connect fails from Windows 7 Clients
SUS-172 Excel 2003 warning dialog when re-saving a file
SUS-173 Open fails if the client does not ask for read_attribute permission
re #7815 SMB server delivers old modification time...
re #11215 rb3676 sesctl to SGI JBOD hangs in biowait() with a command stuck in mptsas driver
re #10734 NT Trans. Notify returning too quickly

@@ -18,26 +18,27 @@
  *
  * 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 <sys/sid.h>
 #include <sys/nbmlock.h>
 #include <smbsrv/smb_fsops.h>
 #include <smbsrv/smb_kproto.h>
 #include <acl/acl_common.h>
 #include <sys/fcntl.h>
+#include <sys/filio.h>
 #include <sys/flock.h>
 #include <fs/fs_subr.h>
 
 extern caller_context_t smb_ct;
 
-static int smb_fsop_create_stream(smb_request_t *, cred_t *, smb_node_t *,
-    char *, char *, int, smb_attr_t *, smb_node_t **);
+static int smb_fsop_create_file_with_stream(smb_request_t *, cred_t *,
+    smb_node_t *, char *, char *, int, smb_attr_t *, smb_node_t **);
 
 static int smb_fsop_create_file(smb_request_t *, cred_t *, smb_node_t *,
     char *, int, smb_attr_t *, smb_node_t **);
 
 #ifdef  _KERNEL

@@ -131,12 +132,14 @@
         cred_t *kcr = zone_kcred();
         int aclbsize = 0;       /* size of acl list in bytes */
         int flags = 0;
         int rc;
         boolean_t is_dir;
+        boolean_t do_audit;
 
         ASSERT(fs_sd);
+        ASSERT(ret_snode != NULL);
 
         if (SMB_TREE_IS_CASEINSENSITIVE(sr))
                 flags = SMB_IGNORE_CASE;
         if (SMB_TREE_SUPPORTS_CATIA(sr))
                 flags |= SMB_CATIA;

@@ -143,10 +146,11 @@
 
         ASSERT(cr);
 
         is_dir = ((fs_sd->sd_flags & SMB_FSSD_FLAGS_DIR) != 0);
 
+        do_audit = smb_audit_init(sr);
         if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_ACLONCREATE)) {
                 if (fs_sd->sd_secinfo & SMB_ACL_SECINFO) {
                         dacl = fs_sd->sd_zdacl;
                         sacl = fs_sd->sd_zsacl;
                         ASSERT(dacl || sacl);

@@ -181,10 +185,16 @@
                         if (SMB_TREE_HAS_ACCESS(sr, ACE_ADD_FILE) != 0)
                                 rc = smb_vop_create(dnode->vp, name, attr,
                                     &vp, flags, cr, vsap);
                 }
 
+                if (do_audit) {
+                        smb_audit_fini(sr,
+                            is_dir ? ACE_ADD_SUBDIRECTORY : ACE_ADD_FILE,
+                            dnode, rc == 0);
+                }
+
                 if (vsap != NULL)
                         kmem_free(vsap->vsa_aclentp, aclbsize);
 
                 if (rc != 0)
                         return (rc);

@@ -235,10 +245,16 @@
                 } else {
                         rc = smb_vop_create(dnode->vp, name, attr, &vp,
                             flags, cr, NULL);
                 }
 
+                if (do_audit) {
+                        smb_audit_fini(sr,
+                            is_dir ? ACE_ADD_SUBDIRECTORY : ACE_ADD_FILE,
+                            dnode, rc == 0);
+                }
+
                 if (rc != 0)
                         return (rc);
 
                 *ret_snode = smb_node_lookup(sr, &sr->arg.open, cr, vp,
                     name, dnode, NULL);

@@ -317,11 +333,11 @@
         if (smb_is_stream_name(name)) {
                 fname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
                 sname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
                 smb_stream_parse_name(name, fname, sname);
 
-                rc = smb_fsop_create_stream(sr, cr, dnode,
+                rc = smb_fsop_create_file_with_stream(sr, cr, dnode,
                     fname, sname, flags, attr, ret_snode);
 
                 kmem_free(fname, MAXNAMELEN);
                 kmem_free(sname, MAXNAMELEN);
                 return (rc);

@@ -346,40 +362,38 @@
 
 }
 
 
 /*
- * smb_fsop_create_stream
+ * smb_fsop_create_file_with_stream
  *
- * Create NTFS named stream file (sname) on unnamed stream
- * file (fname), creating the unnamed stream file if it
+ * Create named stream (sname) on file (fname), creating the file if it
  * doesn't exist.
- * If we created the unnamed stream file and then creation
- * of the named stream file fails, we delete the unnamed stream.
+ * If we created the file and then creation of the named stream fails,
+ * we delete the file.
  * Since we use the real file name for the smb_vop_remove we
  * clear the SMB_IGNORE_CASE flag to ensure a case sensitive
  * match.
  *
- * The second parameter of smb_vop_setattr() is set to
- * NULL, even though an unnamed stream exists.  This is
- * because we want to set the UID and GID on the named
- * stream in this case for consistency with the (unnamed
- * stream) file (see comments for smb_vop_setattr()).
+ * Note that some stream "types" are "restricted" and only
+ * internal callers (cr == kcred) can create those.
  */
 static int
-smb_fsop_create_stream(smb_request_t *sr, cred_t *cr,
+smb_fsop_create_file_with_stream(smb_request_t *sr, cred_t *cr,
     smb_node_t *dnode, char *fname, char *sname, int flags,
     smb_attr_t *attr, smb_node_t **ret_snode)
 {
-        smb_attr_t      fattr;
         smb_node_t      *fnode;
-        vnode_t         *xattrdvp;
-        vnode_t         *vp;
         cred_t          *kcr = zone_kcred();
         int             rc = 0;
         boolean_t       fcreate = B_FALSE;
 
+        ASSERT(ret_snode != NULL);
+
+        if (cr != kcr && smb_strname_restricted(sname))
+                return (EACCES);
+
         /* Look up / create the unnamed stream, fname */
         rc = smb_fsop_lookup(sr, cr, flags | SMB_FOLLOW_LINKS,
             sr->tid_tree->t_snode, dnode, fname, &fnode);
         if (rc == ENOENT) {
                 fcreate = B_TRUE;

@@ -387,52 +401,91 @@
                     attr, &fnode);
         }
         if (rc != 0)
                 return (rc);
 
-        fattr.sa_mask = SMB_AT_UID | SMB_AT_GID;
-        rc = smb_vop_getattr(fnode->vp, NULL, &fattr, 0, kcr);
+        rc = smb_fsop_create_stream(sr, cr, dnode, fnode, sname, flags, attr,
+            ret_snode);
 
-        if (rc == 0) {
-                /* create the named stream, sname */
-                rc = smb_vop_stream_create(fnode->vp, sname, attr,
-                    &vp, &xattrdvp, flags, cr);
-        }
         if (rc != 0) {
                 if (fcreate) {
                         flags &= ~SMB_IGNORE_CASE;
                         (void) smb_vop_remove(dnode->vp,
                             fnode->od_name, flags, cr);
                 }
+        }
+
                 smb_node_release(fnode);
                 return (rc);
+}
+
+/*
+ * smb_fsop_create_stream
+ *
+ * Create named stream (sname) on existing file (fnode).
+ *
+ * The second parameter of smb_vop_setattr() is set to
+ * NULL, even though an unnamed stream exists.  This is
+ * because we want to set the UID and GID on the named
+ * stream in this case for consistency with the (unnamed
+ * stream) file (see comments for smb_vop_setattr()).
+ *
+ * Note that some stream "types" are "restricted" and only
+ * internal callers (cr == kcred) can create those.
+ */
+int
+smb_fsop_create_stream(smb_request_t *sr, cred_t *cr,
+    smb_node_t *dnode, smb_node_t *fnode, char *sname, int flags,
+    smb_attr_t *attr, smb_node_t **ret_snode)
+{
+        smb_attr_t      fattr;
+        vnode_t         *xattrdvp;
+        vnode_t         *vp;
+        cred_t          *kcr = zone_kcred();
+        int             rc = 0;
+
+        ASSERT(ret_snode != NULL);
+
+        if (cr != kcr && smb_strname_restricted(sname))
+                return (EACCES);
+
+        bzero(&fattr, sizeof (fattr));
+        fattr.sa_mask = SMB_AT_UID | SMB_AT_GID;
+        rc = smb_vop_getattr(fnode->vp, NULL, &fattr, 0, kcr);
+
+        if (rc == 0) {
+                /* create the named stream, sname */
+                rc = smb_vop_stream_create(fnode->vp, sname,
+                    attr, &vp, &xattrdvp, flags, cr);
         }
+        if (rc != 0)
+                return (rc);
 
         attr->sa_vattr.va_uid = fattr.sa_vattr.va_uid;
         attr->sa_vattr.va_gid = fattr.sa_vattr.va_gid;
         attr->sa_mask = SMB_AT_UID | SMB_AT_GID;
 
         rc = smb_vop_setattr(vp, NULL, attr, 0, kcr);
         if (rc != 0) {
-                smb_node_release(fnode);
+                VN_RELE(xattrdvp);
+                VN_RELE(vp);
                 return (rc);
         }
 
         *ret_snode = smb_stream_node_lookup(sr, cr, fnode, xattrdvp,
             vp, sname);
 
-        smb_node_release(fnode);
         VN_RELE(xattrdvp);
         VN_RELE(vp);
 
         if (*ret_snode == NULL)
                 rc = ENOMEM;
 
         /* notify change to the unnamed stream */
         if (rc == 0)
                 smb_node_notify_change(dnode,
-                    FILE_ACTION_ADDED_STREAM, fname);
+                    FILE_ACTION_ADDED_STREAM, fnode->od_name);
 
         return (rc);
 }
 
 /*

@@ -445,10 +498,12 @@
 {
         smb_arg_open_t  *op = &sr->sr_open;
         vnode_t         *vp;
         int             rc;
 
+        ASSERT(ret_snode != NULL);
+
 #ifdef  _KERNEL
         smb_fssd_t      fs_sd;
         uint32_t        secinfo;
         uint32_t        status;
 

@@ -486,14 +541,23 @@
 #endif  /* _KERNEL */
         {
                 /*
                  * No incoming SD and filesystem is not ZFS
                  * let the filesystem handles the inheritance.
+                 *
+                 * fsop_create_with_sd handles auditing in the other cases.
+                 * Handle it explicitly here.
                  */
+                boolean_t do_audit = smb_audit_init(sr);
+
                 rc = smb_vop_create(dnode->vp, name, attr, &vp,
                     flags, cr, NULL);
 
+                if (do_audit) {
+                        smb_audit_fini(sr, ACE_ADD_FILE, dnode, rc == 0);
+                }
+
                 if (rc == 0) {
                         *ret_snode = smb_node_lookup(sr, op, cr, vp,
                             name, dnode, NULL);
 
                         if (*ret_snode == NULL)

@@ -625,13 +689,24 @@
                 smb_fssd_term(&fs_sd);
 
         } else
 #endif  /* _KERNEL */
         {
+                /*
+                 * fsop_create_with_sd handles auditing in the other cases.
+                 * Handle it explicitly here.
+                 */
+                boolean_t do_audit = smb_audit_init(sr);
+
                 rc = smb_vop_mkdir(dnode->vp, name, attr, &vp, flags, cr,
                     NULL);
 
+                if (do_audit) {
+                        smb_audit_fini(sr, ACE_ADD_SUBDIRECTORY, dnode,
+                            rc == 0);
+                }
+
                 if (rc == 0) {
                         *ret_snode = smb_node_lookup(sr, op, cr, vp, name,
                             dnode, NULL);
 
                         if (*ret_snode == NULL)

@@ -656,10 +731,13 @@
  * for avoiding this wrapper.
  *
  * It is assumed that a reference exists on snode coming into this routine.
  *
  * A null smb_request might be passed to this function.
+ *
+ * Note that some stream "types" are "restricted" and only
+ * internal callers (cr == kcred) can remove those.
  */
 int
 smb_fsop_remove(
     smb_request_t       *sr,
     cred_t              *cr,

@@ -691,10 +769,15 @@
 
         fname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
         sname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
 
         if (dnode->flags & NODE_XATTR_DIR) {
+                if (cr != zone_kcred() && smb_strname_restricted(name)) {
+                        rc = EACCES;
+                        goto out;
+                }
+
                 fnode = dnode->n_dnode;
                 rc = smb_vop_stream_remove(fnode->vp, name, flags, cr);
 
                 /* notify change to the unnamed stream */
                 if ((rc == 0) && fnode->n_dnode) {

@@ -702,10 +785,15 @@
                             FILE_ACTION_REMOVED_STREAM, fnode->od_name);
                 }
         } else if (smb_is_stream_name(name)) {
                 smb_stream_parse_name(name, fname, sname);
 
+                if (cr != zone_kcred() && smb_strname_restricted(sname)) {
+                        rc = EACCES;
+                        goto out;
+                }
+
                 /*
                  * Look up the unnamed stream (i.e. fname).
                  * Unmangle processing will be done on fname
                  * as well as any link target.
                  */

@@ -712,13 +800,11 @@
 
                 rc = smb_fsop_lookup(sr, cr, flags | SMB_FOLLOW_LINKS,
                     sr->tid_tree->t_snode, dnode, fname, &fnode);
 
                 if (rc != 0) {
-                        kmem_free(fname, MAXNAMELEN);
-                        kmem_free(sname, MAXNAMELEN);
-                        return (rc);
+                        goto out;
                 }
 
                 /*
                  * XXX
                  * Need to find out what permission is required by NTFS

@@ -737,13 +823,11 @@
                 rc = smb_vop_remove(dnode->vp, name, flags, cr);
 
                 if (rc == ENOENT) {
                         if (!SMB_TREE_SUPPORTS_SHORTNAMES(sr) ||
                             !smb_maybe_mangled(name)) {
-                                kmem_free(fname, MAXNAMELEN);
-                                kmem_free(sname, MAXNAMELEN);
-                                return (rc);
+                                goto out;
                         }
                         longname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
 
                         if (SMB_TREE_SUPPORTS_ABE(sr))
                                 flags |= SMB_ABE;

@@ -769,10 +853,11 @@
                         smb_node_notify_change(dnode,
                             FILE_ACTION_REMOVED, name);
                 }
         }
 
+out:
         kmem_free(fname, MAXNAMELEN);
         kmem_free(sname, MAXNAMELEN);
 
         return (rc);
 }

@@ -814,10 +899,11 @@
 
         status = smb_odir_openat(sr, fnode, &od);
         switch (status) {
         case 0:
                 break;
+        case NT_STATUS_OBJECT_NAME_NOT_FOUND:
         case NT_STATUS_NO_SUCH_FILE:
         case NT_STATUS_NOT_SUPPORTED:
                 /* No streams to remove. */
                 return (0);
         default:

@@ -1112,12 +1198,17 @@
 
         /*
          * XXX: Lock required through smb_node_release() below?
          */
 
+        /*
+         * Don't audit the lookup
+         */
+        smb_audit_save();
         rc = smb_vop_lookup(from_dnode->vp, from_name, &from_vp, NULL,
             flags, &ret_flags, NULL, &from_attr, cr);
+        smb_audit_load();
 
         if (rc != 0)
                 return (rc);
 
         if (from_attr.sa_dosattr & FILE_ATTRIBUTE_REPARSE_POINT) {

@@ -1153,10 +1244,11 @@
                 if (rc != NT_STATUS_SUCCESS) {
                         VN_RELE(from_vp);
                         return (EACCES);
                 }
 
+                /* TODO: rename drops ATTR_NOACLCHECK, so this is a no-op. */
                 if (smb_tree_has_feature(sr->tid_tree,
                     SMB_TREE_ACEMASKONACCESS))
                         flags = ATTR_NOACLCHECK;
         }
 

@@ -1237,29 +1329,10 @@
         if (SMB_TREE_HAS_ACCESS(sr,
             ACE_WRITE_ATTRIBUTES | ACE_WRITE_NAMED_ATTRS) == 0)
                 return (EACCES);
 
         /*
-         * The file system cannot detect pending READDONLY
-         * (i.e. if the file has been opened readonly but
-         * not yet closed) so we need to test READONLY here.
-         *
-         * Note that file handle that were opened before the
-         * READONLY flag was set in the node (or the FS) are
-         * immune to that change, and remain writable.
-         */
-        if (sr && (set_attr->sa_mask & SMB_AT_SIZE)) {
-                if (sr->fid_ofile) {
-                        if (SMB_OFILE_IS_READONLY(sr->fid_ofile))
-                                return (EACCES);
-                } else {
-                        if (SMB_PATHFILE_IS_READONLY(sr, snode))
-                                return (EACCES);
-                }
-        }
-
-        /*
          * SMB checks access on open and retains an access granted
          * mask for use while the file is open.  ACL changes should
          * not affect access to an open file.
          *
          * If the setattr is being performed on an ofile:

@@ -1311,25 +1384,22 @@
         return (rc);
 }
 
 /*
  * Support for SMB2 setinfo FileValidDataLengthInformation.
- * Free data from the specified offset to EoF.
- *
- * This can effectively truncate data.  It truncates the data
- * leaving the file size as it was, leaving zeros after the
- * offset specified here.  That is effectively modifying the
- * file content, so for access control this is a write.
+ * Free (zero out) data in the range off, off+len
  */
 int
-smb_fsop_set_data_length(
+smb_fsop_freesp(
     smb_request_t       *sr,
     cred_t              *cr,
-    smb_node_t          *node,
-    offset_t            end_of_data)
+    smb_ofile_t         *ofile,
+    off64_t             off,
+    off64_t             len)
 {
         flock64_t flk;
+        smb_node_t *node = ofile->f_node;
         uint32_t status;
         uint32_t access = FILE_WRITE_DATA;
         int rc;
 
         ASSERT(cr);

@@ -1345,27 +1415,10 @@
 
         if (SMB_TREE_HAS_ACCESS(sr, access) == 0)
                 return (EACCES);
 
         /*
-         * The file system cannot detect pending READDONLY
-         * (i.e. if the file has been opened readonly but
-         * not yet closed) so we need to test READONLY here.
-         *
-         * Note that file handle that were opened before the
-         * READONLY flag was set in the node (or the FS) are
-         * immune to that change, and remain writable.
-         */
-        if (sr->fid_ofile) {
-                if (SMB_OFILE_IS_READONLY(sr->fid_ofile))
-                        return (EACCES);
-        } else {
-                /* This requires an open file. */
-                return (EACCES);
-        }
-
-        /*
          * SMB checks access on open and retains an access granted
          * mask for use while the file is open.  ACL changes should
          * not affect access to an open file.
          *
          * If the setattr is being performed on an ofile:

@@ -1375,11 +1428,12 @@
         status = smb_ofile_access(sr->fid_ofile, cr, access);
         if (status != NT_STATUS_SUCCESS)
                 return (EACCES);
 
         bzero(&flk, sizeof (flk));
-        flk.l_start = end_of_data;
+        flk.l_start = off;
+        flk.l_len = len;
 
         rc = smb_vop_space(node->vp, F_FREESP, &flk, FWRITE, 0LL, cr);
         return (rc);
 }
 

@@ -1390,37 +1444,48 @@
  * the the calls are performed with the appropriate credentials.
  * Please document any direct call to explain the reason
  * for avoiding this wrapper.
  *
  * It is assumed that a reference exists on snode coming into this routine.
+ * Note that ofile may be different from sr->fid_ofile, or may be NULL.
  */
 int
-smb_fsop_read(smb_request_t *sr, cred_t *cr, smb_node_t *snode, uio_t *uio)
+smb_fsop_read(smb_request_t *sr, cred_t *cr, smb_node_t *snode,
+    smb_ofile_t *ofile, uio_t *uio, int ioflag)
 {
         caller_context_t ct;
         cred_t *kcr = zone_kcred();
+        uint32_t amask;
         int svmand;
         int rc;
 
         ASSERT(cr);
         ASSERT(snode);
         ASSERT(snode->n_magic == SMB_NODE_MAGIC);
         ASSERT(snode->n_state != SMB_NODE_STATE_DESTROYING);
 
         ASSERT(sr);
-        ASSERT(sr->fid_ofile);
 
-        if (SMB_TREE_HAS_ACCESS(sr, ACE_READ_DATA) == 0)
+        if (ofile != NULL) {
+                /*
+                 * Check tree access.  Not SMB_TREE_HAS_ACCESS
+                 * because we need to use ofile->f_tree
+                 */
+                if ((ofile->f_tree->t_access & ACE_READ_DATA) == 0)
                 return (EACCES);
 
-        rc = smb_ofile_access(sr->fid_ofile, cr, FILE_READ_DATA);
-        if ((rc != NT_STATUS_SUCCESS) &&
-            (sr->smb_flg2 & SMB_FLAGS2_READ_IF_EXECUTE))
-                rc = smb_ofile_access(sr->fid_ofile, cr, FILE_EXECUTE);
-
-        if (rc != NT_STATUS_SUCCESS)
+                /*
+                 * Check ofile access.  Use in-line smb_ofile_access
+                 * so we can check both amask bits at the same time.
+                 * If any bit in amask is granted, allow this read.
+                 */
+                amask = FILE_READ_DATA;
+                if (sr->smb_flg2 & SMB_FLAGS2_READ_IF_EXECUTE)
+                        amask |= FILE_EXECUTE;
+                if (cr != kcr && (ofile->f_granted_access & amask) == 0)
                 return (EACCES);
+        }
 
         /*
          * Streams permission are checked against the unnamed stream,
          * but in FS level they have their own permissions. To avoid
          * rejection by FS due to lack of permission on the actual

@@ -1434,74 +1499,82 @@
         if (rc) {
                 smb_node_end_crit(snode);
                 return (rc);
         }
 
+        /*
+         * Note: SMB allows a zero-byte read, which should not
+         * conflict with any locks.  However nbl_lock_conflict
+         * takes a zero-byte length as lock to EOF, so we must
+         * special case that here.
+         */
+        if (uio->uio_resid > 0) {
         ct = smb_ct;
-        ct.cc_pid = sr->fid_ofile->f_uniqid;
+                if (ofile != NULL)
+                        ct.cc_pid = ofile->f_uniqid;
         rc = nbl_lock_conflict(snode->vp, NBL_READ, uio->uio_loffset,
-            uio->uio_iov->iov_len, svmand, &ct);
-
-        if (rc) {
+                    uio->uio_resid, svmand, &ct);
+                if (rc != 0) {
                 smb_node_end_crit(snode);
                 return (ERANGE);
         }
+        }
 
-        rc = smb_vop_read(snode->vp, uio, cr);
+        rc = smb_vop_read(snode->vp, uio, ioflag, cr);
         smb_node_end_crit(snode);
 
         return (rc);
 }
 
 /*
  * smb_fsop_write
  *
- * This is a wrapper function used for smb_write and smb_write_raw operations.
- *
  * It is assumed that a reference exists on snode coming into this routine.
+ * Note that ofile may be different from sr->fid_ofile, or may be NULL.
  */
 int
 smb_fsop_write(
     smb_request_t *sr,
     cred_t *cr,
     smb_node_t *snode,
+    smb_ofile_t *ofile,
     uio_t *uio,
     uint32_t *lcount,
     int ioflag)
 {
         caller_context_t ct;
         smb_attr_t attr;
+        cred_t *kcr = zone_kcred();
         smb_node_t *u_node;
         vnode_t *u_vp = NULL;
-        smb_ofile_t *of;
         vnode_t *vp;
-        cred_t *kcr = zone_kcred();
+        uint32_t amask;
         int svmand;
         int rc;
 
         ASSERT(cr);
         ASSERT(snode);
         ASSERT(snode->n_magic == SMB_NODE_MAGIC);
         ASSERT(snode->n_state != SMB_NODE_STATE_DESTROYING);
 
         ASSERT(sr);
-        ASSERT(sr->tid_tree);
-        of = sr->fid_ofile;
         vp = snode->vp;
 
-        if (SMB_TREE_IS_READONLY(sr))
+        if (ofile != NULL) {
+                amask = FILE_WRITE_DATA | FILE_APPEND_DATA;
+
+                /* Check tree access. */
+                if ((ofile->f_tree->t_access & amask) == 0)
                 return (EROFS);
 
-        if (SMB_OFILE_IS_READONLY(of) ||
-            SMB_TREE_HAS_ACCESS(sr, ACE_WRITE_DATA | ACE_APPEND_DATA) == 0)
+                /*
+                 * Check ofile access.  Use in-line smb_ofile_access
+                 * so we can check both amask bits at the same time.
+                 * If any bit in amask is granted, allow this write.
+                 */
+                if (cr != kcr && (ofile->f_granted_access & amask) == 0)
                 return (EACCES);
-
-        rc = smb_ofile_access(of, cr, FILE_WRITE_DATA);
-        if (rc != NT_STATUS_SUCCESS) {
-                rc = smb_ofile_access(of, cr, FILE_APPEND_DATA);
-                if (rc != NT_STATUS_SUCCESS)
-                        return (EACCES);
         }
 
         /*
          * Streams permission are checked against the unnamed stream,
          * but in FS level they have their own permissions. To avoid

@@ -1521,19 +1594,27 @@
         if (rc) {
                 smb_node_end_crit(snode);
                 return (rc);
         }
 
+        /*
+         * Note: SMB allows a zero-byte write, which should not
+         * conflict with any locks.  However nbl_lock_conflict
+         * takes a zero-byte length as lock to EOF, so we must
+         * special case that here.
+         */
+        if (uio->uio_resid > 0) {
         ct = smb_ct;
-        ct.cc_pid = of->f_uniqid;
+                if (ofile != NULL)
+                        ct.cc_pid = ofile->f_uniqid;
         rc = nbl_lock_conflict(vp, NBL_WRITE, uio->uio_loffset,
-            uio->uio_iov->iov_len, svmand, &ct);
-
-        if (rc) {
+                    uio->uio_resid, svmand, &ct);
+                if (rc != 0) {
                 smb_node_end_crit(snode);
                 return (ERANGE);
         }
+        }
 
         rc = smb_vop_write(vp, uio, ioflag, lcount, cr);
 
         /*
          * Once the mtime has been set via this ofile, the

@@ -1544,12 +1625,13 @@
          * The VFS interface does not offer a way to ask it to
          * skip the mtime updates, so we simulate the desired
          * behavior by re-setting the mtime after writes on a
          * handle where the mtime has been set.
          */
-        if (of->f_pending_attr.sa_mask & SMB_AT_MTIME) {
-                bcopy(&of->f_pending_attr, &attr, sizeof (attr));
+        if (ofile != NULL &&
+            (ofile->f_pending_attr.sa_mask & SMB_AT_MTIME) != 0) {
+                bcopy(&ofile->f_pending_attr, &attr, sizeof (attr));
                 attr.sa_mask = SMB_AT_MTIME;
                 (void) smb_vop_setattr(vp, u_vp, &attr, 0, kcr);
         }
 
         smb_node_end_crit(snode);

@@ -1556,10 +1638,34 @@
 
         return (rc);
 }
 
 /*
+ * Find the next allocated range starting at or after
+ * the offset (*datap), returning the start/end of
+ * that range in (*datap, *holep)
+ */
+int
+smb_fsop_next_alloc_range(
+    cred_t *cr,
+    smb_node_t *node,
+    off64_t *datap,
+    off64_t *holep)
+{
+        int err;
+
+        err = smb_vop_ioctl(node->vp, _FIO_SEEK_DATA, datap, cr);
+        if (err != 0)
+                return (err);
+
+        *holep = *datap;
+        err = smb_vop_ioctl(node->vp, _FIO_SEEK_HOLE, holep, cr);
+
+        return (err);
+}
+
+/*
  * smb_fsop_statfs
  *
  * This is a wrapper function used for stat operations.
  */
 int

@@ -1587,10 +1693,13 @@
  * separate from that on the unnamed stream. If READ or EXECUTE
  * access has been requested on a named stream, an additional access
  * check is performed on the named stream in case it has been
  * quarantined.  kcred is used to avoid issues with the permissions
  * set on the extended attribute file representing the named stream.
+ *
+ * Note that some stream "types" are "restricted" and only
+ * internal callers (cr == kcred) can access those.
  */
 int
 smb_fsop_access(smb_request_t *sr, cred_t *cr, smb_node_t *snode,
     uint32_t faccess)
 {

@@ -1617,21 +1726,26 @@
         if (smb_node_is_reparse(snode) && (faccess & DELETE))
                 return (NT_STATUS_ACCESS_DENIED);
 
         unnamed_node = SMB_IS_STREAM(snode);
         if (unnamed_node) {
+                cred_t *kcr = zone_kcred();
+
                 ASSERT(unnamed_node->n_magic == SMB_NODE_MAGIC);
                 ASSERT(unnamed_node->n_state != SMB_NODE_STATE_DESTROYING);
 
+                if (cr != kcr && smb_strname_restricted(snode->od_name))
+                        return (NT_STATUS_ACCESS_DENIED);
+
                 /*
                  * Perform VREAD access check on the named stream in case it
                  * is quarantined. kcred is passed to smb_vop_access so it
                  * doesn't fail due to lack of permission.
                  */
                 if (faccess & (FILE_READ_DATA | FILE_EXECUTE)) {
                         error = smb_vop_access(snode->vp, VREAD,
-                            0, NULL, zone_kcred());
+                            0, NULL, kcr);
                         if (error)
                                 return (NT_STATUS_ACCESS_DENIED);
                 }
 
                 /*

@@ -1690,13 +1804,17 @@
 }
 
 /*
  * smb_fsop_lookup_name()
  *
+ * Lookup both the file and stream specified in 'name'.
  * If name indicates that the file is a stream file, perform
  * stream specific lookup, otherwise call smb_fsop_lookup.
  *
+ * On success, returns the found node in *ret_snode. This will be either a named
+ * or unnamed stream node, depending on the name specified.
+ *
  * Return an error if the looked-up file is in outside the tree.
  * (Required when invoked from open path.)
  *
  * Case sensitivity flags (SMB_IGNORE_CASE, SMB_CASE_SENSITIVE):
  * if SMB_CASE_SENSITIVE is set, the SMB_IGNORE_CASE flag will NOT be set

@@ -1714,22 +1832,68 @@
     smb_node_t  *root_node,
     smb_node_t  *dnode,
     char        *name,
     smb_node_t  **ret_snode)
 {
-        smb_node_t      *fnode;
-        vnode_t         *xattrdirvp;
-        vnode_t         *vp;
-        char            *od_name;
+        char *sname = NULL;
+        int rc;
+        smb_node_t *tmp_node;
+
+        ASSERT(ret_snode != NULL);
+
+        rc = smb_fsop_lookup_file(sr, cr, flags, root_node, dnode, name,
+            &sname, ret_snode);
+
+        if (rc != 0 || sname == NULL)
+                return (rc);
+
+        tmp_node = *ret_snode;
+        rc = smb_fsop_lookup_stream(sr, cr, flags, root_node, tmp_node, sname,
+            ret_snode);
+        kmem_free(sname, MAXNAMELEN);
+        smb_node_release(tmp_node);
+
+        return (rc);
+}
+
+/*
+ * smb_fsop_lookup_file()
+ *
+ * Look up of the file portion of 'name'. If a Stream is specified,
+ * return the stream name in 'sname', which this allocates.
+ * The caller must free 'sname'.
+ *
+ * Return an error if the looked-up file is outside the tree.
+ * (Required when invoked from open path.)
+ *
+ * Case sensitivity flags (SMB_IGNORE_CASE, SMB_CASE_SENSITIVE):
+ * if SMB_CASE_SENSITIVE is set, the SMB_IGNORE_CASE flag will NOT be set
+ * based on the tree's case sensitivity. However, if the SMB_IGNORE_CASE
+ * flag is set in the flags value passed as a parameter, a case insensitive
+ * lookup WILL be done (regardless of whether SMB_CASE_SENSITIVE is set
+ * or not).
+ */
+
+int
+smb_fsop_lookup_file(
+    smb_request_t *sr,
+    cred_t      *cr,
+    int         flags,
+    smb_node_t  *root_node,
+    smb_node_t  *dnode,
+    char        *name,
+    char        **sname,
+    smb_node_t  **ret_snode)
+{
         char            *fname;
-        char            *sname;
         int             rc;
 
         ASSERT(cr);
         ASSERT(dnode);
         ASSERT(dnode->n_magic == SMB_NODE_MAGIC);
         ASSERT(dnode->n_state != SMB_NODE_STATE_DESTROYING);
+        ASSERT(ret_snode != NULL);
 
         /*
          * The following check is required for streams processing, below
          */
 

@@ -1736,82 +1900,99 @@
         if (!(flags & SMB_CASE_SENSITIVE)) {
                 if (SMB_TREE_IS_CASEINSENSITIVE(sr))
                         flags |= SMB_IGNORE_CASE;
         }
 
+        *sname = NULL;
+        if (smb_is_stream_name(name)) {
+                *sname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
         fname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
-        sname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
+                smb_stream_parse_name(name, fname, *sname);
 
-        if (smb_is_stream_name(name)) {
-                smb_stream_parse_name(name, fname, sname);
-
                 /*
                  * Look up the unnamed stream (i.e. fname).
                  * Unmangle processing will be done on fname
                  * as well as any link target.
                  */
                 rc = smb_fsop_lookup(sr, cr, flags, root_node, dnode,
-                    fname, &fnode);
-
-                if (rc != 0) {
+                    fname, ret_snode);
                         kmem_free(fname, MAXNAMELEN);
-                        kmem_free(sname, MAXNAMELEN);
+        } else {
+                rc = smb_fsop_lookup(sr, cr, flags, root_node, dnode, name,
+                    ret_snode);
+        }
+
+        if (rc == 0) {
+                ASSERT(ret_snode);
+                if (SMB_TREE_CONTAINS_NODE(sr, *ret_snode) == 0) {
+                        smb_node_release(*ret_snode);
+                        *ret_snode = NULL;
+                        rc = EACCES;
+                }
+        }
+
+        if (rc != 0 && *sname != NULL) {
+                kmem_free(*sname, MAXNAMELEN);
+                *sname = NULL;
+        }
                         return (rc);
+}
+
+/*
+ * smb_fsop_lookup_stream
+ *
+ * The file exists, see if the stream exists.
+ */
+int
+smb_fsop_lookup_stream(
+    smb_request_t *sr,
+    cred_t *cr,
+    int flags,
+    smb_node_t *root_node,
+    smb_node_t *fnode,
+    char *sname,
+    smb_node_t **ret_snode)
+{
+        char            *od_name;
+        vnode_t         *xattrdirvp;
+        vnode_t         *vp;
+        int rc;
+
+        /*
+         * The following check is required for streams processing, below
+         */
+
+        if (!(flags & SMB_CASE_SENSITIVE)) {
+                if (SMB_TREE_IS_CASEINSENSITIVE(sr))
+                        flags |= SMB_IGNORE_CASE;
                 }
 
                 od_name = kmem_alloc(MAXNAMELEN, KM_SLEEP);
 
                 /*
                  * od_name is the on-disk name of the stream, except
                  * without the prepended stream prefix (SMB_STREAM_PREFIX)
                  */
 
-                /*
-                 * XXX
-                 * What permissions NTFS requires for stream lookup if any?
-                 */
                 rc = smb_vop_stream_lookup(fnode->vp, sname, &vp, od_name,
                     &xattrdirvp, flags, root_node->vp, cr);
 
                 if (rc != 0) {
-                        smb_node_release(fnode);
-                        kmem_free(fname, MAXNAMELEN);
-                        kmem_free(sname, MAXNAMELEN);
                         kmem_free(od_name, MAXNAMELEN);
                         return (rc);
                 }
 
                 *ret_snode = smb_stream_node_lookup(sr, cr, fnode, xattrdirvp,
                     vp, od_name);
 
                 kmem_free(od_name, MAXNAMELEN);
-                smb_node_release(fnode);
                 VN_RELE(xattrdirvp);
                 VN_RELE(vp);
 
-                if (*ret_snode == NULL) {
-                        kmem_free(fname, MAXNAMELEN);
-                        kmem_free(sname, MAXNAMELEN);
+        if (*ret_snode == NULL)
                         return (ENOMEM);
-                }
-        } else {
-                rc = smb_fsop_lookup(sr, cr, flags, root_node, dnode, name,
-                    ret_snode);
-        }
 
-        if (rc == 0) {
-                ASSERT(ret_snode);
-                if (SMB_TREE_CONTAINS_NODE(sr, *ret_snode) == 0) {
-                        smb_node_release(*ret_snode);
-                        *ret_snode = NULL;
-                        rc = EACCES;
-                }
-        }
-
-        kmem_free(fname, MAXNAMELEN);
-        kmem_free(sname, MAXNAMELEN);
-
         return (rc);
 }
 
 /*
  * smb_fsop_lookup

@@ -1879,12 +2060,21 @@
         if (SMB_TREE_SUPPORTS_CATIA(sr))
                 flags |= SMB_CATIA;
         if (SMB_TREE_SUPPORTS_ABE(sr))
                 flags |= SMB_ABE;
 
-        od_name = kmem_alloc(MAXNAMELEN, KM_SLEEP);
+        /*
+         * Can have "" or "." when opening named streams on a directory.
+         */
+        if (name[0] == '\0' || (name[0] == '.' && name[1] == '\0')) {
+                smb_node_ref(dnode);
+                *ret_snode = dnode;
+                return (0);
+        }
 
+        od_name = kmem_zalloc(MAXNAMELEN, KM_SLEEP);
+
         rc = smb_vop_lookup(dnode->vp, name, &vp, od_name, flags,
             &ret_flags, root_node ? root_node->vp : NULL, &attr, cr);
 
         if (rc != 0) {
                 if (!SMB_TREE_SUPPORTS_SHORTNAMES(sr) ||

@@ -1925,11 +2115,11 @@
         }
 
         if ((flags & SMB_FOLLOW_LINKS) && (vp->v_type == VLNK) &&
             ((attr.sa_dosattr & FILE_ATTRIBUTE_REPARSE_POINT) == 0)) {
                 rc = smb_pathname(sr, od_name, FOLLOW, root_node, dnode,
-                    &lnk_dnode, &lnk_target_node, cr);
+                    &lnk_dnode, &lnk_target_node, cr, NULL);
 
                 if (rc != 0) {
                         /*
                          * The link is assumed to be for the last component
                          * of a path.  Hence any ENOTDIR error will be returned

@@ -2034,14 +2224,17 @@
 {
         int error = 0;
         int flags = 0;
         int access = 0;
         acl_t *acl;
-        smb_node_t *unnamed_node;
 
         ASSERT(cr);
 
+        /* Can't query security on named streams */
+        if (SMB_IS_STREAM(snode) != NULL)
+                return (EINVAL);
+
         if (SMB_TREE_HAS_ACCESS(sr, ACE_READ_ACL) == 0)
                 return (EACCES);
 
         if (sr->fid_ofile) {
                 if (fs_sd->sd_secinfo & SMB_DACL_SECINFO)

@@ -2054,20 +2247,10 @@
                 if (error != NT_STATUS_SUCCESS) {
                         return (EACCES);
                 }
         }
 
-        unnamed_node = SMB_IS_STREAM(snode);
-        if (unnamed_node) {
-                ASSERT(unnamed_node->n_magic == SMB_NODE_MAGIC);
-                ASSERT(unnamed_node->n_state != SMB_NODE_STATE_DESTROYING);
-                /*
-                 * Streams don't have ACL, any read ACL attempt on a stream
-                 * should be performed on the unnamed stream.
-                 */
-                snode = unnamed_node;
-        }
 
         if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_ACEMASKONACCESS))
                 flags = ATTR_NOACLCHECK;
 
         error = smb_vop_acl_read(snode->vp, &acl, flags,

@@ -2100,19 +2283,22 @@
         int target_flavor;
         int error = 0;
         int flags = 0;
         int access = 0;
         acl_t *acl, *dacl, *sacl;
-        smb_node_t *unnamed_node;
 
         ASSERT(cr);
 
         ASSERT(sr);
         ASSERT(sr->tid_tree);
         if (SMB_TREE_IS_READONLY(sr))
                 return (EROFS);
 
+        /* Can't set security on named streams */
+        if (SMB_IS_STREAM(snode) != NULL)
+                return (EINVAL);
+
         if (SMB_TREE_HAS_ACCESS(sr, ACE_WRITE_ACL) == 0)
                 return (EACCES);
 
         if (sr->fid_ofile) {
                 if (fs_sd->sd_secinfo & SMB_DACL_SECINFO)

@@ -2136,21 +2322,10 @@
                 break;
         default:
                 return (EINVAL);
         }
 
-        unnamed_node = SMB_IS_STREAM(snode);
-        if (unnamed_node) {
-                ASSERT(unnamed_node->n_magic == SMB_NODE_MAGIC);
-                ASSERT(unnamed_node->n_state != SMB_NODE_STATE_DESTROYING);
-                /*
-                 * Streams don't have ACL, any write ACL attempt on a stream
-                 * should be performed on the unnamed stream.
-                 */
-                snode = unnamed_node;
-        }
-
         dacl = fs_sd->sd_zdacl;
         sacl = fs_sd->sd_zsacl;
 
         ASSERT(dacl || sacl);
         if ((dacl == NULL) && (sacl == NULL))

@@ -2201,10 +2376,14 @@
         smb_attr_t attr;
 
         ASSERT(cr);
         ASSERT(fs_sd);
 
+        /* Can't query security on named streams */
+        if (SMB_IS_STREAM(snode) != NULL)
+                return (EINVAL);
+
         /*
          * File's uid/gid is fetched in two cases:
          *
          * 1. it's explicitly requested
          *

@@ -2366,10 +2545,14 @@
         ASSERT(sr);
         ASSERT(sr->tid_tree);
         if (SMB_TREE_IS_READONLY(sr))
                 return (EROFS);
 
+        /* Can't set security on named streams */
+        if (SMB_IS_STREAM(snode) != NULL)
+                return (EINVAL);
+
         bzero(&set_attr, sizeof (smb_attr_t));
 
         if (fs_sd->sd_secinfo & SMB_OWNER_SECINFO) {
                 set_attr.sa_vattr.va_uid = fs_sd->sd_uid;
                 set_attr.sa_mask |= SMB_AT_UID;

@@ -2556,10 +2739,19 @@
                 *eaccess |= FILE_EXECUTE;
 
         if (access & VWRITE)
                 *eaccess |= FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES |
                     FILE_WRITE_EA | FILE_APPEND_DATA | FILE_DELETE_CHILD;
+
+        if (access & (VREAD | VWRITE))
+                *eaccess |= SYNCHRONIZE;
+
+#ifdef  _FAKE_KERNEL
+        /* Should be: if (we are the owner)... */
+        if (access & VWRITE)
+                *eaccess |= DELETE | WRITE_DAC | WRITE_OWNER;
+#endif
 }
 
 /*
  * smb_fsop_shrlock
  *

@@ -2636,10 +2828,18 @@
          *    incompatibilities between POSIX and Windows. In the Windows world,
          *    if a client submits such a lock, the server will not lock any
          *    bytes. Interestingly if the same lock (same offset and length) is
          *    resubmitted Windows will consider that there is an overlap and
          *    the granting rules will then apply.
+         *
+         * 3) The SMB-level process IDs (smb_pid) are not passed down to the
+         *    POSIX level in l_pid because (a) the rules about lock PIDs are
+         *    different in SMB, and (b) we're putting our ofile f_uniqid in
+         *    the POSIX l_pid field to segregate locks per SMB ofile.
+         *    (We're also using a "remote" system ID in l_sysid.)
+         *    All SMB locking PIDs are handled at the SMB level and
+         *    not exposed in POSIX locking.
          */
         if ((lock->l_length == 0) ||
             ((lock->l_start + lock->l_length - 1) < lock->l_start))
                 return (0);