Print this page
NEX-14666 Need to provide SMB 2.1 Client
NEX-17187 panic in smbfs_acl_store
NEX-17231 smbfs create xattr files finds wrong file
NEX-17224 smbfs lookup EINVAL should be ENOENT
NEX-17260 SMB1 client fails to list directory after NEX-14666
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Rick McNeal <rick.mcneal@nexenta.com>
Reviewed by: Saso Kiselkov <saso.kiselkov@nexenta.com>
Reviewed by: Joyce McIntosh <joyce.mcintosh@nexenta.com>
and: (cleanup)
NEX-16824 SMB client connection setup rework
NEX-17232 SMB client reconnect failures
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
and: (improve debug)

@@ -32,10 +32,12 @@
  * $Id: smb_rq.c,v 1.29 2005/02/11 01:44:17 lindak Exp $
  */
 
 /*
  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Portions Copyright (C) 2001 - 2013 Apple Inc. All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/time.h>

@@ -49,14 +51,16 @@
 #include <sys/sdt.h>
 
 #include <netsmb/smb_osdep.h>
 
 #include <netsmb/smb.h>
+#include <netsmb/smb2.h>
 #include <netsmb/smb_conn.h>
 #include <netsmb/smb_subr.h>
 #include <netsmb/smb_tran.h>
 #include <netsmb/smb_rq.h>
+#include <netsmb/smb2_rq.h>
 
 /*
  * How long to wait before restarting a request (after reconnect)
  */
 #define SMB_RCNDELAY            2       /* seconds */

@@ -67,13 +71,12 @@
  */
 #define SMBMAXRESTARTS          0
 
 
 static int  smb_rq_reply(struct smb_rq *rqp);
+static int  smb_rq_parsehdr(struct smb_rq *rqp);
 static int  smb_rq_enqueue(struct smb_rq *rqp);
-static int  smb_rq_getenv(struct smb_connobj *layer,
-                struct smb_vc **vcpp, struct smb_share **sspp);
 static int  smb_rq_new(struct smb_rq *rqp, uchar_t cmd);
 static int  smb_t2_reply(struct smb_t2rq *t2p);
 static int  smb_nt_reply(struct smb_ntrq *ntp);
 
 

