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)
NEX-16818 Add fksmbcl development tool
NEX-17264 SMB client test tp_smbutil_013 fails after NEX-14666
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
and: (fix ref leaks)
NEX-16805 Add smbutil discon command
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
re #12739 rb4173 AD join with lmauth_level=2 fails
re #12394 rb3934 Even NULL sessions should use SPNEGO

@@ -32,11 +32,11 @@
  * $Id: ctx.c,v 1.32.70.2 2005/06/02 00:55:40 lindak Exp $
  */
 
 /*
  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <sys/param.h>
 #include <sys/ioctl.h>
 #include <sys/time.h>

@@ -146,14 +146,10 @@
                 printf("BROWSEOK ");
         if (flags & SMBCF_AUTHREQ)
                 printf("AUTHREQ ");
         if (flags & SMBCF_KCSAVE)
                 printf("KCSAVE  ");
-        if (flags & SMBCF_XXX)
-                printf("XXX ");
-        if (flags & SMBCF_SSNACTIVE)
-                printf("SSNACTIVE ");
         if (flags & SMBCF_KCDOMAIN)
                 printf("KCDOMAIN ");
         printf("\n");
 }
 

@@ -167,10 +163,12 @@
         dump_sockaddr(&ssn->ssn_srvaddr.sa);
         printf(" dom=\"%s\", user=\"%s\"\n",
             ssn->ssn_domain, ssn->ssn_user);
         printf(" ct_vopt=0x%x, ct_owner=%d\n",
             ssn->ssn_vopt, ssn->ssn_owner);
+        printf(" ct_minver=0x%x, ct_maxver=0x%x\n",
+            ssn->ssn_minver, ssn->ssn_maxver);
         printf(" ct_authflags=0x%x\n", is->iod_authflags);
 
         printf(" ct_nthash:");
         if (bcmp(zeros, &is->iod_nthash, NTLM_HASH_SZ))
                 smb_hexdump(&is->iod_nthash, NTLM_HASH_SZ);

@@ -252,20 +250,20 @@
         if (error)
                 return (error);
 
         ctx->ct_dev_fd = -1;
         ctx->ct_door_fd = -1;
-        ctx->ct_tran_fd = -1;
         ctx->ct_parsedlevel = SMBL_NONE;
         ctx->ct_minlevel = SMBL_NONE;
         ctx->ct_maxlevel = SMBL_PATH;
 
         /* Fill in defaults */
-        ctx->ct_vopt = SMBVOPT_EXT_SEC;
+        ctx->ct_vopt = SMBVOPT_SIGNING_ENABLED;
         ctx->ct_owner = SMBM_ANY_OWNER;
         ctx->ct_authflags = SMB_AT_DEFAULT;
         ctx->ct_minauth = SMB_AT_MINAUTH;
