/* $OpenBSD: ttymodes.c,v 1.29 2008/11/02 00:16:16 stevesk Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ /* * SSH2 tty modes support by Kevin Steves. * Copyright (c) 2001 Kevin Steves. 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. */ /* * Encoding and decoding of terminal modes in a portable way. * Much of the format is defined in ttymodes.h; it is included multiple times * into this file with the appropriate macro definitions to generate the * suitable code. */ #include #include #include #include #include #define PACKET_SKIP_COMPAT2 #define PACKET_SKIP_COMPAT #include "packet.h" #include "log.h" #include "ssh1.h" #include "compat.h" #include "sshbuf.h" #include "err.h" #define TTY_OP_END 0 /* * uint32 (u_int) follows speed in SSH1 and SSH2 */ #define TTY_OP_ISPEED_PROTO1 192 #define TTY_OP_OSPEED_PROTO1 193 #define TTY_OP_ISPEED_PROTO2 128 #define TTY_OP_OSPEED_PROTO2 129 /* * Converts POSIX speed_t to a baud rate. The values of the * constants for speed_t are not themselves portable. */ static int speed_to_baud(speed_t speed) { switch (speed) { case B0: return 0; case B50: return 50; case B75: return 75; case B110: return 110; case B134: return 134; case B150: return 150; case B200: return 200; case B300: return 300; case B600: return 600; case B1200: return 1200; case B1800: return 1800; case B2400: return 2400; case B4800: return 4800; case B9600: return 9600; #ifdef B19200 case B19200: return 19200; #else /* B19200 */ #ifdef EXTA case EXTA: return 19200; #endif /* EXTA */ #endif /* B19200 */ #ifdef B38400 case B38400: return 38400; #else /* B38400 */ #ifdef EXTB case EXTB: return 38400; #endif /* EXTB */ #endif /* B38400 */ #ifdef B7200 case B7200: return 7200; #endif /* B7200 */ #ifdef B14400 case B14400: return 14400; #endif /* B14400 */ #ifdef B28800 case B28800: return 28800; #endif /* B28800 */ #ifdef B57600 case B57600: return 57600; #endif /* B57600 */ #ifdef B76800 case B76800: return 76800; #endif /* B76800 */ #ifdef B115200 case B115200: return 115200; #endif /* B115200 */ #ifdef B230400 case B230400: return 230400; #endif /* B230400 */ default: return 9600; } } /* * Converts a numeric baud rate to a POSIX speed_t. */ static speed_t baud_to_speed(int baud) { switch (baud) { case 0: return B0; case 50: return B50; case 75: return B75; case 110: return B110; case 134: return B134; case 150: return B150; case 200: return B200; case 300: return B300; case 600: return B600; case 1200: return B1200; case 1800: return B1800; case 2400: return B2400; case 4800: return B4800; case 9600: return B9600; #ifdef B19200 case 19200: return B19200; #else /* B19200 */ #ifdef EXTA case 19200: return EXTA; #endif /* EXTA */ #endif /* B19200 */ #ifdef B38400 case 38400: return B38400; #else /* B38400 */ #ifdef EXTB case 38400: return EXTB; #endif /* EXTB */ #endif /* B38400 */ #ifdef B7200 case 7200: return B7200; #endif /* B7200 */ #ifdef B14400 case 14400: return B14400; #endif /* B14400 */ #ifdef B28800 case 28800: return B28800; #endif /* B28800 */ #ifdef B57600 case 57600: return B57600; #endif /* B57600 */ #ifdef B76800 case 76800: return B76800; #endif /* B76800 */ #ifdef B115200 case 115200: return B115200; #endif /* B115200 */ #ifdef B230400 case 230400: return B230400; #endif /* B230400 */ default: return B9600; } } static void put1(struct sshbuf *b, u_int v) { int r; if ((r = sshbuf_put_u8(b, v)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); } static void put2(struct sshbuf *b, u_int v) { int r; if ((r = sshbuf_put_u32(b, v)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); } static u_int get1(struct ssh *ssh) { int r; u_char v; if ((r = sshpkt_get_u8(ssh, &v)) != 0) fatal("%s: packet error: %s", __func__, ssh_err(r)); return v; } static u_int get2(struct ssh *ssh) { int r; u_int v; if ((r = sshpkt_get_u32(ssh, &v)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); return v; } /* * Encodes terminal modes for the terminal referenced by fd * or tiop in a portable manner, and appends the modes to a packet * being constructed. */ void tty_make_modes(struct ssh *ssh, int fd, struct termios *tiop) { struct termios tio; struct sshbuf *buf; int r, tty_op_ospeed, tty_op_ispeed, ibaud, obaud; void (*put_arg)(struct sshbuf *, u_int); if ((buf = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); if (compat20) { tty_op_ospeed = TTY_OP_OSPEED_PROTO2; tty_op_ispeed = TTY_OP_ISPEED_PROTO2; put_arg = put2; } else { tty_op_ospeed = TTY_OP_OSPEED_PROTO1; tty_op_ispeed = TTY_OP_ISPEED_PROTO1; put_arg = put1; } if (tiop == NULL) { if (fd == -1) { debug("tty_make_modes: no fd or tio"); goto end; } if (tcgetattr(fd, &tio) == -1) { logit("tcgetattr: %.100s", strerror(errno)); goto end; } } else tio = *tiop; /* Store input and output baud rates. */ obaud = speed_to_baud(cfgetospeed(&tio)); ibaud = speed_to_baud(cfgetispeed(&tio)); if ((r = sshbuf_put_u8(buf, tty_op_ospeed)) != 0 || (r = sshbuf_put_u32(buf, obaud)) != 0 || (r = sshbuf_put_u8(buf, tty_op_ispeed)) != 0 || (r = sshbuf_put_u32(buf, ibaud)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); /* Store values of mode flags. */ #define TTYCHAR(NAME, OP) \ if ((r = sshbuf_put_u8(buf, OP)) != 0) \ fatal("%s: buffer error: %s", __func__, ssh_err(r)); \ put_arg(buf, tio.c_cc[NAME]); #define TTYMODE(NAME, FIELD, OP) \ if ((r = sshbuf_put_u8(buf, OP)) != 0) \ fatal("%s: buffer error: %s", __func__, ssh_err(r)); \ put_arg(buf, ((tio.FIELD & NAME) != 0)); #include "ttymodes.h" #undef TTYCHAR #undef TTYMODE end: /* Mark end of mode data. */ if ((r = sshbuf_put_u8(buf, TTY_OP_END)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if ((r = (compat20 ? sshpkt_put_stringb : sshpkt_putb)(ssh, buf)) != 0) fatal("%s: packet error: %s", __func__, ssh_err(r)); sshbuf_free(buf); } /* * Decodes terminal modes for the terminal referenced by fd in a portable * manner from a packet being read. */ void tty_parse_modes(struct ssh *ssh, int fd, int *n_bytes_ptr) { struct termios tio; u_char opcode; u_int baud; int n_bytes = 0; int failure = 0; u_int (*get_arg)(struct ssh *); u_int u; int arg_size; int r; if (compat20) { if ((r = sshpkt_get_u32(ssh, &u)) != 0) fatal("%s: packet error: %s", __func__, ssh_err(r)); *n_bytes_ptr = u; if (*n_bytes_ptr == 0) return; get_arg = get2; arg_size = 4; } else { get_arg = get1; arg_size = 1; } /* * Get old attributes for the terminal. We will modify these * flags. I am hoping that if there are any machine-specific * modes, they will initially have reasonable values. */ if (tcgetattr(fd, &tio) == -1) { logit("tcgetattr: %.100s", strerror(errno)); failure = -1; } for (;;) { n_bytes += 1; if ((r = sshpkt_get_u8(ssh, &opcode)) != 0) fatal("%s: packet error: %s", __func__, ssh_err(r)); switch (opcode) { case TTY_OP_END: goto set; /* XXX: future conflict possible */ case TTY_OP_ISPEED_PROTO1: case TTY_OP_ISPEED_PROTO2: n_bytes += 4; if ((r = sshpkt_get_u32(ssh, &baud)) != 0) fatal("%s: packet error: %s", __func__, ssh_err(r)); if (failure != -1 && cfsetispeed(&tio, baud_to_speed(baud)) == -1) error("cfsetispeed failed for %d", baud); break; /* XXX: future conflict possible */ case TTY_OP_OSPEED_PROTO1: case TTY_OP_OSPEED_PROTO2: n_bytes += 4; if ((r = sshpkt_get_u32(ssh, &baud)) != 0) fatal("%s: packet error: %s", __func__, ssh_err(r)); if (failure != -1 && cfsetospeed(&tio, baud_to_speed(baud)) == -1) error("cfsetospeed failed for %d", baud); break; #define TTYCHAR(NAME, OP) \ case OP: \ n_bytes += arg_size; \ tio.c_cc[NAME] = get_arg(ssh); \ break; #define TTYMODE(NAME, FIELD, OP) \ case OP: \ n_bytes += arg_size; \ if (get_arg(ssh)) \ tio.FIELD |= NAME; \ else \ tio.FIELD &= ~NAME; \ break; #include "ttymodes.h" #undef TTYCHAR #undef TTYMODE default: debug("Ignoring unsupported tty mode opcode %d (0x%x)", opcode, opcode); if (!compat20) { /* * SSH1: * Opcodes 1 to 127 are defined to have * a one-byte argument. * Opcodes 128 to 159 are defined to have * an integer argument. */ if (opcode > 0 && opcode < 128) { n_bytes += 1; if ((r = sshpkt_get_u8(ssh, NULL)) != 0) fatal("%s: packet error: %s", __func__, ssh_err(r)); break; } else if (opcode >= 128 && opcode < 160) { n_bytes += 4; if ((r = sshpkt_get_u32(ssh, NULL)) != 0) fatal("%s: packet error: %s", __func__, ssh_err(r)); break; } else { /* * It is a truly undefined opcode * (160 to 255). We have no idea about * its arguments. So we must stop * parsing. Note that some data may be * left in the packet; hopefully there * is nothing more coming after the * mode data. */ logit("%s: unknown opcode %d", __func__, opcode); goto set; } } else { /* * SSH2: * Opcodes 1 to 159 are defined to have * a uint32 argument. * Opcodes 160 to 255 are undefined and * cause parsing to stop. */ if (opcode > 0 && opcode < 160) { n_bytes += 4; if ((r = sshpkt_get_u32(ssh, NULL)) != 0) fatal("%s: packet error: %s", __func__, ssh_err(r)); break; } else { logit("%s: unknown opcode %d", __func__, opcode); goto set; } } } } set: if (*n_bytes_ptr != n_bytes) { *n_bytes_ptr = n_bytes; logit("parse_tty_modes: n_bytes_ptr != n_bytes: %d %d", *n_bytes_ptr, n_bytes); return; /* Don't process bytes passed */ } if (failure == -1) return; /* Packet parsed ok but tcgetattr() failed */ /* Set the new modes for the terminal. */ if (tcsetattr(fd, TCSANOW, &tio) == -1) logit("Setting tty modes failed: %.100s", strerror(errno)); }