GUACAMOLE-860: Add TN5250 packet processing.

This commit is contained in:
Virtually Nick 2022-07-24 08:11:57 -04:00
parent b9330e5850
commit 0c8f79a6a0
4 changed files with 390 additions and 53 deletions

View File

@ -36,7 +36,8 @@ libguac_client_tn5250_la_SOURCES = \
pipe.c \ pipe.c \
settings.c \ settings.c \
tn5250.c \ tn5250.c \
user.c user.c \
lib5250/lib5250.c
noinst_HEADERS = \ noinst_HEADERS = \
argv.h \ argv.h \
@ -46,7 +47,8 @@ noinst_HEADERS = \
pipe.h \ pipe.h \
settings.h \ settings.h \
tn5250.h \ tn5250.h \
user.h user.h \
lib5250/lib5250.h
libguac_client_tn5250_la_CFLAGS = \ libguac_client_tn5250_la_CFLAGS = \
-Werror -Wall -Iinclude \ -Werror -Wall -Iinclude \

View File

@ -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 <guacamole/client.h>
#include <arpa/inet.h>
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;
}

View File

@ -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 <guacamole/client.h>
#include <stdint.h>
/**
* 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

View File

@ -21,6 +21,7 @@
#include "argv.h" #include "argv.h"
#include "tn5250.h" #include "tn5250.h"
#include "lib5250/lib5250.h"
#include "terminal/terminal.h" #include "terminal/terminal.h"
#include <guacamole/client.h> #include <guacamole/client.h>
@ -55,9 +56,6 @@ static const telnet_telopt_t __telnet_options[] = {
{ TELNET_TELOPT_ECHO, TELNET_WONT, TELNET_DO }, { TELNET_TELOPT_ECHO, TELNET_WONT, TELNET_DO },
{ TELNET_TELOPT_EOR, TELNET_WILL, TELNET_DO }, { TELNET_TELOPT_EOR, TELNET_WILL, TELNET_DO },
{ TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DONT }, { 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 }, { TELNET_TELOPT_NEW_ENVIRON, TELNET_WILL, TELNET_DONT },
{ -1, 0, 0 } { -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 * Write the entire buffer given to the specified file descriptor, retrying
* the write automatically if necessary. This function will return a value * 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. * future writes.
* *
* @param fd The file descriptor to write to. * @param fd
* @param buffer The buffer to write. * The file descriptor to write to.
* @param size The number of bytes from the buffer to write. *
* @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) { 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 * Event handler, as defined by libtelnet. This function is passed to
* telnet_init() and will be called for every event fired by libtelnet, * telnet_init() and will be called for every event fired by libtelnet,
* including feature enable/disable and receipt/transmission of data. * 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) { 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); "%s: Received %d bytes of data from remote side.", __func__, event->data.size);
if (tn5250_client->binary_mode) { if (tn5250_client->binary_mode) {
guac_client_log(client, GUAC_LOG_TRACE, 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__); __func__);
if (guac_lib5250_process_packet(event->data.buffer, client)) {
return;
}
} }
else { 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_terminal_write(tn5250_client->term, event->data.buffer, event->data.size);
guac_tn5250_search(client, 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: case TELNET_EV_WILL:
if (event->neg.telopt == TELNET_TELOPT_ECHO) if (event->neg.telopt == TELNET_TELOPT_ECHO)
tn5250_client->echo_enabled = 0; /* Disable local echo, as remote will echo */ tn5250_client->echo_enabled = 0; /* Disable local echo, as remote will echo */
if (event->neg.telopt == TELNET_TELOPT_EOR) { if (event->neg.telopt == TELNET_TELOPT_EOR) {
guac_client_log(client, GUAC_LOG_DEBUG, guac_client_log(client, GUAC_LOG_DEBUG,
"%s: Received End of Record support from remote side.", "%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) { if (event->ttype.cmd == TELNET_TTYPE_SEND) {
guac_client_log(client, GUAC_LOG_DEBUG, guac_client_log(client, GUAC_LOG_DEBUG,
"%s: Sending terminal type \"%s\" to remote side.", __func__, settings->terminal_type); "%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); telnet_ttype_is(tn5250_client->tn5250, settings->terminal_type);
} }
break; 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.", "%s: Received environment request from the remote side.",
__func__); __func__);
/* Only send USER if entire environment was requested */ /* 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); guac_tn5250_send_user(tn5250, settings->username);
}
break; 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 * continuously reads from the terminal's STDIN and transfers all read
* data to the tn5250 connection. * data to the tn5250 connection.
* *
* @param data The current guac_client instance. * @param data
* @return Always NULL. * The current guac_client instance.
*
* @return
* Always NULL.
*/ */
static void* __guac_tn5250_input_thread(void* data) { static void* __guac_tn5250_input_thread(void* data) {
@ -454,7 +489,8 @@ static void* __guac_tn5250_input_thread(void* data) {
* with the given guac_client, which will have been populated by * with the given guac_client, which will have been populated by
* guac_client_init. * guac_client_init.
* *
* @return The connected tn5250 instance, if successful, or NULL if the * @return
* The connected tn5250 instance, if successful, or NULL if the
* connection fails for any reason. * connection fails for any reason.
*/ */
static telnet_t* __guac_tn5250_create_session(guac_client* client) { 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 * Sends a 16-bit value over the given tn5250 connection with the byte order
* required by the tn5250 protocol. * required by the tn5250 protocol.
* *
* @param tn5250 The tn5250 connection to use. * @param tn5250
* @param value The value to send. * The tn5250 connection to use.
*
* @param value
* The value to send.
*/ */
/* /*
static void __guac_tn5250_send_uint16(telnet_t* tn5250, uint16_t value) { 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. * Sends an 8-bit value over the given tn5250 connection.
* *
* @param tn5250 The tn5250 connection to use. * @param tn5250
* @param value The value to send. * The tn5250 connection to use.
*
* @param value
* The value to send.
*/ */
/*
static void __guac_tn5250_send_uint8(telnet_t* tn5250, uint8_t value) { static void __guac_tn5250_send_uint8(telnet_t* tn5250, uint8_t value) {
telnet_send(tn5250, (char*) (&value), 1); telnet_send(tn5250, (char*) (&value), 1);
} }
*/
void guac_tn5250_send_user(telnet_t* tn5250, const char* username) { void guac_tn5250_send_user(telnet_t* tn5250, const char* username) {
/* IAC SB NEW-ENVIRON IS */ /* IAC SB NEW-ENVIRON IS */
telnet_begin_sb(tn5250, TELNET_TELOPT_NEW_ENVIRON); telnet_begin_newenviron(tn5250, TELNET_ENVIRON_IS);
__guac_tn5250_send_uint8(tn5250, TELNET_ENVIRON_IS);
/* Only send username if defined */ /* Only send username if defined */
if (username != NULL) { if (username != NULL) {
/* VAR "USER" */ telnet_newenviron_value(tn5250, TELNET_ENVIRON_VAR, "USER");
__guac_tn5250_send_uint8(tn5250, TELNET_ENVIRON_VAR); telnet_newenviron_value(tn5250, TELNET_ENVIRON_VALUE, username);
telnet_send(tn5250, "USER", 4);
/* VALUE username */
__guac_tn5250_send_uint8(tn5250, TELNET_ENVIRON_VALUE);
telnet_send(tn5250, username, strlen(username));
} }
/* IAC SE */ /* IAC SB */
telnet_finish_sb(tn5250); telnet_finish_sb(tn5250);
} }
@ -635,21 +673,6 @@ void* guac_tn5250_client_thread(void* data) {
char buffer[8192]; char buffer[8192];
int wait_result; 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 */ /* Set up screen recording, if requested */
if (settings->recording_path != NULL) { if (settings->recording_path != NULL) {
tn5250_client->recording = guac_recording_create(client, tn5250_client->recording = guac_recording_create(client,