BBN-V6/telnet/netcmd.c

Compare this file to the similar file:
Show the results in this format:

#
/*
 * Finite-state-machine for handling THP/TELNET controls.
 * This file contains all of the real intelligence needed for
 * handling either protocol.
 * Use: first allocate a state block using AllocSB().
 * Then do
 *      IoxEnter(&NetCmd, connp)
 * where connp is a ptr to a NetConn block containing
 * the state-block ptr returned from AllocSB()
 * NetCmd will read from FmNetBuf and call UserChar or do option neg.
 * as required.
 */

#include "globdefs.h"
#include "protocol.h"
#include "cbuf.h"
#include "netconn.h"
#ifndef NEWTTY
#include "/sys/sys/h/sgttybuf.h"
#endif
#ifdef NEWTTY
#include "/sys/sys/h/modtty.h"
#endif

#define entries(f) (sizeof(f)/sizeof(f[0]))

/* States of FSM; EXP stands for "expected" */
#define ESCAPE_EXP 0
#define HIBYTE_EXP 1
#define LOBYTE_EXP 2
#define CTRL_EXP   3

/* The states above always preceed the states below */
/* This ordering is used by NetChar() */

#define OPTION_EXP 4
#define MODE_EXP   5
#define WITHIN_SUBNEG 6
#define IAC_WITHIN_SUBNEG 7
#define COUNT_EXP  8
#define CHAR_EXP   9
#define WITHIN_DATA 10

#define INIT ESCAPE_EXP

/* Internal representation of WILL, WONT, DO, and DONT */

#define MY_WILL 0
#define MY_DO	1
#define MY_WONT 2
#define MY_DONT 3

extern Never(), Always(), NoAct(), EchoTst(), EchoAct();
extern BinTst(), BinAct();
extern FormTst(), LineAct(), PageAct();
extern SizeTst(), SizeAct();
extern DispTst(), DispAct();
extern ReconTst(), ReconAct();
struct OPT  /* Option negotiation table */
{
    char *optname;      /* Name of the option */
    int  optlength;     /* Length, excluding WILL/WONT/DO/DONT byte */
    int  optsr;         /* Set if it has SEND/RECEIVE byte */
    int  (*testf)();    /* Test function */
    int  (*actf)();      /* Action routine */
} opt[]
{

/* ---- Name ------------- len sr -- Test ------ Act ---------------- */

#ifdef TELNET
    {"binary",		    1,	0,  BinTst,	BinAct},
    {"echo",		    1,	0,  EchoTst,	EchoAct},
    {"reconnect",	    1,	0,  Never,	NoAct},
    {"suppress-go-ahead",   1,	0,  Always,	NoAct},
    {"set-record-size",     1,	0,  Never,	NoAct},
    {"status",		    1,	0,  Never,	NoAct},
    {"timing-mark",	    1,	0,  Always,	NoAct},
    {"RCTE",		    1,	0,  Never,	NoAct},
    {"linewidth",	    1,	1,  Never,	NoAct},
    {"pagesize",	    1,	1,  Never,	NoAct},
    {"cr-disp", 	    1,	1,  Never,	NoAct},
#endif
#ifdef THP
    {"echo",		    1,	0,  EchoTst,	EchoAct},
    {"binary",		    1,	0,  BinTst,	BinAct},
    {"RCTE",		    1,	0,  Never,	NoAct},
    {"include-go-ahead",    1,	0,  Never,	NoAct},
    {"extended-ascii",	    1,	0,  Never,	NoAct},
    {"reconnect",	    9,	0,  ReconTst,	ReconAct},
    {"set-message-size",    3,	0,  SizeTst,	SizeAct},
    {"linewidth",	    3,	1,  FormTst,	LineAct},
    {"pagesize",	    3,	1,  FormTst,	PageAct},
    {"set-vtabs",	   -1,	1,  Never,	NoAct},
    {"set-htabs",	   -1,	1,  Never,	NoAct},
    {"cr-disp", 	    3,	1,  DispTst,	DispAct},
    {"lf-disp", 	    3,	1,  DispTst,	DispAct},
    {"ff-disp", 	    3,	1,  DispTst,	DispAct},
    {"ht-disp", 	    3,	1,  DispTst,	DispAct},
    {"vt-disp", 	    3,	1,  DispTst,	DispAct},
#endif
    {"",		   -1,	0,  Never,	NoAct},
};

/* The generic "unimplemented" option is the last one. */

#define OPT_UNIMPL (entries(opt)-1)

struct SBlk    /* State block */
{
    char cmdstate;
    int ctrl;	/* (unsigned char) WILL|WONT|DO|DONT or SET_MODE|REQUEST_MODE */
    int count;	/* bytes left in record */
    int modep;
    char rest[16];
    char *restp;
#ifdef THP
    int XMode;	/* RECORD or STREAM */
#endif
    char state[entries(opt)][2];
    char orig[entries(opt)][2];
    char value[entries(opt)][2][2];
#ifdef NEWTTY
    int oldiflags[entries(opt)];
    int oldoflags[entries(opt)];
#endif
    int oldflags[entries(opt)];

};

/* -------------------------- A L L O C S B ------------------------- */
/*
 * AllocSB() allocates a state block for the connection and returns a ptr
 * to it. The state block contains all the information NetCmd needs to
 * process characters coming in on the connection.
 */
AllocSB()
{
    int i;
    int flags1;
    int flags2;
    struct SBlk *sbp;

    sbp = alloc(sizeof(*sbp));
    sbp->cmdstate = INIT;
    sbp->count = 0;
    sbp->restp = &(sbp->rest[0]);
#ifdef THP
    sbp->XMode = STREAM;
#endif
    for (i = 0; i < entries(opt); i++)
    {
	sbp->state[i][0] = 0;
	sbp->state[i][1] = 0;
	sbp->orig[i][0] = 0;
	sbp->orig[i][1] = 0;
    }
    sbp->modep = AllocMode();
    flags1 = GetFlags(OrigMode());
    for (i = 0; i < entries(opt); i++)
	sbp->oldflags[i] = flags1;
#ifdef NEWTTY
    flags1 = GetXMode(OrigMode())->t_iflags;
    flags2 = GetXMode(OrigMode())->t_oflags;
    for (i = 0; i < entries(opt); i++)
    {
	sbp->oldiflags[i] = flags1;
	sbp->oldoflags[i] = flags2;
    }
#endif
    return(sbp);
}

/* -------------------------- F R E E S B --------------------------- */
/*
 * FreeSB(sbp) is called to dispose of a state block.
 */
FreeSB(sbp)
struct SBlk *sbp;
{

    FreeMode(sbp->modep);
    free(sbp);
}

