+/*
+ * 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 4.19 83/05/03";
-#endif
+static char sccsid[] = "@(#)telnetd.c 5.30 (Berkeley) %G%";
+#endif /* not lint */
/*
- * Stripped-down telnet server.
+ * Telnet server.
*/
-#include <sys/types.h>
+#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 <signal.h>
#include <errno.h>
#include <sgtty.h>
-#include <wait.h>
#include <netdb.h>
+#include <syslog.h>
+#include <ctype.h>
-#define BELL '\07'
-
+#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];
* 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;
-int reapchild();
+extern char **environ;
extern int errno;
-char line[] = "/dev/ptyp0";
-
-struct sockaddr_in sin = { AF_INET };
+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[];
{
- int s, pid, options;
- struct servent *sp;
-
- sp = getservbyname("telnet", "tcp");
- if (sp == 0) {
- fprintf(stderr, "telnetd: tcp/telnet: unknown service\n");
+ struct sockaddr_in from;
+ int on = 1, fromlen;
+
+#if defined(DEBUG)
+ {
+ int s, ns, foo;
+ struct servent *sp;
+ static struct sockaddr_in sin = { AF_INET };
+
+ sp = getservbyname("telnet", "tcp");
+ if (sp == 0) {
+ fprintf(stderr, "telnetd: tcp/telnet: unknown service\n");
+ exit(1);
+ }
+ sin.sin_port = sp->s_port;
+ argc--, argv++;
+ if (argc > 0) {
+ sin.sin_port = atoi(*argv);
+ sin.sin_port = htons((u_short)sin.sin_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);
}
- sin.sin_port = sp->s_port;
- argc--, argv++;
- if (argc > 0 && !strcmp(*argv, "-d")) {
- options |= SO_DEBUG;
- argc--, argv++;
- }
- if (argc > 0) {
- sin.sin_port = atoi(*argv);
- if (sin.sin_port <= 0) {
- fprintf(stderr, "telnetd: %s: bad port #\n", *argv);
- exit(1);
- }
- sin.sin_port = htons((u_short)sin.sin_port);
- }
-#ifndef DEBUG
- if (fork())
- exit(0);
- for (s = 0; s < 10; s++)
- (void) close(s);
- (void) open("/", 0);
- (void) dup2(0, 1);
- (void) dup2(0, 2);
- { int tt = open("/dev/tty", 2);
- if (tt > 0) {
- ioctl(tt, TIOCNOTTY, 0);
- close(tt);
- }
- }
-#endif
-again:
- s = socket(AF_INET, SOCK_STREAM, 0, 0);
- if (s < 0) {
- perror("telnetd: socket");;
- sleep(5);
- goto again;
- }
- if (options & SO_DEBUG)
- if (setsockopt(s, SOL_SOCKET, SO_DEBUG, 0, 0) < 0)
- perror("telnetd: setsockopt (SO_DEBUG)");
- if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, 0, 0) < 0)
- perror("telnetd: setsockopt (SO_KEEPALIVE)");
- while (bind(s, (caddr_t)&sin, sizeof (sin), 0) < 0) {
- perror("telnetd: bind");
- sleep(5);
+#endif /* defined(DEBUG) */
+ 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);
}
- sigset(SIGCHLD, reapchild);
- listen(s, 10);
- for (;;) {
- int s2;
-
- s2 = accept(s, (caddr_t)0, 0, 0);
- if (s2 < 0) {
- if (errno == EINTR)
- continue;
- perror("telnetd: accept");
- sleep(1);
- continue;
- }
- if ((pid = fork()) < 0)
- printf("Out of processes\n");
- else if (pid == 0) {
- signal(SIGCHLD, SIG_IGN);
- doit(s2);
- }
- close(s2);
+ if (setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) < 0) {
+ syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
}
- /*NOTREACHED*/
+ doit(0, &from);
}
-reapchild()
-{
- union wait status;
+char *terminaltype = 0;
+char *envinit[2];
+int cleanup();
- while (wait3(&status, WNOHANG, 0) > 0)
- ;
+/*
+ * 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();
+ }
}
-int cleanup();
+/*
+ * 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)
+doit(f, who)
+ int f;
+ struct sockaddr_in *who;
{
- char *cp = line;
- int i, p, cc, t;
+ char *host, *inet_ntoa();
+ int i, p, t;
struct sgttyb b;
+ struct hostent *hp;
+ int c;
- for (i = 0; i < 16; i++) {
- cp[strlen("/dev/ptyp")] = "0123456789abcdef"[i];
- p = open(cp, 2);
- if (p > 0)
- goto gotpty;
+ 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);
- cp[strlen("/dev/")] = 't';
- t = open("/dev/tty", 2);
+ line[strlen("/dev/")] = 't';
+ t = open("/dev/tty", O_RDWR);
if (t >= 0) {
ioctl(t, TIOCNOTTY, 0);
close(t);
}
- t = open(cp, 2);
+ 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, cp, errno);
+ 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;
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.
+ */
+ getterminaltype();
+
if ((i = fork()) < 0)
- fatalperror(f, "fork", errno);
+ fatalperror(f, "fork");
if (i)
telnet(f, p);
close(f);
dup2(t, 1);
dup2(t, 2);
close(t);
- execl("/bin/login", "telnet-login", 0);
- fatalperror(f, "/bin/login", errno);
+ 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);
+ fatalperror(f, "/bin/login");
/*NOTREACHED*/
}
{
char buf[BUFSIZ];
- (void) sprintf(buf, "telnetd: %s.\n", msg);
+ (void) sprintf(buf, "telnetd: %s.\r\n", msg);
(void) write(f, buf, strlen(buf));
exit(1);
}
-fatalperror(f, msg, errno)
+fatalperror(f, msg)
int f;
char *msg;
- int errno;
{
char buf[BUFSIZ];
extern char *sys_errlist[];
- (void) sprintf(buf, "%s: %s", msg, sys_errlist[errno]);
+ (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;
- net = f, pty = p;
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);
- sigset(SIGCHLD, cleanup);
+ /*
+ * 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);
+ }
+ /*
+ * 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;
/*
- * Request to do remote echo.
+ * 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.
*/
- dooption(TELOPT_ECHO);
- myopts[TELOPT_ECHO] = 1;
+
+ 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 (;;) {
- int ibits = 0, obits = 0;
+ 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)
- obits |= (1 << f);
- else
- ibits |= (1 << p);
- if (pfrontp - pbackp)
- obits |= (1 << p);
- else
- ibits |= (1 << f);
- if (ncc < 0 && pcc < 0)
- break;
- select(16, &ibits, &obits, 0, 0);
- if (ibits == 0 && obits == 0) {
+ if (nfrontp - nbackp || pcc > 0) {
+ FD_SET(f, &obits);
+ FD_SET(p, &xbits);
+ } 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);
+ }
+ 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 (ibits & (1 << f)) {
- ncc = read(f, netibuf, BUFSIZ);
- if (ncc < 0 && errno == EWOULDBLOCK)
- ncc = 0;
- else {
- if (ncc <= 0)
- break;
- netip = netibuf;
+ 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 (ibits & (1 << p)) {
+ if (FD_ISSET(p, &xbits)) {
+ if (read(p, ptyibuf, 1) != 1) {
+ break;
+ }
+ }
+ if (FD_ISSET(p, &ibits)) {
pcc = read(p, ptyibuf, BUFSIZ);
if (pcc < 0 && errno == EWOULDBLOCK)
pcc = 0;
else {
if (pcc <= 0)
break;
- ptyip = ptyibuf;
+ /* Skip past "packet" */
+ pcc--;
+ ptyip = ptyibuf+1;
}
}
+ if (ptyibuf[0] & TIOCPKT_FLUSHWRITE) {
+ netclear(); /* clear buffer back */
+ *nfrontp++ = IAC;
+ *nfrontp++ = DM;
+ neturg = nfrontp-1; /* off by one XXX */
+ ptyibuf[0] = 0;
+ }
while (pcc > 0) {
if ((&netobuf[BUFSIZ] - nfrontp) < 2)
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 ((obits & (1 << f)) && (nfrontp - nbackp) > 0)
+ if (FD_ISSET(f, &obits) && (nfrontp - nbackp) > 0)
netflush();
if (ncc > 0)
telrcv();
- if ((obits & (1 << p)) && (pfrontp - pbackp) > 0)
+ if (FD_ISSET(p, &obits) && (pfrontp - pbackp) > 0)
ptyflush();
}
cleanup();
#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_BEGINNEG 3 /* throw away begin's... */
-#define TS_ENDNEG 4 /* ...end's (suboption negotiation) */
+#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 " */
{
register int c;
static int state = TS_DATA;
- struct sgttyb b;
while (ncc > 0) {
if ((&ptyobuf[BUFSIZ] - pfrontp) < 2)
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;
}
if (inter > 0)
break;
- *pfrontp++ = c;
- if (!myopts[TELOPT_BINARY] && c == '\r')
+ /*
+ * 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;
- break;
-
- case TS_CR:
- if (c && c != '\n')
- *pfrontp++ = c;
- state = TS_DATA;
+ }
+ *pfrontp++ = c;
break;
case TS_IAC:
* interrupt. Do this with a NULL or
* interrupt char; depending on the tty mode.
*/
- case BREAK:
case IP:
interrupt();
break;
+ case BREAK:
+ sendbrk();
+ break;
+
/*
* Are You There?
*/
case AYT:
- *pfrontp++ = BELL;
+ 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:
- ptyflush(); /* half-hearted */
- ioctl(pty, TIOCGETP, &b);
- *pfrontp++ = (c == EC) ?
- b.sg_erase : b.sg_kill;
- break;
+ 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_BEGINNEG;
+ state = TS_SB;
continue;
case WILL:
+ state = TS_WILL;
+ continue;
+
case WONT:
+ state = TS_WONT;
+ continue;
+
case DO:
+ state = TS_DO;
+ continue;
+
case DONT:
- state = TS_WILL + (c - WILL);
+ state = TS_DONT;
continue;
case IAC:
state = TS_DATA;
break;
- case TS_BEGINNEG:
- if (c == IAC)
- state = TS_ENDNEG;
+ case TS_SB:
+ if (c == IAC) {
+ state = TS_SE;
+ } else {
+ SB_ACCUM(c);
+ }
break;
- case TS_ENDNEG:
- state = c == SE ? TS_DATA : TS_BEGINNEG;
+ 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])
+ if (hisopts[c] != OPT_YES)
willoption(c);
state = TS_DATA;
continue;
case TS_WONT:
- if (hisopts[c])
+ if (hisopts[c] != OPT_NO)
wontoption(c);
state = TS_DATA;
continue;
case TS_DO:
- if (!myopts[c])
+ if (myopts[c] != OPT_YES)
dooption(c);
state = TS_DATA;
continue;
case TS_DONT:
- if (myopts[c]) {
- myopts[c] = 0;
- sprintf(nfrontp, wont, c);
- nfrontp += sizeof (wont) - 2;
+ 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);
}
case TELOPT_BINARY:
mode(RAW, 0);
- goto common;
+ fmt = doopt;
+ break;
case TELOPT_ECHO:
- mode(0, ECHO|CRMOD);
- /*FALL THRU*/
+ 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_SGA:
- common:
- hisopts[option] = 1;
fmt = doopt;
break;
fmt = dont;
break;
}
- sprintf(nfrontp, fmt, option);
+ if (fmt == doopt) {
+ hisopts[option] = OPT_YES;
+ } else {
+ hisopts[option] = OPT_NO;
+ }
+ (void) sprintf(nfrontp, fmt, option);
nfrontp += sizeof (dont) - 2;
}
char *fmt;
switch (option) {
-
case TELOPT_ECHO:
- mode(ECHO|CRMOD, 0);
- goto common;
+ not42 = 1; /* doesn't seem to be a 4.2 system */
+ break;
case TELOPT_BINARY:
mode(0, RAW);
- /*FALL THRU*/
-
- case TELOPT_SGA:
- common:
- hisopts[option] = 0;
- fmt = dont;
break;
- default:
- fmt = dont;
+ case TELOPT_TTYPE:
+ settimer(ttypeopt);
+ break;
}
- sprintf(nfrontp, fmt, option);
+
+ fmt = dont;
+ hisopts[option] = OPT_NO;
+ (void) sprintf(nfrontp, fmt, option);
nfrontp += sizeof (doopt) - 2;
}
case TELOPT_ECHO:
mode(ECHO|CRMOD, 0);
- goto common;
+ fmt = will;
+ break;
case TELOPT_BINARY:
mode(RAW, 0);
- /*FALL THRU*/
+ fmt = will;
+ break;
case TELOPT_SGA:
- common:
fmt = will;
break;
fmt = wont;
break;
}
- sprintf(nfrontp, fmt, option);
+ 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;
+}
+
+/*
+ * suboption()
+ *
+ * Look at the sub-option buffer, and try to be helpful to the other
+ * side.
+ *
+ * Currently we recognize:
+ *
+ * Terminal type 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;
+ }
+
+ default:
+ ;
+ }
+}
+
mode(on, off)
int on, off;
{
'\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 (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;
+ int n;
- if ((n = nfrontp - nbackp) > 0)
- n = write(net, nbackp, n);
- if (n < 0) {
- if (errno == EWOULDBLOCK)
- return;
- /* should blow this guy away... */
- return;
+ 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 */
+ }
}
- nbackp += n;
- if (nbackp == nfrontp)
- nbackp = nfrontp = netobuf;
+ }
+ 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()
{
-
- rmut();
- vhangup(); /* XXX */
+ 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);
- kill(0, SIGKILL);
exit(1);
}
-#include <utmp.h>
+char editedhost[32];
-struct utmp wtmp;
-char wtmpf[] = "/usr/adm/wtmp";
-char utmp[] = "/etc/utmp";
-#define SCPYN(a, b) strncpy(a, b, sizeof (a))
-#define SCMPN(a, b) strncmp(a, b, sizeof (a))
-
-rmut()
+edithost(pat, host)
+ register char *pat;
+ register char *host;
{
- register f;
- int found = 0;
+ 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;
- f = open(utmp, 2);
- if (f >= 0) {
- while(read(f, (char *)&wtmp, sizeof (wtmp)) == sizeof (wtmp)) {
- if (SCMPN(wtmp.ut_line, line+5) || wtmp.ut_name[0]==0)
- continue;
- lseek(f, -(long)sizeof (wtmp), 1);
- SCPYN(wtmp.ut_name, "");
- time(&wtmp.ut_time);
- write(f, (char *)&wtmp, sizeof (wtmp));
- found++;
}
- close(f);
+ if (res == &editedhost[sizeof editedhost - 1]) {
+ *res = '\0';
+ return;
+ }
+ pat++;
}
- if (found) {
- f = open(wtmpf, 1);
- if (f >= 0) {
- SCPYN(wtmp.ut_line, line+5);
- SCPYN(wtmp.ut_name, "");
- time(&wtmp.ut_time);
- lseek(f, (long)0, 2);
- write(f, (char *)&wtmp, sizeof (wtmp));
- close(f);
+ 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++;
}
- chmod(line, 0666);
- chown(line, 0, 0);
- line[strlen("/dev/")] = 'p';
- chmod(line, 0666);
- chown(line, 0, 0);
}