From 55f2ec6825e720e1395d77ece55fb411c861bb18 Mon Sep 17 00:00:00 2001 From: quamrulmina Date: Mon, 12 Oct 2015 13:49:15 -0500 Subject: [PATCH] Add pty mode support code Pty mode code added so that sshd server can do remote echo, backspace processing. etc and ssh.exe client does not have to do local echo. We can enrich it in future for more features and allowing programs like powershell to run interactive. Pty mode is central for interactive use and will be built using Windows console instead of termios that Linux/Unix uses. --- channels.c | 14 ++- contrib/win32/win32compat/socket.c | 32 +++++++ session.c | 22 ++++- sshpty.c | 9 +- ttymodes.c | 136 +++++++++++++++++++++++++++++ 5 files changed, 207 insertions(+), 6 deletions(-) diff --git a/channels.c b/channels.c index 61bc0ea..f5c3e8d 100644 --- a/channels.c +++ b/channels.c @@ -2459,6 +2459,8 @@ channel_input_data(int type, u_int32_t seq, void *ctxt) if ( data[0] == '\033' ) { // escape char octal 33, decimal 27 if ( (data[1] == '[') && (data[2]== '2') && (data[3]== '0') && ( data[4]== 'h' )) { lftocrlf = 1; + data = data + 5 ; // we have processed the 5 bytes ESC sequence + data_len = data_len - 5; } } } @@ -2466,8 +2468,18 @@ channel_input_data(int type, u_int32_t seq, void *ctxt) if (c->datagram) buffer_put_string(&c->output, data, data_len); - else + else { + #ifndef WIN32_FIXME buffer_append(&c->output, data, data_len); + #else + buffer_append(&c->output, data, data_len); + if ( c->isatty ) { + buffer_append(&c->input, data, data_len); // we echo the data if it is sshd server and pty interactive mode + if ( (data_len ==1) && (data[0] == '\b') ) + buffer_append(&c->input, " \b", 2); // for backspace, we need to send space and another backspace for visual erase + } + #endif + } packet_check_eom(); return 0; } diff --git a/contrib/win32/win32compat/socket.c b/contrib/win32/win32compat/socket.c index 4e0b250..eb660f7 100644 --- a/contrib/win32/win32compat/socket.c +++ b/contrib/win32/win32compat/socket.c @@ -2453,6 +2453,38 @@ int WSHELPread(int sfd, char *dst, unsigned int max) case SFD_TYPE_CONSOLE: { ret = _read(sfd_to_fd(sfd), dst, max); + + if (FD_ISSET(sfd_to_fd(sfd), &debug_sfds)) + { + if (ret > 0) + { + dst[ret] = '\0'; + + debug("read[%d] len %d: %s", sfd_to_fd(sfd), ret, dst); + } + } + + if (ret < 0) + { + error("read from pipe/console sfd [%d] failed with error code [%d]", + sfd, GetLastError()); + } + + break; + } + case 99: + { + ret = _getch(); + if ( ( ret == 0) || (ret == 0xE0) ) { + dst[0] = ret ; + ret = _getch(); // function key or arrow key needs 2 calls, the first returning a 0 or 0xE0 + dst[1] = ret; + ret = 2; + } + else { + dst[0] = ret; + ret = 1; + } if (FD_ISSET(sfd_to_fd(sfd), &debug_sfds)) { diff --git a/session.c b/session.c index f02631e..9919a1a 100644 --- a/session.c +++ b/session.c @@ -774,6 +774,21 @@ do_exec_no_pty(Session *s, const char *command) GetUserName(name, &size); + //if (!(s -> is_subsystem)) { + // Send to the remote client ANSI/VT Sequence so that they send us CRLF in place of LF + //Channel *c=channel_by_id ( s->chanid ); + //buffer_append(&c->input, "\033[20h", 5); + //channel_output_poll(); + //} + + //if (s ->ttyfd != -1) { + // set the channel to tty interactive type + // Channel *c=channel_by_id ( s->chanid ); + // c->isatty = 1; + //} + + if ( (s->term) && (s->term[0]) ) + SetEnvironmentVariable("TERM", s->term); /* * Create new process as other user using access token object. */ @@ -869,8 +884,11 @@ do_exec_no_pty(Session *s, const char *command) */ if (compat20) - { - session_set_fds(s, sockin[1], sockout[1], sockerr[1], s -> is_subsystem, 0); + { + if ( s->ttyfd == -1) + session_set_fds(s, sockin[1], sockout[1], sockerr[1], s -> is_subsystem, 0); + else + session_set_fds(s, sockin[1], sockout[1], sockerr[1], s -> is_subsystem, 1); // tty interctive session } else { diff --git a/sshpty.c b/sshpty.c index e794566..2fba470 100644 --- a/sshpty.c +++ b/sshpty.c @@ -91,10 +91,13 @@ pty_allocate(int *ptyfd, int *ttyfd, char *namebuf, size_t namebuflen) #else /* - * Not implemented on Win32. + * Simple console screen implementation in Win32 to give a Unix like pty for interactive sessions */ - - return 0; + *ttyfd = 0; // first ttyfd & ptyfd is indexed at 0 + *ptyfd = 0; + strlcpy(namebuf, "console", namebuflen); + return 1; + //return 0; #endif } diff --git a/ttymodes.c b/ttymodes.c index 363e78a..bdbf4cf 100644 --- a/ttymodes.c +++ b/ttymodes.c @@ -505,5 +505,141 @@ set: /* Set the new modes for the terminal. */ if (tcsetattr(fd, TCSANOW, &tio) == -1) logit("Setting tty modes failed: %.100s", strerror(errno)); +#else + //struct termios tio; + u_int tio[255]; // win32 dummy + u_int tioFIELD ; + int opcode, baud; + int n_bytes = 0; + int failure = 0; + u_int (*get_arg)(void); + int arg_size; + + if (compat20) { + *n_bytes_ptr = packet_get_int(); + if (*n_bytes_ptr == 0) + return; + get_arg = packet_get_int; + arg_size = 4; + } else { + get_arg = packet_get_char; + 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; + opcode = packet_get_char(); + 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; + baud = packet_get_int(); + break; + + /* XXX: future conflict possible */ + case TTY_OP_OSPEED_PROTO1: + case TTY_OP_OSPEED_PROTO2: + n_bytes += 4; + baud = packet_get_int(); + break; + +#define TTYCHAR(NAME, OP) \ + case OP: \ + n_bytes += arg_size; \ + tio[NAME] = special_char_decode(get_arg()); \ + break; +#define TTYMODE(NAME, FIELD, OP) \ + case OP: \ + n_bytes += arg_size; \ + if (get_arg()) \ + tioFIELD |= NAME; \ + else \ + tioFIELD &= ~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; + (void) packet_get_char(); + break; + } else if (opcode >= 128 && opcode < 160) { + n_bytes += 4; + (void) packet_get_int(); + 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("parse_tty_modes: unknown opcode %d", + 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; + (void) packet_get_int(); + break; + } else { + logit("parse_tty_modes: unknown opcode %d", + 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)); #endif }