GUACAMOLE-860: [WIP] More tn5250 work.

This commit is contained in:
Nick Couchman 2019-08-04 17:08:54 -04:00
parent 30ec3be924
commit 19899756a0
8 changed files with 338 additions and 444 deletions

View File

@ -39,6 +39,7 @@ DIST_SUBDIRS = \
src/protocols/rdp \ src/protocols/rdp \
src/protocols/ssh \ src/protocols/ssh \
src/protocols/telnet \ src/protocols/telnet \
src/protocols/tn5250 \
src/protocols/vnc src/protocols/vnc
SUBDIRS = \ SUBDIRS = \
@ -71,6 +72,7 @@ endif
if ENABLE_TELNET if ENABLE_TELNET
SUBDIRS += src/protocols/telnet SUBDIRS += src/protocols/telnet
SUBDIRS += src/protocols/tn5250
endif endif
if ENABLE_VNC if ENABLE_VNC

View File

@ -1382,6 +1382,7 @@ AC_CONFIG_FILES([Makefile
src/protocols/rdp/tests/Makefile src/protocols/rdp/tests/Makefile
src/protocols/ssh/Makefile src/protocols/ssh/Makefile
src/protocols/telnet/Makefile src/protocols/telnet/Makefile
src/protocols/tn5250/Makefile
src/protocols/vnc/Makefile]) src/protocols/vnc/Makefile])
AC_OUTPUT AC_OUTPUT
@ -1447,6 +1448,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION
RDP ........... ${build_rdp} RDP ........... ${build_rdp}
SSH ........... ${build_ssh} SSH ........... ${build_ssh}
Telnet ........ ${build_telnet} Telnet ........ ${build_telnet}
TN5250 ........ ${build_telnet}
VNC ........... ${build_vnc} VNC ........... ${build_vnc}
Services / tools: Services / tools:

View File

