| /* $OpenBSD: ttymodes.c,v 1.34 2018/07/09 21:20:26 markus Exp $ */ |
| /* |
| * Author: Tatu Ylonen <ylo@cs.hut.fi> |
| * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, 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 "includes.h" |
| |
| #include <sys/types.h> |
| |
| #include <errno.h> |
| #include <string.h> |
| #include <termios.h> |
| #include <stdarg.h> |
| |
| #include "packet.h" |
| #include "log.h" |
| #include "compat.h" |
| #include "sshbuf.h" |
| #include "ssherr.h" |
| |
| #define TTY_OP_END 0 |
| /* |
| * uint32 (u_int) follows speed. |
| */ |
| #define TTY_OP_ISPEED 128 |
| #define TTY_OP_OSPEED 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; |
| } |
| } |
| |
| /* |
| * Encode a special character into SSH line format. |
| */ |
| static u_int |
| special_char_encode(cc_t c) |
| { |
| #ifdef _POSIX_VDISABLE |
| if (c == _POSIX_VDISABLE) |
| return 255; |
| #endif /* _POSIX_VDISABLE */ |
| return c; |
| } |
| |
| /* |
| * Decode a special character from SSH line format. |
| */ |
| static cc_t |
| special_char_decode(u_int c) |
| { |
| #ifdef _POSIX_VDISABLE |
| if (c == 255) |
| return _POSIX_VDISABLE; |
| #endif /* _POSIX_VDISABLE */ |
| return c; |
| } |
| |
| /* |
| * 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 |
| ssh_tty_make_modes(struct ssh *ssh, int fd, struct termios *tiop) |
| { |
| struct termios tio; |
| struct sshbuf *buf; |
| int r, ibaud, obaud; |
| |
| if ((buf = sshbuf_new()) == NULL) |
| fatal("%s: sshbuf_new failed", __func__); |
| |
| if (tiop == NULL) { |
| if (fd == -1) { |
| debug("%s: no fd or tio", __func__); |
| 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 || \ |
| (r = sshbuf_put_u32(buf, \ |
| special_char_encode(tio.c_cc[NAME]))) != 0) \ |
| fatal("%s: buffer error: %s", __func__, ssh_err(r)); \ |
| |
| #define SSH_TTYMODE_IUTF8 42 /* for SSH_BUG_UTF8TTYMODE */ |
| |
| #define TTYMODE(NAME, FIELD, OP) \ |
| if (OP == SSH_TTYMODE_IUTF8 && (datafellows & SSH_BUG_UTF8TTYMODE)) { \ |
| debug3("%s: SSH_BUG_UTF8TTYMODE", __func__); \ |
| } else if ((r = sshbuf_put_u8(buf, OP)) != 0 || \ |
| (r = sshbuf_put_u32(buf, ((tio.FIELD & NAME) != 0))) != 0) \ |
| fatal("%s: buffer error: %s", __func__, ssh_err(r)); \ |
| |
| #include "ttymodes.h" |
| |
| #undef TTYCHAR |
| #undef TTYMODE |
| |
| end: |
| /* Mark end of mode data. */ |
| if ((r = sshbuf_put_u8(buf, TTY_OP_END)) != 0 || |
| (r = sshpkt_put_stringb(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 |
| ssh_tty_parse_modes(struct ssh *ssh, int fd) |
| { |
| struct termios tio; |
| struct sshbuf *buf; |
| const u_char *data; |
| u_char opcode; |
| u_int baud, u; |
| int r, failure = 0; |
| size_t len; |
| |
| if ((r = sshpkt_get_string_direct(ssh, &data, &len)) != 0) |
| fatal("%s: packet error: %s", __func__, ssh_err(r)); |
| if (len == 0) |
| return; |
| if ((buf = sshbuf_from(data, len)) == NULL) { |
| error("%s: sshbuf_from failed", __func__); |
| return; |
| } |
| |
| /* |
| * 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; |
| } |
| |
| while (sshbuf_len(buf) > 0) { |
| if ((r = sshbuf_get_u8(buf, &opcode)) != 0) |
| fatal("%s: packet error: %s", __func__, ssh_err(r)); |
| switch (opcode) { |
| case TTY_OP_END: |
| goto set; |
| |
| case TTY_OP_ISPEED: |
| if ((r = sshbuf_get_u32(buf, &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; |
| |
| case TTY_OP_OSPEED: |
| if ((r = sshbuf_get_u32(buf, &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: \ |
| if ((r = sshbuf_get_u32(buf, &u)) != 0) \ |
| fatal("%s: packet error: %s", __func__, \ |
| ssh_err(r)); \ |
| tio.c_cc[NAME] = special_char_decode(u); \ |
| break; |
| #define TTYMODE(NAME, FIELD, OP) \ |
| case OP: \ |
| if ((r = sshbuf_get_u32(buf, &u)) != 0) \ |
| fatal("%s: packet error: %s", __func__, \ |
| ssh_err(r)); \ |
| if (u) \ |
| 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); |
| /* |
| * 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) { |
| if ((r = sshbuf_get_u32(buf, NULL)) != 0) |
| fatal("%s: packet error: %s", __func__, |
| ssh_err(r)); |
| break; |
| } else { |
| logit("%s: unknown opcode %d", __func__, |
| opcode); |
| goto set; |
| } |
| } |
| } |
| |
| set: |
| len = sshbuf_len(buf); |
| sshbuf_free(buf); |
| if (len > 0) { |
| logit("%s: %zu bytes left", __func__, len); |
| 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)); |
| } |