From 0c8f79a6a094d3628312e031166dcc12cbe17017 Mon Sep 17 00:00:00 2001 From: Virtually Nick Date: Sun, 24 Jul 2022 08:11:57 -0400 Subject: [PATCH] GUACAMOLE-860: Add TN5250 packet processing. --- src/protocols/tn5250/Makefile.am | 24 +-- src/protocols/tn5250/lib5250/lib5250.c | 105 +++++++++++++ src/protocols/tn5250/lib5250/lib5250.h | 207 +++++++++++++++++++++++++ src/protocols/tn5250/tn5250.c | 107 ++++++++----- 4 files changed, 390 insertions(+), 53 deletions(-) create mode 100644 src/protocols/tn5250/lib5250/lib5250.c create mode 100644 src/protocols/tn5250/lib5250/lib5250.h diff --git a/src/protocols/tn5250/Makefile.am b/src/protocols/tn5250/Makefile.am index a4550634..d9c3ad34 100644 --- a/src/protocols/tn5250/Makefile.am +++ b/src/protocols/tn5250/Makefile.am @@ -35,18 +35,20 @@ libguac_client_tn5250_la_SOURCES = \ input.c \ pipe.c \ settings.c \ - tn5250.c \ - user.c + tn5250.c \ + user.c \ + lib5250/lib5250.c -noinst_HEADERS = \ - argv.h \ - client.h \ - clipboard.h \ - input.h \ - pipe.h \ - settings.h \ - tn5250.h \ - user.h +noinst_HEADERS = \ + argv.h \ + client.h \ + clipboard.h \ + input.h \ + pipe.h \ + settings.h \ + tn5250.h \ + user.h \ + lib5250/lib5250.h libguac_client_tn5250_la_CFLAGS = \ -Werror -Wall -Iinclude \ diff --git a/src/protocols/tn5250/lib5250/lib5250.c b/src/protocols/tn5250/lib5250/lib5250.c new file mode 100644 index 00000000..9ba5457f --- /dev/null +++ b/src/protocols/tn5250/lib5250/lib5250.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 "lib5250.h" +#include "tn5250.h" + +#include + +#include + +int guac_lib5250_process_packet(const char* data, guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "%s: Processing expected TN5250 SNA data.", __func__); + + guac_lib5250_packet* packet = (guac_lib5250_packet*) data; + + if (ntohs(packet->type) != GUAC_LIB5250_SNA_GDS) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + "%s: Unexpected SNA packet typing parsing TN5250 packet: 0x%x", + __func__, ntohs(packet->type)); + return 1; + } + + switch (packet->opcode) { + + case GUAC_LIB5250_OPCODE_NOOP: + guac_client_log(client, GUAC_LOG_TRACE, "%s: Received opcode no-op.", __func__); + break; + + case GUAC_LIB5250_OPCODE_INVITE: + guac_client_log(client, GUAC_LOG_TRACE, "%s: Received opcode invite.", __func__); + break; + + case GUAC_LIB5250_OPCODE_OUTPUT: + guac_client_log(client, GUAC_LOG_TRACE, "%s: Received opcode output.", __func__); + break; + + case GUAC_LIB5250_OPCODE_PUTGET: + guac_client_log(client, GUAC_LOG_TRACE, "%s: Received opcode put/get.", __func__); + break; + + case GUAC_LIB5250_OPCODE_SAVESCREEN: + guac_client_log(client, GUAC_LOG_TRACE, "%s: Received opcode save screen.", __func__); + break; + + case GUAC_LIB5250_OPCODE_RESTORESCREEN: + guac_client_log(client, GUAC_LOG_TRACE, "%s: Received opcode restore screen.", __func__); + break; + + case GUAC_LIB5250_OPCODE_READIMMEDIATE: + guac_client_log(client, GUAC_LOG_TRACE, "%s: Received opcode read immediate.", __func__); + break; + + case GUAC_LIB5250_OPCODE_READSCREEN: + guac_client_log(client, GUAC_LOG_TRACE, "%s: Received opcode read screen.", __func__); + break; + + case GUAC_LIB5250_OPCODE_CANCELINVITE: + guac_client_log(client, GUAC_LOG_TRACE, "%s: Received opcode cancel invite.", __func__); + break; + + case GUAC_LIB5250_OPCODE_MSGON: + guac_client_log(client, GUAC_LOG_TRACE, "%s: Received opcode msg on.", __func__); + break; + + case GUAC_LIB5250_OPCODE_MSGOFF: + guac_client_log(client, GUAC_LOG_TRACE, "%s: Received opcode msg off.", __func__); + break; + + default: + guac_client_log(client, GUAC_LOG_TRACE, "%s: Received unknown opcode.", __func__); + + } + + guac_client_log(client, GUAC_LOG_TRACE, "%s: SNA data length: %d bytes.", __func__, ntohs(packet->len)); + guac_client_log(client, GUAC_LOG_TRACE, "%s: Packet type: 0x%x", __func__, ntohs(packet->type)); + guac_client_log(client, GUAC_LOG_TRACE, "%s: Reserved space: 0x%x", __func__, ntohs(packet->reserved)); + guac_client_log(client, GUAC_LOG_TRACE, "%s: Variable header length: 0x%x.", __func__, packet->varlen); + guac_client_log(client, GUAC_LOG_TRACE, "%s: SNA Flags: 0x%x", __func__, packet->flags); + guac_client_log(client, GUAC_LOG_TRACE, "%s: SNA Rerved flags: 0x%x", __func__, packet->reserved_flags); + guac_client_log(client, GUAC_LOG_TRACE, "%s: SNA Opcode: 0x%x", __func__, packet->opcode); + + guac_client_log(client, GUAC_LOG_TRACE, "%s: WHOLE ENCHILADA: 0x%x", __func__, packet); + + guac_client_log(client, GUAC_LOG_WARNING, "%s: Everything else is unimplemented, now.", __func__); + return 0; + +} \ No newline at end of file diff --git a/src/protocols/tn5250/lib5250/lib5250.h b/src/protocols/tn5250/lib5250/lib5250.h new file mode 100644 index 00000000..d0b0ab8d --- /dev/null +++ b/src/protocols/tn5250/lib5250/lib5250.h @@ -0,0 +1,207 @@ +/* + * 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_LIB5250_H +#define GUAC_LIB5250_H + +#include "config.h" +#include "tn5250.h" + +#include + +#include + +/** + * The General Data Stream SNA packet type. + */ +#define GUAC_LIB5250_SNA_GDS 0x12a0 + +/** + * An enum that represents all of the available terminal types according to + * RFC1205, the specification that documents the Telnet 5250 (TN5250) + * standard. + */ +typedef enum guac_lib5250_terminal { + + /* 24 x 80 Double-Byte Character Set color display */ + GUAC_LIB5250_TERMINAL_IBM_5555_C01, + + /* 24 x 80 Double-Byte Character Set (DBCS) */ + GUAC_LIB5250_TERMINAL_IBM_5555_B01, + + /* 27 x 132 color display */ + GUAC_LIB5250_TERMINAL_IBM_3477_FC, + + /* 27 x 132 monochrome display */ + GUAC_LIB5250_TERMINAL_IBM_3477_FG, + + /* 27 x 132 monochrome display */ + GUAC_LIB5250_TERMINAL_IBM_3180_2, + + /* 24 x 80 color display */ + GUAC_LIB5250_TERMINAL_IBM_3179_2, + + /* 24 x 80 monochrome display */ + GUAC_LIB5250_TERMINAL_IBM_3196_A1, + + /* 24 x 80 color display */ + GUAC_LIB5250_TERMINAL_IBM_5292_2, + + /* 24 x 80 monochrome display */ + GUAC_LIB5250_TERMINAL_IBM_5291_1, + + /* 24 x 80 monochrome display */ + GUAC_LIB5250_TERMINAL_IBM_5251_11 + +} guac_lib5250_terminal; + +/** + * A data type that captures a terminal type, from the guac_lib5250_terminal + * enum, and the capabilities associated with that terminal. + */ +typedef struct guac_lib5250_terminal_cap { + + /* The name of the terminal. */ + guac_lib5250_terminal name; + + /* The number of rows supported on the terminal. */ + unsigned int rows; + + /* The number of columns supports on the terminal. */ + unsigned int cols; + + /* 1 if the terminal supports colors, 0 if it is monochrome. */ + unsigned int color; + + /* The number of bytes-per-character the terminal supports. This must + * be either 1 or 2. */ + unsigned int charbytes; + +} guac_lib5250_terminal_cap; + +/** + * A list of the possible TN5250 opcodes, which can be sent to the client to + * direct it to do something with the terminal. These are documented in RFC1205. + */ +typedef enum guac_lib5250_opcode { + + /* No Operation */ + GUAC_LIB5250_OPCODE_NOOP = 0x00, + + /* Invite terminal */ + GUAC_LIB5250_OPCODE_INVITE = 0x01, + + /* Output only */ + GUAC_LIB5250_OPCODE_OUTPUT = 0x02, + + /* Put/Get */ + GUAC_LIB5250_OPCODE_PUTGET = 0x03, + + /* Save screen */ + GUAC_LIB5250_OPCODE_SAVESCREEN = 0x04, + + /* Restore screen */ + GUAC_LIB5250_OPCODE_RESTORESCREEN = 0x05, + + /* Read immediate */ + GUAC_LIB5250_OPCODE_READIMMEDIATE = 0x06, + + /* Read screen */ + GUAC_LIB5250_OPCODE_READSCREEN = 0x08, + + /* Cancel invite */ + GUAC_LIB5250_OPCODE_CANCELINVITE = 0x0A, + + /* Message light on */ + GUAC_LIB5250_OPCODE_MSGON = 0x0B, + + /* Message light off */ + GUAC_LIB5250_OPCODE_MSGOFF = 0x0C + +} guac_lib5250_opcode; + +/** + * A data type that defines the structure of the packet for 5250 communication + * across telnet. The structure of this packet is based off of the definition + * in RFC1205. + */ +typedef struct guac_lib5250_packet { + + /* The length of the entire logical record. */ + uint16_t len; + + /* The SNA record type. This should always be 0x12A0, which is the + * General Data Stream record. */ + uint16_t type; + + /* Empty space, should be zerod. */ + uint16_t reserved; + + /* The length of the variable portion of the header, in octets. According + * to the RFC, this should always be 0x04. */ + uint8_t varlen; + + /* Flags associated with the header, as documented in the RFC. The + * bits are as follows: + * Bit 0: ERR - Data stream output error. + * Bit 1: ATN - Indicates Attention key was pressed. + * Bit 2-4: Reserved, zeros. + * Bit 5: SRQ - System Request key has been pressed. + * Bit 6: TRQ - Test Request key has been pressed. + * Bit 7: HLP - Help in Error State, with actual help code sent as data after + * the header. + */ + uint8_t flags; + + /* At present, the remaining 8 bits of flags are not used at all. As old + * as RFC1205 is, I doubt that's likely to change. These bits should be + * all zeroes. + */ + uint8_t reserved_flags; + + /* The operation code, as requested by the sender. Valid values for this + * are in the gauc_lib5250_opcode enum. + */ + uint8_t opcode; + + /* What remains is the data in the packet, which should consume the + * remainder of the space that the header did not take up. The packet + * should also be terminated with a Telnet IAC EOR record, 0xffef. + */ + void* data; + +} guac_lib5250_packet __attribute__ ((aligned)); + +/** + * Parses the packet that libtelnet has received and then takes action + * based on the contents of the packet. + * + * @param data + * The data buffer delivered by libtelnet, which should be binary telnet + * data that is the actual TN5250 protocol. + * + * @param client + * The Guacamole client associated with this connection. + * + * @return + * Zero if the data was successfully processed, non-zero otherwise. + */ +int guac_lib5250_process_packet(const char* data, guac_client* client); + +#endif // GUAC_LIB5250_H \ No newline at end of file diff --git a/src/protocols/tn5250/tn5250.c b/src/protocols/tn5250/tn5250.c index 3bc8829c..f084c131 100644 --- a/src/protocols/tn5250/tn5250.c +++ b/src/protocols/tn5250/tn5250.c @@ -21,6 +21,7 @@ #include "argv.h" #include "tn5250.h" +#include "lib5250/lib5250.h" #include "terminal/terminal.h" #include @@ -55,9 +56,6 @@ static const telnet_telopt_t __telnet_options[] = { { 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 } }; @@ -65,12 +63,21 @@ static const telnet_telopt_t __telnet_options[] = { /** * 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 + * not equal to the buffer's size if 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. + * @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. + * + * @return + * Return a value equal to the buffer's size on success, or a value not + * equal to the buffer's size or a negative value on failure. */ static int __guac_tn5250_write_all(int fd, const char* buffer, int size) { @@ -271,6 +278,15 @@ static void guac_tn5250_search(guac_client* client, const char* buffer, int size * 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. + * + * @param tn5250 + * The telnet_t object associated with the event. + * + * @param event + * The telnet_event_t that has fired. + * + * @param data + * User data provided to the event. */ static void __guac_tn5250_event_handler(telnet_t* tn5250, telnet_event_t* event, void* data) { @@ -286,10 +302,15 @@ static void __guac_tn5250_event_handler(telnet_t* tn5250, telnet_event_t* event, "%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.", + "%s: Receiving binary data, which will not go directly to the terminal.", __func__); + if (guac_lib5250_process_packet(event->data.buffer, client)) { + return; + } } else { + guac_client_log(client, GUAC_LOG_TRACE, + "%s: Writing ASCII output directly to terminal.", __func__); guac_terminal_write(tn5250_client->term, event->data.buffer, event->data.size); guac_tn5250_search(client, event->data.buffer, event->data.size); } @@ -308,6 +329,7 @@ static void __guac_tn5250_event_handler(telnet_t* tn5250, telnet_event_t* event, 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.", @@ -366,6 +388,14 @@ static void __guac_tn5250_event_handler(telnet_t* tn5250, telnet_event_t* event, 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); + + /* Apparently sending the TERMINAL TYPE to an IBMi system over + * telnet requires both sending the TERM environment variable + * and responding to the actual telnet TTYPE inquiry. */ + telnet_begin_newenviron(tn5250, TELNET_ENVIRON_IS); + telnet_newenviron_value(tn5250, TELNET_ENVIRON_VAR, "TERM"); + telnet_newenviron_value(tn5250, TELNET_ENVIRON_VALUE, settings->terminal_type); + telnet_finish_sb(tn5250); telnet_ttype_is(tn5250_client->tn5250, settings->terminal_type); } break; @@ -376,8 +406,10 @@ static void __guac_tn5250_event_handler(telnet_t* tn5250, telnet_event_t* event, "%s: Received environment request from the remote side.", __func__); /* Only send USER if entire environment was requested */ - if (event->environ.size == 0) + if (event->environ.size == 0) { + guac_client_log(client, GUAC_LOG_TRACE, "%s: Sending username to remote server: %s", __func__, settings->username); guac_tn5250_send_user(tn5250, settings->username); + } break; @@ -427,8 +459,11 @@ static void __guac_tn5250_event_handler(telnet_t* tn5250, telnet_event_t* event, * 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. + * @param data + * The current guac_client instance. + * + * @return + * Always NULL. */ static void* __guac_tn5250_input_thread(void* data) { @@ -454,8 +489,9 @@ static void* __guac_tn5250_input_thread(void* data) { * 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. + * @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) { @@ -553,8 +589,11 @@ static telnet_t* __guac_tn5250_create_session(guac_client* client) { * 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. + * @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) { @@ -571,33 +610,32 @@ static void __guac_tn5250_send_uint16(telnet_t* tn5250, uint16_t value) { /** * Sends an 8-bit value over the given tn5250 connection. * - * @param tn5250 The tn5250 connection to use. - * @param value The value to send. + * @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); + telnet_begin_newenviron(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)); + telnet_newenviron_value(tn5250, TELNET_ENVIRON_VAR, "USER"); + telnet_newenviron_value(tn5250, TELNET_ENVIRON_VALUE, username); } - /* IAC SE */ + /* IAC SB */ telnet_finish_sb(tn5250); } @@ -635,21 +673,6 @@ void* guac_tn5250_client_thread(void* data) { 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,