diff --git a/src/protocols/telnet/settings.c b/src/protocols/telnet/settings.c index bef5cfae..890d5fec 100644 --- a/src/protocols/telnet/settings.c +++ b/src/protocols/telnet/settings.c @@ -53,6 +53,8 @@ const char* GUAC_TELNET_CLIENT_ARGS[] = { "backspace", "terminal-type", "scrollback", + "login-success-regex", + "login-failure-regex", NULL }; @@ -196,12 +198,31 @@ enum TELNET_ARGS_IDX { */ 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 }; /** - * Compiles the given regular expression, returning NULL if compilation fails. - * The returned regex_t must be freed with regfree() AND free(). + * 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_telnet_regex_free(). * * @param user * 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. * * @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) { + /* Nothing to compile if no pattern provided */ + if (pattern == NULL) + return NULL; + int compile_result; 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; } +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, 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, IDX_USERNAME, NULL); - /* Read username regex only if password is specified */ + /* Read username regex only if username is specified */ if (settings->username != NULL) { settings->username_regex = guac_telnet_compile_regex(user, 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)); } + /* 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 */ settings->read_only = 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->password); - /* Free username regex (if allocated) */ - if (settings->username_regex != NULL) { - regfree(settings->username_regex); - free(settings->username_regex); - } - - /* Free password regex (if allocated) */ - if (settings->password_regex != NULL) { - regfree(settings->password_regex); - free(settings->password_regex); - } + /* Free various regexes */ + 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); /* Free display preferences */ free(settings->font_name); diff --git a/src/protocols/telnet/settings.h b/src/protocols/telnet/settings.h index 83ec4313..86302b77 100644 --- a/src/protocols/telnet/settings.h +++ b/src/protocols/telnet/settings.h @@ -117,6 +117,20 @@ typedef struct guac_telnet_settings { */ 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. */ @@ -253,6 +267,16 @@ typedef struct guac_telnet_settings { guac_telnet_settings* guac_telnet_parse_args(guac_user* user, 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 * allocated via guac_telnet_parse_args(). diff --git a/src/protocols/telnet/telnet.c b/src/protocols/telnet/telnet.c index c9a636c2..36e4cac4 100644 --- a/src/protocols/telnet/telnet.c +++ b/src/protocols/telnet/telnet.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -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 * 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 - * value is sent. + * buffer is cleared whenever a newline is read. Returns true if a match is + * 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 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) { /* Send value */ - guac_terminal_send_string(telnet_client->term, value); - guac_terminal_send_key(telnet_client->term, 0xFF0D, 1); - guac_terminal_send_key(telnet_client->term, 0xFF0D, 0); + if (value != NULL) { + guac_terminal_send_string(telnet_client->term, value); + guac_terminal_send_string(telnet_client->term, "\x0D"); + } /* 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, 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; + guac_telnet_regex_free(&settings->username_regex); } } @@ -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"); - /* 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; - } + /* 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); - 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; /* Data destined for remote end */ @@ -508,6 +564,12 @@ void* guac_telnet_client_thread(void* data) { /* Logged in */ 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 */ 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");