@@ -104,10 +107,11 @@
         struct smb_rq **rqpp)
 {
         struct smb_rq *rqp;
         int error;
 
+        // XXX kmem cache?
         rqp = (struct smb_rq *)kmem_alloc(sizeof (struct smb_rq), KM_SLEEP);
         if (rqp == NULL)
                 return (ENOMEM);
         error = smb_rq_init(rqp, layer, cmd, scred);
         if (error) {

@@ -145,11 +149,10 @@
          * when creating and completing requests.
          */
 
         rqp->sr_rexmit = SMBMAXRESTARTS;
         rqp->sr_cred = scred;   /* Note: ref hold done by caller. */
-        rqp->sr_pid = (uint16_t)ddi_get_pid();
         error = smb_rq_new(rqp, cmd);
 
         return (error);
 }
 

@@ -161,38 +164,61 @@
         int error;
 
         ASSERT(rqp != NULL);
 
         rqp->sr_sendcnt = 0;
-        rqp->sr_cmd = cmd;
 
         mb_done(mbp);
         md_done(&rqp->sr_rp);
         error = mb_init(mbp);
         if (error)
                 return (error);
 
+        if (vcp->vc_flags & SMBV_SMB2) {
         /*
-         * Is this the right place to save the flags?
+                 * SMB2 request initialization
          */
+                rqp->sr2_command = cmd;
+                rqp->sr2_creditcharge = 1;
+                rqp->sr2_creditsrequested = 1;
+                rqp->sr_pid = 0xFEFF;   /* Made up, just like Windows */
+                rqp->sr2_rqflags = 0;
+                if ((vcp->vc_flags & SMBV_SIGNING) != 0 &&
+                    vcp->vc_mackey != NULL) {
+                        rqp->sr2_rqflags |= SMB2_FLAGS_SIGNED;
+                }
+
+                /*
+                 * The SMB2 header is filled in later by
+                 * smb2_rq_fillhdr (see smb2_rq.c)
+                 * Just reserve space here.
+                 */
+                mb_put_mem(mbp, NULL, SMB2_HDRLEN, MB_MZERO);
+        } else {
+                /*
+                 * SMB1 request initialization
+                 */
+                rqp->sr_cmd = cmd;
+                rqp->sr_pid = (uint32_t)ddi_get_pid();
         rqp->sr_rqflags  = vcp->vc_hflags;
         rqp->sr_rqflags2 = vcp->vc_hflags2;
 
         /*
          * The SMB header is filled in later by
          * smb_rq_fillhdr (see below)
          * Just reserve space here.
          */
         mb_put_mem(mbp, NULL, SMB_HDRLEN, MB_MZERO);
+        }
 
         return (0);
 }
 
 /*
  * Given a request with it's body already composed,
  * rewind to the start and fill in the SMB header.
- * This is called after the request is enqueued,
+ * This is called when the request is enqueued,
  * so we have the final MID, seq num. etc.
  */
 void
 smb_rq_fillhdr(struct smb_rq *rqp)
 {

@@ -215,11 +241,11 @@
         mb_put_uint16le(mbp, rqp->sr_rqflags2);
         mb_put_uint16le(mbp, 0);        /* pid-high */
         mb_put_mem(mbp, NULL, 8, MB_MZERO);     /* MAC sig. (later) */
         mb_put_uint16le(mbp, 0);        /* reserved */
         mb_put_uint16le(mbp, rqp->sr_rqtid);
-        mb_put_uint16le(mbp, rqp->sr_pid);
+        mb_put_uint16le(mbp, (uint16_t)rqp->sr_pid);
         mb_put_uint16le(mbp, rqp->sr_rquid);
         mb_put_uint16le(mbp, rqp->sr_mid);
 
         /* This will free the mblk from dupb. */
         mb_done(mbp);

@@ -279,10 +305,12 @@
 {
         struct smb_vc *vcp = rqp->sr_vc;
         struct smb_share *ssp = rqp->sr_share;
         int error = 0;
 
+        ASSERT((vcp->vc_flags & SMBV_SMB2) == 0);
+
         /*
          * Normal requests may initiate a reconnect,
          * and/or wait for state changes to finish.
          * Some requests set the NORECONNECT flag
          * to avoid all that (i.e. tree discon)

@@ -323,16 +351,80 @@
          * Store them in the request.
          */
 ok_out:
         rqp->sr_rquid = vcp->vc_smbuid;
         rqp->sr_rqtid = ssp ? ssp->ss_tid : SMB_TID_UNKNOWN;
-        error = smb_iod_addrq(rqp);
+        error = smb1_iod_addrq(rqp);
 
         return (error);
 }
 
 /*
+ * Used by the IOD thread during connection setup,
+ * and for smb_echo after network timeouts.  Note that
+ * unlike smb_rq_simple, callers must check sr_error.
+ */
+int
+smb_rq_internal(struct smb_rq *rqp, int timeout)
+{
+        struct smb_vc *vcp = rqp->sr_vc;
+        int error;
+
+        ASSERT((vcp->vc_flags & SMBV_SMB2) == 0);
+
+        rqp->sr_flags &= ~SMBR_RESTART;
+        rqp->sr_timo = timeout; /* in seconds */
+        rqp->sr_state = SMBRQ_NOTSENT;
+
+        /*
+         * In-line smb_rq_enqueue(rqp) here, as we don't want it
+         * trying to reconnect etc. for an internal request.
+         */
+        rqp->sr_rquid = vcp->vc_smbuid;
+        rqp->sr_rqtid = SMB_TID_UNKNOWN;
+        rqp->sr_flags |= SMBR_INTERNAL;
+        error = smb1_iod_addrq(rqp);
+        if (error != 0)
+                return (error);
+
+        /*
+         * In-line a variant of smb_rq_reply(rqp) here as we may
+         * need to do custom parsing for SMB1-to-SMB2 negotiate.
+         */
+        if (rqp->sr_timo == SMBNOREPLYWAIT) {
+                smb_iod_removerq(rqp);
+                return (0);
+        }
+
+        error = smb_iod_waitrq_int(rqp);
+        if (error)
+                return (error);
+
+        /*
+         * If the request was signed, validate the
+         * signature on the response.
+         */
+        if (rqp->sr_rqflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) {
+                error = smb_rq_verify(rqp);
+                if (error)
+                        return (error);
+        }
+
+        /*
+         * Parse the SMB header.
+         */
+        error = smb_rq_parsehdr(rqp);
+
+        /*
+         * Skip the error translation smb_rq_reply does.
+         * Callers of this expect "raw" NT status.
+         */
+
+        return (error);
+}
+
+/*
  * Mark location of the word count, which is filled in later by
  * smb_rw_wend().  Also initialize the counter that it uses
  * to figure out what value to fill in.
  *
  * Note that the word count happens to be 8-bit.

@@ -396,19 +488,10 @@
         rqp->sr_bcount[0] = bcnt & 0xFF;
         rqp->sr_bcount[1] = (bcnt >> 8);
 }
 
 int
-smb_rq_intr(struct smb_rq *rqp)
-{
-        if (rqp->sr_flags & SMBR_INTR)
-                return (EINTR);
-
-        return (0);
-}
-
-static int
 smb_rq_getenv(struct smb_connobj *co,
         struct smb_vc **vcpp, struct smb_share **sspp)
 {
         struct smb_vc *vcp = NULL;
         struct smb_share *ssp = NULL;

@@ -455,18 +538,16 @@
 
         return (error);
 }
 
 /*
- * Wait for reply on the request
+ * Wait for a reply to this request, then parse it.
  */
 static int
 smb_rq_reply(struct smb_rq *rqp)
 {
-        struct mdchain *mdp = &rqp->sr_rp;
-        u_int8_t tb;
-        int error, rperror = 0;
+        int error;
 
         if (rqp->sr_timo == SMBNOREPLYWAIT) {
                 smb_iod_removerq(rqp);
                 return (0);
         }

@@ -486,18 +567,27 @@
         }
 
         /*
          * Parse the SMB header
          */
-        error = md_get_uint32le(mdp, NULL);
-        if (error)
+        error = smb_rq_parsehdr(rqp);
+        if (error != 0)
                 return (error);
-        error = md_get_uint8(mdp, &tb);
-        error = md_get_uint32le(mdp, &rqp->sr_error);
-        error = md_get_uint8(mdp, &rqp->sr_rpflags);
-        error = md_get_uint16le(mdp, &rqp->sr_rpflags2);
+
+        if (rqp->sr_error != 0) {
         if (rqp->sr_rpflags2 & SMB_FLAGS2_ERR_STATUS) {
+                        error = smb_maperr32(rqp->sr_error);
+                } else {
+                        uint8_t errClass = rqp->sr_error & 0xff;
+                        uint16_t errCode = rqp->sr_error >> 16;
+                        /* Convert to NT status */
+                        rqp->sr_error = smb_doserr2status(errClass, errCode);
+                        error = smb_maperror(errClass, errCode);
+                }
+        }
+
+        if (error != 0) {
                 /*
                  * Do a special check for STATUS_BUFFER_OVERFLOW;
                  * it's not an error.
                  */
                 if (rqp->sr_error == NT_STATUS_BUFFER_OVERFLOW) {

@@ -504,38 +594,69 @@
                         /*
                          * Don't report it as an error to our caller;
                          * they can look at rqp->sr_error if they
                          * need to know whether we got a
                          * STATUS_BUFFER_OVERFLOW.
-                         * XXX - should we do that for all errors
-                         * where (error & 0xC0000000) is 0x80000000,
-                         * i.e. all warnings?
                          */
-                        rperror = 0;
-                } else
-                        rperror = smb_maperr32(rqp->sr_error);
-        } else {
-                rqp->sr_errclass = rqp->sr_error & 0xff;
-                rqp->sr_serror = rqp->sr_error >> 16;
-                rperror = smb_maperror(rqp->sr_errclass, rqp->sr_serror);
-        }
-        if (rperror == EMOREDATA) {
-                rperror = E2BIG;
                 rqp->sr_flags |= SMBR_MOREDATA;
-        } else
+                        error = 0;
+                }
+        } else {
                 rqp->sr_flags &= ~SMBR_MOREDATA;
+        }
 
