#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)
#define TEL_IOCPASSTHRU 0x100
#define TEL_STOPPED 0x80
#define TEL_CRRCV 0x40
#define TEL_CRSND 0x20
#define TEL_GETBLK 0x10
static struct fmodsw fsw = {
"telmod",
&telmodinfo,
D_MTQPAIR | D_MP
};
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 int telmodrput(queue_t *, mblk_t *);
static int telmodrsrv(queue_t *);
static int telmodwput(queue_t *, mblk_t *);
static int 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,
"telmod",
0,
INFPSZ,
512,
256
};
static struct qinit telmodrinit = {
telmodrput,
telmodrsrv,
telmodopen,
telmodclose,
nulldev,
&telmodoinfo,
NULL
};
static struct qinit telmodwinit = {
telmodwput,
telmodwsrv,
NULL,
NULL,
nulldev,
&telmodoinfo,
NULL
};
struct streamtab telmodinfo = {
&telmodrinit,
&telmodwinit,
NULL,
NULL
};
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;
};
static void
dummy_callback(void *arg)
{}
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) {
return (0);
}
tmip = kmem_zalloc(sizeof (*tmip), KM_SLEEP);
q->q_ptr = tmip;
WR(q)->q_ptr = tmip;
noenable(q);
tmip->flags |= TEL_STOPPED;
qprocson(q);
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;
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);
}
static int
telmodclose(queue_t *q, int flag, cred_t *credp)
{
struct telmod_info *tmip = (struct telmod_info *)q->q_ptr;
mblk_t *mp;
while (mp = getq(WR(q)))
putnext(WR(q), mp);
(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);
}
static int
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 (0);
}
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 (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 = mp;
tmip->flags &= ~TEL_GETBLK;
noenable(q);
tmip->flags |= TEL_STOPPED;
putnext(q, newmp);
break;
}
(void) rcv_parse(q, mp);
break;
case M_FLUSH:
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;
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:
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 (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:
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;
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);
}
return (0);
}
static int
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 (0);
}
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 (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 = mp;
tmip->flags &= ~TEL_GETBLK;
noenable(q);
tmip->flags |= TEL_STOPPED;
putnext(q, newmp);
break;
}
if (!rcv_parse(q, mp)) {
return (0);
}
break;
case M_PROTO:
tip = (union T_primitives *)mp->b_rptr;
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:
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 (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:
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;
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);
}
}
return (0);
}
static int
telmodwput(
queue_t *q,
mblk_t *mp)
{
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;
}
(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) {
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;
case TEL_IOC_ENABLE:
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;
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:
rw = *mp->b_rptr;
if (rw & FLUSHR) {
*mp->b_rptr = rw & ~FLUSHW;
qreply(q, mp);
} else {
freemsg(mp);
}
if (rw & FLUSHW) {
flushq(q, FLUSHDATA);
}
break;
case M_PCPROTO:
putnext(q, mp);
break;
case M_PROTO:
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;
}
return (0);
}
static int
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 (0);
}
switch (mp->b_datap->db_type) {
case M_DATA:
if (tmip->flags & TEL_STOPPED) {
(void) putbq(q, mp);
return (0);
}
if (!snd_parse(q, mp)) {
return (0);
}
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);
}
}
return (0);
}
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 (mp->b_rptr == mp->b_wptr) {
prevmp = mp;
mp = mp->b_cont;
continue;
}
if (!(tmip->flags & TEL_BINARY_IN) &&
(tmip->flags & TEL_CRRCV)) {
if ((*mp->b_rptr == '\n') || (*mp->b_rptr == '\0')) {
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) {
return (1);
} else {
mp = datamp;
continue;
}
}
}
mp->b_rptr += 1;
}
tmip->flags &= ~TEL_CRRCV;
}
tmp = mp->b_rptr;
while (tmp < mp->b_wptr) {
if (tmp[0] == IAC) {
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);
}
if ((newmp = allocb(sizeof (char),
BPRI_MED)) == NULL) {
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)) {
if ((tmp == (mp->b_wptr - 1)) &&
(tmp[0] == '\r')) {
tmip->flags |= TEL_CRRCV;
break;
}
if ((tmp[0] == '\r') && ((tmp[1] == '\n') ||
(tmp[1] == '\0'))) {
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);
}
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);
}
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++ = '\0';
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)) {
tmip->flags |= TEL_CRSND;
break;
}
if ((tmp[0] == '\r') && (tmp[1] != '\n')) {
*tmp1++ = '\0';
}
} 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);
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;
}
}