/* -------------------------- C H G S B ----------------------------- */
/*
 * ChgSB(sbp) is called to make the state block pointed to by sbp
 * the current state block. All it does is change to that set of tty
 * modes.
 */
ChgSB(sbp)
    struct SBlk *sbp;
{

    ChgMode(sbp->modep);
}

/* -------------------------- N E T C M D --------------------------- */
/*
 * As long as there is room in ToUsrBuf, get chars from FmNetBuf.
 * There must be enough room to handle the full expansion of any single
 * TELNET/THP control.
 */
NetCmd(aconnp)
    struct NetConn *aconnp;
{
    register struct NetConn *connp;
    register retval;

    retval = 1;
    connp = aconnp;
    while (!Cempty(connp->FmNetBuf) && Cfree(connp->ToUsrBuf) >= USERGROW)
    {
        retval = 0;
        NetChar(Cgetc(connp->FmNetBuf), connp);
    }
    return(retval);
}

/* -------------------------- N E T C H A R ------------------------- */
/*
 * Handle all protocol controls. For option negotiation, convert WILL WONT DO
 * DONT into more convenient form, save it in SBlk.ctrl, and call negotiate()
 * when the control has been assembled.
 * For other controls, wait until they are all gathered up and then act on them.
 * Handle REPEAT and DATA records.
 * Call UserChar as required.
 */
NetChar(c, connp)
{
    register char rstate;
    register struct SBlk *sbp;
    extern int verbose;

    sbp = connp->StateP;
    rstate = sbp->cmdstate;

    c =& 0377;

    switch (rstate)
    {
    case INIT:
        if (c != IAC)
        {
            if (connp->InSynCnt <= 0)
                UserChar(c, connp);
            if (connp->RemState == SUSP || connp->LocState == SUSP)
                fdprintf(2, "Text received in suspended state!\r\n");
        }
        else
#ifdef TELNET
            rstate = CTRL_EXP;
#endif
#ifdef THP
            rstate = HIBYTE_EXP;
#endif
        break;
#ifdef THP
    case HIBYTE_EXP:
        sbp->count = c * 256;
        rstate = LOBYTE_EXP;
        break;
    case LOBYTE_EXP:
        sbp->count =+ c;
        rstate = CTRL_EXP;
        break;
#endif

    case CTRL_EXP:
        switch(c)
        {
#ifdef THP
        case DATA:
            rstate = WITHIN_DATA;
            break;
        case REPEAT:
            rstate = COUNT_EXP;
            break;
        case SET_MODE:
        case REQUEST_MODE:
            sbp->ctrl = c;
            rstate = MODE_EXP;
            break;
        case SUSPEND:
            connp->RemState = SUSP;
            SendCtrl(connp, SUSPENDED, 0, 0);
            fdprintf(2, "Connection remotely suspended\r\n");
            rstate = INIT;
            break;
        case SUSPENDED:
            if (connp->LocState == SUSPING)
            {
                connp->LocState = SUSP;
                fdprintf(2, "Connection suspended\r\n");
            }
            rstate = INIT;
            break;
        case CONTINUE:
            if (connp->RemState == SUSP)
            {
                connp->RemState = ACTIVE;
                fdprintf(2, "Connection remotely resumed\r\n");
            }
            rstate = INIT;
            break;
#endif
#ifdef TELNET
        case SB:
            rstate = WITHIN_SUBNEG;
            break;
        case IAC:
            UserChar(IAC, connp);
            rstate = INIT;
            break;
#endif
        case DM:
            connp->InSynCnt--;
            rstate = INIT;
            break;
        case WILL:
            sbp->ctrl = MY_WILL;
            rstate = OPTION_EXP;
            break;
        case DO:
            sbp->ctrl = MY_DO;
            rstate = OPTION_EXP;
            break;
        case WONT:
            sbp->ctrl = MY_WONT;
            rstate = OPTION_EXP;
            break;
        case DONT:
            sbp->ctrl = MY_DONT;
            rstate = OPTION_EXP;
            break;
        case BREAK:
            UserChar('\0', connp);
            rstate = INIT;
            break;
        case AYT:
            SendCtrl(connp, NOP, 0, 0);
            SendBuf(connp, 5, "Yes\r\n");
            rstate = INIT;
            break;
        case IP:
#ifndef NEWTTY
            UserChar('\177', connp);
#endif
#ifdef NEWTTY
	    UserChar(GetXMode(sbp->modep)->t_intr, connp);
#endif
            SendSynch(connp);
            rstate = INIT;
            break;
        case EC:
            UserChar(GetMode(sbp->modep)->s_erase, connp);
            rstate = INIT;
            break;
        case EL:
            UserChar(GetMode(sbp->modep)->s_kill, connp);
            rstate = INIT;
            break;
        case NOP:
        case AO:
#ifdef TELNET
        case GA:
#endif
            rstate = INIT;
            break;
        default:
            com_err(0, "Unknown net control 0%o (%d.) ignored", c, c);
            rstate = INIT;
            break;
        }
        break;
    case OPTION_EXP:
        if (sbp->restp < &(sbp->rest[sizeof(sbp->rest)]))
            *(sbp->restp)++ = c;
        if (sbp->count == 0)
        {
            negotiate(connp, 0, sbp->ctrl, sbp->restp - sbp->rest, sbp->rest);
            sbp->restp = sbp->rest;
            rstate = INIT;
        }
        break;
#ifdef TELNET
    case WITHIN_SUBNEG:
        if (c == IAC)
            rstate = IAC_WITHIN_SUBNEG;
        break;
    case IAC_WITHIN_SUBNEG:
        if (c == SE)
            rstate = INIT;
        else
            rstate = WITHIN_SUBNEG;
        break;
#endif
#ifdef THP
    case MODE_EXP:
        if (sbp->ctrl == REQUEST_MODE)
        {
            sbp->XMode = c;
            SendCtrl(connp, SET_MODE, 1, &c);
            if (verbose)
                fdprintf(2, "%s mode\r\n", c == RECORD? "Record" : "Stream");
        }
        else if (sbp->ctrl == SET_MODE)
        {
            if (verbose)
                fdprintf(2, "Receiving in %s mode\r\n",
                c == RECORD? "record" : "stream");
        }
        else
            com_err(0, "NetCmd error: mode expected");
        rstate = INIT;
        break;
    case COUNT_EXP:
        sbp->ctrl = c;
        rstate = CHAR_EXP;
        break;
    case CHAR_EXP:    /* Here c is char to repeat, ctrl is count. */
        while (sbp->ctrl-- > 0)
            UserChar(c, connp);
        rstate = INIT;
        break;
    case WITHIN_DATA:
        UserChar(c, connp);
        break;
#endif
    default:
        com_err(0, "NetCmd error: unknown state %d", rstate);
        rstate = INIT;
        break;
    }

#ifdef THP
    if (rstate == INIT && sbp->count != 0)
        com_err(0, "Long record: %d left after 0%o received", sbp->count, c);
    if (rstate > CTRL_EXP)  /* Record header complete */
    {
        if (sbp->count == 0)
            if (rstate == WITHIN_DATA)
                rstate = INIT;
            else
                com_err(0, "Short record count: state = %d, ctrl = 0%o", rstate, c);
        else
            sbp->count--;
    }
#endif

    sbp->cmdstate = rstate;
    return;
}

