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-15557 SMB client tries to connect twice
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-15557 SMB client tries to connect twice
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
SUP-513 Unable to join AD domain (with NtlmMinSeverSec set in the registry)
 Implement "Extended Session Security" and "Key Exchange" in NTLMSSP
re #12394 rb3934 Even NULL sessions should use SPNEGO

@@ -20,11 +20,12 @@
  */
 
 /*
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
- * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+ *
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
  * Functions to setup connections (TCP and/or NetBIOS)
  * This has the fall-back logic for IP6, IP4, NBT

@@ -48,481 +49,348 @@
 #include <sys/fcntl.h>
 
 #include <netinet/in.h>
 #include <netinet/tcp.h>
 #include <arpa/inet.h>
+#include <uuid/uuid.h>
 
 #include <netsmb/smb.h>
 #include <netsmb/smb_lib.h>
+#include <netsmb/mchain.h>
 #include <netsmb/netbios.h>
 #include <netsmb/nb_lib.h>
 #include <netsmb/smb_dev.h>
 
+#include <cflib.h>
+
 #include "charsets.h"
 #include "private.h"
+#include "smb_crypt.h"
 
-/*
- * SMB messages are up to 64K.
- * Let's leave room for two.
- */
-static int smb_tcpsndbuf = 0x20000;
-static int smb_tcprcvbuf = 0x20000;
-static int smb_connect_timeout = 30; /* seconds */
-int smb_recv_timeout = 30; /* seconds */
+static int
+smb__ssnsetup(struct smb_ctx *ctx,
+        struct mbdata *mbc1, struct mbdata *mbc2);
 
-int conn_tcp6(struct smb_ctx *, const struct sockaddr *, int);
-int conn_tcp4(struct smb_ctx *, const struct sockaddr *, int);
-int conn_nbt(struct smb_ctx *, const struct sockaddr *, char *);
+int smb_ssnsetup_spnego(struct smb_ctx *, struct mbdata *);
 