+        ctx->ct_maxver = SMB2_DIALECT_MAX;
 
         /*
          * Default domain, user, ...
          */
         strlcpy(ctx->ct_domain, default_domain,

@@ -331,11 +329,16 @@
         ctx->ct_shtype_req = sharetype;
 
         cf_opt_lock();
         /* Careful: no return/goto before cf_opt_unlock! */
         while (error == 0) {
-                opt = cf_getopt(argc, argv, STDPARAM_OPT);
+                /*
+                 * Leading ':' tells this to skip unknown opts.
+                 * Just get -A and -U here so we know the user
+                 * for config file parsing.
+                 */
+                opt = cf_getopt(argc, argv, ":AU:");
                 if (opt == -1)
                         break;
                 arg = cf_optarg;
                 /* NB: handle most in smb_ctx_opt */
                 switch (opt) {

@@ -396,21 +399,17 @@
 {
 
         rpc_cleanup_smbctx(ctx);
 
         if (ctx->ct_dev_fd != -1) {
-                close(ctx->ct_dev_fd);
+                nsmb_close(ctx->ct_dev_fd);
                 ctx->ct_dev_fd = -1;
         }
         if (ctx->ct_door_fd != -1) {
                 close(ctx->ct_door_fd);
                 ctx->ct_door_fd = -1;
         }
-        if (ctx->ct_tran_fd != -1) {
-                close(ctx->ct_tran_fd);
-                ctx->ct_tran_fd = -1;
-        }
         if (ctx->ct_srvaddr_s) {
                 free(ctx->ct_srvaddr_s);
                 ctx->ct_srvaddr_s = NULL;
         }
         if (ctx->ct_nb) {

@@ -439,22 +438,14 @@
         }
         if (ctx->ct_rpath) {
                 free(ctx->ct_rpath);
                 ctx->ct_rpath = NULL;
         }
-        if (ctx->ct_srv_OS) {
-                free(ctx->ct_srv_OS);
-                ctx->ct_srv_OS = NULL;
+        if (ctx->ct_ssnkey_buf) {
+                free(ctx->ct_ssnkey_buf);
+                ctx->ct_ssnkey_buf = NULL;
         }
-        if (ctx->ct_srv_LM) {
-                free(ctx->ct_srv_LM);
-                ctx->ct_srv_LM = NULL;
-        }
-        if (ctx->ct_mackey) {
-                free(ctx->ct_mackey);
-                ctx->ct_mackey = NULL;
-        }
 }
 
 /*
  * Parse the UNC path.  Here we expect something like
  *   "//[[domain;]user[:password]@]host[/share[/path]]"

@@ -866,10 +857,41 @@
                         ctx->ct_vopt |= SMBVOPT_SIGNING_REQUIRED;
         }
         return (0);
 }
 
+/*
+ * Handle .nsmbrc "minver" option.
+ * Must be <= maxver
+ */
+int
+smb_ctx_setminver(struct smb_ctx *ctx, int ver)
+{
+        if (ver < 0 || ver > ctx->ct_maxver)
+                return (EINVAL);
+        ctx->ct_minver = (uint16_t)ver;
+        return (0);
+}
+
+/*
+ * Handle .nsmbrc "maxver" option.
+ * Must be >= minver
+ *
+ * Any "too high" value is just clamped, so the caller
+ * doesn't need to know what's the highest we support.
+ */
+int
+smb_ctx_setmaxver(struct smb_ctx *ctx, int ver)
+{
+        if (ver < 1 || ver < ctx->ct_minver)
+                return (EINVAL);
+        if (ver > SMB2_DIALECT_MAX)
+                ver = SMB2_DIALECT_MAX;
+        ctx->ct_maxver = (uint16_t)ver;
+        return (0);
+}
+
 static int
 smb_parse_owner(char *pair, uid_t *uid, gid_t *gid)
 {
         struct group gr;
         struct passwd pw;

@@ -897,16 +919,15 @@
 
         return (0);
 }
 
 /*
- * Suport a securty options arg, i.e. -S noext,lm,ntlm
+ * Suport a securty options arg, i.e. -S lm,ntlm
  * for testing various type of authenticators.
  */
 static struct nv
 sectype_table[] = {
-        /* noext - handled below */
         { "anon",       SMB_AT_ANON },
         { "lm",         SMB_AT_LM1 },
         { "ntlm",       SMB_AT_NTLM1 },
         { "ntlm2",      SMB_AT_NTLM2 },
         { "krb5",       SMB_AT_KRB5 },

@@ -928,17 +949,10 @@
 
                 nlen = strcspn(p, sep);
                 if (nlen == 0)
                         break;
 
-                if (nlen == 5 && 0 == strncmp(p, "noext", nlen)) {
-                        /* Don't offer extended security. */
-                        ctx->ct_vopt &= ~SMBVOPT_EXT_SEC;
-                        p += nlen;
-                        continue;
-                }
-
                 /* This is rarely called, so not optimized. */
                 for (nv = sectype_table; nv->name; nv++) {
                         tlen = strlen(nv->name);
                         if (tlen == nlen && 0 == strncmp(p, nv->name, tlen))
                                 break;

@@ -1115,10 +1129,23 @@
                 return (ENODATA);
         }
         assert(ctx->ct_addrinfo != NULL);
 
         /*
+         * Empty user name means an explicit request for
+         * NULL session setup, which is a special case.
+         * (No SMB signing, per [MS-SMB] 3.3.5.3)
+         */
+        if (ctx->ct_user[0] == '\0') {
+                /* Null user should have null domain too. */
+                ctx->ct_domain[0] = '\0';
+                ctx->ct_authflags = SMB_AT_ANON;
+                ctx->ct_vopt |= SMBVOPT_ANONYMOUS;
+                ctx->ct_vopt &= ~SMBVOPT_SIGNING_REQUIRED;
+        }
+
+        /*
          * If we have a user name but no password,
          * check for a keychain entry.
          * XXX: Only for auth NTLM?
          */
         if (ctx->ct_user[0] != '\0') {

@@ -1125,17 +1152,22 @@
                 /*
                  * Have a user name.
                  * If we don't have a p/w yet,
                  * try the keychain.
                  */
-                if (ctx->ct_password[0] == '\0')
-                        (void) smb_get_keychain(ctx);
+                if (ctx->ct_password[0] == '\0' &&
+                    smb_get_keychain(ctx) == 0) {
+                        strlcpy(ctx->ct_password, "$HASH",
+                            sizeof (ctx->ct_password));
+                }
+
                 /*
                  * Mask out disallowed auth types.
                  */
                 ctx->ct_authflags &= ctx->ct_minauth;
         }
+
         if (ctx->ct_authflags == 0) {
                 smb_error(dgettext(TEXT_DOMAIN,
                     "no valid auth. types"), 0);
                 return (ENOTSUP);
         }

@@ -1145,10 +1177,14 @@
                 dump_ctx("after smb_ctx_resolve", ctx);
 
         return (0);
 }
 
+/*
+ * Note: The next three have NODIRECT binding so the
+ * "fksmbcl" development tool can provide its own.
+ */
 int
 smb_open_driver()
 {
         int fd;
 

@@ -1162,20 +1198,32 @@
 
         return (fd);
 }
 
 int
+nsmb_close(int fd)
+{
+        return (close(fd));
+}
+
+int
+nsmb_ioctl(int fd, int cmd, void *arg)
+{
+        return (ioctl(fd, cmd, arg));
+}
+
+
+int
 smb_ctx_gethandle(struct smb_ctx *ctx)
 {
         int fd, err;
         uint32_t version;
 
         if (ctx->ct_dev_fd != -1) {
                 rpc_cleanup_smbctx(ctx);
-                close(ctx->ct_dev_fd);
+                nsmb_close(ctx->ct_dev_fd);
                 ctx->ct_dev_fd = -1;
-                ctx->ct_flags &= ~SMBCF_SSNACTIVE;
         }
 
         fd = smb_open_driver();
         if (fd < 0) {
                 err = errno;

@@ -1185,16 +1233,16 @@
         }
 
         /*
          * Check the driver version (paranoia)
          */
-        if (ioctl(fd, SMBIOC_GETVERS, &version) < 0)
+        if (nsmb_ioctl(fd, SMBIOC_GETVERS, &version) < 0)
                 version = 0;
         if (version != NSMB_VERSION) {
                 smb_error(dgettext(TEXT_DOMAIN,
                     "incorrect driver version"), 0);
-                close(fd);
+                nsmb_close(fd);
                 return (ENODEV);
         }
 
         ctx->ct_dev_fd = fd;
         return (0);

@@ -1210,24 +1258,28 @@
         int err = 0;
 
         if ((ctx->ct_flags & SMBCF_RESOLVED) == 0)
                 return (EINVAL);
 
-        if (ctx->ct_dev_fd < 0) {
-                if ((err = smb_ctx_gethandle(ctx)))
-                        return (err);
-        }
-
         /*
          * Check whether the driver already has a VC
          * we can use.  If so, we're done!
          */
         err = smb_ctx_findvc(ctx);
         if (err == 0) {
                 DPRINT("found an existing VC");
         } else {
                 /*
+                 * If we're authenticating (real user, not NULL session)
+                 * and we don't yet have a password, return EAUTH and
+                 * the caller will prompt for it and call again.
+                 */
+                if (ctx->ct_user[0] != '\0' &&
+                    ctx->ct_password[0] == '\0')
+                        return (EAUTH);
+
+                /*
                  * This calls the IOD to create a new session.
                  */
                 DPRINT("setup a new VC");
                 err = smb_ctx_newvc(ctx);
                 if (err != 0)

@@ -1275,11 +1327,11 @@
         /*
          * Todo: share passwords for share-level security.
          *
          * The driver does the actual TCON call.
          */
-        if (ioctl(ctx->ct_dev_fd, cmd, tcon) == -1) {
+        if (nsmb_ioctl(ctx->ct_dev_fd, cmd, tcon) == -1) {
                 err = errno;
                 goto out;
         }
 
         /*

@@ -1306,11 +1358,11 @@
 int
 smb_ctx_flags2(struct smb_ctx *ctx)
 {
         uint16_t flags2;
 
-        if (ioctl(ctx->ct_dev_fd, SMBIOC_FLAGS2, &flags2) == -1) {
+        if (nsmb_ioctl(ctx->ct_dev_fd, SMBIOC_FLAGS2, &flags2) == -1) {
                 smb_error(dgettext(TEXT_DOMAIN,
                     "can't get flags2 for a session"), errno);
                 return (-1);
         }
         return (flags2);

@@ -1324,11 +1376,11 @@
 smb_fh_getssnkey(int dev_fd, uchar_t *key, size_t len)
 {
         if (len < SMBIOC_HASH_SZ)
                 return (EINVAL);
 
-        if (ioctl(dev_fd, SMBIOC_GETSSNKEY, key) == -1)
+        if (nsmb_ioctl(dev_fd, SMBIOC_GETSSNKEY, key) == -1)
                 return (errno);
 
         return (0);
 }
 

@@ -1346,11 +1398,40 @@
         { "none",       SMB_AT_KRB5|SMB_AT_NTLM2|SMB_AT_NTLM1|SMB_AT_LM1|
                         SMB_AT_ANON },
         { NULL }
 };
 
+int
+smb_cf_minauth_from_str(char *str)
+{
+        struct nv *nvp;
 
+        for (nvp = minauth_table; nvp->name; nvp++)
+                if (strcmp(nvp->name, str) == 0)
+                        return (nvp->value);
+        return (-1);
+}
+
+
+static struct nv
+smbver_table[] = {
+        { "2.1",        SMB2_DIALECT_0210 },
+        { "1",          1 },
+        { NULL,         0 }
+};
+
+int
+smb_cf_version_from_str(char *str)
+{
+        struct nv *nvp;
+
+        for (nvp = smbver_table; nvp->name; nvp++)
+                if (strcmp(nvp->name, str) == 0)
+                        return (nvp->value);
+        return (-1);
+}
+
 /*
  * level values:
  * 0 - default
  * 1 - server
  * 2 - server:user

@@ -1358,11 +1439,13 @@
  */
 static int
 smb_ctx_readrcsection(struct smb_ctx *ctx, const char *sname, int level)
 {
         char *p;
+        int ival;
         int error;
+        int minver, maxver;
 
 #ifdef  KICONV_SUPPORT
         if (level > 0) {
                 rc_getstringptr(smb_rc, sname, "charsets", &p);
                 if (p) {

@@ -1376,23 +1459,83 @@
 #endif
 
         if (level <= 1) {
                 /* Section is: [default] or [server] */
 
+                /*
+                 * Handle min_protocol, max_protocol
+                 * (SMB protocol versions)
+                 */
+                minver = -1;
+                rc_getstringptr(smb_rc, sname, "min_protocol", &p);
+                if (p != NULL) {
+                        minver = smb_cf_version_from_str(p);
+                        if (minver == -1) {
+                                smb_error(dgettext(TEXT_DOMAIN,
+"invalid min_protocol value \"%s\" specified in the section %s"),
+                                    0, p, sname);
+                        }
+                }
+                maxver = -1;
+                rc_getstringptr(smb_rc, sname, "max_protocol", &p);
+                if (p != NULL) {
+                        maxver = smb_cf_version_from_str(p);
+                        if (maxver == -1) {
+                                smb_error(dgettext(TEXT_DOMAIN,
+"invalid max_protocol value \"%s\" specified in the section %s"),
+                                    0, p, sname);
+                        }
+                }
+
+                /*
+                 * If setting both min/max protocol,
+                 * validate against each other
+                 */
+                if (minver != -1 && maxver != -1) {
+                        if (minver > maxver) {
+                                smb_error(dgettext(TEXT_DOMAIN,
+"invalid min/max protocol combination in the section %s"),
+                                    0, sname);
+                        } else {
+                                ctx->ct_minver = minver;
+                                ctx->ct_maxver = maxver;
+                        }
+                }
+
+                /*
+                 * Setting just min or max, validate against
+                 * current settings
+                 */
+                if (minver != -1) {
+                        if (minver > ctx->ct_maxver) {
+                                smb_error(dgettext(TEXT_DOMAIN,
+"invalid min/max protocol combination in the section %s"),
+                                    0, sname);
+                        } else {
+                                ctx->ct_minver = minver;
+                        }
+                }
+                if (maxver != -1) {
+                        if (maxver < ctx->ct_minver) {
+                                smb_error(dgettext(TEXT_DOMAIN,
+"invalid min/max protocol combination in the section %s"),
+                                    0, sname);
+                        } else {
+                                ctx->ct_maxver = maxver;
+                        }
+                }
+
                 rc_getstringptr(smb_rc, sname, "minauth", &p);
                 if (p) {
                         /*
                          * "minauth" was set in this section; override
                          * the current minimum authentication setting.
                          */
-                        struct nv *nvp;
-                        for (nvp = minauth_table; nvp->name; nvp++)
-                                if (strcmp(p, nvp->name) == 0)
-                                        break;
-                        if (nvp->name)
-                                ctx->ct_minauth = nvp->value;
-                        else {
+                        ival = smb_cf_minauth_from_str(p);
+                        if (ival != -1) {
+                                ctx->ct_minauth = ival;
+                        } else {
                                 /*
                                  * Unknown minimum authentication level.
                                  */
                                 smb_error(dgettext(TEXT_DOMAIN,
 "invalid minimum authentication level \"%s\" specified in the section %s"),