/* -------------------------- N E G O T I A T E --------------------- */
/*
 * Handle actual option negotiation. First arg is pointer to connection block.
 * Second is 1 if originating request, 0 if received over net.
 * Third is WILL, WONT, DO, or DONT.
 * Fourth is count of additional data (normally only 1 byte).
 * Fifth is pointer to that data.
 *
 * For origination of a request, we reverse DO and WILL to make it look
 * like it came in over the net. Then we see whether it can be done,
 * by calling the test function. If so, it gets "replied to", which originates
 * in the right way (reverses DO and WILL again). Otherwise the user is informed
 * of his misfortune.
 *
 * For requests originated at the foreign host, we check to see whether the
 * option is already in effect; if so, no reply is made and no action is taken.
 * This is a little tricky, because even if a previous negotiation was
 * successful, this one may be for different values. E.g.,
 * DO LINE-WIDTH SENDER 60 is not already in effect if the present line width
 * is 80, or if the receiver is managing line width.
 * SENDER/RECEIVER and the value byte (line width, page length, etc.) are both
 * regarded as values to be compared against what's there, assuming the option
 * is in effect at all (previous successful negotiation).
 * The same code can handle a request for a different record size, which is
 * a two-byte value without a SENDER/RECEIVER field.
 * If it is not in effect, we call the test function to see whether it can be
 * done, and reply appropriately. We don't bother
 * the test function if the foreign host is saying DONT or WONT, since
 * such requests can never be refused (just ignored, if they are already
 * in effect). If everything checks, an appropriate reply is sent off, and if
 * the result of the test was that it can be done, the action routine is called.
 *
 * Back on the originating host, when the reply is received, the action routine
 * is called. It's too late to test anything; if it can be done, it will be.
 * Note that conflicts (different line widths, etc.) are resolved in the action
 * routine.
 *
 * The test function is permitted to modify the values of the option.
 * If, for example, we are asked to do PAGE-SIZE 30, but all we can really do is
 * PAGE-SIZE 24, then testf can actually modify the values pointed to by "args".
 * If org_here is set, however, then it should not modify the values, but
 * send a message to the user (if it wants) and return CANT. After all, the user
 * can resend the do-able thing if that's really what he wants.
 */
char *which[] { "will", "do", "won't", "don't"};

/* Values returned by testf */
#define CANT 0
#define CAN  1

/* Useful defines */
#define NO  0
#define YES 1

negotiate(connp, org_here, op, nargs, args)
    struct NetConn *connp;
    int org_here;
    int op;
    int nargs;
    char args[];
{
    int opnum;
    register struct SBlk *sbp;
#define DO_SIDE 0
#define WILL_SIDE 1
    int side;   /* DO_SIDE or WILL_SIDE */
    int nstate; /* NO (DONT or WONT) or YES (DO or WILL) */
    int rop;
    register char *values;
    int nvalues;
    int verdict;
    extern int verbose;

    sbp = connp->StateP;
    opnum = args[0] & 0377;
    values = &args[1];
    nvalues = nargs - 1;
    if (opnum >= OPT_UNIMPL) opnum = OPT_UNIMPL;

    if (opt[opnum].optlength != -1 && opt[opnum].optlength != nargs)
    {
        com_err(0, "Option %s is %d bytes long, should be %d",
                    opt[opnum].optname, nargs, opt[opnum].optlength);
        return;
    }

    rop = op;
    if (org_here)   /* Reverse DO and WILL */
        if      (op == MY_DO)   op = MY_WILL;
        else if (op == MY_WILL) op = MY_DO;
        else if (op == MY_DONT) op = MY_WONT;
        else                    op = MY_DONT;

    side = (op == MY_DO || op == MY_DONT)? DO_SIDE : WILL_SIDE;
    nstate = (op == MY_DO || op == MY_WILL)? YES : NO;

    if (org_here)                           /* Origination */
    {
        if (nstate == NO || (*opt[opnum].testf)(connp, org_here, op, nargs, args) == CAN)
        {
            if (verbose) snap("Sending", rop, nargs, args);
            SendReply(connp, YES, op, nargs, args);
            sbp->orig[opnum][side] = YES;
            vcopy(nvalues, values, sbp->value[opnum][side]);/* Remember for conflicts */
        }
        else
            snap("Unimplemented:", rop, nargs, args);
    }

    else if (sbp->orig[opnum][side] == NO)   /* Reply to foreign origination */
    {
/*
 * The option is already in effect if the new state is the same as the current state
 * AND either the new state is DONT/WONT or it is DO/WILL and the new values are the
 * same as the current ones. If the option is "timing mark", reply regardless...
 */
        if (sbp->state[opnum][side] == nstate)
            if (nstate == NO || veq(nvalues, values, sbp->value[opnum][side]))
#ifdef TELNET
                if (opnum != OPT_TMARK)
#endif
                {
                    snap("Already did", op, nargs, args);
                    return;
                }
        vcopy(nvalues, values, sbp->value[opnum][side]);/* Remember for conflicts */

/* It's not already in effect; find out whether it could be. */

        if (nstate == NO)
            verdict = CAN;     /* Can always DONT/WONT */
        else
            verdict = (*opt[opnum].testf)(connp, org_here, op, nargs, args);
        SendReply(connp, verdict, op, nargs, args);
        if (verdict != CANT)
        {
            if (verbose) snap("Affirming", op, nargs, args);
            (*opt[opnum].actf)(connp, sbp->modep, op, nargs, args);
            sbp->state[opnum][side] = nstate;
            vcopy(nvalues, values, sbp->value[opnum][side]); /* Remember */
        }
    }

    else                                     /* Reply to our origination */
    {   if (connp->UsingPty == 0)
            snap("Reply was", op, nargs, args);     /* Always notify user */
        (*opt[opnum].actf)(connp, sbp->modep, op, nargs, args);
        sbp->orig[opnum][side] = NO;
        sbp->state[opnum][side] = nstate;
        vcopy(nvalues, values, sbp->value[opnum][side]); /* Remember */
    }
}

/* -------------------------- V C O P Y ----------------------------- */
/*
 * vcopy(nargs, args, values) copies the arguments into the value array.
 */
