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