GUACAMOLE-622: Start terminal for telnet only after login status is known (if login success/failure detection enabled).

This commit is contained in:
Michael Jumper 2018-09-02 14:32:05 -07:00
parent 1178b475da
commit 442b1d5cc2
3 changed files with 178 additions and 35 deletions

View File

@ -53,6 +53,8 @@ const char* GUAC_TELNET_CLIENT_ARGS[] = {
"backspace", "backspace",
"terminal-type", "terminal-type",
"scrollback", "scrollback",
"login-success-regex",
"login-failure-regex",
NULL NULL
}; };
@ -196,12 +198,31 @@ enum TELNET_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,
TELNET_ARGS_COUNT TELNET_ARGS_COUNT
}; };
/** /**
* Compiles the given regular expression, returning NULL if compilation fails. * Compiles the given regular expression, returning NULL if compilation fails
* The returned regex_t must be freed with regfree() AND free(). * or of the given regular expression is NULL. The returned regex_t must be
* freed with regfree() AND free(), or with guac_telnet_regex_free().
* *
* @param user * @param user
* The user who provided the setting associated with the given regex * The user who provided the setting associated with the given regex
@ -211,10 +232,15 @@ enum TELNET_ARGS_IDX {
* The regular expression pattern to compile. * The regular expression pattern to compile.
* *
* @return * @return
* The compiled regular expression, or NULL if compilation fails. * The compiled regular expression, or NULL if compilation fails or NULL
* was originally provided for the pattern.
*/ */
static regex_t* guac_telnet_compile_regex(guac_user* user, char* pattern) { static regex_t* guac_telnet_compile_regex(guac_user* user, char* pattern) {
/* Nothing to compile if no pattern provided */
if (pattern == NULL)
return NULL;
int compile_result; int compile_result;
regex_t* regex = malloc(sizeof(regex_t)); regex_t* regex = malloc(sizeof(regex_t));
@ -233,6 +259,14 @@ static regex_t* guac_telnet_compile_regex(guac_user* user, char* pattern) {
return regex; return regex;
} }
void guac_telnet_regex_free(regex_t** regex) {
if (*regex != NULL) {
regfree(*regex);
free(*regex);
*regex = NULL;
}
}
guac_telnet_settings* guac_telnet_parse_args(guac_user* user, guac_telnet_settings* guac_telnet_parse_args(guac_user* user,
int argc, const char** argv) { int argc, const char** argv) {
@ -256,7 +290,7 @@ guac_telnet_settings* guac_telnet_parse_args(guac_user* user,
guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv,
IDX_USERNAME, NULL); IDX_USERNAME, NULL);
/* Read username regex only if password is specified */ /* Read username regex only if username is specified */
if (settings->username != NULL) { if (settings->username != NULL) {
settings->username_regex = guac_telnet_compile_regex(user, settings->username_regex = guac_telnet_compile_regex(user,
guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv,
@ -275,6 +309,35 @@ guac_telnet_settings* guac_telnet_parse_args(guac_user* user,
IDX_PASSWORD_REGEX, GUAC_TELNET_DEFAULT_PASSWORD_REGEX)); IDX_PASSWORD_REGEX, GUAC_TELNET_DEFAULT_PASSWORD_REGEX));
} }
/* Read optional login success detection regex */
settings->login_success_regex = guac_telnet_compile_regex(user,
guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv,
IDX_LOGIN_SUCCESS_REGEX, NULL));
/* Read optional login failure detection regex */
settings->login_failure_regex = guac_telnet_compile_regex(user,
guac_user_parse_args_string(user, GUAC_TELNET_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_telnet_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_TELNET_CLIENT_ARGS[IDX_LOGIN_SUCCESS_REGEX],
GUAC_TELNET_CLIENT_ARGS[IDX_LOGIN_FAILURE_REGEX]);
}
else if (settings->login_failure_regex != NULL
&& settings->login_success_regex == NULL) {
guac_telnet_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_TELNET_CLIENT_ARGS[IDX_LOGIN_FAILURE_REGEX],
GUAC_TELNET_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_TELNET_CLIENT_ARGS, argv, guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv,
@ -380,17 +443,11 @@ void guac_telnet_settings_free(guac_telnet_settings* settings) {
free(settings->username); free(settings->username);
free(settings->password); free(settings->password);
/* Free username regex (if allocated) */ /* Free various regexes */
if (settings->username_regex != NULL) { guac_telnet_regex_free(&settings->username_regex);
regfree(settings->username_regex); guac_telnet_regex_free(&settings->password_regex);
free(settings->username_regex); guac_telnet_regex_free(&settings->login_success_regex);
} guac_telnet_regex_free(&settings->login_failure_regex);
/* Free password regex (if allocated) */
if (settings->password_regex != NULL) {
regfree(settings->password_regex);
free(settings->password_regex);
}
/* Free display preferences */ /* Free display preferences */
free(settings->font_name); free(settings->font_name);

View File

@ -117,6 +117,20 @@ typedef struct guac_telnet_settings {
*/ */
regex_t* 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.
*/ */
@ -253,6 +267,16 @@ typedef struct guac_telnet_settings {
guac_telnet_settings* guac_telnet_parse_args(guac_user* user, guac_telnet_settings* guac_telnet_parse_args(guac_user* user,
int argc, const char** argv); 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_telnet_regex_free(regex_t** regex);
/** /**
* Frees the given guac_telnet_settings object, having been previously * Frees the given guac_telnet_settings object, having been previously
* allocated via guac_telnet_parse_args(). * allocated via guac_telnet_parse_args().

View File

@ -31,6 +31,7 @@
#include <netinet/in.h> #include <netinet/in.h>
#include <poll.h> #include <poll.h>
#include <pthread.h> #include <pthread.h>
#include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/socket.h> #include <sys/socket.h>
@ -84,10 +85,32 @@ static int __guac_telnet_write_all(int fd, const char* buffer, int size) {
/** /**
* Searches for a line matching the stored password regex, appending the given * Searches for a line matching the stored password regex, appending the given
* buffer to the internal pattern matching buffer. The internal pattern match * buffer to the internal pattern matching buffer. The internal pattern match
* buffer is cleared whenever a newline is read. Returns TRUE if a match is found and the * buffer is cleared whenever a newline is read. Returns true if a match is
* value is sent. * found and the value is sent. An enter keypress is automatically sent after
* the value is sent.
*
* @param client
* The guac_client associated with the telnet session.
*
* @param regex
* The regex to search for within the output of the telnet session
* associated with the given client.
*
* @param value
* The string value to send once a match is found, or NULL if no value
* should be sent.
*
* @param buffer
* The buffer of received data to search through.
*
* @param size
* The size of the given buffer, in bytes.
*
* @return
* true if a match is found, false otherwise.
*/ */
static bool __guac_telnet_regex_search(guac_client* client, regex_t* regex, char* value, const char* buffer, int size) { static bool __guac_telnet_regex_search(guac_client* client, regex_t* regex,
char* value, const char* buffer, int size) {
static char line_buffer[1024] = {0}; static char line_buffer[1024] = {0};
static int length = 0; static int length = 0;
@ -123,16 +146,17 @@ static bool __guac_telnet_regex_search(guac_client* client, regex_t* regex, char
if (regexec(regex, line_buffer, 0, NULL, 0) == 0) { if (regexec(regex, line_buffer, 0, NULL, 0) == 0) {
/* Send value */ /* Send value */
guac_terminal_send_string(telnet_client->term, value); if (value != NULL) {
guac_terminal_send_key(telnet_client->term, 0xFF0D, 1); guac_terminal_send_string(telnet_client->term, value);
guac_terminal_send_key(telnet_client->term, 0xFF0D, 0); guac_terminal_send_string(telnet_client->term, "\x0D");
}
/* Stop searching for prompt */ /* Stop searching for prompt */
return TRUE; return true;
} }
return FALSE; return false;
} }
/** /**
@ -158,9 +182,7 @@ static void __guac_telnet_event_handler(telnet_t* telnet, telnet_event_t* event,
settings->username_regex, settings->username, settings->username_regex, settings->username,
event->data.buffer, event->data.size)) { event->data.buffer, event->data.size)) {
guac_client_log(client, GUAC_LOG_DEBUG, "Username sent"); guac_client_log(client, GUAC_LOG_DEBUG, "Username sent");
regfree(settings->username_regex); guac_telnet_regex_free(&settings->username_regex);
free(settings->username_regex);
settings->username_regex = NULL;
} }
} }
@ -172,18 +194,52 @@ static void __guac_telnet_event_handler(telnet_t* telnet, telnet_event_t* event,
guac_client_log(client, GUAC_LOG_DEBUG, "Password sent"); guac_client_log(client, GUAC_LOG_DEBUG, "Password sent");
/* Do not continue searching for username once password is sent */ /* Do not continue searching for username/password once password is sent */
if (settings->username_regex != NULL) { guac_telnet_regex_free(&settings->username_regex);
regfree(settings->username_regex); guac_telnet_regex_free(&settings->password_regex);
free(settings->username_regex);
settings->username_regex = NULL;
}
regfree(settings->password_regex);
free(settings->password_regex);
settings->password_regex = NULL;
} }
} }
/* Continue search for login success */
if (settings->login_success_regex != NULL) {
if (__guac_telnet_regex_search(client,
settings->login_success_regex, NULL,
event->data.buffer, event->data.size)) {
/* Allow terminal to render now that login has been deemed successful */
guac_client_log(client, GUAC_LOG_DEBUG, "Login successful");
guac_terminal_start(telnet_client->term);
/* Stop all searches */
guac_telnet_regex_free(&settings->username_regex);
guac_telnet_regex_free(&settings->password_regex);
guac_telnet_regex_free(&settings->login_success_regex);
guac_telnet_regex_free(&settings->login_failure_regex);
}
}
/* Continue search for login failure */
if (settings->login_failure_regex != NULL) {
if (__guac_telnet_regex_search(client,
settings->login_failure_regex, NULL,
event->data.buffer, event->data.size)) {
/* 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_telnet_regex_free(&settings->username_regex);
guac_telnet_regex_free(&settings->password_regex);
guac_telnet_regex_free(&settings->login_success_regex);
guac_telnet_regex_free(&settings->login_failure_regex);
}
}
break; break;
/* Data destined for remote end */ /* Data destined for remote end */
@ -508,6 +564,12 @@ void* guac_telnet_client_thread(void* data) {
/* Logged in */ /* Logged in */
guac_client_log(client, GUAC_LOG_INFO, "Telnet connection successful."); 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(telnet_client->term);
/* Start input thread */ /* Start input thread */
if (pthread_create(&(input_thread), NULL, __guac_telnet_input_thread, (void*) client)) { if (pthread_create(&(input_thread), NULL, __guac_telnet_input_thread, (void*) client)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start input thread"); guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start input thread");