vcopy(nargs, args, values)
    int nargs;
    char args[];
    char values[];
{
    register char *ap;
    register char *vp;
    register char *lastv;
    int len;
    struct SBlk *p;

    len = sizeof(p->value[0][0]);
    if (nargs < len) len = nargs;
    ap = args;
    vp = values;
    lastv = vp + len;

    while (vp < lastv)
        *vp++ = *ap++;
}

/* -------------------------- V C M P ------------------------------- */
/*
 * veq(nargs, args, values) compares the incoming arguments
 * against the current values. It returns 1 if they are equal, 0 if not.
 */
veq(nargs, args, values)
    int nargs;
    char args[];
    char values[];
{
    register char *ap;
    register char *vp;
    int len;
    register char *lastv;
    struct SBlk *p;

    len = sizeof(p->value[0][0]);
    if (nargs < len) len = nargs;
    ap = args;
    vp = values;
    lastv = vp + len;

    while (vp < lastv)
        if (*vp++ != *ap++)
            return(0);
    return(1);
}

/* -------------------------- S E N D R E P L Y --------------------- */
/*
 * SendReply(connp, reply, op, nargs, argp) replies to a negotiated
 * option. If reply is nonzero, it replies affirmatively; if reply is
 * zero, it replies negatively. op is MY_WILL/WONT/DO/DONT; the argument
 * buffer contains at least one char, which is the option number, although
 * it may contain more.
 */
SendReply(connp, reply, op, nargs, argp)
    struct NetConn *connp;
    int reply;
    int op;     /* Unsigned char */
    int nargs;
    char *argp;
{
    extern int verbose;

    switch (op)
    {
    case MY_DO:
        if (reply == 0)
        {
            SendCtrl(connp, WONT, nargs, argp);
	    if (verbose) snap("Refusing", op, nargs, argp);
        }
        else
            SendCtrl(connp, WILL, nargs, argp);
        break;
    case MY_WILL:
        if (reply == 0)
        {
            SendCtrl(connp, DONT, nargs, argp);
	    if (verbose) snap("Refusing", op, nargs, argp);
        }
        else
            SendCtrl(connp, DO, nargs, argp);
        break;
    case MY_DONT:
        SendCtrl(connp, WONT, nargs, argp);
        break;
    case MY_WONT:
        SendCtrl(connp, DONT, nargs, argp);
        break;
    }
}

/* -------------------------- N E V E R ----------------------------- */

Never()
{
    return(CANT);
}

/* -------------------------- A L W A Y S --------------------------- */

Always()
{
    return(CAN);
}

/* -------------------------- N O A C T ----------------------------- */

NoAct()
{
    return;
}

/* -------------------------- E C H O T S T ------------------------- */
/*
 * The PTY can respond to DO ECHO; the TTY can respond to WILL ECHO.
 */
EchoTst(connp, org, op, nargs, args)
    struct NetConn *connp;
    int org;
    int op;
    int nargs;
    char args[];
{
    if (connp->UsingPty)
        if (op == MY_DO)
            return(CAN);
        else
            return(CANT);
    else
        if (op == MY_WILL)
            return(CAN);
        else
            return(CANT);
}

/* -------------------------- E C H O A C T ------------------------- */
/*
 * Handle echo option. Receiving a WILL ECHO causes the tty to turn off
 * its own echoing, go into raw mode, and turn off tab expansion
 * and cr->lf conversion. The former state of these flags is remembered
 * for a future WONT ECHO. Receiving a DO ECHO enables echoing on the pty;
 * receiving a DONT ECHO disables it. This is done through setting the speeds;
 * a speed of 134.5 baud prevents the pty from echoing regardless of the state
 * of the ECHO flag.
 */
EchoAct(connp, modep, op, nargs, args)
    struct NetConn *connp;
    char *modep;
    int op;
    int nargs;
    char *args;
{
    register struct SBlk *sbp;
    struct sgttybuf *ttyp;

    sbp = connp->StateP;
    switch(op)
    {
    case MY_WILL:

/* Turn off tab expansion so Ann Arbor cursor addressing works (!*&) */

	sbp->oldflags[args[0]] = SetFlags(modep, RAW|ECHO|CRMOD|XTABS, RAW);
	fdprintf(2, "Remote echo\r\n");
	break;

    case MY_WONT:
	SetFlags(modep, ECHO|CRMOD|XTABS|RAW, sbp->oldflags[args[0]]);
	fdprintf(2, "Local echo\r\n");
	break;


/* A DO or DONT echo applies (literally) to the pty. */

    case MY_DONT:
	SetFlags(modep, ECHO, 0);
	ttyp = GetMode(modep);
	ttyp->s_ospeed = ttyp->s_ispeed = B134;
	SetMode(modep, ttyp);
	break;

    case MY_DO:
	ttyp = GetMode(modep);
	ttyp->s_ospeed = ttyp->s_ispeed = B2400;
	SetMode(modep, ttyp);
	SetFlags(modep, ECHO, ECHO);
	break;
    }
}

#ifdef THP
/* -------------------------- F O R M T S T ------------------------- */
/*
 * FormTst() checks out the formatting options (line width and page size).
 * The pty can format outgoing data, and the user tty can format incoming
 * data. Both sides are willing to let the other side handle the formatting.
 * What this means is that of the eight possibilities
 * (Pty/Tty  X	DO/WILL  X  SENDER/RECEIVER)
 * the only forbidden ones are User-WILL-Sender (which would require the
 * the user side to format outgoing data) and Server-DO-Receiver
 * (which would require the server side to format incoming data).
 */
FormTst(connp, org, op, nargs, args)
    struct NetConn *connp;
    int   org;
    int op;
    int nargs;
    char *args;
{

    if (connp->UsingPty)
	if (op == MY_DO && args[1] == RECEIVER)
	    return(CANT);
	else
	    return(CAN);
    else
	if (op == MY_WILL && args[1] == SENDER)
	    return(CANT);
	else
	    return(CAN);
}

/* -------------------------- L I N E A C T ------------------------- */
/*
 * Set line width. Depending on whether this is the pty or tty, and on
 * whether DO or WILL and whether SENDER or RECEIVER, it may or may not
 * be this side's job to set the line width. The pty can only be the
 * sender of line-width adjusted data; the tty can only be the receiver.
 * If the other side is doing it, it must be turned off here.
 * Consult the THP definition for more information.
 * Note: the line width conflict resolution algorithm of the THP report is not
 * implemented. It just uses what the incoming record specifies.
 */
