| /* |
| * 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. |
| * Copyright (c) 2016 by Delphix. All rights reserved. |
| */ |
| |
| #pragma ident "%Z%%M% %I% %E% SMI" |
| |
| /* |
| * This module implements the services provided by the rlogin daemon |
| * after the connection is set up. Mainly this means responding to |
| * interrupts and window size changes. It begins operation in "disabled" |
| * state, and sends a T_DATA_REQ to the daemon to indicate that it is |
| * in place and ready to be enabled. The daemon can then know when all |
| * data which sneaked passed rlmod (before it was pushed) has been received. |
| * The daemon may process this data, or send data back to be inserted in |
| * the read queue at the head with the RL_IOC_ENABLE ioctl. |
| */ |
| |
| #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/conf.h> |
| #include <sys/debug.h> |
| #include <sys/modctl.h> |
| #include <sys/vtrace.h> |
| #include <sys/rlioctl.h> |
| #include <sys/termios.h> |
| #include <sys/termio.h> |
| #include <sys/byteorder.h> |
| #include <sys/cmn_err.h> |
| #include <sys/cryptmod.h> |
| |
| extern struct streamtab rloginmodinfo; |
| |
| static struct fmodsw fsw = { |
| "rlmod", |
| &rloginmodinfo, |
| D_MTQPAIR | D_MP |
| }; |
| |
| /* |
| * Module linkage information for the kernel. |
| */ |
| |
| static struct modlstrmod modlstrmod = { |
| &mod_strmodops, |
| "rloginmod module", |
| &fsw |
| }; |
| |
| static struct modlinkage modlinkage = { |
| MODREV_1, &modlstrmod, NULL |
| }; |
| |
| |
| int |
| _init(void) |
| { |
| return (mod_install(&modlinkage)); |
| } |
| |
| int |
| _fini(void) |
| { |
| return (mod_remove(&modlinkage)); |
| } |
| |
| int |
| _info(struct modinfo *modinfop) |
| { |
| return (mod_info(&modlinkage, modinfop)); |
| } |
| |
| struct rlmod_info; /* forward reference for function prototype */ |
| |
| static int rlmodopen(queue_t *, dev_t *, int, int, cred_t *); |
| static int rlmodclose(queue_t *, int, cred_t *); |
| static int rlmodrput(queue_t *, mblk_t *); |
| static int rlmodrsrv(queue_t *); |
| static int rlmodwput(queue_t *, mblk_t *); |
| static int rlmodwsrv(queue_t *); |
| static int rlmodrmsg(queue_t *, mblk_t *); |
| static mblk_t *make_expmblk(char); |
| static int rlwinctl(queue_t *, mblk_t *); |
| static mblk_t *rlwinsetup(queue_t *, mblk_t *, unsigned char *); |
| |
| static void rlmod_timer(void *); |
| static void rlmod_buffer(void *); |
| static boolean_t tty_flow(queue_t *, struct rlmod_info *, mblk_t *); |
| static boolean_t rlmodwioctl(queue_t *, mblk_t *); |
| static void recover(queue_t *, mblk_t *, size_t); |
| static void recover1(queue_t *, size_t); |
| |
| #define RLMOD_ID 106 |
| #define SIMWAIT (1*hz) |
| |
| /* |
| * Stream module data structure definitions. |
| * generally pushed onto tcp by rlogin daemon |
| * |
| */ |
| static struct module_info rloginmodiinfo = { |
| RLMOD_ID, /* module id number */ |
| "rlmod", /* module name */ |
| 0, /* minimum packet size */ |
| INFPSZ, /* maximum packet size */ |
| 512, /* hi-water mark */ |
| 256 /* lo-water mark */ |
| }; |
| |
| static struct qinit rloginmodrinit = { |
| rlmodrput, |
| rlmodrsrv, |
| rlmodopen, |
| rlmodclose, |
| nulldev, |
| &rloginmodiinfo, |
| NULL |
| }; |
| |
| static struct qinit rloginmodwinit = { |
| rlmodwput, |
| rlmodwsrv, |
| NULL, |
| NULL, |
| nulldev, |
| &rloginmodiinfo, |
| NULL |
| }; |
| |
| struct streamtab rloginmodinfo = { |
| &rloginmodrinit, |
| &rloginmodwinit, |
| NULL, |
| NULL |
| }; |
| |
| /* |
| * Per-instance state struct for the rloginmod module. |
| */ |
| struct rlmod_info |
| { |
| int flags; |
| bufcall_id_t wbufcid; |
| bufcall_id_t rbufcid; |
| timeout_id_t wtimoutid; |
| timeout_id_t rtimoutid; |
| int rl_expdat; |
| int stopmode; |
| mblk_t *unbind_mp; |
| char startc; |
| char stopc; |
| char oobdata[1]; |
| mblk_t *wndw_sz_hd_mp; |
| }; |
| |
| /* |
| * Flag used in flags |
| */ |
| #define RL_DISABLED 0x1 |
| #define RL_IOCPASSTHRU 0x2 |
| |
| /*ARGSUSED*/ |
| static void |
| dummy_callback(void *arg) |
| {} |
| |
| /* |
| * rlmodopen - open routine gets called when the |
| * module gets pushed onto the stream. |
| */ |
| /*ARGSUSED*/ |
| static int |
| rlmodopen(queue_t *q, dev_t *devp, int oflag, int sflag, cred_t *cred) |
| { |
| struct rlmod_info *rmip; |
| union T_primitives *tp; |
| mblk_t *bp; |
| int error; |
| |
| if (sflag != MODOPEN) |
| return (EINVAL); |
| |
| if (q->q_ptr != NULL) { |
| /* It's already attached. */ |
| return (0); |
| } |
| |
| /* |
| * Allocate state structure. |
| */ |
| rmip = kmem_zalloc(sizeof (*rmip), KM_SLEEP); |
| |
| /* |
| * Cross-link. |
| */ |
| q->q_ptr = rmip; |
| WR(q)->q_ptr = rmip; |
| rmip->rl_expdat = 0; |
| rmip->stopmode = TIOCPKT_DOSTOP; |
| rmip->startc = CTRL('q'); |
| rmip->stopc = CTRL('s'); |
| rmip->oobdata[0] = (char)TIOCPKT_WINDOW; |
| rmip->wndw_sz_hd_mp = NULL; |
| /* |
| * Allow only non-M_DATA blocks to pass up to in.rlogind until |
| * it is ready for M_DATA (indicated by RL_IOC_ENABLE). |
| */ |
| rmip->flags |= RL_DISABLED; |
| |
| 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 ((rmip->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); |
| } |
| rmip->unbind_mp->b_wptr = rmip->unbind_mp->b_rptr + |
| sizeof (struct T_unbind_req); |
| rmip->unbind_mp->b_datap->db_type = M_PROTO; |
| tp = (union T_primitives *)rmip->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 the 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 (rmip->unbind_mp != NULL) { |
| freemsg(rmip->unbind_mp); |
| } |
| kmem_free(rmip, sizeof (struct rlmod_info)); |
| q->q_ptr = NULL; |
| WR(q)->q_ptr = NULL; |
| return (error); |
| } |
| |
| |
| /* |
| * rlmodclose - This routine gets called when the module |
| * gets popped off of the stream. |
| */ |
| |
| /*ARGSUSED*/ |
| static int |
| rlmodclose(queue_t *q, int flag, cred_t *credp) |
| { |
| struct rlmod_info *rmip = (struct rlmod_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 (rmip->wbufcid) { |
| qunbufcall(q, rmip->wbufcid); |
| rmip->wbufcid = 0; |
| } |
| if (rmip->rbufcid) { |
| qunbufcall(q, rmip->rbufcid); |
| rmip->rbufcid = 0; |
| } |
| if (rmip->wtimoutid) { |
| (void) quntimeout(q, rmip->wtimoutid); |
| rmip->wtimoutid = 0; |
| } |
| if (rmip->rtimoutid) { |
| (void) quntimeout(q, rmip->rtimoutid); |
| rmip->rtimoutid = 0; |
| } |
| |
| if (rmip->unbind_mp != NULL) { |
| freemsg(rmip->unbind_mp); |
| } |
| |
| if (rmip->wndw_sz_hd_mp != NULL) { |
| freemsg(rmip->wndw_sz_hd_mp); |
| } |
| |
| kmem_free(q->q_ptr, sizeof (struct rlmod_info)); |
| q->q_ptr = WR(q)->q_ptr = NULL; |
| return (0); |
| } |
| |
| /* |
| * rlmodrput - Module read queue put procedure. |
| * This is called from the module or |
| * driver downstream. |
| */ |
| |
| static int |
| rlmodrput(queue_t *q, mblk_t *mp) |
| { |
| struct rlmod_info *rmip = (struct rlmod_info *)q->q_ptr; |
| union T_primitives *tip; |
| |
| TRACE_2(TR_FAC_RLOGINP, TR_RLOGINP_RPUT_IN, "rlmodrput start: " |
| "q %p, mp %p", q, mp); |
| |
| |
| /* if low (normal) priority... */ |
| if ((mp->b_datap->db_type < QPCTL) && |
| /* ...and data is already queued... */ |
| ((q->q_first) || |
| /* ...or currently disabled and this is M_DATA... */ |
| ((rmip->flags & RL_DISABLED) && |
| (mp->b_datap->db_type == M_DATA)))) { |
| /* ...delay delivery of the message */ |
| (void) putq(q, mp); |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_RPUT_OUT, |
| "rlmodrput end: q %p, mp %p, %s", q, mp, "flow"); |
| return (0); |
| } |
| |
| switch (mp->b_datap->db_type) { |
| |
| case M_PROTO: |
| case M_PCPROTO: |
| tip = (union T_primitives *)mp->b_rptr; |
| switch (tip->type) { |
| |
| case T_ORDREL_IND: |
| case T_DISCON_IND: |
| /* Make into M_HANGUP and putnext */ |
| 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 && rmip->unbind_mp != |
| NULL) { |
| putnext(q, mp); |
| qreply(q, rmip->unbind_mp); |
| rmip->unbind_mp = NULL; |
| } else { |
| putnext(q, mp); |
| } |
| break; |
| |
| /* |
| * We only get T_OK_ACK when we issue the unbind, and it can |
| * be ignored safely. |
| */ |
| case T_OK_ACK: |
| ASSERT(rmip->unbind_mp == NULL); |
| freemsg(mp); |
| break; |
| |
| default: |
| cmn_err(CE_NOTE, |
| "rlmodrput: got 0x%x type M_PROTO/M_PCPROTO msg", |
| tip->type); |
| freemsg(mp); |
| } |
| break; |
| |
| case M_DATA: |
| if (canputnext(q) && q->q_first == NULL) { |
| (void) rlmodrmsg(q, mp); |
| } else { |
| (void) putq(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: |
| case M_IOCACK: |
| case M_IOCNAK: |
| case M_SETOPTS: |
| if (mp->b_datap->db_type <= QPCTL && !canputnext(q)) |
| (void) putq(q, mp); |
| else |
| putnext(q, mp); |
| break; |
| |
| default: |
| #ifdef DEBUG |
| cmn_err(CE_NOTE, "rlmodrput: unexpected msg type 0x%x", |
| mp->b_datap->db_type); |
| #endif |
| freemsg(mp); |
| } |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_RPUT_OUT, "rlmodrput end: q %p, " |
| "mp %p, %s", q, mp, "done"); |
| return (0); |
| } |
| |
| /* |
| * rlmodrsrv - module read service procedure |
| */ |
| static int |
| rlmodrsrv(queue_t *q) |
| { |
| mblk_t *mp; |
| struct rlmod_info *rmip = (struct rlmod_info *)q->q_ptr; |
| union T_primitives *tip; |
| |
| TRACE_1(TR_FAC_RLOGINP, TR_RLOGINP_RSRV_IN, "rlmodrsrv start: " |
| "q %p", q); |
| while ((mp = getq(q)) != NULL) { |
| |
| switch (mp->b_datap->db_type) { |
| case M_DATA: |
| if (rmip->flags & RL_DISABLED) { |
| (void) putbq(q, mp); |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_RSRV_OUT, |
| "rlmodrsrv end: q %p, mp %p, %s", q, mp, |
| "disabled"); |
| return (0); |
| } |
| if (!canputnext(q)) { |
| (void) putbq(q, mp); |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_RSRV_OUT, |
| "rlmodrsrv end: q %p, mp %p, %s", |
| q, mp, "!canputnext"); |
| return (0); |
| } |
| if (!rlmodrmsg(q, mp)) { |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_RSRV_OUT, |
| "rlmodrsrv end: q %p, mp %p, %s", |
| q, mp, "!rlmodrmsg"); |
| return (0); |
| } |
| break; |
| |
| case M_PROTO: |
| tip = (union T_primitives *)mp->b_rptr; |
| switch (tip->type) { |
| |
| case T_ORDREL_IND: |
| case T_DISCON_IND: |
| /* Make into M_HANGUP and putnext */ |
| 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 && |
| rmip->unbind_mp != NULL) { |
| putnext(q, mp); |
| qreply(q, rmip->unbind_mp); |
| rmip->unbind_mp = NULL; |
| } else { |
| putnext(q, mp); |
| } |
| break; |
| |
| /* |
| * We only get T_OK_ACK when we issue the unbind, and |
| * it can be ignored safely. |
| */ |
| case T_OK_ACK: |
| ASSERT(rmip->unbind_mp == NULL); |
| freemsg(mp); |
| break; |
| |
| default: |
| cmn_err(CE_NOTE, |
| "rlmodrsrv: got 0x%x type PROTO msg", |
| tip->type); |
| freemsg(mp); |
| } |
| break; |
| |
| case M_SETOPTS: |
| if (!canputnext(q)) { |
| (void) putbq(q, mp); |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_RSRV_OUT, |
| "rlmodrsrv end: q %p, mp %p, %s", |
| q, mp, "!canputnext M_SETOPTS"); |
| return (0); |
| } |
| putnext(q, mp); |
| break; |
| |
| default: |
| #ifdef DEBUG |
| cmn_err(CE_NOTE, |
| "rlmodrsrv: unexpected msg type 0x%x", |
| mp->b_datap->db_type); |
| #endif |
| freemsg(mp); |
| } |
| } |
| |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_RSRV_OUT, "rlmodrsrv end: q %p, " |
| "mp %p, %s", q, mp, "empty"); |
| |
| return (0); |
| } |
| |
| /* |
| * rlmodwput - Module write queue put procedure. |
| * All non-zero messages are send downstream unchanged |
| */ |
| static int |
| rlmodwput(queue_t *q, mblk_t *mp) |
| { |
| char cntl; |
| struct rlmod_info *rmip = (struct rlmod_info *)q->q_ptr; |
| mblk_t *tmpmp; |
| int rw; |
| |
| TRACE_2(TR_FAC_RLOGINP, TR_RLOGINP_WPUT_IN, "rlmodwput start: " |
| "q %p, mp %p", q, mp); |
| |
| if (rmip->rl_expdat) { |
| /* |
| * call make_expmblk to create an expedited |
| * message block. |
| */ |
| cntl = rmip->oobdata[0] | TIOCPKT_FLUSHWRITE; |
| |
| if (!canputnext(q)) { |
| (void) putq(q, mp); |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_WPUT_OUT, |
| "rlmodwput end: q %p, mp %p, %s", |
| q, mp, "expdata && !canputnext"); |
| return (0); |
| } |
| if ((tmpmp = make_expmblk(cntl))) { |
| putnext(q, tmpmp); |
| rmip->rl_expdat = 0; |
| } else { |
| recover1(q, sizeof (mblk_t)); /* XXX.sparker */ |
| } |
| } |
| |
| if ((q->q_first || rmip->rl_expdat) && mp->b_datap->db_type < QPCTL) { |
| (void) putq(q, mp); |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_WPUT_OUT, "rlmodwput end: " |
| "q %p, mp %p, %s", q, mp, "queued data"); |
| return (0); |
| } |
| switch (mp->b_datap->db_type) { |
| |
| case M_DATA: |
| if (!canputnext(q)) |
| (void) putq(q, mp); |
| else |
| putnext(q, mp); |
| break; |
| |
| case M_FLUSH: |
| /* |
| * We must take care to create and forward out-of-band data |
| * indicating the flush to the far side. |
| */ |
| rw = *mp->b_rptr; |
| *mp->b_rptr &= ~FLUSHW; |
| qreply(q, mp); |
| if (rw & FLUSHW) { |
| /* |
| * Since all rlogin protocol data is sent in this |
| * direction as urgent data, and TCP does not flush |
| * urgent data, it is okay to actually forward this |
| * flush. (telmod cannot.) |
| */ |
| flushq(q, FLUSHDATA); |
| /* |
| * The putnextctl1() call can only fail if we're |
| * out of memory. Ideally, we might set a state |
| * bit and reschedule ourselves when memory |
| * becomes available, so we make sure not to miss |
| * sending the FLUSHW to TCP before the urgent |
| * byte. Not doing this just means in some cases |
| * a bit more trash passes before the flush takes |
| * hold. |
| */ |
| (void) putnextctl1(q, M_FLUSH, FLUSHW); |
| /* |
| * Notify peer of the write flush request. |
| */ |
| cntl = rmip->oobdata[0] | TIOCPKT_FLUSHWRITE; |
| if (!canputnext(q)) { |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_WPUT_OUT, |
| "rlmodwput end: q %p, mp %p, %s", |
| q, mp, "flushw && !canputnext"); |
| return (0); |
| } |
| if ((mp = make_expmblk(cntl)) == NULL) { |
| rmip->rl_expdat = 1; |
| recover1(q, sizeof (mblk_t)); |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_WPUT_OUT, |
| "rlmodwput end: q %p, mp %p, %s", |
| q, mp, "!make_expmblk"); |
| return (0); |
| } |
| putnext(q, mp); |
| } |
| break; |
| |
| case M_IOCTL: |
| if (!rlmodwioctl(q, mp)) |
| (void) putq(q, mp); |
| break; |
| |
| case M_PROTO: |
| switch (((union T_primitives *)mp->b_rptr)->type) { |
| case T_EXDATA_REQ: |
| case T_ORDREL_REQ: |
| case T_DISCON_REQ: |
| putnext(q, mp); |
| break; |
| |
| default: |
| #ifdef DEBUG |
| cmn_err(CE_NOTE, |
| "rlmodwput: unexpected TPI primitive 0x%x", |
| ((union T_primitives *)mp->b_rptr)->type); |
| #endif |
| freemsg(mp); |
| } |
| break; |
| |
| case M_PCPROTO: |
| if (((struct T_exdata_req *)mp->b_rptr)->PRIM_type == |
| T_DISCON_REQ) { |
| putnext(q, mp); |
| } else { |
| /* XXX.sparker Log unexpected message */ |
| freemsg(mp); |
| } |
| break; |
| |
| default: |
| #ifdef DEBUG |
| cmn_err(CE_NOTE, |
| "rlmodwput: unexpected msg type 0x%x", |
| mp->b_datap->db_type); |
| #endif |
| freemsg(mp); |
| break; |
| } |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_WPUT_OUT, "rlmodwput end: " |
| "q %p, mp %p, %s", q, mp, "done"); |
| return (0); |
| } |
| |
| /* |
| * rlmodwsrv - module write service procedure |
| */ |
| static int |
| rlmodwsrv(queue_t *q) |
| { |
| mblk_t *mp, *tmpmp; |
| char cntl; |
| struct rlmod_info *rmip = (struct rlmod_info *)q->q_ptr; |
| |
| TRACE_1(TR_FAC_RLOGINP, TR_RLOGINP_WSRV_IN, "rlmodwsrv " |
| "start: q %p", q); |
| if (rmip->rl_expdat) { |
| /* |
| * call make_expmblk to create an expedited |
| * message block. |
| */ |
| cntl = rmip->oobdata[0] | TIOCPKT_FLUSHWRITE; |
| if (!canputnext(q)) { |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_WSRV_OUT, |
| "rlmodwsrv end: q %p, mp %p, %s", |
| q, NULL, "!canputnext && expdat"); |
| return (0); |
| } |
| if ((tmpmp = make_expmblk(cntl))) { |
| putnext(q, tmpmp); |
| rmip->rl_expdat = 0; |
| } else { |
| recover1(q, sizeof (mblk_t)); |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_WSRV_OUT, |
| "rlmodwsrv end: q %p, mp %p, %s", |
| q, NULL, "!make_expmblk"); |
| return (0); |
| } |
| } |
| while ((mp = getq(q)) != NULL) { |
| |
| if (!canputnext(q) || rmip->rl_expdat) { |
| (void) putbq(q, mp); |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_WSRV_OUT, |
| "rlmodwsrv end: q %p, mp %p, %s", |
| q, mp, "!canputnext || expdat"); |
| return (0); |
| } |
| if (mp->b_datap->db_type == M_IOCTL) { |
| if (!rlmodwioctl(q, mp)) { |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_WSRV_OUT, |
| "rlmodwsrv end: q %p, mp %p, %s", |
| q, mp, "!rlmodwioctl"); |
| (void) putbq(q, mp); |
| return (0); |
| } |
| continue; |
| } |
| putnext(q, mp); |
| } |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_WSRV_OUT, "rlmodwsrv end: q %p, " |
| "mp %p, %s", q, mp, "done"); |
| return (0); |
| } |
| |
| /* |
| * This routine returns a message block with an expedited |
| * data request |
| */ |
| static mblk_t * |
| make_expmblk(char cntl) |
| { |
| mblk_t *mp; |
| mblk_t *bp; |
| struct T_exdata_req *data_req; |
| |
| bp = allocb(sizeof (struct T_exdata_req), BPRI_MED); |
| if (bp == NULL) |
| return (NULL); |
| if ((mp = allocb(sizeof (char), BPRI_MED)) == NULL) { |
| freeb(bp); |
| return (NULL); |
| } |
| bp->b_datap->db_type = M_PROTO; |
| data_req = (struct T_exdata_req *)bp->b_rptr; |
| data_req->PRIM_type = T_EXDATA_REQ; |
| data_req->MORE_flag = 0; |
| |
| bp->b_wptr += sizeof (struct T_exdata_req); |
| /* |
| * Send a 1 byte data message block with appropriate |
| * control character. |
| */ |
| mp->b_datap->db_type = M_DATA; |
| mp->b_wptr = mp->b_rptr + 1; |
| (*(char *)(mp->b_rptr)) = cntl; |
| bp->b_cont = mp; |
| return (bp); |
| } |
| /* |
| * This routine parses M_DATA messages checking for window size protocol |
| * from a given message block. It returns TRUE if no resource exhaustion |
| * conditions are found. This is for use in the service procedure, which |
| * needs to know whether to continue, or stop processing the queue. |
| */ |
| static int |
| rlmodrmsg(queue_t *q, mblk_t *mp) |
| { |
| unsigned char *tmp, *tmp1; |
| mblk_t *newmp; |
| size_t sz; |
| ssize_t count, newcount = 0; |
| struct rlmod_info *rmip = (struct rlmod_info *)q->q_ptr; |
| |
| /* |
| * Eliminate any zero length messages here, so we don't filter EOFs |
| * accidentally. |
| */ |
| if (msgdsize(mp) == 0) { |
| ASSERT(rmip->wndw_sz_hd_mp == NULL); |
| goto out; |
| } |
| /* |
| * Check if we have stored a previous message block because a window |
| * update was split over TCP segments. If so, append the new one to |
| * the stored one and process the stored one as if it just arrived. |
| */ |
| if (rmip->wndw_sz_hd_mp != NULL) { |
| linkb(rmip->wndw_sz_hd_mp, mp); |
| mp = rmip->wndw_sz_hd_mp; |
| rmip->wndw_sz_hd_mp = NULL; |
| } |
| newmp = mp; |
| |
| while (mp) { |
| tmp = mp->b_rptr; |
| /* |
| * scan through the entire message block |
| */ |
| while (tmp < mp->b_wptr) { |
| /* |
| * check for FF (rlogin magic escape sequence) |
| */ |
| if (tmp[0] == RLOGIN_MAGIC) { |
| /* |
| * Update bytes read so far. |
| */ |
| count = newcount + tmp - mp->b_rptr; |
| /* |
| * Pull together message chain in case |
| * window escape is split across blocks. |
| */ |
| if ((pullupmsg(newmp, -1)) == 0) { |
| sz = msgdsize(newmp); |
| recover(q, newmp, sz); |
| return (NULL); |
| } |
| /* |
| * pullupmsg results in newmp consuming |
| * all message blocks in this chain, and |
| * therefor mp wants updating. |
| */ |
| mp = newmp; |
| |
| /* |
| * adjust tmp to where we |
| * stopped - count keeps track |
| * of bytes read so far. |
| * reset newcount = 0. |
| */ |
| tmp = mp->b_rptr + count; |
| newcount = 0; |
| |
| /* |
| * Use the variable tmp1 to compute where |
| * the end of the window escape (currently |
| * the only rlogin protocol sequence), then |
| * check to see if we got all those bytes. |
| */ |
| tmp1 = tmp + 4 + sizeof (struct winsize); |
| |
| if (tmp1 > mp->b_wptr) { |
| /* |
| * All the window escape bytes aren't |
| * in this TCP segment. Store this |
| * mblk to one side so we can append |
| * the rest of the escape to it when |
| * its segment arrives. |
| */ |
| rmip->wndw_sz_hd_mp = mp; |
| return (TRUE); |
| } |
| /* |
| * check for FF FF s s pattern |
| */ |
| if ((tmp[1] == RLOGIN_MAGIC) && |
| (tmp[2] == 's') && (tmp[3] == 's')) { |
| |
| /* |
| * If rlwinsetup returns an error, |
| * we do recover with newmp which |
| * points to new chain of mblks after |
| * doing window control ioctls. |
| * rlwinsetup returns newmp which |
| * contains only data part. |
| * Note that buried inside rlwinsetup |
| * is where we do the putnext. |
| */ |
| if (rlwinsetup(q, mp, tmp) == NULL) { |
| sz = msgdsize(mp); |
| recover(q, mp, sz); |
| return (NULL); |
| } |
| /* |
| * We have successfully consumed the |
| * window sequence, but rlwinsetup() |
| * and its children have moved memory |
| * up underneath us. This means that |
| * the byte underneath *tmp has not |
| * been scanned now. We will now need |
| * to rescan it. |
| */ |
| continue; |
| } |
| } |
| tmp++; |
| } |
| /* |
| * bump newcount to include size of this particular block. |
| */ |
| newcount += (mp->b_wptr - mp->b_rptr); |
| mp = mp->b_cont; |
| } |
| /* |
| * If we trimmed the message down to nothing to forward, don't |
| * send any M_DATA message. (Don't want to send EOF!) |
| */ |
| if (msgdsize(newmp) == 0) { |
| freemsg(newmp); |
| newmp = NULL; |
| } |
| out: |
| if (newmp) { |
| if (!canputnext(q)) { |
| (void) putbq(q, newmp); |
| return (NULL); |
| } else { |
| putnext(q, newmp); |
| } |
| } |
| return (TRUE); |
| } |
| |
| |
| /* |
| * This routine is called to handle window size changes. |
| * The routine returns 1 on success and 0 on error (allocb failure). |
| */ |
| static int |
| rlwinctl(queue_t *q, mblk_t *mp) |
| { |
| mblk_t *rl_msgp; |
| struct iocblk *iocbp; |
| struct rlmod_info *rmip = (struct rlmod_info *)q->q_ptr; |
| |
| TRACE_2(TR_FAC_RLOGINP, TR_RLOGINP_WINCTL_IN, "rlwinctl start: q %p, " |
| "mp %p", q, mp); |
| |
| rmip->oobdata[0] &= ~TIOCPKT_WINDOW; /* we know they heard */ |
| |
| if ((rl_msgp = mkiocb(TIOCSWINSZ)) == NULL) { |
| TRACE_2(TR_FAC_RLOGINP, TR_RLOGINP_WINCTL_OUT, "rlwinctl end: " |
| "q %p, mp %p, allocb failed", q, mp); |
| return (0); |
| } |
| |
| /* |
| * create an M_IOCTL message type. |
| */ |
| rl_msgp->b_cont = mp; |
| iocbp = (struct iocblk *)rl_msgp->b_rptr; |
| iocbp->ioc_count = msgdsize(mp); |
| |
| putnext(q, rl_msgp); |
| TRACE_2(TR_FAC_RLOGINP, TR_RLOGINP_WINCTL_OUT, "rlwinctl end: " |
| "q %p, mp %p, done", q, mp); |
| return (1); |
| } |
| |
| /* |
| * This routine sets up window size change protocol. |
| * The routine returns the new mblk after issuing rlwinctl |
| * for window size changes. New mblk contains only data part |
| * of the message block. The routine returns 0 on error. |
| */ |
| static mblk_t * |
| rlwinsetup(queue_t *q, mblk_t *mp, unsigned char *blk) |
| { |
| mblk_t *mp1; |
| unsigned char *jmpmp; |
| ssize_t left = 0; |
| struct winsize win; |
| |
| /* |
| * Set jmpmp to where to jump, to get just past the end of the |
| * window size protocol sequence. |
| */ |
| jmpmp = (blk + 4 + sizeof (struct winsize)); |
| left = mp->b_wptr - jmpmp; |
| |
| if ((mp1 = allocb(sizeof (struct winsize), BPRI_MED)) == NULL) |
| return (0); |
| mp1->b_datap->db_type = M_DATA; |
| mp1->b_wptr = mp1->b_rptr + sizeof (struct winsize); |
| bcopy(blk + 4, &win, sizeof (struct winsize)); |
| win.ws_row = ntohs(win.ws_row); |
| win.ws_col = ntohs(win.ws_col); |
| win.ws_xpixel = ntohs(win.ws_xpixel); |
| win.ws_ypixel = ntohs(win.ws_ypixel); |
| bcopy(&win, mp1->b_rptr, sizeof (struct winsize)); |
| |
| if ((rlwinctl(q, mp1)) == NULL) { |
| freeb(mp1); |
| return (0); |
| } |
| if (left > 0) { |
| /* |
| * Must delete the window size protocol sequence. We do |
| * this by sliding all the stuff after the sequence (jmpmp) |
| * to where the sequence itself began (blk). |
| */ |
| bcopy(jmpmp, blk, left); |
| mp->b_wptr = blk + left; |
| } else |
| mp->b_wptr = blk; |
| return (mp); |
| } |
| |
| /* |
| * When an ioctl changes software flow control on the tty, we must notify |
| * the rlogin client, so it can adjust its behavior appropriately. This |
| * routine, called from either the put or service routine, determines if |
| * the flow handling has changed. If so, it tries to send the indication |
| * to the client. It returns true or false depending upon whether the |
| * message was fully processed. If it wasn't fully processed it queues |
| * the message for retry later when resources |
| * (allocb/canputnext) are available. |
| */ |
| static boolean_t |
| tty_flow(queue_t *q, struct rlmod_info *rmip, mblk_t *mp) |
| { |
| struct iocblk *ioc; |
| struct termios *tp; |
| struct termio *ti; |
| int stop, ixon; |
| mblk_t *tmpmp; |
| char cntl; |
| int error; |
| |
| ioc = (struct iocblk *)mp->b_rptr; |
| switch (ioc->ioc_cmd) { |
| |
| /* |
| * If it is a tty ioctl, save the output flow |
| * control flag and the start and stop flow control |
| * characters if they are available. |
| */ |
| case TCSETS: |
| case TCSETSW: |
| case TCSETSF: |
| error = miocpullup(mp, sizeof (struct termios)); |
| if (error != 0) { |
| miocnak(q, mp, 0, error); |
| return (B_TRUE); |
| } |
| tp = (struct termios *)(mp->b_cont->b_rptr); |
| rmip->stopc = tp->c_cc[VSTOP]; |
| rmip->startc = tp->c_cc[VSTART]; |
| ixon = tp->c_iflag & IXON; |
| break; |
| |
| case TCSETA: |
| case TCSETAW: |
| case TCSETAF: |
| error = miocpullup(mp, sizeof (struct termio)); |
| if (error != 0) { |
| miocnak(q, mp, 0, error); |
| return (B_TRUE); |
| } |
| ti = (struct termio *)(mp->b_cont->b_rptr); |
| ixon = ti->c_iflag & IXON; |
| break; |
| |
| default: |
| /* |
| * This function must never be called for an M_IOCTL |
| * except the listed ones. |
| */ |
| #ifdef DEBUG |
| cmn_err(CE_PANIC, |
| "rloginmod: tty_flow: bad ioctl 0x%x", ioc->ioc_cmd); |
| #else |
| miocnak(q, mp, 0, EINVAL); |
| return (B_TRUE); |
| #endif |
| } |
| /* |
| * If tty ioctl processing is done, check for stopmode |
| */ |
| stop = (ixon && (rmip->stopc == CTRL('s')) && |
| (rmip->startc == CTRL('q'))); |
| if (rmip->stopmode == TIOCPKT_NOSTOP) { |
| if (stop) { |
| cntl = rmip->oobdata[0] | TIOCPKT_DOSTOP; |
| if ((tmpmp = make_expmblk(cntl)) == NULL) { |
| recover(q, mp, sizeof (mblk_t)); |
| return (B_FALSE); |
| } |
| if (!canputnext(q)) { |
| freemsg(tmpmp); |
| return (B_FALSE); |
| } |
| putnext(q, tmpmp); |
| rmip->stopmode = TIOCPKT_DOSTOP; |
| } |
| } else { |
| if (!stop) { |
| cntl = rmip->oobdata[0] | TIOCPKT_NOSTOP; |
| if ((tmpmp = make_expmblk(cntl)) == NULL) { |
| recover(q, mp, sizeof (mblk_t)); |
| return (B_FALSE); |
| } |
| if (!canputnext(q)) { |
| freemsg(tmpmp); |
| return (B_FALSE); |
| } |
| putnext(q, tmpmp); |
| rmip->stopmode = TIOCPKT_NOSTOP; |
| } |
| } |
| |
| miocack(q, mp, 0, 0); |
| return (B_TRUE); |
| } |
| |
| /* rlmodwioctl - handle M_IOCTL messages on the write queue. */ |
| |
| static boolean_t |
| rlmodwioctl(queue_t *q, mblk_t *mp) |
| { |
| struct iocblk *ioc; |
| struct rlmod_info *rmip = (struct rlmod_info *)q->q_ptr; |
| int error; |
| |
| ioc = (struct iocblk *)mp->b_rptr; |
| switch (ioc->ioc_cmd) { |
| |
| /* |
| * This is a special ioctl to reenable the queue. |
| * The initial data read from the stream head is |
| * put back on the queue. |
| */ |
| case RL_IOC_ENABLE: |
| /* |
| * Send negative ack if RL_DISABLED flag is not set |
| */ |
| |
| if (!(rmip->flags & RL_DISABLED)) { |
| miocnak(q, mp, 0, EINVAL); |
| break; |
| } |
| if (mp->b_cont) { |
| (void) putbq(RD(q), mp->b_cont); |
| mp->b_cont = NULL; |
| } |
| |
| if (rmip->flags & RL_DISABLED) |
| rmip->flags &= ~RL_DISABLED; |
| qenable(RD(q)); |
| miocack(q, mp, 0, 0); |
| TRACE_3(TR_FAC_RLOGINP, TR_RLOGINP_WPUT_OUT, |
| "rlmodwput end: q %p, mp %p, %s", |
| q, mp, "IOCACK enable"); |
| return (B_TRUE); |
| |
| /* |
| * If it is a tty ioctl, save the output flow |
| * control flag and the start and stop flow control |
| * characters if they are available. |
| */ |
| case TCSETS: |
| case TCSETSW: |
| case TCSETSF: |
| case TCSETA: |
| case TCSETAW: |
| case TCSETAF: |
| return (tty_flow(q, rmip, mp)); |
| |
| #ifdef DEBUG |
| case TIOCSWINSZ: |
| case TIOCSTI: |
| case TCSBRK: |
| 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) |
| rmip->flags |= RL_IOCPASSTHRU; |
| else |
| rmip->flags &= ~RL_IOCPASSTHRU; |
| |
| miocack(q, mp, NULL, 0); |
| break; |
| |
| default: |
| if (rmip->flags & RL_IOCPASSTHRU) { |
| putnext(q, mp); |
| } else { |
| #ifdef DEBUG |
| cmn_err(CE_NOTE, |
| "rlmodwioctl: unexpected ioctl type 0x%x", |
| ioc->ioc_cmd); |
| #endif |
| miocnak(q, mp, 0, EINVAL); |
| } |
| } |
| return (B_TRUE); |
| } |
| |
| static void |
| rlmod_timer(void *arg) |
| { |
| queue_t *q = arg; |
| struct rlmod_info *rmip = (struct rlmod_info *)q->q_ptr; |
| |
| ASSERT(rmip); |
| if (q->q_flag & QREADR) { |
| ASSERT(rmip->rtimoutid); |
| rmip->rtimoutid = 0; |
| } else { |
| ASSERT(rmip->wtimoutid); |
| rmip->wtimoutid = 0; |
| } |
| enableok(q); |
| qenable(q); |
| } |
| |
| static void |
| rlmod_buffer(void *arg) |
| { |
| queue_t *q = arg; |
| struct rlmod_info *rmip = (struct rlmod_info *)q->q_ptr; |
| |
| ASSERT(rmip); |
| if (q->q_flag & QREADR) { |
| ASSERT(rmip->rbufcid); |
| rmip->rbufcid = 0; |
| } else { |
| ASSERT(rmip->wbufcid); |
| rmip->wbufcid = 0; |
| } |
| enableok(q); |
| qenable(q); |
| } |
| |
| static void |
| recover(queue_t *q, mblk_t *mp, size_t size) |
| { |
| /* |
| * Avoid re-enabling the queue. |
| */ |
| ASSERT(mp->b_datap->db_type < QPCTL); |
| |
| noenable(q); |
| (void) putbq(q, mp); |
| recover1(q, size); |
| } |
| |
| static void |
| recover1(queue_t *q, size_t size) |
| { |
| struct rlmod_info *rmip = (struct rlmod_info *)q->q_ptr; |
| timeout_id_t tid; |
| bufcall_id_t bid; |
| |
| /* |
| * Make sure there is at most one outstanding request per queue. |
| */ |
| if (q->q_flag & QREADR) { |
| if (rmip->rtimoutid || rmip->rbufcid) |
| return; |
| } else { |
| if (rmip->wtimoutid || rmip->wbufcid) |
| return; |
| } |
| if (!(bid = qbufcall(RD(q), size, BPRI_MED, rlmod_buffer, q))) { |
| tid = qtimeout(RD(q), rlmod_timer, q, SIMWAIT); |
| if (q->q_flag & QREADR) |
| rmip->rtimoutid = tid; |
| else |
| rmip->wtimoutid = tid; |
| } else { |
| if (q->q_flag & QREADR) |
| rmip->rbufcid = bid; |
| else |
| rmip->wbufcid = bid; |
| } |
| } |