From cd6e84a6c3602cd75fc9bfa4d66c5a5cbfb3dc10 Mon Sep 17 00:00:00 2001 From: Daniel Evers Date: Sat, 28 Oct 2023 16:16:11 +0200 Subject: [PATCH] Issue #157: Use the current TTY's settings by default. When opening a PTY on the server, try to use the current TTY's settings (i.e. based on STDIN). If that fails or STDIN isn't a TTY, use default modes that avoid any character translation. Don't rely on stdin to be a TTY (breaks CI). Instead, open a PTY and temporarily use that as "fake" stdin. Signed-off-by: Daniel Evers (daniel.evers@utimaco.com) Reviewed-by: Jakub Jelen --- include/libssh/priv.h | 8 + src/CMakeLists.txt | 1 + src/channels.c | 15 +- src/ttyopts.c | 442 +++++++++++++++++++++++ tests/client/CMakeLists.txt | 2 +- tests/client/torture_request_pty_modes.c | 138 ++++++- 6 files changed, 589 insertions(+), 17 deletions(-) create mode 100644 src/ttyopts.c diff --git a/include/libssh/priv.h b/include/libssh/priv.h index 4ec3b2a7..596cc2d6 100644 --- a/include/libssh/priv.h +++ b/include/libssh/priv.h @@ -47,6 +47,10 @@ # endif #endif /* !defined(HAVE_STRTOULL) */ +#ifdef HAVE_TERMIOS_H +#include +#endif + #ifdef __cplusplus extern "C" { #endif @@ -452,6 +456,10 @@ bool is_ssh_initialized(void); #define SSH_ERRNO_MSG_MAX 1024 char *ssh_strerror(int err_num, char *buf, size_t buflen); +/** 55 defined options (5 bytes each) + terminator */ +#define SSH_TTY_MODES_MAX_BUFSIZE (55 * 5 + 1) +int encode_current_tty_opts(unsigned char *buf, size_t buflen); + #ifdef __cplusplus } #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 09390082..748bb7a9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -127,6 +127,7 @@ set(libssh_SRCS socket.c string.c threads.c + ttyopts.c wrapper.c external/bcrypt_pbkdf.c external/blowfish.c diff --git a/src/channels.c b/src/channels.c index ee3c8eca..9e613715 100644 --- a/src/channels.c +++ b/src/channels.c @@ -1991,9 +1991,18 @@ error: int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, int col, int row) { - /* default modes/options: none */ - const unsigned char modes[1] = {0}; - return ssh_channel_request_pty_size_modes(channel, terminal, col, row, modes, sizeof(modes)); + /* use modes from the current TTY */ + unsigned char modes_buf[SSH_TTY_MODES_MAX_BUFSIZE]; + int rc = encode_current_tty_opts(modes_buf, sizeof(modes_buf)); + if (rc < 0) { + return rc; + } + return ssh_channel_request_pty_size_modes(channel, + terminal, + col, + row, + modes_buf, + (size_t)rc); } /** diff --git a/src/ttyopts.c b/src/ttyopts.c new file mode 100644 index 00000000..d30d62d0 --- /dev/null +++ b/src/ttyopts.c @@ -0,0 +1,442 @@ +/* + * ttyopts.c - encoding of TTY modes. + * + * This file is part of the SSH Library + * + * Copyright (c) 2023 by Utimaco TS GmbH + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include +#include + +#ifdef HAVE_TERMIOS_H +#include +#endif + +/** Terminal mode opcodes */ +enum { + TTY_OP_END = 0, + TTY_OP_VINTR = 1, + TTY_OP_VQUIT = 2, + TTY_OP_VERASE = 3, + TTY_OP_VKILL = 4, + TTY_OP_VEOF = 5, + TTY_OP_VEOL = 6, + TTY_OP_VEOL2 = 7, + TTY_OP_VSTART = 8, + TTY_OP_VSTOP = 9, + TTY_OP_VSUSP = 10, + TTY_OP_VDSUSP = 11, + TTY_OP_VREPRINT = 12, + TTY_OP_VWERASE = 13, + TTY_OP_VLNEXT = 14, + TTY_OP_VFLUSH = 15, + TTY_OP_VSWTC = 16, + TTY_OP_VSTATUS = 17, + TTY_OP_VDISCARD = 18, + TTY_OP_IGNPAR = 30, + TTY_OP_PARMRK = 31, + TTY_OP_INPCK = 32, + TTY_OP_ISTRIP = 33, + TTY_OP_INLCR = 34, + TTY_OP_IGNCR = 35, + TTY_OP_ICRNL = 36, + TTY_OP_IUCLC = 37, + TTY_OP_IXON = 38, + TTY_OP_IXANY = 39, + TTY_OP_IXOFF = 40, + TTY_OP_IMAXBEL = 41, + TTY_OP_IUTF8 = 42, + TTY_OP_ISIG = 50, + TTY_OP_ICANON = 51, + TTY_OP_XCASE = 52, + TTY_OP_ECHO = 53, + TTY_OP_ECHOE = 54, + TTY_OP_ECHOK = 55, + TTY_OP_ECHONL = 56, + TTY_OP_NOFLSH = 57, + TTY_OP_TOSTOP = 58, + TTY_OP_IEXTEN = 59, + TTY_OP_ECHOCTL = 60, + TTY_OP_ECHOKE = 61, + TTY_OP_PENDIN = 62, + TTY_OP_OPOST = 70, + TTY_OP_OLCUC = 71, + TTY_OP_ONLCR = 72, + TTY_OP_OCRNL = 73, + TTY_OP_ONOCR = 74, + TTY_OP_ONLRET = 75, + TTY_OP_CS7 = 90, + TTY_OP_CS8 = 91, + TTY_OP_PARENB = 92, + TTY_OP_PARODD = 93, + TTY_OP_ISPEED = 128, + TTY_OP_OSPEED = 129, +}; + +/** + * Encodes a single SSH terminal mode option into the buffer. + * + * @param[in] attr The mode's opcode value. + * + * @param[in] value The mode's value. + * + * @param[out] buf Destination buffer to encode into. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes written to the buffer on success, -1 on + * error. + */ +static int +encode_termios_opt(unsigned char opcode, + uint32_t value, + unsigned char *buf, + size_t buflen) +{ + int offset = 0; + + /* always need 5 bytes */ + if (buflen < 5) { + return -1; + } + + /* 1 byte opcode */ + buf[offset++] = opcode; + + /* 4 bytes value (big endian) */ + value = htonl(value); + memcpy(buf + offset, &value, sizeof(value)); + offset += sizeof(value); + + return offset; +} + +#ifdef HAVE_TERMIOS_H +/** Converts a baudrate constant (Bxxxx) to a numeric value. */ +static int +baud2speed(int baudrate) +{ + switch (baudrate) { + default: + 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; + case B19200: + return 19200; + case B38400: + return 38400; + case B57600: + return 57600; + case B115200: + return 115200; + case B230400: + return 230400; + } +} + +/** + * Encodes all terminal options from the given \c termios structure + * into the buffer. + * + * @param[in] attr The terminal options to encode. + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +static int +encode_termios_opts(struct termios *attr, unsigned char *buf, size_t buflen) +{ + unsigned int offset = 0; + int rc; + +#define SSH_ENCODE_OPT(code, value) \ + rc = encode_termios_opt(code, value, buf + offset, buflen - offset); \ + if (rc < 0) { \ + return rc; \ + } else { \ + offset += rc; \ + } + +#define SSH_ENCODE_INPUT_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_iflag & opt) ? 1 : 0) + SSH_ENCODE_INPUT_OPT(IGNPAR) + SSH_ENCODE_INPUT_OPT(PARMRK) + SSH_ENCODE_INPUT_OPT(INPCK) + SSH_ENCODE_INPUT_OPT(ISTRIP) + SSH_ENCODE_INPUT_OPT(INLCR) + SSH_ENCODE_INPUT_OPT(IGNCR) + SSH_ENCODE_INPUT_OPT(ICRNL) + SSH_ENCODE_INPUT_OPT(IUCLC) + SSH_ENCODE_INPUT_OPT(IXON) + SSH_ENCODE_INPUT_OPT(IXANY) + SSH_ENCODE_INPUT_OPT(IXOFF) + SSH_ENCODE_INPUT_OPT(IMAXBEL) +#ifdef IUTF8 + SSH_ENCODE_INPUT_OPT(IUTF8) +#endif +#undef SSH_ENCODE_INPUT_OPT + +#define SSH_ENCODE_OUTPUT_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_oflag & opt) ? 1 : 0) + SSH_ENCODE_OUTPUT_OPT(OPOST) + SSH_ENCODE_OUTPUT_OPT(OLCUC) + SSH_ENCODE_OUTPUT_OPT(ONLCR) + SSH_ENCODE_OUTPUT_OPT(OCRNL) + SSH_ENCODE_OUTPUT_OPT(ONOCR) + SSH_ENCODE_OUTPUT_OPT(ONLRET) +#undef SSH_ENCODE_OUTPUT_OPT + +#define SSH_ENCODE_CONTROL_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_cflag & opt) ? 1 : 0) + SSH_ENCODE_CONTROL_OPT(CS7) + SSH_ENCODE_CONTROL_OPT(CS8) + SSH_ENCODE_CONTROL_OPT(PARENB) + SSH_ENCODE_CONTROL_OPT(PARODD) +#undef SSH_ENCODE_CONTROL_OPT + +#define SSH_ENCODE_LOCAL_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_lflag & opt) ? 1 : 0) + SSH_ENCODE_LOCAL_OPT(ISIG) + SSH_ENCODE_LOCAL_OPT(ICANON) + SSH_ENCODE_LOCAL_OPT(XCASE) + SSH_ENCODE_LOCAL_OPT(ECHO) + SSH_ENCODE_LOCAL_OPT(ECHOE) + SSH_ENCODE_LOCAL_OPT(ECHOK) + SSH_ENCODE_LOCAL_OPT(ECHONL) + SSH_ENCODE_LOCAL_OPT(NOFLSH) + SSH_ENCODE_LOCAL_OPT(TOSTOP) + SSH_ENCODE_LOCAL_OPT(IEXTEN) + SSH_ENCODE_LOCAL_OPT(ECHOCTL) + SSH_ENCODE_LOCAL_OPT(ECHOKE) + SSH_ENCODE_LOCAL_OPT(PENDIN) +#undef SSH_ENCODE_LOCAL_OPT + +#define SSH_ENCODE_CC_OPT(opt) SSH_ENCODE_OPT(TTY_OP_##opt, attr->c_cc[opt]) + SSH_ENCODE_CC_OPT(VINTR) + SSH_ENCODE_CC_OPT(VQUIT) + SSH_ENCODE_CC_OPT(VERASE) + SSH_ENCODE_CC_OPT(VKILL) + SSH_ENCODE_CC_OPT(VEOF) + SSH_ENCODE_CC_OPT(VEOL) + SSH_ENCODE_CC_OPT(VEOL2) + SSH_ENCODE_CC_OPT(VSTART) + SSH_ENCODE_CC_OPT(VSTOP) + SSH_ENCODE_CC_OPT(VSUSP) +#ifdef VDSUSP + SSH_ENCODE_CC_OPT(VDSUSP) +#endif + SSH_ENCODE_CC_OPT(VREPRINT) + SSH_ENCODE_CC_OPT(VWERASE) + SSH_ENCODE_CC_OPT(VLNEXT) +#ifdef VFLUSH + SSH_ENCODE_CC_OPT(VFLUSH) +#endif +#ifdef VSWTC + SSH_ENCODE_CC_OPT(VSWTC) +#endif +#ifdef VSTATUS + SSH_ENCODE_CC_OPT(VSTATUS) +#endif + SSH_ENCODE_CC_OPT(VDISCARD) +#undef SSH_ENCODE_CC_OPT + + SSH_ENCODE_OPT(TTY_OP_ISPEED, baud2speed(cfgetispeed(attr))) + SSH_ENCODE_OPT(TTY_OP_OSPEED, baud2speed(cfgetospeed(attr))) +#undef SSH_ENCODE_OPT + + /* end of options */ + if (buflen > offset) { + buf[offset++] = TTY_OP_END; + } else { + return -1; + } + + return (int)offset; +} +#endif + +/** + * Encodes a set of default options to ensure "sane" PTY behavior. + * This function intentionally doesn't use the \c termios structure + * to allow it to work on Windows as well. + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +static int +encode_default_opts(unsigned char *buf, size_t buflen) +{ + unsigned int offset = 0; + int rc; + +#define SSH_ENCODE_OPT(code, value) \ + rc = encode_termios_opt(code, value, buf + offset, buflen - offset); \ + if (rc < 0) { \ + return rc; \ + } else { \ + offset += rc; \ + } + + SSH_ENCODE_OPT(TTY_OP_VINTR, 003) + SSH_ENCODE_OPT(TTY_OP_VQUIT, 034) + SSH_ENCODE_OPT(TTY_OP_VERASE, 0177) + SSH_ENCODE_OPT(TTY_OP_VKILL, 025) + SSH_ENCODE_OPT(TTY_OP_VEOF, 0) + SSH_ENCODE_OPT(TTY_OP_VEOL, 0) + SSH_ENCODE_OPT(TTY_OP_VEOL2, 0) + SSH_ENCODE_OPT(TTY_OP_VSTART, 021) + SSH_ENCODE_OPT(TTY_OP_VSTOP, 023) + SSH_ENCODE_OPT(TTY_OP_VSUSP, 032) + SSH_ENCODE_OPT(TTY_OP_VDSUSP, 031) + SSH_ENCODE_OPT(TTY_OP_VREPRINT, 022) + SSH_ENCODE_OPT(TTY_OP_VWERASE, 027) + SSH_ENCODE_OPT(TTY_OP_VLNEXT, 026) + SSH_ENCODE_OPT(TTY_OP_VDISCARD, 017) + SSH_ENCODE_OPT(TTY_OP_IGNPAR, 0) + SSH_ENCODE_OPT(TTY_OP_PARMRK, 0) + SSH_ENCODE_OPT(TTY_OP_INPCK, 0) + SSH_ENCODE_OPT(TTY_OP_ISTRIP, 0) + SSH_ENCODE_OPT(TTY_OP_INLCR, 0) + SSH_ENCODE_OPT(TTY_OP_IGNCR, 0) + SSH_ENCODE_OPT(TTY_OP_ICRNL, 0) + SSH_ENCODE_OPT(TTY_OP_IUCLC, 0) + SSH_ENCODE_OPT(TTY_OP_IXON, 1) + SSH_ENCODE_OPT(TTY_OP_IXANY, 0) + SSH_ENCODE_OPT(TTY_OP_IXOFF, 0) + SSH_ENCODE_OPT(TTY_OP_IMAXBEL, 0) + SSH_ENCODE_OPT(TTY_OP_IUTF8, 1) + SSH_ENCODE_OPT(TTY_OP_ISIG, 1) + SSH_ENCODE_OPT(TTY_OP_ICANON, 1) + SSH_ENCODE_OPT(TTY_OP_XCASE, 0) + SSH_ENCODE_OPT(TTY_OP_ECHO, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOE, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOK, 1) + SSH_ENCODE_OPT(TTY_OP_ECHONL, 0) + SSH_ENCODE_OPT(TTY_OP_NOFLSH, 0) + SSH_ENCODE_OPT(TTY_OP_TOSTOP, 0) + SSH_ENCODE_OPT(TTY_OP_IEXTEN, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOCTL, 0) + SSH_ENCODE_OPT(TTY_OP_ECHOKE, 1) + SSH_ENCODE_OPT(TTY_OP_PENDIN, 0) + SSH_ENCODE_OPT(TTY_OP_OPOST, 1) + SSH_ENCODE_OPT(TTY_OP_OLCUC, 0) + SSH_ENCODE_OPT(TTY_OP_ONLCR, 0) + SSH_ENCODE_OPT(TTY_OP_OCRNL, 0) + SSH_ENCODE_OPT(TTY_OP_ONOCR, 0) + SSH_ENCODE_OPT(TTY_OP_ONLRET, 0) + SSH_ENCODE_OPT(TTY_OP_CS7, 1) + SSH_ENCODE_OPT(TTY_OP_CS8, 1) + SSH_ENCODE_OPT(TTY_OP_PARENB, 0) + SSH_ENCODE_OPT(TTY_OP_PARODD, 0) + SSH_ENCODE_OPT(TTY_OP_ISPEED, 38400); + SSH_ENCODE_OPT(TTY_OP_OSPEED, 38400); + +#undef SSH_ENCODE_OPT + + /* end of options */ + if (buflen > offset) { + buf[offset++] = TTY_OP_END; + } else { + return -1; + } + + return (int)offset; +} + +/** + * @ingroup libssh_misc + * + * @brief Encode the current TTY options as SSH modes. + * + * Call this function to determine the settings of the process' TTY and + * encode them as SSH Terminal Modes according to RFC 4254 section 8. + * + * If STDIN isn't connected to a TTY, this function fills the buffer with + * "sane" default modes. + * + * The encoded modes can be passed to \c ssh_channel_request_pty_size_modes . + * + * @code + * unsigned char modes_buf[SSH_TTY_MODES_MAX_BUFSIZE]; + * encode_current_tty_opts(modes_buf, sizeof(modes_buf)); + * @endcode + * + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +int +encode_current_tty_opts(unsigned char *buf, size_t buflen) +{ +#ifdef HAVE_TERMIOS_H + struct termios attr; + ZERO_STRUCT(attr); + + if (isatty(STDIN_FILENO)) { + /* get local terminal attributes */ + if (tcgetattr(STDIN_FILENO, &attr) < 0) { + perror("tcgetattr"); + return -1; + } + return encode_termios_opts(&attr, buf, buflen); + } +#endif + + /* use "sane" default attributes */ + return encode_default_opts(buf, buflen); +} diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt index adfc0e74..c99c94ef 100755 --- a/tests/client/CMakeLists.txt +++ b/tests/client/CMakeLists.txt @@ -67,7 +67,7 @@ foreach(_CLI_TEST ${LIBSSH_CLIENT_TESTS}) add_cmocka_test(${_CLI_TEST} SOURCES ${_CLI_TEST}.c COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} - LINK_LIBRARIES ${TORTURE_LIBRARY} + LINK_LIBRARIES ${TORTURE_LIBRARY} util ) if (OSX) diff --git a/tests/client/torture_request_pty_modes.c b/tests/client/torture_request_pty_modes.c index 8004c52c..a00be2a7 100755 --- a/tests/client/torture_request_pty_modes.c +++ b/tests/client/torture_request_pty_modes.c @@ -27,8 +27,13 @@ #include #include +#include #include #include +#include +#include +#include +#include static int sshd_setup(void **state) { @@ -75,6 +80,26 @@ static int session_teardown(void **state) return 0; } +/* reads from the channel, expecting the given output */ +static int check_channel_output(ssh_channel c, const char *expected) +{ + char buffer[4096] = {0}; + int nbytes; + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer) - 1, 0); + while (nbytes > 0) { + buffer[nbytes]='\0'; + if (strstr(buffer, expected) != NULL) + { + return 1; + } + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer), 0); + } + return 0; +} + +/* set explicit TTY modes and validate that the server uses them */ static void torture_request_pty_modes_translate_ocrnl(void **state) { const unsigned char modes[] = { @@ -92,8 +117,6 @@ static void torture_request_pty_modes_translate_ocrnl(void **state) struct torture_state *s = *state; ssh_session session = s->ssh.session; ssh_channel c; - char buffer[4096] = {0}; - int nbytes; int rc; int string_found = 0; @@ -106,20 +129,103 @@ static void torture_request_pty_modes_translate_ocrnl(void **state) rc = ssh_channel_request_pty_size_modes(c, "xterm", 80, 25, modes, sizeof(modes)); assert_ssh_return_code(session, rc); - rc = ssh_channel_request_exec(c, "echo -e '>TEST\\r\\n<'"); + rc = ssh_channel_request_exec(c, "/bin/echo -e '>TEST\\r\\n<'"); assert_ssh_return_code(session, rc); - nbytes = ssh_channel_read(c, buffer, sizeof(buffer) - 1, 0); - while (nbytes > 0) { - buffer[nbytes]='\0'; - /* expect 2 newline characters */ - if (strstr(buffer, ">TEST\n\n<") != NULL) { - string_found = 1; - break; - } + /* expect 2 newline characters */ + string_found = check_channel_output(c, ">TEST\n\n<"); + assert_int_equal(string_found, 1); - nbytes = ssh_channel_read(c, buffer, sizeof(buffer), 0); - } + ssh_channel_close(c); +} + +/* if stdin is a TTY, its modes are passed to the server */ +static void torture_request_pty_modes_use_stdin_modes(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + int rc; + int string_found = 0; + struct termios modes; + int stdin_backup_fd = -1; + int master_fd, slave_fd; + + c = ssh_channel_new(session); + assert_non_null(c); + + rc = ssh_channel_open_session(c); + assert_ssh_return_code(session, rc); + + /* stdin must be a TTY, so open one and replace the FD */ + stdin_backup_fd = dup(STDIN_FILENO); + rc = openpty(&master_fd, &slave_fd, NULL, NULL, NULL); + assert_int_equal(rc, 0); + dup2(master_fd, STDIN_FILENO); + assert_true(isatty(STDIN_FILENO)); + /* translate NL to CRNL on output to see a noticeable effect */ + memset(&modes, 0, sizeof(modes)); + tcgetattr(STDIN_FILENO, &modes); + modes.c_oflag |= ONLCR; + modes.c_iflag &= ~(ICRNL | INLCR | IGNCR); + tcsetattr(STDIN_FILENO, TCSANOW, &modes); + + rc = ssh_channel_request_pty_size(c, "xterm", 80, 25); + + /* revert the changes to STDIN first! */ + dup2(stdin_backup_fd, STDIN_FILENO); + close(stdin_backup_fd); + close(master_fd); + close(slave_fd); + + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "/bin/echo -e '>TEST\\r\\n<'"); + assert_ssh_return_code(session, rc); + + /* expect 2 carriage return characters + newline */ + string_found = check_channel_output(c, ">TEST\r\r\n<"); + assert_int_equal(string_found, 1); + + ssh_channel_close(c); +} + +/* if stdin is NOT a TTY, default modes are passed to the server */ +static void torture_request_pty_modes_use_default_modes(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + int rc; + int string_found = 0; + int stdin_backup_fd = -1; + + c = ssh_channel_new(session); + assert_non_null(c); + + rc = ssh_channel_open_session(c); + assert_ssh_return_code(session, rc); + + /* stdin must not a TTY - change the FD to something else */ + stdin_backup_fd = dup(STDIN_FILENO); + close(STDIN_FILENO); + rc = open("/dev/null", O_RDONLY); // reuses FD 0 now + assert_int_equal(rc, STDIN_FILENO); + assert_false(isatty(STDIN_FILENO)); + + rc = ssh_channel_request_pty_size(c, "xterm", 80, 25); + + /* revert the changes to STDIN first! */ + dup2(stdin_backup_fd, STDIN_FILENO); + close(stdin_backup_fd); + + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "/bin/echo -e '>TEST\\r\\n<'"); + assert_ssh_return_code(session, rc); + + /* expect the input unmodified */ + string_found = check_channel_output(c, ">TEST\r\n<"); assert_int_equal(string_found, 1); ssh_channel_close(c); @@ -132,6 +238,12 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_request_pty_modes_translate_ocrnl, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_request_pty_modes_use_stdin_modes, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_request_pty_modes_use_default_modes, + session_setup, + session_teardown), }; ssh_init();