LineAct(connp, modep, op, nargs, args)
    struct NetConn *connp;
    char *modep;
    int op;
    int nargs;
    char args[];
{
    register char *mp;
    register int width;
    register int me;	/* Set if I am to act */
    struct SBlk *sbp;
    extern int verbose;

    if (connp->UsingPty)
	if (op == MY_WILL && args[1] == SENDER)
	    me = 1;
	else
	    me = 0;
    else
	if (op == MY_DO && args[1] == RECEIVER)
	    me = 1;
	else
	    me = 0;

    mp = GetXMode(modep);
    sbp = connp->StateP;

    if (me)
    {
	sbp->oldoflags[args[0]] =
		SetXFlags(modep, 'o', TO_AUTONL|TO_CRMOD, TO_AUTONL|TO_CRMOD);
	width = args[2] & 0377;
	if (width == 0)
	    width = 80; /* Default size */
	else if (width == 255)
	    width = 0;	/* Infinite size */
	mp->t_width = width;
    }
    else if (op == MY_WONT || op == MY_DONT)
	SetXFlags(modep, 'o', TO_AUTONL|TO_CRMOD, sbp->oldoflags[args[0]]);
    else
	sbp->oldoflags[args[0]] =
		SetXFlags(modep, 'o', TO_AUTONL, 0);

    SetXMode(modep, mp);
    return;
}

/* -------------------------- P A G E A C T ------------------------- */
/*
 * Set the page size. Exactly the same considerations apply as with
 * setting the line width. Furthermore, if the other side is doing
 * the page-size processing, then XONXOFF processing must be off on this side;
 * if this side is doing it, then XONXOFF processing must be on.
 * When the option is turned off again, the bit must be restored to its
 * previous value.
 *
 * Note: the conflict resolution algorithm of the THP report is not
 * implemented. It just uses what the incoming record specifies.
 */
PageAct(connp, modep, op, nargs, args)
    struct NetConn *connp;
    char *modep;
    int op;
    int nargs;
    char args[];
{
    register char *mp;
    register int length;
    register int me;	/* Set if I am to act */
    struct SBlk * sbp;
    extern int verbose;

    mp = GetXMode(modep);

    if (connp->UsingPty)
	if (op == MY_WILL && args[1] == SENDER)
	    me = 1;
	else
	    me = 0;
    else
	if (op == MY_DO && args[1] == RECEIVER)
	    me = 1;
	else
	    me = 0;

    sbp = connp->StateP;
    if (me)
    {
	sbp->oldoflags[args[0]] = SetXFlags(modep, 'o', TO_XONXOFF, TO_XONXOFF);
	length = args[2] & 0377;
	if (length == 0)
	    length = 24; /* Default size */
	else if (length == 255)
	    length = 0;  /* Infinite size */
	mp->t_pagelen = length;
    }
    else if (op == MY_WONT || op == MY_DONT)
	SetXFlags(modep, 'o', TO_XONXOFF, sbp->oldoflags[args[0]]);
    else
    {
	sbp->oldoflags[args[0]] = SetXFlags(modep, 'o', TO_XONXOFF, 0);
	mp->t_pagelen = 0;
    }
    SetXMode(modep, mp);
    return;
}

/* -------------------------- R E C O N T S T ----------------------- */
/*
 * ReconTst() checks out the suggested THP Reconnection negotiation.
 * It is only willing to accept DO host/port START; DO host/port EXPECT
 * is not implemented.
 */
ReconTst(connp, org, op, nargs, argp)
    struct NetConn *connp;
    int org;
    int op;         /* MY_WILL/WONTDO/DONT */
    int nargs;     /* Length of rest of record */
    char *argp;     /* Rest of record */
{
    struct Reconn reconn;
    register char *rp;
    register char *ap;
    int i;

    rp = &reconn;
    ap = argp;

/* Throw away RECONNECT byte */

    nargs--;
    ap++;

/* Copy rest of record */

    for (i = 0; i < sizeof(reconn) && i < nargs; i++)
        rp[i] = ap[i];

/* Check it out */

    if (reconn.RType != START)
        return(CANT);  /* Don't know how to handle it */

    if (org == 1)
        if (op == MY_WILL)
            return(CAN);
        else
            return(CANT);
    else
        if (op == MY_DO)
            return(CAN);
        else
            return(CANT);
}

/* -------------------------- R E C O N A C T ----------------------- */
/*
 * ReconAct() implements the THP reconnection option.
 * Actually, it only implements the response to DO host/port START;
 * the response to DO host/port EXPECT depends on the purpose of
 * the reconnection (e.g., if the reconnection is from an authenticator,
 * the DO EXPECT should set up a listening server connection which
 * when established will require no login).
 *
 * Note that implementation of DO host/port START is sufficient for
 * a User THP which wants to be able to move the other end of its
 * connection from some authenticator to the host the user wanted.
 */
ReconAct(connp, modep, op, nargs, argp)
    struct NetConn *connp;
    char *modep;    /* Ignored */
    int op;	    /* MY_WILL/WONTDO/DONT */
    int nargs;	   /* Length of rest of record */
    char *argp;     /* Rest of record */
{
    int i;
    struct Reconn reconn;
    char *rp;
    char *open_args[5];

/* Fill in structure */

    rp = &reconn;
    for (i = 1; i < sizeof(reconn) && i < nargs; i++)
	*rp++ = argp[i];

    if (op == MY_WILL || op == MY_WONT || op == MY_DONT)
	return;

/* Set EOF. The reply will be the last data sent over this connection. */

    connp->ToNetEOF = 1;

/* Build up an open command. */

    i = 0;
    sbegin();
    open_args[i++] = "open";
    open_args[i++] = scatn(hostname(reconn.RHost), -1);     /* Copy */
    open_args[i++] = "-fp";
    open_args[i++] = scatn(locv(0, reconn.RPort), -1);	    /* Copy */
    open_args[i] = 0;
    fdprintf(2, "Opening connection to %s, port %s\r\n",
		open_args[1], open_args[3]);
    netopen(0, i, open_args);
    send();
}

/* -------------------------- S I Z E T S T ------------------------- */
/*
 * SizeTst() checks out the Approximate Record Size option. We are willing
 * to receive any record size; we can only xmit up to about CBUFSIZE ourselves.
 */
SizeTst(connp, org, op, nargs, args)
    struct NetConn *connp;
    int org;
    int op;

    int nargs;
    char *args;
{
    int size;

    size = (args[1] & 0377) * 256 + (args[2] & 0377);
    if (op == MY_WILL)
	return(CAN);
    else if (size < CBUFSIZE && size > 4)
	return(CAN);
    else
	return(CANT);
}

/* -------------------------- S I Z E A C T ------------------------- */
/*
 * SizeAct() does the work associated with the Approximate Record Size
 * option. If we are being asked to accept some record size (WILL), there is
 * nothing for us to do; if we are being asked to perform it (DO),
 * the connection's RecSize field is set appropriately.
 */
