BBN-Vax-TCP/src/telnet/netc.c
#
/*
* 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;
}
}
/* ------------------------------------------------------------------ */