BBN-Vax-TCP/src/telnet/netc.c

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

#
/*
 * Finite-state-machine for handling TELNET controls.
 * This file contains all of the real intelligence needed for
 * handling the TELNET protocol.
 * Use: first allocate a conn block using telinit(fd).
 * Then
 *	init ToNetBuf, ToUsrBuf to point at suitable areas
 *	for each char received from net
 *	    NetChar(connp, char)
 * where connp is a ptr to a NetConn block.
 * NetChar will write to ToUsrBuf or ToNetBuf as required. If ToNetBuf
 *	is not empty, write it out to the net.
 */

#include "stdio.h"
#include "tnio.h"
#include "telnet.h"

/* States of FSM; EXP stands for "expected" */

/* PROTINIT has already been defined. */
#define CTRL_EXP	  1
#define OPTION_EXP	  2
#define SUBNEG_OPT_EXP	  3
#define WITHIN_SUBNEG	  4
#define IAC_WITHIN_SUBNEG 5

/* -------------------------- C A L L ------------------------------- */
/*
 * The call macro is used to call optionally-defined functions.
 * Given a pointer to a function, and the arguments for that function,
 * it calls the function if and only if the pointer is non-null.
 */
#define call(f,args) { if ((f) != NULL) (*(f))args; }

struct OptTab  /* Option negotiation table */
{
    int   optspec;     /* Set to skip already-in-effect test (see Negotiate) */
    int (*testf)();    /* Test function */
    int (*actf)();     /* Action routine */
    int (*sbf)();      /* Subnegotiation routine */
};

extern Never(), NoAct();
struct OptTab opt[N_OPTIONS] =
{

/* ---- Spec --- Test ------ Act ---- Subneg ----- */

	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
	{0,	 NULL,       NULL,    NULL},
};

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

#define OPT_UNIMPL (N_OPTIONS - 1)

int verbose;

/* -------------------------- U S E R C H A R ----------------------- */

#define UserChar(c,conn) Bputc(c,connp->ToUsrBuf)

/* -------------------------- 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 OptCtrl, and call Negotiate()
 * when the control has been assembled.
 * For other controls, wait until they are all gathered up and then act on them.
 * Call UserChar as required.
 */
NetChar(connp, c)
    register NETCONN *connp;
    register int c; /* Unsigned char */
{
    register int state;
    extern int verbose;

    c &= 0377;
    state = connp->ProtState;

    switch (state)
    {
    case PROTINIT:
	if (c == IAC)
	    state = CTRL_EXP;
	else
	{
#	ifdef NCP
	    if (connp->INSCount <= 0)
#	endif NCP
#	ifdef TCP
	    if (!(connp->UrgInput))
#	endif TCP
		UserChar(c, connp);
	}
	break;

    case CTRL_EXP:
	switch(c)
	{
	case SB:
	    state = SUBNEG_OPT_EXP;
	    Bsetbuf(connp->ToOptBuf, connp->SBarray, sizeof(connp->SBarray));
	    break;
	case WILL:
	    connp->OptCtrl = TN_WILL;
	    state = OPTION_EXP;
	    break;
	case DO:
	    connp->OptCtrl = TN_DO;
	    state = OPTION_EXP;
	    break;
	case WONT:
	    connp->OptCtrl = TN_WONT;
	    state = OPTION_EXP;
	    break;
	case DONT:
	    connp->OptCtrl = TN_DONT;
	    state = OPTION_EXP;
	    break;
	default:
	    NetCtrl(connp, c);
	    state = PROTINIT;
	    break;
	}
	break;
    case OPTION_EXP:
	connp->OptNum = c;
	Negotiate(connp, 0, connp->OptCtrl, connp->OptNum);
	state = PROTINIT;
	break;
    case SUBNEG_OPT_EXP:
	connp->OptNum = c;
	state = WITHIN_SUBNEG;
	break;
    case WITHIN_SUBNEG:
	if (c == IAC)
	    state = IAC_WITHIN_SUBNEG;
	else
	    Bputc(c, connp->ToOptBuf);
	break;
    case IAC_WITHIN_SUBNEG:
	if (c == IAC)
	{
	    Bputc(c, connp->ToOptBuf);
	    state = WITHIN_SUBNEG;
	}
	else if (c == SE)
	{
	    if (verbose)
	    {
		int i;
		char * p;

		fprintf(stderr, "Received subneg for option %d: ", connp->OptNum);
		for (p = Baddr(connp->ToOptBuf), i = 0; i < Blen(connp->ToOptBuf); i++, p++)
		    fprintf(stderr, "0%o ", *p & 0377);
		fprintf(stderr, "\n");
	    }

	    if (opt[connp->OptNum].sbf != NULL)
		(*(opt[connp->OptNum].sbf))(connp, connp->OptNum,
					    Baddr(connp->ToOptBuf), Blen(connp->ToOptBuf));
	    state = PROTINIT;
	}
	else
	    state = WITHIN_SUBNEG;
	break;
    default:
	fprintf(stderr, "TELNET library error: unknown state %d", state);
	state = PROTINIT;
	break;
    }

    connp->ProtState = state;
    return(state);
}