SizeAct(connp, modep, op, nargs, args)
    struct NetConn *connp;
    char *modep;
    int op;
    int nargs;
    char *args;
{
    int size;

    size = (args[1] & 0377) * 256 + (args[2] & 0377);
    if (op == MY_DO)
	connp->RecSize = size;
    else if (op == MY_DONT)
	connp->RecSize = 0; /* Unspecified size */
}

/* -------------------------- D I S P T S T ------------------------- */
/*
 * DispTst() approves the disposition option. The only form we do is
 * WILL SENDER (that is, telling us to perform disposition on
 * from-user/to-net data); DO RECEIVER (telling us to perform disposition
 * on from-net/to-user data) is not implemented. However, we are willing
 * to accept the other side's doing disposition: WILL RECEIVER and DO SENDER
 * are acceptable.
 *
 * The exception to the above is HT simulation, which gets done in the pty or tty.
 * The pty can only do it on from-pty/to-net data; the tty can only do it on
 * from-net/to-user data.
 */
DispTst(connp, org, op, nargs, args)
    struct NetConn *connp;
    int   org;
    int op;
    int nargs;
    char *args;
{
    int value;  /* Unsigned char */

    value = args[2] & 0377;

    if ((op==MY_WILL && args[1]==RECEIVER) || (op==MY_DO && args[1]==SENDER))
        return(CAN);
    else if (op == MY_DO && args[1] == RECEIVER)
    {
	if (args[0] == OPT_HT_DISP && value == DISP_SIMULATE)
            return(connp->UsingPty == 0? CAN : CANT);   /* The tty can do it */
        else
            return(CANT);
    }
    else if (op == MY_WILL && args[1] == SENDER)
/*
 * We do NOT turn line feeds into NL and spaces,
 * or FF or VT into the right number of blank lines.
 */
	if (value == DISP_SIMULATE)
            if (args[0] == OPT_LF_DISP || args[0] == OPT_FF_DISP || args[0] == OPT_VT_DISP)
                return(CANT);
            else if (args[0] == OPT_HT_DISP)         /* Simulate tabs? */
                return(connp->UsingPty? CAN : CANT); /* The tty can do it */
        else
            return(CAN);
}

/* -------------------------- D I S P A C T ------------------------- */
/*
 * Implement disposition options. Most of the work is actually done
 * by SendBuf below; all this routine implements is HT simulation,
 * which can be done purely by manipulating the pty or tty.
 */
DispAct(connp, modep, op, nargs, args)
    struct NetConn *connp;
    char *modep;
    int op;
    int nargs;
    char args[];
{
    int value;	/* Unsigned char */
    int out;
    int me;

    value = args[2] & 0377;

/* out is true if the action affects the output path of the pty or tty. */

    if (connp->UsingPty)
	if (op == MY_WILL || op == MY_WONT)
	    out = 1;
	else
	    out = 0;
    else
	if (op == MY_DO || op == MY_DONT)
	    out = 1;
	else
	    out = 0;

/* me is true if the local side is supposed to do the tab handling. */

    if (
	 (op == MY_WILL && args[1] == SENDER)
	 ||
	 (op == MY_DO && args[1] == RECEIVER)
       )
	me = 1;
    else
	me = 0;

    if (args[0] == OPT_HT_DISP)
    {
	if (!out)
	{
	    if (me && value == DISP_SIMULATE)
		com_err(0, "Can't expand tabs on input.");
	    return;
	}

	if (me && value == DISP_SIMULATE)
	    SetFlags(modep, XTABS, XTABS);
	else
	    SetFlags(modep, XTABS, 0);
    }

}
#endif

/* -------------------------- B I N T S T --------------------------- */
/*
 * We can do binary, in either direction, if we have the new tty driver
 * at our disposal.
 */
BinTst()
{
#ifdef NEWTTY
    return(CAN);
#endif
#ifndef NEWTTY
    return(CANT);
#endif
}

/* -------------------------- B I N A C T --------------------------- */
/*
 * Perform binary option. The pty is the mirror image of the tty.
 * There is a hidden assumption that if the user negotiates into binary
 * mode, he will negotiate out of it before negotiating into or out of
 * any other mode. Otherwise TTY settings could get upset. Solving
 * this problem for the general case is a real pain.
 */
BinAct(connp, modep, op, nargs, args)
    struct NetConn *connp;
    char *modep;
    int op;
    int nargs;
    char args[];
{
#ifdef NEWTTY
    struct SBlk *sbp;

    sbp = connp->StateP;

#define USER 1000
#define SERVER 2000

#define B_IN_IMASK (TI_CLR_MSB|TI_CRMOD|TI_NOSPCL|TI_ONECASE)
#define B_IN_ISET  TI_NOSPCL
#define B_IN_OMASK TO_XONXOFF
#define B_IN_OSET  0
/* Have to clear TO_XONXOFF to let ctrl-Q, ctrl-S in */

#define B_OUT_OMASK (TO_CLR_MSB|TO_CRMOD|TO_ONECASE|TO_XTABS|TO_AUTONL|TO_LITERAL)
#define B_OUT_OSET  TO_LITERAL

/* The handling of CRs specified by the protocol gets turned off in binary mode */

    switch(op)
    {
    case MY_DO:
	connp->ToNetBin = 1;
	break;
    case MY_DONT:
	connp->ToNetBin = 0;
	break;
    case MY_WILL:
	connp->ToUsrBin = 1;
	break;
    case MY_WONT:
	connp->ToUsrBin = 0;
	break;
    }

    op =+ connp->UsingPty? SERVER : USER;
    switch(op)
    {
    case USER	+ MY_DO :  /* We (user) sending binary */
    case SERVER + MY_WILL: /* We (server) rcving binary */
	sbp->oldiflags[args[0]] = SetXFlags(modep, 'i', B_IN_IMASK, B_IN_ISET);
	sbp->oldoflags[args[0]] = SetXFlags(modep, 'o', B_IN_OMASK, B_IN_OSET);
	SetXFlags(modep, 'p', TP_PENABLE, 0);
	break;

    case USER	+ MY_DONT:
    case SERVER + MY_WONT:
	SetXFlags(modep, 'i', B_IN_IMASK, sbp->oldiflags[args[0]]);
	SetXFlags(modep, 'o', B_IN_OMASK, sbp->oldoflags[args[0]]);
	/* Restoring TP_PENABLE is not worth the trouble */
	break;

    case USER	+ MY_WILL: /* We (user) rcving binary */
    case SERVER + MY_DO:   /* We (server) sending binary */
	sbp->oldoflags[args[0]] = SetXFlags(modep, 'o', B_OUT_OMASK, B_OUT_OSET);
	break;

    case USER	+ MY_WONT:
    case SERVER + MY_DONT:
	SetXFlags(modep, 'o', B_OUT_OMASK, sbp->oldoflags[args[0]]);
	break;
    }
#endif
}