-        error = md_get_uint32le(mdp, NULL);
-        error = md_get_uint32le(mdp, NULL);
-        error = md_get_uint32le(mdp, NULL);
+        return (error);
+}
 
-        error = md_get_uint16le(mdp, &rqp->sr_rptid);
-        error = md_get_uint16le(mdp, &rqp->sr_rppid);
-        error = md_get_uint16le(mdp, &rqp->sr_rpuid);
+/*
+ * Parse the SMB header
+ */
+static int
+smb_rq_parsehdr(struct smb_rq *rqp)
+{
+        struct mdchain mdp_save;
+        struct mdchain *mdp = &rqp->sr_rp;
+        u_int8_t tb, sig[4];
+        int error;
+
+        /*
+         * Parse the signature.  The reader already checked that
+         * the signature is valid.  Here we just have to check
+         * for SMB1-to-SMB2 negotiate.  Caller handles an EPROTO
+         * as a signal that we got an SMB2 reply.  If we return
+         * EPROTO, rewind the mdchain back where it was.
+         */
+        mdp_save = *mdp;
+        error = md_get_mem(mdp, sig, 4, MB_MSYSTEM);
+        if (error)
+                return (error);
+        if (sig[0] != SMB_HDR_V1) {
+                if (rqp->sr_cmd == SMB_COM_NEGOTIATE) {
+                        *mdp = mdp_save;
+                        return (EPROTO);
+                }
+                return (EBADRPC);
+        }
+
+        /* Check cmd */
+        error = md_get_uint8(mdp, &tb);
+        if (tb != rqp->sr_cmd)
+                return (EBADRPC);
+
+        md_get_uint32le(mdp, &rqp->sr_error);
+        md_get_uint8(mdp, &rqp->sr_rpflags);
+        md_get_uint16le(mdp, &rqp->sr_rpflags2);
+
+        /* Skip: pid-high(2), MAC sig(8), reserved(2) */
+        md_get_mem(mdp, NULL, 12, MB_MSYSTEM);
+
+        md_get_uint16le(mdp, &rqp->sr_rptid);
+        md_get_uint16le(mdp, &rqp->sr_rppid);
+        md_get_uint16le(mdp, &rqp->sr_rpuid);
         error = md_get_uint16le(mdp, &rqp->sr_rpmid);
 
-        return ((error) ? error : rperror);
+        return (error);
 }
 
 
 #define ALIGN4(a)       (((a) + 3) & ~3)
 

