X-Git-Url: https://git.subgeniuskitty.com/unix-history/.git/blobdiff_plain/9f5156939d103f5d7b35348a398089334054058a..c847f2e11bcf72d05ca24ec2cc0f27393e2e0606:/usr/src/libexec/telnetd/telnetd.c diff --git a/usr/src/libexec/telnetd/telnetd.c b/usr/src/libexec/telnetd/telnetd.c index a92b52b2f1..426a1e7937 100644 --- a/usr/src/libexec/telnetd/telnetd.c +++ b/usr/src/libexec/telnetd/telnetd.c @@ -1,27 +1,39 @@ /* - * Copyright (c) 1983 Regents of the University of California. - * All rights reserved. The Berkeley software License Agreement - * specifies the terms and conditions for redistribution. + * 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 Regents of the University of California.\n\ +"@(#) Copyright (c) 1983, 1986 Regents of the University of California.\n\ All rights reserved.\n"; -#endif not lint +#endif /* not lint */ #ifndef lint -static char sccsid[] = "@(#)telnetd.c 5.8 (Berkeley) %G%"; -#endif not lint +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 @@ -33,10 +45,12 @@ static char sccsid[] = "@(#)telnetd.c 5.8 (Berkeley) %G%"; #include #include #include +#include -#define BELL '\07' -#define BANNER "\r\n\r\n4.3 BSD UNIX (%s)\r\n\r\r\n\r%s" - +#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]; @@ -49,9 +63,31 @@ 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; @@ -59,13 +95,74 @@ 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) + main(argc, argv) char *argv[]; { 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); + } +#endif /* defined(DEBUG) */ openlog("telnetd", LOG_PID | LOG_ODELAY, LOG_DAEMON); fromlen = sizeof (from); if (getpeername(0, &from, &fromlen) < 0) { @@ -79,9 +176,71 @@ main(argc, argv) doit(0, &from); } -char *envinit[] = { "TERM=network", 0 }; +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. */ @@ -93,7 +252,7 @@ doit(f, who) int i, p, t; struct sgttyb b; struct hostent *hp; - char c; + int c; for (c = 'p'; c <= 's'; c++) { struct stat stb; @@ -104,8 +263,8 @@ doit(f, who) if (stat(line, &stb) < 0) break; for (i = 0; i < 16; i++) { - line[strlen("/dev/ptyp")] = "0123456789abcdef"[i]; - p = open(line, 2); + line[sizeof("/dev/ptyp") - 1] = "0123456789abcdef"[i]; + p = open(line, O_RDWR); if (p > 0) goto gotpty; } @@ -122,7 +281,15 @@ gotpty: } t = open(line, O_RDWR); if (t < 0) - fatalperror(f, line, errno); + 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); @@ -135,8 +302,17 @@ gotpty: 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); @@ -145,9 +321,18 @@ gotpty: dup2(t, 1); dup2(t, 2); close(t); + envinit[0] = terminaltype; + envinit[1] = 0; environ = envinit; - execl("/bin/login", "login", "-h", host, 0); - fatalperror(f, "/bin/login", errno); + /* + * -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*/ } @@ -162,10 +347,9 @@ fatal(f, msg) exit(1); } -fatalperror(f, msg, errno) +fatalperror(f, msg) int f; char *msg; - int errno; { char buf[BUFSIZ]; extern char *sys_errlist[]; @@ -174,6 +358,35 @@ fatalperror(f, msg, 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. @@ -181,77 +394,234 @@ fatalperror(f, msg, errno) telnet(f, p) { int on = 1; - char hostname[32]; + 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); + /* + * 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. + * Request to do remote echo and to suppress go ahead. */ - dooption(TELOPT_ECHO); - myopts[TELOPT_ECHO] = 1; + 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)); - sprintf(nfrontp, BANNER, hostname, ""); - nfrontp += strlen(nfrontp); + 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 || pcc > 0) - obits |= (1 << f); - else - ibits |= (1 << p); - if (pfrontp - pbackp || ncc > 0) - 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) @@ -260,7 +630,8 @@ telnet(f, p) if (c == IAC) *nfrontp++ = c; *nfrontp++ = c; - if (c == '\r') { + /* 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--; @@ -268,11 +639,11 @@ telnet(f, p) *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(); @@ -284,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 " */ @@ -295,7 +666,6 @@ telrcv() { register int c; static int state = TS_DATA; - struct sgttyb b; while (ncc > 0) { if ((&ptyobuf[BUFSIZ] - pfrontp) < 2) @@ -305,6 +675,7 @@ telrcv() case TS_CR: state = TS_DATA; + /* Strip off \n or \0 after a \r */ if ((c == 0) || (c == '\n')) { break; } @@ -318,19 +689,18 @@ telrcv() if (inter > 0) break; /* - * We map \r\n ==> \n, since \r\n says + * 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 (!myopts[TELOPT_BINARY] && c == '\r') { - if ((ncc > 0) && ('\n' == *netip)) { - netip++; ncc--; - c = '\n'; - } else { - state = TS_CR; - } + if ((c == '\r') && (hisopts[TELOPT_BINARY] == OPT_NO)) { + state = TS_CR; } *pfrontp++ = c; break; @@ -343,11 +713,14 @@ 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? */ @@ -356,36 +729,73 @@ telrcv() 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: @@ -395,43 +805,55 @@ 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: + syslog(LOG_ERR, "telnetd: panic state=%d\n", state); printf("telnetd: panic state=%d\n", state); exit(1); } @@ -447,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; @@ -467,7 +906,12 @@ willoption(option) 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; } @@ -477,25 +921,22 @@ 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); + + fmt = dont; + hisopts[option] = OPT_NO; + (void) sprintf(nfrontp, fmt, option); nfrontp += sizeof (doopt) - 2; } @@ -512,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; @@ -527,10 +969,86 @@ dooption(option) 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; { @@ -563,6 +1081,26 @@ 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; @@ -575,87 +1113,266 @@ ptyflush() 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. + */ + +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; +} + +/* + * 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); exit(1); } -#include - -struct utmp wtmp; -char wtmpf[] = "/usr/adm/wtmp"; -char utmpf[] = "/etc/utmp"; -#define SCPYN(a, b) strncpy(a, b, sizeof(a)) -#define SCMPN(a, b) strncmp(a, b, sizeof(a)) +char editedhost[32]; -rmut() +edithost(pat, host) + register char *pat; + register char *host; { - register f; - int found = 0; - struct utmp *u, *utmp; - int nutmp; - struct stat statbf; - - f = open(utmpf, O_RDWR); - if (f >= 0) { - fstat(f, &statbf); - utmp = (struct utmp *)malloc(statbf.st_size); - if (!utmp) - syslog(LOG_ERR, "utmp malloc failed"); - if (statbf.st_size && utmp) { - nutmp = read(f, utmp, statbf.st_size); - nutmp /= sizeof(struct utmp); - - for (u = utmp ; u < &utmp[nutmp] ; u++) { - if (SCMPN(u->ut_line, line+5) || - u->ut_name[0]==0) - continue; - lseek(f, ((long)u)-((long)utmp), L_SET); - SCPYN(u->ut_name, ""); - SCPYN(u->ut_host, ""); - time(&u->ut_time); - write(f, (char *)u, sizeof(wtmp)); - found++; - } + 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; + } - close(f); + if (res == &editedhost[sizeof editedhost - 1]) { + *res = '\0'; + return; + } + pat++; } - if (found) { - f = open(wtmpf, O_WRONLY|O_APPEND); - if (f >= 0) { - SCPYN(wtmp.ut_line, line+5); - SCPYN(wtmp.ut_name, ""); - SCPYN(wtmp.ut_host, ""); - time(&wtmp.ut_time); - 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); }