/* -------------------------- N E T C T R L ------------------------- */
/*
 * Handle all one-character TELNET controls.
 */
NetCtrl(connp, c)
    register NETCONN *connp;
    int c;  /* Unsigned char */
{
    switch(c)
    {
    case IAC:
	UserChar(IAC, connp);
	break;
    case DM:
#	ifdef NCP
	    connp->INSCount--;
#	endif NCP
#	ifdef TCP
	    if (Bleft(connp->FmNetBuf) == 0) connp->UrgInput = 0;
#	endif
	break;
    case BREAK:
	call(connp->BREAKf, (connp));
	break;
    case AYT:
	call(connp->AYTf, (connp));
	break;
    case IP:
	call(connp->IPf, (connp));
	break;
    case EC:
	call(connp->ECf, (connp));
	break;
    case EL:
	call(connp->ELf, (connp));
	break;
    case AO:
	call(connp->AOf, (connp));
	break;
    case GA:
	call(connp->GAf, (connp));
	break;
    case NOP:
	break;
    default:
	fprintf(stderr, "Unknown TELNET control 0%o (%d.) ignored", c, c);
	break;
    }
}

/* -------------------------- 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 option number.
 * Return value is 0 if OK, nonzero if some problem, like option number out
 * of range or test function said no.
 *
 * 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. Such deeper issues are
 * finessed by allowing the user to set opt.optspec, which bypasses this check
 * altogether.
 *
 * If it is not in effect, we call the test function to see whether it can be
 * done, and reply appropriately. We normally 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. (Hence, the test function
 * must be called anyway if opt.optspec is set.)
 * 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.) should be resolved in the
 * action routine.
 */

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