@@ -1132,11 +1253,11 @@
                         if (error)
                                 goto bad;
                         mb_put_mbuf(mbp, m);
                 }
                 smb_rq_bend(rqp);
-                error = smb_iod_multirq(rqp);
+                error = smb1_iod_multirq(rqp);
                 if (error)
                         goto bad;
         }       /* while left params or data */
         error = smb_t2_reply(t2p);
         if (error && !(t2p->t2_flags & SMBT2_MOREDATA))

@@ -1343,11 +1464,11 @@
                         if (error)
                                 goto bad;
                         mb_put_mbuf(mbp, m);
                 }
                 smb_rq_bend(rqp);
-                error = smb_iod_multirq(rqp);
+                error = smb1_iod_multirq(rqp);
                 if (error)
                         goto bad;
         }       /* while left params or data */
         error = smb_nt_reply(ntp);
         if (error && !(ntp->nt_flags & SMBT2_MOREDATA))

@@ -1435,6 +1556,86 @@
                         delay(SEC_TO_TICK(SMB_RCNDELAY));
                 }
                 mutex_exit(&(ntp)->nt_lock);
         }
         return (error);
+}
+
+/*
+ * Run an SMB transact named pipe.
+ * Note: send_mb is consumed.
+ */
+int
+smb_t2_xnp(struct smb_share *ssp, uint16_t fid,
+    struct mbchain *send_mb, struct mdchain *recv_md,
+    uint32_t *data_out_sz, /* max / returned */
+    uint32_t *more, struct smb_cred *scrp)
+{
+        struct smb_t2rq *t2p = NULL;
+        mblk_t *m;
+        uint16_t setup[2];
+        int err;
+
+        setup[0] = TRANS_TRANSACT_NAMED_PIPE;
+        setup[1] = fid;
+
+        t2p = kmem_alloc(sizeof (*t2p), KM_SLEEP);
+        err = smb_t2_init(t2p, SSTOCP(ssp), setup, 2, scrp);
+        if (err) {
+                *data_out_sz = 0;
+                goto out;
+        }
+
+        t2p->t2_setupcount = 2;
+        t2p->t2_setupdata  = setup;
+
+        t2p->t_name = "\\PIPE\\";
+        t2p->t_name_len = 6;
+
+        t2p->t2_maxscount = 0;
+        t2p->t2_maxpcount = 0;
+        t2p->t2_maxdcount = (uint16_t)*data_out_sz;
+
+        /* Transmit parameters (none) */
+
+        /*
+         * Transmit data
+         *
+         * Copy the mb, and clear the source so we
+         * don't end up with a double free.
+         */
+        t2p->t2_tdata = *send_mb;
+        bzero(send_mb, sizeof (*send_mb));
+
+        /*
+         * Run the request
+         */
+        err = smb_t2_request(t2p);
+
+        /* No returned parameters. */
+
+        if (err == 0 && (m = t2p->t2_rdata.md_top) != NULL) {
+                /*
+                 * Received data
+                 *
+                 * Copy the mdchain, and clear the source so we
+                 * don't end up with a double free.
+                 */
+                *data_out_sz = msgdsize(m);
+                md_initm(recv_md, m);
+                t2p->t2_rdata.md_top = NULL;
+        } else {
+                *data_out_sz = 0;
+        }
+
+        if (t2p->t2_sr_error == NT_STATUS_BUFFER_OVERFLOW)
+                *more = 1;
+
+out:
+        if (t2p != NULL) {
+                /* Note: t2p->t_name no longer allocated */
+                smb_t2_done(t2p);
+                kmem_free(t2p, sizeof (*t2p));
+        }
+
+        return (err);
 }