BBN-V6/telnet/main.c

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

#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;