#ifdef DEBUG
#define QUEUEDEBUG 1
#endif
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/atomic.h>
#include <sys/proc.h>
#include <sys/thread.h>
#include <sys/file.h>
#include <sys/kmem.h>
#include <sys/unistd.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/types.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/time.h>
#include <sys/class.h>
#include <sys/disp.h>
#include <sys/cmn_err.h>
#include <sys/zone.h>
#include <sys/sdt.h>
#include <netsmb/smb_osdep.h>
#include <netsmb/smb.h>
#include <netsmb/smb2.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_rq.h>
#include <netsmb/smb2_rq.h>
#include <netsmb/smb_subr.h>
#include <netsmb/smb_tran.h>
#include <netsmb/smb_trantcp.h>
static int smb_tcpsndbuf = 0x20000;
static int smb_tcprcvbuf = 0x20000;
static int smb_connect_timeout = 10;
static int smb1_iod_process(smb_vc_t *, mblk_t *);
static int smb2_iod_process(smb_vc_t *, mblk_t *);
static int smb_iod_send_echo(smb_vc_t *, cred_t *cr);
static int smb_iod_logoff(struct smb_vc *vcp, cred_t *cr);
static smb_fscb_t *fscb;
void
smb_fscb_set(smb_fscb_t *cb)
{
fscb = cb;
}
static void
smb_iod_share_disconnected(smb_share_t *ssp)
{
smb_share_invalidate(ssp);
if (fscb && fscb->fscb_disconn) {
fscb->fscb_disconn(ssp);
}
}
void
smb_iod_newstate(struct smb_vc *vcp, int state)
{
vcp->vc_state = state;
}
static inline void
smb_iod_rqprocessed_LH(
struct smb_rq *rqp,
int error,
int flags)
{
rqp->sr_flags |= flags;
rqp->sr_lerror = error;
rqp->sr_rpgen++;
rqp->sr_state = SMBRQ_NOTIFIED;
cv_broadcast(&rqp->sr_cond);
}
static void
smb_iod_rqprocessed(
struct smb_rq *rqp,
int error,
int flags)
{
SMBRQ_LOCK(rqp);
smb_iod_rqprocessed_LH(rqp, error, flags);
SMBRQ_UNLOCK(rqp);
}
static void
smb_iod_invrq(struct smb_vc *vcp)
{
struct smb_rq *rqp;
rw_enter(&vcp->iod_rqlock, RW_READER);
TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) {
smb_iod_rqprocessed(rqp, ENOTCONN, SMBR_RESTART);
}
rw_exit(&vcp->iod_rqlock);
cv_broadcast(&vcp->iod_muxwait);
}
void
smb_iod_disconnect(struct smb_vc *vcp)
{
SMB_VC_LOCK(vcp);
if (vcp->vc_state != SMBIOD_ST_DEAD) {
smb_iod_newstate(vcp, SMBIOD_ST_DEAD);
cv_broadcast(&vcp->vc_statechg);
}
SMB_VC_UNLOCK(vcp);
SMB_TRAN_DISCONNECT(vcp);
}
static void
smb1_iod_sendrq(struct smb_rq *rqp)
{
struct smb_vc *vcp = rqp->sr_vc;
mblk_t *m;
int error;
ASSERT(vcp);
ASSERT(RW_WRITE_HELD(&vcp->iod_rqlock));
ASSERT((vcp->vc_flags & SMBV_SMB2) == 0);
if ((rqp->sr_flags & SMBR_INTERNAL) == 0 &&
vcp->vc_state != SMBIOD_ST_VCACTIVE) {
SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state);
smb_iod_rqprocessed(rqp, ENOTCONN, SMBR_RESTART);
return;
}
smb_rq_fillhdr(rqp);
if (rqp->sr_rqflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) {
smb_rq_sign(rqp);
}
m = dupmsg(rqp->sr_rq.mb_top);
if (m == NULL) {
error = ENOBUFS;
goto fatal;
}
#ifdef DTRACE_PROBE2
DTRACE_PROBE2(iod_sendrq,
(smb_rq_t *), rqp, (mblk_t *), m);
#endif
error = SMB_TRAN_SEND(vcp, m);
m = 0;
rqp->sr_lerror = error;
if (error == 0) {
SMBRQ_LOCK(rqp);
rqp->sr_flags |= SMBR_SENT;
rqp->sr_state = SMBRQ_SENT;
SMBRQ_UNLOCK(rqp);
return;
}
if (SMB_TRAN_FATAL(vcp, error)) {
fatal:
SMBSDEBUG("TRAN_SEND returned fatal error %d\n", error);
smb_iod_rqprocessed(rqp, error, SMBR_RESTART);
return;
}
}
static void
smb2_iod_sendrq(struct smb_rq *rqp)
{
struct smb_rq *c_rqp;
struct smb_vc *vcp = rqp->sr_vc;
struct smb_sopt *sv = &vcp->vc_sopt;
mblk_t *top_m;
mblk_t *cur_m;
int error;
boolean_t encrypt = B_FALSE;
ASSERT(vcp);
ASSERT(RW_WRITE_HELD(&vcp->iod_rqlock));
ASSERT((vcp->vc_flags & SMBV_SMB2) != 0);
if ((rqp->sr_flags & SMBR_INTERNAL) == 0 &&
vcp->vc_state != SMBIOD_ST_VCACTIVE) {
SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state);
smb_iod_rqprocessed(rqp, ENOTCONN, SMBR_RESTART);
return;
}
if ((sv->sv2_sessflags & SMB2_SESSION_FLAG_ENCRYPT_DATA) != 0) {
if (rqp->sr2_command != SMB2_NEGOTIATE) {
encrypt = B_TRUE;
}
} else if (rqp->sr_share != NULL &&
(rqp->sr_share->ss2_share_flags &
SMB2_SHAREFLAG_ENCRYPT_DATA) != 0) {
if ((rqp->sr2_command != SMB2_NEGOTIATE) &&
(rqp->sr2_command != SMB2_SESSION_SETUP) &&
(rqp->sr2_command != SMB2_TREE_CONNECT)) {
encrypt = B_TRUE;
}
}
smb2_rq_fillhdr(rqp);
if (!encrypt && (rqp->sr2_rqflags & SMB2_FLAGS_SIGNED) != 0) {
smb2_rq_sign(rqp);
}
c_rqp = rqp->sr2_compound_next;
while (c_rqp != NULL) {
smb2_rq_fillhdr(c_rqp);
if (!encrypt &&
(c_rqp->sr2_rqflags & SMB2_FLAGS_SIGNED) != 0) {
smb2_rq_sign(c_rqp);
}
c_rqp = c_rqp->sr2_compound_next;
}
DTRACE_PROBE2(iod_sendrq,
(smb_rq_t *), rqp, (mblk_t *), rqp->sr_rq.mb_top);
if (encrypt)
top_m = copymsg(rqp->sr_rq.mb_top);
else
top_m = dupmsg(rqp->sr_rq.mb_top);
if (top_m == NULL) {
error = ENOBUFS;
goto fatal;
}
c_rqp = rqp->sr2_compound_next;
while (c_rqp != NULL) {
size_t len = msgdsize(top_m);
ASSERT((len & 7) == 0);
if (encrypt)
cur_m = copymsg(c_rqp->sr_rq.mb_top);
else
cur_m = dupmsg(c_rqp->sr_rq.mb_top);
if (cur_m == NULL) {
freemsg(top_m);
error = ENOBUFS;
goto fatal;
}
linkb(top_m, cur_m);
}
if (encrypt) {
error = smb3_msg_encrypt(vcp, &top_m);
if (error != 0)
goto fatal;
}
error = SMB_TRAN_SEND(vcp, top_m);
top_m = 0;
rqp->sr_lerror = error;
if (error == 0) {
SMBRQ_LOCK(rqp);
rqp->sr_flags |= SMBR_SENT;
rqp->sr_state = SMBRQ_SENT;
SMBRQ_UNLOCK(rqp);
return;
}
if (SMB_TRAN_FATAL(vcp, error)) {
fatal:
SMBSDEBUG("TRAN_SEND returned fatal error %d\n", error);
smb_iod_rqprocessed(rqp, error, SMBR_RESTART);
return;
}
}
static int
smb_iod_recvmsg(struct smb_vc *vcp, mblk_t **mpp)
{
mblk_t *m;
int error;
top:
m = NULL;
error = SMB_TRAN_RECV(vcp, &m);
if (error == EAGAIN)
goto top;
if (error)
return (error);
ASSERT(m != NULL);
m = m_pullup(m, 4);
if (m == NULL) {
return (ENOSR);
}
*mpp = m;
return (0);
}
#ifdef DEBUG
int smb_iod_idle_keep_time = 60;
#else
int smb_iod_idle_keep_time = 300;
#endif
int
smb_iod_recvall(struct smb_vc *vcp, boolean_t poll)
{
mblk_t *m;
int error = 0;
int etime_idle = 0;
int etime_count = 0;
for (;;) {
if (vcp->iod_flags & SMBIOD_SHUTDOWN) {
SMBIODEBUG("SHUTDOWN set\n");
SMB_VC_LOCK(vcp);
smb_iod_newstate(vcp, SMBIOD_ST_DEAD);
cv_broadcast(&vcp->vc_statechg);
SMB_VC_UNLOCK(vcp);
error = EINTR;
break;
}
m = NULL;
error = smb_iod_recvmsg(vcp, &m);
if (error == ETIME && poll)
break;
if (error == ETIME &&
vcp->iod_rqlist.tqh_first != NULL) {
if (etime_idle != 0) {
etime_idle = 0;
} else if (etime_count < INT16_MAX) {
etime_count++;
}
if (etime_count > 0 &&
vcp->iod_noresp == B_FALSE) {
vcp->iod_noresp = B_TRUE;
zprintf(vcp->vc_zoneid,
"SMB server %s not responding\n",
vcp->vc_srvname);
}
if (etime_count == 2) {
SMBIODEBUG("send echo\n");
(void) smb_iod_send_echo(vcp, CRED());
}
if (etime_count == 3) {
SMB_VC_LOCK(vcp);
smb_iod_newstate(vcp, SMBIOD_ST_RECONNECT);
SMB_VC_UNLOCK(vcp);
SMB_TRAN_DISCONNECT(vcp);
break;
}
continue;
}
if (error == ETIME) {
etime_count = 0;
if (etime_idle < INT16_MAX)
etime_idle++;
if ((etime_idle * SMB_NBTIMO) <
smb_iod_idle_keep_time)
continue;
SMB_VC_LOCK(vcp);
if (vcp->vc_co.co_usecount == 1) {
smb_iod_newstate(vcp, SMBIOD_ST_IDLE);
SMB_VC_UNLOCK(vcp);
SMBIODEBUG("logoff & disconnect\n");
(void) smb_iod_logoff(vcp, CRED());
SMB_TRAN_DISCONNECT(vcp);
error = 0;
break;
}
SMB_VC_UNLOCK(vcp);
continue;
}
if (error) {
SMB_VC_LOCK(vcp);
if (vcp->iod_rqlist.tqh_first != NULL)
smb_iod_newstate(vcp, SMBIOD_ST_RECONNECT);
else
smb_iod_newstate(vcp, SMBIOD_ST_IDLE);
cv_broadcast(&vcp->vc_statechg);
SMB_VC_UNLOCK(vcp);
SMB_TRAN_DISCONNECT(vcp);
break;
}
etime_count = 0;
etime_idle = 0;
if (vcp->iod_noresp) {
vcp->iod_noresp = B_FALSE;
zprintf(vcp->vc_zoneid, "SMB server %s OK\n",
vcp->vc_srvname);
}
if ((vcp->vc_flags & SMBV_SMB2) != 0) {
error = smb2_iod_process(vcp, m);
} else {
error = smb1_iod_process(vcp, m);
}
if (poll) {
error = 0;
break;
}
}
return (error);
}
static int
smb1_iod_process(smb_vc_t *vcp, mblk_t *m)
{
struct mdchain md;
struct smb_rq *rqp;
uint8_t cmd, sig[4];
uint16_t mid;
int err, skip;
m = m_pullup(m, SMB_HDRLEN);
if (m == NULL)
return (ENOMEM);
md_initm(&md, m);
err = md_get_mem(&md, sig, 4, MB_MSYSTEM);
if (err)
return (err);
if (sig[1] != 'S' || sig[2] != 'M' || sig[3] != 'B') {
goto bad_hdr;
}
switch (sig[0]) {
case SMB_HDR_V1:
md_get_uint8(&md, &cmd);
skip = SMB_HDR_OFF_MID - 5;
md_get_mem(&md, NULL, skip, MB_MSYSTEM);
err = md_get_uint16le(&md, &mid);
if (err)
return (err);
break;
case SMB_HDR_V2:
if (vcp->vc_state == SMBIOD_ST_CONNECTED) {
cmd = SMB_COM_NEGOTIATE;
mid = 0;
break;
}
bad_hdr:
default:
SMBIODEBUG("Bad SMB hdr\n");
m_freem(m);
return (EPROTO);
}
rw_enter(&vcp->iod_rqlock, RW_READER);
TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) {
if (rqp->sr_mid != mid)
continue;
DTRACE_PROBE2(iod_post_reply,
(smb_rq_t *), rqp, (mblk_t *), m);
m_dumpm(m);
SMBRQ_LOCK(rqp);
if (rqp->sr_rp.md_top == NULL) {
md_initm(&rqp->sr_rp, m);
} else {
if (rqp->sr_flags & SMBR_MULTIPACKET) {
md_append_record(&rqp->sr_rp, m);
} else {
SMBRQ_UNLOCK(rqp);
rqp = NULL;
break;
}
}
smb_iod_rqprocessed_LH(rqp, 0, 0);
SMBRQ_UNLOCK(rqp);
break;
}
rw_exit(&vcp->iod_rqlock);
if (rqp == NULL) {
if (cmd != SMB_COM_ECHO) {
SMBSDEBUG("drop resp: MID 0x%04x\n", (uint_t)mid);
}
m_freem(m);
}
return (0);
}
static int
smb2_iod_process(smb_vc_t *vcp, mblk_t *m)
{
struct mdchain md;
struct smb_rq *rqp;
uint8_t sig[4];
mblk_t *next_m = NULL;
uint64_t message_id, async_id;
uint32_t flags, next_cmd_off, status;
uint16_t command, credits_granted;
boolean_t encrypted = B_FALSE;
int err;
top:
m = m_pullup(m, SMB2_HDRLEN);
if (m == NULL)
return (ENOMEM);
md_initm(&md, m);
err = md_get_mem(&md, sig, 4, MB_MSYSTEM);
if (err)
return (err);
if (sig[1] != 'S' || sig[2] != 'M' || sig[3] != 'B') {
goto bad_hdr;
}
switch (sig[0]) {
case SMB_HDR_V2:
break;
case SMB_HDR_V3E:
err = smb3_msg_decrypt(vcp, &m);
if (err != 0) {
SMBIODEBUG("SMB3 decrypt failed\n");
m_freem(m);
return (ENOMSG);
}
encrypted = B_TRUE;
goto top;
bad_hdr:
default:
SMBIODEBUG("Bad SMB2 hdr\n");
m_freem(m);
return (EPROTO);
}
md_get_uint32le(&md, NULL);
md_get_uint32le(&md, &status);
md_get_uint16le(&md, &command);
md_get_uint16le(&md, &credits_granted);
md_get_uint32le(&md, &flags);
md_get_uint32le(&md, &next_cmd_off);
md_get_uint64le(&md, &message_id);
if (flags & SMB2_FLAGS_ASYNC_COMMAND) {
md_get_uint64le(&md, &async_id);
} else {
async_id = 0;
}
if (next_cmd_off != 0) {
if ((next_cmd_off & 7) != 0)
SMBIODEBUG("Misaligned next cmd\n");
else
next_m = m_split(m, next_cmd_off, 1);
}
if (command == SMB2_NEGOTIATE && credits_granted == 0)
credits_granted = 1;
rw_enter(&vcp->iod_rqlock, RW_WRITER);
vcp->vc2_limit_message_id += credits_granted;
rw_downgrade(&vcp->iod_rqlock);
TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) {
if (rqp->sr2_messageid != message_id)
continue;
DTRACE_PROBE2(iod_post_reply,
(smb_rq_t *), rqp, (mblk_t *), m);
m_dumpm(m);
if (status == NT_STATUS_PENDING && async_id != 0) {
rqp->sr2_rspasyncid = async_id;
m_freem(m);
break;
}
SMBRQ_LOCK(rqp);
if (rqp->sr_rp.md_top == NULL) {
md_initm(&rqp->sr_rp, m);
} else {
SMBRQ_UNLOCK(rqp);
rqp = NULL;
break;
}
if (encrypted)
rqp->sr_flags |= SMBR_ENCRYPTED;
smb_iod_rqprocessed_LH(rqp, 0, 0);
SMBRQ_UNLOCK(rqp);
break;
}
rw_exit(&vcp->iod_rqlock);
if (rqp == NULL) {
if (command != SMB2_ECHO) {
SMBSDEBUG("drop resp: MID %lld\n",
(long long)message_id);
}
m_freem(m);
}
if (next_m != NULL) {
m = next_m;
goto top;
}
return (0);
}
static int
smb_iod_send_echo(smb_vc_t *vcp, cred_t *cr)
{
smb_cred_t scred;
int err, tmo = SMBNOREPLYWAIT;
ASSERT(vcp->iod_thr == curthread);
smb_credinit(&scred, cr);
if ((vcp->vc_flags & SMBV_SMB2) != 0) {
err = smb2_smb_echo(vcp, &scred, tmo);
} else {
err = smb_smb_echo(vcp, &scred, tmo);
}
smb_credrele(&scred);
return (err);
}
static int
smb_iod_muxwait(smb_vc_t *vcp, boolean_t sig_ok)
{
int rc;
SMB_VC_LOCK(vcp);
vcp->iod_muxwant++;
if (sig_ok) {
rc = cv_wait_sig(&vcp->iod_muxwait, &vcp->vc_lock);
} else {
cv_wait(&vcp->iod_muxwait, &vcp->vc_lock);
rc = 1;
}
vcp->iod_muxwant--;
SMB_VC_UNLOCK(vcp);
return (rc);
}
int
smb1_iod_addrq(struct smb_rq *rqp)
{
struct smb_vc *vcp = rqp->sr_vc;
uint16_t need;
boolean_t sig_ok =
(rqp->sr_flags & SMBR_NOINTR_SEND) == 0;
ASSERT(rqp->sr_cred);
ASSERT((vcp->vc_flags & SMBV_SMB2) == 0);
rqp->sr_owner = curthread;
rw_enter(&vcp->iod_rqlock, RW_WRITER);
recheck:
if ((rqp->sr_flags & SMBR_INTERNAL) == 0 &&
vcp->vc_state != SMBIOD_ST_VCACTIVE) {
SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state);
rw_exit(&vcp->iod_rqlock);
return (ENOTCONN);
}
need = 1;
if ((rqp->sr_flags & SMBR_INTERNAL) == 0)
need++;
if ((vcp->iod_muxcnt + need) > vcp->vc_maxmux) {
rw_exit(&vcp->iod_rqlock);
if (rqp->sr_flags & SMBR_INTERNAL)
return (EBUSY);
if (smb_iod_muxwait(vcp, sig_ok) == 0)
return (EINTR);
rw_enter(&vcp->iod_rqlock, RW_WRITER);
goto recheck;
}
rqp->sr_mid = vcp->vc_next_mid++;
if (vcp->vc_mackey != NULL && (rqp->sr_rqflags2 &
SMB_FLAGS2_SECURITY_SIGNATURE) != 0) {
rqp->sr_seqno = vcp->vc_next_seq++;
rqp->sr_rseqno = vcp->vc_next_seq++;
}
vcp->iod_muxcnt++;
TAILQ_INSERT_TAIL(&vcp->iod_rqlist, rqp, sr_link);
smb1_iod_sendrq(rqp);
rw_exit(&vcp->iod_rqlock);
return (0);
}
int
smb2_iod_addrq(struct smb_rq *rqp)
{
struct smb_vc *vcp = rqp->sr_vc;
struct smb_rq *c_rqp;
uint16_t charge;
boolean_t sig_ok =
(rqp->sr_flags & SMBR_NOINTR_SEND) == 0;
ASSERT(rqp->sr_cred != NULL);
ASSERT((vcp->vc_flags & SMBV_SMB2) != 0);
rqp->sr2_totalcreditcharge = rqp->sr2_creditcharge;
c_rqp = rqp->sr2_compound_next;
while (c_rqp != NULL) {
rqp->sr2_totalcreditcharge += c_rqp->sr2_creditcharge;
c_rqp = c_rqp->sr2_compound_next;
}
if (rqp->sr_flags & SMBR_INTERNAL) {
if (rqp->sr2_compound_next != NULL) {
ASSERT(0);
return (EINVAL);
}
}
rqp->sr_owner = curthread;
rw_enter(&vcp->iod_rqlock, RW_WRITER);
recheck:
if ((rqp->sr_flags & SMBR_INTERNAL) == 0 &&
vcp->vc_state != SMBIOD_ST_VCACTIVE) {
SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state);
rw_exit(&vcp->iod_rqlock);
return (ENOTCONN);
}
charge = rqp->sr2_totalcreditcharge;
if ((rqp->sr_flags & SMBR_INTERNAL) == 0)
charge++;
if ((vcp->vc2_next_message_id + charge) >
vcp->vc2_limit_message_id) {
rw_exit(&vcp->iod_rqlock);
if (rqp->sr_flags & SMBR_INTERNAL)
return (EBUSY);
if (smb_iod_muxwait(vcp, sig_ok) == 0)
return (EINTR);
rw_enter(&vcp->iod_rqlock, RW_WRITER);
goto recheck;
}
rqp->sr2_messageid = vcp->vc2_next_message_id;
vcp->vc2_next_message_id += rqp->sr2_creditcharge;
TAILQ_INSERT_TAIL(&vcp->iod_rqlist, rqp, sr_link);
c_rqp = rqp->sr2_compound_next;
while (c_rqp != NULL) {
c_rqp->sr2_messageid = vcp->vc2_next_message_id;
vcp->vc2_next_message_id += c_rqp->sr2_creditcharge;
TAILQ_INSERT_TAIL(&vcp->iod_rqlist, c_rqp, sr_link);
c_rqp = c_rqp->sr2_compound_next;
}
smb2_iod_sendrq(rqp);
rw_exit(&vcp->iod_rqlock);
return (0);
}
int
smb1_iod_multirq(struct smb_rq *rqp)
{
struct smb_vc *vcp = rqp->sr_vc;
ASSERT(rqp->sr_flags & SMBR_MULTIPACKET);
if (vcp->vc_flags & SMBV_SMB2) {
ASSERT("!SMB2?");
return (EINVAL);
}
if (rqp->sr_flags & SMBR_INTERNAL)
return (EINVAL);
if (vcp->vc_state != SMBIOD_ST_VCACTIVE) {
SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state);
return (ENOTCONN);
}
rw_enter(&vcp->iod_rqlock, RW_WRITER);
rqp->sr_state = SMBRQ_NOTSENT;
smb1_iod_sendrq(rqp);
rw_exit(&vcp->iod_rqlock);
return (0);
}
void
smb_iod_removerq(struct smb_rq *rqp)
{
struct smb_rq *rqp2;
struct smb_vc *vcp = rqp->sr_vc;
boolean_t was_head = B_FALSE;
rw_enter(&vcp->iod_rqlock, RW_WRITER);
#ifdef QUEUEDEBUG
ASSERT(rqp->sr_link.tqe_next != (void *)1L);
#endif
if (TAILQ_FIRST(&vcp->iod_rqlist) == rqp)
was_head = B_TRUE;
TAILQ_REMOVE(&vcp->iod_rqlist, rqp, sr_link);
if (vcp->vc_flags & SMBV_SMB2) {
rqp2 = TAILQ_FIRST(&vcp->iod_rqlist);
if (was_head && rqp2 != NULL) {
vcp->vc2_oldest_message_id =
rqp2->sr2_messageid;
}
} else {
ASSERT(vcp->iod_muxcnt > 0);
vcp->iod_muxcnt--;
}
rw_exit(&vcp->iod_rqlock);
SMB_VC_LOCK(vcp);
if (vcp->iod_muxwant != 0)
cv_signal(&vcp->iod_muxwait);
SMB_VC_UNLOCK(vcp);
}
int
smb_iod_waitrq(struct smb_rq *rqp)
{
struct smb_vc *vcp = rqp->sr_vc;
clock_t tr, tmo1, tmo2;
int error;
if (rqp->sr_flags & SMBR_INTERNAL) {
return (smb_iod_waitrq_int(rqp));
}
ASSERT(curthread != vcp->iod_thr);
SMBRQ_LOCK(rqp);
if (smb_timo_notice && (smb_timo_notice < rqp->sr_timo))
tmo1 = SEC_TO_TICK(smb_timo_notice);
else
tmo1 = 0;
tmo2 = ddi_get_lbolt() + SEC_TO_TICK(rqp->sr_timo);
if (tmo1 && rqp->sr_rpgen == rqp->sr_rplast) {
if (rqp->sr_flags & SMBR_NOINTR_RECV)
tr = cv_reltimedwait(&rqp->sr_cond,
&rqp->sr_lock, tmo1, TR_CLOCK_TICK);
else
tr = cv_reltimedwait_sig(&rqp->sr_cond,
&rqp->sr_lock, tmo1, TR_CLOCK_TICK);
if (tr == 0) {
error = EINTR;
goto out;
}
if (tr < 0) {
DTRACE_PROBE1(smb_iod_waitrq1,
(smb_rq_t *), rqp);
}
}
while (rqp->sr_rpgen == rqp->sr_rplast) {
if (rqp->sr_flags & SMBR_NOINTR_RECV)
tr = cv_timedwait(&rqp->sr_cond,
&rqp->sr_lock, tmo2);
else
tr = cv_timedwait_sig(&rqp->sr_cond,
&rqp->sr_lock, tmo2);
if (tr == 0) {
error = EINTR;
goto out;
}
if (tr < 0) {
DTRACE_PROBE1(smb_iod_waitrq2,
(smb_rq_t *), rqp);
error = ETIME;
goto out;
}
}
error = rqp->sr_lerror;
rqp->sr_rplast++;
out:
SMBRQ_UNLOCK(rqp);
if ((rqp->sr_flags & SMBR_MULTIPACKET) == 0)
smb_iod_removerq(rqp);
return (error);
}
int
smb_iod_waitrq_int(struct smb_rq *rqp)
{
struct smb_vc *vcp = rqp->sr_vc;
int timeleft = rqp->sr_timo;
int error;
ASSERT((rqp->sr_flags & SMBR_MULTIPACKET) == 0);
again:
error = smb_iod_recvall(vcp, B_TRUE);
if (error == ETIME) {
timeleft -= SMB_NBTIMO;
if (timeleft > 0)
goto again;
}
smb_iod_removerq(rqp);
if (rqp->sr_state != SMBRQ_NOTIFIED)
error = ETIME;
return (error);
}
void
smb_iod_shutdown_share(struct smb_share *ssp)
{
struct smb_vc *vcp = SSTOVC(ssp);
struct smb_rq *rqp;
rw_enter(&vcp->iod_rqlock, RW_READER);
TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) {
if (rqp->sr_state != SMBRQ_NOTIFIED && rqp->sr_share == ssp)
smb_iod_rqprocessed(rqp, EIO, 0);
}
rw_exit(&vcp->iod_rqlock);
}
int
nsmb_iod_connect(struct smb_vc *vcp, cred_t *cr)
{
int err, val;
ASSERT(vcp->iod_thr == curthread);
if (vcp->vc_state != SMBIOD_ST_RECONNECT) {
cmn_err(CE_NOTE, "iod_connect: bad state %d", vcp->vc_state);
return (EINVAL);
}
SMB_VC_LOCK(vcp);
if (vcp->vc_tdata)
SMB_TRAN_DONE(vcp);
err = SMB_TRAN_CREATE(vcp, cr);
SMB_VC_UNLOCK(vcp);
if (err != 0)
return (err);
val = smb_tcpsndbuf;
err = SMB_TRAN_SETPARAM(vcp, SMBTP_SNDBUF, &val);
if (err != 0) {
cmn_err(CE_NOTE, "iod_connect: setopt SNDBUF, err=%d", err);
}
val = smb_tcprcvbuf;
err = SMB_TRAN_SETPARAM(vcp, SMBTP_RCVBUF, &val);
if (err != 0) {
cmn_err(CE_NOTE, "iod_connect: setopt RCVBUF, err=%d", err);
}
val = 1;
err = SMB_TRAN_SETPARAM(vcp, SMBTP_KEEPALIVE, &val);
if (err != 0) {
cmn_err(CE_NOTE, "iod_connect: setopt KEEPALIVE, err=%d", err);
}
val = 1;
err = SMB_TRAN_SETPARAM(vcp, SMBTP_TCP_NODELAY, &val);
if (err != 0) {
cmn_err(CE_NOTE, "iod_connect: setopt TCP_NODELAY err=%d", err);
}
val = smb_connect_timeout * 1000;
err = SMB_TRAN_SETPARAM(vcp, SMBTP_TCP_CON_TMO, &val);
if (err != 0) {
cmn_err(CE_NOTE, "iod_connect: setopt TCP con tmo err=%d", err);
}
err = SMB_TRAN_BIND(vcp, NULL);
if (err != 0) {
cmn_err(CE_NOTE, "iod_connect: t_kbind: err=%d", err);
}
err = SMB_TRAN_CONNECT(vcp, &vcp->vc_srvaddr.sa);
if (err == 0) {
SMB_VC_LOCK(vcp);
smb_iod_newstate(vcp, SMBIOD_ST_CONNECTED);
SMB_VC_UNLOCK(vcp);
}
return (err);
}
int
nsmb_iod_negotiate(struct smb_vc *vcp, cred_t *cr)
{
struct smb_sopt *sv = &vcp->vc_sopt;
smb_cred_t scred;
int err = 0;
ASSERT(vcp->iod_thr == curthread);
smb_credinit(&scred, cr);
if (vcp->vc_state != SMBIOD_ST_CONNECTED) {
cmn_err(CE_NOTE, "iod_negotiate: bad state %d", vcp->vc_state);
err = EINVAL;
goto out;
}
if (vcp->vc_maxver == 0 || vcp->vc_minver > vcp->vc_maxver) {
err = EINVAL;
goto out;
}
bzero(sv, sizeof (*sv));
vcp->vc2_next_message_id = 0;
vcp->vc2_limit_message_id = 1;
vcp->vc2_session_id = 0;
vcp->vc_next_seq = 0;
SMB_VC_LOCK(vcp);
if (vcp->vc_mackey != NULL) {
kmem_free(vcp->vc_mackey, vcp->vc_mackeylen);
vcp->vc_mackey = NULL;
vcp->vc_mackeylen = 0;
}
if (vcp->vc_ssnkey != NULL) {
kmem_free(vcp->vc_ssnkey, vcp->vc_ssnkeylen);
vcp->vc_ssnkey = NULL;
vcp->vc_ssnkeylen = 0;
}
SMB_VC_UNLOCK(vcp);
if ((vcp->vc_flags & SMBV_SMB2) == 0) {
if (vcp->vc_minver < SMB2_DIALECT_BASE) {
err = smb_smb_negotiate(vcp, &scred);
if (err != 0)
goto out;
}
if (sv->sv_proto == SMB_DIALECT_SMB2_FF ||
vcp->vc_minver >= SMB2_DIALECT_BASE) {
SMB_VC_LOCK(vcp);
vcp->vc_flags |= SMBV_SMB2;
SMB_VC_UNLOCK(vcp);
}
}
if ((vcp->vc_flags & SMBV_SMB2) != 0) {
err = smb2_smb_negotiate(vcp, &scred);
}
out:
if (err == 0) {
SMB_VC_LOCK(vcp);
smb_iod_newstate(vcp, SMBIOD_ST_NEGOTIATED);
SMB_VC_UNLOCK(vcp);
}
smb_credrele(&scred);
return (err);
}
int
nsmb_iod_ssnsetup(struct smb_vc *vcp, cred_t *cr)
{
smb_cred_t scred;
int err;
ASSERT(vcp->iod_thr == curthread);
switch (vcp->vc_state) {
case SMBIOD_ST_NEGOTIATED:
case SMBIOD_ST_AUTHCONT:
break;
default:
return (EINVAL);
}
smb_credinit(&scred, cr);
if (vcp->vc_flags & SMBV_SMB2)
err = smb2_smb_ssnsetup(vcp, &scred);
else
err = smb_smb_ssnsetup(vcp, &scred);
smb_credrele(&scred);
SMB_VC_LOCK(vcp);
switch (err) {
case 0:
smb_iod_newstate(vcp, SMBIOD_ST_AUTHOK);
break;
case EINPROGRESS:
smb_iod_newstate(vcp, SMBIOD_ST_AUTHCONT);
break;
default:
smb_iod_newstate(vcp, SMBIOD_ST_AUTHFAIL);
break;
}
SMB_VC_UNLOCK(vcp);
return (err);
}
static int
smb_iod_logoff(struct smb_vc *vcp, cred_t *cr)
{
smb_cred_t scred;
int err;
ASSERT(vcp->iod_thr == curthread);
smb_credinit(&scred, cr);
if (vcp->vc_flags & SMBV_SMB2)
err = smb2_smb_logoff(vcp, &scred);
else
err = smb_smb_logoff(vcp, &scred);
smb_credrele(&scred);
return (err);
}
int
smb_iod_vc_work(struct smb_vc *vcp, int flags, cred_t *cr)
{
smbioc_ssn_work_t *wk = &vcp->vc_work;
int err = 0;
ASSERT(vcp->iod_thr == curthread);
if (vcp->vc_state != SMBIOD_ST_AUTHOK) {
cmn_err(CE_NOTE, "iod_vc_work: bad state %d", vcp->vc_state);
return (EINVAL);
}
if (wk->wk_u_ssnkey_len > 1024) {
cmn_err(CE_NOTE, "iod_vc_work: ssn key too long");
return (EINVAL);
}
ASSERT(vcp->vc_ssnkey == NULL);
SMB_VC_LOCK(vcp);
if (wk->wk_u_ssnkey_len != 0 &&
wk->wk_u_ssnkey_buf.lp_ptr != NULL) {
vcp->vc_ssnkeylen = wk->wk_u_ssnkey_len;
vcp->vc_ssnkey = kmem_alloc(vcp->vc_ssnkeylen, KM_SLEEP);
if (ddi_copyin(wk->wk_u_ssnkey_buf.lp_ptr,
vcp->vc_ssnkey, vcp->vc_ssnkeylen, flags) != 0) {
err = EFAULT;
}
}
SMB_VC_UNLOCK(vcp);
if (err)
return (err);
ASSERT(vcp->vc_mackey == NULL);
if (vcp->vc_ssnkey != NULL) {
if (vcp->vc_flags & SMBV_SMB2)
err = smb2_sign_init(vcp);
else
err = smb_sign_init(vcp);
if (err != 0)
return (err);
if (SMB_DIALECT(vcp) >= SMB2_DIALECT_0300)
nsmb_crypt_init_keys(vcp);
}
SMB_VC_LOCK(vcp);
vcp->vc_genid++;
smb_iod_newstate(vcp, SMBIOD_ST_VCACTIVE);
cv_broadcast(&vcp->vc_statechg);
SMB_VC_UNLOCK(vcp);
if (fscb && fscb->fscb_connect)
smb_vc_walkshares(vcp, fscb->fscb_connect);
(void) smb_iod_recvall(vcp, B_FALSE);
smb_vc_walkshares(vcp, smb_iod_share_disconnected);
smb_iod_invrq(vcp);
return (err);
}
int
smb_iod_vc_idle(struct smb_vc *vcp)
{
int err = 0;
boolean_t destroy = B_FALSE;
ASSERT(vcp->iod_thr == curthread);
if (vcp->vc_state != SMBIOD_ST_IDLE &&
vcp->vc_state != SMBIOD_ST_RECONNECT) {
cmn_err(CE_NOTE, "iod_vc_idle: bad state %d", vcp->vc_state);
return (EINVAL);
}
SMB_VC_LOCK(vcp);
while (vcp->vc_state == SMBIOD_ST_IDLE &&
vcp->vc_co.co_usecount > 1) {
if (cv_wait_sig(&vcp->iod_idle, &vcp->vc_lock) == 0) {
err = EINTR;
break;
}
}
if (vcp->vc_state == SMBIOD_ST_IDLE &&
vcp->vc_co.co_usecount == 1) {
vcp->vc_flags |= SMBV_GONE;
destroy = B_TRUE;
}
SMB_VC_UNLOCK(vcp);
if (destroy) {
smb_iod_disconnect(vcp);
}
return (err);
}
int
smb_iod_vc_rcfail(struct smb_vc *vcp)
{
clock_t tr;
int err = 0;
ASSERT(vcp->iod_thr == curthread);
SMB_VC_LOCK(vcp);
smb_iod_newstate(vcp, SMBIOD_ST_RCFAILED);
cv_broadcast(&vcp->vc_statechg);
tr = cv_reltimedwait_sig(&vcp->iod_idle, &vcp->vc_lock,
SEC_TO_TICK(5), TR_CLOCK_TICK);
if (tr == 0)
err = EINTR;
if (vcp->vc_state != SMBIOD_ST_RECONNECT) {
smb_iod_newstate(vcp, SMBIOD_ST_IDLE);
cv_broadcast(&vcp->vc_statechg);
}
SMB_VC_UNLOCK(vcp);
return (err);
}
int
smb_iod_reconnect(struct smb_vc *vcp)
{
int err = 0, rv;
SMB_VC_LOCK(vcp);
again:
switch (vcp->vc_state) {
case SMBIOD_ST_IDLE:
smb_iod_newstate(vcp, SMBIOD_ST_RECONNECT);
cv_signal(&vcp->iod_idle);
case SMBIOD_ST_RECONNECT:
case SMBIOD_ST_CONNECTED:
case SMBIOD_ST_NEGOTIATED:
case SMBIOD_ST_AUTHCONT:
case SMBIOD_ST_AUTHOK:
rv = cv_wait_sig(&vcp->vc_statechg, &vcp->vc_lock);
if (rv == 0) {
err = EINTR;
break;
}
goto again;
case SMBIOD_ST_VCACTIVE:
err = 0;
break;
case SMBIOD_ST_AUTHFAIL:
case SMBIOD_ST_RCFAILED:
case SMBIOD_ST_DEAD:
default:
err = ENOTCONN;
break;
}
SMB_VC_UNLOCK(vcp);
return (err);
}