diff options
Diffstat (limited to 'sys/netncp/ncp_ncp.c')
-rw-r--r-- | sys/netncp/ncp_ncp.c | 613 |
1 files changed, 613 insertions, 0 deletions
diff --git a/sys/netncp/ncp_ncp.c b/sys/netncp/ncp_ncp.c new file mode 100644 index 0000000..943c0ca --- /dev/null +++ b/sys/netncp/ncp_ncp.c @@ -0,0 +1,613 @@ +/* + * Copyright (c) 1999, 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$ + * + * Core of NCP protocol + */ +#include "opt_inet.h" +#include "opt_ipx.h" + +#include <sys/param.h> +#include <sys/errno.h> +#include <sys/malloc.h> +#include <sys/systm.h> +#include <sys/proc.h> +#include <sys/kernel.h> +#include <sys/poll.h> +#include <sys/signalvar.h> +#include <sys/socketvar.h> +#include <sys/mbuf.h> + +#ifdef IPX +#include <netipx/ipx.h> +#include <netipx/ipx_var.h> +#endif + +#include <netncp/ncp.h> +#include <netncp/ncp_conn.h> +#include <netncp/ncp_sock.h> +#include <netncp/ncp_subr.h> +#include <netncp/ncp_ncp.h> +#include <netncp/ncp_rq.h> +#include <netncp/nwerror.h> + +static int ncp_do_request(struct ncp_conn *,struct ncp_rq *rqp); +static int ncp_negotiate_buffersize(struct ncp_conn *conn, int size, int *target); +static int ncp_renegotiate_connparam(struct ncp_conn *conn, int buffsize, int in_options); +static void ncp_sign_packet(struct ncp_conn *conn, struct ncp_rq *rqp, int *size); + + +#ifdef NCP_DATA_DEBUG +static +void m_dumpm(struct mbuf *m) { + char *p; + int len; + printf("d="); + while(m) { + p=mtod(m,char *); + len=m->m_len; + printf("(%d)",len); + while(len--){ + printf("%02x ",((int)*(p++)) & 0xff); + } + m=m->m_next; + }; + printf("\n"); +} +#endif /* NCP_DATA_DEBUG */ + +int +ncp_chkintr(struct ncp_conn *conn, struct proc *p) +{ + sigset_t tmpset; + + if (p == NULL) + return 0; + tmpset = p->p_siglist; + SIGSETNAND(tmpset, p->p_sigmask); + SIGSETNAND(tmpset, p->p_sigignore); + if (SIGNOTEMPTY(p->p_siglist) && NCP_SIGMASK(tmpset)) + return EINTR; + return 0; +} + +/* + * Process initial NCP handshake (attach) + * NOTE: Since all functions below may change conn attributes, they + * should be called with LOCKED connection, also they use procp & ucred + */ +int +ncp_ncp_connect(struct ncp_conn *conn) { + int error; + struct ncp_rphdr *rp; + DECLARE_RQ; + + conn->flags &= ~(NCPFL_INVALID | NCPFL_SIGNACTIVE | NCPFL_SIGNWANTED); + conn->seq = 0; + checkbad(ncp_rq_head(rqp,NCP_ALLOC_SLOT,0,conn->procp,conn->ucred)); + error=ncp_do_request(conn,rqp); + if (!error) { + rp = mtod(rqp->rp, struct ncp_rphdr*); + conn->connid = rp->conn_low + (rp->conn_high << 8); + } + ncp_rq_done(rqp); + if (error) return error; + conn->flags |= NCPFL_ATTACHED; + + error = ncp_renegotiate_connparam(conn, NCP_DEFAULT_BUFSIZE, 0); + if (error == NWE_SIGNATURE_LEVEL_CONFLICT) { + printf("Unable to negotiate requested security level\n"); + error = EOPNOTSUPP; + } + if (error) { + ncp_ncp_disconnect(conn); + return error; + } +#ifdef NCPBURST + ncp_burst_connect(conn); +#endif +bad: + return error; +} + +int +ncp_ncp_disconnect(struct ncp_conn *conn) { + int error; + struct ncp_rqhdr *ncprq; + DECLARE_RQ; + + NCPSDEBUG("for connid=%d\n",conn->nc_id); +#ifdef NCPBURST + ncp_burst_disconnect(conn); +#endif + error=ncp_rq_head(rqp,NCP_FREE_SLOT,0,conn->procp,conn->ucred); + ncprq = mtod(rqp->rq,struct ncp_rqhdr*); + error=ncp_do_request(conn,rqp); + ncp_rq_done(rqp); + ncp_conn_invalidate(conn); + ncp_sock_disconnect(conn); + return 0; +} +/* + * Make a signature for the current packet and add it at the end of the + * packet. + */ +static void +ncp_sign_packet(struct ncp_conn *conn, struct ncp_rq *rqp, int *size) { + u_char data[64]; + + bzero(data, sizeof(data)); + bcopy(conn->sign_root, data, 8); + setdle(data, 8, *size); + m_copydata(rqp->rq, sizeof(struct ncp_rqhdr)-1, + min((*size) - sizeof(struct ncp_rqhdr)+1, 52),data+12); + ncp_sign(conn->sign_state, data, conn->sign_state); + ncp_rq_mem(rqp, (void*)conn->sign_state, 8); + (*size) += 8; +} + +/* + * Low level send rpc, here we do not attempt to restore any connection, + * Connection expected to be locked + */ +static int +ncp_do_request(struct ncp_conn *conn, struct ncp_rq *rqp) { + int error=EIO,len, dosend, plen = 0, gotpacket, s; + struct socket *so; + struct proc *p = conn->procp; + struct ncp_rqhdr *rq; + struct ncp_rphdr *rp=NULL; + struct timeval tv; + struct mbuf *m, *mreply = NULL; + + conn->nc_rq = rqp; + if (p == NULL) + p = curproc; /* XXX maybe procpage ? */ + if (!ncp_conn_valid(conn)) { + printf("%s: conn not valid\n",__FUNCTION__); + return (error); + } + so = conn->ncp_so; + if (!so) { + printf("%s: ncp_so is NULL !\n",__FUNCTION__); + ncp_conn_invalidate(conn); /* wow ! how we do that ? */ + return EBADF; + } + /* + * Flush out replies on previous reqs + */ + s = splnet(); + while (1/*so->so_rcv.sb_cc*/) { + if (ncp_poll(so,POLLIN) == 0) break; + if (ncp_sock_recv(so,&m,&len) != 0) break; + m_freem(m); + } + rq = mtod(rqp->rq,struct ncp_rqhdr *); + rq->seq = conn->seq; + m = rqp->rq; + len = 0; + while (m) { + len += m->m_len; + m = m->m_next; + } + rqp->rq->m_pkthdr.len = len; + switch(rq->fn) { + case 0x15: case 0x16: case 0x17: case 0x23: + m = rqp->rq; + *((u_int16_t*)(mtod(m,u_int8_t*)+sizeof(*rq))) = htons(len-2-sizeof(*rq)); + break; + } + if (conn->flags & NCPFL_SIGNACTIVE) { + ncp_sign_packet(conn, rqp, &len); + rqp->rq->m_pkthdr.len = len; + } + rq->conn_low = conn->connid & 0xff; + /* rq->task = p->p_pgrp->pg_id & 0xff; */ /*p->p_pid*/ + /* XXX: this is temporary fix till I find a better solution */ + rq->task = rq->conn_low; + rq->conn_high = conn->connid >> 8; + rqp->rexmit = conn->li.retry_count; + for(dosend = 1;;) { + if (rqp->rexmit-- == 0) { + error = ETIMEDOUT; + break; + } + error = 0; + if (dosend) { + NCPSDEBUG("send:%04x f=%02x c=%d l=%d s=%d t=%d\n",rq->type, rq->fn, (rq->conn_high << 8) + rq->conn_low, + rqp->rq->m_pkthdr.len, rq->seq, rq->task + ); + error = ncp_sock_send(so, rqp->rq, rqp); + if (error) break; + } + tv.tv_sec = conn->li.timeout; + tv.tv_usec = 0; + error = ncp_sock_rselect(so, p, &tv, POLLIN); + if (error == EWOULDBLOCK ) /* timeout expired */ + continue; + error = ncp_chkintr(conn, p); + if (error == EINTR) /* we dont restart */ + break; + if (error) break; + /* + * At this point it is possible to get more than one + * reply from server. In general, last reply should be for + * current request, but not always. So, we loop through + * all replies to find the right answer and flush others. + */ + gotpacket = 0; /* nothing good found */ + dosend = 1; /* resend rq if error */ + for (;;) { + error = 0; + if (ncp_poll(so,POLLIN) == 0) break; +/* if (so->so_rcv.sb_cc == 0) { + break; + }*/ + error = ncp_sock_recv(so,&m,&len); + if (error) break; /* must be more checks !!! */ + if (m->m_len < sizeof(*rp)) { + m = m_pullup(m, sizeof(*rp)); + if (m == NULL) { + printf("%s: reply too short\n",__FUNCTION__); + continue; + } + } + rp = mtod(m, struct ncp_rphdr*); + if (len == sizeof(*rp) && rp->type == NCP_POSITIVE_ACK) { + NCPSDEBUG("got positive acknowledge\n"); + m_freem(m); + rqp->rexmit = conn->li.retry_count; + dosend = 0; /* server just busy and will reply ASAP */ + continue; + } + NCPSDEBUG("recv:%04x c=%d l=%d s=%d t=%d cc=%02x cs=%02x\n",rp->type, + (rp->conn_high << 8) + rp->conn_low, len, rp->seq, rp->task, + rp->completion_code, rp->connection_state); + NCPDDEBUG(m); + if ( (rp->type == NCP_REPLY) && + ((rq->type == NCP_ALLOC_SLOT) || + ((rp->conn_low == rq->conn_low) && + (rp->conn_high == rq->conn_high) + ))) { + if (rq->seq > rp->seq || (rq->seq == 0 && rp->seq == 0xff)) { + dosend = 1; + } + if (rp->seq == rq->seq) { + if (gotpacket) { + m_freem(m); + } else { + gotpacket = 1; + mreply = m; + plen = len; + } + continue; /* look up other for other packets */ + } + } + m_freem(m); + NCPSDEBUG("reply mismatch\n"); + } /* for receive */ + if (error) break; + if (gotpacket) break; + /* try to resend, or just wait */ + } + splx(s); + conn->seq++; + if (error) { + NCPSDEBUG("error=%d\n",error); + if (error != EINTR) /* if not just interrupt */ + ncp_conn_invalidate(conn); /* only reconnect to restore */ + return(error); + } + if (conn->flags & NCPFL_SIGNACTIVE) { + /* XXX: check reply signature */ + m_adj(mreply, -8); + plen -= 8; + } + len = plen; + m = mreply; + rp = mtod(m, struct ncp_rphdr*); + len -= sizeof(*rp); + rqp->rpsize = len; + rqp->cc = error = rp->completion_code; + if (error) error |= 0x8900; /* server error */ + rqp->cs = rp->connection_state; + if (rqp->cs & (NCP_CS_BAD_CONN | NCP_CS_SERVER_DOWN)) { + NCPSDEBUG("server drop us\n"); + ncp_conn_invalidate(conn); + error = ECONNRESET; + } + rqp->rp = m; + rqp->mrp = m; + rqp->bpos = mtod(m, caddr_t) + sizeof(*rp); + return error; +} + +/* + * Here we will try to restore any loggedin & dropped connection, + * connection should be locked on entry + */ +int ncp_restore_login(struct ncp_conn *conn); +int +ncp_restore_login(struct ncp_conn *conn) { + int error, oldflags; + + if (conn->flags & NCPFL_RESTORING) { + printf("Hey, ncp_restore_login called twise !!!\n"); + return 0; + } + oldflags = conn->flags; + printf("Restoring connection, flags = %d\n",oldflags); + if ((oldflags & NCPFL_LOGGED) == 0) { + return ECONNRESET; /* no need to restore empty conn */ + } + conn->flags &= ~(NCPFL_LOGGED | NCPFL_ATTACHED); + conn->flags |= NCPFL_RESTORING; + do { /* not a loop */ + error = ncp_reconnect(conn); + if (error) break; + if (conn->li.user) + error = ncp_login_object(conn, conn->li.user, conn->li.objtype, conn->li.password,conn->procp,conn->ucred); + if (error) break; + conn->flags |= NCPFL_LOGGED; + } while(0); + if (error) { + conn->flags = oldflags | NCPFL_INVALID; + } + conn->flags &= ~NCPFL_RESTORING; + return error; +} + +int +ncp_request(struct ncp_conn *conn, struct ncp_rq *rqp) { + int error, rcnt; +/* struct ncp_rqhdr *rq = mtod(rqp->rq,struct ncp_rqhdr*);*/ + + error = ncp_conn_lock(conn,rqp->p,rqp->cred,NCPM_EXECUTE); + if (error) return error; + rcnt = NCP_RESTORE_COUNT; + for(;;) { + if (!ncp_conn_valid(conn)) { + if (rcnt==0) { + error = ECONNRESET; + break; + } + rcnt--; + error = ncp_restore_login(conn); + if (error) + continue; + } + error=ncp_do_request(conn, rqp); + if (ncp_conn_valid(conn)) /* not just error ! */ + break; + } + ncp_conn_unlock(conn,rqp->p); + return error; +} + +/* + * All negotiation functions expect a locked connection + */ +static int +ncp_negotiate_buffersize(struct ncp_conn *conn, int size, int *target) { + int error; + DECLARE_RQ; + + NCP_RQ_HEAD(0x21,conn->procp,conn->ucred); + ncp_rq_word_hl(rqp, size); + checkbad(ncp_request(conn,rqp)); + *target = min(ncp_rp_word_hl(rqp), size); + NCP_RQ_EXIT; + return error; +} + +static int +ncp_negotiate_size_and_options(struct ncp_conn *conn, int size, int options, + int *ret_size, int *ret_options) { + int error; + int rs; + DECLARE_RQ; + + NCP_RQ_HEAD(0x61,conn->procp,conn->ucred); + ncp_rq_word_hl(rqp, size); + ncp_rq_byte(rqp, options); + checkbad(ncp_request(conn, rqp)); + rs = ncp_rp_word_hl(rqp); + *ret_size = (rs == 0) ? size : min(rs, size); + ncp_rp_word_hl(rqp); /* skip echo socket */ + *ret_options = ncp_rp_byte(rqp); + NCP_RQ_EXIT; + return error; +} + +static int +ncp_renegotiate_connparam(struct ncp_conn *conn, int buffsize, int in_options) +{ + int neg_buffsize, error, options, sl; + + sl = conn->li.sig_level; + if (sl >= 2) + in_options |= NCP_SECURITY_LEVEL_SIGN_HEADERS; +#ifdef IPX + if (ipxcksum == 2) + in_options |= NCP_IPX_CHECKSUM; +#endif + error = ncp_negotiate_size_and_options(conn, buffsize, in_options, + &neg_buffsize, &options); + if (!error) { +#ifdef IPX + if ((options ^ in_options) & NCP_IPX_CHECKSUM) { + if (ipxcksum == 2) { + printf("Server refuses to support IPX checksums\n"); + return NWE_REQUESTER_FAILURE; + } + in_options |= NCP_IPX_CHECKSUM; + error = 1; + } +#endif /* IPX */ + if ((options ^ in_options) & 2) { + if (sl == 0 || sl == 3) + return NWE_SIGNATURE_LEVEL_CONFLICT; + if (sl == 1) { + in_options |= NCP_SECURITY_LEVEL_SIGN_HEADERS; + error = 1; + } + } + if (error) { + error = ncp_negotiate_size_and_options(conn, + buffsize, in_options, &neg_buffsize, &options); + if ((options ^ in_options) & 3) { + return NWE_SIGNATURE_LEVEL_CONFLICT; + } + } + } else { + in_options &= ~NCP_SECURITY_LEVEL_SIGN_HEADERS; + error = ncp_negotiate_buffersize(conn, NCP_DEFAULT_BUFSIZE, + &neg_buffsize); + } + if (error) return error; + if ((neg_buffsize < 512) || (neg_buffsize > NCP_MAX_BUFSIZE)) + return EINVAL; + conn->buffer_size = neg_buffsize; + if (in_options & NCP_SECURITY_LEVEL_SIGN_HEADERS) + conn->flags |= NCPFL_SIGNWANTED; +#ifdef IPX + ncp_sock_checksum(conn, in_options & NCP_IPX_CHECKSUM); +#endif + return 0; +} + +int +ncp_reconnect(struct ncp_conn *conn) { + int error; + + /* close any open sockets */ + ncp_sock_disconnect(conn); + switch( conn->li.saddr.sa_family ) { +#ifdef IPX + case AF_IPX: + error = ncp_sock_connect_ipx(conn); + break; +#endif +#ifdef INET + case AF_INET: + error = ncp_sock_connect_in(conn); + break; +#endif + default: + return EPROTONOSUPPORT; + } + if (!error) + error = ncp_ncp_connect(conn); + return error; +} + +/* + * Create conn structure and try to do low level connect + * Server addr should be filled in. + */ +int +ncp_connect(struct ncp_conn_args *li, struct proc *p, struct ucred *cred, + struct ncp_conn **aconn) +{ + struct ncp_conn *conn; + struct ucred *owner; + int error, isroot; + + if (li->saddr.sa_family != AF_INET && li->saddr.sa_family != AF_IPX) + return EPROTONOSUPPORT; + isroot = ncp_suser(cred) == 0; + /* + * Only root can change ownership + */ + if (li->owner != NCP_DEFAULT_OWNER && !isroot) + return EPERM; + if (li->group != NCP_DEFAULT_GROUP && + !groupmember(li->group, cred) && !isroot) + return EPERM; + if (li->owner != NCP_DEFAULT_OWNER) { + owner = crget(); + owner->cr_uid = li->owner; + } else { + owner = cred; + crhold(owner); + } + error = ncp_conn_alloc(p, owner, &conn); + if (error) + return (error); + if (error) { + ncp_conn_free(conn); + return error; + } + conn->li = *li; + conn->nc_group = (li->group != NCP_DEFAULT_GROUP) ? + li->group : cred->cr_groups[0]; + + if (li->retry_count == 0) + conn->li.retry_count = NCP_RETRY_COUNT; + if (li->timeout == 0) + conn->li.timeout = NCP_RETRY_TIMEOUT; + error = ncp_reconnect(conn); + if (error) { + ncp_disconnect(conn); + } else { + *aconn=conn; + } + return error; +} +/* + * Break connection and deallocate memory + */ +int +ncp_disconnect(struct ncp_conn *conn) { + + if (ncp_conn_access(conn,conn->ucred,NCPM_WRITE)) + return EACCES; + if (conn->ref_cnt != 0) return EBUSY; + if (conn->flags & NCPFL_PERMANENT) return EBUSY; + if (ncp_conn_valid(conn)) { + ncp_ncp_disconnect(conn); + } + ncp_sock_disconnect(conn); + ncp_conn_free(conn); + return 0; +} + +void +ncp_check_rq(struct ncp_conn *conn){ + return; + if (conn->flags & NCPFL_INTR) return; + /* first, check for signals */ + if (ncp_chkintr(conn,conn->procp)) { + conn->flags |= NCPFL_INTR; + } + return; +} |