[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Patch for terminal modes




Hi,

libssh lacks terminal modes support, the OpenSSH sends tty modes to remote side, while libssh does not.

Without terminal modes, some applications (e.g. Karaf OSGi container) will sends incorrect control sequences perversely (e.g. client requires CRLF, but sends just LF).

It's very easy to reproduce the problem by using our Mac app -- SSH Shell, please see attached screenshots for before/after patch applied.

This patch based on branch 0.7, we added a new function `ssh_channel_request_pty_size_modes` for sending tty modes. Compare to `ssh_channel_request_pty_size`, this function takes an extra parameter with type `struct termios`, and convert it into terminal modes data that remote side could parse.

The patch was tested under MacOS, but it should also work in other platform. The function would be disabled if the platform does not have `termios` terminology.

Note: we only implement terminal modes in client-side, the server-side still missing.

** reference **

- [RFC4254 - Encoding of Terminal Modes](https://tools.ietf.org/html/rfc4254#page-19)
- OpenSSH relate implementation:

Attachment: v0-7-ttymodes.diff
Description: Binary data


** before **

PNG image


** after **

PNG image



From e46654b5bb852e2946550b9bc422d9de9232f5e4 Mon Sep 17 00:00:00 2001
From: Hu Xiaomao <hu@xxxxxxxxxx>
Date: Wed, 16 Aug 2017 09:15:18 +0800
Subject: [PATCH] Add function  to support tty modes on

Signed-off-by: Hu Xiaomao <hu@xxxxxxxxxx>

---
 include/libssh/libssh.h |   3 +
 include/libssh/priv.h   |   6 ++
 src/CMakeLists.txt      |   7 ++
 src/channels.c          |  96 +++++++++++++++++++++-------
 src/ttymodes.c          | 166 ++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 256 insertions(+), 22 deletions(-)
 create mode 100644 src/ttymodes.c

diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h
index 3721489..50b8b5b 100644
--- a/include/libssh/libssh.h
+++ b/include/libssh/libssh.h
@@ -124,6 +124,7 @@ typedef struct ssh_session_struct* ssh_session;
 typedef struct ssh_string_struct* ssh_string;
 typedef struct ssh_event_struct* ssh_event;
 typedef void* ssh_gssapi_creds;
+typedef void* ssh_termios;
 
 /* Socket type */
 #ifdef _WIN32
@@ -396,6 +397,8 @@ LIBSSH_API int ssh_channel_request_exec(ssh_channel channel, const char *cmd);
 LIBSSH_API int ssh_channel_request_pty(ssh_channel channel);
 LIBSSH_API int ssh_channel_request_pty_size(ssh_channel channel, const char *term,
     int cols, int rows);
+LIBSSH_API int ssh_channel_request_pty_size_modes(ssh_channel channel, const char *term,
+    int cols, int rows, ssh_termios termios);
 LIBSSH_API int ssh_channel_request_shell(ssh_channel channel);
 LIBSSH_API int ssh_channel_request_send_signal(ssh_channel channel, const char *signum);
 LIBSSH_API int ssh_channel_request_sftp(ssh_channel channel);
diff --git a/include/libssh/priv.h b/include/libssh/priv.h
index 5a74915..8c34d39 100644
--- a/include/libssh/priv.h
+++ b/include/libssh/priv.h
@@ -256,6 +256,12 @@ unsigned char *bin_to_base64(const unsigned char *source, int len);
 int compress_buffer(ssh_session session,ssh_buffer buf);
 int decompress_buffer(ssh_session session,ssh_buffer buf, size_t maxlen);
 
+#ifdef HAVE_TERMIOS_H
+/* ttymodes.c */
+#include <termios.h>
+ssh_buffer tty_make_modes(struct termios *termios_p);
+#endif
+
 /* match.c */
 int match_hostname(const char *host, const char *pattern, unsigned int len);
 
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1012ddf..1613b73 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -206,6 +206,13 @@ if (WITH_ZLIB)
   )
 endif(WITH_ZLIB)
 
+if (HAVE_TERMIOS_H)
+  set(libssh_SRCS
+    ${libssh_SRCS}
+    ttymodes.c
+  )
+endif(HAVE_TERMIOS_H)
+
 if (WITH_GSSAPI AND GSSAPI_FOUND)
   set(libssh_SRCS
     ${libssh_SRCS}
diff --git a/src/channels.c b/src/channels.c
index 30c3146..11fd02b 100644
--- a/src/channels.c
+++ b/src/channels.c
@@ -34,6 +34,7 @@
 #include <arpa/inet.h>
 #endif
 
+#include "libssh/libssh.h"
 #include "libssh/priv.h"
 #include "libssh/ssh2.h"
 #include "libssh/buffer.h"
@@ -776,7 +777,7 @@ SSH_PACKET_CALLBACK(channel_rcv_request) {
 #else
     SSH_LOG(SSH_LOG_WARNING, "Unhandled channel request %s", request);
 #endif
-
+
  SAFE_FREE(request);
 
  return SSH_PACKET_USED;
@@ -1602,23 +1603,26 @@ error:
 /**
  * @brief Request a pty with a specific type and size.
  *
- * @param[in]  channel  The channel to sent the request.
+ * @param[in]  channel    The channel to sent the request.
  *
- * @param[in]  terminal The terminal type ("vt100, xterm,...").
+ * @param[in]  terminal   The terminal type ("vt100, xterm,...").
  *
- * @param[in]  col      The number of columns.
+ * @param[in]  col        The number of columns.
  *
- * @param[in]  row      The number of rows.
+ * @param[in]  row        The number of rows.
  *
- * @return              SSH_OK on success,
- *                      SSH_ERROR if an error occurred,
- *                      SSH_AGAIN if in nonblocking mode and call has
- *                      to be done again.
+ * @param[in]  termios_p  The termios used to set modes for pty
+ *
+ * @return                SSH_OK on success,
+ *                        SSH_ERROR if an error occurred,
+ *                        SSH_AGAIN if in nonblocking mode and call has
+ *                        to be done again.
  */
-int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal,
-    int col, int row) {
+int ssh_channel_request_pty_size_modes(ssh_channel channel, const char *terminal,
+    int col, int row, ssh_termios termios) {
   ssh_session session;
   ssh_buffer buffer = NULL;
+  ssh_buffer tty_modes_buffer = NULL;
   int rc = SSH_ERROR;
 
   if(channel == NULL) {
@@ -1636,7 +1640,7 @@ int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal,
     rc = channel_request_pty_size1(channel,terminal, col, row);
 
     return rc;
-    }
+  }
 #endif
   switch(channel->request_state){
   case SSH_CHANNEL_REQ_STATE_NONE:
@@ -1651,15 +1655,41 @@ int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal,
     goto error;
   }
 
-  rc = ssh_buffer_pack(buffer,
-                       "sdddddb",
-                       terminal,
-                       col,
-                       row,
-                       0, /* pix */
-                       0, /* pix */
-                       1, /* add a 0byte string */
-                       0);
+#ifdef HAVE_TERMIOS_H
+  if (termios != NULL) {
+    tty_modes_buffer = tty_make_modes(termios);
+    if (tty_modes_buffer == NULL) {
+      ssh_set_error_oom(session);
+      goto error;
+    }
+  }
+#endif
+
+  if (tty_modes_buffer != NULL) {
+    size_t tty_modes_buffer_size = tty_modes_buffer->used;
+    rc = ssh_buffer_pack(buffer,
+                         "sdddddPb",
+                         terminal,
+                         col,
+                         row,
+                         0, /* pix */
+                         0, /* pix */
+                         1 + tty_modes_buffer_size, /* add tty modes and a 0byte string */
+                         tty_modes_buffer_size,
+                         tty_modes_buffer->data,
+                         0);
+    
+  } else {
+    rc = ssh_buffer_pack(buffer,
+                         "sdddddb",
+                         terminal,
+                         col,
+                         row,
+                         0, /* pix */
+                         0, /* pix */
+                         1, /* add a 0byte string */
+                         0);
+  }
 
   if (rc != SSH_OK) {
     ssh_set_error_oom(session);
@@ -1669,11 +1699,33 @@ pending:
   rc = channel_request(channel, "pty-req", buffer, 1);
 error:
   ssh_buffer_free(buffer);
+  ssh_buffer_free(tty_modes_buffer);
 
   return rc;
 }
 
 /**
+ * @brief Request a pty with a specific type and size.
+ *
+ * @param[in]  channel  The channel to sent the request.
+ *
+ * @param[in]  terminal The terminal type ("vt100, xterm,...").
+ *
+ * @param[in]  col      The number of columns.
+ *
+ * @param[in]  row      The number of rows.
+ *
+ * @return              SSH_OK on success,
+ *                      SSH_ERROR if an error occurred,
+ *                      SSH_AGAIN if in nonblocking mode and call has
+ *                      to be done again.
+ */
+int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal,
+    int col, int row) {
+  return ssh_channel_request_pty_size_modes(channel, terminal, col, row, NULL);
+}
+
+/**
  * @brief Request a PTY.
  *
  * @param[in]  channel  The channel to send the request.
@@ -3264,7 +3316,7 @@ error:
  *          forward the content of a socket to the channel. You still have to
  *          use channel_read and channel_write for this.
  */
-int ssh_channel_open_x11(ssh_channel channel, 
+int ssh_channel_open_x11(ssh_channel channel,
         const char *orig_addr, int orig_port) {
   ssh_session session;
   ssh_buffer payload = NULL;
diff --git a/src/ttymodes.c b/src/ttymodes.c
new file mode 100644
index 0000000..7437fd6
--- /dev/null
+++ b/src/ttymodes.c
@@ -0,0 +1,166 @@
+/*
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2017 by Hu Xiaomao <hu@xxxxxxxxxx>
+ *
+ * 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 <termios.h>
+
+#include <libssh/priv.h>
+#include <libssh/libssh.h>
+#include <libssh/buffer.h>
+
+static int tty_mode_flags[36][2] = {
+  /* c_iflag */
+  {IGNPAR, 30},
+  {PARMRK, 31},
+  {INPCK,  32},
+  {ISTRIP, 33},
+  {INLCR,  34},
+  {IGNCR,  35},
+  {ICRNL,  36},
+#if defined(IUCLC)
+  {IUCLC,  37},
+#endif
+  {IXON,   38},
+  {IXANY,  39},
+  {IXOFF,  40},
+#ifdef IMAXBEL
+  {IMAXBEL,41},
+#endif /* IMAXBEL */
+  
+  /* c_lflag */
+  {ISIG,      50},
+  {ICANON,    51},
+#ifdef XCASE
+  {XCASE,     52},
+#endif
+  {ECHO,      53},
+  {ECHOE,     54},
+  {ECHOK,     55},
+  {ECHONL,    56},
+  {NOFLSH,    57},
+  {TOSTOP,    58},
+#ifdef IEXTEN
+  {IEXTEN,    59},
+#endif /* IEXTEN */
+#if defined(ECHOCTL)
+  {ECHOCTL,   60},
+#endif /* ECHOCTL */
+#ifdef ECHOKE
+  {ECHOKE,    61},
+#endif /* ECHOKE */
+#if defined(PENDIN)
+  {PENDIN,    62},
+#endif /* PENDIN */
+  
+  /* c_oflag */
+  {OPOST,      70},
+#if defined(OLCUC)
+  {OLCUC,      71},
+#endif
+#ifdef ONLCR
+  {ONLCR,      72},
+#endif
+#ifdef OCRNL
+  {OCRNL,      73},
+#endif
+#ifdef ONOCR
+  {ONOCR,      74},
+#endif
+#ifdef ONLRET
+  {ONLRET,     75},
+#endif
+  
+  /* c_cflag */
+  {CS7,         90},
+  {CS8,         91},
+  {PARENB,      92},
+  {PARODD,      93},
+  
+  /* end flag */
+  {0,      0}
+};
+
+static int buffer_add_ttymode(ssh_buffer buffer, int tty_mode[2], tcflag_t termios_flag) {
+  int rc = SSH_ERROR;
+  uint32_t mode_mark = tty_mode[0];
+  uint8_t opcode = tty_mode[1];
+  rc = buffer_add_u8(buffer, opcode);
+  if (rc != SSH_OK) {
+    return rc;
+  }
+
+  uint32_t flag = htonl((termios_flag & mode_mark) != 0);
+  rc = buffer_add_u32(buffer, flag);\
+  return rc;
+}
+
+static inline tcflag_t get_termios_flag(struct termios termios, uint8_t opcode) {
+  if (opcode >= 30 && opcode < 50) {
+    return termios.c_iflag;
+  }
+  if (opcode >= 50 && opcode < 70) {
+    return termios.c_lflag;
+  }
+  if (opcode >= 70 && opcode < 90) {
+    return termios.c_oflag;
+  }
+  if (opcode >= 90 && opcode < 100) {
+    return termios.c_oflag;
+  }
+  return 0;
+}
+
+static int buffer_add_tty_modes(ssh_buffer buffer, int tty_modes[][2], struct termios termios) {
+  int rc = SSH_ERROR;
+  int i = 0;
+  do {
+    tcflag_t termios_flag = get_termios_flag(termios, tty_modes[i][1]);
+    rc = buffer_add_ttymode(buffer, tty_modes[i], termios_flag);
+    i++;
+    if (rc != SSH_OK) {
+      return rc;
+    }
+  } while ( tty_modes[i][0] );
+  return rc;
+}
+
+/**
+ * @brief Encodes terminal modes
+ *
+ * @param[in]  termios_p  The termios used to set modes for tty
+ *
+ * @return                A SSH buffer of Encoded terminal modes, NULL on error.
+ */
+ssh_buffer tty_make_modes(struct termios *termios_p) {
+  int rc = SSH_ERROR;
+  
+  ssh_buffer buffer = ssh_buffer_new();
+  if (buffer == NULL) {
+    return NULL;
+  }
+
+  rc = buffer_add_tty_modes(buffer, tty_mode_flags, *termios_p);
+
+  if (rc != SSH_OK) {
+    ssh_buffer_free(buffer);
+    return NULL;
+  }
+
+  return buffer;
+}
-- 
2.8.1



Archive administrator: postmaster@lists.cynapses.org