X-Git-Url: https://git.subgeniuskitty.com/unix-history/.git/blobdiff_plain/ac6e6727f1a36bbb2728cfcab4b8db6f5e490e41..c847f2e11bcf72d05ca24ec2cc0f27393e2e0606:/usr/src/libexec/telnetd/telnetd.c diff --git a/usr/src/libexec/telnetd/telnetd.c b/usr/src/libexec/telnetd/telnetd.c index 45fb0dc7b4..426a1e7937 100644 --- a/usr/src/libexec/telnetd/telnetd.c +++ b/usr/src/libexec/telnetd/telnetd.c @@ -1,23 +1,56 @@ +/* + * 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.5 82/03/23"; -#endif +static char sccsid[] = "@(#)telnetd.c 5.30 (Berkeley) %G%"; +#endif /* not lint */ /* - * Stripped-down telnet server. + * Telnet server. */ +#include +#include +#include +#include +#include +#include + +#include + +#include + #include #include #include #include -#include -#include -#include -#include -#include "telnet.h" - -#define INFINITY 10000000 -#define BELL '\07' - +#include +#include +#include + +#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]; @@ -30,99 +63,256 @@ 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; -char netobuf[BUFSIZ] = - { IAC, DO, TELOPT_ECHO, '\r', '\n' }, - *nfrontp = netobuf + 5, *nbackp = netobuf; +#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[] = "/dev/ptyp0"; - -struct sockaddr_in sin = { AF_INET, IPPORT_TELNET }; -int options = SO_ACCEPTCONN|SO_KEEPALIVE; +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) + main(argc, argv) char *argv[]; { - int s, pid; - union wait status; - - argc--, argv++; - if (argc > 0 && !strcmp(argv[0], "-d")) - options |= SO_DEBUG; -#if vax || pdp11 - sin.sin_port = htons(sin.sin_port); -#endif - for (;;) { - errno = 0; - if ((s = socket(SOCK_STREAM, 0, &sin, options)) < 0) { - perror("socket"); - sleep(5); - continue; - } - if (accept(s, 0) < 0) { - perror("accept"); - close(s); - sleep(1); - continue; - } - if ((pid = fork()) < 0) - printf("Out of processes\n"); - else if (pid == 0) - doit(s); - close(s); - while (wait3(status, WNOHANG, 0) > 0) - continue; + 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); } - /*NOTREACHED*/ +#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); + } + 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(); + } +} + +/* + * 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 (c = 'p'; c <= 's'; c++) { + struct stat stb; - for (i = 0; i < 16; i++) { - cp[strlen("/dev/ptyp")] = "0123456789abcdef"[i]; - p = open(cp, 2); - if (p > 0) - goto gotpty; + 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; + } } - dup2(f, 1); - printf("All network ports in use.\n"); - exit(1); + 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); - if (t < 0) { - dup2(f, 2); - perror(cp); - exit(1); - } + 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 = ECHO|CRMOD|XTABS|ANYP; + b.sg_flags = CRMOD|XTABS|ANYP; ioctl(t, TIOCSETP, &b); - if ((i = fork()) < 0) { - dup2(f, 2); - perror("fork"); - exit(1); - } + 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"); if (i) telnet(f, p); close(f); @@ -131,11 +321,72 @@ gotpty: dup2(t, 1); dup2(t, 2); close(t); - execl("/bin/login", "telnet-login", 0); - perror("/bin/login"); + 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*/ +} + +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; + } +} + /* * Main loop. Select from pty and network, and * hand data to telnet receiver finite state machine. @@ -143,64 +394,234 @@ gotpty: 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; + + /* + * 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 (;;) { - 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(32, &ibits, &obits, INFINITY); - 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) @@ -209,12 +630,20 @@ telnet(f, p) 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(); @@ -226,8 +655,8 @@ telnet(f, p) #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 " */ @@ -237,7 +666,6 @@ telrcv() { register int c; static int state = TS_DATA; - struct sgttyb b; while (ncc > 0) { if ((&ptyobuf[BUFSIZ] - pfrontp) < 2) @@ -245,6 +673,14 @@ telrcv() 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; @@ -252,15 +688,21 @@ telrcv() } 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: @@ -271,48 +713,89 @@ telrcv() * 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: @@ -322,44 +805,56 @@ telrcv() 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: - printf("netser: panic state=%d\n", state); + syslog(LOG_ERR, "telnetd: panic state=%d\n", state); + printf("telnetd: panic state=%d\n", state); exit(1); } } @@ -374,15 +869,32 @@ willoption(option) 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; @@ -394,8 +906,13 @@ willoption(option) fmt = dont; break; } - sprintf(nfrontp, fmt, option); - nfrontp += sizeof(dont) - 2; + if (fmt == doopt) { + hisopts[option] = OPT_YES; + } else { + hisopts[option] = OPT_NO; + } + (void) sprintf(nfrontp, fmt, option); + nfrontp += sizeof (dont) - 2; } wontoption(option) @@ -404,26 +921,23 @@ wontoption(option) 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); - nfrontp += sizeof(doopt) - 2; + + fmt = dont; + hisopts[option] = OPT_NO; + (void) sprintf(nfrontp, fmt, option); + nfrontp += sizeof (doopt) - 2; } dooption(option) @@ -439,14 +953,15 @@ dooption(option) 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; @@ -454,8 +969,84 @@ dooption(option) fmt = wont; break; } - sprintf(nfrontp, fmt, option); - nfrontp += sizeof(doopt) - 2; + 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) @@ -490,83 +1081,298 @@ interrupt() '\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 && errno == EWOULDBLOCK) - n = 0; + if (n < 0) + return; pbackp += n; if (pbackp == pfrontp) pbackp = pfrontp = ptyobuf; } + +/* + * 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. + */ -netflush() +char * +nextitem(current) +char *current; { - int n; + 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; + } +} + - if ((n = nfrontp - nbackp) > 0) - n = write(net, nbackp, n); - if (n < 0 && errno == EWOULDBLOCK) - n = 0; - nbackp += n; - if (nbackp == nfrontp) - nbackp = nfrontp = netobuf; +/* + * 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; } + +/* + * netflush + * Send as much data as possible to the network, + * handling requests for urgent data. + */ -cleanup() + +netflush() { - int how = 2; + 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; + } +} - rmut(); - vhangup(); - ioctl(net, SIOCDONE, &how); - kill(0, SIGKILL); +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); } -#include +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); }