/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* This module implements the "fast path" processing for the telnet protocol.
* Since it only knows a very small number of the telnet protocol options,
* the daemon is required to assist this module. This module must be run
* underneath logindmux, which handles switching messages between the
* daemon and the pty master stream appropriately. When an unknown telnet
* option is received it is handled as a stop-and-wait operation. The
* module refuses to forward data in either direction, and waits for the
* daemon to deal with the option, and forward any unprocessed data back
* to the daemon.
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/strsun.h>
#include <sys/kmem.h>
#include <sys/errno.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/tihdr.h>
#include <sys/ptem.h>
#include <sys/logindmux.h>
#include <sys/telioctl.h>
#include <sys/termios.h>
#include <sys/debug.h>
#include <sys/conf.h>
#include <sys/modctl.h>
#include <sys/cmn_err.h>
#include <sys/cryptmod.h>
#define IAC 255
extern struct streamtab telmodinfo;
#define TELMOD_ID 105
#define SIMWAIT (1*hz)
/*
* Module state flags
*/
#define TEL_IOCPASSTHRU 0x100
#define TEL_STOPPED 0x80
#define TEL_CRRCV 0x40
#define TEL_CRSND 0x20
#define TEL_GETBLK 0x10
/*
* NOTE: values TEL_BINARY_IN and TEL_BINARY_OUT are defined in
* telioctl.h, passed in the TEL_IOC_MODE ioctl and stored (bitwise)
* in the module state flag. So those values are not available
* even though they are not defined here.
*/
/*
* Per queue instances are single-threaded since the q_ptr
* field of queues need to be shared among threads.
*/
static struct fmodsw fsw = {
"telmod",
&telmodinfo,
D_MTQPAIR | D_MP
};
/*
* Module linkage information for the kernel.
*/
static struct modlstrmod modlstrmod = {
&mod_strmodops,
"telnet module",
&fsw
};
static struct modlinkage modlinkage = {
MODREV_1, &modlstrmod, NULL
};
int
_init()
{
return (mod_install(&modlinkage));
}
int
_fini()
{
return (mod_remove(&modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static int telmodopen(queue_t *, dev_t *, int, int, cred_t *);
static int telmodclose(queue_t *, int, cred_t *);
static void telmodrput(queue_t *, mblk_t *);
static void telmodrsrv(queue_t *);
static void telmodwput(queue_t *, mblk_t *);
static void telmodwsrv(queue_t *);
static int rcv_parse(queue_t *q, mblk_t *mp);
static int snd_parse(queue_t *q, mblk_t *mp);
static void telmod_timer(void *);
static void telmod_buffer(void *);
static void recover(queue_t *, mblk_t *, size_t);
static struct module_info telmodoinfo = {
TELMOD_ID, /* module id number */
"telmod", /* module name */
0, /* minimum packet size */
INFPSZ, /* maximum packet size */
512, /* hi-water mark */
256 /* lo-water mark */
};
static struct qinit telmodrinit = {
(int (*)())telmodrput,
(int (*)())telmodrsrv,
telmodopen,
telmodclose,
nulldev,
&telmodoinfo,
NULL
};
static struct qinit telmodwinit = {
(int (*)())telmodwput,
(int (*)())telmodwsrv,
NULL,
NULL,
nulldev,
&telmodoinfo,
NULL
};
struct streamtab telmodinfo = {
&telmodrinit,
&telmodwinit,
NULL,
NULL
};
/*
* Per-instance state struct for the telnet module.
*/
struct telmod_info {
int flags;
bufcall_id_t wbufcid;
bufcall_id_t rbufcid;
timeout_id_t wtimoutid;
timeout_id_t rtimoutid;
mblk_t *unbind_mp;
};
/*ARGSUSED*/
static void
dummy_callback(void *arg)
{}
/*
* telmodopen -
* A variety of telnet options can never really be processed in the
* kernel. For example, TELOPT_TTYPE, must be based in the TERM
* environment variable to the login process. Also, data may already
* have reached the stream head before telmod was pushed on the stream.
* So when telmod is opened, it begins in stopped state, preventing
* further data passing either direction through it. It sends a
* T_DATA_REQ messages up toward the daemon. This is so the daemon
* can be sure that all data which was not processed by telmod
* (because it wasn't yet pushed) has been received at the stream head.
*/
/*ARGSUSED*/
static int
telmodopen(queue_t *q, dev_t *devp, int oflag, int sflag, cred_t *credp)
{
struct telmod_info *tmip;
mblk_t *bp;
union T_primitives *tp;
int error;
if (sflag != MODOPEN)
return (EINVAL);
if (q->q_ptr != NULL) {
/* It's already attached. */
return (0);
}
/*
* Allocate state structure.
*/
tmip = kmem_zalloc(sizeof (*tmip), KM_SLEEP);
/*
* Cross-link.
*/
q->q_ptr = tmip;
WR(q)->q_ptr = tmip;
noenable(q);
tmip->flags |= TEL_STOPPED;
qprocson(q);
/*
* Since TCP operates in the TLI-inspired brain-dead fashion,
* the connection will revert to bound state if the connection
* is reset by the client. We must send a T_UNBIND_REQ in
* that case so the port doesn't get "wedged" (preventing
* inetd from being able to restart the listener). Allocate
* it here, so that we don't need to worry about allocb()
* failures later.
*/
while ((tmip->unbind_mp = allocb(sizeof (union T_primitives),
BPRI_HI)) == NULL) {
bufcall_id_t id = qbufcall(q, sizeof (union T_primitives),
BPRI_HI, dummy_callback, NULL);
if (!qwait_sig(q)) {
qunbufcall(q, id);
error = EINTR;
goto fail;
}
qunbufcall(q, id);
}
tmip->unbind_mp->b_wptr = tmip->unbind_mp->b_rptr +
sizeof (struct T_unbind_req);
tmip->unbind_mp->b_datap->db_type = M_PROTO;
tp = (union T_primitives *)tmip->unbind_mp->b_rptr;
tp->type = T_UNBIND_REQ;
/*
* Send a M_PROTO msg of type T_DATA_REQ (this is unique for
* read queue since only write queue can get T_DATA_REQ).
* Readstream routine in telnet daemon will do a getmsg() till
* it receives this proto message
*/
while ((bp = allocb(sizeof (union T_primitives), BPRI_HI)) == NULL) {
bufcall_id_t id = qbufcall(q, sizeof (union T_primitives),
BPRI_HI, dummy_callback, NULL);
if (!qwait_sig(q)) {
qunbufcall(q, id);
error = EINTR;
goto fail;
}
qunbufcall(q, id);
}
bp->b_datap->db_type = M_PROTO;
bp->b_wptr = bp->b_rptr + sizeof (union T_primitives);
tp = (union T_primitives *)bp->b_rptr;
tp->type = T_DATA_REQ;
tp->data_req.MORE_flag = 0;
putnext(q, bp);
return (0);
fail:
qprocsoff(q);
if (tmip->unbind_mp != NULL) {
freemsg(tmip->unbind_mp);
}
kmem_free(tmip, sizeof (struct telmod_info));
q->q_ptr = NULL;
WR(q)->q_ptr = NULL;
return (error);
}
/*
* telmodclose - just the normal streams clean-up is required.
*/
/*ARGSUSED*/
static int
telmodclose(queue_t *q, int flag, cred_t *credp)
{
struct telmod_info *tmip = (struct telmod_info *)q->q_ptr;
mblk_t *mp;
/*
* Flush any write-side data downstream. Ignoring flow
* control at this point is known to be safe because the
* M_HANGUP below poisons the stream such that no modules can
* be pushed again.
*/
while (mp = getq(WR(q)))
putnext(WR(q), mp);
/* Poison the stream head so that we can't be pushed again. */
(void) putnextctl(q, M_HANGUP);
qprocsoff(q);
if (tmip->wbufcid) {
qunbufcall(q, tmip->wbufcid);
tmip->wbufcid = 0;
}
if (tmip->rbufcid) {
qunbufcall(q, tmip->rbufcid);
tmip->rbufcid = 0;
}
if (tmip->wtimoutid) {
(void) quntimeout(q, tmip->wtimoutid);
tmip->wtimoutid = 0;
}
if (tmip->rtimoutid) {
(void) quntimeout(q, tmip->rtimoutid);
tmip->rtimoutid = 0;
}
if (tmip->unbind_mp != NULL) {
freemsg(tmip->unbind_mp);
}
kmem_free(q->q_ptr, sizeof (struct telmod_info));
q->q_ptr = WR(q)->q_ptr = NULL;
return (0);
}
/*
* telmodrput:
* Be sure to preserve data order. If the daemon is waiting for additional
* data (TEL_GETBLK state) forward new data. Otherwise, apply normal
* telnet protocol processing to M_DATA. Take notice of TLI messages
* indicating connection tear-down, and change them into M_HANGUP's.
*/
static void
telmodrput(queue_t *q, mblk_t *mp)
{
mblk_t *newmp;
struct telmod_info *tmip = (struct telmod_info *)q->q_ptr;
union T_primitives *tip;
if ((mp->b_datap->db_type < QPCTL) &&
((q->q_first) || ((tmip->flags & TEL_STOPPED) &&
!(tmip->flags & TEL_GETBLK)) || !canputnext(q))) {
(void) putq(q, mp);
return;
}
switch (mp->b_datap->db_type) {
case M_DATA:
/*
* If the user level daemon requests for 1 more
* block of data (needs more data for protocol processing)
* create a M_CTL message block with the mp.
*/
is_mdata:
if (tmip->flags & TEL_GETBLK) {
if ((newmp = allocb(sizeof (char), BPRI_MED)) == NULL) {
recover(q, mp, msgdsize(mp));
return;
}
newmp->b_datap->db_type = M_CTL;
newmp->b_wptr = newmp->b_rptr + 1;
*(newmp->b_rptr) = M_CTL_MAGIC_NUMBER;
newmp->b_cont = mp;
tmip->flags &= ~TEL_GETBLK;
noenable(q);
tmip->flags |= TEL_STOPPED;
putnext(q, newmp);
break;
}
/*
* call the protocol parsing routine which processes
* the data part of the message block first. Then it
* handles protocol and CR/LF processing.
* If an error is found inside allocb/dupb, recover
* routines inside rcv_parse will queue up the
* original message block in its service queue.
*/
(void) rcv_parse(q, mp);
break;
case M_FLUSH:
/*
* Since M_FLUSH came from TCP, we mark it bound for
* daemon, not tty. This only happens when TCP expects
* to do a connection reset.
*/
mp->b_flag |= MSGMARK;
if (*mp->b_rptr & FLUSHR)
flushq(q, FLUSHALL);
putnext(q, mp);
break;
case M_PCSIG:
case M_ERROR:
if (tmip->flags & TEL_GETBLK)
tmip->flags &= ~TEL_GETBLK;
/* FALLTHRU */
case M_IOCACK:
case M_IOCNAK:
case M_SETOPTS:
putnext(q, mp);
break;
case M_PROTO:
case M_PCPROTO:
if (tmip->flags & TEL_GETBLK)
tmip->flags &= ~TEL_GETBLK;
tip = (union T_primitives *)mp->b_rptr;
switch (tip->type) {
case T_ORDREL_IND:
case T_DISCON_IND:
/* Make into M_HANGUP and putnext */
ASSERT(mp->b_cont == NULL);
mp->b_datap->db_type = M_HANGUP;
mp->b_wptr = mp->b_rptr;
if (mp->b_cont) {
freemsg(mp->b_cont);
mp->b_cont = NULL;
}
/*
* If we haven't already, send T_UNBIND_REQ to prevent
* TCP from going into "BOUND" state and locking up the
* port.
*/
if (tip->type == T_DISCON_IND && tmip->unbind_mp !=
NULL) {
putnext(q, mp);
qreply(q, tmip->unbind_mp);
tmip->unbind_mp = NULL;
} else {
putnext(q, mp);
}
break;
case T_EXDATA_IND:
case T_DATA_IND: /* conform to TPI, but never happens */
newmp = mp->b_cont;
freeb(mp);
mp = newmp;
if (mp) {
ASSERT(mp->b_datap->db_type == M_DATA);
if (msgdsize(mp) != 0) {
goto is_mdata;
}
freemsg(mp);
}
break;
/*
* We only get T_OK_ACK when we issue the unbind, and it can
* be ignored safely.
*/
case T_OK_ACK:
ASSERT(tmip->unbind_mp == NULL);
freemsg(mp);
break;
default:
#ifdef DEBUG
cmn_err(CE_NOTE,
"telmodrput: unexpected TLI primitive msg "
"type 0x%x", tip->type);
#endif
freemsg(mp);
}
break;
default:
#ifdef DEBUG
cmn_err(CE_NOTE,
"telmodrput: unexpected msg type 0x%x",
mp->b_datap->db_type);
#endif
freemsg(mp);
}
}
/*
* telmodrsrv:
* Mostly we end up here because of M_DATA processing delayed due to flow
* control or lack of memory. XXX.sparker: TLI primitives here?
*/
static void
telmodrsrv(queue_t *q)
{
mblk_t *mp, *newmp;
struct telmod_info *tmip = (struct telmod_info *)q->q_ptr;
union T_primitives *tip;
while ((mp = getq(q)) != NULL) {
if (((tmip->flags & TEL_STOPPED) &&
!(tmip->flags & TEL_GETBLK)) || !canputnext(q)) {
(void) putbq(q, mp);
return;
}
switch (mp->b_datap->db_type) {
case M_DATA:
is_mdata:
if (tmip->flags & TEL_GETBLK) {
if ((newmp = allocb(sizeof (char),
BPRI_MED)) == NULL) {
recover(q, mp, msgdsize(mp));
return;
}
newmp->b_datap->db_type = M_CTL;
newmp->b_wptr = newmp->b_rptr + 1;
*(newmp->b_rptr) = M_CTL_MAGIC_NUMBER;
newmp->b_cont = mp;
tmip->flags &= ~TEL_GETBLK;
noenable(q);
tmip->flags |= TEL_STOPPED;
putnext(q, newmp);
break;
}
if (!rcv_parse(q, mp)) {
return;
}
break;
case M_PROTO:
tip = (union T_primitives *)mp->b_rptr;
/*
* Unless the M_PROTO message indicates data, clear
* TEL_GETBLK so that we stop passing our messages
* up to the telnet daemon.
*/
if (tip->type != T_DATA_IND &&
tip->type != T_EXDATA_IND)
tmip->flags &= ~TEL_GETBLK;
switch (tip->type) {
case T_ORDREL_IND:
case T_DISCON_IND:
/* Make into M_HANGUP and putnext */
ASSERT(mp->b_cont == NULL);
mp->b_datap->db_type = M_HANGUP;
mp->b_wptr = mp->b_rptr;
if (mp->b_cont) {
freemsg(mp->b_cont);
mp->b_cont = NULL;
}
/*
* If we haven't already, send T_UNBIND_REQ
* to prevent TCP from going into "BOUND"
* state and locking up the port.
*/
if (tip->type == T_DISCON_IND &&
tmip->unbind_mp != NULL) {
putnext(q, mp);
qreply(q, tmip->unbind_mp);
tmip->unbind_mp = NULL;
} else {
putnext(q, mp);
}
break;
case T_DATA_IND: /* conform to TPI, but never happens */
case T_EXDATA_IND:
newmp = mp->b_cont;
freeb(mp);
mp = newmp;
if (mp) {
ASSERT(mp->b_datap->db_type == M_DATA);
if (msgdsize(mp) != 0) {
goto is_mdata;
}
freemsg(mp);
}
break;
/*
* We only get T_OK_ACK when we issue the unbind, and
* it can be ignored safely.
*/
case T_OK_ACK:
ASSERT(tmip->unbind_mp == NULL);
freemsg(mp);
break;
default:
#ifdef DEBUG
cmn_err(CE_NOTE,
"telmodrsrv: unexpected TLI primitive "
"msg type 0x%x", tip->type);
#endif
freemsg(mp);
}
break;
case M_SETOPTS:
putnext(q, mp);
break;
default:
#ifdef DEBUG
cmn_err(CE_NOTE,
"telmodrsrv: unexpected msg type 0x%x",
mp->b_datap->db_type);
#endif
freemsg(mp);
}
}
}
/*
* telmodwput:
* M_DATA is processed and forwarded if we aren't stopped awaiting the daemon
* to process something. M_CTL's are data from the daemon bound for the
* network. We forward them immediately. There are two classes of ioctl's
* we must handle here also. One is ioctl's forwarded by ptem which we
* ignore. The other is ioctl's issued by the daemon to control us.
* Process them appropriately. M_PROTO's we pass along, figuring they are
* are TPI operations for TCP. M_FLUSH requires careful processing, since
* telnet cannot tolerate flushing its protocol requests. Also the flushes
* can be running either daemon<->TCP or application<->telmod. We must
* carefully deal with this.
*/
static void
telmodwput(
queue_t *q, /* Pointer to the read queue */
mblk_t *mp) /* Pointer to current message block */
{
struct telmod_info *tmip;
struct iocblk *ioc;
mblk_t *savemp;
int rw;
int error;
tmip = (struct telmod_info *)q->q_ptr;
switch (mp->b_datap->db_type) {
case M_DATA:
if (!canputnext(q) || (tmip->flags & TEL_STOPPED) ||
(q->q_first)) {
noenable(q);
(void) putq(q, mp);
break;
}
/*
* This routine parses data generating from ptm side.
* Insert a null character if carraige return
* is not followed by line feed unless we are in binary mode.
* Also, duplicate IAC if found in the data.
*/
(void) snd_parse(q, mp);
break;
case M_CTL:
if (((mp->b_wptr - mp->b_rptr) == 1) &&
(*(mp->b_rptr) == M_CTL_MAGIC_NUMBER)) {
savemp = mp->b_cont;
freeb(mp);
mp = savemp;
}
putnext(q, mp);
break;
case M_IOCTL:
ioc = (struct iocblk *)mp->b_rptr;
switch (ioc->ioc_cmd) {
/*
* This ioctl is issued by user level daemon to
* request one more message block to process protocol
*/
case TEL_IOC_GETBLK:
if (!(tmip->flags & TEL_STOPPED)) {
miocnak(q, mp, 0, EINVAL);
break;
}
tmip->flags |= TEL_GETBLK;
qenable(RD(q));
enableok(RD(q));
miocack(q, mp, 0, 0);
break;
/*
* This ioctl is issued by user level daemon to reenable the
* read and write queues. This is issued during startup time
* after setting up the mux links and also after processing
* the protocol. It is also issued after each time an
* an unrecognized telnet option is forwarded to the daemon.
*/
case TEL_IOC_ENABLE:
/*
* Send negative ack if TEL_STOPPED flag is not set
*/
if (!(tmip->flags & TEL_STOPPED)) {
miocnak(q, mp, 0, EINVAL);
break;
}
tmip->flags &= ~TEL_STOPPED;
if (mp->b_cont) {
(void) putbq(RD(q), mp->b_cont);
mp->b_cont = 0;
}
qenable(RD(q));
enableok(RD(q));
qenable(q);
enableok(q);
miocack(q, mp, 0, 0);
break;
/*
* Set binary/normal mode for input and output
* according to the instructions from the daemon.
*/
case TEL_IOC_MODE:
error = miocpullup(mp, sizeof (uchar_t));
if (error != 0) {
miocnak(q, mp, 0, error);
break;
}
tmip->flags |= *(mp->b_cont->b_rptr) &
(TEL_BINARY_IN|TEL_BINARY_OUT);
miocack(q, mp, 0, 0);
break;
#ifdef DEBUG
case TCSETAF:
case TCSETSF:
case TCSETA:
case TCSETAW:
case TCSETS:
case TCSETSW:
case TCSBRK:
case TIOCSTI:
case TIOCSWINSZ:
miocnak(q, mp, 0, EINVAL);
break;
#endif
case CRYPTPASSTHRU:
error = miocpullup(mp, sizeof (uchar_t));
if (error != 0) {
miocnak(q, mp, 0, error);
break;
}
if (*(mp->b_cont->b_rptr) == 0x01)
tmip->flags |= TEL_IOCPASSTHRU;
else
tmip->flags &= ~TEL_IOCPASSTHRU;
miocack(q, mp, 0, 0);
break;
default:
if (tmip->flags & TEL_IOCPASSTHRU) {
putnext(q, mp);
} else {
#ifdef DEBUG
cmn_err(CE_NOTE,
"telmodwput: unexpected ioctl type 0x%x",
ioc->ioc_cmd);
#endif
miocnak(q, mp, 0, EINVAL);
}
break;
}
break;
case M_FLUSH:
/*
* Flushing is tricky: We try to flush all we can, but certain
* data cannot be flushed. Telnet protocol sequences cannot
* be flushed. So, TCP's queues cannot be flushed since we
* cannot tell what might be telnet protocol data. Then we
* must take care to create and forward out-of-band data
* indicating the flush to the far side.
*/
rw = *mp->b_rptr;
if (rw & FLUSHR) {
/*
* We cannot flush our read queue, since there may
* be telnet protocol bits in the queue, awaiting
* processing. However, once it leaves this module
* it's guaranteed that all protocol data is in
* M_CTL, so we do flush read data beyond us, expecting
* them (actually logindmux) to do FLUSHDATAs also.
*/
*mp->b_rptr = rw & ~FLUSHW;
qreply(q, mp);
} else {
freemsg(mp);
}
if (rw & FLUSHW) {
/*
* Since all telnet protocol data comes from the
* daemon, stored as M_CTL messages, flushq will
* do exactly what's needed: Flush bytes which do
* not have telnet protocol data.
*/
flushq(q, FLUSHDATA);
}
break;
case M_PCPROTO:
putnext(q, mp);
break;
case M_PROTO:
/* We may receive T_DISCON_REQ from the mux */
if (!canputnext(q) || q->q_first != NULL)
(void) putq(q, mp);
else
putnext(q, mp);
break;
default:
#ifdef DEBUG
cmn_err(CE_NOTE,
"telmodwput: unexpected msg type 0x%x",
mp->b_datap->db_type);
#endif
freemsg(mp);
break;
}
}
/*
* telmodwsrv - module write service procedure
*/
static void
telmodwsrv(queue_t *q)
{
mblk_t *mp, *savemp;
struct telmod_info *tmip = (struct telmod_info *)q->q_ptr;
while ((mp = getq(q)) != NULL) {
if (!canputnext(q)) {
ASSERT(mp->b_datap->db_type < QPCTL);
(void) putbq(q, mp);
return;
}
switch (mp->b_datap->db_type) {
case M_DATA:
if (tmip->flags & TEL_STOPPED) {
(void) putbq(q, mp);
return;
}
/*
* Insert a null character if carraige return
* is not followed by line feed
*/
if (!snd_parse(q, mp)) {
return;
}
break;
case M_CTL:
if (((mp->b_wptr - mp->b_rptr) == 1) &&
(*(mp->b_rptr) == M_CTL_MAGIC_NUMBER)) {
savemp = mp->b_cont;
freeb(mp);
mp = savemp;
}
putnext(q, mp);
break;
case M_PROTO:
putnext(q, mp);
break;
default:
#ifdef DEBUG
cmn_err(CE_NOTE,
"telmodwsrv: unexpected msg type 0x%x",
mp->b_datap->db_type);
#endif
freemsg(mp);
}
}
}
/*
* This routine is called from read put/service procedure and parses
* message block to check for telnet protocol by detecting an IAC.
* The routine processes the data part of the message block first and
* then sends protocol followed after IAC to the telnet daemon. The
* routine also processes CR/LF by eliminating LF/NULL followed after CR.
*
* Since the code to do this with streams mblks is complicated, some
* explanations are in order. If an IAC is found, a dupb() is done,
* and the pointers are adjusted to create two streams message. The
* (possibly empty) first message contains preceeding data, and the
* second begins with the IAC and contains the rest of the streams
* message.
*
* The variables:
* datamp: Points to the head of a chain of mblks containing data
* which requires no expansion, and can be forwarded directly
* to the pty.
* prevmp: Points to the last mblk on the datamp chain, used to add
* to the chain headed by datamp.
* newmp: When an M_CTL header is required, this pointer references
* that "header" mblk.
* protomp: When an IAC is discovered, a dupb() is done on the first mblk
* containing an IAC. protomp points to this dup'ed mblk.
* This mblk is eventually forwarded to the daemon.
*/
static int
rcv_parse(queue_t *q, mblk_t *mp)
{
mblk_t *protomp, *newmp, *datamp, *prevmp;
unsigned char *tmp;
size_t msgsize;
struct telmod_info *tmip = (struct telmod_info *)q->q_ptr;
datamp = mp;
prevmp = protomp = 0;
while (mp) {
/*
* If the mblk is empty, just continue scanning.
*/
if (mp->b_rptr == mp->b_wptr) {
prevmp = mp;
mp = mp->b_cont;
continue;
}
/*
* First check to see if we have received CR and are checking
* for a following LF/NULL. If so, do what's necessary to
* trim the LF/NULL. This case is for when the LF/NULL is
* at the beginning of a subsequent mblk.
*/
if (!(tmip->flags & TEL_BINARY_IN) &&
(tmip->flags & TEL_CRRCV)) {
if ((*mp->b_rptr == '\n') || (*mp->b_rptr == NULL)) {
if (mp->b_wptr == (mp->b_rptr + 1)) {
tmip->flags &= ~TEL_CRRCV;
if (prevmp) {
prevmp->b_cont = mp->b_cont;
freeb(mp);
mp = prevmp->b_cont;
continue;
} else {
datamp = mp->b_cont;
freeb(mp);
if (datamp == NULL) {
/*
* Message contained
* only a '\0' after
* a '\r' in a previous
* message, so we can
* read more, even
* though we have
* nothing to putnext.
*/
return (1);
} else {
mp = datamp;
continue;
}
}
}
mp->b_rptr += 1;
}
tmip->flags &= ~TEL_CRRCV;
}
tmp = mp->b_rptr;
/*
* Now scan through the entire message block, for IACs
* and CR characters, which need processing.
*/
while (tmp < mp->b_wptr) {
if (tmp[0] == IAC) {
/*
* Telnet protocol - parse it now
* process data part of mblk
* before sending the protocol.
*/
if (tmp > mp->b_rptr) {
if ((protomp = dupb(mp)) == NULL) {
msgsize = msgdsize(datamp);
recover(q, datamp, msgsize);
return (0);
}
ASSERT(tmp >= mp->b_datap->db_base);
ASSERT(tmp <= mp->b_datap->db_lim);
ASSERT(tmp >=
protomp->b_datap->db_base);
ASSERT(tmp <= protomp->b_datap->db_lim);
mp->b_wptr = tmp;
protomp->b_rptr = tmp;
protomp->b_cont = mp->b_cont;
mp->b_cont = 0;
if (prevmp)
prevmp->b_cont = mp;
} else {
protomp = mp;
if (prevmp)
prevmp->b_cont = 0;
else
datamp = 0;
}
if (datamp) {
putnext(q, datamp);
}
/*
* create a 1 byte M_CTL message block with
* protomp and send it down.
*/
if ((newmp = allocb(sizeof (char),
BPRI_MED)) == NULL) {
/*
* Save the dup'ed mp containing
* the protocol information which
* we couldn't get an M_CTL header
* for.
*/
msgsize = msgdsize(protomp);
recover(q, protomp, msgsize);
return (0);
}
newmp->b_datap->db_type = M_CTL;
newmp->b_wptr = newmp->b_rptr + 1;
*(newmp->b_rptr) = M_CTL_MAGIC_NUMBER;
newmp->b_cont = protomp;
noenable(q);
tmip->flags |= TEL_STOPPED;
putnext(q, newmp);
return (0);
}
if (!(tmip->flags & TEL_BINARY_IN)) {
/*
* Set TEL_CRRCV flag if last character is CR
*/
if ((tmp == (mp->b_wptr - 1)) &&
(tmp[0] == '\r')) {
tmip->flags |= TEL_CRRCV;
break;
}
/*
* If CR is followed by LF/NULL, get rid of
* LF/NULL and realign the message block.
*/
if ((tmp[0] == '\r') && ((tmp[1] == '\n') ||
(tmp[1] == NULL))) {
/*
* If CR is in the middle of a block,
* we need to get rid of LF and join
* the two pieces together.
*/
if (mp->b_wptr > (tmp + 2)) {
bcopy(tmp + 2, tmp + 1,
(mp->b_wptr - tmp - 2));
mp->b_wptr -= 1;
} else {
mp->b_wptr = tmp + 1;
}
if (prevmp)
prevmp->b_cont = mp;
}
}
tmp++;
}
prevmp = mp;
mp = mp->b_cont;
}
putnext(q, datamp);
return (1);
}
/*
* This routine is called from write put/service procedures and processes
* CR-LF. If CR is not followed by LF, it inserts a NULL character if we are
* in non binary mode. Also, duplicate IAC(0xFF) if found in the mblk.
* This routine is pessimistic: It pre-allocates a buffer twice the size
* of the incoming message, which is the maximum size a message can become
* after IAC expansion.
*
* savemp: Points at the original message, so it can be freed when
* processing is complete.
* mp: The current point of scanning the message.
* newmp: New message being created with the processed output.
*/
static int
snd_parse(queue_t *q, mblk_t *mp)
{
unsigned char *tmp, *tmp1;
mblk_t *newmp, *savemp;
struct telmod_info *tmip = (struct telmod_info *)q->q_ptr;
size_t size = msgdsize(mp);
savemp = mp;
if (size == 0) {
putnext(q, mp);
return (1);
}
/*
* Extra byte to allocb() takes care of the case when there was
* a '\r' at the end of the previous message and there's a '\r'
* at the beginning of the current message.
*/
if ((newmp = allocb((2 * size)+1, BPRI_MED)) == NULL) {
recover(q, mp, (2 * size)+1);
return (0);
}
newmp->b_datap->db_type = M_DATA;
tmp1 = newmp->b_rptr;
while (mp) {
if (!(tmip->flags & TEL_BINARY_OUT) &&
(tmip->flags & TEL_CRSND)) {
if (*(mp->b_rptr) != '\n')
*tmp1++ = NULL;
tmip->flags &= ~TEL_CRSND;
}
tmp = mp->b_rptr;
while (tmp < mp->b_wptr) {
if (!(tmip->flags & TEL_BINARY_OUT)) {
*tmp1++ = *tmp;
if ((tmp == (mp->b_wptr - 1)) &&
(tmp[0] == '\r')) {
tmip->flags |= TEL_CRSND;
break;
}
if ((tmp[0] == '\r') &&
(tmp1 == newmp->b_wptr)) {
/* XXX.sparker: can't happen */
tmip->flags |= TEL_CRSND;
break;
}
if ((tmp[0] == '\r') && (tmp[1] != '\n')) {
*tmp1++ = NULL;
}
} else
*tmp1++ = *tmp;
if (tmp[0] == IAC) {
*tmp1++ = IAC;
}
tmp++;
}
mp = mp->b_cont;
}
newmp->b_wptr = tmp1;
putnext(q, newmp);
freemsg(savemp);
return (1);
}
static void
telmod_timer(void *arg)
{
queue_t *q = arg;
struct telmod_info *tmip = (struct telmod_info *)q->q_ptr;
ASSERT(tmip);
if (q->q_flag & QREADR) {
ASSERT(tmip->rtimoutid);
tmip->rtimoutid = 0;
} else {
ASSERT(tmip->wtimoutid);
tmip->wtimoutid = 0;
}
enableok(q);
qenable(q);
}
static void
telmod_buffer(void *arg)
{
queue_t *q = arg;
struct telmod_info *tmip = (struct telmod_info *)q->q_ptr;
ASSERT(tmip);
if (q->q_flag & QREADR) {
ASSERT(tmip->rbufcid);
tmip->rbufcid = 0;
} else {
ASSERT(tmip->wbufcid);
tmip->wbufcid = 0;
}
enableok(q);
qenable(q);
}
static void
recover(queue_t *q, mblk_t *mp, size_t size)
{
bufcall_id_t bid;
timeout_id_t tid;
struct telmod_info *tmip = (struct telmod_info *)q->q_ptr;
ASSERT(mp->b_datap->db_type < QPCTL);
noenable(q);
(void) putbq(q, mp);
/*
* Make sure there is at most one outstanding request per queue.
*/
if (q->q_flag & QREADR) {
if (tmip->rtimoutid || tmip->rbufcid) {
return;
}
} else {
if (tmip->wtimoutid || tmip->wbufcid) {
return;
}
}
if (!(bid = qbufcall(RD(q), size, BPRI_MED, telmod_buffer, q))) {
tid = qtimeout(RD(q), telmod_timer, q, SIMWAIT);
if (q->q_flag & QREADR)
tmip->rtimoutid = tid;
else
tmip->wtimoutid = tid;
} else {
if (q->q_flag & QREADR)
tmip->rbufcid = bid;
else
tmip->wbufcid = bid;
}
}