/* * 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; } }