/* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 1988, 1990, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ /* * The following routines try to encapsulate what is system dependent * (at least between 4.x and dos) which is used in telnet.c. */ #include #include #include #include #include #include #include #include #include "ring.h" #include "defines.h" #include "externs.h" #include "types.h" #define SIG_FUNC_RET void int tout; /* Output file descriptor */ static int tin; /* Input file descriptor */ int net = -1; #ifndef USE_TERMIO struct tchars otc = { 0 }, ntc = { 0 }; struct ltchars oltc = { 0 }, nltc = { 0 }; struct sgttyb ottyb = { 0 }, nttyb = { 0 }; int olmode = 0; #define cfgetispeed(ptr) (ptr)->sg_ispeed #define cfgetospeed(ptr) (ptr)->sg_ospeed #define old_tc ottyb #else /* USE_TERMIO */ static struct termio old_tc = { 0 }; extern struct termio new_tc; #endif /* USE_TERMIO */ static fd_set ibits, obits, xbits; static SIG_FUNC_RET susp(int); void fatal_tty_error(char *doing_what); void init_sys() { tout = fileno(stdout); tin = fileno(stdin); FD_ZERO(&ibits); FD_ZERO(&obits); FD_ZERO(&xbits); errno = 0; } int TerminalWrite(buf, n) char *buf; int n; { return (write(tout, buf, n)); } static int TerminalRead(buf, n) char *buf; int n; { return (read(tin, buf, n)); } #ifdef KLUDGELINEMODE extern int kludgelinemode; #endif /* * TerminalSpecialChars() * * Look at an input character to see if it is a special character * and decide what to do. * * Output: * * 0 Don't add this character. * 1 Do add this character */ int TerminalSpecialChars(c) int c; { /* * Don't check for signal characters here. If MODE_TRAPSIG is on, * then the various signal handlers will catch the characters. If * the character in question gets here, then it must have been LNEXTed */ if (c == termQuitChar) { #ifdef KLUDGELINEMODE if (kludgelinemode) { if (sendbrk() == -1) { /* This won't return. */ fatal_tty_error("write"); } return (0); } #endif } else if (c == termFlushChar) { /* Transmit Abort Output */ if (xmitAO() == -1) { /* This won't return. */ fatal_tty_error("write"); } return (0); } else if (!MODE_LOCAL_CHARS(globalmode)) { if (c == termKillChar) { xmitEL(); return (0); } else if (c == termEraseChar) { xmitEC(); /* Transmit Erase Character */ return (0); } } return (1); } /* * Flush output to the terminal */ void TerminalFlushOutput() { if (isatty(fileno(stdout))) { (void) ioctl(fileno(stdout), TIOCFLUSH, NULL); } } void TerminalSaveState() { #ifndef USE_TERMIO (void) ioctl(0, TIOCGETP, &ottyb); (void) ioctl(0, TIOCGETC, &otc); (void) ioctl(0, TIOCGLTC, &oltc); (void) ioctl(0, TIOCLGET, &olmode); ntc = otc; nltc = oltc; nttyb = ottyb; #else /* USE_TERMIO */ (void) tcgetattr(0, &old_tc); new_tc = old_tc; termAytChar = CONTROL('T'); #endif /* USE_TERMIO */ } cc_t * tcval(func) register int func; { switch (func) { case SLC_IP: return (&termIntChar); case SLC_ABORT: return (&termQuitChar); case SLC_EOF: return (&termEofChar); case SLC_EC: return (&termEraseChar); case SLC_EL: return (&termKillChar); case SLC_XON: return (&termStartChar); case SLC_XOFF: return (&termStopChar); case SLC_FORW1: return (&termForw1Char); #ifdef USE_TERMIO case SLC_FORW2: return (&termForw2Char); case SLC_AO: return (&termFlushChar); case SLC_SUSP: return (&termSuspChar); case SLC_EW: return (&termWerasChar); case SLC_RP: return (&termRprntChar); case SLC_LNEXT: return (&termLiteralNextChar); #endif case SLC_SYNCH: case SLC_BRK: case SLC_EOR: default: return ((cc_t *)0); } } void TerminalDefaultChars() { #ifndef USE_TERMIO ntc = otc; nltc = oltc; nttyb.sg_kill = ottyb.sg_kill; nttyb.sg_erase = ottyb.sg_erase; #else /* USE_TERMIO */ (void) memcpy(new_tc.c_cc, old_tc.c_cc, sizeof (old_tc.c_cc)); termAytChar = CONTROL('T'); #endif /* USE_TERMIO */ } /* * TerminalNewMode - set up terminal to a specific mode. * MODE_ECHO: do local terminal echo * MODE_FLOW: do local flow control * MODE_TRAPSIG: do local mapping to TELNET IAC sequences * MODE_EDIT: do local line editing * * Command mode: * MODE_ECHO|MODE_EDIT|MODE_FLOW|MODE_TRAPSIG * local echo * local editing * local xon/xoff * local signal mapping * * Linemode: * local/no editing * Both Linemode and Single Character mode: * local/remote echo * local/no xon/xoff * local/no signal mapping */ void TerminalNewMode(f) register int f; { static int prevmode = -2; /* guaranteed unique */ #ifndef USE_TERMIO struct tchars tc; struct ltchars ltc; struct sgttyb sb; int lmode; #else /* USE_TERMIO */ struct termio tmp_tc; #endif /* USE_TERMIO */ int onoff; int old; cc_t esc; sigset_t nset; globalmode = f&~MODE_FORCE; if (prevmode == f) return; /* * Write any outstanding data before switching modes * ttyflush() returns 0 only when there was no data * to write out; it returns -1 if it couldn't do * anything at all, returns -2 if there was a write * error (other than EWOULDBLOCK), and otherwise it * returns 1 + the number of characters left to write. */ #ifndef USE_TERMIO /* * We would really like ask the kernel to wait for the output * to drain, like we can do with the TCSADRAIN, but we don't have * that option. The only ioctl that waits for the output to * drain, TIOCSETP, also flushes the input queue, which is NOT * what we want(TIOCSETP is like TCSADFLUSH). */ #endif old = ttyflush(SYNCHing|flushout); if (old == -1 || old > 1) { #ifdef USE_TERMIO (void) tcgetattr(tin, &tmp_tc); #endif /* USE_TERMIO */ do { /* * Wait for data to drain, then flush again. */ #ifdef USE_TERMIO (void) tcsetattr(tin, TCSADRAIN, &tmp_tc); #endif /* USE_TERMIO */ old = ttyflush(SYNCHing|flushout); } while (old == -1 || old > 1); } old = prevmode; prevmode = f&~MODE_FORCE; #ifndef USE_TERMIO sb = nttyb; tc = ntc; ltc = nltc; lmode = olmode; #else tmp_tc = new_tc; #endif if (f&MODE_ECHO) { #ifndef USE_TERMIO sb.sg_flags |= ECHO; #else tmp_tc.c_lflag |= ECHO; tmp_tc.c_oflag |= ONLCR; if (crlf) tmp_tc.c_iflag |= ICRNL; #endif } else { #ifndef USE_TERMIO sb.sg_flags &= ~ECHO; #else tmp_tc.c_lflag &= ~ECHO; tmp_tc.c_oflag &= ~ONLCR; #ifdef notdef if (crlf) tmp_tc.c_iflag &= ~ICRNL; #endif #endif } if ((f&MODE_FLOW) == 0) { #ifndef USE_TERMIO tc.t_startc = _POSIX_VDISABLE; tc.t_stopc = _POSIX_VDISABLE; #else tmp_tc.c_iflag &= ~(IXOFF|IXON); /* Leave the IXANY bit alone */ } else { if (restartany < 0) { /* Leave the IXANY bit alone */ tmp_tc.c_iflag |= IXOFF|IXON; } else if (restartany > 0) { tmp_tc.c_iflag |= IXOFF|IXON|IXANY; } else { tmp_tc.c_iflag |= IXOFF|IXON; tmp_tc.c_iflag &= ~IXANY; } #endif } if ((f&MODE_TRAPSIG) == 0) { #ifndef USE_TERMIO tc.t_intrc = _POSIX_VDISABLE; tc.t_quitc = _POSIX_VDISABLE; tc.t_eofc = _POSIX_VDISABLE; ltc.t_suspc = _POSIX_VDISABLE; ltc.t_dsuspc = _POSIX_VDISABLE; #else tmp_tc.c_lflag &= ~ISIG; #endif localchars = 0; } else { #ifdef USE_TERMIO tmp_tc.c_lflag |= ISIG; #endif localchars = 1; } if (f&MODE_EDIT) { #ifndef USE_TERMIO sb.sg_flags &= ~CBREAK; sb.sg_flags |= CRMOD; #else tmp_tc.c_lflag |= ICANON; #endif } else { #ifndef USE_TERMIO sb.sg_flags |= CBREAK; if (f&MODE_ECHO) sb.sg_flags |= CRMOD; else sb.sg_flags &= ~CRMOD; #else tmp_tc.c_lflag &= ~ICANON; tmp_tc.c_iflag &= ~ICRNL; tmp_tc.c_cc[VMIN] = 1; tmp_tc.c_cc[VTIME] = 0; #endif } if ((f&(MODE_EDIT|MODE_TRAPSIG)) == 0) { #ifndef USE_TERMIO ltc.t_lnextc = _POSIX_VDISABLE; #else tmp_tc.c_cc[VLNEXT] = (cc_t)(_POSIX_VDISABLE); #endif } if (f&MODE_SOFT_TAB) { #ifndef USE_TERMIO sb.sg_flags |= XTABS; #else tmp_tc.c_oflag &= ~TABDLY; tmp_tc.c_oflag |= TAB3; #endif } else { #ifndef USE_TERMIO sb.sg_flags &= ~XTABS; #else tmp_tc.c_oflag &= ~TABDLY; #endif } if (f&MODE_LIT_ECHO) { #ifndef USE_TERMIO lmode &= ~LCTLECH; #else tmp_tc.c_lflag &= ~ECHOCTL; #endif } else { #ifndef USE_TERMIO lmode |= LCTLECH; #else tmp_tc.c_lflag |= ECHOCTL; #endif } if (f == -1) { onoff = 0; } else { #ifndef USE_TERMIO if (f & MODE_OUTBIN) lmode |= LLITOUT; else lmode &= ~LLITOUT; #else if (f & MODE_OUTBIN) { tmp_tc.c_cflag &= ~(CSIZE|PARENB); tmp_tc.c_cflag |= CS8; tmp_tc.c_oflag &= ~OPOST; } else { tmp_tc.c_cflag &= ~(CSIZE|PARENB); tmp_tc.c_cflag |= old_tc.c_cflag & (CSIZE|PARENB); tmp_tc.c_oflag |= OPOST; } #endif onoff = 1; } if (f != -1) { (void) signal(SIGTSTP, susp); #if defined(USE_TERMIO) && defined(NOKERNINFO) tmp_tc.c_lflag |= NOKERNINFO; #endif /* * We don't want to process ^Y here. It's just another * character that we'll pass on to the back end. It has * to process it because it will be processed when the * user attempts to read it, not when we send it. */ #ifndef USE_TERMIO ltc.t_dsuspc = _POSIX_VDISABLE; #else tmp_tc.c_cc[VDSUSP] = (cc_t)(_POSIX_VDISABLE); #endif #ifdef USE_TERMIO /* * If the VEOL character is already set, then use VEOL2, * otherwise use VEOL. */ esc = (rlogin != _POSIX_VDISABLE) ? rlogin : escape; if ((tmp_tc.c_cc[VEOL] != esc) /* XXX */ && (tmp_tc.c_cc[VEOL2] != esc) /* XXX */) { if (tmp_tc.c_cc[VEOL] == (cc_t)(_POSIX_VDISABLE)) tmp_tc.c_cc[VEOL] = esc; else if (tmp_tc.c_cc[VEOL2] == (cc_t)(_POSIX_VDISABLE)) tmp_tc.c_cc[VEOL2] = esc; } #else if (tc.t_brkc == (cc_t)(_POSIX_VDISABLE)) tc.t_brkc = esc; #endif } else { (void) signal(SIGTSTP, SIG_DFL); (void) sigemptyset(&nset); (void) sigaddset(&nset, SIGTSTP); (void) sigprocmask(SIG_UNBLOCK, &nset, 0); #ifndef USE_TERMIO ltc = oltc; tc = otc; sb = ottyb; lmode = olmode; #else tmp_tc = old_tc; #endif } if (isatty(tin)) { #ifndef USE_TERMIO (void) ioctl(tin, TIOCLSET, &lmode); (void) ioctl(tin, TIOCSLTC, <c); (void) ioctl(tin, TIOCSETC, &tc); (void) ioctl(tin, TIOCSETN, &sb); #else if (tcsetattr(tin, TCSADRAIN, &tmp_tc) < 0) (void) tcsetattr(tin, TCSANOW, &tmp_tc); #endif (void) ioctl(tin, FIONBIO, &onoff); (void) ioctl(tout, FIONBIO, &onoff); } } /* * This code assumes that the values B0, B50, B75... * are in ascending order. They do not have to be * contiguous. */ static struct termspeeds { int speed; int value; } termspeeds[] = { { 0, B0 }, { 50, B50 }, { 75, B75 }, { 110, B110 }, { 134, B134 }, { 150, B150 }, { 200, B200 }, { 300, B300 }, { 600, B600 }, { 1200, B1200 }, { 1800, B1800 }, { 2400, B2400 }, { 4800, B4800 }, { 9600, B9600 }, { 19200, B19200 }, { 38400, B38400 }, { 57600, B57600 }, { 76800, B76800 }, { 115200, B115200 }, { 153600, B153600 }, { 230400, B230400 }, { 307200, B307200 }, { 460800, B460800 }, { 921600, B921600 }, { 1000000, B1000000 }, { 1152000, B1152000 }, { 1500000, B1500000 }, { 2000000, B2000000 }, { 2500000, B2500000 }, { 3000000, B3000000 }, { 3500000, B3500000 }, { 4000000, B4000000 }, { -1, B0 } }; void TerminalSpeeds(ispeed, ospeed) int *ispeed; int *ospeed; { register struct termspeeds *tp; register int in, out; out = cfgetospeed(&old_tc); in = cfgetispeed(&old_tc); if (in == 0) in = out; tp = termspeeds; while ((tp->speed != -1) && (tp->value < in)) { tp++; } if (tp->speed == -1) tp--; /* back up to fastest defined speed */ *ispeed = tp->speed; tp = termspeeds; while ((tp->speed != -1) && (tp->value < out)) { tp++; } if (tp->speed == -1) tp--; *ospeed = tp->speed; } int TerminalWindowSize(rows, cols) unsigned short *rows, *cols; { struct winsize ws; if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) >= 0) { *rows = ws.ws_row; *cols = ws.ws_col; return (1); } return (0); } static void NetNonblockingIO(fd, onoff) int fd; int onoff; { (void) ioctl(fd, FIONBIO, &onoff); } /* * Various signal handling routines. */ /* ARGSUSED */ static SIG_FUNC_RET deadpeer(sig) int sig; { /* * Once is all we should catch SIGPIPE. If we get it again, * it means we tried to put still more data out to a pipe * which has disappeared. In that case, telnet will exit. */ (void) signal(SIGPIPE, SIG_IGN); flushout = 1; setcommandmode(); longjmp(peerdied, -1); } boolean_t intr_happened = B_FALSE; boolean_t intr_waiting = B_FALSE; /* ARGSUSED */ static SIG_FUNC_RET intr(sig) int sig; { if (intr_waiting) { intr_happened = 1; return; } (void) signal(SIGINT, intr); if (localchars) { intp(); return; } setcommandmode(); longjmp(toplevel, -1); } /* ARGSUSED */ static SIG_FUNC_RET intr2(sig) int sig; { (void) signal(SIGQUIT, intr2); if (localchars) { /* * Ignore return to the next two function calls * since we're doing SIGQUIT */ #ifdef KLUDGELINEMODE if (kludgelinemode) { (void) sendbrk(); } else #endif sendabort(); return; } } /* ARGSUSED */ static SIG_FUNC_RET susp(sig) int sig; { (void) signal(SIGTSTP, susp); if ((rlogin != _POSIX_VDISABLE) && rlogin_susp()) return; if (localchars) sendsusp(); } /* ARGSUSED */ static SIG_FUNC_RET sendwin(sig) int sig; { (void) signal(SIGWINCH, sendwin); if (connected) { sendnaws(); } } void sys_telnet_init() { (void) signal(SIGINT, intr); (void) signal(SIGQUIT, intr2); (void) signal(SIGPIPE, deadpeer); (void) signal(SIGWINCH, sendwin); (void) signal(SIGTSTP, susp); setconnmode(0); NetNonblockingIO(net, 1); if (SetSockOpt(net, SOL_SOCKET, SO_OOBINLINE, 1) == -1) { perror("SetSockOpt"); } } /* * fatal_tty_error - * Handle case where there is an unrecoverable error on the tty * connections. Print an error, reset the terminal settings * and get out as painlessly as possible. */ void fatal_tty_error(char *doing_what) { TerminalNewMode(-1); (void) fprintf(stderr, "Error processing %s: %s\n", doing_what, strerror(errno)); exit(1); } /* * Process rings - * * This routine tries to fill up/empty our various rings. * * The parameter specifies whether this is a poll operation, * or a block-until-something-happens operation. * * The return value is 1 if something happened, 0 if not. */ int process_rings(netin, netout, netex, ttyin, ttyout, poll) int poll; /* If 0, then block until something to do */ { register int c; /* * One wants to be a bit careful about setting returnValue * to one, since a one implies we did some useful work, * and therefore probably won't be called to block next * time (TN3270 mode only). */ int returnValue = 0; static struct timeval TimeValue = { 0 }; int i; if (netout) { FD_SET(net, &obits); } if (ttyout) { FD_SET(tout, &obits); } if (ttyin) { FD_SET(tin, &ibits); } if (netin) { FD_SET(net, &ibits); } if (netex) { FD_SET(net, &xbits); } if ((c = select(16, &ibits, &obits, &xbits, (poll == 0) ? NULL : &TimeValue)) < 0) { if (c == -1) { /* * we can get EINTR if we are in line mode, * and the user does an escape (TSTP), or * some other signal generator. */ if (errno == EINTR) { return (0); } /* I don't like this, does it ever happen? */ (void) printf("sleep(5) from telnet, after select\r\n"); (void) sleep(5); } return (0); } /* * Any urgent data? */ if (FD_ISSET(net, &xbits)) { FD_CLR(net, &xbits); SYNCHing = 1; /* flush any data that is already enqueued */ i = ttyflush(1); if (i == -2) { /* This will not return. */ fatal_tty_error("write"); } } /* * Something to read from the network... */ if (FD_ISSET(net, &ibits)) { int canread; FD_CLR(net, &ibits); canread = ring_empty_consecutive(&netiring); c = recv(net, netiring.supply, canread, 0); if (c < 0 && errno == EWOULDBLOCK) { c = 0; } else if (c <= 0) { return (-1); } if (netdata) { Dump('<', netiring.supply, c); } if (c) ring_supplied(&netiring, c); returnValue = 1; } /* * Something to read from the tty... */ if (FD_ISSET(tin, &ibits)) { FD_CLR(tin, &ibits); c = TerminalRead((char *)ttyiring.supply, ring_empty_consecutive(&ttyiring)); if (c < 0) { if (errno != EWOULDBLOCK) { /* This will not return. */ fatal_tty_error("read"); } c = 0; } else { /* EOF detection for line mode!!!! */ if ((c == 0) && MODE_LOCAL_CHARS(globalmode) && isatty(tin)) { /* must be an EOF... */ eof_pending = 1; return (1); } if (c <= 0) { returnValue = -1; goto next; } if (termdata) { Dump('<', ttyiring.supply, c); } ring_supplied(&ttyiring, c); } returnValue = 1; /* did something useful */ } next: if (FD_ISSET(net, &obits)) { FD_CLR(net, &obits); returnValue |= netflush(); } if (FD_ISSET(tout, &obits)) { FD_CLR(tout, &obits); i = ttyflush(SYNCHing|flushout); if (i == -2) { /* This will not return. */ fatal_tty_error("write"); } returnValue |= (i > 0); } return (returnValue); }