diff options
Diffstat (limited to 'usr/src/lib/libsmbfs/smb/connect.c')
| -rw-r--r-- | usr/src/lib/libsmbfs/smb/connect.c | 603 |
1 files changed, 210 insertions, 393 deletions
diff --git a/usr/src/lib/libsmbfs/smb/connect.c b/usr/src/lib/libsmbfs/smb/connect.c index c2ccc3361d..a45fa90958 100644 --- a/usr/src/lib/libsmbfs/smb/connect.c +++ b/usr/src/lib/libsmbfs/smb/connect.c @@ -22,7 +22,8 @@ /* * 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. */ /* @@ -53,322 +54,63 @@ #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 "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 */ - -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 *); - -/* - * 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) -{ - struct t_optmgmt oreq, ores; - struct { - struct t_opthdr oh; - int ival; - } opts; - - /* 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); - } - if (ores.flags != T_SUCCESS) { - DPRINT("flags 0x%x, status 0x%x", - (int)ores.flags, (int)opts.oh.status); - return (EPROTO); - } - - return (0); -} - -static int -smb_setopts(int fd) -{ - int err; - - /* - * Set various socket/TCP options. - * Failures here are not fatal - - * just log a complaint. - * - * We don't need these two: - * SO_RCVTIMEO, SO_SNDTIMEO - */ - - 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) -{ - struct sockaddr_in6 sin6; - char *dev = "/dev/tcp6"; - char paddrbuf[INET6_ADDRSTRLEN]; - struct t_call sndcall; - int fd, err; - - if (sa->sa_family != AF_INET6) { - DPRINT("bad af %d", sa->sa_family); - return (EINVAL); - } - bcopy(sa, &sin6, sizeof (sin6)); - sin6.sin6_port = htons(port); - - DPRINT("tcp6: %s (%d)", - inet_ntop(AF_INET6, &sin6.sin6_addr, - paddrbuf, sizeof (paddrbuf)), port); - - fd = t_open(dev, O_RDWR, NULL); - if (fd < 0) { - /* Assume t_errno = TSYSERR */ - err = errno; - perror(dev); - 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); +smb__ssnsetup(struct smb_ctx *ctx, + struct mbdata *mbc1, struct mbdata *mbc2); -errout: - close(fd); - return (err); -} +int smb_ssnsetup_spnego(struct smb_ctx *, struct mbdata *); -/* - * This is used for both SMB over TCP (port 445) - * and NetBIOS - see conn_nbt(). - */ -int -conn_tcp4(struct smb_ctx *ctx, const struct sockaddr *sa, int port) +const char * +smb_iod_state_name(enum smbiod_state st) { - struct sockaddr_in sin; - char *dev = "/dev/tcp"; - char paddrbuf[INET_ADDRSTRLEN]; - struct t_call sndcall; - int fd, err; - - 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); + const char *n = "(?)"; - 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 */ - err = errno; - perror(dev); - 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("tcp4: connected, fd=%d", fd); - ctx->ct_tran_fd = fd; - return (0); - -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. - */ -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; - - bcopy(saarg, &sin, sizeof (sin)); - sa = (struct sockaddr *)&sin; - - switch (sin.sin_family) { - case AF_NETBIOS: /* our fake AF */ - sin.sin_family = AF_INET; + switch (st) { + case SMBIOD_ST_UNINIT: + n = "UNINIT!"; break; - case AF_INET: + 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; - default: - DPRINT("bad af %d", sin.sin_family); - return (EINVAL); - } - 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. - */ - 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)); - } - } - - /* - * Establish the TCP connection. - * Careful to close it on errors. - */ - if ((err = conn_tcp4(ctx, sa, port)) != 0) { - DPRINT("TCP connect: err=%d", err); - goto out; } - /* 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; - } - } - return (err); + return (n); } /* @@ -377,9 +119,12 @@ out: int smb_iod_connect(smb_ctx_t *ctx) { - struct sockaddr *sa; - int err, err2; + smbioc_ossn_t *ossn = &ctx->ct_ssn; + smbioc_ssn_work_t *work = &ctx->ct_work; + int err; struct mbdata blob; + char *nego_buf = NULL; + uint32_t nego_len; memset(&blob, 0, sizeof (blob)); @@ -393,15 +138,6 @@ smb_iod_connect(smb_ctx_t *ctx) dump_ctx("smb_iod_connect", ctx); /* - * This may be a reconnect, so - * cleanup if necessary. - */ - if (ctx->ct_tran_fd != -1) { - close(ctx->ct_tran_fd); - ctx->ct_tran_fd = -1; - } - - /* * Get local machine name. * Full name - not a NetBIOS name. */ @@ -420,109 +156,190 @@ smb_iod_connect(smb_ctx_t *ctx) */ 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; - } - 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; - } - - - if (err) { - DPRINT("connect, err=%d", err); + /* + * 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)); /* - * Do SMB Negotiate Protocol. + * Setup a buffer to recv the nego. hint. */ - err = smb_negprot(ctx, &blob); + 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; /* - * 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]. + * Ask the driver for SMB negotiate */ - 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; + 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) - * - * If the server negotiated extended security, - * run the SPNEGO state machine, otherwise do - * one of the old-style variants. + * Always "extended security" now (SPNEGO) */ - 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; - } + 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); +} + +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) { - 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); + DPRINT("smb__ssnsetup, ssp next, err=%d", err); + goto out; + } + for (;;) { + err = smb__ssnsetup(ctx, &send_mb, &recv_mb); + if (err != 0 && err != EINPROGRESS) { + DPRINT("smb__ssnsetup, err=%d", err); + goto out; + } + DPRINT("smb__ssnsetup, new state=%s", + smb_iod_state_name(work->wk_out_state)); + if (work->wk_out_state == SMBIOD_ST_AUTHOK) { + err = 0; + break; + } + if (work->wk_out_state == SMBIOD_ST_AUTHFAIL) { + err = EAUTH; + goto out; + } + if (work->wk_out_state != SMBIOD_ST_AUTHCONT) { + err = EPROTO; + goto out; + } + /* state == SMBIOD_ST_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; + } + } + + /* NULL output indicates last 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); } |
