diff options
author | bp <bp@FreeBSD.org> | 2001-04-10 07:59:06 +0000 |
---|---|---|
committer | bp <bp@FreeBSD.org> | 2001-04-10 07:59:06 +0000 |
commit | a414f03f5d87ade219aa2e4bcd0830eceaee6bd3 (patch) | |
tree | e02d13218bf41d783a93777341cba62b2921dc10 /sys/netsmb/smb_trantcp.c | |
parent | 88436d21df771b1b6d764b003c5daecf40dd767f (diff) | |
download | FreeBSD-src-a414f03f5d87ade219aa2e4bcd0830eceaee6bd3.zip FreeBSD-src-a414f03f5d87ade219aa2e4bcd0830eceaee6bd3.tar.gz |
Import kernel part of SMB/CIFS requester.
Add smbfs(CIFS) filesystem.
Userland part will be in the ports tree for a while.
Obtained from: smbfs-1.3.7-dev package.
Diffstat (limited to 'sys/netsmb/smb_trantcp.c')
-rw-r--r-- | sys/netsmb/smb_trantcp.c | 672 |
1 files changed, 672 insertions, 0 deletions
diff --git a/sys/netsmb/smb_trantcp.c b/sys/netsmb/smb_trantcp.c new file mode 100644 index 0000000..c9d0622 --- /dev/null +++ b/sys/netsmb/smb_trantcp.c @@ -0,0 +1,672 @@ +/* + * Copyright (c) 2000-2001 Boris Popov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Boris Popov. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/proc.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/poll.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/mchain.h> + +#include <netsmb/netbios.h> + +#include <netsmb/smb.h> +#include <netsmb/smb_conn.h> +#include <netsmb/smb_tran.h> +#include <netsmb/smb_trantcp.h> +#include <netsmb/smb_subr.h> + +#define M_NBDATA M_PCB + +static int smb_tcpsndbuf = 10 * 1024; +static int smb_tcprcvbuf = 10 * 1024; + +SYSCTL_DECL(_net_smb); +SYSCTL_INT(_net_smb, OID_AUTO, tcpsndbuf, CTLFLAG_RW, &smb_tcpsndbuf, 0, ""); +SYSCTL_INT(_net_smb, OID_AUTO, tcprcvbuf, CTLFLAG_RW, &smb_tcprcvbuf, 0, ""); + +#define nb_sosend(so,m,flags,p) (so)->so_proto->pr_usrreqs->pru_sosend( \ + so, NULL, 0, m, 0, flags, p) + +static int nbssn_recv(struct nbpcb *nbp, struct mbuf **mpp, int *lenp, + u_int8_t *rpcodep, struct proc *p); +static int smb_nbst_disconnect(struct smb_vc *vcp, struct proc *p); + +static int +nb_setsockopt_int(struct socket *so, int level, int name, int val) +{ + struct sockopt sopt; + + bzero(&sopt, sizeof(sopt)); + sopt.sopt_level = level; + sopt.sopt_name = name; + sopt.sopt_val = &val; + sopt.sopt_valsize = sizeof(val); + return sosetopt(so, &sopt); +} + +static __inline int +nb_poll(struct nbpcb *nbp, int events, struct proc *p) +{ + return nbp->nbp_tso->so_proto->pr_usrreqs->pru_sopoll(nbp->nbp_tso, + events, NULL, p); +} + +static int +nbssn_rselect(struct nbpcb *nbp, struct timeval *tv, int events, struct proc *p) +{ + struct timeval atv, rtv, ttv; + int s, timo, error; + + if (tv) { + atv = *tv; + if (itimerfix(&atv)) { + error = EINVAL; + goto done; + } + getmicrouptime(&rtv); + timevaladd(&atv, &rtv); + } + timo = 0; +retry: + p->p_flag |= P_SELECT; + error = nb_poll(nbp, events, p); + if (error) { + error = 0; + goto done; + } + if (tv) { + getmicrouptime(&rtv); + if (timevalcmp(&rtv, &atv, >=)) + goto done; + ttv = atv; + timevalsub(&ttv, &rtv); + timo = tvtohz(&ttv); + } + s = splhigh(); + if ((p->p_flag & P_SELECT) == 0) { + splx(s); + goto retry; + } + p->p_flag &= ~P_SELECT; + error = tsleep((caddr_t)&selwait, PSOCK, "nbsel", timo); + splx(s); +done: + p->p_flag &= ~P_SELECT; + if (error == ERESTART) + return 0; + return error; +} + +static int +nb_intr(struct nbpcb *nbp, struct proc *p) +{ + return 0; +} + +static void +nb_upcall(struct socket *so, void *arg, int waitflag) +{ + struct nbpcb *nbp = arg; + + if (arg == NULL || nbp->nbp_selectid == NULL) + return; + wakeup(nbp->nbp_selectid); +} + +static int +nb_sethdr(struct mbuf *m, u_int8_t type, u_int32_t len) +{ + u_int32_t *p = mtod(m, u_int32_t *); + + *p = htonl((len & 0x1FFFF) | (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, cp, seglen, MB_MSYSTEM); + if (error) + return error; + if (seglen == 1) + break; + cp += seglen; + } + return 0; +} + +static int +nb_connect_in(struct nbpcb *nbp, struct sockaddr_in *to, struct proc *p) +{ + struct socket *so; + int error, s; + + error = socreate(AF_INET, &so, SOCK_STREAM, IPPROTO_TCP, p); + if (error) + return error; + nbp->nbp_tso = so; + so->so_upcallarg = (caddr_t)nbp; + so->so_upcall = nb_upcall; + so->so_rcv.sb_flags |= SB_UPCALL; + so->so_rcv.sb_timeo = (5 * hz); + so->so_snd.sb_timeo = (5 * hz); + error = soreserve(so, nbp->nbp_sndbuf, nbp->nbp_rcvbuf); + if (error) + goto bad; + nb_setsockopt_int(so, SOL_SOCKET, SO_KEEPALIVE, 1); + nb_setsockopt_int(so, IPPROTO_TCP, TCP_NODELAY, 1); + so->so_rcv.sb_flags &= ~SB_NOINTR; + so->so_snd.sb_flags &= ~SB_NOINTR; + error = soconnect(so, (struct sockaddr*)to, p); + if (error) + goto bad; + s = splnet(); + while ((so->so_state & SS_ISCONNECTING) && so->so_error == 0) { + tsleep(&so->so_timeo, PSOCK, "nbcon", 2 * hz); + if ((so->so_state & SS_ISCONNECTING) && so->so_error == 0 && + (error = nb_intr(nbp, p)) != 0) { + so->so_state &= ~SS_ISCONNECTING; + splx(s); + goto bad; + } + } + if (so->so_error) { + error = so->so_error; + so->so_error = 0; + splx(s); + goto bad; + } + splx(s); + return 0; +bad: + smb_nbst_disconnect(nbp->nbp_vc, p); + return error; +} + +static int +nbssn_rq_request(struct nbpcb *nbp, struct proc *p) +{ + struct mbchain mb, *mbp = &mb; + struct mdchain md, *mdp = &md; + struct mbuf *m0; + struct timeval tv; + struct sockaddr_in sin; + u_short port; + u_int8_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(mbp->mb_top, NB_SSN_REQUEST, mb_fixhdr(mbp) - 4); + error = nb_sosend(nbp->nbp_tso, mbp->mb_top, 0, p); + if (!error) { + nbp->nbp_state = NBST_RQSENT; + } + mb_detach(mbp); + mb_done(mbp); + if (error) + return error; + TIMESPEC_TO_TIMEVAL(&tv, &nbp->nbp_timo); + error = nbssn_rselect(nbp, &tv, POLLIN, p); + if (error == EWOULDBLOCK) { /* Timeout */ + NBDEBUG("initial request timeout\n"); + return ETIMEDOUT; + } + if (error) /* restart or interrupt */ + return error; + error = nbssn_recv(nbp, &m0, &rplen, &rpcode, p); + if (error) { + NBDEBUG("recv() error %d\n", error); + return error; + } + /* + * Process NETBIOS reply + */ + if (m0) + md_initm(mdp, m0); + error = 0; + do { + if (rpcode == NB_SSN_POSRESP) { + nbp->nbp_state = NBST_SESSION; + nbp->nbp_flags |= NBF_CONNECTED; + 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, p); + error = nb_connect_in(nbp, &sin, p); + if (!error) + error = nbssn_rq_request(nbp, p); + if (error) { + smb_nbst_disconnect(nbp->nbp_vc, p); + break; + } + } while(0); + if (m0) + md_done(mdp); + return error; +} + +static int +nbssn_recvhdr(struct nbpcb *nbp, int *lenp, + u_int8_t *rpcodep, int flags, struct proc *p) +{ + struct socket *so = nbp->nbp_tso; + struct uio auio; + struct iovec aio; + u_int32_t len; + int error; + + aio.iov_base = (caddr_t)&len; + aio.iov_len = sizeof(len); + auio.uio_iov = &aio; + auio.uio_iovcnt = 1; + auio.uio_segflg = UIO_SYSSPACE; + auio.uio_rw = UIO_READ; + auio.uio_offset = 0; + auio.uio_resid = sizeof(len); + auio.uio_procp = p; + error = so->so_proto->pr_usrreqs->pru_soreceive + (so, (struct sockaddr **)NULL, &auio, + (struct mbuf **)NULL, (struct mbuf **)NULL, &flags); + if (error) + return error; + if (auio.uio_resid > 0) { + SMBSDEBUG("short reply\n"); + return EPIPE; + } + len = ntohl(len); + *rpcodep = (len >> 24) & 0xFF; + len &= 0x1ffff; + if (len > SMB_MAXPKTLEN) { + SMBERROR("packet too long (%d)\n", len); + return EFBIG; + } + *lenp = len; + return 0; +} + +static int +nbssn_recv(struct nbpcb *nbp, struct mbuf **mpp, int *lenp, + u_int8_t *rpcodep, struct proc *p) +{ + struct socket *so = nbp->nbp_tso; + struct uio auio; + struct mbuf *m; + u_int8_t rpcode; + int len; + int error, rcvflg; + + if (so == NULL) + return ENOTCONN; + + if (mpp) + *mpp = NULL; + for(;;) { + m = NULL; + error = nbssn_recvhdr(nbp, &len, &rpcode, MSG_DONTWAIT, p); + if (so->so_state & + (SS_ISDISCONNECTING | SS_ISDISCONNECTED | SS_CANTRCVMORE)) { + nbp->nbp_state = NBST_CLOSED; + NBDEBUG("session closed by peer\n"); + return ECONNRESET; + } + if (error) + return error; + if (len == 0 && nbp->nbp_state != NBST_SESSION) + break; + if (rpcode == NB_SSN_KEEPALIVE) + continue; + bzero(&auio, sizeof(auio)); + auio.uio_resid = len; + auio.uio_procp = p; + do { + rcvflg = MSG_WAITALL; + error = so->so_proto->pr_usrreqs->pru_soreceive + (so, (struct sockaddr **)NULL, + &auio, &m, (struct mbuf **)NULL, &rcvflg); + } while (error == EWOULDBLOCK || error == EINTR || + error == ERESTART); + if (error) + break; + if (auio.uio_resid > 0) { + SMBERROR("packet is shorter than expected\n"); + error = EPIPE; + break; + } + if (nbp->nbp_state == NBST_SESSION && + rpcode == NB_SSN_MESSAGE) + break; + NBDEBUG("non-session packet %x\n", rpcode); + if (m) + m_freem(m); + } + if (error) { + if (m) + m_freem(m); + return error; + } + if (mpp) + *mpp = m; + else + m_freem(m); + *lenp = len; + *rpcodep = rpcode; + return 0; +} + +/* + * SMB transport interface + */ +static int +smb_nbst_create(struct smb_vc *vcp, struct proc *p) +{ + struct nbpcb *nbp; + + MALLOC(nbp, struct nbpcb *, sizeof *nbp, M_NBDATA, M_WAITOK); + bzero(nbp, sizeof *nbp); + nbp->nbp_timo.tv_sec = 15; /* XXX: sysctl ? */ + nbp->nbp_state = NBST_CLOSED; + nbp->nbp_vc = vcp; + nbp->nbp_sndbuf = smb_tcpsndbuf; + nbp->nbp_rcvbuf = smb_tcprcvbuf; + vcp->vc_tdata = nbp; + return 0; +} + +static int +smb_nbst_done(struct smb_vc *vcp, struct proc *p) +{ + struct nbpcb *nbp = vcp->vc_tdata; + + if (nbp == NULL) + return ENOTCONN; + smb_nbst_disconnect(vcp, p); + if (nbp->nbp_laddr) + free(nbp->nbp_laddr, M_SONAME); + if (nbp->nbp_paddr) + free(nbp->nbp_paddr, M_SONAME); + free(nbp, M_NBDATA); + return 0; +} + +static int +smb_nbst_bind(struct smb_vc *vcp, struct sockaddr *sap, struct proc *p) +{ + struct nbpcb *nbp = vcp->vc_tdata; + struct sockaddr_nb *snb; + int error, slen; + + NBDEBUG("\n"); + error = EINVAL; + do { + if (nbp->nbp_flags & NBF_LOCADDR) + break; + /* + * It is possible to create NETBIOS name in the kernel, + * but nothing prevents us to do it in the user space. + */ + if (sap == NULL) + break; + slen = sap->sa_len; + if (slen < NB_MINSALEN) + break; + snb = (struct sockaddr_nb*)dup_sockaddr(sap, 1); + if (snb == NULL) { + error = ENOMEM; + break; + } + nbp->nbp_laddr = snb; + nbp->nbp_flags |= NBF_LOCADDR; + error = 0; + } while(0); + return error; +} + +static int +smb_nbst_connect(struct smb_vc *vcp, struct sockaddr *sap, struct proc *p) +{ + struct nbpcb *nbp = vcp->vc_tdata; + struct sockaddr_in sin; + struct sockaddr_nb *snb; + struct timespec ts1, ts2; + int error, slen; + + NBDEBUG("\n"); + if (nbp->nbp_tso != NULL) + return EISCONN; + if (nbp->nbp_laddr == NULL) + return EINVAL; + slen = sap->sa_len; + if (slen < NB_MINSALEN) + return EINVAL; + if (nbp->nbp_paddr) { + free(nbp->nbp_paddr, M_SONAME); + nbp->nbp_paddr = NULL; + } + snb = (struct sockaddr_nb*)dup_sockaddr(sap, 1); + if (snb == NULL) + return ENOMEM; + nbp->nbp_paddr = snb; + sin = snb->snb_addrin; + getnanotime(&ts1); + error = nb_connect_in(nbp, &sin, p); + if (error) + return error; + getnanotime(&ts2); + timespecsub(&ts2, &ts1); + if (ts2.tv_sec == 0 && ts2.tv_sec == 0) + ts2.tv_sec = 1; + nbp->nbp_timo = ts2; + timespecadd(&nbp->nbp_timo, &ts2); + timespecadd(&nbp->nbp_timo, &ts2); + timespecadd(&nbp->nbp_timo, &ts2); /* * 4 */ + error = nbssn_rq_request(nbp, p); + if (error) + smb_nbst_disconnect(vcp, p); + return error; +} + +static int +smb_nbst_disconnect(struct smb_vc *vcp, struct proc *p) +{ + struct nbpcb *nbp = vcp->vc_tdata; + struct socket *so; + + if (nbp == NULL || nbp->nbp_tso == NULL) + return ENOTCONN; + if ((so = nbp->nbp_tso) != NULL) { + nbp->nbp_flags &= ~NBF_CONNECTED; + nbp->nbp_tso = (struct socket *)NULL; + soshutdown(so, 2); + soclose(so); + } + if (nbp->nbp_state != NBST_RETARGET) { + nbp->nbp_state = NBST_CLOSED; + } + return 0; +} + +static int +smb_nbst_send(struct smb_vc *vcp, struct mbuf *m0, struct proc *p) +{ + struct nbpcb *nbp = vcp->vc_tdata; + int error; + + if (nbp->nbp_state != NBST_SESSION) { + error = ENOTCONN; + goto abort; + } + M_PREPEND(m0, 4, M_WAITOK); + if (m0 == NULL) + return ENOBUFS; + nb_sethdr(m0, NB_SSN_MESSAGE, m_fixhdr(m0) - 4); + error = nb_sosend(nbp->nbp_tso, m0, 0, p); + return error; +abort: + if (m0) + m_freem(m0); + return error; +} + + +static int +smb_nbst_recv(struct smb_vc *vcp, struct mbuf **mpp, struct proc *p) +{ + struct nbpcb *nbp = vcp->vc_tdata; + u_int8_t rpcode; + int error, rplen; + + nbp->nbp_flags |= NBF_RECVLOCK; + error = nbssn_recv(nbp, mpp, &rplen, &rpcode, p); + nbp->nbp_flags &= ~NBF_RECVLOCK; + return error; +} + +static void +smb_nbst_timo(struct smb_vc *vcp) +{ + return; +} + +static void +smb_nbst_intr(struct smb_vc *vcp) +{ + struct nbpcb *nbp = vcp->vc_tdata; + + if (nbp == NULL || nbp->nbp_tso == NULL) + return; + sorwakeup(nbp->nbp_tso); + sowwakeup(nbp->nbp_tso); +} + +static int +smb_nbst_getparam(struct smb_vc *vcp, int param, void *data) +{ + struct nbpcb *nbp = vcp->vc_tdata; + + switch (param) { + case SMBTP_SNDSZ: + *(int*)data = nbp->nbp_sndbuf; + break; + case SMBTP_RCVSZ: + *(int*)data = nbp->nbp_rcvbuf; + break; + case SMBTP_TIMEOUT: + *(struct timespec*)data = nbp->nbp_timo; + 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; + + switch (param) { + case SMBTP_SELECTID: + nbp->nbp_selectid = data; + break; + default: + return EINVAL; + } + return 0; +} + +/* + * Check for fatal errors + */ +static int +smb_nbst_fatal(struct smb_vc *vcp, int error) +{ + switch (error) { + case ENOTCONN: + case ENETRESET: + case ECONNABORTED: + 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_intr, + smb_nbst_getparam, smb_nbst_setparam, + smb_nbst_fatal +}; + |