-/*
- * Internal set sockopt for int-sized options.
- * Borrowed from: libnsl/rpc/ti_opts.c
- */
-static int
-smb_setopt_int(int fd, int level, int name, int val)
+const char *
+smb_iod_state_name(enum smbiod_state st)
 {
-        struct t_optmgmt oreq, ores;
-        struct {
-                struct t_opthdr oh;
-                int ival;
-        } opts;
+        const char *n = "(?)";
 
-        /* opt header */
-        opts.oh.len = sizeof (opts);
-        opts.oh.level = level;
-        opts.oh.name = name;
-        opts.oh.status = 0;
-        opts.ival = val;
-
-        oreq.flags = T_NEGOTIATE;
-        oreq.opt.buf = (void *)&opts;
-        oreq.opt.len = sizeof (opts);
-
-        ores.flags = 0;
-        ores.opt.buf = NULL;
-        ores.opt.maxlen = 0;
-
-        if (t_optmgmt(fd, &oreq, &ores) < 0) {
-                DPRINT("t_opgmgnt, t_errno = %d", t_errno);
-                if (t_errno == TSYSERR)
-                        return (errno);
-                return (EPROTO);
+        switch (st) {
+        case SMBIOD_ST_UNINIT:
+                n = "UNINIT!";
+                break;
+        case SMBIOD_ST_IDLE:
+                n = "IDLE";
+                break;
+        case SMBIOD_ST_RECONNECT:
+                n = "RECONNECT";
+                break;
+        case SMBIOD_ST_RCFAILED:
+                n = "RCFAILED";
+                break;
+        case SMBIOD_ST_CONNECTED:
+                n = "CONNECTED";
+                break;
+        case SMBIOD_ST_NEGOTIATED:
+                n = "NEGOTIATED";
+                break;
+        case SMBIOD_ST_AUTHCONT:
+                n = "AUTHCONT";
+                break;
+        case SMBIOD_ST_AUTHFAIL:
+                n = "AUTHFAIL";
+                break;
+        case SMBIOD_ST_AUTHOK:
+                n = "AUTHOK";
+                break;
+        case SMBIOD_ST_VCACTIVE:
+                n = "VCACTIVE";
+                break;
+        case SMBIOD_ST_DEAD:
+                n = "DEAD";
+                break;
         }
-        if (ores.flags != T_SUCCESS) {
-                DPRINT("flags 0x%x, status 0x%x",
-                    (int)ores.flags, (int)opts.oh.status);
-                return (EPROTO);
-        }
 
-        return (0);
+        return (n);
 }
 
-static int
-smb_setopts(int fd)
-{
-        int err;
-
-        /*
-         * Set various socket/TCP options.
-         * Failures here are not fatal -
-         * just log a complaint.
+/*
+ * Make a new connection, or reconnect.
          *
-         * We don't need these two:
-         *   SO_RCVTIMEO, SO_SNDTIMEO
+ * This is called first from the door service thread in smbiod
+ * (so that can report success or failure to the door client)
+ * and thereafter it's called when we need to reconnect after a
+ * network outage (or whatever might cause connection loss).
          */
-
-        err = smb_setopt_int(fd, SOL_SOCKET, SO_SNDBUF, smb_tcpsndbuf);
-        if (err) {
-                DPRINT("set SO_SNDBUF, err %d", err);
-        }
-
-        err = smb_setopt_int(fd, SOL_SOCKET, SO_RCVBUF, smb_tcprcvbuf);
-        if (err) {
-                DPRINT("set SO_RCVBUF, err %d", err);
-        }
-
-        err = smb_setopt_int(fd, SOL_SOCKET, SO_KEEPALIVE, 1);
-        if (err) {
-                DPRINT("set SO_KEEPALIVE, err %d", err);
-        }
-
-        err = smb_setopt_int(fd, IPPROTO_TCP, TCP_NODELAY, 1);
-        if (err) {
-                DPRINT("set TCP_NODELAY, err %d", err);
-        }
-
-        /* Set the connect timeout (in milliseconds). */
-        err = smb_setopt_int(fd, IPPROTO_TCP,
-            TCP_CONN_ABORT_THRESHOLD,
-            smb_connect_timeout * 1000);
-        if (err) {
-                DPRINT("set connect timeout, err %d", err);
-        }
-        return (0);
-}
-
-
 int
-conn_tcp6(struct smb_ctx *ctx, const struct sockaddr *sa, int port)
+smb_iod_connect(smb_ctx_t *ctx)
 {
-        struct sockaddr_in6 sin6;
-        char *dev = "/dev/tcp6";
-        char paddrbuf[INET6_ADDRSTRLEN];
-        struct t_call sndcall;
-        int fd, err;
+        smbioc_ossn_t *ossn = &ctx->ct_ssn;
+        smbioc_ssn_work_t *work = &ctx->ct_work;
+        char *uuid_str;
+        int err;
+        struct mbdata blob;
+        char *nego_buf = NULL;
+        uint32_t nego_len;
 
-        if (sa->sa_family != AF_INET6) {
-                DPRINT("bad af %d", sa->sa_family);
+        memset(&blob, 0, sizeof (blob));
+
+        if (ctx->ct_srvname[0] == '\0') {
+                DPRINT("sername not set!");
                 return (EINVAL);
         }
-        bcopy(sa, &sin6, sizeof (sin6));
-        sin6.sin6_port = htons(port);
+        DPRINT("server: %s", ctx->ct_srvname);
 
-        DPRINT("tcp6: %s (%d)",
-            inet_ntop(AF_INET6, &sin6.sin6_addr,
-            paddrbuf, sizeof (paddrbuf)), port);
+        if (smb_debug)
+                dump_ctx("smb_iod_connect", ctx);
 
-        fd = t_open(dev, O_RDWR, NULL);
-        if (fd < 0) {
-                /* Assume t_errno = TSYSERR */
-                err = errno;
-                perror(dev);
+        /*
+         * Get local machine name.
+         * Full name - not a NetBIOS name.
+         */
+        if (ctx->ct_locname == NULL) {
+                err = smb_getlocalname(&ctx->ct_locname);
+                if (err) {
+                        smb_error(dgettext(TEXT_DOMAIN,
+                            "can't get local name"), err);
                 return (err);
         }
-        if ((err = smb_setopts(fd)) != 0)
-                goto errout;
-        if (t_bind(fd, NULL, NULL) < 0) {
-                DPRINT("t_bind t_errno %d", t_errno);
-                if (t_errno == TSYSERR)
-                        err = errno;
-                else
-                        err = EPROTO;
-                goto errout;
         }
-        sndcall.addr.maxlen = sizeof (sin6);
-        sndcall.addr.len = sizeof (sin6);
-        sndcall.addr.buf = (void *) &sin6;
-        sndcall.opt.len = 0;
-        sndcall.udata.len = 0;
-        if (t_connect(fd, &sndcall, NULL) < 0) {
-                err = get_xti_err(fd);
-                DPRINT("connect, err %d", err);
-                goto errout;
-        }
 
-        DPRINT("tcp6: connected, fd=%d", fd);
-        ctx->ct_tran_fd = fd;
-        return (0);
-
-errout:
-        close(fd);
+        /*
+         * Get local machine uuid.
+         */
+        uuid_str = cf_get_client_uuid();
+        if (uuid_str == NULL) {
+                err = EINVAL;
+                smb_error(dgettext(TEXT_DOMAIN,
+                    "can't get local UUID"), err);
         return (err);
-}
+        }
+        (void) uuid_parse(uuid_str, ctx->ct_work.wk_cl_guid);
+        free(uuid_str);
+        uuid_str = NULL;
 
-/*
- * This is used for both SMB over TCP (port 445)
- * and NetBIOS - see conn_nbt().
+        /*
+         * We're called with each IP address
+         * already copied into ct_srvaddr.
  */
-int
-conn_tcp4(struct smb_ctx *ctx, const struct sockaddr *sa, int port)
-{
-        struct sockaddr_in sin;
-        char *dev = "/dev/tcp";
-        char paddrbuf[INET_ADDRSTRLEN];
-        struct t_call sndcall;
-        int fd, err;
+        ctx->ct_flags |= SMBCF_RESOLVED;
 
-        if (sa->sa_family != AF_INET) {
-                DPRINT("bad af %d", sa->sa_family);
-                return (EINVAL);
-        }
-        bcopy(sa, &sin, sizeof (sin));
-        sin.sin_port = htons(port);
-
-        DPRINT("tcp4: %s (%d)",
-            inet_ntop(AF_INET, &sin.sin_addr,
-            paddrbuf, sizeof (paddrbuf)), port);
-
-        fd = t_open(dev, O_RDWR, NULL);
-        if (fd < 0) {
-                /* Assume t_errno = TSYSERR */
+        /*
+         * Ask the drvier to connect.
+         */
+        DPRINT("Try ioctl connect...");
+        if (nsmb_ioctl(ctx->ct_dev_fd, SMBIOC_IOD_CONNECT, work) < 0) {
                 err = errno;
-                perror(dev);
+                smb_error(dgettext(TEXT_DOMAIN,
+                    "%s: connect failed"),
+                    err, ossn->ssn_srvname);
                 return (err);
         }
-        if ((err = smb_setopts(fd)) != 0)
-                goto errout;
-        if (t_bind(fd, NULL, NULL) < 0) {
-                DPRINT("t_bind t_errno %d", t_errno);
-                if (t_errno == TSYSERR)
-                        err = errno;
-                else
-                        err = EPROTO;
-                goto errout;
-        }
-        sndcall.addr.maxlen = sizeof (sin);
-        sndcall.addr.len = sizeof (sin);
-        sndcall.addr.buf = (void *) &sin;
-        sndcall.opt.len = 0;
-        sndcall.udata.len = 0;
-        if (t_connect(fd, &sndcall, NULL) < 0) {
-                err = get_xti_err(fd);
-                DPRINT("connect, err %d", err);
-                goto errout;
-        }
+        DPRINT("Connect OK, new state=%s",
+            smb_iod_state_name(work->wk_out_state));
 
-        DPRINT("tcp4: connected, fd=%d", fd);
-        ctx->ct_tran_fd = fd;
-        return (0);
+        /*
+         * Setup a buffer to recv the nego. hint.
+         */
+        nego_len = 4096;
+        err = mb_init_sz(&blob, nego_len);
+        if (err)
+                goto out;
+        nego_buf = blob.mb_top->m_data;
+        work->wk_u_auth_rbuf.lp_ptr = nego_buf;
+        work->wk_u_auth_rlen = nego_len;
 
-errout:
-        close(fd);
-        return (err);
-}
-
-/*
- * Open a NetBIOS connection (session, port 139)
- *
- * The optional name parameter, if passed, means
- * we found the sockaddr via NetBIOS name lookup,
- * and can just use that for our session request.
- * Otherwise (if name is NULL), we're connecting
- * by IP address, and need to come up with the
- * NetBIOS name by other means.
+        /*
+         * Ask the driver for SMB negotiate
  */
-int
-conn_nbt(struct smb_ctx *ctx, const struct sockaddr *saarg, char *name)
-{
-        struct sockaddr_in sin;
-        struct sockaddr *sa;
-        char server[NB_NAMELEN];
-        char workgroup[NB_NAMELEN];
-        int err, nberr, port;
+        DPRINT("Try ioctl negotiate...");
+        if (nsmb_ioctl(ctx->ct_dev_fd, SMBIOC_IOD_NEGOTIATE, work) < 0) {
+                err = errno;
+                smb_error(dgettext(TEXT_DOMAIN,
+                    "%s: negotiate failed"),
+                    err, ossn->ssn_srvname);
+                goto out;
+        }
+        DPRINT("Negotiate OK, new state=%s",
+            smb_iod_state_name(work->wk_out_state));
 
-        bcopy(saarg, &sin, sizeof (sin));
-        sa = (struct sockaddr *)&sin;
+        nego_len = work->wk_u_auth_rlen;
+        blob.mb_top->m_len = nego_len;
 
-        switch (sin.sin_family) {
-        case AF_NETBIOS:        /* our fake AF */
-                sin.sin_family = AF_INET;
-                break;
-        case AF_INET:
-                break;
-        default:
-                DPRINT("bad af %d", sin.sin_family);
-                return (EINVAL);
+        if (smb_debug) {
+                DPRINT("Sec. blob: %d", nego_len);
+                smb_hexdump(nego_buf, nego_len);
         }
-        port = IPPORT_NETBIOS_SSN;
 
         /*
-         * If we have a NetBIOS name, just use it.
-         * This is the path taken when we've done a
-         * NetBIOS name lookup on this name to get
-         * the IP address in the passed sa. Otherwise,
-         * we're connecting by IP address, and need to
-         * figure out what NetBIOS name to use.
+         * Do SMB Session Setup (authenticate)
+         * Always "extended security" now (SPNEGO)
          */
-        if (name) {
-                strlcpy(server, name, sizeof (server));
-                DPRINT("given name: %s", server);
-        } else {
-                /*
-                 *
-                 * Try a NetBIOS node status query,
-                 * which searches for a type=[20] name.
-                 * If that doesn't work, just use the
-                 * (fake) "*SMBSERVER" name.
-                 */
-                DPRINT("try node status");
-                server[0] = '\0';
-                nberr = nbns_getnodestatus(ctx->ct_nb,
-                    &sin.sin_addr, server, workgroup);
-                if (nberr == 0 && server[0] != '\0') {
-                        /* Found the name.  Save for reconnect. */
-                        DPRINT("found name: %s", server);
-                        strlcpy(ctx->ct_srvname, server,
-                            sizeof (ctx->ct_srvname));
-                } else {
-                        DPRINT("getnodestatus, nberr %d", nberr);
-                        strlcpy(server, "*SMBSERVER", sizeof (server));
+        DPRINT("Do session setup...");
+        err = smb_ssnsetup_spnego(ctx, &blob);
+        if (err != 0) {
+                DPRINT("Session setup err=%d", err);
+                goto out;
                 }
-        }
 
         /*
-         * Establish the TCP connection.
-         * Careful to close it on errors.
+         * Success! We return zero now, and our caller (normally
+         * the smbiod program) will then call smb_iod_work in a
+         * new thread to service this VC as long as necessary.
          */
-        if ((err = conn_tcp4(ctx, sa, port)) != 0) {
-                DPRINT("TCP connect: err=%d", err);
-                goto out;
-        }
+        DPRINT("Session setup OK");
 
-        /* Connected.  Do NetBIOS session request. */
-        err = nb_ssn_request(ctx, server);
-        if (err)
-                DPRINT("ssn_rq, err %d", err);
-
 out:
-        if (err) {
-                if (ctx->ct_tran_fd != -1) {
-                        close(ctx->ct_tran_fd);
-                        ctx->ct_tran_fd = -1;
-                }
-        }
+        mb_done(&blob);
+
         return (err);
 }
 
 /*
- * Make a new connection, or reconnect.
+ * smb_ssnsetup_spnego
+ *
+ * This does an SMB session setup sequence using SPNEGO.
+ * The state changes seen during this sequence are there
+ * just to help track what's going on.
  */
 int
-smb_iod_connect(smb_ctx_t *ctx)
+smb_ssnsetup_spnego(struct smb_ctx *ctx, struct mbdata *hint_mb)
 {
-        struct sockaddr *sa;
-        int err, err2;
-        struct mbdata blob;
+        struct mbdata send_mb, recv_mb;
+        smbioc_ssn_work_t *work = &ctx->ct_work;
+        int             err;
 
-        memset(&blob, 0, sizeof (blob));
+        bzero(&send_mb, sizeof (send_mb));
+        bzero(&recv_mb, sizeof (recv_mb));
 
-        if (ctx->ct_srvname[0] == '\0') {
-                DPRINT("sername not set!");
-                return (EINVAL);
+        err = ssp_ctx_create_client(ctx, hint_mb);
+        if (err)
+                goto out;
+
+        /* NULL input indicates first call. */
+        err = ssp_ctx_next_token(ctx, NULL, &send_mb);
+        if (err) {
+                DPRINT("smb__ssnsetup, ssp next, err=%d", err);
+                goto out;
         }
-        DPRINT("server: %s", ctx->ct_srvname);
+        for (;;) {
+                err = smb__ssnsetup(ctx, &send_mb, &recv_mb);
+                DPRINT("smb__ssnsetup rc=%d, new state=%s", err,
+                    smb_iod_state_name(work->wk_out_state));
 
-        if (smb_debug)
-                dump_ctx("smb_iod_connect", ctx);
-
+                if (err == 0) {
         /*
-         * This may be a reconnect, so
-         * cleanup if necessary.
+                         * Session setup complete w/ success.
+                         * Should have state AUTHOK
          */
-        if (ctx->ct_tran_fd != -1) {
-                close(ctx->ct_tran_fd);
-                ctx->ct_tran_fd = -1;
+                        if (work->wk_out_state != SMBIOD_ST_AUTHOK) {
+                                DPRINT("Wrong state (expected AUTHOK)");
         }
+                        break;
+                }
 
+                if (err != EINPROGRESS) {
         /*
-         * Get local machine name.
-         * Full name - not a NetBIOS name.
+                         * Session setup complete w/ failure.
+                         * Should have state AUTHFAIL
          */
-        if (ctx->ct_locname == NULL) {
-                err = smb_getlocalname(&ctx->ct_locname);
-                if (err) {
-                        smb_error(dgettext(TEXT_DOMAIN,
-                            "can't get local name"), err);
-                        return (err);
+                        if (work->wk_out_state != SMBIOD_ST_AUTHFAIL) {
+                                DPRINT("Wrong state (expected AUTHFAIL)");
                 }
+                        goto out;
         }
 
         /*
-         * We're called with each IP address
-         * already copied into ct_srvaddr.
+                 * err == EINPROGRESS
+                 * Session setup continuing.
+                 * Should have state AUTHCONT
          */
-        ctx->ct_flags |= SMBCF_RESOLVED;
-
-        sa = &ctx->ct_srvaddr.sa;
-        switch (sa->sa_family) {
-
-        case AF_INET6:
-                err = conn_tcp6(ctx, sa, IPPORT_SMB);
-                break;
-
-        case AF_INET:
-                err = conn_tcp4(ctx, sa, IPPORT_SMB);
-                /*
-                 * If port 445 was not listening, try port 139.
-                 * Note: Not doing NetBIOS name lookup here.
-                 * We already have the IP address.
-                 */
-                switch (err) {
-                case ECONNRESET:
-                case ECONNREFUSED:
-                        err2 = conn_nbt(ctx, sa, NULL);
-                        if (err2 == 0)
-                                err = 0;
+                if (work->wk_out_state != SMBIOD_ST_AUTHCONT) {
+                        DPRINT("Wrong state (expected AUTHCONT)");
                 }
-                break;
 
-        case AF_NETBIOS:
-                /* Like AF_INET, but use NetBIOS ssn. */
-                err = conn_nbt(ctx, sa, ctx->ct_srvname);
-                break;
-
-        default:
-                DPRINT("skipped family %d", sa->sa_family);
-                err = EPROTONOSUPPORT;
-                break;
-        }
-
-
+                /* middle calls get both in, out */
+                err = ssp_ctx_next_token(ctx, &recv_mb, &send_mb);
         if (err) {
-                DPRINT("connect, err=%d", err);
-                return (err);
+                        DPRINT("smb__ssnsetup, ssp next, err=%d", err);
+                        goto out;
         }
+        }
 
         /*
-         * Do SMB Negotiate Protocol.
+         * Only get here via break in the err==0 case above,
+         * so we're finalizing a successful session setup.
+         *
+         * NULL output token here indicates the final call.
          */
-        err = smb_negprot(ctx, &blob);
-        if (err)
-                goto out;
+        (void) ssp_ctx_next_token(ctx, &recv_mb, NULL);
 
         /*
-         * Empty user name means an explicit request for
-         * NULL session setup, which is a special case.
-         * If negotiate determined that we want to do
-         * SMB signing, we have to turn that off for a
-         * NULL session. [MS-SMB 3.3.5.3].
+         * The signing key is in ctx->ct_mackey
+         * (a.k.a. ct_work.wk_iods.is_u_mackey)
+         * and session key is in ctx->ct_ssn_key
+         * (a.k.a. ct_work.wk_iods.is_ssn_key)
          */
-        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_clnt_caps &= ~SMB_CAP_EXT_SECURITY;
-                ctx->ct_vcflags &= ~SMBV_WILL_SIGN;
-        }
 
-        /*
-         * Do SMB Session Setup (authenticate)
+out:
+        /* Done with ctx->ct_ssp_ctx */
+        ssp_ctx_destroy(ctx);
+
+        return (err);
+}
+
+int smb_max_authtok_sz = 0x10000;
+
+/*
+ * Session Setup function, calling the nsmb driver.
          *
-         * If the server negotiated extended security,
-         * run the SPNEGO state machine, otherwise do
-         * one of the old-style variants.
+ * Args
+ *      send_mb: [in]  outgoing blob data to send
+ *      recv_mb: [out] received blob data buffer
          */
-        if (ctx->ct_clnt_caps & SMB_CAP_EXT_SECURITY) {
-                err = smb_ssnsetup_spnego(ctx, &blob);
-        } else {
-                /*
-                 * Server did NOT negotiate extended security.
-                 * Try NTLMv2, NTLMv1, or ANON (if enabled).
-                 */
-                if (ctx->ct_authflags & SMB_AT_NTLM2) {
-                        err = smb_ssnsetup_ntlm2(ctx);
-                } else if (ctx->ct_authflags & SMB_AT_NTLM1) {
-                        err = smb_ssnsetup_ntlm1(ctx);
-                } else if (ctx->ct_authflags & SMB_AT_ANON) {
-                        err = smb_ssnsetup_null(ctx);
-                } else {
-                        /*
-                         * Don't return EAUTH, because a new
-                         * password prompt will not help.
-                         */
-                        DPRINT("No NTLM authflags");
-                        err = ENOTSUP;
+static int
+smb__ssnsetup(struct smb_ctx *ctx,
+        struct mbdata *send_mb, struct mbdata *recv_mb)
+{
+        smbioc_ossn_t *ossn = &ctx->ct_ssn;
+        smbioc_ssn_work_t *work = &ctx->ct_work;
+        mbuf_t *m;
+        int err;
+
+        /* Setup receive buffer for the auth data. */
+        err = mb_init_sz(recv_mb, smb_max_authtok_sz);
+        if (err != 0)
+                return (err);
+        m = recv_mb->mb_top;
+        work->wk_u_auth_rbuf.lp_ptr = m->m_data;
+        work->wk_u_auth_rlen        = m->m_maxlen;
+
+        /* ... and the auth data to send. */
+        m = send_mb->mb_top;
+        work->wk_u_auth_wbuf.lp_ptr = m->m_data;
+        work->wk_u_auth_wlen        = m->m_len;
+
+        DPRINT("Session setup ioctl...");
+        if (nsmb_ioctl(ctx->ct_dev_fd, SMBIOC_IOD_SSNSETUP, work) < 0) {
+                err = errno;
+                if (err != 0 && err != EINPROGRESS) {
+                        smb_error(dgettext(TEXT_DOMAIN,
+                            "%s: session setup "),
+                            err, ossn->ssn_srvname);
                 }
         }
+        DPRINT("Session setup ret %d", err);
 
-out:
-        mb_done(&blob);
+        /* Free the auth data we sent. */
+        mb_done(send_mb);
 
-        if (err) {
-                close(ctx->ct_tran_fd);
-                ctx->ct_tran_fd = -1;
-        } else {
-                /* Tell library code we have a session. */
-                ctx->ct_flags |= SMBCF_SSNACTIVE;
-                DPRINT("tran_fd = %d", ctx->ct_tran_fd);
-        }
+        /* Setup length of received auth data */
+        m = recv_mb->mb_top;
+        m->m_len = work->wk_u_auth_rlen;
 
         return (err);
 }