From b9330e585008af42d9446605c83d9486830604e4 Mon Sep 17 00:00:00 2001 From: Virtually Nick Date: Sat, 23 Jul 2022 18:42:34 -0400 Subject: [PATCH] GUACAMOLE-860: Initial copy of telnet protocol to tn5250. --- Makefile.am | 5 + configure.ac | 6 + src/protocols/tn5250/Makefile.am | 65 +++ src/protocols/tn5250/argv.c | 84 ++++ src/protocols/tn5250/argv.h | 73 +++ src/protocols/tn5250/client.c | 105 +++++ src/protocols/tn5250/client.h | 39 ++ src/protocols/tn5250/clipboard.c | 61 +++ src/protocols/tn5250/clipboard.h | 43 ++ src/protocols/tn5250/input.c | 138 ++++++ src/protocols/tn5250/input.h | 46 ++ src/protocols/tn5250/pipe.c | 50 +++ src/protocols/tn5250/pipe.h | 42 ++ src/protocols/tn5250/settings.c | 501 +++++++++++++++++++++ src/protocols/tn5250/settings.h | 321 +++++++++++++ src/protocols/tn5250/tn5250.c | 747 +++++++++++++++++++++++++++++++ src/protocols/tn5250/tn5250.h | 95 ++++ src/protocols/tn5250/user.c | 122 +++++ src/protocols/tn5250/user.h | 38 ++ 19 files changed, 2581 insertions(+) create mode 100644 src/protocols/tn5250/Makefile.am create mode 100644 src/protocols/tn5250/argv.c create mode 100644 src/protocols/tn5250/argv.h create mode 100644 src/protocols/tn5250/client.c create mode 100644 src/protocols/tn5250/client.h create mode 100644 src/protocols/tn5250/clipboard.c create mode 100644 src/protocols/tn5250/clipboard.h create mode 100644 src/protocols/tn5250/input.c create mode 100644 src/protocols/tn5250/input.h create mode 100644 src/protocols/tn5250/pipe.c create mode 100644 src/protocols/tn5250/pipe.h create mode 100644 src/protocols/tn5250/settings.c create mode 100644 src/protocols/tn5250/settings.h create mode 100644 src/protocols/tn5250/tn5250.c create mode 100644 src/protocols/tn5250/tn5250.h create mode 100644 src/protocols/tn5250/user.c create mode 100644 src/protocols/tn5250/user.h diff --git a/Makefile.am b/Makefile.am index 6ce3814a..6c0e23c8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -39,6 +39,7 @@ DIST_SUBDIRS = \ src/protocols/rdp \ src/protocols/ssh \ src/protocols/telnet \ + src/protocols/tn5250 \ src/protocols/vnc SUBDIRS = \ @@ -73,6 +74,10 @@ if ENABLE_TELNET SUBDIRS += src/protocols/telnet endif +if ENABLE_TN5250 +SUBDIRS += src/protocols/tn5250 +endif + if ENABLE_VNC SUBDIRS += src/protocols/vnc endif diff --git a/configure.ac b/configure.ac index 624aae13..449d0b3b 100644 --- a/configure.ac +++ b/configure.ac @@ -1004,6 +1004,9 @@ fi AM_CONDITIONAL([ENABLE_TELNET], [test "x${have_libtelnet}" = "xyes" \ -a "x${have_terminal}" = "xyes"]) +AM_CONDITIONAL([ENABLE_TN5250], [test "x${have_libtelnet}" = "xyes" \ + -a "x${have_terminal}" = "xyes"]) + AC_SUBST(TELNET_LIBS) # @@ -1190,6 +1193,7 @@ AC_CONFIG_FILES([Makefile src/protocols/rdp/tests/Makefile src/protocols/ssh/Makefile src/protocols/telnet/Makefile + src/protocols/tn5250/Makefile src/protocols/vnc/Makefile]) AC_OUTPUT @@ -1201,6 +1205,7 @@ AM_COND_IF([ENABLE_KUBERNETES], [build_kubernetes=yes], [build_kubernetes=no]) AM_COND_IF([ENABLE_RDP], [build_rdp=yes], [build_rdp=no]) AM_COND_IF([ENABLE_SSH], [build_ssh=yes], [build_ssh=no]) AM_COND_IF([ENABLE_TELNET], [build_telnet=yes], [build_telnet=no]) +AM_COND_IF([ENABLE_TN5250], [build_tn5250=yes], [build_tn5250=no]) AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no]) # @@ -1262,6 +1267,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION RDP ........... ${build_rdp} SSH ........... ${build_ssh} Telnet ........ ${build_telnet} + TN5250 ........ ${build_tn5250} VNC ........... ${build_vnc} Services / tools: diff --git a/src/protocols/tn5250/Makefile.am b/src/protocols/tn5250/Makefile.am new file mode 100644 index 00000000..a4550634 --- /dev/null +++ b/src/protocols/tn5250/Makefile.am @@ -0,0 +1,65 @@ +# +# 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. +# +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# + +AUTOMAKE_OPTIONS = foreign +ACLOCAL_AMFLAGS = -I m4 + +lib_LTLIBRARIES = libguac-client-tn5250.la + +libguac_client_tn5250_la_SOURCES = \ + argv.c \ + client.c \ + clipboard.c \ + input.c \ + pipe.c \ + settings.c \ + tn5250.c \ + user.c + +noinst_HEADERS = \ + argv.h \ + client.h \ + clipboard.h \ + input.h \ + pipe.h \ + settings.h \ + tn5250.h \ + user.h + +libguac_client_tn5250_la_CFLAGS = \ + -Werror -Wall -Iinclude \ + @LIBGUAC_INCLUDE@ \ + @TERMINAL_INCLUDE@ + +libguac_client_tn5250_la_LIBADD = \ + @COMMON_LTLIB@ \ + @LIBGUAC_LTLIB@ \ + @TERMINAL_LTLIB@ + +libguac_client_tn5250_la_LDFLAGS = \ + -version-info 0:0:0 \ + @PTHREAD_LIBS@ \ + @TELNET_LIBS@ + diff --git a/src/protocols/tn5250/argv.c b/src/protocols/tn5250/argv.c new file mode 100644 index 00000000..efd27401 --- /dev/null +++ b/src/protocols/tn5250/argv.c @@ -0,0 +1,84 @@ +/* + * 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 "argv.h" +#include "tn5250.h" +#include "terminal/terminal.h" + +#include +#include +#include + +#include +#include +#include + +int guac_tn5250_argv_callback(guac_user* user, const char* mimetype, + const char* name, const char* value, void* data) { + + guac_client* client = user->client; + guac_tn5250_client* tn5250_client = (guac_tn5250_client*) client->data; + guac_terminal* terminal = tn5250_client->term; + + /* Update color scheme */ + if (strcmp(name, GUAC_TN5250_ARGV_COLOR_SCHEME) == 0) + guac_terminal_apply_color_scheme(terminal, value); + + /* Update font name */ + else if (strcmp(name, GUAC_TN5250_ARGV_FONT_NAME) == 0) + guac_terminal_apply_font(terminal, value, -1, 0); + + /* Update only if font size is sane */ + else if (strcmp(name, GUAC_TN5250_ARGV_FONT_SIZE) == 0) { + int size = atoi(value); + if (size > 0) + guac_terminal_apply_font(terminal, NULL, size, + tn5250_client->settings->resolution); + } + + return 0; + +} + +void* guac_tn5250_send_current_argv(guac_user* user, void* data) { + + guac_tn5250_client* tn5250_client = (guac_tn5250_client*) data; + guac_terminal* terminal = tn5250_client->term; + + /* Send current color scheme */ + guac_user_stream_argv(user, user->socket, "text/plain", + GUAC_TN5250_ARGV_COLOR_SCHEME, + guac_terminal_get_color_scheme(terminal)); + + /* Send current font name */ + guac_user_stream_argv(user, user->socket, "text/plain", + GUAC_TN5250_ARGV_FONT_NAME, + guac_terminal_get_font_name(terminal)); + + /* Send current font size */ + char font_size[64]; + sprintf(font_size, "%i", guac_terminal_get_font_size(terminal)); + guac_user_stream_argv(user, user->socket, "text/plain", + GUAC_TN5250_ARGV_FONT_SIZE, font_size); + + return NULL; + +} + diff --git a/src/protocols/tn5250/argv.h b/src/protocols/tn5250/argv.h new file mode 100644 index 00000000..6a87b2d7 --- /dev/null +++ b/src/protocols/tn5250/argv.h @@ -0,0 +1,73 @@ +/* + * 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_TN5250_ARGV_H +#define GUAC_TN5250_ARGV_H + +#include "config.h" + +#include +#include + +/** + * The name of the parameter that specifies/updates the color scheme used by + * the terminal emulator. + */ +#define GUAC_TN5250_ARGV_COLOR_SCHEME "color-scheme" + +/** + * The name of the parameter that specifies/updates the name of the font used + * by the terminal emulator. + */ +#define GUAC_TN5250_ARGV_FONT_NAME "font-name" + +/** + * The name of the parameter that specifies/updates the font size used by the + * terminal emulator. + */ +#define GUAC_TN5250_ARGV_FONT_SIZE "font-size" + +/** + * Handles a received argument value from a Guacamole "argv" instruction, + * updating the given connection parameter. + */ +guac_argv_callback guac_tn5250_argv_callback; + +/** + * Sends the current values of all non-sensitive parameters which may be set + * while the connection is running to the given user. Note that the user + * receiving these values will not necessarily be able to set new values + * themselves if their connection is read-only. This function can be used as + * the callback for guac_client_foreach_user() and guac_client_for_owner() + * + * @param user + * The user that should receive the values of all non-sensitive parameters + * which may be set while the connection is running. + * + * @param data + * The guac_n5250_client instance associated with the current connection. + * + * @return + * Always NULL. + */ +void* guac_tn5250_send_current_argv(guac_user* user, void* data); + +#endif + diff --git a/src/protocols/tn5250/client.c b/src/protocols/tn5250/client.c new file mode 100644 index 00000000..9e32e8cc --- /dev/null +++ b/src/protocols/tn5250/client.c @@ -0,0 +1,105 @@ +/* + * 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 "argv.h" +#include "client.h" +#include "settings.h" +#include "tn5250.h" +#include "user.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +int guac_client_init(guac_client* client) { + + guac_client_log(client, GUAC_LOG_INFO, "%s: Starting initialization of TN5250 client.", __func__); + + /* Set client args */ + client->args = GUAC_TN5250_CLIENT_ARGS; + + /* Allocate client instance data */ + guac_tn5250_client* tn5250_client = calloc(1, sizeof(guac_tn5250_client)); + client->data = tn5250_client; + + /* Init tn5250 client */ + tn5250_client->socket_fd = -1; + tn5250_client->echo_enabled = 1; + + /* Set handlers */ + client->join_handler = guac_tn5250_user_join_handler; + client->free_handler = guac_tn5250_client_free_handler; + client->leave_handler = guac_tn5250_user_leave_handler; + + /* Register handlers for argument values that may be sent after the handshake */ + guac_argv_register(GUAC_TN5250_ARGV_COLOR_SCHEME, guac_tn5250_argv_callback, NULL, GUAC_ARGV_OPTION_ECHO); + guac_argv_register(GUAC_TN5250_ARGV_FONT_NAME, guac_tn5250_argv_callback, NULL, GUAC_ARGV_OPTION_ECHO); + guac_argv_register(GUAC_TN5250_ARGV_FONT_SIZE, guac_tn5250_argv_callback, NULL, GUAC_ARGV_OPTION_ECHO); + + /* Set locale and warn if not UTF-8 */ + setlocale(LC_CTYPE, ""); + if (strcmp(nl_langinfo(CODESET), "UTF-8") != 0) { + guac_client_log(client, GUAC_LOG_INFO, + "Current locale does not use UTF-8. Some characters may " + "not render correctly."); + } + + /* Success */ + return 0; + +} + +int guac_tn5250_client_free_handler(guac_client* client) { + + guac_tn5250_client* tn5250_client = (guac_tn5250_client*) client->data; + + /* Close tn5250 connection */ + if (tn5250_client->socket_fd != -1) + close(tn5250_client->socket_fd); + + /* Clean up recording, if in progress */ + if (tn5250_client->recording != NULL) + guac_recording_free(tn5250_client->recording); + + /* Kill terminal */ + guac_terminal_free(tn5250_client->term); + + /* Wait for and free telnet session, if connected */ + if (tn5250_client->tn5250 != NULL) { + pthread_join(tn5250_client->client_thread, NULL); + telnet_free(tn5250_client->tn5250); + } + + /* Free settings */ + if (tn5250_client->settings != NULL) + guac_tn5250_settings_free(tn5250_client->settings); + + free(tn5250_client); + return 0; + +} + diff --git a/src/protocols/tn5250/client.h b/src/protocols/tn5250/client.h new file mode 100644 index 00000000..183ab974 --- /dev/null +++ b/src/protocols/tn5250/client.h @@ -0,0 +1,39 @@ +/* + * 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_TN5250_CLIENT_H +#define GUAC_TN5250_CLIENT_H + +#include "config.h" +#include "terminal/terminal.h" + +#include +#include +#include + +#include + +/** + * Free handler. Required by libguac and called when the guac_client is + * disconnected and must be cleaned up. + */ +guac_client_free_handler guac_tn5250_client_free_handler; + +#endif + diff --git a/src/protocols/tn5250/clipboard.c b/src/protocols/tn5250/clipboard.c new file mode 100644 index 00000000..614ad69c --- /dev/null +++ b/src/protocols/tn5250/clipboard.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 "clipboard.h" +#include "tn5250.h" +#include "terminal/terminal.h" + +#include +#include +#include + +int guac_tn5250_clipboard_handler(guac_user* user, guac_stream* stream, + char* mimetype) { + + /* Clear clipboard and prepare for new data */ + guac_client* client = user->client; + guac_tn5250_client* tn5250_client = (guac_tn5250_client*) client->data; + guac_terminal_clipboard_reset(tn5250_client->term, mimetype); + + /* Set handlers for clipboard stream */ + stream->blob_handler = guac_tn5250_clipboard_blob_handler; + stream->end_handler = guac_tn5250_clipboard_end_handler; + + return 0; +} + +int guac_tn5250_clipboard_blob_handler(guac_user* user, guac_stream* stream, + void* data, int length) { + + /* Append new data */ + guac_client* client = user->client; + guac_tn5250_client* tn5250_client = (guac_tn5250_client*) client->data; + guac_terminal_clipboard_append(tn5250_client->term, data, length); + + return 0; +} + +int guac_tn5250_clipboard_end_handler(guac_user* user, guac_stream* stream) { + + /* Nothing to do - clipboard is implemented within client */ + + return 0; +} + diff --git a/src/protocols/tn5250/clipboard.h b/src/protocols/tn5250/clipboard.h new file mode 100644 index 00000000..65d7dcd7 --- /dev/null +++ b/src/protocols/tn5250/clipboard.h @@ -0,0 +1,43 @@ +/* + * 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_TN5250_CLIPBOARD_H +#define GUAC_TN5250_CLIPBOARD_H + +#include "config.h" + +#include + +/** + * Handler for inbound clipboard streams. + */ +guac_user_clipboard_handler guac_tn5250_clipboard_handler; + +/** + * Handler for data received along clipboard streams. + */ +guac_user_blob_handler guac_tn5250_clipboard_blob_handler; + +/** + * Handler for end-of-stream related to clipboard. + */ +guac_user_end_handler guac_tn5250_clipboard_end_handler; + +#endif + diff --git a/src/protocols/tn5250/input.c b/src/protocols/tn5250/input.c new file mode 100644 index 00000000..c004fd3e --- /dev/null +++ b/src/protocols/tn5250/input.c @@ -0,0 +1,138 @@ +/* + * 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 "input.h" +#include "terminal/terminal.h" +#include "tn5250.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +int guac_tn5250_user_mouse_handler(guac_user* user, int x, int y, int mask) { + + guac_client* client = user->client; + guac_tn5250_client* tn5250_client = (guac_tn5250_client*) client->data; + guac_tn5250_settings* settings = tn5250_client->settings; + guac_terminal* term = tn5250_client->term; + + /* Skip if terminal not yet ready */ + if (term == NULL) + return 0; + + /* Report mouse position within recording */ + if (tn5250_client->recording != NULL) + guac_recording_report_mouse(tn5250_client->recording, x, y, + mask); + + /* Send mouse if not searching for password or username */ + if (settings->password_regex == NULL && settings->username_regex == NULL) + guac_terminal_send_mouse(term, user, x, y, mask); + + return 0; + +} + +int guac_tn5250_user_key_handler(guac_user* user, int keysym, int pressed) { + + guac_client* client = user->client; + guac_tn5250_client* tn5250_client = (guac_tn5250_client*) client->data; + guac_tn5250_settings* settings = tn5250_client->settings; + guac_terminal* term = tn5250_client->term; + + /* Report key state within recording */ + if (tn5250_client->recording != NULL) + guac_recording_report_key(tn5250_client->recording, + keysym, pressed); + + /* Skip if terminal not yet ready */ + if (term == NULL) + return 0; + + /* Stop searching for password */ + if (settings->password_regex != NULL) { + + guac_client_log(client, GUAC_LOG_DEBUG, + "Stopping password prompt search due to user input."); + + regfree(settings->password_regex); + free(settings->password_regex); + settings->password_regex = NULL; + + } + + /* Stop searching for username */ + if (settings->username_regex != NULL) { + + guac_client_log(client, GUAC_LOG_DEBUG, + "Stopping username prompt search due to user input."); + + regfree(settings->username_regex); + free(settings->username_regex); + settings->username_regex = NULL; + + } + + /* Intercept and handle Pause / Break / Ctrl+0 as "IAC BRK" */ + if (pressed && ( + keysym == 0xFF13 /* Pause */ + || keysym == 0xFF6B /* Break */ + || ( + guac_terminal_get_mod_ctrl(term) + && keysym == '0' + ) /* Ctrl + 0 */ + )) { + + /* Send IAC BRK */ + telnet_iac(tn5250_client->tn5250, TELNET_BREAK); + + return 0; + } + + /* Send key */ + guac_terminal_send_key(term, keysym, pressed); + + return 0; + +} + +int guac_tn5250_user_size_handler(guac_user* user, int width, int height) { + + /* Get terminal */ + guac_client* client = user->client; + guac_tn5250_client* tn5250_client = (guac_tn5250_client*) client->data; + guac_terminal* terminal = tn5250_client->term; + + /* Skip if terminal not yet ready */ + if (terminal == NULL) + return 0; + + /* Resize terminal */ + guac_terminal_resize(terminal, width, height); + + return 0; +} + diff --git a/src/protocols/tn5250/input.h b/src/protocols/tn5250/input.h new file mode 100644 index 00000000..acccbd71 --- /dev/null +++ b/src/protocols/tn5250/input.h @@ -0,0 +1,46 @@ +/* + * 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_TN5250_INPUT_H +#define GUAC_TN5250_INPUT_H + +#include "config.h" + +#include + +/** + * Handler for key events. Required by libguac and called whenever key events + * are received. + */ +guac_user_key_handler guac_tn5250_user_key_handler; + +/** + * Handler for mouse events. Required by libguac and called whenever mouse + * events are received. + */ +guac_user_mouse_handler guac_tn5250_user_mouse_handler; + +/** + * Handler for size events. Required by libguac and called whenever the remote + * display (window) is resized. + */ +guac_user_size_handler guac_tn5250_user_size_handler; + +#endif + diff --git a/src/protocols/tn5250/pipe.c b/src/protocols/tn5250/pipe.c new file mode 100644 index 00000000..5f875398 --- /dev/null +++ b/src/protocols/tn5250/pipe.c @@ -0,0 +1,50 @@ +/* + * 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 "pipe.h" +#include "tn5250.h" +#include "terminal/terminal.h" + +#include +#include +#include + +#include + +int guac_tn5250_pipe_handler(guac_user* user, guac_stream* stream, + char* mimetype, char* name) { + + guac_client* client = user->client; + guac_tn5250_client* tn5250_client = (guac_tn5250_client*) client->data; + + /* Redirect STDIN if pipe has required name */ + if (strcmp(name, GUAC_TN5250_STDIN_PIPE_NAME) == 0) { + guac_terminal_send_stream(tn5250_client->term, user, stream); + return 0; + } + + /* No other inbound pipe streams are supported */ + guac_protocol_send_ack(user->socket, stream, "No such input stream.", + GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND); + guac_socket_flush(user->socket); + return 0; + +} + diff --git a/src/protocols/tn5250/pipe.h b/src/protocols/tn5250/pipe.h new file mode 100644 index 00000000..9b7006ee --- /dev/null +++ b/src/protocols/tn5250/pipe.h @@ -0,0 +1,42 @@ +/* + * 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_TN5250_PIPE_H +#define GUAC_TN5250_PIPE_H + +#include "config.h" + +#include + +/** + * The name reserved for the inbound pipe stream which forces the terminal + * emulator's STDIN to be received from the pipe. + */ +#define GUAC_TN5250_STDIN_PIPE_NAME "STDIN" + +/** + * Handles an incoming stream from a Guacamole "pipe" instruction. If the pipe + * is named "STDIN", the the contents of the pipe stream are redirected to + * STDIN of the terminal emulator for as long as the pipe is open. + */ +guac_user_pipe_handler guac_tn5250_pipe_handler; + +#endif + diff --git a/src/protocols/tn5250/settings.c b/src/protocols/tn5250/settings.c new file mode 100644 index 00000000..8d2e928a --- /dev/null +++ b/src/protocols/tn5250/settings.c @@ -0,0 +1,501 @@ +/* + * 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 "argv.h" +#include "common/defaults.h" +#include "settings.h" +#include "terminal/terminal.h" + +#include +#include + +#include +#include +#include +#include +#include + +/* Client plugin arguments */ +const char* GUAC_TN5250_CLIENT_ARGS[] = { + "hostname", + "port", + "username", + "username-regex", + "password", + "password-regex", + GUAC_TN5250_ARGV_FONT_NAME, + GUAC_TN5250_ARGV_FONT_SIZE, + GUAC_TN5250_ARGV_COLOR_SCHEME, + "typescript-path", + "typescript-name", + "create-typescript-path", + "recording-path", + "recording-name", + "recording-exclude-output", + "recording-exclude-mouse", + "recording-include-keys", + "create-recording-path", + "read-only", + "backspace", + "terminal-type", + "scrollback", + "login-success-regex", + "login-failure-regex", + "disable-copy", + "disable-paste", + NULL +}; + +enum TN5250_ARGS_IDX { + + /** + * The hostname to connect to. Required. + */ + IDX_HOSTNAME, + + /** + * The port to connect to. Optional. + */ + IDX_PORT, + + /** + * The name of the user to login as. Optional. + */ + IDX_USERNAME, + + /** + * The regular expression to use when searching for the username/login + * prompt. Optional. + */ + IDX_USERNAME_REGEX, + + /** + * The password to use when logging in. Optional. + */ + IDX_PASSWORD, + + /** + * The regular expression to use when searching for the password prompt. + * Optional. + */ + IDX_PASSWORD_REGEX, + + /** + * The name of the font to use within the terminal. + */ + IDX_FONT_NAME, + + /** + * The size of the font to use within the terminal, in points. + */ + IDX_FONT_SIZE, + + /** + * The color scheme to use, as a series of semicolon-separated color-value + * pairs: "background: ", "foreground: ", or + * "color: ", where is a number from 0 to 255, and is + * "color" or an X11 color code (e.g. "aqua" or "rgb:12/34/56"). + * The color scheme can also be one of the special values: "black-white", + * "white-black", "gray-black", or "green-black". + */ + IDX_COLOR_SCHEME, + + /** + * The full absolute path to the directory in which typescripts should be + * written. + */ + IDX_TYPESCRIPT_PATH, + + /** + * The name that should be given to typescripts which are written in the + * given path. Each typescript will consist of two files: "NAME" and + * "NAME.timing". + */ + IDX_TYPESCRIPT_NAME, + + /** + * Whether the specified typescript path should automatically be created + * if it does not yet exist. + */ + IDX_CREATE_TYPESCRIPT_PATH, + + /** + * The full absolute path to the directory in which screen recordings + * should be written. + */ + IDX_RECORDING_PATH, + + /** + * The name that should be given to screen recordings which are written in + * the given path. + */ + IDX_RECORDING_NAME, + + /** + * Whether output which is broadcast to each connected client (graphics, + * streams, etc.) should NOT be included in the session recording. Output + * is included by default, as it is necessary for any recording which must + * later be viewable as video. + */ + IDX_RECORDING_EXCLUDE_OUTPUT, + + /** + * Whether changes to mouse state, such as position and buttons pressed or + * released, should NOT be included in the session recording. Mouse state + * is included by default, as it is necessary for the mouse cursor to be + * rendered in any resulting video. + */ + IDX_RECORDING_EXCLUDE_MOUSE, + + /** + * Whether keys pressed and released should be included in the session + * recording. Key events are NOT included by default within the recording, + * as doing so has privacy and security implications. Including key events + * may be necessary in certain auditing contexts, but should only be done + * with caution. Key events can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + IDX_RECORDING_INCLUDE_KEYS, + + /** + * Whether the specified screen recording path should automatically be + * created if it does not yet exist. + */ + IDX_CREATE_RECORDING_PATH, + + /** + * "true" if this connection should be read-only (user input should be + * dropped), "false" or blank otherwise. + */ + IDX_READ_ONLY, + + /** + * ASCII code, as an integer to use for the backspace key, or + * GUAC_TERMINAL_DEFAULT_BACKSPACE if not specified. + */ + IDX_BACKSPACE, + + /** + * The terminal emulator type that is passed to the remote system (e.g. + * "xterm" or "xterm-256color"). "linux" is used if unspecified. + */ + IDX_TERMINAL_TYPE, + + /** + * The maximum size of the scrollback buffer in rows. + */ + IDX_SCROLLBACK, + + /** + * The regular expression to use when searching for whether login was + * successful. This parameter is optional. If given, the + * "login-failure-regex" parameter must also be specified, and the first + * frame of the Guacamole connection will be withheld until login + * success/failure has been determined. + */ + IDX_LOGIN_SUCCESS_REGEX, + + /** + * The regular expression to use when searching for whether login was + * unsuccessful. This parameter is optional. If given, the + * "login-success-regex" parameter must also be specified, and the first + * frame of the Guacamole connection will be withheld until login + * success/failure has been determined. + */ + IDX_LOGIN_FAILURE_REGEX, + + /** + * Whether outbound clipboard access should be blocked. If set to "true", + * it will not be possible to copy data from the terminal to the client + * using the clipboard. By default, clipboard access is not blocked. + */ + IDX_DISABLE_COPY, + + /** + * Whether inbound clipboard access should be blocked. If set to "true", it + * will not be possible to paste data from the client to the terminal using + * the clipboard. By default, clipboard access is not blocked. + */ + IDX_DISABLE_PASTE, + + TN5250_ARGS_COUNT +}; + +/** + * Compiles the given regular expression, returning NULL if compilation fails + * or of the given regular expression is NULL. The returned regex_t must be + * freed with regfree() AND free(), or with guac_tn5250_regex_free(). + * + * @param user + * The user who provided the setting associated with the given regex + * pattern. Error messages will be logged on behalf of this user. + * + * @param pattern + * The regular expression pattern to compile. + * + * @return + * The compiled regular expression, or NULL if compilation fails or NULL + * was originally provided for the pattern. + */ +static regex_t* guac_tn5250_compile_regex(guac_user* user, char* pattern) { + + /* Nothing to compile if no pattern provided */ + if (pattern == NULL) + return NULL; + + int compile_result; + regex_t* regex = malloc(sizeof(regex_t)); + + /* Compile regular expression */ + compile_result = regcomp(regex, pattern, + REG_EXTENDED | REG_NOSUB | REG_ICASE | REG_NEWLINE); + + /* Notify of failure to parse/compile */ + if (compile_result != 0) { + guac_user_log(user, GUAC_LOG_ERROR, "Regular expression '%s' " + "could not be compiled.", pattern); + free(regex); + return NULL; + } + + return regex; +} + +void guac_tn5250_regex_free(regex_t** regex) { + if (*regex != NULL) { + regfree(*regex); + free(*regex); + *regex = NULL; + } +} + +guac_tn5250_settings* guac_tn5250_parse_args(guac_user* user, + int argc, const char** argv) { + + /* Validate arg count */ + if (argc != TN5250_ARGS_COUNT) { + guac_user_log(user, GUAC_LOG_WARNING, "Incorrect number of connection " + "parameters provided: expected %i, got %i.", + TN5250_ARGS_COUNT, argc); + return NULL; + } + + guac_tn5250_settings* settings = calloc(1, sizeof(guac_tn5250_settings)); + + /* Read parameters */ + settings->hostname = + guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_HOSTNAME, ""); + + /* Read username */ + settings->username = + guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_USERNAME, NULL); + + /* Read username regex only if username is specified */ + if (settings->username != NULL) { + settings->username_regex = guac_tn5250_compile_regex(user, + guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_USERNAME_REGEX, GUAC_TN5250_DEFAULT_USERNAME_REGEX)); + } + + /* Read password */ + settings->password = + guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_PASSWORD, NULL); + + /* Read password regex only if password is specified */ + if (settings->password != NULL) { + settings->password_regex = guac_tn5250_compile_regex(user, + guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_PASSWORD_REGEX, GUAC_TN5250_DEFAULT_PASSWORD_REGEX)); + } + + /* Read optional login success detection regex */ + settings->login_success_regex = guac_tn5250_compile_regex(user, + guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_LOGIN_SUCCESS_REGEX, NULL)); + + /* Read optional login failure detection regex */ + settings->login_failure_regex = guac_tn5250_compile_regex(user, + guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_LOGIN_FAILURE_REGEX, NULL)); + + /* Both login success and login failure regexes must be provided if either + * is present at all */ + if (settings->login_success_regex != NULL + && settings->login_failure_regex == NULL) { + guac_tn5250_regex_free(&settings->login_success_regex); + guac_user_log(user, GUAC_LOG_WARNING, "Ignoring provided value for " + "\"%s\" as \"%s\" must also be provided.", + GUAC_TN5250_CLIENT_ARGS[IDX_LOGIN_SUCCESS_REGEX], + GUAC_TN5250_CLIENT_ARGS[IDX_LOGIN_FAILURE_REGEX]); + } + else if (settings->login_failure_regex != NULL + && settings->login_success_regex == NULL) { + guac_tn5250_regex_free(&settings->login_failure_regex); + guac_user_log(user, GUAC_LOG_WARNING, "Ignoring provided value for " + "\"%s\" as \"%s\" must also be provided.", + GUAC_TN5250_CLIENT_ARGS[IDX_LOGIN_FAILURE_REGEX], + GUAC_TN5250_CLIENT_ARGS[IDX_LOGIN_SUCCESS_REGEX]); + } + + /* Read-only mode */ + settings->read_only = + guac_user_parse_args_boolean(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_READ_ONLY, false); + + /* Read maximum scrollback size */ + settings->max_scrollback = + guac_user_parse_args_int(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_SCROLLBACK, GUAC_TERMINAL_DEFAULT_MAX_SCROLLBACK); + + /* Read font name */ + settings->font_name = + guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_FONT_NAME, GUAC_TERMINAL_DEFAULT_FONT_NAME); + + /* Read font size */ + settings->font_size = + guac_user_parse_args_int(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_FONT_SIZE, GUAC_TERMINAL_DEFAULT_FONT_SIZE); + + /* Copy requested color scheme */ + settings->color_scheme = + guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_COLOR_SCHEME, GUAC_TERMINAL_DEFAULT_COLOR_SCHEME); + + /* Pull width/height/resolution directly from user */ + settings->width = user->info.optimal_width; + settings->height = user->info.optimal_height; + settings->resolution = user->info.optimal_resolution; + + /* Read port */ + settings->port = + guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_PORT, GUAC_TN5250_DEFAULT_PORT); + + /* Read typescript path */ + settings->typescript_path = + guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_TYPESCRIPT_PATH, NULL); + + /* Read typescript name */ + settings->typescript_name = + guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_TYPESCRIPT_NAME, GUAC_TN5250_DEFAULT_TYPESCRIPT_NAME); + + /* Parse path creation flag */ + settings->create_typescript_path = + guac_user_parse_args_boolean(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_CREATE_TYPESCRIPT_PATH, false); + + /* Read recording path */ + settings->recording_path = + guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_RECORDING_PATH, NULL); + + /* Read recording name */ + settings->recording_name = + guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_RECORDING_NAME, GUAC_TN5250_DEFAULT_RECORDING_NAME); + + /* Parse output exclusion flag */ + settings->recording_exclude_output = + guac_user_parse_args_boolean(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_RECORDING_EXCLUDE_OUTPUT, false); + + /* Parse mouse exclusion flag */ + settings->recording_exclude_mouse = + guac_user_parse_args_boolean(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_RECORDING_EXCLUDE_MOUSE, false); + + /* Parse key event inclusion flag */ + settings->recording_include_keys = + guac_user_parse_args_boolean(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_RECORDING_INCLUDE_KEYS, false); + + /* Parse path creation flag */ + settings->create_recording_path = + guac_user_parse_args_boolean(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_CREATE_RECORDING_PATH, false); + + /* Parse backspace key code */ + settings->backspace = + guac_user_parse_args_int(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_BACKSPACE, GUAC_TERMINAL_DEFAULT_BACKSPACE); + + /* Read terminal emulator type. */ + settings->terminal_type = + guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_TERMINAL_TYPE, "linux"); + + /* Parse clipboard copy disable flag */ + settings->disable_copy = + guac_user_parse_args_boolean(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_DISABLE_COPY, false); + + /* Parse clipboard paste disable flag */ + settings->disable_paste = + guac_user_parse_args_boolean(user, GUAC_TN5250_CLIENT_ARGS, argv, + IDX_DISABLE_PASTE, false); + + /* Parsing was successful */ + return settings; + +} + +void guac_tn5250_settings_free(guac_tn5250_settings* settings) { + + /* Free network connection information */ + free(settings->hostname); + free(settings->port); + + /* Free credentials */ + free(settings->username); + free(settings->password); + + /* Free various regexes */ + guac_tn5250_regex_free(&settings->username_regex); + guac_tn5250_regex_free(&settings->password_regex); + guac_tn5250_regex_free(&settings->login_success_regex); + guac_tn5250_regex_free(&settings->login_failure_regex); + + /* Free display preferences */ + free(settings->font_name); + free(settings->color_scheme); + + /* Free typescript settings */ + free(settings->typescript_name); + free(settings->typescript_path); + + /* Free screen recording settings */ + free(settings->recording_name); + free(settings->recording_path); + + /* Free terminal emulator type. */ + free(settings->terminal_type); + + /* Free overall structure */ + free(settings); + +} + diff --git a/src/protocols/tn5250/settings.h b/src/protocols/tn5250/settings.h new file mode 100644 index 00000000..aeedd775 --- /dev/null +++ b/src/protocols/tn5250/settings.h @@ -0,0 +1,321 @@ +/* + * 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_TN5250_SETTINGS_H +#define GUAC_TN5250_SETTINGS_H + +#include "config.h" + +#include + +#include +#include +#include +/** + * The port to connect to when initiating any tn5250 connection, if no other + * port is specified. + */ +#define GUAC_TN5250_DEFAULT_PORT "23" + +/** + * The filename to use for the typescript, if not specified. + */ +#define GUAC_TN5250_DEFAULT_TYPESCRIPT_NAME "typescript" + +/** + * The filename to use for the screen recording, if not specified. + */ +#define GUAC_TN5250_DEFAULT_RECORDING_NAME "recording" + +/** + * The regular expression to use when searching for the username/login prompt + * if no other regular expression is specified. + */ +#define GUAC_TN5250_DEFAULT_USERNAME_REGEX "[Ll]ogin:" + +/** + * The regular expression to use when searching for the password prompt if no + * other regular expression is specified. + */ +#define GUAC_TN5250_DEFAULT_PASSWORD_REGEX "[Pp]assword:" + +/** + * Settings for the TN5250 connection. The values for this structure are parsed + * from the arguments given during the Guacamole protocol handshake using the + * guac_tn5250_parse_args() function. + */ +typedef struct guac_tn5250_settings { + + /** + * The hostname of the tn5250 server to connect to. + */ + char* hostname; + + /** + * The port of the tn5250 server to connect to. + */ + char* port; + + /** + * The name of the user to login as, if any. If no username is specified, + * this will be NULL. + */ + char* username; + + /** + * The regular expression to use when searching for the username/login + * prompt. If no username is specified, this will be NULL. If a username + * is specified, this will either be the specified username regex, or the + * default username regex. + */ + regex_t* username_regex; + + /** + * The password to give when authenticating, if any. If no password is + * specified, this will be NULL. + */ + char* password; + + /** + * The regular expression to use when searching for the password prompt. If + * no password is specified, this will be NULL. If a password is specified, + * this will either be the specified password regex, or the default + * password regex. + */ + regex_t* password_regex; + + /** + * The regular expression to use when searching for whether login was + * successful. If no such regex is specified, or if no login failure regex + * was specified, this will be NULL. + */ + regex_t* login_success_regex; + + /** + * The regular expression to use when searching for whether login failed. + * If no such regex is specified, or if no login success regex was + * specified, this will be NULL. + */ + regex_t* login_failure_regex; + + /** + * Whether this connection is read-only, and user input should be dropped. + */ + bool read_only; + + /** + * The maximum size of the scrollback buffer in rows. + */ + int max_scrollback; + + /** + * The name of the font to use for display rendering. + */ + char* font_name; + + /** + * The size of the font to use, in points. + */ + int font_size; + + /** + * The name of the color scheme to use. + */ + char* color_scheme; + + /** + * The desired width of the terminal display, in pixels. + */ + int width; + + /** + * The desired height of the terminal display, in pixels. + */ + int height; + + /** + * The desired screen resolution, in DPI. + */ + int resolution; + + /** + * Whether outbound clipboard access should be blocked. If set, it will not + * be possible to copy data from the terminal to the client using the + * clipboard. + */ + bool disable_copy; + + /** + * Whether inbound clipboard access should be blocked. If set, it will not + * be possible to paste data from the client to the terminal using the + * clipboard. + */ + bool disable_paste; + + /** + * The path in which the typescript should be saved, if enabled. If no + * typescript should be saved, this will be NULL. + */ + char* typescript_path; + + /** + * The filename to use for the typescript, if enabled. + */ + char* typescript_name; + + /** + * Whether the typescript path should be automatically created if it does + * not already exist. + */ + bool create_typescript_path; + + /** + * The path in which the screen recording should be saved, if enabled. If + * no screen recording should be saved, this will be NULL. + */ + char* recording_path; + + /** + * The filename to use for the screen recording, if enabled. + */ + char* recording_name; + + /** + * Whether the screen recording path should be automatically created if it + * does not already exist. + */ + bool create_recording_path; + + /** + * Whether output which is broadcast to each connected client (graphics, + * streams, etc.) should NOT be included in the session recording. Output + * is included by default, as it is necessary for any recording which must + * later be viewable as video. + */ + bool recording_exclude_output; + + /** + * Whether changes to mouse state, such as position and buttons pressed or + * released, should NOT be included in the session recording. Mouse state + * is included by default, as it is necessary for the mouse cursor to be + * rendered in any resulting video. + */ + bool recording_exclude_mouse; + + /** + * Whether keys pressed and released should be included in the session + * recording. Key events are NOT included by default within the recording, + * as doing so has privacy and security implications. Including key events + * may be necessary in certain auditing contexts, but should only be done + * with caution. Key events can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + bool recording_include_keys; + + /** + * The ASCII code, as an integer, that the tn5250 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; + + /** + * The terminal emulator type that is passed to the remote system. + */ + char* terminal_type; + + /** + * Whether or not to send the magic Wake-on-LAN (WoL) packet prior to + * continuing the connection. + */ + bool wol_send_packet; + + /** + * The MAC address to put in the magic WoL packet for the remote host to + * wake. + */ + char* wol_mac_addr; + + /** + * The broadcast address to which to send the magic WoL packet to wake + * the remote host. + */ + char* wol_broadcast_addr; + + /** + * The UDP port to use when sending the WoL packet. + */ + unsigned short wol_udp_port; + + /** + * The number of seconds to wait after sending the magic WoL packet before + * continuing the connection. + */ + int wol_wait_time; + +} guac_tn5250_settings; + +/** + * Parses all given args, storing them in a newly-allocated settings object. If + * the args fail to parse, NULL is returned. + * + * @param user + * The user who submitted the given arguments while joining the + * connection. + * + * @param argc + * The number of arguments within the argv array. + * + * @param argv + * The values of all arguments provided by the user. + * + * @return + * A newly-allocated settings object which must be freed with + * guac_tn5250_settings_free() when no longer needed. If the arguments fail + * to parse, NULL is returned. + */ +guac_tn5250_settings* guac_tn5250_parse_args(guac_user* user, + int argc, const char** argv); + +/** + * Frees the regex pointed to by the given pointer, assigning the value NULL to + * that pointer once the regex is freed. If the pointer already contains NULL, + * this function has no effect. + * + * @param regex + * The address of the pointer to the regex that should be freed. + */ +void guac_tn5250_regex_free(regex_t** regex); + +/** + * Frees the given guac_tn5250_settings object, having been previously + * allocated via guac_tn5250_parse_args(). + * + * @param settings + * The settings object to free. + */ +void guac_tn5250_settings_free(guac_tn5250_settings* settings); + +/** + * NULL-terminated array of accepted client args. + */ +extern const char* GUAC_TN5250_CLIENT_ARGS[]; + +#endif + diff --git a/src/protocols/tn5250/tn5250.c b/src/protocols/tn5250/tn5250.c new file mode 100644 index 00000000..3bc8829c --- /dev/null +++ b/src/protocols/tn5250/tn5250.c @@ -0,0 +1,747 @@ +/* + * 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 "argv.h" +#include "tn5250.h" +#include "terminal/terminal.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Support levels for various telnet options, required for connection + * negotiation by telnet_init(), part of libtelnet. According to RFC1205, + * support for BINARY, TERMINAL TYPE (TTYPE), and END OF RECORD (EOR) must be + * enabled for TN5250 to operate. TERMINAL is only supported + * client-side (WILL), while BINARY and END OF RECORD should be agreed upon + * between client and server. + */ +static const telnet_telopt_t __telnet_options[] = { + { TELNET_TELOPT_BINARY, TELNET_WILL, TELNET_DO }, + { TELNET_TELOPT_ECHO, TELNET_WONT, TELNET_DO }, + { TELNET_TELOPT_EOR, TELNET_WILL, TELNET_DO }, + { TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DONT }, + { TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DO }, + { TELNET_TELOPT_MSSP, TELNET_WONT, TELNET_DO }, + { TELNET_TELOPT_NAWS, TELNET_WILL, TELNET_DONT }, + { TELNET_TELOPT_NEW_ENVIRON, TELNET_WILL, TELNET_DONT }, + { -1, 0, 0 } +}; + +/** + * Write the entire buffer given to the specified file descriptor, retrying + * the write automatically if necessary. This function will return a value + * not equal to the buffer's size iff an error occurs which prevents all + * future writes. + * + * @param fd The file descriptor to write to. + * @param buffer The buffer to write. + * @param size The number of bytes from the buffer to write. + */ +static int __guac_tn5250_write_all(int fd, const char* buffer, int size) { + + int remaining = size; + while (remaining > 0) { + + /* Attempt to write data */ + int ret_val = write(fd, buffer, remaining); + if (ret_val <= 0) + return -1; + + /* If successful, contine with what data remains (if any) */ + remaining -= ret_val; + buffer += ret_val; + + } + + return size; + +} + +/** + * Matches the given line against the given regex, returning true and sending + * the given value if a match is found. An enter keypress is automatically + * sent after the value is sent. + * + * @param client + * The guac_client associated with the tn5250 session. + * + * @param regex + * The regex to search for within the given line buffer. + * + * @param value + * The string value to send through STDIN of the tn5250 session if a + * match is found, or NULL if no value should be sent. + * + * @param line_buffer + * The line of character data to test. + * + * @return + * true if a match is found, false otherwise. + */ +static bool guac_tn5250_regex_exec(guac_client* client, regex_t* regex, + const char* value, const char* line_buffer) { + + guac_tn5250_client* tn5250_client = (guac_tn5250_client*) client->data; + + /* Send value upon match */ + if (regexec(regex, line_buffer, 0, NULL, 0) == 0) { + + /* Send value */ + if (value != NULL) { + guac_terminal_send_string(tn5250_client->term, value); + guac_terminal_send_string(tn5250_client->term, "\x0D"); + } + + /* Stop searching for prompt */ + return true; + + } + + return false; + +} + +/** + * Matches the given line against the various stored regexes, automatically + * sending the configured username, password, or reporting login + * success/failure depending on context. If no search is in progress, either + * because no regexes have been defined or because all applicable searches have + * completed, this function has no effect. + * + * @param client + * The guac_client associated with the tn5250 session. + * + * @param line_buffer + * The line of character data to test. + */ +static void guac_tn5250_search_line(guac_client* client, const char* line_buffer) { + + guac_tn5250_client* tn5250_client = (guac_tn5250_client*) client->data; + guac_tn5250_settings* settings = tn5250_client->settings; + + /* Continue search for username prompt */ + if (settings->username_regex != NULL) { + if (guac_tn5250_regex_exec(client, settings->username_regex, + settings->username, line_buffer)) { + guac_client_log(client, GUAC_LOG_DEBUG, "Username sent"); + guac_tn5250_regex_free(&settings->username_regex); + } + } + + /* Continue search for password prompt */ + if (settings->password_regex != NULL) { + if (guac_tn5250_regex_exec(client, settings->password_regex, + settings->password, line_buffer)) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Password sent"); + + /* Do not continue searching for username/password once password is sent */ + guac_tn5250_regex_free(&settings->username_regex); + guac_tn5250_regex_free(&settings->password_regex); + + } + } + + /* Continue search for login success */ + if (settings->login_success_regex != NULL) { + if (guac_tn5250_regex_exec(client, settings->login_success_regex, + NULL, line_buffer)) { + + /* Allow terminal to render now that login has been deemed successful */ + guac_client_log(client, GUAC_LOG_DEBUG, "Login successful"); + guac_terminal_start(tn5250_client->term); + + /* Stop all searches */ + guac_tn5250_regex_free(&settings->username_regex); + guac_tn5250_regex_free(&settings->password_regex); + guac_tn5250_regex_free(&settings->login_success_regex); + guac_tn5250_regex_free(&settings->login_failure_regex); + + } + } + + /* Continue search for login failure */ + if (settings->login_failure_regex != NULL) { + if (guac_tn5250_regex_exec(client, settings->login_failure_regex, + NULL, line_buffer)) { + + /* Advise that login has failed and connection should be closed */ + guac_client_abort(client, + GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Login failed"); + + /* Stop all searches */ + guac_tn5250_regex_free(&settings->username_regex); + guac_tn5250_regex_free(&settings->password_regex); + guac_tn5250_regex_free(&settings->login_success_regex); + guac_tn5250_regex_free(&settings->login_failure_regex); + + } + } + +} + +/** + * Searches for a line matching the various stored regexes, automatically + * sending the configured username, password, or reporting login + * success/failure depending on context. If no search is in progress, either + * because no regexes have been defined or because all applicable searches + * have completed, this function has no effect. + * + * @param client + * The guac_client associated with the tn5250 session. + * + * @param buffer + * The buffer of received data to search through. + * + * @param size + * The size of the given buffer, in bytes. + */ +static void guac_tn5250_search(guac_client* client, const char* buffer, int size) { + + static char line_buffer[1024] = {0}; + static int length = 0; + + /* Append all characters in buffer to current line */ + const char* current = buffer; + for (int i = 0; i < size; i++) { + + char c = *(current++); + + /* Attempt pattern match and clear buffer upon reading newline */ + if (c == '\n') { + if (length > 0) { + line_buffer[length] = '\0'; + guac_tn5250_search_line(client, line_buffer); + length = 0; + } + } + + /* Append all non-newline characters to line buffer as long as space + * remains */ + else if (length < sizeof(line_buffer) - 1) + line_buffer[length++] = c; + + } + + /* Attempt pattern match if an unfinished line remains (may be a prompt) */ + if (length > 0) { + line_buffer[length] = '\0'; + guac_tn5250_search_line(client, line_buffer); + } + +} + +/** + * Event handler, as defined by libtelnet. This function is passed to + * telnet_init() and will be called for every event fired by libtelnet, + * including feature enable/disable and receipt/transmission of data. + */ +static void __guac_tn5250_event_handler(telnet_t* tn5250, telnet_event_t* event, void* data) { + + guac_client* client = (guac_client*) data; + guac_tn5250_client* tn5250_client = (guac_tn5250_client*) client->data; + guac_tn5250_settings* settings = tn5250_client->settings; + + switch (event->type) { + + /* Terminal output received */ + case TELNET_EV_DATA: + guac_client_log(client, GUAC_LOG_TRACE, + "%s: Received %d bytes of data from remote side.", __func__, event->data.size); + if (tn5250_client->binary_mode) { + guac_client_log(client, GUAC_LOG_TRACE, + "%s: Receiving binary data - it will not be printed to the screen.", + __func__); + } + else { + guac_terminal_write(tn5250_client->term, event->data.buffer, event->data.size); + guac_tn5250_search(client, event->data.buffer, event->data.size); + } + break; + + /* Data destined for remote end */ + case TELNET_EV_SEND: + guac_client_log(client, GUAC_LOG_TRACE, + "%s: Sending %d bytes of data to remote side.", __func__, event->data.size); + if (__guac_tn5250_write_all(tn5250_client->socket_fd, event->data.buffer, event->data.size) + != event->data.size) + guac_client_stop(client); + break; + + /* Remote feature enabled */ + case TELNET_EV_WILL: + if (event->neg.telopt == TELNET_TELOPT_ECHO) + tn5250_client->echo_enabled = 0; /* Disable local echo, as remote will echo */ + if (event->neg.telopt == TELNET_TELOPT_EOR) { + guac_client_log(client, GUAC_LOG_DEBUG, + "%s: Received End of Record support from remote side.", + __func__); + } + if (event->neg.telopt == TELNET_TELOPT_BINARY) { + guac_client_log(client, GUAC_LOG_DEBUG, + "%s: Received Binary support from remote side.", + __func__); + } + break; + + /* Remote feature disabled */ + case TELNET_EV_WONT: + if (event->neg.telopt == TELNET_TELOPT_ECHO) + tn5250_client->echo_enabled = 1; /* Enable local echo, as remote won't echo */ + + /* END OF RECORD are require for TN5250 - stop if remote + * side does not support it. */ + if (event->neg.telopt == TELNET_TELOPT_EOR) { + guac_client_log(client, GUAC_LOG_ERROR, "%s: Unexpected lack of " + " END OF RECORD support on the remote side.", __func__); + guac_client_stop(client); + + } + + if (event->neg.telopt == TELNET_TELOPT_BINARY) { + guac_client_log(client, GUAC_LOG_DEBUG, "%s: End of binary data.", + __func__); + tn5250_client->binary_mode = 0; + } + break; + + /* Local feature enable */ + case TELNET_EV_DO: + if (event->neg.telopt == TELNET_TELOPT_EOR) + guac_client_log(client, GUAC_LOG_DEBUG, + "%s: Local EOR support enabled.", + __func__); + + /* Sending a DO BINARY means the next data we receive from the + * server should be binary data. */ + if (event->neg.telopt == TELNET_TELOPT_BINARY) { + guac_client_log(client, GUAC_LOG_DEBUG, + "%s: Local BINARY support enabled, " + "expecting next data to be binary.", + __func__); + tn5250_client->binary_mode = 1; + } + break; + + /* Terminal type request */ + case TELNET_EV_TTYPE: + guac_client_log(client, GUAC_LOG_DEBUG, + "%s: Received TERMINAL TYPE request from the remote side.", __func__); + if (event->ttype.cmd == TELNET_TTYPE_SEND) { + guac_client_log(client, GUAC_LOG_DEBUG, + "%s: Sending terminal type \"%s\" to remote side.", __func__, settings->terminal_type); + telnet_ttype_is(tn5250_client->tn5250, settings->terminal_type); + } + break; + + /* Environment request */ + case TELNET_EV_ENVIRON: + guac_client_log(client, GUAC_LOG_DEBUG, + "%s: Received environment request from the remote side.", + __func__); + /* Only send USER if entire environment was requested */ + if (event->environ.size == 0) + guac_tn5250_send_user(tn5250, settings->username); + + break; + + /* Generic IAC received */ + case TELNET_EV_IAC: + if (event->iac.cmd == TELNET_EOR) { + guac_client_log(client, GUAC_LOG_TRACE, "%s: Receive END OF RECORD from remote side.", __func__); + break; + } + guac_client_log(client, GUAC_LOG_DEBUG, + "%s: Received generic IAC data: 0x%x", + __func__, event->iac.cmd); + break; + + /* For subnegotiation, we need to break and let the libtelnet internals + * finish handling the request. */ + case TELNET_EV_SUBNEGOTIATION: + guac_client_log(client, GUAC_LOG_TRACE, + "%s: Received subnegotiation, sending on to libtelnet.", + __func__); + break; + + /* Connection warnings */ + case TELNET_EV_WARNING: + guac_client_log(client, GUAC_LOG_WARNING, "%s", event->error.msg); + break; + + /* Connection errors */ + case TELNET_EV_ERROR: + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + "Telnet connection closing with error: %s", event->error.msg); + break; + + /* Ignore other events */ + default: + guac_client_log(client, GUAC_LOG_WARNING, + "%s: Unknown or unsupported telnet command received: 0x%x", + __func__, event->type); + break; + + } + +} + +/** + * Input thread, started by the main tn5250 client thread. This thread + * continuously reads from the terminal's STDIN and transfers all read + * data to the tn5250 connection. + * + * @param data The current guac_client instance. + * @return Always NULL. + */ +static void* __guac_tn5250_input_thread(void* data) { + + guac_client* client = (guac_client*) data; + guac_tn5250_client* tn5250_client = (guac_tn5250_client*) client->data; + + char buffer[8192]; + int bytes_read; + + /* Write all data read */ + while ((bytes_read = guac_terminal_read_stdin(tn5250_client->term, buffer, sizeof(buffer))) > 0) { + telnet_send(tn5250_client->tn5250, buffer, bytes_read); + if (tn5250_client->echo_enabled) + guac_terminal_write(tn5250_client->term, buffer, bytes_read); + } + + return NULL; + +} + +/** + * Connects to the tn5250 server specified within the data associated + * with the given guac_client, which will have been populated by + * guac_client_init. + * + * @return The connected tn5250 instance, if successful, or NULL if the + * connection fails for any reason. + */ +static telnet_t* __guac_tn5250_create_session(guac_client* client) { + + int retval; + + int fd; + struct addrinfo* addresses; + struct addrinfo* current_address; + + char connected_address[1024]; + char connected_port[64]; + + guac_tn5250_client* tn5250_client = (guac_tn5250_client*) client->data; + guac_tn5250_settings* settings = tn5250_client->settings; + + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP + }; + + /* Get socket */ + fd = socket(AF_INET, SOCK_STREAM, 0); + + /* Get addresses connection */ + if ((retval = getaddrinfo(settings->hostname, settings->port, + &hints, &addresses))) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error parsing given address or port: %s", + gai_strerror(retval)); + return NULL; + + } + + /* Attempt connection to each address until success */ + current_address = addresses; + while (current_address != NULL) { + + int retval; + + /* Resolve hostname */ + if ((retval = getnameinfo(current_address->ai_addr, + current_address->ai_addrlen, + connected_address, sizeof(connected_address), + connected_port, sizeof(connected_port), + NI_NUMERICHOST | NI_NUMERICSERV))) + guac_client_log(client, GUAC_LOG_DEBUG, "Unable to resolve host: %s", gai_strerror(retval)); + + /* Connect */ + if (connect(fd, current_address->ai_addr, + current_address->ai_addrlen) == 0) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Successfully connected to " + "host %s, port %s", connected_address, connected_port); + + /* Done if successful connect */ + break; + + } + + /* Otherwise log information regarding bind failure */ + else + guac_client_log(client, GUAC_LOG_DEBUG, "Unable to connect to " + "host %s, port %s: %s", + connected_address, connected_port, strerror(errno)); + + current_address = current_address->ai_next; + + } + + /* If unable to connect to anything, fail */ + if (current_address == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND, + "Unable to connect to any addresses."); + return NULL; + } + + /* Free addrinfo */ + freeaddrinfo(addresses); + + /* Open tn5250 session */ + telnet_t* tn5250 = telnet_init(__telnet_options, __guac_tn5250_event_handler, 0, client); + if (tn5250 == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Telnet client allocation failed."); + return NULL; + } + + /* Save file descriptor */ + tn5250_client->socket_fd = fd; + + return tn5250; + +} + +/** + * Sends a 16-bit value over the given tn5250 connection with the byte order + * required by the tn5250 protocol. + * + * @param tn5250 The tn5250 connection to use. + * @param value The value to send. + */ +/* +static void __guac_tn5250_send_uint16(telnet_t* tn5250, uint16_t value) { + + unsigned char buffer[2]; + buffer[0] = (value >> 8) & 0xFF; + buffer[1] = value & 0xFF; + + telnet_send(tn5250, (char*) buffer, 2); + +} +*/ + +/** + * Sends an 8-bit value over the given tn5250 connection. + * + * @param tn5250 The tn5250 connection to use. + * @param value The value to send. + */ +static void __guac_tn5250_send_uint8(telnet_t* tn5250, uint8_t value) { + telnet_send(tn5250, (char*) (&value), 1); +} + +void guac_tn5250_send_user(telnet_t* tn5250, const char* username) { + + /* IAC SB NEW-ENVIRON IS */ + telnet_begin_sb(tn5250, TELNET_TELOPT_NEW_ENVIRON); + __guac_tn5250_send_uint8(tn5250, TELNET_ENVIRON_IS); + + /* Only send username if defined */ + if (username != NULL) { + + /* VAR "USER" */ + __guac_tn5250_send_uint8(tn5250, TELNET_ENVIRON_VAR); + telnet_send(tn5250, "USER", 4); + + /* VALUE username */ + __guac_tn5250_send_uint8(tn5250, TELNET_ENVIRON_VALUE); + telnet_send(tn5250, username, strlen(username)); + + } + + /* IAC SE */ + telnet_finish_sb(tn5250); + +} + +/** + * Waits for data on the given file descriptor for up to one second. The + * return value is identical to that of select(): 0 on timeout, < 0 on + * error, and > 0 on success. + * + * @param socket_fd The file descriptor to wait for. + * @return A value greater than zero on success, zero on timeout, and + * less than zero on error. + */ +static int __guac_tn5250_wait(int socket_fd) { + + /* Build array of file descriptors */ + struct pollfd fds[] = {{ + .fd = socket_fd, + .events = POLLIN, + .revents = 0, + }}; + + /* Wait for one second */ + return poll(fds, 1, 1000); + +} + +void* guac_tn5250_client_thread(void* data) { + + guac_client* client = (guac_client*) data; + guac_tn5250_client* tn5250_client = (guac_tn5250_client*) client->data; + guac_tn5250_settings* settings = tn5250_client->settings; + + pthread_t input_thread; + char buffer[8192]; + int wait_result; + + /* If Wake-on-LAN is enabled, attempt to wake. */ + if (settings->wol_send_packet) { + guac_client_log(client, GUAC_LOG_DEBUG, "Sending Wake-on-LAN packet, " + "and pausing for %d seconds.", settings->wol_wait_time); + + /* Send the Wake-on-LAN request. */ + if (guac_wol_wake(settings->wol_mac_addr, settings->wol_broadcast_addr, + settings->wol_udp_port)) + return NULL; + + /* If wait time is specified, sleep for that amount of time. */ + if (settings->wol_wait_time > 0) + guac_timestamp_msleep(settings->wol_wait_time * 1000); + } + + /* Set up screen recording, if requested */ + if (settings->recording_path != NULL) { + tn5250_client->recording = guac_recording_create(client, + settings->recording_path, + settings->recording_name, + settings->create_recording_path, + !settings->recording_exclude_output, + !settings->recording_exclude_mouse, + 0, /* Touch events not supported */ + settings->recording_include_keys); + } + + /* Create terminal options with required parameters */ + guac_terminal_options* options = guac_terminal_options_create( + settings->width, settings->height, settings->resolution); + + /* Set optional parameters */ + options->disable_copy = settings->disable_copy; + options->max_scrollback = settings->max_scrollback; + options->font_name = settings->font_name; + options->font_size = settings->font_size; + options->color_scheme = settings->color_scheme; + options->backspace = settings->backspace; + + /* Create terminal */ + tn5250_client->term = guac_terminal_create(client, options); + + /* Free options struct now that it's been used */ + free(options); + + /* Fail if terminal init failed */ + if (tn5250_client->term == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Terminal initialization failed"); + return NULL; + } + + /* Send current values of exposed arguments to owner only */ + guac_client_for_owner(client, guac_tn5250_send_current_argv, + tn5250_client); + + /* Set up typescript, if requested */ + if (settings->typescript_path != NULL) { + guac_terminal_create_typescript(tn5250_client->term, + settings->typescript_path, + settings->typescript_name, + settings->create_typescript_path); + } + + /* Open tn5250 session */ + tn5250_client->tn5250 = __guac_tn5250_create_session(client); + if (tn5250_client->tn5250 == NULL) { + /* Already aborted within __guac_tn5250_create_session() */ + return NULL; + } + + /* Logged in */ + guac_client_log(client, GUAC_LOG_INFO, "Telnet connection successful."); + + /* Allow terminal to render if login success/failure detection is not + * enabled */ + if (settings->login_success_regex == NULL + && settings->login_failure_regex == NULL) + guac_terminal_start(tn5250_client->term); + + /* Start input thread */ + if (pthread_create(&(input_thread), NULL, __guac_tn5250_input_thread, (void*) client)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start input thread"); + return NULL; + } + + /* While data available, write to terminal */ + while ((wait_result = __guac_tn5250_wait(tn5250_client->socket_fd)) >= 0) { + + /* Resume waiting of no data available */ + if (wait_result == 0) + continue; + + int bytes_read = read(tn5250_client->socket_fd, buffer, sizeof(buffer)); + if (bytes_read <= 0) + break; + + telnet_recv(tn5250_client->tn5250, buffer, bytes_read); + + } + + /* Kill client and Wait for input thread to die */ + guac_client_stop(client); + pthread_join(input_thread, NULL); + + guac_client_log(client, GUAC_LOG_INFO, "Telnet connection ended."); + return NULL; + +} + diff --git a/src/protocols/tn5250/tn5250.h b/src/protocols/tn5250/tn5250.h new file mode 100644 index 00000000..804b7313 --- /dev/null +++ b/src/protocols/tn5250/tn5250.h @@ -0,0 +1,95 @@ +/* + * 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_TN5250_H +#define GUAC_TN5250_H + +#include "config.h" +#include "settings.h" +#include "terminal/terminal.h" + +#include +#include + +#include + +/** + * TN5250-specific client data. + */ +typedef struct guac_tn5250_client { + + /** + * Telnet connection settings. + */ + guac_tn5250_settings* settings; + + /** + * The TN5250 client thread. + */ + pthread_t client_thread; + + /** + * The file descriptor of the socket connected to the tn5250 server, + * or -1 if no connection has been established. + */ + int socket_fd; + + /** + * TN5250 connection, used by the telnet client thread. + */ + telnet_t* tn5250; + + /** + * Whether all user input should be automatically echoed to the + * terminal. + */ + int echo_enabled; + + /** + * The terminal which will render all output from the tn5250 client. + */ + guac_terminal* term; + + /** + * The in-progress session recording, or NULL if no recording is in + * progress. + */ + guac_recording* recording; + + /** + * Indicates whether or not the TN5250 client is now in binary mode and + * subsequent data should be binary. + */ + int binary_mode; + +} guac_tn5250_client; + +/** + * Main tn5250 client thread, handling transfer of TN5250 output to STDOUT. + */ +void* guac_tn5250_client_thread(void* data); + +/** + * Sends the given username by setting the remote USER environment variable + * using the telnet NEW-ENVIRON option. + */ +void guac_tn5250_send_user(telnet_t* tn5250, const char* username); + +#endif + diff --git a/src/protocols/tn5250/user.c b/src/protocols/tn5250/user.c new file mode 100644 index 00000000..6aaca289 --- /dev/null +++ b/src/protocols/tn5250/user.c @@ -0,0 +1,122 @@ +/* + * 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 "argv.h" +#include "clipboard.h" +#include "input.h" +#include "pipe.h" +#include "settings.h" +#include "tn5250.h" +#include "terminal/terminal.h" +#include "user.h" + +#include +#include +#include + +#include +#include + +int guac_tn5250_user_join_handler(guac_user* user, int argc, char** argv) { + + guac_client* client = user->client; + guac_tn5250_client* tn5250_client = (guac_tn5250_client*) client->data; + + /* Parse provided arguments */ + guac_tn5250_settings* settings = guac_tn5250_parse_args(user, + argc, (const char**) argv); + + /* Fail if settings cannot be parsed */ + if (settings == NULL) { + guac_user_log(user, GUAC_LOG_INFO, + "Badly formatted client arguments."); + return 1; + } + + /* Store settings at user level */ + user->data = settings; + + /* Connect via tn5250 if owner */ + if (user->owner) { + + /* Store owner's settings at client level */ + tn5250_client->settings = settings; + + /* Start client thread */ + if (pthread_create(&(tn5250_client->client_thread), NULL, + guac_tn5250_client_thread, (void*) client)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Unable to start tn5250 client thread"); + return 1; + } + + } + + /* If not owner, synchronize with current display */ + else { + guac_terminal_dup(tn5250_client->term, user, user->socket); + guac_tn5250_send_current_argv(user, tn5250_client); + guac_socket_flush(user->socket); + } + + /* Only handle events if not read-only */ + if (!settings->read_only) { + + /* General mouse/keyboard events */ + user->key_handler = guac_tn5250_user_key_handler; + user->mouse_handler = guac_tn5250_user_mouse_handler; + + /* Inbound (client to server) clipboard transfer */ + if (!settings->disable_paste) + user->clipboard_handler = guac_tn5250_clipboard_handler; + + /* STDIN redirection */ + user->pipe_handler = guac_tn5250_pipe_handler; + + /* Updates to connection parameters */ + user->argv_handler = guac_argv_handler; + + /* Display size change events */ + user->size_handler = guac_tn5250_user_size_handler; + + } + + return 0; + +} + +int guac_tn5250_user_leave_handler(guac_user* user) { + + guac_tn5250_client* tn5250_client = + (guac_tn5250_client*) user->client->data; + + /* Remove the user from the terminal */ + guac_terminal_remove_user(tn5250_client->term, user); + + /* Free settings if not owner (owner settings will be freed with client) */ + if (!user->owner) { + guac_tn5250_settings* settings = (guac_tn5250_settings*) user->data; + guac_tn5250_settings_free(settings); + } + + return 0; +} + diff --git a/src/protocols/tn5250/user.h b/src/protocols/tn5250/user.h new file mode 100644 index 00000000..d9ec386c --- /dev/null +++ b/src/protocols/tn5250/user.h @@ -0,0 +1,38 @@ +/* + * 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_TN5250_USER_H +#define GUAC_TN5250_USER_H + +#include "config.h" + +#include + +/** + * Handler for joining users. + */ +guac_user_join_handler guac_tn5250_user_join_handler; + +/** + * Handler for leaving users. + */ +guac_user_leave_handler guac_tn5250_user_leave_handler; + +#endif +