/* -------------------------- S N A P ------------------------------- */
/*
 * dump out a description of an option. Assumes that opnum has been made
 * to fit inside the option table, and assumes that length checking on
 * nargs has already been done. Knows about some options; for those it
 * knows nothing about, it prints one byte of argument as a single number,
 * two bytes as a single word, and more than that as individual bytes.
 */
snap(msg, op, nargs, args)
    char *msg;
    int op;
    int nargs;
    char *args;
{
    register char *ap;
    int opnum;
    struct Reconn reconn;
    register char *rp;

    ap = args;
    opnum = *ap++;
    nargs--;
    fdprintf(2, "%s %s ", msg, which[op]);
    if (opnum >= 0 && opnum < OPT_UNIMPL)
	fdprintf(2, "%s ", opt[opnum].optname);
    else
	fdprintf(2, "Unimplemented option ");
    if (opt[opnum].optsr)
    {
	if (nargs > 0)
	    if (*ap == SENDER)
		fdprintf(2, "sender ");
	    else if (*ap == RECEIVER)
		fdprintf(2, "receiver ");
	    else
		fdprintf(2, "%d? (should be sender or receiver) ", *ap & 0377);
	nargs--;
	ap++;
    }
    if (nargs == 1)
	fdprintf(2, "%d", *ap & 0377);
    else if (nargs == 2)
	fdprintf(2, "%d", (*ap & 0377) * 256 + (*(ap+1) & 0377));
    else if (opnum == OPT_RECONNECT)
    {
	rp = &reconn;
	while (nargs-- > 0)
	    *rp++ = *ap++;
	fdprintf(2, "host %s port 0%o ", hostname(reconn.RHost), reconn.RPort);
	if (reconn.RType == START)
	    fdprintf(2, "start");
	else if (reconn.RType == EXPECT)
	    fdprintf(2, "expect");
	else
	    fdprintf(2, "%d? (should be start or expect)", reconn.RType);
    }
    else
	while(nargs-- > 0)
	    fdprintf(2, "%d ", *ap++);
    fdprintf(2, "\r\n");
}

/* -------------------------- O P T I O N --------------------------- */
/*
 * option(arg, argc, argv) is the option-negotiation command. It starts
 * the negotiation process for the requested option.
 * For THP, the connection mode is also handled here, as though it were
 * a genuine negotiated option. (God only knows why it isn't...)
 * "do" and "dont" are mapped into REQUEST_MODE, and "will" and "wont"
 * are mapped into SET_MODE.
 */