@ -72,30 +72,6 @@ int guac_tn5250_user_key_handler(guac_user* user, int keysym, int pressed) {
if (term == NULL) if (term == NULL)
return 0; 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" */ /* Intercept and handle Pause / Break / Ctrl+0 as "IAC BRK" */
if (pressed && ( if (pressed && (
keysym == 0xFF13 /* Pause */ keysym == 0xFF13 /* Pause */

View File

@ -34,10 +34,7 @@ const char* GUAC_TN5250_CLIENT_ARGS[] = {
"hostname", "hostname",
"port", "port",
"ssl", "ssl",
"username", "enhanced",
"username-regex",
"password",
"password-regex",
"font-name", "font-name",
"font-size", "font-size",
"color-scheme", "color-scheme",
@ -54,8 +51,6 @@ const char* GUAC_TN5250_CLIENT_ARGS[] = {
"backspace", "backspace",
"terminal-type", "terminal-type",
"scrollback", "scrollback",
"login-success-regex",
"login-failure-regex",
"disable-copy", "disable-copy",
"disable-paste", "disable-paste",
NULL NULL
@ -79,26 +74,9 @@ enum TN5250_ARGS_IDX {
IDX_SSL, IDX_SSL,
/** /**
* The name of the user to login as. Optional. * Whether or not to use RFC2877 enhanced TN5250. Optional.
*/ */
IDX_USERNAME, IDX_ENHANCED,
/**
* 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. * The name of the font to use within the terminal.
@ -206,24 +184,6 @@ enum TN5250_ARGS_IDX {
*/ */
IDX_SCROLLBACK, 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", * 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 * it will not be possible to copy data from the terminal to the client
@ -241,54 +201,6 @@ enum TN5250_ARGS_IDX {
TN5250_ARGS_COUNT 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, guac_tn5250_settings* guac_tn5250_parse_args(guac_user* user,
int argc, const char** argv) { int argc, const char** argv) {
@ -307,59 +219,6 @@ guac_tn5250_settings* guac_tn5250_parse_args(guac_user* user,
guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv,
IDX_HOSTNAME, ""); 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 */ /* Read-only mode */
settings->read_only = settings->read_only =
guac_user_parse_args_boolean(user, GUAC_TN5250_CLIENT_ARGS, argv, guac_user_parse_args_boolean(user, GUAC_TN5250_CLIENT_ARGS, argv,
@ -405,6 +264,11 @@ guac_tn5250_settings* guac_tn5250_parse_args(guac_user* user,
guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv,
IDX_PORT, defualt_port); IDX_PORT, defualt_port);
/* Enhanced mode */
settings->enhanced =
guac_user_parse_args_boolean(user, GUAC_TN5250_CLIENT_ARGS, argv,
IDX_ENHANCED, false);
/* Read typescript path */ /* Read typescript path */
settings->typescript_path = settings->typescript_path =
guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv,
@ -456,9 +320,37 @@ guac_tn5250_settings* guac_tn5250_parse_args(guac_user* user,
IDX_BACKSPACE, 127); IDX_BACKSPACE, 127);
/* Read terminal emulator type. */ /* Read terminal emulator type. */
settings->terminal_type = char* terminal_type = guac_user_parse_args_string(user,
guac_user_parse_args_string(user, GUAC_TN5250_CLIENT_ARGS, argv, GUAC_TN5250_CLIENT_ARGS, argv, IDX_TERMINAL_TYPE, "5251-11");
IDX_TERMINAL_TYPE, "linux");
if (strcmp(terminal_type, "3179-2") == 0)
settings->terminal_type = IBM_3179_2;
else if (strcmp(terminal_type, "3180-2") == 0)
settings->terminal_type = IBM_3180_2;
else if (strcmp(terminal_type, "3196-a1") == 0)
settings->terminal_type = IBM_3196_A1;
else if (strcmp(terminal_type, "3477-fc") == 0)
settings->terminal_type = IBM_3477_FC;
else if (strcmp(terminal_type, "3477-fg") == 0)
settings->terminal_type = IBM_3477_FG;
else if (strcmp(terminal_type, "5251-11") == 0)
settings->terminal_type = IBM_5251_11;
else if (strcmp(terminal_type, "5291-1") == 0)
settings->terminal_type = IBM_5291_1;
else if (strcmp(terminal_type, "5292-2") == 0)
settings->terminal_type = IBM_5292_2;
else if (strcmp(terminal_type, "5555-b01") == 0)
settings->terminal_type = IBM_5555_B01;
else if (strcmp(terminal_type, "5555-c01") == 0)
settings->terminal_type = IBM_5555_C01;
else {
guac_user_log(user, GUAC_LOG_WARNING,
"Invalid terminal type %s, defaulting to 5251-11",
terminal_type);
settings->terminal_type = IBM_5251_11;
}
free(terminal_type);
/* Parse clipboard copy disable flag */ /* Parse clipboard copy disable flag */
settings->disable_copy = settings->disable_copy =
@ -481,16 +373,6 @@ void guac_tn5250_settings_free(guac_tn5250_settings* settings) {
free(settings->hostname); free(settings->hostname);
free(settings->port); 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 display preferences */
free(settings->font_name); free(settings->font_name);
free(settings->color_scheme); free(settings->color_scheme);

View File

@ -61,18 +61,6 @@
*/ */
#define GUAC_TN5250_DEFAULT_RECORDING_NAME "recording" #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:"
/** /**
* The default maximum scrollback size in rows. * The default maximum scrollback size in rows.
*/ */
@ -136,12 +124,16 @@ typedef enum guac_tn5250_terminal_types {
} guac_tn5250_terminal_type; } guac_tn5250_terminal_type;
/**
* Data structure to store all of the characteristics of various types of
* 5250-compatible terminals.
*/
typedef struct __guac_tn5250_terminal_params { typedef struct __guac_tn5250_terminal_params {
/** /**
* The type of terminal, as defined in RFC-1205 * The type of terminal, as defined in RFC-1205
*/ */
guac_tn5250_terminal_type terminal; char* terminal;
/** /**
* The number of rows in the terminal * The number of rows in the terminal
@ -160,19 +152,24 @@ typedef struct __guac_tn5250_terminal_params {
} __guac_tn5250_terminal_params; } __guac_tn5250_terminal_params;
/**
* An array of all of the possible terminal types, including the ENUM value,
* the height (in rows) and width (in columns), and whether or not the terminal
* supports color (true if color, false if monochrome).
*/
__guac_tn5250_terminal_params __guac_tn5250_terminals[] = { __guac_tn5250_terminal_params __guac_tn5250_terminals[] = {
{IBM_3179_2, 24, 80, true }, {"IBM_3179_2", 24, 80, true },
{IBM_3180_2, 27, 132, false}, {"IBM_3180_2", 27, 132, false},
{IBM_3196_A1, 24, 80, false}, {"IBM_3196_A1", 24, 80, false},
{IBM_3477_FC, 27, 132, true }, {"IBM_3477_FC", 27, 132, true },
{IBM_3477_FG, 27, 132, false}, {"IBM_3477_FG", 27, 132, false},
{IBM_5251_11, 24, 80, false}, {"IBM_5251_11", 24, 80, false},
{IBM_5291_1, 24, 80, false}, {"IBM_5291_1", 24, 80, false},
{IBM_5292_2, 24, 80, true }, {"IBM_5292_2", 24, 80, true },
{IBM_5555_B01, 24, 80, false}, {"IBM_5555_B01", 24, 80, false},
{IBM_5555_C01, 24, 80, true }, {"IBM_5555_C01", 24, 80, true },
{NULL, -1, -1, false} {NULL, -1, -1, false}
} };
/** /**
* Settings for the TN5250 connection. The values for this structure are parsed * Settings for the TN5250 connection. The values for this structure are parsed
@ -197,46 +194,9 @@ typedef struct guac_tn5250_settings {
bool ssl; bool ssl;
/** /**
* The name of the user to login as, if any. If no username is specified, * Whether or not to use enhanced TN5250 mode (RFC2877)
* this will be NULL.
*/ */
char* username; bool enhanced;
/**
* 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. * Whether this connection is read-only, and user input should be dropped.

174
src/protocols/tn5250/sna.h Normal file
View File

@ -0,0 +1,174 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/*
* File: sna.h
* Author: nick_couchman
*
* Created on August 4, 2019, 1:43 PM
*/
#ifndef GUAC_SNA_H
#define GUAC_SNA_H
/**
* This is the packet header that defines traffic on the TN5250 link as
* SNA traffic. It will be present on traffic coming from the mainframe,
* and should also be sent for any SNA-specific traffic to the mainframe.
*/
#define SNA_PACKET_HEADER 0x12a0
/**
* This is a constant header on all SNA packets.
*/
#define SNA_VAR_HEADER 0x04
/**
* Operational codes as defined in RFC1207.
*
* No operation.
*/
#define OPCODE_NOOP 0x00
/**
* Invite operation, sent by server to allow client to talk.
*/
#define OPCODE_INVITE 0x01
/**
* Output only
*/
#define OPCODE_OUTPUT 0x02
/**
* Put/get operation
*/
#define OPCODE_PUT_GET 0x03
/**
* Save screen
*/
#define OPCODE_SAVE_SCREEN 0x04
/**
* Restore screen
*/
#define OPCODE_RESTORE_SCREEN 0x05
/**
* Read immediate
*/
#define OPCODE_READ_IMMEIDATE 0x06
/**
* Read screen
*/
#define OPCODE_READ_SCREEN 0x08
/**
* Cancel invite
*/
#define OPCODE_CANCEL_INVITE 0x0a
/**
* Message light on
*/
#define OPCODE_MSG_ON 0x0b
/**
* Message light off
*/
#define OPCODE_MSG_OFF 0x0c
/**
* A 16-bit set of flags, as defined in RFC1205, Section 3, for TN5250-specific
* data transfer.
*/
struct sna_flags {
/**
* Indicates data stream output error.
*/
unsigned int ERR : 1;
/**
* 5250 Attention Key has been pressed.
*/
unsigned int ATN : 1;
/**
* Reserved/unused.
*/
unsigned int : 3;
/**
* System Request key was pressed.
*/
unsigned int SRQ : 1;
/**
* Test Request key was pressed.
*/
unsigned int TRQ : 1;
/**
* Help in Error State function - error code will follow header in data.
*/
unsigned int HLP : 1;
/**
* Final 8 bits are reserved/unused.
*/
unsigned int : 8;
} sna_flags;
struct sna_packet {
/**
* The length of the packet.
*/
unsigned int len : 16;
/**
* The SNA header marker, 0x12a0
*/
unsigned int sna_header : 16;
/**
* Reserved portion, should be zeroes.
*/
unsigned int reserved : 16;
/**
* The variable header length, always 0x04.
*/
unsigned int varlen : 8;
/**
* SNA flags.
*/
unsigned int sna_flags : 16;
/**
* The opcode.
*/
unsigned int opcode : 8;
/**
* Any data sent in the packet.
*/
void* sna_data;
/**
* IAC and EOR marker.
*/
unsigned int sna_end : 16;
} sna_packet;
#endif /* SNA_H */

View File

@ -44,11 +44,13 @@
*/ */
static const telnet_telopt_t __telnet_options[] = { static const telnet_telopt_t __telnet_options[] = {
{ TELNET_TELOPT_ECHO, TELNET_WONT, TELNET_DO }, { TELNET_TELOPT_ECHO, TELNET_WONT, TELNET_DO },
{ TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DONT }, { TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DO },
{ TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DO }, { TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DO },
{ TELNET_TELOPT_MSSP, TELNET_WONT, TELNET_DO }, { TELNET_TELOPT_MSSP, TELNET_WONT, TELNET_DO },
{ TELNET_TELOPT_NAWS, TELNET_WILL, TELNET_DONT }, { TELNET_TELOPT_NAWS, TELNET_WONT, TELNET_DONT },
{ TELNET_TELOPT_NEW_ENVIRON, TELNET_WILL, TELNET_DONT }, { TELNET_TELOPT_NEW_ENVIRON, TELNET_WILL, TELNET_DONT },
{ TELNET_TELOPT_EOR, TELNET_WILL, TELNET_DO },
{ TELNET_TELOPT_BINARY, TELNET_WILL, TELNET_DO },
{ -1, 0, 0 } { -1, 0, 0 }
}; };
@ -82,181 +84,6 @@ static int __guac_telnet_write_all(int fd, const char* buffer, int 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 * 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,
@ -272,46 +99,52 @@ static void __guac_tn5250_event_handler(telnet_t* telnet, telnet_event_t* event,
/* Terminal output received */ /* Terminal output received */
case TELNET_EV_DATA: case TELNET_EV_DATA:
guac_terminal_write(tn5250_client->term, event->data.buffer, event->data.size); __guac_tn5250_recv_sna_packet(client, event);
guac_tn5250_search(client, event->data.buffer, event->data.size); // guac_terminal_write(tn5250_client->term, event->data.buffer, event->data.size);
break; break;
/* Data destined for remote end */ /* Data destined for remote end */
case TELNET_EV_SEND: case TELNET_EV_SEND:
if (__guac_tn5250_write_all(tn5250_client->socket_fd, event->data.buffer, event->data.size) if (__guac_tn5250_send_sna_packet(client, event))
!= event->data.size)
guac_client_stop(client); guac_client_stop(client);
break; break;
/* Remote feature enabled */ /* Remote feature enabled */
case TELNET_EV_WILL: case TELNET_EV_WILL:
guac_client_log(client, GUAC_LOG_DEBUG, "Received TELNET_EV_WILL "
"for option %d", event->neg.telopt);
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 */
break; break;
/* Remote feature disabled */ /* Remote feature disabled */
case TELNET_EV_WONT: case TELNET_EV_WONT:
guac_client_log(client, GUAC_LOG_DEBUG, "Received TELNET_EV_WONT "
"for option %d", event->neg.telopt);
if (event->neg.telopt == TELNET_TELOPT_ECHO) if (event->neg.telopt == TELNET_TELOPT_ECHO)
tn5250_client->echo_enabled = 1; /* Enable local echo, as remote won't echo */ tn5250_client->echo_enabled = 1; /* Enable local echo, as remote won't echo */
break; break;
/* Local feature enable */ /* Local feature enable */
case TELNET_EV_DO: case TELNET_EV_DO:
if (event->neg.telopt == TELNET_TELOPT_NAWS) { guac_client_log(client, GUAC_LOG_DEBUG, "Received TELNET_EV_DO "
tn5250_client->naws_enabled = 1; "for option %d", event->neg.telopt);
guac_tn5250_send_naws(telnet, tn5250_client->term->term_width, tn5250_client->term->term_height);
}
break; break;
/* Terminal type request */ /* Terminal type request */
case TELNET_EV_TTYPE: case TELNET_EV_TTYPE:
guac_client_log(client, GUAC_LOG_DEBUG, "Received TELNET_EV_TTYPE "
"for command %d", event->ttype.cmd);
if (event->ttype.cmd == TELNET_TTYPE_SEND) if (event->ttype.cmd == TELNET_TTYPE_SEND)
tn5250_ttype_is(tn5250_client->telnet, settings->terminal_type); tn5250_ttype_is(tn5250_client->telnet, __guac_tn5250_terminals[settings->terminal_type].terminal);
break; break;
/* Environment request */ /* Environment request */
case TELNET_EV_ENVIRON: case TELNET_EV_ENVIRON:
guac_client_log(client, GUAC_LOG_DEBUG, "Received TELNET_EV_ENVIRON "
"for size %d", event->environ.size);
/* 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_tn5250_send_user(telnet, settings->username); guac_tn5250_send_user(telnet, settings->username);
@ -485,6 +318,7 @@ static void __guac_tn5250_send_uint16(telnet_t* telnet, uint16_t value) {
* Sends an 8-bit value over the given telnet connection. * Sends an 8-bit value over the given telnet connection.
* *
* @param telnet The telnet connection to use. * @param telnet The telnet connection to use.
*
* @param value The value to send. * @param value The value to send.
*/ */
static void __guac_tn5250_send_uint8(telnet_t* telnet, uint8_t value) { static void __guac_tn5250_send_uint8(telnet_t* telnet, uint8_t value) {
@ -528,6 +362,7 @@ void guac_tn5250_send_user(telnet_t* telnet, const char* username) {
* error, and > 0 on success. * error, and > 0 on success.
* *
* @param socket_fd The file descriptor to wait for. * @param socket_fd The file descriptor to wait for.
*
* @return A value greater than zero on success, zero on timeout, and * @return A value greater than zero on success, zero on timeout, and
* less than zero on error. * less than zero on error.
*/ */
@ -580,6 +415,10 @@ void* guac_tn5250_client_thread(void* data) {
return NULL; return NULL;
} }
/* Fix terminal width/height */
tn5250_client->term.term_width = __guac_tn5250_terminals[settings->terminal_type].cols;
tn5250_client->term.term_height = __guac_tn5250_terminals[settings->terminal_type].rows;
/* Set up typescript, if requested */ /* Set up typescript, if requested */
if (settings->typescript_path != NULL) { if (settings->typescript_path != NULL) {
guac_terminal_create_typescript(tn5250_client->term, guac_terminal_create_typescript(tn5250_client->term,
@ -598,11 +437,8 @@ void* guac_tn5250_client_thread(void* data) {
/* Logged in */ /* Logged in */
guac_client_log(client, GUAC_LOG_INFO, "TN5250 connection successful."); guac_client_log(client, GUAC_LOG_INFO, "TN5250 connection successful.");
/* Allow terminal to render if login success/failure detection is not /* Allow terminal to render */
* enabled */ guac_terminal_start(tn5250_client->term);
if (settings->login_success_regex == NULL
&& settings->login_failure_regex == NULL)
guac_terminal_start(tn5250_client->term);
/* Start input thread */ /* Start input thread */
if (pthread_create(&(input_thread), NULL, __guac_tn5250_input_thread, (void*) client)) { if (pthread_create(&(input_thread), NULL, __guac_tn5250_input_thread, (void*) client)) {
@ -634,3 +470,13 @@ void* guac_tn5250_client_thread(void* data) {
} }
void __guac_tn5250_send_sna_packet(void* data, tn5250_flags flags,
unsigned char opcode, char* data) {
}
void __guac_tn5250_recv_sna_packet(guac_client* client, telnet_event_t* event) {
}

View File

@ -24,6 +24,7 @@
#include "common/clipboard.h" #include "common/clipboard.h"
#include "common/recording.h" #include "common/recording.h"
#include "settings.h" #include "settings.h"
#include "sna.h"
#include "terminal/terminal.h" #include "terminal/terminal.h"
#include <libtelnet.h> #include <libtelnet.h>
@ -85,22 +86,73 @@ typedef struct guac_tn5250_client {
} guac_tn5250_client; } guac_tn5250_client;
unsigned char tn5250_opcodes[] = {
0x00, /* No operation */
0x01, /* Invite */
0x02, /* Output only */
0x03, /* Put/Get */
0x04, /* Save screen */
0x05, /* Restore screen */
0x06, /* Read immediate */
0x07, /* Reserved */
0x08, /* Read screen */
0x09, /* Reserved */
0x0a, /* Cancel invite */
0x0b, /* Turn on message light */
0x0c, /* Turn off message light */
NULL
}
/** /**
* Main tn5250 client thread, handling transfer of tn5250 output to STDOUT. * Main tn5250 client thread, handling transfer of tn5250 output to STDOUT.
*
* @param data
* The client data associated with this thread.
*/ */
void* guac_tn5250_client_thread(void* data); void* guac_tn5250_client_thread(void* data);
/**
* Send a telnet NAWS message indicating the given terminal window dimensions
* in characters.
*/
void guac_tn5250_send_naws(telnet_t* telnet, uint16_t width, uint16_t height);
/** /**
* Sends the given username by setting the remote USER environment variable * Sends the given username by setting the remote USER environment variable
* using the tn5250 NEW-ENVIRON option. * using the tn5250 NEW-ENVIRON option.
*
* @param telnet
* The telnet connection to send the USER variable to.
*
* @param username
* The username to send.
*/ */
void guac_tn5250_send_user(telnet_t* telnet, const char* username); void guac_tn5250_send_user(telnet_t* telnet, const char* username);
/**
* Sends the given data in TN5250 mode, creating the necessary packet
* structure over the telnet connection to talk to the system.
*
* @param telnet
* The telnet client sending the packet.
*
* @param sna_flags
* Any flags that should be set in the packet.
*
* @param opcode
* Any opcode that should be sent to the mainframe.
*
* @param data
* Data that should be sent to the mainframe.
*/
void __guac_tn5250_send_sna_packet(telnet_t* telnet, sna_flags flags,
unsigned char opcode, char* data);
/**
* Handles a received SNA packet, processing flags and opcodes, and then writing
* any output to the terminal.
*
* @param client
* The Guacamole Client that is receiving the packet.
*
* @param event
* The Telnet event that is being processed.
*/
void __guac_tn5250_recv_sna_packet(guac_client* client, telnet_event_t* event);
#endif #endif