root/usr/src/lib/libsmbfs/smb/connect.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * 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
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <libintl.h>
#include <xti.h>
#include <assert.h>

#include <sys/types.h>
#include <sys/time.h>
#include <sys/byteorder.h>
#include <sys/socket.h>
#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"

static int
smb__ssnsetup(struct smb_ctx *ctx,
        struct mbdata *mbc1, struct mbdata *mbc2);

int smb_ssnsetup_spnego(struct smb_ctx *, struct mbdata *);

const char *
smb_iod_state_name(enum smbiod_state st)
{
        const char *n = "(?)";

        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;
        }

        return (n);
}

/*
 * Make a new connection, or reconnect.
 *
 * 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).
 */
int
smb_iod_connect(smb_ctx_t *ctx)
{
        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;

        memset(&blob, 0, sizeof (blob));

        if (ctx->ct_srvname[0] == '\0') {
                DPRINT("sername not set!");
                return (EINVAL);
        }
        DPRINT("server: %s", ctx->ct_srvname);

        if (smb_debug)
                dump_ctx("smb_iod_connect", ctx);

        /*
         * 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);
                }
        }

        /*
         * 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;

        /*
         * We're called with each IP address
         * already copied into ct_srvaddr.
         */
        ctx->ct_flags |= SMBCF_RESOLVED;

        /*
         * Ask the drvier to connect.
         */
        DPRINT("Try ioctl connect...");
        if (nsmb_ioctl(ctx->ct_dev_fd, SMBIOC_IOD_CONNECT, work) < 0) {
                err = errno;
                smb_error(dgettext(TEXT_DOMAIN,
                    "%s: connect failed"),
                    err, ossn->ssn_srvname);
                return (err);
        }
        DPRINT("Connect OK, new state=%s",
            smb_iod_state_name(work->wk_out_state));

        /*
         * 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;

        /*
         * Ask the driver for SMB negotiate
         */
        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));

        nego_len = work->wk_u_auth_rlen;
        blob.mb_top->m_len = nego_len;

        if (smb_debug) {
                DPRINT("Sec. blob: %d", nego_len);
                smb_hexdump(nego_buf, nego_len);
        }

        /*
         * Do SMB Session Setup (authenticate)
         * Always "extended security" now (SPNEGO)
         */
        DPRINT("Do session setup...");
        err = smb_ssnsetup_spnego(ctx, &blob);
        if (err != 0) {
                DPRINT("Session setup err=%d", err);
                goto out;
        }

        /*
         * 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.
         */
        DPRINT("Session setup OK");

out:
        mb_done(&blob);

        return (err);
}

/*
 * 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_ssnsetup_spnego(struct smb_ctx *ctx, struct mbdata *hint_mb)
{
        struct mbdata send_mb, recv_mb;
        smbioc_ssn_work_t *work = &ctx->ct_work;
        int             err;

        bzero(&send_mb, sizeof (send_mb));
        bzero(&recv_mb, sizeof (recv_mb));

        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;
        }
        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 (err == 0) {
                        /*
                         * Session setup complete w/ success.
                         * Should have state AUTHOK
                         */
                        if (work->wk_out_state != SMBIOD_ST_AUTHOK) {
                                DPRINT("Wrong state (expected AUTHOK)");
                        }
                        break;
                }

                if (err != EINPROGRESS) {
                        /*
                         * Session setup complete w/ failure.
                         * Should have state AUTHFAIL
                         */
                        if (work->wk_out_state != SMBIOD_ST_AUTHFAIL) {
                                DPRINT("Wrong state (expected AUTHFAIL)");
                        }
                        goto out;
                }

                /*
                 * err == EINPROGRESS
                 * Session setup continuing.
                 * Should have state AUTHCONT
                 */
                if (work->wk_out_state != SMBIOD_ST_AUTHCONT) {
                        DPRINT("Wrong state (expected AUTHCONT)");
                }

                /* middle calls get both in, out */
                err = ssp_ctx_next_token(ctx, &recv_mb, &send_mb);
                if (err) {
                        DPRINT("smb__ssnsetup, ssp next, err=%d", err);
                        goto out;
                }
        }

        /*
         * 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.
         */
        (void) ssp_ctx_next_token(ctx, &recv_mb, NULL);

        /*
         * The session key is in ctx->ct_ssnkey_buf
         * (a.k.a. ct_work.wk_u_ssn_key_buf)
         */

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.
 *
 * Args
 *      send_mb: [in]  outgoing blob data to send
 *      recv_mb: [out] received blob data buffer
 */
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);

        /* Free the auth data we sent. */
        mb_done(send_mb);

        /* Setup length of received auth data */
        m = recv_mb->mb_top;
        m->m_len = work->wk_u_auth_rlen;

        return (err);
}