BBN-V6/telnet/main.c
#include "cbuf.h"
#include "protocol.h"
#include "/sys/sys/h/sgttybuf.h"
#include "netconn.h"
#define EINTR 4 /* System call interrupted (by SIGINR) */
/* -------------------------- M A I N ----------------------------- */
main(argc, argv)
int argc;
char *argv[];
{
char *p;
int i, j;
static char argbuf[512]; /* argv stuff copied into here */
extern char *progname;
extern char *arglist[];
extern int prompt;
extern process();
extern FromUser();
/* Copy argument list where it will be safe from ravages of sbrk */
for (i = 0, p = argbuf; i < argc && i < 50; i++)
{
arglist[i] = p;
p = scopy(argv[i], p, &argbuf[sizeof(argbuf)-1]) + 1;
}
arglist[argc] = 0; /* Make it useful for execs */
/* Initialize */
progname = arglist[0];
/* Process -e (execute remaining args) */
for (i = 1; i < argc; i++)
if (seq(argv[i], "-e"))
{
for (j = i+1; j < argc; j++)
ExecCmd(argv[j], slength(argv[j]));
break;
}
argc = i; /* Ignore them */
/* If there are any other arguments, do a net open with them. */
if (argc > 1)
netopen(1, argc, argv); /* The 1 means exit on error */
else
{
#ifdef TELNET
fdprintf(2, "User Telnet version 2.\r\n");
#endif
#ifdef THP
fdprintf(2, "User THP version 2.\r\n");
#endif
fdprintf(2, "Type \"%chelp\" for information.\r\n", prompt);
}
/* Queue up user-stream handler. The handler argument of 0 is used
* to indicate that this stream is the one from the user, and should
* have command interpretation performed on it.
*/
IoEnter(0, FromUser, 0, 0); /* Read from standard input */
IoRun(20); /* Loop forever, awaiting 20 seconds */
fatal(0, "Unexpected return from IoRun");
}
/* -------------------------- T O U S E R --------------------------- */
/*
* ToUser copies from the ToUsrBuf for this connection to the user/pty stream.
* If connp is 0, it is handling the user, and uses NetConP.
*/
ToUser(fd, cap, connp)
int fd;
int cap;
struct NetConn *connp;
{
extern int errno;
extern struct NetConn *NetConP;
if (cap < 0)
cap = 32767; /* Infinity */
if (connp == 0)
connp = NetConP; /* Use current connection */
if (cap > 0 && connp != 0 && !Cempty(connp->ToUsrBuf))
{
switch(Cwrite(fd, connp->ToUsrBuf, cap))
{
case -1:
if (errno == EINTR)
break; /* Ignore it */
else
fatal(errno, "writing user output");
case 0:
fatal(0, "writing user output returned EOF");
}
return(0);
}
else
return(1); /* Did nothing */
}
/* -------------------------- F R O M U S E R ----------------------- */
/*
* FromUser() is called by IoRun() when there is input from the user or pty.
* It uses UsingPty to decide which. If ToNetBuf is less than half full,
* FromUser reads the input fd. If ToNetBuf is more than half full, then it reads
* only if this is the user stream and command processing is turned on,
* in which case it still only reads enough to fit (assuming worst case
* expansion of each character), or at least one char, anyway. This algorithm
* postpones the discarding of chars from the user as long as possible.
*/
FromUser(fd, cap, aconnp)
int fd;
int cap;
struct NetConn *aconnp;
{
register struct NetConn *connp;
int nread, nreq;
char inbuf[(CBUFSIZE/NETGROW)+1];
int usersw;
extern int process();
extern int errno;
extern int OldInput;
extern int OldPrompt;
extern struct NetConn *NetConP;
extern int prompt;
connp = aconnp;
if (connp == 0)
connp = NetConP;
if (connp == 0 || connp->UsingPty == 0)
usersw = 1;
else
usersw = 0;
if (connp != 0)
{
if (connp->RemState == SUSP)
return(1);
nreq = CBUFSIZE - Clen(connp->ToNetBuf);
}
else
nreq = 1;
if (nreq < CBUFSIZE/2)
if (usersw == 0 || prompt == -1)
return(1); /* Not enough room to make it worthwhile */
else /* Command processing turned on, read anyway */
if (nreq == 0)
nreq = 1;
if (nreq >= NETGROW)
nreq =/ NETGROW; /* Assume every char expands into maximum of protocol */
if (nreq > cap && cap >= 0)
nreq = cap;
if (nreq > 0)
{
RETRY:
switch(nread = read(fd, inbuf, nreq))
{
case -1:
if (errno == EINTR)
goto RETRY;
fatal(errno, "reading user input");
default:
break;
case 0:
if (usersw && OldInput >= 0)
{
movefd(OldInput, fd);
prompt = OldPrompt;
OldInput = -1;
awtenb(fd);
com_err(0, "Input file sent.");
goto RETRY;
}
IoDelete(fd); /* Don't try to read any more */
break;
}
if (usersw)
process(connp, inbuf, nread);
else
servproc(connp, inbuf, nread);
return(0);
}
return(1);
}
/* -------------------------- S E R V P R O C ----------------------- */
/*
* Handle output of PTY going out over network.
* If a pty puts out CR-LF, it should turn into CR-LF,
* and if it puts out CR-CR-LF, it should turn into CR-NUL-CR-LF.
* So just put out the CR, and remember. On the next char, if it is not LF,
* put out a NUL first. The TELNET protocol really needs an IAC NEWLINE...
*/
servproc(aconnp, inbuf, nread)
struct NetConn *aconnp;
char *inbuf;
int nread;
{
register struct NetConn *connp;
register char *top;
register char c;
register char *op;
char *ip;
static char outbuf[CBUFSIZE];
int count;
connp = aconnp;
op = outbuf;
top = &inbuf[nread];
for (ip = inbuf; ip < top; ip++)
{
c = *ip;
if (connp->ToNetCr && c != '\n')
*op++ = '\0';
*op++ = c;
connp->ToNetCr = 0;
if (c == '\r' && connp->ToNetBin == 0)
connp->ToNetCr = 1;
}
count = op - outbuf;
if (connp != 0 && count > 0)
SendBuf(connp, count, outbuf);
}
/* -------------------------- U S E R C H A R ----------------------- */
/*
* Handle chars going to user or pty. The protocol says that a CR will
* be followed by LF if it is meant as a newline, or by a NUL if it
* is meant as a 'real' CR. For a pty, this means that the char after
* the CR should always be suppressed (as long as it is CR or NUL).
* For a user, the full CR-LF sequence should be output, but CR-NUL
* should be stripped of the NUL.
*/
UserChar(c, aconnp)
char c;
struct NetConn *aconnp;
{
register struct NetConn *connp;
extern int errno;
connp = aconnp;
if (c == '\r' && connp->ToUsrBin == 0)
{
Cputc(c, connp->ToUsrBuf);
connp->ToUsrCr = 1;
}
else if (connp->ToUsrCr == 0)
Cputc(c, connp->ToUsrBuf);
else
{
if (c == '\0' || (c == '\n' && connp->UsingPty))
; /* Drop the char */
else
Cputc(c, connp->ToUsrBuf);
connp->ToUsrCr = 0;
}
}
/* -------------------------- N E T I N T R ------------------------- */
netintr()
{
extern struct NetConn *NetConP;
SendCtrl(NetConP, IP, 0, 0);
SendSynch(NetConP);
signal(2, &netintr);
}
/* -------------------------- O P E N P T Y ------------------------ */
/*
* openpty() finds the first available pty and opens it for reading an
* writing. It first tries to open the given name; if that fails, it adds
* "step" to the last letter and tries again. It continues until it finds
* a pty it can open, in which case it returns the fd of the pty, or until
* it runs out, in which case it returns -1.
*/
openpty(ptyname, step)
char *ptyname;
int step;
{
register char *s1;
int ptyfd;
extern int errno;
/* Find end of device name */
s1 = ptyname;
while (*s1++) ;
s1 =- 2;
for (; ; (*s1) =+ step)
{
ptyfd = open(ptyname, 2); /* Try this PTY */
if (ptyfd >= 0)
return(ptyfd);
if (errno == 2)
return(-1); /* No such file. Assume end of PTYs */
}
}
#define CMDBEGIN 10
#define CMDEND 20
#define NONE 30
/* -------------------------- P R O C E S S ------------------------- */
/*
* Read from input buffer inbuf and put in command buffer cline (contained in
* this routine) or output buffer via SendBuf.
* It is assumed that there is enough room in the output buffer.
* This routine identifies and executes the user's commands,
* echoing them on fd 2 if necessary, and follows every CR with LF.
* When the end of user input is reached (cnt = 0), it calls quit().
*
* This routine contains static variables. It can only be used on one stream
* at a time in a process.
*/
process(connp, inbuf, cnt)
struct NetConn *connp;
char inbuf[];
int cnt;
{
register char *p; /* Points into inbuf */
static char outbuf[CBUFSIZE]; /* Static just to keep stack size down */
register char *op; /* Points into outbuf */
int type;
int c; /* Unsigned char */
int junk[3]; /* For stty/gtty */
static int cmdstate; /*
* 0 -- not within command
* 1 -- cmdchar just typed
* other: within command
*/
static char cline[256];
static char *clinep;
static trunc;
extern int prompt;
extern int NoUsrEOF;
if (cnt == 0)
{
if (NoUsrEOF == 0)
quit(0, 1, 0);
return;
}
op = outbuf;
for (p = inbuf; cnt > 0; p++, cnt--)
{
c = *p & 0377;
if ((c&0177) == prompt)
type = CMDBEGIN;
else if ((c&0177) == '\n' || (c&0177) == '\r')
type = CMDEND;
else
type = NONE;
switch(type + cmdstate)
{
case CMDBEGIN + 0: /* Start receiving command */
cmdstate = 1;
clinep = cline;
trunc = 0;
if ((GetFlags(OrigMode())&ECHO) && ! (GetFlags(CurMode())&ECHO))
write(2, &c, 1);
break;
case NONE + 0: /* Ordinary char -- send it */
case CMDBEGIN + 1: /* Doubled command char -- send it */
case CMDEND + 0: /* CMDEND outside command mode -- send it */
*op++ = c;
if (c == '\r' && connp->ToNetBin == 0) /* Follow CR with LF */
*op++ = '\n';
cmdstate = 0;
break;
case NONE + 1: /* Ordinary char in command mode */
case NONE + 2:
case CMDBEGIN + 2:
if ((GetFlags(OrigMode())&ECHO) && ! (GetFlags(CurMode())&ECHO))
write(2, &c, 1);
if (clinep < &cline[sizeof(cline)])
*clinep++ = c;
else
trunc = 1; /* Remember we are losing chars */
break;
case CMDEND + 1: /* Empty command line */
case CMDEND + 2: /* Execute command line */
if ((GetFlags(OrigMode())&ECHO) && ! (GetFlags(CurMode())&ECHO))
write(2, "\r\n", 2);
if (trunc)
fdprintf(2, "Command line too long -- ignored.\r\n");
else
{
/* Flush what we have in case command involves it */
if (connp != 0 && op - outbuf > 0)
SendBuf(connp, op - outbuf, outbuf);
op = outbuf;
/*
* If the user input is a terminal, and it was not originally in raw mode
* but is now, then do canonicalization (line editing).
*/
if (gtty(0, junk) != -1
&& !(GetFlags(OrigMode())&RAW) && GetFlags(CurMode())&RAW)
canon(cline, GetMode(OrigMode())->s_erase,
GetMode(OrigMode())->s_kill,
'\\');
ExecCmd(cline, clinep - cline);
}
cmdstate = 0;
break;
}
}
if (connp != 0 && op - outbuf > 0)
SendBuf(connp, op - outbuf, outbuf);
return;
}
/* ------------------------- U S E P T Y --------------------------- */
/* usepty() is a server utility. It opens a pty and attaches it
* via FromUser and ToUser.
*/
usepty(connp)
struct NetConn *connp;
{
int fd;
struct sgttybuf ttybuf;
extern int termfd;
extern int FromUser(), ToUser();
if ((fd = openpty("/dev/ptyA", 1)) == -1)
{
SendBuf(connp, 7, "Sorry\r\n");
netclose(0, 1, 0);
com_err(0, "Unable to get pty");
return; /* Hopefully remote user will close and leave */
}
/* Default is not to echo. Kludge this by setting speed to 134.5 baud. */
gtty(fd, &ttybuf);
ttybuf.s_ospeed = ttybuf.s_ispeed = B134;
stty(fd, &ttybuf);
connp->UsingPty = 1;
termfd = fd; /* Terminal (for tty mode ctrl) is now pty */
IoEnter(fd, &FromUser, &ToUser, connp);
return;
}
/* -------------------------- C A N O N ----------------------------- */
/*
* canon(line, erase, kill, esc) applies erase and kill processing
* to the given line. esc is the escape character. The line is replaced.
*/
canon(line, erase, kill, esc)
char *line;
char erase;
char kill;
char esc;
{
register char *ip;
register char *op;
for (ip = op = line; *ip; ip++)
{
if (*ip == erase)
{
if (op > line)
op--;
}
else if (*ip == kill)
op = line;
else if (*ip == esc && (ip[1] == erase || ip[1] == kill))
*op++ = *++ip;
else
*op++ = *ip;
}
*op = '\0';
}
/* -------------------------- M O V E F D --------------------------- */
/*
* movefd(fd1, fd2) moves the device on fd1 to fd2. fd2 is closed to
* assure that the move will work.
*/
movefd(fd1, fd2)
int fd1;
int fd2;
{
int i;
extern int errno;
close(fd2);
while((i = dup(fd1)) < fd2)
if (i == -1)
fatal(errno, "Cannot move fd %d.", fd1);
else
close(i);
}
/* -------------------------- R E D I R ----------------------------- */
/*
* redir(arg, argc, argv) redirects the user input. If a file is specified,
* it opens it, and moves the fd to the input or output. If an fd is specified,
* it uses it directly.
*/
redir(arg, argc, argv)
int arg;
int argc;
char *argv[];
{
int fd;
extern int errno;
extern int OldInput;
extern int OldPrompt;
extern int prompt;
extern char *atoiv();
if (argc == 0)
fdprintf(2, "Redirect user input\r\n");
else if (argc == 1 || argc > 3)
fdprintf(2, "Usage: %s -fd #\r\n\t %s filename\r\n",
argv[0], argv[0]);
else
{
OldPrompt = prompt; /* In case it is changed */
if (seq(argv[1], "-fd"))
{
if (argc < 3)
fdprintf(2, "%s: No fd specified.\r\n", argv[0]);
else if (*atoiv(argv[2], &fd))
fdprintf(2, "%s: Non-numeric fd specified: %s\r\n",
argv[0], argv[2]);
}
else if ((fd = open(argv[1], 0)) == -1)
{
com_err(errno, "Cannot open input file\r\n");
return;
}
OldInput = dup(0);
movefd(fd, 0);
prompt = -1; /* Turn off command interpretation */
awtenb(0);
}
}
/* -------------------------- A L L O C O N N ----------------------- */
/*
* AllocConn() allocates a connection block and initializes it for
* the normal kind of connection.
*/
struct NetConn *
AllocConn()
{
register struct NetConn *np;
np = alloc(sizeof(*np));
if (np == -1)
return(-1);
np->CName[0] = '\0';/* To be initialized later by ChgConn */
np->StateP = 0; /* To be initialized later, when conn estab */
np->ExitOnClose = 0;
np->Init[0] = 0;
np->SendSecur = 0;
np->UtcbP = 0; /* To be initialized later, if TCP */
np->NetFD = -1; /* To be initialized later, if NCP */
np->UsePtySw = 0;
np->UsingPty = 0;
np->Type = NORMAL;
np->LocState = ACTIVE;
np->RemState = ACTIVE;
np->OutSynCnt = 0;
np->InSynCnt = 0;
np->RecSize = 0;
np->ToNetBin = 0;
np->ToNetCr = 0;
np->ToUsrBin = 0;
np->ToUsrCr = 0;
np->ToNetEOF = 0;
np->ToUsrEOF = 0;
Cinit(np->ToNetBuf);
Cinit(np->ToUsrBuf);
Cinit(np->FmNetBuf);
return(np);
}
/* -------------------------- F R E E C O N N ----------------------- */
/*
* FreeConn frees a connection block.
*/
FreeConn(connp)
struct NetConn *connp;
{
free(connp);
}
/* -------------------------- G L O B A L S ----------------------- */
/*
* These globals are used only within this file. Putting them at the
* end forces them to be explicitly declared within every routine
* that actually uses them, encouraging visibility.
*/
int OldInput -1;
int OldPrompt;