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