Negotiate(connp, org_here, op, opnum)
    register NETCONN * connp;
    int org_here;
    int op;
    int opnum;
{
#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;
    int verdict;
    extern int verbose;

    if (setexit())
	return(1);  /* Some error happened */

    rop = op;
    if (org_here)   /* Reverse DO and WILL */
	if	(op == TN_DO)	op = TN_WILL;
	else if (op == TN_WILL) op = TN_DO;
	else if (op == TN_DONT) op = TN_WONT;
	else			op = TN_DONT;

    side = (op == TN_DO || op == TN_DONT)? DO_SIDE : WILL_SIDE;
    nstate = (op == TN_DO || op == TN_WILL)? YES : NO;

    if (opnum >= OPT_UNIMPL)
	if (org_here)
	    rcmderr(0, "Unimplemented option.\n");
	else if (nstate == YES)
	{
	    SendReply(connp, CANT, op, opnum);
	    return(0);
	}

    if (org_here)			    /* Origination */
    {
	if (nstate == NO ||
	    (opt[opnum].testf != NULL && (*opt[opnum].testf)(connp, org_here, op, opnum) == CAN))
	{
	    if (verbose) snap("Sending", rop, opnum);
	    SendReply(connp, YES, op, opnum);
	    connp->Orig[opnum][side] = YES;
	}
	else
	{
	    if (verbose) snap("Unimplemented:", rop, opnum);
	    reset(1);
	}
    }

/* Reply to foreign origination */
    else if (connp->Orig[opnum][side] == NO)
    {
	if (opt[opnum].optspec && opt[opnum].testf != NULL)
	    verdict = (*opt[opnum].testf)(connp, org_here, op, opnum);
	else
	{
	    if (connp->OptState[opnum][side] == nstate)
		verdict = ALREADY;
	    else if (nstate == NO)
		verdict = CAN; /* Can always DONT/WONT */
	    else if (opt[opnum].testf == NULL)
		verdict = CANT;
	    else
		verdict = (*opt[opnum].testf)(connp, org_here, op, opnum);
	}
	if (verdict == ALREADY)
	    return(1);

	if (nstate == NO)
	    verdict = CAN;  /* Force the test function to comply with the protocol */

	SendReply(connp, verdict, op, opnum);
	if (verdict == CAN)
	{
	    if (verbose) snap("Affirming", op, opnum);
	    call(opt[opnum].actf, (connp, op, opnum));
	    connp->OptState[opnum][side] = nstate;
	}
    }

    else				     /* Reply to our origination */
    {
	if (verbose) snap("Reply was", op, opnum);
	call(opt[opnum].actf, (connp, op, opnum));
	connp->Orig[opnum][side] = NO;
	connp->OptState[opnum][side] = nstate;
    }
    return(0);
}

/* -------------------------- S E N D R E P L Y --------------------- */
/*
 * SendReply(connp, reply, op, opnum) replies to a negotiated
 * option. If reply is nonzero, it replies affirmatively; if reply is
 * zero, it replies negatively. op is TN_WILL/WONT/DO/DONT; opnum is the
 * option number.
 */
