#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/kpi_mbuf.h>
#include <sys/proc.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/smb_apple.h>
#include <sys/mchain.h>
#include <netsmb/netbios.h>
#include <netsmb/smb.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_rq.h>
#include <netsmb/smb_tran.h>
#include <netsmb/smb_trantcp.h>
#include <netsmb/smb_subr.h>
#include <netsmb/smb_sleephandler.h>
#define M_NBDATA M_PCB
static uint32_t smb_tcpsndbuf = 1024 * 255;
static uint32_t smb_tcprcvbuf = 1024 * 255;
SYSCTL_DECL(_net_smb_fs);
SYSCTL_INT(_net_smb_fs, OID_AUTO, tcpsndbuf, CTLFLAG_RW, &smb_tcpsndbuf, 0, "");
SYSCTL_INT(_net_smb_fs, OID_AUTO, tcprcvbuf, CTLFLAG_RW, &smb_tcprcvbuf, 0, "");
static int nbssn_recv(struct nbpcb *nbp, mbuf_t *mpp, int *lenp, uint8_t *rpcodep,
struct timespec *wait_time);
static int smb_nbst_disconnect(struct smb_vc *vcp);
static int
nb_setsockopt_int(socket_t so, int level, int name, int val)
{
return (sock_setsockopt(so, level, name, &val, (int)sizeof(val)));
}
static void
nb_upcall(socket_t so, void *arg, int waitflag)
{
#pragma unused(so, waitflag)
struct nbpcb *nbp = (struct nbpcb *)arg;
if ((so == NULL) || (nbp == NULL) || (nbp->nbp_tso == NULL) || (nbp->nbp_tso != so)) {
#ifdef SMB_DEBUG
if (nbp && nbp->nbp_tso) {
SMBDEBUG("UPCALLED: so = %p nbp = %p nbp->nbp_tso = %p\n", so, nbp, (nbp) ? nbp->nbp_tso : NULL);
}
#endif // SMB_DEBUG
return;
}
lck_mtx_lock(&nbp->nbp_lock);
nbp->nbp_flags |= NBF_UPCALLED;
if (nbp->nbp_upcall) {
nbp->nbp_upcall(nbp->nbp_selectid);
} else if (nbp->nbp_selectid)
wakeup(nbp->nbp_selectid);
lck_mtx_unlock(&nbp->nbp_lock);
return;
}
static int
nb_sethdr(struct nbpcb *nbp, mbuf_t m, uint8_t type, uint32_t len)
{
uint32_t *p = mbuf_data(m);
if (nbp->nbp_flags & NBF_NETBIOS) {
*p = htonl((len & SMB_MAXPKTLEN) | (type << 24));
} else {
*p = htonl((len & SMB_LARGE_MAXPKTLEN) | (type << 24));
}
return (0);
}
static int
nb_put_name(struct mbchain *mbp, struct sockaddr_nb *snb)
{
int error;
u_char seglen, *cp;
cp = snb->snb_name;
if (*cp == 0)
return (EINVAL);
NBDEBUG("[%s]\n", cp);
for (;;) {
seglen = (*cp) + 1;
error = mb_put_mem(mbp, (const char *)cp, seglen, MB_MSYSTEM);
if (error)
return (error);
if (seglen == 1)
break;
cp += seglen;
}
return (0);
}
static int tcp_connect(struct nbpcb *nbp, struct sockaddr *to)
{
socket_t so;
int error;
struct timeval tv;
int optlen;
uint32_t bufsize;
error = sock_socket(to->sa_family, SOCK_STREAM, IPPROTO_TCP, nb_upcall, nbp, &so);
if (error)
return (error);
nbp->nbp_tso = so;
tv.tv_sec = SMBSBTIMO;
tv.tv_usec = 0;
error = sock_setsockopt(so, SOL_SOCKET, SO_RCVTIMEO, &tv, (int)sizeof(tv));
if (error)
goto bad;
error = sock_setsockopt(so, SOL_SOCKET, SO_SNDTIMEO, &tv, (int)sizeof(tv));
if (error)
goto bad;
bufsize = nbp->nbp_sndbuf;
optlen = sizeof(bufsize);
error = sock_getsockopt(so, SOL_SOCKET, SO_SNDBUF, &bufsize, &optlen);
if (error) {
goto bad;
}
if (bufsize < nbp->nbp_sndbuf) {
optlen = sizeof(nbp->nbp_sndbuf);
error = sock_setsockopt(so, SOL_SOCKET, SO_SNDBUF, &nbp->nbp_sndbuf, optlen);
if (error) {
nbp->nbp_sndbuf = bufsize;
}
}
bufsize = nbp->nbp_rcvbuf;
optlen = sizeof(bufsize);
error = sock_getsockopt(so, SOL_SOCKET, SO_RCVBUF, &bufsize, &optlen);
if (error) {
goto bad;
}
if (bufsize < nbp->nbp_rcvbuf) {
optlen = sizeof(nbp->nbp_rcvbuf);
error = sock_setsockopt(so, SOL_SOCKET, SO_RCVBUF, &nbp->nbp_rcvbuf, optlen);
if (error) {
nbp->nbp_rcvbuf = bufsize;
}
}
if ((nbp->nbp_rcvbuf < smb_tcprcvbuf) || (nbp->nbp_sndbuf < smb_tcpsndbuf)) {
SMBWARNING("nbp_rcvbuf = %d nbp_sndbuf = %d\n", nbp->nbp_rcvbuf, nbp->nbp_sndbuf);
}
error = nb_setsockopt_int(so, SOL_SOCKET, SO_KEEPALIVE, 1);
if (error)
goto bad;
error = nb_setsockopt_int(so, IPPROTO_TCP, TCP_NODELAY, 1);
if (error)
goto bad;
error = nb_setsockopt_int(so, SOL_SOCKET, SO_NOADDRERR, 1);
if (error)
goto bad;
nb_setsockopt_int(so, SOL_SOCKET, SO_UPCALLCLOSEWAIT, 1);
error = sock_nointerrupt(so, 0);
if (error)
goto bad;
error = sock_connect(so, (struct sockaddr*)to, MSG_DONTWAIT);
if (error && error != EINPROGRESS)
goto bad;
tv.tv_sec = 2;
tv.tv_usec = 0;
while ((error = sock_connectwait(so, &tv)) == EINPROGRESS) {
if ((error = smb_iod_nb_intr(nbp->nbp_vc)))
break;
}
if (!error)
return (0);
bad:
smb_nbst_disconnect(nbp->nbp_vc);
return (error);
}
static int
nbssn_rq_request(struct nbpcb *nbp)
{
struct mbchain mb, *mbp = &mb;
struct mdchain md, *mdp = &md;
mbuf_t m0;
struct sockaddr_in sin;
u_short port;
uint8_t rpcode;
int error, rplen;
error = mb_init(mbp);
if (error)
return (error);
mb_put_uint32le(mbp, 0);
nb_put_name(mbp, nbp->nbp_paddr);
nb_put_name(mbp, nbp->nbp_laddr);
nb_sethdr(nbp, mbp->mb_top, NB_SSN_REQUEST, (uint32_t)(mb_fixhdr(mbp) - 4));
error = sock_sendmbuf(nbp->nbp_tso, NULL, (mbuf_t)mbp->mb_top, 0, NULL);
if (!error)
nbp->nbp_state = NBST_RQSENT;
mb_detach(mbp);
mb_done(mbp);
if (error)
return (error);
error = nbssn_recv(nbp, &m0, &rplen, &rpcode, &nbp->nbp_timo);
if (error == EWOULDBLOCK) {
NBDEBUG("initial request timeout\n");
return (ETIMEDOUT);
}
if (error) {
NBDEBUG("recv() error %d\n", error);
return (error);
}
if (m0)
md_initm(mdp, m0);
error = 0;
do {
if (rpcode == NB_SSN_POSRESP) {
lck_mtx_lock(&nbp->nbp_lock);
nbp->nbp_state = NBST_SESSION;
nbp->nbp_flags |= NBF_CONNECTED;
lck_mtx_unlock(&nbp->nbp_lock);
break;
}
if (rpcode != NB_SSN_RTGRESP) {
error = ECONNABORTED;
break;
}
if (rplen != 6) {
error = ECONNABORTED;
break;
}
md_get_mem(mdp, (caddr_t)&sin.sin_addr, 4, MB_MSYSTEM);
md_get_uint16(mdp, &port);
sin.sin_port = port;
nbp->nbp_state = NBST_RETARGET;
smb_nbst_disconnect(nbp->nbp_vc);
error = tcp_connect(nbp, (struct sockaddr *)&sin);
if (!error)
error = nbssn_rq_request(nbp);
if (error) {
smb_nbst_disconnect(nbp->nbp_vc);
break;
}
} while(0);
if (m0)
md_done(mdp);
return (error);
}
static int nbssn_recvhdr(struct nbpcb *nbp, uint32_t *lenp, uint8_t *rpcodep,
struct timespec *wait_time)
{
struct iovec aio;
uint32_t len;
uint8_t *bytep;
int error;
size_t resid, recvdlen;
struct msghdr msg;
struct smbiod *iod = nbp->nbp_vc->vc_iod;
int flags = MSG_DONTWAIT;
resid = sizeof(len);
bytep = (uint8_t *)&len;
while (resid != 0) {
aio.iov_base = bytep;
aio.iov_len = resid;
bzero(&msg, sizeof(msg));
msg.msg_iov = &aio;
msg.msg_iovlen = 1;
error = sock_receive(nbp->nbp_tso, &msg, flags, &recvdlen);
if ((error == EWOULDBLOCK) && wait_time) {
lck_mtx_lock(&nbp->nbp_lock);
if (!(nbp->nbp_flags & NBF_UPCALLED))
msleep(&iod->iod_flags, &nbp->nbp_lock, PWAIT, "nbssn", wait_time);
lck_mtx_unlock(&nbp->nbp_lock);
wait_time = 0;
flags = MSG_WAITALL;
continue;
}
if ((error == 0) && (recvdlen == 0) && resid) {
SMBWARNING("Server closed their side of the connection.\n");
nbp->nbp_state = NBST_CLOSED;
error = EPIPE;
}
if ((error == 0) && (recvdlen > resid)) {
SMBERROR("Got more data than we asked for!\n");
error = EPIPE;
}
if (!sock_isconnected(nbp->nbp_tso)) {
nbp->nbp_state = NBST_CLOSED;
NBDEBUG("session closed by peer\n");
error = EPIPE;
}
if (error) {
if ((error == EWOULDBLOCK) && resid && (resid < sizeof(len)))
SMBERROR("Timed out reading the nbt header: missing %ld bytes\n", resid);
return error;
}
flags = MSG_WAITALL;
resid -= recvdlen;
bytep += recvdlen;
}
len = ntohl(len);
*rpcodep = (len >> 24) & 0xFF;
if (nbp->nbp_flags & NBF_NETBIOS) {
if ((len >> 16) & 0xFE) {
SMBERROR("bad nb header received 0x%x (MBZ flag set)\n", len);
return (EPIPE);
}
len &= SMB_MAXPKTLEN;
} else {
len &= SMB_LARGE_MAXPKTLEN;
}
switch (*rpcodep) {
case NB_SSN_MESSAGE:
case NB_SSN_KEEPALIVE:
break;
case NB_SSN_REQUEST:
case NB_SSN_POSRESP:
case NB_SSN_NEGRESP:
case NB_SSN_RTGRESP:
if (nbp->nbp_flags & NBF_NETBIOS) {
break;
}
default:
SMBERROR("bad nb header received 0x%x (bogus type)\n", len);
return (EPIPE);
}
*lenp = len;
return (0);
}
static int nbssn_recv(struct nbpcb *nbp, mbuf_t *mpp, int *lenp, uint8_t *rpcodep,
struct timespec *wait_time)
{
socket_t so = nbp->nbp_tso;
mbuf_t m;
mbuf_t tm;
uint8_t rpcode;
uint32_t len;
int32_t error;
size_t recvdlen, resid;
if (so == NULL)
return (ENOTCONN);
if (mpp)
*mpp = NULL;
m = NULL;
for(;;) {
lck_mtx_lock(&nbp->nbp_lock);
nbp->nbp_flags &= ~NBF_UPCALLED;
lck_mtx_unlock(&nbp->nbp_lock);
error = nbssn_recvhdr(nbp, &len, &rpcode, wait_time);
if (error)
return (error);
resid = len;
while (resid != 0) {
struct timespec tstart, tend;
tm = NULL;
nanouptime(&tstart);
do {
recvdlen = MIN(resid, NB_SORECEIVE_CHUNK);
error = sock_receivembuf(so, NULL, &tm, MSG_WAITALL, &recvdlen);
if (error == EAGAIN) {
nanouptime(&tend);
if (tstart.tv_sec < gWakeTime.tv_sec)
tstart.tv_sec = gWakeTime.tv_sec;
if (tend.tv_sec > (tstart.tv_sec + SMB_SB_RCVTIMEO)) {
error = EPIPE;
SMBERROR("Breaking connection, sock_receivembuf blocked for %d\n", (int)(tend.tv_sec - tstart.tv_sec));
}
}
} while ((error == EAGAIN) || (error == EINTR) || (error == ERESTART));
if ((error == 0) && (recvdlen == 0) && resid) {
SMBWARNING("Server closed their side of the connection.\n");
error = EPIPE;
}
if ((error == 0) && (recvdlen > resid)) {
SMBERROR("Got more data than we asked for!\n");
if (tm)
mbuf_freem(tm);
error = EPIPE;
}
if (error)
goto out;
resid -= recvdlen;
if (!m) {
m = (mbuf_t )tm;
} else if (tm) {
mbuf_cat_internal(m, (mbuf_t )tm);
}
}
if (rpcode == NB_SSN_KEEPALIVE) {
if (m) {
mbuf_freem(m);
m = NULL;
}
continue;
}
if (nbp->nbp_state != NBST_SESSION) {
break;
}
if (rpcode == NB_SSN_MESSAGE) {
if (!m) {
SMBERROR("empty session packet\n");
continue;
}
break;
}
SMBERROR("non-session packet %x\n", rpcode);
if (m) {
mbuf_freem(m);
m = NULL;
}
}
out:
if (error) {
if (m)
mbuf_freem(m);
return (error);
}
if (mpp)
*mpp = m;
else
mbuf_freem(m);
*lenp = len;
*rpcodep = rpcode;
return (0);
}
static int
smb_nbst_create(struct smb_vc *vcp)
{
struct nbpcb *nbp;
SMB_MALLOC(nbp, struct nbpcb *, sizeof *nbp, M_NBDATA, M_WAITOK);
bzero(nbp, sizeof *nbp);
nbp->nbp_timo.tv_sec = SMB_NBTIMO;
nbp->nbp_state = NBST_CLOSED;
nbp->nbp_vc = vcp;
nbp->nbp_sndbuf = smb_tcpsndbuf;
nbp->nbp_rcvbuf = smb_tcprcvbuf;
lck_mtx_init(&nbp->nbp_lock, nbp_lck_group, nbp_lck_attr);
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);
smb_nbst_disconnect(vcp);
if (nbp->nbp_laddr)
SMB_FREE(nbp->nbp_laddr, M_SONAME);
if (nbp->nbp_paddr)
SMB_FREE(nbp->nbp_paddr, M_SONAME);
vcp->vc_tdata = NULL;
lck_mtx_destroy(&nbp->nbp_lock, nbp_lck_group);
SMB_FREE(nbp, M_NBDATA);
return (0);
}
static int
smb_nbst_bind(struct smb_vc *vcp, struct sockaddr *sap)
{
struct nbpcb *nbp = vcp->vc_tdata;
struct sockaddr_nb *snb;
int error, slen;
DBG_ASSERT(vcp->vc_tdata != NULL);
NBDEBUG("\n");
error = EINVAL;
do {
if (nbp->nbp_flags & NBF_LOCADDR)
break;
if (sap == NULL)
break;
slen = sap->sa_len;
if (slen < (int)NB_MINSALEN)
break;
snb = (struct sockaddr_nb*)smb_dup_sockaddr(sap, 1);
if (snb == NULL) {
error = ENOMEM;
break;
}
lck_mtx_lock(&nbp->nbp_lock);
nbp->nbp_laddr = snb;
nbp->nbp_flags |= NBF_LOCADDR;
lck_mtx_unlock(&nbp->nbp_lock);
error = 0;
} while(0);
return (error);
}
static int
smb_nbst_connect(struct smb_vc *vcp, struct sockaddr *sap)
{
struct nbpcb *nbp = vcp->vc_tdata;
struct sockaddr *so;
struct timespec ts1, ts2;
int error, slen;
NBDEBUG("\n");
if (nbp == NULL)
return (EINVAL);
if (nbp->nbp_tso != NULL)
return (EISCONN);
if (sap->sa_family == AF_NETBIOS) {
if (nbp->nbp_laddr == NULL)
return (EINVAL);
slen = sap->sa_len;
if (slen < (int)NB_MINSALEN)
return (EINVAL);
if (nbp->nbp_paddr) {
SMB_FREE(nbp->nbp_paddr, M_SONAME);
nbp->nbp_paddr = NULL;
}
nbp->nbp_paddr = (struct sockaddr_nb*)smb_dup_sockaddr(sap, 1);
if (nbp->nbp_paddr == NULL)
return (ENOMEM);
so = (struct sockaddr*)&(nbp->nbp_paddr)->snb_addrin;
} else {
so = sap;
}
nanouptime(&ts1);
error = tcp_connect(nbp, so);
if (error)
return (error);
nanouptime(&ts2);
timespecsub(&ts2, &ts1);
timespecadd(&ts2, &ts2);
timespecadd(&ts2, &ts2);
if (timespeccmp(&ts2, &nbp->nbp_timo, >))
nbp->nbp_timo = ts2;
if (sap->sa_family != AF_NETBIOS)
nbp->nbp_state = NBST_SESSION;
else {
nbp->nbp_flags |= NBF_NETBIOS;
error = nbssn_rq_request(nbp);
if (error)
smb_nbst_disconnect(vcp);
}
return (error);
}
static int
smb_nbst_disconnect(struct smb_vc *vcp)
{
struct nbpcb *nbp = vcp->vc_tdata;
socket_t so;
if (nbp == NULL || nbp->nbp_tso == NULL)
return (ENOTCONN);
if ((so = nbp->nbp_tso) != NULL) {
lck_mtx_lock(&nbp->nbp_lock);
nbp->nbp_flags &= ~NBF_CONNECTED;
nbp->nbp_tso = (socket_t) NULL;
lck_mtx_unlock(&nbp->nbp_lock);
sock_shutdown(so, 2);
sock_close(so);
}
if (nbp->nbp_state != NBST_RETARGET) {
nbp->nbp_state = NBST_CLOSED;
}
return (0);
}
static int
smb_nbst_send(struct smb_vc *vcp, mbuf_t m0)
{
struct nbpcb *nbp = vcp->vc_tdata;
int error;
DBG_ASSERT(nbp);
if ((nbp == NULL) || (nbp->nbp_state != NBST_SESSION)) {
error = ENOTCONN;
goto abort;
}
if (mbuf_prepend(&m0, 4, MBUF_WAITOK))
return (ENOBUFS);
nb_sethdr(nbp, m0, NB_SSN_MESSAGE, (uint32_t)(m_fixhdr(m0) - 4));
error = sock_sendmbuf(nbp->nbp_tso, NULL, (mbuf_t)m0, 0, NULL);
return (error);
abort:
if (m0)
mbuf_freem(m0);
return (error);
}
static int
smb_nbst_recv(struct smb_vc *vcp, mbuf_t *mpp)
{
struct nbpcb *nbp = vcp->vc_tdata;
uint8_t rpcode;
int error, rplen;
DBG_ASSERT(nbp);
if (nbp == NULL)
return (ENOTCONN);
lck_mtx_lock(&nbp->nbp_lock);
if (nbp->nbp_flags & NBF_RECVLOCK) {
SMBERROR("attempt to reenter session layer!\n");
lck_mtx_unlock(&nbp->nbp_lock);
return (EWOULDBLOCK);
}
nbp->nbp_flags |= NBF_RECVLOCK;
lck_mtx_unlock(&nbp->nbp_lock);
error = nbssn_recv(nbp, mpp, &rplen, &rpcode, NULL);
lck_mtx_lock(&nbp->nbp_lock);
nbp->nbp_flags &= ~NBF_RECVLOCK;
lck_mtx_unlock(&nbp->nbp_lock);
return (error);
}
static void
smb_nbst_timo(struct smb_vc *vcp)
{
#pragma unused(vcp)
return;
}
static int
smb_nbst_getparam(struct smb_vc *vcp, int param, void *data)
{
struct nbpcb *nbp = vcp->vc_tdata;
DBG_ASSERT(nbp);
if (nbp == NULL)
return (EINVAL);
switch (param) {
case SMBTP_SNDSZ:
*(uint32_t*)data = nbp->nbp_sndbuf;
break;
case SMBTP_RCVSZ:
*(uint32_t*)data = nbp->nbp_rcvbuf;
break;
case SMBTP_TIMEOUT:
*(struct timespec*)data = nbp->nbp_timo;
break;
case SMBTP_SELECTID:
*(void **)data = nbp->nbp_selectid;
break;
case SMBTP_UPCALL:
*(void **)data = nbp->nbp_upcall;
break;
default:
return (EINVAL);
}
return (0);
}
static int
smb_nbst_setparam(struct smb_vc *vcp, int param, void *data)
{
struct nbpcb *nbp = vcp->vc_tdata;
DBG_ASSERT(nbp);
if (nbp == NULL)
return (EINVAL);
switch (param) {
case SMBTP_SELECTID:
nbp->nbp_selectid = data;
break;
case SMBTP_UPCALL:
nbp->nbp_upcall = data;
break;
default:
return (EINVAL);
}
return (0);
}
static int
smb_nbst_fatal(struct smb_vc *vcp, int error)
{
struct nbpcb *nbp;
switch (error) {
case EHOSTDOWN:
case ENETUNREACH:
case ENOTCONN:
case ENETRESET:
case ECONNABORTED:
case EPIPE:
case EADDRNOTAVAIL:
return 1;
}
DBG_ASSERT(vcp);
nbp = vcp->vc_tdata;
if ((nbp == NULL) || (nbp->nbp_tso == NULL) || (! sock_isconnected(nbp->nbp_tso)))
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_connect, smb_nbst_disconnect,
smb_nbst_send, smb_nbst_recv,
smb_nbst_timo,
smb_nbst_getparam, smb_nbst_setparam,
smb_nbst_fatal,
{NULL, NULL}
};