option(arg, argc, argv)
    int arg;
    int argc;
    char *argv[];
{
    int i, type;
    int nmatch, prevmatch;
    int candidate;
    int value;
#ifdef THP
    char *cmode[2];
    struct Reconn reconn;
    char r[sizeof(reconn)+1];
    char *rp;
#endif
    extern char *compar();
    extern long gethost();
    extern char *atoiv();
    extern struct NetConn *NetConP;

#ifdef THP
    cmode[RECORD] = "record";
    cmode[STREAM] = "stream";
#endif

    if (argc == 0)
    {
        fdprintf(2, "Try to negotiate the specified option.\r\n");
        return;
    }
    if (argc < 2)
    {
        fdprintf(2, "Usage: %s option\r\n", argv[0]);
        fdprintf(2, "Available options are:\r\n\t  ");
#ifdef TELNET
        for (i = 0; i < entries(opt); i++)
        {
            fdprintf(2, "%-20s", opt[i].optname);
#endif
#ifdef THP
        for (i = 0; i < entries(opt) + 2; i++)
        {
            fdprintf(2, "%-20s",
            i < entries(opt)? opt[i].optname : cmode[i - entries(opt)]);
#endif
            if ((i+1) % 3 == 0)
                fdprintf(2, "\r\n\t  ");
        }
        fdprintf(2, "\r\n");
        return;
    }
    if (NetConP == 0 || NetConP->StateP == 0)
    {
        fdprintf(2, "No active connection\r\n");
        return;
    }
    if      (arg == DO)   type = MY_DO;
    else if (arg == DONT) type = MY_DONT;
    else if (arg == WILL) type = MY_WILL;
    else if (arg == WONT) type = MY_WONT;
    else
    {
        com_err(0, "Error in command table\r\n");
        return;
    }
    prevmatch = 0;
    candidate = -1;
#ifdef TELNET
    for (i = 0; i < entries(opt); i++)
        if (nmatch = compar(argv[1], opt[i].optname))
#endif
#ifdef THP
    for (i = 0; i < entries(opt) + 2; i++)
        if (nmatch = compar(argv[1], i < entries(opt)?
                    opt[i].optname : cmode[i - entries(opt)]))
#endif
            if (nmatch == -1)
            {
                candidate = i;
                break;
            }
            else if (nmatch > prevmatch)
            {
                candidate = i;
                prevmatch = nmatch;
            }
            else if (nmatch == prevmatch)
            {
                fdprintf(2, "%s: option not unique\r\n", argv[0]);
                return;
            }
    if (candidate == -1)
    {
        fdprintf(2, "%s: option not recognized. %s\r\n", argv[0], argv[1]);
        return;
    }

    if (candidate < entries(opt))    /* Normal neg. opt. */
    {
        if (opt[candidate].optlength == 1)
            negotiate(NetConP, 1, type, 1, &candidate);
#ifdef THP
        else if (candidate == OPT_RECONNECT)
        {
            if (argc != 4)
            {
                fdprintf(2, "Usage: %s %s host port\r\n", argv[0], argv[1]);
                return;
            }
            reconn.RHost = gethost(argv[2]);
            if (*atoiv(argv[3], &reconn.RPort))
            {
                fdprintf(2, "Non-numeric port: %s\r\n", argv[3]);
                return;
            }
            reconn.RType = START;
            rp = &reconn;
            r[0] = OPT_RECONNECT;
            for (i = 1; i < sizeof(reconn)+1; i++)
                r[i] = rp[i-1];
            negotiate(NetConP, 1, type, i, r);
        }
        else if (candidate == OPT_RECORD_SIZE)
        {
            if (argc != 3)
            {
                fdprintf(2, "Usage: %s %s record-size\r\n", argv[0], opt[candidate].optname);
                return;
            }
            if (*atoiv(argv[2], &value) || value < 0)
            {
                fdprintf(2, "Bad record size: %s\r\n", argv[2]);
                return;
            }
            r[0] = candidate;
            r[1] = value/256;
            r[2] = value%256;
            negotiate(NetConP, 1, type, 3, r);
        }
        else if (opt[candidate].optlength == 3 && opt[candidate].optsr)
        {
            if (argc<3 || argc>4 ||  (argc==4 && argv[3][0] != 's' && argv[3][0] != 'r')
            ||  *atoiv(argv[2], &value))
            {
                fdprintf(2, "Usage: %s %s value [sender|receiver]\r\n",
                argv[0], argv[1]);
                return;
            }
            r[0] = candidate;
            r[1] = argc == 3? SENDER : argv[3][0] == 's'? SENDER : RECEIVER;
            r[2] = value;
            negotiate(NetConP, 1, type, 3, r);
        }
#endif
        else
            fdprintf(2, "Not yet implemented: %s %s\r\n", argv[0], argv[1]);
    }
#ifdef THP
    else
    {
        candidate =- entries(opt);    /* candidate is now 0 or 1 */
        if (type == MY_WONT || type == MY_DONT)
            candidate = 1 - candidate;  /* Reverse state */
        if (type == MY_WILL || type == MY_WONT)
        {
            SendCtrl(NetConP, SET_MODE, 1, &candidate);
            NetConP->StateP->XMode = candidate;
        }
        else
            SendCtrl(NetConP, REQUEST_MODE, 1, &candidate);
    }
#endif
}

/* -------------------------- S E N D C T R L ----------------------- */
/*
 * SendCtrl(connp, ctrl, len, args) sends the args over the net
 * by putting them in the ToNetBuf for the given connection.
 * It puts in the character ctrl, followed by len characters from args.
 * For THP, it inserts a two-byte count.
 */
SendCtrl(connp, ctrl, alen, args)
    struct NetConn *connp;
    char ctrl;
    int alen;
    char *args;
{
    register int len;
    register char *ap;

    len = alen;    /* ctrl doesn't count */
    ap = args;
    Cputc(IAC, connp->ToNetBuf);
#ifdef THP
    Cputc(len >> 8, connp->ToNetBuf);     /* High byte of count */
    Cputc(len & 0377, connp->ToNetBuf);   /* Low byte of count */
#endif
    Cputc(ctrl, connp->ToNetBuf);
    while(len-- > 0)
        Cputc(*ap++, connp->ToNetBuf);
}

/* -------------------------- S E N D B U F ----------------------- */
/*
 * SendBuf(connp, len, buf) checks the buffer for characters which require
 * padding, and puts out THP REPEAT records for the appropriate number
 * of nulls, or discards the character, as required. The text itself
 * is also put out.
 *
 * Note that no attempt is made to keep CR and LF together. If the
 * user requested CR padding, it was undoubtedly because he wanted
 * CRs padded, not LFs.
 */
SendBuf(connp, len, inbufp)
    struct NetConn *connp;
    int len;
    char *inbufp;
{
#ifdef TELNET
    NetBuf(connp, len, inbufp);
#endif
#ifdef THP
    register char *op;
    register char *ip;
    struct SBlk *sbp;
    int optnum;
    int action;
    char obuf[CBUFSIZE];
    char rpt[2];    /* REPEAT record -- count, char */

    sbp = connp->StateP;
    rpt[1] = '\0';  /* Pad with nulls */

    op = &obuf[0];
    for (ip = inbufp; ip < inbufp+len; ip++)
    {
	switch(*ip)
	{
	case '\t':
	    optnum = OPT_HT_DISP;
	    break;
	case '\n':
	    optnum = OPT_LF_DISP;
	    break;
	case '\13':
	    optnum = OPT_VT_DISP;
	    break;
	case '\14':
	    optnum = OPT_FF_DISP;
	    break;
	case '\r':
	    optnum = OPT_CR_DISP;
	    break;
	default:
	    *op++ = *ip;
	    continue;
	}
	if (sbp->state[optnum][WILL_SIDE] == YES
	&& sbp->value[optnum][WILL_SIDE][0] == SENDER)
		action = sbp->value[optnum][WILL_SIDE][1];
	else
	    action = 0;
	action =& 0377;
/* Discard? */
	if (action == DISP_DISCARD)
	    continue;
/* Replace with CRLF? */
	else if (action == DISP_CRLF && (*ip == '\13' || *ip == '\14'))
	{
	    NetBuf(connp, op - obuf, obuf);
	    op = obuf;
	    SendBuf(connp, 2, "\r\n");	/* Handle padding on them, too */
	}
	else if (action)
	{
	    *op++ = *ip;
	    NetBuf(connp, op - obuf, obuf);
	    op = obuf;
	    rpt[0] = action;
	    SendCtrl(connp, REPEAT, 2, rpt);
	}
	else
	    *op++ = *ip;
    }
    if (op - obuf > 0)
	NetBuf(connp, op - obuf, obuf);
#endif
}

/* -------------------------- N E T B U F ------------------------- */
/*
 * NetBuf(connp, len, buf) sends the given bufferful of data over the net.
 * For TELNET, IACs are doubled. For THP,
 * if the connection mode is RECORD, or if the buffer contains an IAC,
 * the data is sent as a record of type DATA; otherwise it is just
 * sent as a stream.
 */
NetBuf(connp, len, buf)
    struct NetConn *connp;
    int len;
    char buf[];
{
#ifdef THP
    register char *ep;      /* Points to end of buf */
    register char *bp;      /* Current position in buf */
    register int chunk;
    int recfm;              /* Just for this bufferful */

    ep = buf + len;
    recfm = STREAM;
    if (connp->StateP->XMode == RECORD)
        recfm = RECORD;
    else
        for (bp = &buf[0]; bp < ep; bp++)
            if ((*bp & 0377) == (IAC & 0377))
            {
                recfm = RECORD;
                break;
            }

    for (bp = &buf[0]; bp < ep;)
    {
        chunk = ep - bp;
        if (recfm == RECORD && connp->RecSize > 0 && connp->RecSize < chunk)
            chunk = connp->RecSize;
        if (recfm == RECORD)
        {
            SendCtrl(connp, DATA, chunk, bp);
            bp =+ chunk;
        }
        else
            while (chunk-- > 0)
                Cputc(*bp++, connp->ToNetBuf);
    }
#endif
#ifdef TELNET
    register int l;
    register char *bp;

    l = len;
    bp = &buf[0];
    while(l-- > 0)
    {
        if ((*bp & 0377) == (IAC & 0377))
            Cputc(IAC, connp->ToNetBuf);
        Cputc(*bp++, connp->ToNetBuf);
    }
#endif
}

/* -------------------------- S E N D S Y N C H --------------------- */
/*
 * SendSynch(connp) sends a synch sequence. It should be called AFTER
 * the appropriate telnet op (e.g. IP) has been sent.
 */
SendSynch(connp)
struct NetConn *connp;
{

    SendCtrl(connp, DM, 0, 0);
    SendUrg(connp);
}

/* -------------------------- G L O B A L S ------------------------- */