This seems necessary to get things to work.
[unix-history] / usr / src / libexec / telnetd / telnetd.c
/*
* Copyright (c) 1983, 1986 Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the University of California, Berkeley. The name of the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef lint
char copyright[] =
"@(#) Copyright (c) 1983, 1986 Regents of the University of California.\n\
All rights reserved.\n";
#endif /* not lint */
#ifndef lint
static char sccsid[] = "@(#)telnetd.c 5.34 (Berkeley) %G%";
#endif /* not lint */
/*
* Telnet server.
*/
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/telnet.h>
#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <sgtty.h>
#include <netdb.h>
#include <syslog.h>
#include <ctype.h>
#define OPT_NO 0 /* won't do this option */
#define OPT_YES 1 /* will do this option */
#define OPT_YES_BUT_ALWAYS_LOOK 2
#define OPT_NO_BUT_ALWAYS_LOOK 3
char hisopts[256];
char myopts[256];
char doopt[] = { IAC, DO, '%', 'c', 0 };
char dont[] = { IAC, DONT, '%', 'c', 0 };
char will[] = { IAC, WILL, '%', 'c', 0 };
char wont[] = { IAC, WONT, '%', 'c', 0 };
/*
* I/O data buffers, pointers, and counters.
*/
char ptyibuf[BUFSIZ], *ptyip = ptyibuf;
char ptyobuf[BUFSIZ], *pfrontp = ptyobuf, *pbackp = ptyobuf;
char netibuf[BUFSIZ], *netip = netibuf;
#define NIACCUM(c) { *netip++ = c; \
ncc++; \
}
char netobuf[BUFSIZ], *nfrontp = netobuf, *nbackp = netobuf;
char *neturg = 0; /* one past last bye of urgent data */
/* the remote system seems to NOT be an old 4.2 */
int not42 = 1;
#define BANNER "\r\n\r\n4.3 BSD UNIX (%s)\r\n\r\r\n\r"
/* buffer for sub-options */
char subbuffer[100], *subpointer= subbuffer, *subend= subbuffer;
#define SB_CLEAR() subpointer = subbuffer;
#define SB_TERM() { subend = subpointer; SB_CLEAR(); }
#define SB_ACCUM(c) if (subpointer < (subbuffer+sizeof subbuffer)) { \
*subpointer++ = (c); \
}
#define SB_GET() ((*subpointer++)&0xff)
#define SB_EOF() (subpointer >= subend)
int pcc, ncc;
int pty, net;
int inter;
extern char **environ;
extern int errno;
char *line;
int SYNCHing = 0; /* we are in TELNET SYNCH mode */
/*
* The following are some clocks used to decide how to interpret
* the relationship between various variables.
*/
struct {
int
system, /* what the current time is */
echotoggle, /* last time user entered echo character */
modenegotiated, /* last time operating mode negotiated */
didnetreceive, /* last time we read data from network */
ttypeopt, /* ttype will/won't received */
ttypesubopt, /* ttype subopt is received */
getterminal, /* time started to get terminal information */
gotDM; /* when did we last see a data mark */
} clocks;
#define settimer(x) (clocks.x = ++clocks.system)
#define sequenceIs(x,y) (clocks.x < clocks.y)
\f
main(argc, argv)
char *argv[];
{
struct sockaddr_in from;
int on = 1, fromlen;
if ((argc > 1) && (strcmp(argv[1], "-debug") == 0)) {
int s, ns, foo;
struct servent *sp;
static struct sockaddr_in sin = { AF_INET };
argc--, argv++;
if (argc > 1) {
sin.sin_port = atoi(argv[1]);
sin.sin_port = htons((u_short)sin.sin_port);
} else {
sp = getservbyname("telnet", "tcp");
if (sp == 0) {
fprintf(stderr,
"telnetd: tcp/telnet: unknown service\n");
exit(1);
}
sin.sin_port = sp->s_port;
}
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
perror("telnetd: socket");;
exit(1);
}
if (bind(s, &sin, sizeof sin) < 0) {
perror("bind");
exit(1);
}
if (listen(s, 1) < 0) {
perror("listen");
exit(1);
}
foo = sizeof sin;
ns = accept(s, &sin, &foo);
if (ns < 0) {
perror("accept");
exit(1);
}
dup2(ns, 0);
close(s);
}
openlog("telnetd", LOG_PID | LOG_ODELAY, LOG_DAEMON);
fromlen = sizeof (from);
if (getpeername(0, &from, &fromlen) < 0) {
fprintf(stderr, "%s: ", argv[0]);
perror("getpeername");
_exit(1);
}
if (setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) < 0) {
syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
}
doit(0, &from);
}
char *terminaltype = 0;
char *envinit[2];
int cleanup();
/*
* ttloop
*
* A small subroutine to flush the network output buffer, get some data
* from the network, and pass it through the telnet state machine. We
* also flush the pty input buffer (by dropping its data) if it becomes
* too full.
*/
void
ttloop()
{
if (nfrontp-nbackp) {
netflush();
}
ncc = read(net, netibuf, sizeof netibuf);
if (ncc < 0) {
syslog(LOG_INFO, "ttloop: read: %m\n");
exit(1);
} else if (ncc == 0) {
syslog(LOG_INFO, "ttloop: peer died: %m\n");
exit(1);
}
netip = netibuf;
telrcv(); /* state machine */
if (ncc > 0) {
pfrontp = pbackp = ptyobuf;
telrcv();
}
}
/*
* getterminalspeed
*
* Ask the other end to send along its terminal speed.
* subopt does the rest. Interlocked so it can't happen during
* getterminaltype.
*/
void
getterminalspeed()
{
static char sbuf[] = { IAC, SB, TELOPT_TSPEED, TELQUAL_SEND, IAC, SE };
bcopy(sbuf, nfrontp, sizeof sbuf);
nfrontp += sizeof sbuf;
}
/*
* getterminaltype
*
* Ask the other end to send along its terminal type.
* Output is the variable terminaltype filled in.
*/
void
getterminaltype()
{
static char sbuf[] = { IAC, DO, TELOPT_TTYPE };
settimer(getterminal);
bcopy(sbuf, nfrontp, sizeof sbuf);
nfrontp += sizeof sbuf;
hisopts[TELOPT_TTYPE] = OPT_YES_BUT_ALWAYS_LOOK;
while (sequenceIs(ttypeopt, getterminal)) {
ttloop();
}
if (hisopts[TELOPT_TTYPE] == OPT_YES) {
static char sbbuf[] = { IAC, SB, TELOPT_TTYPE, TELQUAL_SEND, IAC, SE };
bcopy(sbbuf, nfrontp, sizeof sbbuf);
nfrontp += sizeof sbbuf;
while (sequenceIs(ttypesubopt, getterminal)) {
ttloop();
}
}
}
/*
* Get a pty, scan input lines.
*/
doit(f, who)
int f;
struct sockaddr_in *who;
{
char *host, *inet_ntoa();
int i, p, t;
struct sgttyb b;
struct hostent *hp;
int c;
for (c = 'p'; c <= 's'; c++) {
struct stat stb;
line = "/dev/ptyXX";
line[strlen("/dev/pty")] = c;
line[strlen("/dev/ptyp")] = '0';
if (stat(line, &stb) < 0)
break;
for (i = 0; i < 16; i++) {
line[sizeof("/dev/ptyp") - 1] = "0123456789abcdef"[i];
p = open(line, O_RDWR);
if (p > 0)
goto gotpty;
}
}
fatal(f, "All network ports in use");
/*NOTREACHED*/
gotpty:
dup2(f, 0);
line[strlen("/dev/")] = 't';
t = open("/dev/tty", O_RDWR);
if (t >= 0) {
ioctl(t, TIOCNOTTY, 0);
close(t);
}
t = open(line, O_RDWR);
if (t < 0)
fatalperror(f, line);
if (fchmod(t, 0))
fatalperror(f, line);
(void)signal(SIGHUP, SIG_IGN);
vhangup();
(void)signal(SIGHUP, SIG_DFL);
t = open(line, O_RDWR);
if (t < 0)
fatalperror(f, line);
ioctl(t, TIOCGETP, &b);
b.sg_flags = CRMOD|XTABS|ANYP;
ioctl(t, TIOCSETP, &b);
ioctl(p, TIOCGETP, &b);
b.sg_flags &= ~ECHO;
b.sg_ospeed = b.sg_ispeed = B9600;
ioctl(p, TIOCSETP, &b);
hp = gethostbyaddr(&who->sin_addr, sizeof (struct in_addr),
who->sin_family);
if (hp)
host = hp->h_name;
else
host = inet_ntoa(who->sin_addr);
net = f;
pty = p;
/*
* get terminal type and size.
*/
getterminaltype();
if ((i = fork()) < 0)
fatalperror(f, "fork");
if (i) {
close(t);
telnet(f, p);
}
if (setsid() < 0)
fatalperror(f, "setsid");
if (ioctl(t, TIOCSCTTY, 0) < 0)
fatalperror(f, "ioctl(sctty)");
close(f);
close(p);
dup2(t, 0);
dup2(t, 1);
dup2(t, 2);
close(t);
envinit[0] = terminaltype;
envinit[1] = 0;
environ = envinit;
/*
* -h : pass on name of host.
* WARNING: -h is accepted by login if and only if
* getuid() == 0.
* -p : don't clobber the environment (so terminal type stays set).
*/
execl("/bin/login", "login", "-h", host,
terminaltype ? "-p" : 0, 0);
syslog(LOG_ERR, "/bin/login: %m\n");
fatalperror(2, "/bin/login");
/*NOTREACHED*/
}
fatal(f, msg)
int f;
char *msg;
{
char buf[BUFSIZ];
(void) sprintf(buf, "telnetd: %s.\r\n", msg);
(void) write(f, buf, strlen(buf));
exit(1);
}
fatalperror(f, msg)
int f;
char *msg;
{
char buf[BUFSIZ];
extern char *sys_errlist[];
(void) sprintf(buf, "%s: %s\r\n", msg, sys_errlist[errno]);
fatal(f, buf);
}
/*
* Check a descriptor to see if out of band data exists on it.
*/
stilloob(s)
int s; /* socket number */
{
static struct timeval timeout = { 0 };
fd_set excepts;
int value;
do {
FD_ZERO(&excepts);
FD_SET(s, &excepts);
value = select(s+1, (fd_set *)0, (fd_set *)0, &excepts, &timeout);
} while ((value == -1) && (errno == EINTR));
if (value < 0) {
fatalperror(pty, "select");
}
if (FD_ISSET(s, &excepts)) {
return 1;
} else {
return 0;
}
}
\f
/*
* Main loop. Select from pty and network, and
* hand data to telnet receiver finite state machine.
*/
telnet(f, p)
{
int on = 1;
char hostname[MAXHOSTNAMELEN];
#define TABBUFSIZ 512
char defent[TABBUFSIZ];
char defstrs[TABBUFSIZ];
#undef TABBUFSIZ
char *HE;
char *HN;
char *IM;
ioctl(f, FIONBIO, &on);
ioctl(p, FIONBIO, &on);
ioctl(p, TIOCPKT, &on);
#if defined(SO_OOBINLINE)
setsockopt(net, SOL_SOCKET, SO_OOBINLINE, &on, sizeof on);
#endif /* defined(SO_OOBINLINE) */
signal(SIGTSTP, SIG_IGN);
/*
* Ignoring SIGTTOU keeps the kernel from blocking us
* in ttioctl() in /sys/tty.c.
*/
signal(SIGTTOU, SIG_IGN);
signal(SIGCHLD, cleanup);
setpgrp(0, 0);
/*
* Request to do remote echo and to suppress go ahead.
*/
if (!myopts[TELOPT_ECHO]) {
dooption(TELOPT_ECHO);
}
if (!myopts[TELOPT_SGA]) {
dooption(TELOPT_SGA);
}
if (!hisopts[TELOPT_NAWS]) {
willoption(TELOPT_NAWS);
hisopts[TELOPT_NAWS] = OPT_NO;
}
if (!hisopts[TELOPT_TSPEED]) {
willoption(TELOPT_TSPEED);
hisopts[TELOPT_TSPEED] = OPT_NO;
}
if (!hisopts[TELOPT_LFLOW]) {
willoption(TELOPT_LFLOW);
hisopts[TELOPT_LFLOW] = OPT_NO;
}
/*
* Is the client side a 4.2 (NOT 4.3) system? We need to know this
* because 4.2 clients are unable to deal with TCP urgent data.
*
* To find out, we send out a "DO ECHO". If the remote system
* answers "WILL ECHO" it is probably a 4.2 client, and we note
* that fact ("WILL ECHO" ==> that the client will echo what
* WE, the server, sends it; it does NOT mean that the client will
* echo the terminal input).
*/
(void) sprintf(nfrontp, doopt, TELOPT_ECHO);
nfrontp += sizeof doopt-2;
hisopts[TELOPT_ECHO] = OPT_YES_BUT_ALWAYS_LOOK;
/*
* Show banner that getty never gave.
*
* We put the banner in the pty input buffer. This way, it
* gets carriage return null processing, etc., just like all
* other pty --> client data.
*/
gethostname(hostname, sizeof (hostname));
if (getent(defent, "default") == 1) {
char *getstr();
char *p=defstrs;
HE = getstr("he", &p);
HN = getstr("hn", &p);
IM = getstr("im", &p);
if (HN && *HN)
strcpy(hostname, HN);
edithost(HE, hostname);
if (IM && *IM)
putf(IM, ptyibuf+1);
} else {
sprintf(ptyibuf+1, BANNER, hostname);
}
ptyip = ptyibuf+1; /* Prime the pump */
pcc = strlen(ptyip); /* ditto */
/* Clear ptybuf[0] - where the packet information is received */
ptyibuf[0] = 0;
/*
* Call telrcv() once to pick up anything received during
* terminal type negotiation.
*/
telrcv();
for (;;) {
fd_set ibits, obits, xbits;
register int c;
if (ncc < 0 && pcc < 0)
break;
FD_ZERO(&ibits);
FD_ZERO(&obits);
FD_ZERO(&xbits);
/*
* Never look for input if there's still
* stuff in the corresponding output buffer
*/
if (nfrontp - nbackp || pcc > 0) {
FD_SET(f, &obits);
} else {
FD_SET(p, &ibits);
}
if (pfrontp - pbackp || ncc > 0) {
FD_SET(p, &obits);
} else {
FD_SET(f, &ibits);
}
if (!SYNCHing) {
FD_SET(f, &xbits);
}
FD_SET(p, &xbits);
if ((c = select(16, &ibits, &obits, &xbits,
(struct timeval *)0)) < 1) {
if (c == -1) {
if (errno == EINTR) {
continue;
}
}
sleep(5);
continue;
}
/*
* Any urgent data?
*/
if (FD_ISSET(net, &xbits)) {
SYNCHing = 1;
}
/*
* Something to read from the network...
*/
if (FD_ISSET(net, &ibits)) {
#if !defined(SO_OOBINLINE)
/*
* In 4.2 (and 4.3 beta) systems, the
* OOB indication and data handling in the kernel
* is such that if two separate TCP Urgent requests
* come in, one byte of TCP data will be overlaid.
* This is fatal for Telnet, but we try to live
* with it.
*
* In addition, in 4.2 (and...), a special protocol
* is needed to pick up the TCP Urgent data in
* the correct sequence.
*
* What we do is: if we think we are in urgent
* mode, we look to see if we are "at the mark".
* If we are, we do an OOB receive. If we run
* this twice, we will do the OOB receive twice,
* but the second will fail, since the second
* time we were "at the mark", but there wasn't
* any data there (the kernel doesn't reset
* "at the mark" until we do a normal read).
* Once we've read the OOB data, we go ahead
* and do normal reads.
*
* There is also another problem, which is that
* since the OOB byte we read doesn't put us
* out of OOB state, and since that byte is most
* likely the TELNET DM (data mark), we would
* stay in the TELNET SYNCH (SYNCHing) state.
* So, clocks to the rescue. If we've "just"
* received a DM, then we test for the
* presence of OOB data when the receive OOB
* fails (and AFTER we did the normal mode read
* to clear "at the mark").
*/
if (SYNCHing) {
int atmark;
ioctl(net, SIOCATMARK, (char *)&atmark);
if (atmark) {
ncc = recv(net, netibuf, sizeof (netibuf), MSG_OOB);
if ((ncc == -1) && (errno == EINVAL)) {
ncc = read(net, netibuf, sizeof (netibuf));
if (sequenceIs(didnetreceive, gotDM)) {
SYNCHing = stilloob(net);
}
}
} else {
ncc = read(net, netibuf, sizeof (netibuf));
}
} else {
ncc = read(net, netibuf, sizeof (netibuf));
}
settimer(didnetreceive);
#else /* !defined(SO_OOBINLINE)) */
ncc = read(net, netibuf, sizeof (netibuf));
#endif /* !defined(SO_OOBINLINE)) */
if (ncc < 0 && errno == EWOULDBLOCK)
ncc = 0;
else {
if (ncc <= 0) {
break;
}
netip = netibuf;
}
}
/*
* Something to read from the pty...
*/
if (FD_ISSET(p, &ibits) || FD_ISSET(p, &xbits)) {
pcc = read(p, ptyibuf, BUFSIZ);
if (pcc < 0 && errno == EWOULDBLOCK)
pcc = 0;
else {
if (pcc <= 0)
break;
if (ptyibuf[0] & TIOCPKT_FLUSHWRITE) {
netclear(); /* clear buffer back */
*nfrontp++ = IAC;
*nfrontp++ = DM;
neturg = nfrontp-1; /* off by one XXX */
}
if (hisopts[TELOPT_LFLOW] &&
(ptyibuf[0] &
(TIOCPKT_NOSTOP|TIOCPKT_DOSTOP))) {
sprintf(nfrontp,"%c%c%c%c%c%c",
IAC, SB, TELOPT_LFLOW,
ptyibuf[0] & TIOCPKT_DOSTOP ? 1 : 0,
IAC, SE);
nfrontp += 6;
}
pcc--;
ptyip = ptyibuf+1;
}
}
while (pcc > 0) {
if ((&netobuf[BUFSIZ] - nfrontp) < 2)
break;
c = *ptyip++ & 0377, pcc--;
if (c == IAC)
*nfrontp++ = c;
*nfrontp++ = c;
/* Don't do CR-NUL if we are in binary mode */
if ((c == '\r') && (myopts[TELOPT_BINARY] == OPT_NO)) {
if (pcc > 0 && ((*ptyip & 0377) == '\n')) {
*nfrontp++ = *ptyip++ & 0377;
pcc--;
} else
*nfrontp++ = '\0';
}
}
if (FD_ISSET(f, &obits) && (nfrontp - nbackp) > 0)
netflush();
if (ncc > 0)
telrcv();
if (FD_ISSET(p, &obits) && (pfrontp - pbackp) > 0)
ptyflush();
}
cleanup();
}
/*
* State for recv fsm
*/
#define TS_DATA 0 /* base state */
#define TS_IAC 1 /* look for double IAC's */
#define TS_CR 2 /* CR-LF ->'s CR */
#define TS_SB 3 /* throw away begin's... */
#define TS_SE 4 /* ...end's (suboption negotiation) */
#define TS_WILL 5 /* will option negotiation */
#define TS_WONT 6 /* wont " */
#define TS_DO 7 /* do " */
#define TS_DONT 8 /* dont " */
telrcv()
{
register int c;
static int state = TS_DATA;
while (ncc > 0) {
if ((&ptyobuf[BUFSIZ] - pfrontp) < 2)
return;
c = *netip++ & 0377, ncc--;
switch (state) {
case TS_CR:
state = TS_DATA;
/* Strip off \n or \0 after a \r */
if ((c == 0) || (c == '\n')) {
break;
}
/* FALL THROUGH */
case TS_DATA:
if (c == IAC) {
state = TS_IAC;
break;
}
if (inter > 0)
break;
/*
* We now map \r\n ==> \r for pragmatic reasons.
* Many client implementations send \r\n when
* the user hits the CarriageReturn key.
*
* We USED to map \r\n ==> \n, since \r\n says
* that we want to be in column 1 of the next
* printable line, and \n is the standard
* unix way of saying that (\r is only good
* if CRMOD is set, which it normally is).
*/
if ((c == '\r') && (hisopts[TELOPT_BINARY] == OPT_NO)) {
state = TS_CR;
}
*pfrontp++ = c;
break;
case TS_IAC:
switch (c) {
/*
* Send the process on the pty side an
* interrupt. Do this with a NULL or
* interrupt char; depending on the tty mode.
*/
case IP:
interrupt();
break;
case BREAK:
sendbrk();
break;
/*
* Are You There?
*/
case AYT:
strcpy(nfrontp, "\r\n[Yes]\r\n");
nfrontp += 9;
break;
/*
* Abort Output
*/
case AO: {
struct ltchars tmpltc;
ptyflush(); /* half-hearted */
ioctl(pty, TIOCGLTC, &tmpltc);
if (tmpltc.t_flushc != '\377') {
*pfrontp++ = tmpltc.t_flushc;
}
netclear(); /* clear buffer back */
*nfrontp++ = IAC;
*nfrontp++ = DM;
neturg = nfrontp-1; /* off by one XXX */
break;
}
/*
* Erase Character and
* Erase Line
*/
case EC:
case EL: {
struct sgttyb b;
char ch;
ptyflush(); /* half-hearted */
ioctl(pty, TIOCGETP, &b);
ch = (c == EC) ?
b.sg_erase : b.sg_kill;
if (ch != '\377') {
*pfrontp++ = ch;
}
break;
}
/*
* Check for urgent data...
*/
case DM:
SYNCHing = stilloob(net);
settimer(gotDM);
break;
/*
* Begin option subnegotiation...
*/
case SB:
state = TS_SB;
SB_CLEAR();
continue;
case WILL:
state = TS_WILL;
continue;
case WONT:
state = TS_WONT;
continue;
case DO:
state = TS_DO;
continue;
case DONT:
state = TS_DONT;
continue;
case IAC:
*pfrontp++ = c;
break;
}
state = TS_DATA;
break;
case TS_SB:
if (c == IAC) {
state = TS_SE;
} else {
SB_ACCUM(c);
}
break;
case TS_SE:
if (c != SE) {
if (c != IAC) {
SB_ACCUM(IAC);
}
SB_ACCUM(c);
state = TS_SB;
} else {
SB_TERM();
suboption(); /* handle sub-option */
state = TS_DATA;
}
break;
case TS_WILL:
if (hisopts[c] != OPT_YES)
willoption(c);
state = TS_DATA;
if (c == TELOPT_TSPEED)
getterminalspeed();
continue;
case TS_WONT:
if (hisopts[c] != OPT_NO)
wontoption(c);
state = TS_DATA;
continue;
case TS_DO:
if (myopts[c] != OPT_YES)
dooption(c);
state = TS_DATA;
continue;
case TS_DONT:
if (myopts[c] != OPT_NO) {
dontoption(c);
}
state = TS_DATA;
continue;
default:
syslog(LOG_ERR, "telnetd: panic state=%d\n", state);
printf("telnetd: panic state=%d\n", state);
exit(1);
}
}
}
willoption(option)
int option;
{
char *fmt;
switch (option) {
case TELOPT_BINARY:
mode(RAW, 0);
fmt = doopt;
break;
case TELOPT_ECHO:
not42 = 0; /* looks like a 4.2 system */
/*
* Now, in a 4.2 system, to break them out of ECHOing
* (to the terminal) mode, we need to send a "WILL ECHO".
* Kludge upon kludge!
*/
if (myopts[TELOPT_ECHO] == OPT_YES) {
dooption(TELOPT_ECHO);
}
fmt = dont;
break;
case TELOPT_TTYPE:
settimer(ttypeopt);
if (hisopts[TELOPT_TTYPE] == OPT_YES_BUT_ALWAYS_LOOK) {
hisopts[TELOPT_TTYPE] = OPT_YES;
return;
}
fmt = doopt;
break;
case TELOPT_NAWS:
case TELOPT_TSPEED:
case TELOPT_LFLOW:
case TELOPT_SGA:
fmt = doopt;
break;
case TELOPT_TM:
fmt = dont;
break;
default:
fmt = dont;
break;
}
if (fmt == doopt) {
hisopts[option] = OPT_YES;
} else {
hisopts[option] = OPT_NO;
}
(void) sprintf(nfrontp, fmt, option);
nfrontp += sizeof (dont) - 2;
}
wontoption(option)
int option;
{
char *fmt;
switch (option) {
case TELOPT_ECHO:
not42 = 1; /* doesn't seem to be a 4.2 system */
break;
case TELOPT_BINARY:
mode(0, RAW);
break;
case TELOPT_TTYPE:
settimer(ttypeopt);
break;
}
fmt = dont;
hisopts[option] = OPT_NO;
(void) sprintf(nfrontp, fmt, option);
nfrontp += sizeof (doopt) - 2;
}
dooption(option)
int option;
{
char *fmt;
switch (option) {
case TELOPT_TM:
fmt = wont;
break;
case TELOPT_ECHO:
mode(ECHO|CRMOD, 0);
fmt = will;
break;
case TELOPT_BINARY:
mode(RAW, 0);
fmt = will;
break;
case TELOPT_SGA:
fmt = will;
break;
default:
fmt = wont;
break;
}
if (fmt == will) {
myopts[option] = OPT_YES;
} else {
myopts[option] = OPT_NO;
}
(void) sprintf(nfrontp, fmt, option);
nfrontp += sizeof (doopt) - 2;
}
dontoption(option)
int option;
{
char *fmt;
switch (option) {
case TELOPT_ECHO: /* we should stop echoing */
mode(0, ECHO);
fmt = wont;
break;
default:
fmt = wont;
break;
}
if (fmt = wont) {
myopts[option] = OPT_NO;
} else {
myopts[option] = OPT_YES;
}
(void) sprintf(nfrontp, fmt, option);
nfrontp += sizeof (wont) - 2;
}
/*
* Given a string, assign the "best" speed which we support.
*
* "Best" is defined as rounding up, unless what is presented is
* higher than the highest.
*/
string2speed(s)
char *s;
{
/*
* The order here is important. The index of each speed needs to
* correspond with the sgtty structure value for that speed.
*
* Additionally, the search algorithm assumes the table is in
* ascending sequence.
*/
static int ttyspeeds[] = {
0, 50, 75, 110, 134, 150, 200, 300,
600, 1200, 1800, 2400, 4800, 9600, 19200, 38400 };
#define NUMSPEEDS sizeof ttyspeeds/sizeof ttyspeeds[0]
int i;
int theirspeed = atoi(s);
for (i = 0; i < NUMSPEEDS; i++) {
if (ttyspeeds[i] == theirspeed) { /* Exact match */
return(i);
} else if (ttyspeeds[i] > theirspeed) {
if (i > 0) {
return i-1;
}
}
}
/* Their number is greater than any of our numbers */
return(NUMSPEEDS-1);
}
/*
* suboption()
*
* Look at the sub-option buffer, and try to be helpful to the other
* side.
*
* Currently we recognize:
*
* Terminal type is
* Terminal size
* Terminal speed is
*/
suboption()
{
switch (SB_GET()) {
case TELOPT_TTYPE: { /* Yaaaay! */
static char terminalname[5+41] = "TERM=";
settimer(ttypesubopt);
if (SB_GET() != TELQUAL_IS) {
return; /* ??? XXX but, this is the most robust */
}
terminaltype = terminalname+strlen(terminalname);
while ((terminaltype < (terminalname + sizeof terminalname-1)) &&
!SB_EOF()) {
register int c;
c = SB_GET();
if (isupper(c)) {
c = tolower(c);
}
*terminaltype++ = c; /* accumulate name */
}
*terminaltype = 0;
terminaltype = terminalname;
break;
}
case TELOPT_NAWS: {
struct winsize win;
char c;
#define SB_GETCHAR(c) \
{ if ((c = SB_GET()) == IAC && SB_GET() != IAC) return; }
ioctl(pty, TIOCGWINSZ, &win);
settimer(ttypesubopt);
syslog(LOG_INFO, "%x %x %x %x",
subpointer[0],subpointer[1],subpointer[2],subpointer[3]);
SB_GETCHAR(c);
win.ws_col = c << 8;
SB_GETCHAR(c);
win.ws_col |= c;
SB_GETCHAR(c);
win.ws_row = c << 8;
SB_GETCHAR(c);
win.ws_row |= c;
syslog(LOG_INFO, "col %d row %d", win.ws_col, win.ws_row);
ioctl(pty, TIOCSWINSZ, &win);
break;
}
case TELOPT_TSPEED: {
char speeds[41],*cp=speeds;
struct sgttyb b;
int ispeed,ospeed;
char *ispeeds,*ospeeds;
if (SB_GET() != TELQUAL_IS) {
return; /* ??? XXX but, this is the most robust */
}
ispeeds = NULL;
ospeeds = speeds;
ispeed = 0;
ospeed = 0;
while ((cp < (speeds + sizeof speeds-1)) && !SB_EOF()) {
register int c;
c = SB_GET();
if (c == ',') {
c = 0;
ispeeds = cp+1;
}
*cp++ = c; /* accumulate name */
}
*cp = 0;
if (ispeeds)
ispeed = string2speed(ispeeds);
if (ospeeds)
ospeed = string2speed(ospeeds);
if (ispeed && ospeed) {
ioctl(pty, TIOCGETP, &b);
b.sg_ospeed = ospeed;
b.sg_ispeed = ispeed;
ioctl(pty, TIOCSETP, &b);
}
break;
}
default:
;
}
}
mode(on, off)
int on, off;
{
struct sgttyb b;
ptyflush();
ioctl(pty, TIOCGETP, &b);
b.sg_flags |= on;
b.sg_flags &= ~off;
ioctl(pty, TIOCSETP, &b);
}
/*
* Send interrupt to process on other side of pty.
* If it is in raw mode, just write NULL;
* otherwise, write intr char.
*/
interrupt()
{
struct sgttyb b;
struct tchars tchars;
ptyflush(); /* half-hearted */
ioctl(pty, TIOCGETP, &b);
if (b.sg_flags & RAW) {
*pfrontp++ = '\0';
return;
}
*pfrontp++ = ioctl(pty, TIOCGETC, &tchars) < 0 ?
'\177' : tchars.t_intrc;
}
/*
* Send quit to process on other side of pty.
* If it is in raw mode, just write NULL;
* otherwise, write quit char.
*/
sendbrk()
{
struct sgttyb b;
struct tchars tchars;
ptyflush(); /* half-hearted */
ioctl(pty, TIOCGETP, &b);
if (b.sg_flags & RAW) {
*pfrontp++ = '\0';
return;
}
*pfrontp++ = ioctl(pty, TIOCGETC, &tchars) < 0 ?
'\034' : tchars.t_quitc;
}
ptyflush()
{
int n;
if ((n = pfrontp - pbackp) > 0)
n = write(pty, pbackp, n);
if (n < 0)
return;
pbackp += n;
if (pbackp == pfrontp)
pbackp = pfrontp = ptyobuf;
}
\f
/*
* nextitem()
*
* Return the address of the next "item" in the TELNET data
* stream. This will be the address of the next character if
* the current address is a user data character, or it will
* be the address of the character following the TELNET command
* if the current address is a TELNET IAC ("I Am a Command")
* character.
*/
char *
nextitem(current)
char *current;
{
if ((*current&0xff) != IAC) {
return current+1;
}
switch (*(current+1)&0xff) {
case DO:
case DONT:
case WILL:
case WONT:
return current+3;
case SB: /* loop forever looking for the SE */
{
register char *look = current+2;
for (;;) {
if ((*look++&0xff) == IAC) {
if ((*look++&0xff) == SE) {
return look;
}
}
}
}
default:
return current+2;
}
}
/*
* netclear()
*
* We are about to do a TELNET SYNCH operation. Clear
* the path to the network.
*
* Things are a bit tricky since we may have sent the first
* byte or so of a previous TELNET command into the network.
* So, we have to scan the network buffer from the beginning
* until we are up to where we want to be.
*
* A side effect of what we do, just to keep things
* simple, is to clear the urgent data pointer. The principal
* caller should be setting the urgent data pointer AFTER calling
* us in any case.
*/
netclear()
{
register char *thisitem, *next;
char *good;
#define wewant(p) ((nfrontp > p) && ((*p&0xff) == IAC) && \
((*(p+1)&0xff) != EC) && ((*(p+1)&0xff) != EL))
thisitem = netobuf;
while ((next = nextitem(thisitem)) <= nbackp) {
thisitem = next;
}
/* Now, thisitem is first before/at boundary. */
good = netobuf; /* where the good bytes go */
while (nfrontp > thisitem) {
if (wewant(thisitem)) {
int length;
next = thisitem;
do {
next = nextitem(next);
} while (wewant(next) && (nfrontp > next));
length = next-thisitem;
bcopy(thisitem, good, length);
good += length;
thisitem = next;
} else {
thisitem = nextitem(thisitem);
}
}
nbackp = netobuf;
nfrontp = good; /* next byte to be sent */
neturg = 0;
}
\f
/*
* netflush
* Send as much data as possible to the network,
* handling requests for urgent data.
*/
netflush()
{
int n;
if ((n = nfrontp - nbackp) > 0) {
/*
* if no urgent data, or if the other side appears to be an
* old 4.2 client (and thus unable to survive TCP urgent data),
* write the entire buffer in non-OOB mode.
*/
if ((neturg == 0) || (not42 == 0)) {
n = write(net, nbackp, n); /* normal write */
} else {
n = neturg - nbackp;
/*
* In 4.2 (and 4.3) systems, there is some question about
* what byte in a sendOOB operation is the "OOB" data.
* To make ourselves compatible, we only send ONE byte
* out of band, the one WE THINK should be OOB (though
* we really have more the TCP philosophy of urgent data
* rather than the Unix philosophy of OOB data).
*/
if (n > 1) {
n = send(net, nbackp, n-1, 0); /* send URGENT all by itself */
} else {
n = send(net, nbackp, n, MSG_OOB); /* URGENT data */
}
}
}
if (n < 0) {
if (errno == EWOULDBLOCK)
return;
/* should blow this guy away... */
return;
}
nbackp += n;
if (nbackp >= neturg) {
neturg = 0;
}
if (nbackp == nfrontp) {
nbackp = nfrontp = netobuf;
}
}
cleanup()
{
char *p;
p = line + sizeof("/dev/") - 1;
if (logout(p))
logwtmp(p, "", "");
(void)chmod(line, 0666);
(void)chown(line, 0, 0);
*p = 'p';
(void)chmod(line, 0666);
(void)chown(line, 0, 0);
shutdown(net, 2);
exit(1);
}
char editedhost[32];
edithost(pat, host)
register char *pat;
register char *host;
{
register char *res = editedhost;
if (!pat)
pat = "";
while (*pat) {
switch (*pat) {
case '#':
if (*host)
host++;
break;
case '@':
if (*host)
*res++ = *host++;
break;
default:
*res++ = *pat;
break;
}
if (res == &editedhost[sizeof editedhost - 1]) {
*res = '\0';
return;
}
pat++;
}
if (*host)
strncpy(res, host, sizeof editedhost - (res - editedhost) - 1);
else
*res = '\0';
editedhost[sizeof editedhost - 1] = '\0';
}
static char *putlocation;
puts(s)
register char *s;
{
while (*s)
putchr(*s++);
}
putchr(cc)
{
*putlocation++ = cc;
}
putf(cp, where)
register char *cp;
char *where;
{
char *slash;
char datebuffer[60];
extern char *rindex();
putlocation = where;
while (*cp) {
if (*cp != '%') {
putchr(*cp++);
continue;
}
switch (*++cp) {
case 't':
slash = rindex(line, '/');
if (slash == (char *) 0)
puts(line);
else
puts(&slash[1]);
break;
case 'h':
puts(editedhost);
break;
case 'd':
get_date(datebuffer);
puts(datebuffer);
break;
case '%':
putchr('%');
break;
}
cp++;
}
}