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