diff --git a/src/protocols/ssh/Makefile.am b/src/protocols/ssh/Makefile.am index 3a5c7f73..6a6de034 100644 --- a/src/protocols/ssh/Makefile.am +++ b/src/protocols/ssh/Makefile.am @@ -29,6 +29,7 @@ libguac_client_ssh_la_SOURCES = \ settings.c \ sftp.c \ ssh.c \ + ttymode.c \ user.c noinst_HEADERS = \ @@ -38,6 +39,7 @@ noinst_HEADERS = \ settings.h \ sftp.h \ ssh.h \ + ttymode.h \ user.h # Add agent sources if enabled diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index 7c803eaa..983f7f08 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -56,6 +56,7 @@ const char* GUAC_SSH_CLIENT_ARGS[] = { "create-recording-path", "read-only", "server-alive-interval", + "backspace", NULL }; @@ -209,6 +210,13 @@ enum SSH_ARGS_IDX { */ IDX_SERVER_ALIVE_INTERVAL, + /** + * The ASCII code, as an integer, to send for the backspace key, as configured + * by the SSH connection from the client. By default this will be 127, + * the ASCII DELETE code. + */ + IDX_BACKSPACE, + SSH_ARGS_COUNT }; @@ -348,6 +356,11 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user, guac_user_parse_args_int(user, GUAC_SSH_CLIENT_ARGS, argv, IDX_SERVER_ALIVE_INTERVAL, 0); + /* Parse backspace key setting */ + settings->backspace = + guac_user_parse_args_int(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_BACKSPACE, 127); + /* Parsing was successful */ return settings; diff --git a/src/protocols/ssh/settings.h b/src/protocols/ssh/settings.h index 689d4258..175ece93 100644 --- a/src/protocols/ssh/settings.h +++ b/src/protocols/ssh/settings.h @@ -223,6 +223,11 @@ typedef struct guac_ssh_settings { */ int server_alive_interval; + /** + * The integer ASCII code of the command to send for backspace. + */ + int backspace; + } guac_ssh_settings; /** diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 0ea60bc4..7c76037a 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -26,6 +26,7 @@ #include "sftp.h" #include "ssh.h" #include "terminal/terminal.h" +#include "ttymode.h" #ifdef ENABLE_SSH_AGENT #include "ssh_agent.h" @@ -191,6 +192,8 @@ void* ssh_client_thread(void* data) { return NULL; } + char ssh_ttymodes[GUAC_SSH_TTYMODES_SIZE(1)]; + /* Set up screen recording, if requested */ if (settings->recording_path != NULL) { ssh_client->recording = guac_common_recording_create(client, @@ -206,7 +209,7 @@ void* ssh_client_thread(void* data) { ssh_client->term = guac_terminal_create(client, settings->font_name, settings->font_size, settings->resolution, settings->width, settings->height, - settings->color_scheme); + settings->color_scheme, settings->backspace); /* Fail if terminal init failed */ if (ssh_client->term == NULL) { @@ -296,9 +299,17 @@ void* ssh_client_thread(void* data) { } + /* Set up the ttymode array prior to requesting the PTY */ + int ttymodeBytes = guac_ssh_ttymodes_init(ssh_ttymodes, + GUAC_SSH_TTY_OP_VERASE, settings->backspace, GUAC_SSH_TTY_OP_END); + if (ttymodeBytes < 1) + guac_client_log(client, GUAC_LOG_WARNING, "Unable to set TTY modes." + " Backspace may not work as expected."); + /* Request PTY */ - if (libssh2_channel_request_pty_ex(ssh_client->term_channel, "linux", sizeof("linux")-1, NULL, 0, - ssh_client->term->term_width, ssh_client->term->term_height, 0, 0)) { + if (libssh2_channel_request_pty_ex(ssh_client->term_channel, "linux", sizeof("linux")-1, + ssh_ttymodes, ttymodeBytes, ssh_client->term->term_width, + ssh_client->term->term_height, 0, 0)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to allocate PTY."); return NULL; } diff --git a/src/protocols/ssh/ttymode.c b/src/protocols/ssh/ttymode.c new file mode 100644 index 00000000..e6731b4a --- /dev/null +++ b/src/protocols/ssh/ttymode.c @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "ttymode.h" + +#include +#include +#include +#include + +int guac_ssh_ttymodes_init(char opcode_array[], ...) { + + /* Initialize the variable argument list. */ + va_list args; + va_start(args, opcode_array); + + /* Initialize array pointer and byte counter. */ + char *current = opcode_array; + + /* Loop through variable argument list. */ + while (true) { + + /* Next argument should be an opcode. */ + char opcode = (char)va_arg(args, int); + *(current++) = opcode; + + /* If it's the end opcode, we're done. */ + if (opcode == GUAC_SSH_TTY_OP_END) + break; + + /* Next argument should be 4-byte value. */ + uint32_t value = va_arg(args, uint32_t); + *(current++) = (value >> 24) & 0xFF; + *(current++) = (value >> 16) & 0xFF; + *(current++) = (value >> 8) & 0xFF; + *(current++) = value & 0xFF; + } + + /* We're done processing arguments. */ + va_end(args); + + return current - opcode_array; + +} diff --git a/src/protocols/ssh/ttymode.h b/src/protocols/ssh/ttymode.h new file mode 100644 index 00000000..92e081b3 --- /dev/null +++ b/src/protocols/ssh/ttymode.h @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_SSH_TTYMODE_H +#define GUAC_SSH_TTYMODE_H + +#include "config.h" + +#include + +/** + * The size of a TTY mode encoding opcode and + * value pair. As defined in the SSH RFC, this + * is 5 bytes - a single byte for the opcode, and + * 4 bytes for the value. + */ +#define GUAC_SSH_TTY_OPCODE_SIZE 5 + +/** + * The SSH TTY mode encoding opcode that terminates + * the list of TTY modes. + */ +#define GUAC_SSH_TTY_OP_END 0 + +/** + * The SSH TTY mode encoding opcode that configures + * the TTY erase code to configure the server + * backspace key. + */ +#define GUAC_SSH_TTY_OP_VERASE 3 + +/** + * Macro for calculating the number of bytes required + * to pass a given number of opcodes, which calculates + * the size of the number of opcodes plus the single byte + * end opcode. + * + * @param num_opcodes + * The number of opcodes for which a size in bytes + * should be calculated. + * + * @returns + * The number of bytes that the given number of + * opcodes will require. + */ +#define GUAC_SSH_TTYMODES_SIZE(num_opcodes) ((GUAC_SSH_TTY_OPCODE_SIZE * num_opcodes) + 1) + +/** + * Opcodes and value pairs are passed to the SSH connection + * in a single array, beginning with the opcode and followed + * by a four byte value, repeating until the end opcode is + * encountered. This function takes the array that will be + * sent and a variable number of opcode and value pair + * arguments and places the opcode and values in the array + * as expected by the SSH connection. + * + * @param opcode_array + * Pointer to the opcode array that will ultimately + * be passed to the SSH connection. The array must + * be size to handle 5 bytes for each opcode and value + * pair, plus one additional byte for the end opcode. + * + * @params ... + * A variable number of opcode and value pairs + * to place in the array. + * + * @return + * Number of bytes written to the array, or zero + * if a failure occurs. + */ +int guac_ssh_ttymodes_init(char opcode_array[], ...); + +#endif diff --git a/src/protocols/telnet/settings.c b/src/protocols/telnet/settings.c index 082cff48..8f802915 100644 --- a/src/protocols/telnet/settings.c +++ b/src/protocols/telnet/settings.c @@ -50,6 +50,7 @@ const char* GUAC_TELNET_CLIENT_ARGS[] = { "recording-include-keys", "create-recording-path", "read-only", + "backspace", NULL }; @@ -174,6 +175,12 @@ enum TELNET_ARGS_IDX { */ IDX_READ_ONLY, + /** + * ASCII code, as an integer to use for the backspace key, or 127 + * if not specified. + */ + IDX_BACKSPACE, + TELNET_ARGS_COUNT }; @@ -328,6 +335,11 @@ guac_telnet_settings* guac_telnet_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, IDX_CREATE_RECORDING_PATH, false); + /* Parse backspace key code */ + settings->backspace = + guac_user_parse_args_int(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_BACKSPACE, 127); + /* Parsing was successful */ return settings; diff --git a/src/protocols/telnet/settings.h b/src/protocols/telnet/settings.h index 11761c63..9bc3dc37 100644 --- a/src/protocols/telnet/settings.h +++ b/src/protocols/telnet/settings.h @@ -207,6 +207,13 @@ typedef struct guac_telnet_settings { */ bool recording_include_keys; + /** + * The ASCII code, as an integer, that the telnet client will use when the + * backspace key is pressed. By default, this is 127, ASCII delete, if + * not specified in the client settings. + */ + int backspace; + } guac_telnet_settings; /** diff --git a/src/protocols/telnet/telnet.c b/src/protocols/telnet/telnet.c index 2a4000d8..040d10be 100644 --- a/src/protocols/telnet/telnet.c +++ b/src/protocols/telnet/telnet.c @@ -480,7 +480,7 @@ void* guac_telnet_client_thread(void* data) { telnet_client->term = guac_terminal_create(client, settings->font_name, settings->font_size, settings->resolution, settings->width, settings->height, - settings->color_scheme); + settings->color_scheme, settings->backspace); /* Fail if terminal init failed */ if (telnet_client->term == NULL) { diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index 4d2171a0..076305a9 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -257,7 +257,8 @@ void* guac_terminal_thread(void* data) { guac_terminal* guac_terminal_create(guac_client* client, const char* font_name, int font_size, int dpi, - int width, int height, const char* color_scheme) { + int width, int height, const char* color_scheme, + const int backspace) { int default_foreground; int default_background; @@ -406,6 +407,9 @@ guac_terminal* guac_terminal_create(guac_client* client, return NULL; } + /* Configure backspace */ + term->backspace = backspace; + return term; } @@ -1594,7 +1598,11 @@ static int __guac_terminal_send_key(guac_terminal* term, int keysym, int pressed /* Non-printable keys */ else { - if (keysym == 0xFF08) return guac_terminal_send_string(term, "\x7F"); /* Backspace */ + /* Backspace can vary based on configuration of terminal by client. */ + if (keysym == 0xFF08) { + char backspace_str[] = { term->backspace, '\0' }; + return guac_terminal_send_string(term, backspace_str); + } if (keysym == 0xFF09 || keysym == 0xFF89) return guac_terminal_send_string(term, "\x09"); /* Tab */ if (keysym == 0xFF0D || keysym == 0xFF8D) return guac_terminal_send_string(term, "\x0D"); /* Enter */ if (keysym == 0xFF1B) return guac_terminal_send_string(term, "\x1B"); /* Esc */ diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index 37b07484..c071b402 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -432,6 +432,11 @@ struct guac_terminal { */ guac_common_clipboard* clipboard; + /** + * ASCII character to send when backspace is pressed. + */ + char backspace; + }; /** @@ -464,13 +469,18 @@ struct guac_terminal { * invalid, a warning will be logged, and the terminal will fall back on * GUAC_TERMINAL_SCHEME_GRAY_BLACK. * + * @param backspace + * The integer ASCII code to send when backspace is pressed in + * this terminal. + * * @return * A new guac_terminal having the given font, dimensions, and attributes * which renders all text to the given client. */ guac_terminal* guac_terminal_create(guac_client* client, const char* font_name, int font_size, int dpi, - int width, int height, const char* color_scheme); + int width, int height, const char* color_scheme, + const int backspace); /** * Frees all resources associated with the given terminal.