GUACAMOLE-622: Merge withold first terminal frame until connection is verified.
This commit is contained in:
commit
54fda21366
@ -233,6 +233,9 @@ void* ssh_client_thread(void* data) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure connection is kept alive during lengthy connects */
|
||||||
|
guac_socket_require_keep_alive(client->socket);
|
||||||
|
|
||||||
/* Open SSH session */
|
/* Open SSH session */
|
||||||
ssh_client->session = guac_common_ssh_create_session(client,
|
ssh_client->session = guac_common_ssh_create_session(client,
|
||||||
settings->hostname, settings->port, ssh_client->user, settings->server_alive_interval,
|
settings->hostname, settings->port, ssh_client->user, settings->server_alive_interval,
|
||||||
@ -335,6 +338,7 @@ void* ssh_client_thread(void* data) {
|
|||||||
|
|
||||||
/* Logged in */
|
/* Logged in */
|
||||||
guac_client_log(client, GUAC_LOG_INFO, "SSH connection successful.");
|
guac_client_log(client, GUAC_LOG_INFO, "SSH connection successful.");
|
||||||
|
guac_terminal_start(ssh_client->term);
|
||||||
|
|
||||||
/* Start input thread */
|
/* Start input thread */
|
||||||
if (pthread_create(&(input_thread), NULL, ssh_input_thread, (void*) client)) {
|
if (pthread_create(&(input_thread), NULL, ssh_input_thread, (void*) client)) {
|
||||||
|
@ -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);
|
||||||
|
@ -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().
|
||||||
|
@ -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>
|
||||||
@ -82,57 +83,178 @@ 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
|
* Matches the given line against the given regex, returning true and sending
|
||||||
* buffer to the internal pattern matching buffer. The internal pattern match
|
* the given value if a match is found. An enter keypress is automatically
|
||||||
* buffer is cleared whenever a newline is read. Returns TRUE if a match is found and the
|
* sent after the value is sent.
|
||||||
* value is sent.
|
*
|
||||||
|
* @param client
|
||||||
|
* The guac_client associated with the telnet session.
|
||||||
|
*
|
||||||
|
* @param regex
|
||||||
|
* The regex to search for within the given line buffer.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The string value to send through STDIN of the telnet 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_telnet_regex_search(guac_client* client, regex_t* regex, char* value, const char* buffer, int size) {
|
static bool guac_telnet_regex_exec(guac_client* client, regex_t* regex,
|
||||||
|
const char* value, const char* line_buffer) {
|
||||||
static char line_buffer[1024] = {0};
|
|
||||||
static int length = 0;
|
|
||||||
|
|
||||||
guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
|
guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
|
||||||
|
|
||||||
int i;
|
|
||||||
const char* current;
|
|
||||||
|
|
||||||
/* Ensure line buffer contains only the most recent line */
|
|
||||||
current = buffer;
|
|
||||||
for (i = 0; i < size; i++) {
|
|
||||||
|
|
||||||
/* Reset line buffer and shift input buffer for each newline */
|
|
||||||
if (*(current++) == '\n') {
|
|
||||||
length = 0;
|
|
||||||
buffer += i;
|
|
||||||
size -= i;
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Truncate if necessary */
|
|
||||||
if (size + length + 1 > sizeof(line_buffer))
|
|
||||||
size = sizeof(line_buffer) - length - 1;
|
|
||||||
|
|
||||||
/* Append to line */
|
|
||||||
memcpy(&(line_buffer[length]), buffer, size);
|
|
||||||
length += size;
|
|
||||||
line_buffer[length] = '\0';
|
|
||||||
|
|
||||||
/* Send value upon match */
|
/* Send value upon match */
|
||||||
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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 telnet session.
|
||||||
|
*
|
||||||
|
* @param line_buffer
|
||||||
|
* The line of character data to test.
|
||||||
|
*/
|
||||||
|
static void guac_telnet_search_line(guac_client* client, const char* line_buffer) {
|
||||||
|
|
||||||
|
guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
|
||||||
|
guac_telnet_settings* settings = telnet_client->settings;
|
||||||
|
|
||||||
|
/* Continue search for username prompt */
|
||||||
|
if (settings->username_regex != NULL) {
|
||||||
|
if (guac_telnet_regex_exec(client, settings->username_regex,
|
||||||
|
settings->username, line_buffer)) {
|
||||||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Username sent");
|
||||||
|
guac_telnet_regex_free(&settings->username_regex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Continue search for password prompt */
|
||||||
|
if (settings->password_regex != NULL) {
|
||||||
|
if (guac_telnet_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_telnet_regex_free(&settings->username_regex);
|
||||||
|
guac_telnet_regex_free(&settings->password_regex);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Continue search for login success */
|
||||||
|
if (settings->login_success_regex != NULL) {
|
||||||
|
if (guac_telnet_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(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_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_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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 telnet session.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
* The buffer of received data to search through.
|
||||||
|
*
|
||||||
|
* @param size
|
||||||
|
* The size of the given buffer, in bytes.
|
||||||
|
*/
|
||||||
|
static void guac_telnet_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_telnet_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_telnet_search_line(client, line_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -151,39 +273,7 @@ static void __guac_telnet_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(telnet_client->term, event->data.buffer, event->data.size);
|
guac_terminal_write(telnet_client->term, event->data.buffer, event->data.size);
|
||||||
|
guac_telnet_search(client, event->data.buffer, event->data.size);
|
||||||
/* Continue search for username prompt */
|
|
||||||
if (settings->username_regex != NULL) {
|
|
||||||
if (__guac_telnet_regex_search(client,
|
|
||||||
settings->username_regex, settings->username,
|
|
||||||
event->data.buffer, event->data.size)) {
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Username sent");
|
|
||||||
regfree(settings->username_regex);
|
|
||||||
free(settings->username_regex);
|
|
||||||
settings->username_regex = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Continue search for password prompt */
|
|
||||||
if (settings->password_regex != NULL) {
|
|
||||||
if (__guac_telnet_regex_search(client,
|
|
||||||
settings->password_regex, settings->password,
|
|
||||||
event->data.buffer, event->data.size)) {
|
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Password sent");
|
|
||||||
|
|
||||||
/* Do not continue searching for username once password is sent */
|
|
||||||
if (settings->username_regex != NULL) {
|
|
||||||
regfree(settings->username_regex);
|
|
||||||
free(settings->username_regex);
|
|
||||||
settings->username_regex = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
regfree(settings->password_regex);
|
|
||||||
free(settings->password_regex);
|
|
||||||
settings->password_regex = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Data destined for remote end */
|
/* Data destined for remote end */
|
||||||
@ -508,6 +598,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");
|
||||||
|
@ -103,6 +103,22 @@ static int guac_terminal_input_stream_end_handler(guac_user* user,
|
|||||||
static int __guac_terminal_send_stream(guac_terminal* term, guac_user* user,
|
static int __guac_terminal_send_stream(guac_terminal* term, guac_user* user,
|
||||||
guac_stream* stream) {
|
guac_stream* stream) {
|
||||||
|
|
||||||
|
/* Deny redirecting STDIN if terminal is not started */
|
||||||
|
if (!term->started) {
|
||||||
|
|
||||||
|
guac_user_log(user, GUAC_LOG_DEBUG, "Attempt to direct the contents "
|
||||||
|
"of an inbound stream to STDIN denied. The terminal is not "
|
||||||
|
"yet ready for input.");
|
||||||
|
|
||||||
|
guac_protocol_send_ack(user->socket, stream,
|
||||||
|
"Terminal not yet started.",
|
||||||
|
GUAC_PROTOCOL_STATUS_RESOURCE_CONFLICT);
|
||||||
|
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/* If a stream is already being used for STDIN, deny creation of
|
/* If a stream is already being used for STDIN, deny creation of
|
||||||
* further streams */
|
* further streams */
|
||||||
if (term->input_stream != NULL) {
|
if (term->input_stream != NULL) {
|
||||||
|
@ -596,6 +596,7 @@ guac_terminal* guac_terminal_create(guac_client* client,
|
|||||||
available_width = 0;
|
available_width = 0;
|
||||||
|
|
||||||
guac_terminal* term = malloc(sizeof(guac_terminal));
|
guac_terminal* term = malloc(sizeof(guac_terminal));
|
||||||
|
term->started = false;
|
||||||
term->client = client;
|
term->client = client;
|
||||||
term->upload_path_handler = NULL;
|
term->upload_path_handler = NULL;
|
||||||
term->file_download_handler = NULL;
|
term->file_download_handler = NULL;
|
||||||
@ -723,6 +724,11 @@ guac_terminal* guac_terminal_create(guac_client* client,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void guac_terminal_start(guac_terminal* term) {
|
||||||
|
term->started = true;
|
||||||
|
guac_terminal_notify(term);
|
||||||
|
}
|
||||||
|
|
||||||
void guac_terminal_stop(guac_terminal* term) {
|
void guac_terminal_stop(guac_terminal* term) {
|
||||||
|
|
||||||
/* Close input pipe and set fds to invalid */
|
/* Close input pipe and set fds to invalid */
|
||||||
@ -851,11 +857,13 @@ wait_complete:
|
|||||||
|
|
||||||
int guac_terminal_render_frame(guac_terminal* terminal) {
|
int guac_terminal_render_frame(guac_terminal* terminal) {
|
||||||
|
|
||||||
|
guac_client* client = terminal->client;
|
||||||
|
|
||||||
int wait_result;
|
int wait_result;
|
||||||
|
|
||||||
/* Wait for data to be available */
|
/* Wait for data to be available */
|
||||||
wait_result = guac_terminal_wait(terminal, 1000);
|
wait_result = guac_terminal_wait(terminal, 1000);
|
||||||
if (wait_result) {
|
if (wait_result || !terminal->started) {
|
||||||
|
|
||||||
guac_timestamp frame_start = guac_timestamp_current();
|
guac_timestamp frame_start = guac_timestamp_current();
|
||||||
|
|
||||||
@ -867,13 +875,14 @@ int guac_terminal_render_frame(guac_terminal* terminal) {
|
|||||||
- frame_end;
|
- frame_end;
|
||||||
|
|
||||||
/* Wait again if frame remaining */
|
/* Wait again if frame remaining */
|
||||||
if (frame_remaining > 0)
|
if (frame_remaining > 0 || !terminal->started)
|
||||||
wait_result = guac_terminal_wait(terminal,
|
wait_result = guac_terminal_wait(terminal,
|
||||||
GUAC_TERMINAL_FRAME_TIMEOUT);
|
GUAC_TERMINAL_FRAME_TIMEOUT);
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
|
|
||||||
} while (wait_result > 0);
|
} while (client->state == GUAC_CLIENT_RUNNING
|
||||||
|
&& (wait_result > 0 || !terminal->started));
|
||||||
|
|
||||||
/* Flush terminal */
|
/* Flush terminal */
|
||||||
guac_terminal_lock(terminal);
|
guac_terminal_lock(terminal);
|
||||||
@ -934,6 +943,9 @@ char* guac_terminal_prompt(guac_terminal* terminal, const char* title,
|
|||||||
int pos;
|
int pos;
|
||||||
char in_byte;
|
char in_byte;
|
||||||
|
|
||||||
|
/* Prompting implicitly requires user input */
|
||||||
|
guac_terminal_start(terminal);
|
||||||
|
|
||||||
/* Print title */
|
/* Print title */
|
||||||
guac_terminal_printf(terminal, "%s", title);
|
guac_terminal_printf(terminal, "%s", title);
|
||||||
|
|
||||||
@ -1672,6 +1684,13 @@ int guac_terminal_send_string(guac_terminal* term, const char* data) {
|
|||||||
|
|
||||||
static int __guac_terminal_send_key(guac_terminal* term, int keysym, int pressed) {
|
static int __guac_terminal_send_key(guac_terminal* term, int keysym, int pressed) {
|
||||||
|
|
||||||
|
/* Ignore user input if terminal is not started */
|
||||||
|
if (!term->started) {
|
||||||
|
guac_client_log(term->client, GUAC_LOG_DEBUG, "Ignoring user input "
|
||||||
|
"while terminal has not yet started.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hide mouse cursor if not already hidden */
|
/* Hide mouse cursor if not already hidden */
|
||||||
if (term->current_cursor != GUAC_TERMINAL_CURSOR_BLANK) {
|
if (term->current_cursor != GUAC_TERMINAL_CURSOR_BLANK) {
|
||||||
term->current_cursor = GUAC_TERMINAL_CURSOR_BLANK;
|
term->current_cursor = GUAC_TERMINAL_CURSOR_BLANK;
|
||||||
@ -1845,6 +1864,13 @@ int guac_terminal_send_key(guac_terminal* term, int keysym, int pressed) {
|
|||||||
static int __guac_terminal_send_mouse(guac_terminal* term, guac_user* user,
|
static int __guac_terminal_send_mouse(guac_terminal* term, guac_user* user,
|
||||||
int x, int y, int mask) {
|
int x, int y, int mask) {
|
||||||
|
|
||||||
|
/* Ignore user input if terminal is not started */
|
||||||
|
if (!term->started) {
|
||||||
|
guac_client_log(term->client, GUAC_LOG_DEBUG, "Ignoring user input "
|
||||||
|
"while terminal has not yet started.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Determine which buttons were just released and pressed */
|
/* Determine which buttons were just released and pressed */
|
||||||
int released_mask = term->mouse_mask & ~mask;
|
int released_mask = term->mouse_mask & ~mask;
|
||||||
int pressed_mask = ~term->mouse_mask & mask;
|
int pressed_mask = ~term->mouse_mask & mask;
|
||||||
|
@ -171,6 +171,16 @@ struct guac_terminal {
|
|||||||
*/
|
*/
|
||||||
guac_client* client;
|
guac_client* client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether user input should be handled and this terminal should render
|
||||||
|
* frames. Initially, this will be false, user input will be ignored, and
|
||||||
|
* rendering of frames will be withheld until guac_terminal_start() has
|
||||||
|
* been invoked. The data within frames will still be rendered, and text
|
||||||
|
* data received will still be handled, however actual frame boundaries
|
||||||
|
* will not be sent.
|
||||||
|
*/
|
||||||
|
bool started;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The terminal render thread.
|
* The terminal render thread.
|
||||||
*/
|
*/
|
||||||
@ -526,7 +536,13 @@ struct guac_terminal {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new guac_terminal, having the given width and height, and
|
* Creates a new guac_terminal, having the given width and height, and
|
||||||
* rendering to the given client.
|
* rendering to the given client. As failover mechanisms and the Guacamole
|
||||||
|
* client implementation typically use the receipt of a "sync" message to
|
||||||
|
* denote successful connection, rendering of frames (sending of "sync") will
|
||||||
|
* be withheld until guac_terminal_start() is called, and user input will be
|
||||||
|
* ignored. The guac_terminal_start() function should be invoked only after
|
||||||
|
* either the underlying connection has truly succeeded, or until visible
|
||||||
|
* terminal output or user input is required.
|
||||||
*
|
*
|
||||||
* @param client
|
* @param client
|
||||||
* The client to which the terminal will be rendered.
|
* The client to which the terminal will be rendered.
|
||||||
@ -604,6 +620,17 @@ int guac_terminal_render_frame(guac_terminal* terminal);
|
|||||||
*/
|
*/
|
||||||
int guac_terminal_read_stdin(guac_terminal* terminal, char* c, int size);
|
int guac_terminal_read_stdin(guac_terminal* terminal, char* c, int size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the terminal that rendering should begin and that user input should
|
||||||
|
* now be accepted. This function must be invoked following terminal creation
|
||||||
|
* for the end of frames to be signalled with "sync" messages. Until this
|
||||||
|
* function is invoked, "sync" messages will be withheld.
|
||||||
|
*
|
||||||
|
* @param term
|
||||||
|
* The terminal to start.
|
||||||
|
*/
|
||||||
|
void guac_terminal_start(guac_terminal* term);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manually stop the terminal to forcibly unblock any pending reads/writes,
|
* Manually stop the terminal to forcibly unblock any pending reads/writes,
|
||||||
* e.g. forcing guac_terminal_read_stdin() to return and cease all terminal I/O.
|
* e.g. forcing guac_terminal_read_stdin() to return and cease all terminal I/O.
|
||||||
@ -625,7 +652,9 @@ void guac_terminal_notify(guac_terminal* terminal);
|
|||||||
/**
|
/**
|
||||||
* Reads a single line from this terminal's STDIN, storing the result in a
|
* Reads a single line from this terminal's STDIN, storing the result in a
|
||||||
* newly-allocated string. Input is retrieved in the same manner as
|
* newly-allocated string. Input is retrieved in the same manner as
|
||||||
* guac_terminal_read_stdin() and the same restrictions apply.
|
* guac_terminal_read_stdin() and the same restrictions apply. As reading input
|
||||||
|
* naturally requires user interaction, this function will implicitly invoke
|
||||||
|
* guac_terminal_start().
|
||||||
*
|
*
|
||||||
* @param terminal
|
* @param terminal
|
||||||
* The terminal to which the provided title should be output, and from
|
* The terminal to which the provided title should be output, and from
|
||||||
|
Loading…
Reference in New Issue
Block a user