SendReply(connp, reply, op, opnum_int)
    struct NetConn *connp;
    int reply;
    int op;	/* Unsigned char */
    int opnum_int;  /* Unsigned char */
{
    char opnum;
    extern int verbose;

    opnum = opnum_int;
    switch (op)
    {
    case TN_DO:
	if (reply == 0)
	{
	    SendCtrl(connp, WONT, 1, &opnum);
	    if (verbose) snap("Refusing", op, opnum);
	}
	else
	    SendCtrl(connp, WILL, 1, &opnum);
	break;
    case TN_WILL:
	if (reply == 0)
	{
	    SendCtrl(connp, DONT, 1, &opnum);
	    if (verbose) snap("Refusing", op, opnum);
	}
	else
	    SendCtrl(connp, DO, 1, &opnum);
	break;
    case TN_DONT:
	SendCtrl(connp, WONT, 1, &opnum);
	break;
    case TN_WONT:
	SendCtrl(connp, DONT, 1, &opnum);
	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;
}

/* -------------------------- S E N D C T R L ----------------------- */
/*
 * SendCtrl(connp, OptCtrl, len, args) sends the args over the net
 * by calling SendC.
 * It puts in IAC, OptCtrl, and len characters from args.
 */
SendCtrl(connp, OptCtrl, alen, args)
    struct NetConn *connp;
    char OptCtrl;
    int alen;
    char *args;
{
    register int len;
    register char *ap;
    extern int AutoFlush;

    len = alen;    /* OptCtrl doesn't count */
    ap = args;
    SendC(connp, IAC);
    SendC(connp, OptCtrl);
    while(len-- > 0)
	SendC(connp, *ap++);
    if (AutoFlush)
	telflush(connp);
}


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

/* -------------------------- B R E A K D E F A U L T --------------- */
/*
 * Default handler for TELNET BREAK controls. Writes a NUL.
 */
BREAKdefault(connp)
    struct NetConn *connp;
{
    UserChar('\0', connp);
}

/* -------------------------- A Y T D E F A U L T ------------------- */
/*
 * Default handler for TELNET AYT controls. Sends back IAC NOP, "Yes\n".
 */
AYTdefault(connp)
    struct NetConn *connp;
{
    SendCtrl(connp, NOP, 0, 0);
    telwrite(connp, "Yes\r\n", 5);
}

/* -------------------------- I P D E F A U L T --------------------- */
/*
 * Default handler for TELNET IP controls. Sends a RUBOUT to the user,
 * returns a SYNCH to the other side.
 */
IPdefault(connp)
    struct NetConn *connp;
{
    UserChar('\177', connp);
    SendSynch(connp);
}

/* -------------------------- S N A P ------------------------------- */
/*
 * dump out a description of an option. Assumes that opnum has been made
 * to fit inside the option table.
 */
snap(msg, op, opnum)
    char *msg;
    int op;   /* Unsigned char */
    int opnum;/* Unsigned char */
{
    static char *which[] = { "don't", "do", "will", "won't"};
    static char *names[] = {
	"binary",
	"echo",
	"reconnect",
	"suppress-go-ahead",
	"set-record-size",
	"status",
	"timing-mark",
	"RCTE",
	"linewidth",
	"pagesize",
	"cr-disp",
	"ht-stops",
	"ht-disp",
	"ff-disp",
	"vt-stops",
	"vt-disp",
	"lf-disp",
    };

    fprintf(stderr, "%s %s ", msg, which[op]);
    if (opnum >= 0 && opnum < sizeof(names)/sizeof(names[0]))
	fprintf(stderr, "%s ", names[opnum]);
    else
	fprintf(stderr, "%d ", opnum);
    fprintf(stderr, "\r\n");
}

/* -------------------------- S E T O P T --------------------------- */
/*
 * Set up for an option.
 */
setopt(option, skflag, test, actor, subneg)
    int option;
    int skflag;
    int (*test)();
    int (*actor)();
    int (*subneg)();
{
    if (option >= N_OPTIONS)
	return(-1);
    opt[option].optspec = skflag;
    opt[option].testf = test;
    opt[option].actf = actor;
    opt[option].sbf = subneg;

    return(0);
}

/* -------------------------- S E N D O P T ------------------------- */
/*
 * Send an option negotiation request. The work is actually
 * done by Negotiate. Return error code from Negotiate.
 */
sendopt(connp, opcode, option)
    register NETCONN * connp;
    int opcode;
    int option;
{
    return(Negotiate(connp, 1, opcode, option));
}

/* -------------------------- S E N D S U B ------------------------- */
/*
 * Send a subnegotiation.
 */
sendsub(connp, option, subp, sublen)
    register NETCONN * connp;
    char option;
    char * subp;
    int sublen;
{
    SendCtrl(connp, SB, 0, 0);
    telwrite(connp, &option, 1);
    telwrite(connp, subp, sublen);
    SendCtrl(connp, SE, 0, 0);
    if (verbose)
    {
	fprintf(stderr, "Sending subneg for option %d: ", option);
	while (sublen-- > 0)
	    fprintf(stderr, "0%o ", *subp++ & 0377);
	fprintf(stderr, "\n");
    }
}

/* -------------------------- T E L S T A T U S --------------------- */
/*
 * Report status of the given TELNET option.
 */
telstatus(connp, option)
    register NETCONN * connp;
    int option;
{
    int retval;

    retval = 0;
    if (option < 0 || option >= N_OPTIONS)
	return(retval);
    if (connp->OptState[option][DO_SIDE])
	retval += 1;
    if (connp->OptState[option][WILL_SIDE])
	retval += 3;
    return(retval);
}

/* -------------------------- S E N D C T L ------------------------- */
/*
 * Send a TELNET control. AO and IP must be followed by a SYNCH.
 */
sendctl(connp, c)
    register NETCONN * connp;
    int c;
{
    switch(c)
    {
    case EC:
    case EL:
    case BREAK:
    case AYT:
    case NOP:
    case GA:
	SendCtrl(connp, c, 0, 0);
	break;
    case IP:
    case AO:
	SendCtrl(connp, c, 0, 0);
	SendSynch(connp);
	break;
    }
}

/* ------------------------------------------------------------------ */