#include <sys/param.h>
#include <sys/systm.h>
#include <sys/autoconf.h>
#include <sys/sysmacros.h>
#include <sys/sunddi.h>
#include <sys/kmem.h>
#include <sys/proc.h>
#include <sys/protosw.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <sys/stream.h>
#include <sys/strsubr.h>
#include <sys/strsun.h>
#include <sys/stropts.h>
#include <sys/cmn_err.h>
#include <sys/tihdr.h>
#include <sys/tiuser.h>
#include <sys/t_kuser.h>
#include <sys/priv.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netsmb/smb_osdep.h>
#include <netsmb/mchain.h>
#include <netsmb/netbios.h>
#include <netsmb/smb.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_subr.h>
#include <netsmb/smb_tran.h>
#include <netsmb/smb_trantcp.h>
static int nb_disconnect(struct nbpcb *nbp);
static int
nb_getmsg_mlen(struct nbpcb *nbp, mblk_t **mpp, size_t mlen)
{
mblk_t *im, *tm;
union T_primitives *pptr;
size_t dlen;
int events, fmode, timo, waitflg;
int error = 0;
ASSERT(nbp->nbp_flags & NBF_RECVLOCK);
dlen = msgdsize(*mpp);
while (dlen < mlen) {
#if 1
events = 0;
waitflg = READWAIT;
timo = SEC_TO_TICK(SMB_NBTIMO);
error = t_kspoll(nbp->nbp_tiptr, timo, waitflg, &events);
if (!error && !events)
error = ETIME;
if (error)
break;
fmode = FNDELAY;
#else
fmode = 0;
#endif
tm = NULL;
error = tli_recv(nbp->nbp_tiptr, &tm, fmode);
if (error == EAGAIN)
continue;
if (error)
break;
switch (tm->b_datap->db_type) {
case M_DATA:
break;
case M_PROTO:
case M_PCPROTO:
pptr = (union T_primitives *)tm->b_rptr;
switch (pptr->type) {
case T_DATA_IND:
im = tm->b_cont;
tm->b_cont = NULL;
freeb(tm);
tm = im;
break;
case T_DISCON_IND:
NBDEBUG("T_DISCON_IND: reason=%d",
(int)pptr->discon_ind.DISCON_reason);
goto discon;
case T_ORDREL_IND:
NBDEBUG("T_ORDREL_IND");
goto discon;
case T_OK_ACK:
switch (pptr->ok_ack.CORRECT_prim) {
case T_DISCON_REQ:
NBDEBUG("T_OK_ACK/T_DISCON_REQ");
goto discon;
default:
NBDEBUG("T_OK_ACK/prim=%d",
(int)pptr->ok_ack.CORRECT_prim);
goto discon;
}
default:
NBDEBUG("M_PROTO/type=%d", (int)pptr->type);
goto discon;
}
break;
default:
NBDEBUG("unexpected msg type=%d",
tm->b_datap->db_type);
discon:
freemsg(tm);
(void) nb_disconnect(nbp);
return (ENOTCONN);
}
if (!tm)
continue;
if (*mpp == NULL) {
*mpp = tm;
} else {
for (im = *mpp; im->b_cont; im = im->b_cont)
;
im->b_cont = tm;
}
dlen += msgdsize(tm);
}
return (error);
}
static int
nb_snddis(struct nbpcb *nbp)
{
TIUSER *tiptr = nbp->nbp_tiptr;
cred_t *cr = nbp->nbp_cred;
mblk_t *mp;
struct T_discon_req *dreq;
int error, mlen;
ASSERT(MUTEX_HELD(&nbp->nbp_lock));
if (tiptr == NULL)
return (EBADF);
mlen = sizeof (struct T_discon_req);
if (!(mp = allocb_cred_wait(mlen, STR_NOSIG, &error, cr, NOPID)))
return (error);
mp->b_datap->db_type = M_PROTO;
dreq = (struct T_discon_req *)mp->b_wptr;
dreq->PRIM_type = T_DISCON_REQ;
dreq->SEQ_number = -1;
mp->b_wptr += sizeof (struct T_discon_req);
error = tli_send(tiptr, mp, tiptr->fp->f_flag);
return (error);
}
static void
nb_sethdr(mblk_t *m, uint8_t type, uint32_t len)
{
uint32_t *p;
len &= 0x1FFFF;
len |= (type << 24);
p = (uint32_t *)m->b_rptr;
*p = htonl(len);
}
static int
nbssn_peekhdr(struct nbpcb *nbp, size_t *lenp, uint8_t *rpcodep)
{
uint32_t len, *hdr;
int error;
error = nb_getmsg_mlen(nbp, &nbp->nbp_frag, sizeof (len));
if (error)
return (error);
if (!pullupmsg(nbp->nbp_frag, sizeof (len)))
return (ENOSR);
hdr = (uint32_t *)nbp->nbp_frag->b_rptr;
len = ntohl(*hdr);
if ((len >> 16) & 0xFE) {
NBDEBUG("bad nb header received 0x%x (MBZ flag set)\n", len);
return (EPIPE);
}
*rpcodep = (len >> 24) & 0xFF;
switch (*rpcodep) {
case NB_SSN_MESSAGE:
case NB_SSN_REQUEST:
case NB_SSN_POSRESP:
case NB_SSN_NEGRESP:
case NB_SSN_RTGRESP:
case NB_SSN_KEEPALIVE:
break;
default:
NBDEBUG("bad nb header received 0x%x (bogus type)\n", len);
return (EPIPE);
}
len &= 0x1ffff;
if (len > NB_MAXPKTLEN) {
NBDEBUG("packet too long (%d)\n", len);
return (EFBIG);
}
*lenp = len;
return (0);
}
static int
nbssn_recv(struct nbpcb *nbp, mblk_t **mpp, int *lenp,
uint8_t *rpcodep)
{
mblk_t *m0;
uint8_t rpcode;
int error;
size_t rlen, len;
ASSERT(nbp->nbp_flags & NBF_RECVLOCK);
if (nbp->nbp_tiptr == NULL)
return (EBADF);
if (mpp) {
if (*mpp) {
NBDEBUG("*mpp not 0 - leak?");
}
*mpp = NULL;
}
m0 = NULL;
error = nbssn_peekhdr(nbp, &len, &rpcode);
if (error) {
if (error != ETIME)
NBDEBUG("peekhdr, error=%d\n", error);
return (error);
}
NBDEBUG("Have pkt, type=0x%x len=0x%x\n",
(int)rpcode, (int)len);
error = nb_getmsg_mlen(nbp, &nbp->nbp_frag, len + 4);
if (error) {
NBDEBUG("getmsg(body), error=%d\n", error);
return (error);
}
m0 = nbp->nbp_frag;
m0->b_rptr += 4;
rlen = msgdsize(m0);
ASSERT(rlen >= len);
nbp->nbp_frag = NULL;
if (rlen > len)
nbp->nbp_frag = m_split(m0, len, 1);
if (nbp->nbp_state != NBST_SESSION) {
goto out;
}
switch (rpcode) {
case NB_SSN_KEEPALIVE:
if (len)
NBDEBUG("Keepalive with data %d\n", (int)len);
error = EAGAIN;
break;
case NB_SSN_MESSAGE:
if (len == 0) {
error = EAGAIN;
break;
}
error = 0;
break;
default:
NBDEBUG("non-session packet %x\n", rpcode);
error = EAGAIN;
break;
}
out:
if (error) {
if (m0)
m_freem(m0);
return (error);
}
if (mpp)
*mpp = m0;
else
m_freem(m0);
*lenp = (int)len;
*rpcodep = rpcode;
return (0);
}
static int
smb_nbst_create(struct smb_vc *vcp, cred_t *cr)
{
TIUSER *tiptr = NULL;
struct nbpcb *nbp = NULL;
dev_t dev;
int rc;
ushort_t fmode;
switch (vcp->vc_srvaddr.sa.sa_family) {
case AF_INET:
dev = nsmb_dev_tcp;
break;
case AF_INET6:
dev = nsmb_dev_tcp6;
break;
default:
return (EAFNOSUPPORT);
}
fmode = FREAD|FWRITE;
rc = t_kopen(NULL, dev, fmode, &tiptr, cr);
if (rc != 0) {
cmn_err(CE_NOTE, "t_kopen failed, rc=%d", rc);
return (rc);
}
ASSERT(tiptr != NULL);
nbp = kmem_zalloc(sizeof (struct nbpcb), KM_SLEEP);
nbp->nbp_timo.tv_sec = SMB_NBTIMO;
nbp->nbp_state = NBST_IDLE;
nbp->nbp_vc = vcp;
nbp->nbp_tiptr = tiptr;
nbp->nbp_fmode = fmode;
nbp->nbp_cred = cr;
crhold(cr);
mutex_init(&nbp->nbp_lock, NULL, MUTEX_DRIVER, NULL);
vcp->vc_tdata = nbp;
return (0);
}
static int
smb_nbst_done(struct smb_vc *vcp)
{
struct nbpcb *nbp = vcp->vc_tdata;
if (nbp == NULL)
return (ENOTCONN);
vcp->vc_tdata = NULL;
if (nbp->nbp_flags & NBF_CONNECTED)
(void) nb_disconnect(nbp);
if (nbp->nbp_tiptr)
(void) t_kclose(nbp->nbp_tiptr, 0);
if (nbp->nbp_laddr)
smb_free_sockaddr((struct sockaddr *)nbp->nbp_laddr);
if (nbp->nbp_paddr)
smb_free_sockaddr((struct sockaddr *)nbp->nbp_paddr);
if (nbp->nbp_cred)
crfree(nbp->nbp_cred);
mutex_destroy(&nbp->nbp_lock);
kmem_free(nbp, sizeof (*nbp));
return (0);
}
static int
smb_nbst_bind(struct smb_vc *vcp, struct sockaddr *sap)
{
struct nbpcb *nbp = vcp->vc_tdata;
TIUSER *tiptr = nbp->nbp_tiptr;
int err;
if (sap != NULL)
return (ENOTSUP);
err = t_kbind(tiptr, NULL, NULL);
return (err);
}
static int
smb_nbst_unbind(struct smb_vc *vcp)
{
struct nbpcb *nbp = vcp->vc_tdata;
TIUSER *tiptr = nbp->nbp_tiptr;
int err;
err = t_kunbind(tiptr);
return (err);
}
static int
smb_nbst_connect(struct smb_vc *vcp, struct sockaddr *sap)
{
struct t_call call;
struct nbpcb *nbp = vcp->vc_tdata;
TIUSER *tiptr = nbp->nbp_tiptr;
int alen, err;
switch (sap->sa_family) {
case AF_INET:
alen = sizeof (struct sockaddr_in);
break;
case AF_INET6:
alen = sizeof (struct sockaddr_in6);
break;
default:
return (EAFNOSUPPORT);
}
bzero(&call, sizeof (call));
call.addr.buf = (char *)sap;
call.addr.len = alen;
call.addr.maxlen = alen;
err = t_kconnect(tiptr, &call, NULL);
if (err != 0)
return (err);
mutex_enter(&nbp->nbp_lock);
nbp->nbp_flags |= NBF_CONNECTED;
nbp->nbp_state = NBST_SESSION;
mutex_exit(&nbp->nbp_lock);
return (0);
}
static int
smb_nbst_disconnect(struct smb_vc *vcp)
{
struct nbpcb *nbp = vcp->vc_tdata;
if (nbp == NULL)
return (ENOTCONN);
return (nb_disconnect(nbp));
}
static int
nb_disconnect(struct nbpcb *nbp)
{
int err = 0;
mutex_enter(&nbp->nbp_lock);
if ((nbp->nbp_flags & NBF_CONNECTED) != 0) {
nbp->nbp_flags &= ~NBF_CONNECTED;
err = nb_snddis(nbp);
}
mutex_exit(&nbp->nbp_lock);
return (err);
}
static int
nbssn_send(struct nbpcb *nbp, mblk_t *m)
{
ptrdiff_t diff;
uint32_t mlen;
int error;
ASSERT(nbp->nbp_flags & NBF_SENDLOCK);
if (nbp->nbp_tiptr == NULL) {
error = EBADF;
goto errout;
}
mlen = msgdsize(m);
diff = MBLKHEAD(m);
if (diff == 4 && DB_REF(m) == 1) {
m->b_rptr -= 4;
} else {
mblk_t *m0;
m0 = allocb_wait(4, BPRI_LO, STR_NOSIG, &error);
if (m0 == NULL)
goto errout;
m0->b_wptr += 4;
m0->b_cont = m;
m = m0;
}
nb_sethdr(m, NB_SSN_MESSAGE, mlen);
error = tli_send(nbp->nbp_tiptr, m, 0);
return (error);
errout:
if (m != NULL)
m_freem(m);
return (error);
}
static int
smb_nbst_send(struct smb_vc *vcp, mblk_t *m)
{
struct nbpcb *nbp = vcp->vc_tdata;
int err;
mutex_enter(&nbp->nbp_lock);
if ((nbp->nbp_flags & NBF_CONNECTED) == 0) {
err = ENOTCONN;
goto out;
}
if (nbp->nbp_flags & NBF_SENDLOCK) {
NBDEBUG("multiple smb_nbst_send!\n");
err = EWOULDBLOCK;
goto out;
}
nbp->nbp_flags |= NBF_SENDLOCK;
mutex_exit(&nbp->nbp_lock);
err = nbssn_send(nbp, m);
m = NULL;
mutex_enter(&nbp->nbp_lock);
nbp->nbp_flags &= ~NBF_SENDLOCK;
if (nbp->nbp_flags & NBF_LOCKWAIT) {
nbp->nbp_flags &= ~NBF_LOCKWAIT;
cv_broadcast(&nbp->nbp_cv);
}
out:
mutex_exit(&nbp->nbp_lock);
if (m != NULL)
m_freem(m);
return (err);
}
static int
smb_nbst_recv(struct smb_vc *vcp, mblk_t **mpp)
{
struct nbpcb *nbp = vcp->vc_tdata;
uint8_t rpcode;
int err, rplen;
mutex_enter(&nbp->nbp_lock);
if ((nbp->nbp_flags & NBF_CONNECTED) == 0) {
err = ENOTCONN;
goto out;
}
if (nbp->nbp_flags & NBF_RECVLOCK) {
NBDEBUG("multiple smb_nbst_recv!\n");
err = EWOULDBLOCK;
goto out;
}
nbp->nbp_flags |= NBF_RECVLOCK;
mutex_exit(&nbp->nbp_lock);
err = nbssn_recv(nbp, mpp, &rplen, &rpcode);
mutex_enter(&nbp->nbp_lock);
nbp->nbp_flags &= ~NBF_RECVLOCK;
if (nbp->nbp_flags & NBF_LOCKWAIT) {
nbp->nbp_flags &= ~NBF_LOCKWAIT;
cv_broadcast(&nbp->nbp_cv);
}
out:
mutex_exit(&nbp->nbp_lock);
return (err);
}
static int
smb_nbst_poll(struct smb_vc *vcp, int ticks)
{
return (ENOTSUP);
}
static int
smb_nbst_getparam(struct smb_vc *vcp, int param, void *data)
{
return (EINVAL);
}
static int
smb_nbst_setparam(struct smb_vc *vcp, int param, void *data)
{
struct t_optmgmt oreq, ores;
struct {
struct T_opthdr oh;
int ival;
} opts;
struct nbpcb *nbp = vcp->vc_tdata;
int level, name, err;
switch (param) {
case SMBTP_TCP_NODELAY:
level = IPPROTO_TCP;
name = TCP_NODELAY;
break;
case SMBTP_TCP_CON_TMO:
level = IPPROTO_TCP;
name = TCP_CONN_ABORT_THRESHOLD;
break;
case SMBTP_KEEPALIVE:
case SMBTP_SNDBUF:
case SMBTP_RCVBUF:
case SMBTP_RCVTIMEO:
level = SOL_SOCKET;
name = param;
break;
default:
return (EINVAL);
}
opts.oh.len = sizeof (opts);
opts.oh.level = level;
opts.oh.name = name;
opts.oh.status = 0;
opts.ival = *(int *)data;
oreq.flags = T_NEGOTIATE;
oreq.opt.buf = (void *)&opts;
oreq.opt.len = sizeof (opts);
oreq.opt.maxlen = oreq.opt.len;
ores.flags = 0;
ores.opt.buf = NULL;
ores.opt.len = 0;
ores.opt.maxlen = 0;
err = t_koptmgmt(nbp->nbp_tiptr, &oreq, &ores);
if (err != 0) {
cmn_err(CE_NOTE, "t_opgmgnt, err = %d", err);
return (EPROTO);
}
if ((ores.flags & T_SUCCESS) == 0) {
cmn_err(CE_NOTE, "smb_nbst_setparam: "
"flags 0x%x, status 0x%x",
(int)ores.flags, (int)opts.oh.status);
return (EPROTO);
}
return (0);
}
static int
smb_nbst_fatal(struct smb_vc *vcp, int error)
{
switch (error) {
case ENOTCONN:
case ENETRESET:
case ECONNABORTED:
case EPIPE:
return (1);
}
return (0);
}
struct smb_tran_desc smb_tran_nbtcp_desc = {
SMBT_NBTCP,
smb_nbst_create,
smb_nbst_done,
smb_nbst_bind,
smb_nbst_unbind,
smb_nbst_connect,
smb_nbst_disconnect,
smb_nbst_send,
smb_nbst_recv,
smb_nbst_poll,
smb_nbst_getparam,
smb_nbst_setparam,
smb_nbst_fatal,
{NULL, NULL}
};