GUAC-1389: Add screen sharing support to telnet.

This commit is contained in:
Michael Jumper 2016-03-14 19:47:36 -07:00
parent 3a4aec3708
commit 4faf1829d2
15 changed files with 925 additions and 571 deletions

View File

@ -58,7 +58,7 @@ SUBDIRS += src/protocols/ssh
endif endif
if ENABLE_TELNET if ENABLE_TELNET
#SUBDIRS += src/protocols/telnet SUBDIRS += src/protocols/telnet
endif endif
if ENABLE_VNC if ENABLE_VNC

View File

@ -28,14 +28,18 @@ lib_LTLIBRARIES = libguac-client-telnet.la
libguac_client_telnet_la_SOURCES = \ libguac_client_telnet_la_SOURCES = \
client.c \ client.c \
clipboard.c \ clipboard.c \
guac_handlers.c \ input.c \
telnet_client.c settings.c \
telnet.c \
user.c
noinst_HEADERS = \ noinst_HEADERS = \
client.h \ client.h \
clipboard.h \ clipboard.h \
guac_handlers.h \ input.h \
telnet_client.h settings.h \
telnet.h \
user.h
libguac_client_telnet_la_CFLAGS = \ libguac_client_telnet_la_CFLAGS = \
-Werror -Wall -Iinclude \ -Werror -Wall -Iinclude \

View File

@ -22,260 +22,43 @@
#include "config.h" #include "config.h"
#include "client.h" #include "client.h"
#include "clipboard.h" #include "settings.h"
#include "guac_handlers.h" #include "telnet.h"
#include "telnet_client.h"
#include "terminal.h" #include "terminal.h"
#include "user.h"
#include <langinfo.h> #include <langinfo.h>
#include <locale.h> #include <locale.h>
#include <pthread.h> #include <pthread.h>
#include <regex.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/types.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#define GUAC_TELNET_DEFAULT_FONT_NAME "monospace" int guac_client_init(guac_client* client) {
#define GUAC_TELNET_DEFAULT_FONT_SIZE 12
#define GUAC_TELNET_DEFAULT_PORT "23"
/* Client plugin arguments */ /* Set client args */
const char* GUAC_CLIENT_ARGS[] = { client->args = GUAC_TELNET_CLIENT_ARGS;
"hostname",
"port",
"username",
"username-regex",
"password",
"password-regex",
"font-name",
"font-size",
"color-scheme",
"typescript-path",
"typescript-name",
"create-typescript-path",
NULL
};
enum __TELNET_ARGS_IDX { /* Allocate client instance data */
guac_telnet_client* telnet_client = calloc(1, sizeof(guac_telnet_client));
client->data = telnet_client;
/** /* Init telnet client */
* The hostname to connect to. Required. telnet_client->socket_fd = -1;
*/ telnet_client->naws_enabled = 0;
IDX_HOSTNAME, telnet_client->echo_enabled = 1;
/** /* Set handlers */
* The port to connect to. Optional. client->join_handler = guac_telnet_user_join_handler;
*/ client->free_handler = guac_telnet_client_free_handler;
IDX_PORT,
/**
* The name of the user to login as. Optional.
*/
IDX_USERNAME,
/**
* 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.
*/
IDX_FONT_NAME,
/**
* The size of the font to use within the terminal, in points.
*/
IDX_FONT_SIZE,
/**
* The name of the color scheme to use. Currently valid color schemes are:
* "black-white", "white-black", "gray-black", and "green-black", each
* following the "foreground-background" pattern. By default, this will be
* "gray-black".
*/
IDX_COLOR_SCHEME,
/**
* The full absolute path to the directory in which typescripts should be
* written.
*/
IDX_TYPESCRIPT_PATH,
/**
* The name that should be given to typescripts which are written in the
* given path. Each typescript will consist of two files: "NAME" and
* "NAME.timing".
*/
IDX_TYPESCRIPT_NAME,
/**
* Whether the specified typescript path should automatically be created
* if it does not yet exist.
*/
IDX_CREATE_TYPESCRIPT_PATH,
TELNET_ARGS_COUNT
};
/**
* Compiles the given regular expression, returning NULL if compilation fails.
*/
static regex_t* __guac_telnet_compile_regex(guac_client* client, char* pattern) {
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_client_log(client, GUAC_LOG_ERROR, "Regular expression '%s' could not be compiled.", pattern);
free(regex);
return NULL;
}
return regex;
}
int guac_client_init(guac_client* client, int argc, char** argv) {
guac_socket* socket = client->socket;
guac_telnet_client_data* client_data = malloc(sizeof(guac_telnet_client_data));
/* Init client data */
client->data = client_data;
client_data->telnet = NULL;
client_data->socket_fd = -1;
client_data->naws_enabled = 0;
client_data->echo_enabled = 1;
if (argc != TELNET_ARGS_COUNT) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Wrong number of arguments");
return -1;
}
/* Set locale and warn if not UTF-8 */ /* Set locale and warn if not UTF-8 */
setlocale(LC_CTYPE, ""); setlocale(LC_CTYPE, "");
if (strcmp(nl_langinfo(CODESET), "UTF-8") != 0) if (strcmp(nl_langinfo(CODESET), "UTF-8") != 0) {
guac_client_log(client, GUAC_LOG_INFO, "Current locale does not use UTF-8. Some characters may not render correctly."); guac_client_log(client, GUAC_LOG_INFO,
"Current locale does not use UTF-8. Some characters may "
/* Read parameters */ "not render correctly.");
strcpy(client_data->hostname, argv[IDX_HOSTNAME]);
strcpy(client_data->username, argv[IDX_USERNAME]);
strcpy(client_data->password, argv[IDX_PASSWORD]);
/* Set username regex, if needed */
if (client_data->username[0] != 0) {
/* Compile regular expression */
if (argv[IDX_USERNAME_REGEX][0] != 0)
client_data->username_regex = __guac_telnet_compile_regex(client, argv[IDX_USERNAME_REGEX]);
else
client_data->username_regex = __guac_telnet_compile_regex(client, GUAC_TELNET_DEFAULT_USERNAME_REGEX);
}
else
client_data->username_regex = NULL;
/* Set password regex, if needed */
if (client_data->password[0] != 0) {
/* Compile regular expression */
if (argv[IDX_PASSWORD_REGEX][0] != 0)
client_data->password_regex = __guac_telnet_compile_regex(client, argv[IDX_PASSWORD_REGEX]);
else
client_data->password_regex = __guac_telnet_compile_regex(client, GUAC_TELNET_DEFAULT_PASSWORD_REGEX);
}
else
client_data->password_regex = NULL;
/* Read port */
if (argv[IDX_PORT][0] != 0)
strcpy(client_data->port, argv[IDX_PORT]);
else
strcpy(client_data->port, GUAC_TELNET_DEFAULT_PORT);
/* Read font name */
if (argv[IDX_FONT_NAME][0] != 0)
strcpy(client_data->font_name, argv[IDX_FONT_NAME]);
else
strcpy(client_data->font_name, GUAC_TELNET_DEFAULT_FONT_NAME );
/* Read font size */
if (argv[IDX_FONT_SIZE][0] != 0)
client_data->font_size = atoi(argv[IDX_FONT_SIZE]);
else
client_data->font_size = GUAC_TELNET_DEFAULT_FONT_SIZE;
/* Create terminal */
client_data->term = guac_terminal_create(client,
client_data->font_name, client_data->font_size,
client->info.optimal_resolution,
client->info.optimal_width, client->info.optimal_height,
argv[IDX_COLOR_SCHEME]);
/* Fail if terminal init failed */
if (client_data->term == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Terminal initialization failed");
return -1;
}
/* Set up typescript, if requested */
const char* typescript_path = argv[IDX_TYPESCRIPT_PATH];
if (typescript_path[0] != 0) {
/* Default to "typescript" if no name provided */
const char* typescript_name = argv[IDX_TYPESCRIPT_NAME];
if (typescript_name[0] == 0)
typescript_name = "typescript";
/* Parse path creation flag */
int create_path =
strcmp(argv[IDX_CREATE_TYPESCRIPT_PATH], "true") == 0;
/* Create typescript */
guac_terminal_create_typescript(client_data->term, typescript_path,
typescript_name, create_path);
}
/* Send initial name */
guac_protocol_send_name(socket, client_data->hostname);
guac_socket_flush(socket);
/* Set basic handlers */
client->handle_messages = guac_telnet_client_handle_messages;
client->key_handler = guac_telnet_client_key_handler;
client->mouse_handler = guac_telnet_client_mouse_handler;
client->size_handler = guac_telnet_client_size_handler;
client->free_handler = guac_telnet_client_free_handler;
client->clipboard_handler = guac_telnet_clipboard_handler;
/* Start client thread */
if (pthread_create(&(client_data->client_thread), NULL, guac_telnet_client_thread, (void*) client)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start telnet client thread");
return -1;
} }
/* Success */ /* Success */
@ -283,3 +66,29 @@ int guac_client_init(guac_client* client, int argc, char** argv) {
} }
int guac_telnet_client_free_handler(guac_client* client) {
guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
/* Close telnet connection */
if (telnet_client->socket_fd != -1)
close(telnet_client->socket_fd);
/* Kill terminal */
guac_terminal_free(telnet_client->term);
/* Wait for and free telnet session, if connected */
if (telnet_client->telnet != NULL) {
pthread_join(telnet_client->client_thread, NULL);
telnet_free(telnet_client->telnet);
}
/* Free settings */
if (telnet_client->settings != NULL)
guac_telnet_settings_free(telnet_client->settings);
free(telnet_client);
return 0;
}

View File

@ -32,91 +32,11 @@
#include <libtelnet.h> #include <libtelnet.h>
#define GUAC_TELNET_DEFAULT_USERNAME_REGEX "[Ll]ogin:"
#define GUAC_TELNET_DEFAULT_PASSWORD_REGEX "[Pp]assword:"
/** /**
* Telnet-specific client data. * Free handler. Required by libguac and called when the guac_client is
* disconnected and must be cleaned up.
*/ */
typedef struct guac_telnet_client_data { guac_client_free_handler guac_telnet_client_free_handler;
/**
* The hostname of the telnet server to connect to.
*/
char hostname[1024];
/**
* The port of the telnet server to connect to.
*/
char port[64];
/**
* The name of the user to login as.
*/
char username[1024];
/**
* The regular expression to use when searching for the username
* prompt. This will be NULL unless the telnet client is currently
* searching for the username prompt.
*/
regex_t* username_regex;
/**
* The password to give when authenticating.
*/
char password[1024];
/**
* The regular expression to use when searching for the password
* prompt. This will be NULL unless the telnet client is currently
* searching for the password prompt.
*/
regex_t* password_regex;
/**
* The name of the font to use for display rendering.
*/
char font_name[1024];
/**
* The size of the font to use, in points.
*/
int font_size;
/**
* The telnet client thread.
*/
pthread_t client_thread;
/**
* The file descriptor of the socket connected to the telnet server,
* or -1 if no connection has been established.
*/
int socket_fd;
/**
* Telnet connection, used by the telnet client thread.
*/
telnet_t* telnet;
/**
* Whether window size should be sent when the window is resized.
*/
int naws_enabled;
/**
* Whether all user input should be automatically echoed to the
* terminal.
*/
int echo_enabled;
/**
* The terminal which will render all output from the telnet client.
*/
guac_terminal* term;
} guac_telnet_client_data;
#endif #endif

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2014 Glyptodon LLC * Copyright (C) 2016 Glyptodon, Inc.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -21,19 +21,21 @@
*/ */
#include "config.h" #include "config.h"
#include "client.h"
#include "clipboard.h" #include "clipboard.h"
#include "telnet.h"
#include "terminal.h" #include "terminal.h"
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/stream.h> #include <guacamole/stream.h>
#include <guacamole/user.h>
int guac_telnet_clipboard_handler(guac_client* client, guac_stream* stream, int guac_telnet_clipboard_handler(guac_user* user, guac_stream* stream,
char* mimetype) { char* mimetype) {
/* Clear clipboard and prepare for new data */ /* Clear clipboard and prepare for new data */
guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; guac_client* client = user->client;
guac_terminal_clipboard_reset(client_data->term, mimetype); guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
guac_terminal_clipboard_reset(telnet_client->term, mimetype);
/* Set handlers for clipboard stream */ /* Set handlers for clipboard stream */
stream->blob_handler = guac_telnet_clipboard_blob_handler; stream->blob_handler = guac_telnet_clipboard_blob_handler;
@ -42,17 +44,18 @@ int guac_telnet_clipboard_handler(guac_client* client, guac_stream* stream,
return 0; return 0;
} }
int guac_telnet_clipboard_blob_handler(guac_client* client, guac_stream* stream, int guac_telnet_clipboard_blob_handler(guac_user* user, guac_stream* stream,
void* data, int length) { void* data, int length) {
/* Append new data */ /* Append new data */
guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; guac_client* client = user->client;
guac_terminal_clipboard_append(client_data->term, data, length); guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
guac_terminal_clipboard_append(telnet_client->term, data, length);
return 0; return 0;
} }
int guac_telnet_clipboard_end_handler(guac_client* client, guac_stream* stream) { int guac_telnet_clipboard_end_handler(guac_user* user, guac_stream* stream) {
/* Nothing to do - clipboard is implemented within client */ /* Nothing to do - clipboard is implemented within client */

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2014 Glyptodon LLC * Copyright (C) 2016 Glyptodon, Inc.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -20,30 +20,27 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#ifndef GUAC_TELNET__CLIPBOARD_H #ifndef GUAC_TELNET_CLIPBOARD_H
#define GUAC_TELNET__CLIPBOARD_H #define GUAC_TELNET_CLIPBOARD_H
#include "config.h" #include "config.h"
#include <guacamole/client.h> #include <guacamole/user.h>
#include <guacamole/stream.h>
/** /**
* Handler for inbound clipboard data. * Handler for inbound clipboard streams.
*/ */
int guac_telnet_clipboard_handler(guac_client* client, guac_stream* stream, guac_user_clipboard_handler guac_telnet_clipboard_handler;
char* mimetype);
/** /**
* Handler for stream data related to clipboard. * Handler for data received along clipboard streams.
*/ */
int guac_telnet_clipboard_blob_handler(guac_client* client, guac_stream* stream, guac_user_blob_handler guac_telnet_clipboard_blob_handler;
void* data, int length);
/** /**
* Handler for end-of-stream related to clipboard. * Handler for end-of-stream related to clipboard.
*/ */
int guac_telnet_clipboard_end_handler(guac_client* client, guac_stream* stream); guac_user_end_handler guac_telnet_clipboard_end_handler;
#endif #endif

View File

@ -1,150 +0,0 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "client.h"
#include "guac_handlers.h"
#include "terminal.h"
#include "telnet_client.h"
#include <guacamole/client.h>
#include <libtelnet.h>
#include <pthread.h>
#include <regex.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int guac_telnet_client_handle_messages(guac_client* client) {
guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data;
return guac_terminal_render_frame(client_data->term);
}
int guac_telnet_client_mouse_handler(guac_client* client, int x, int y, int mask) {
guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data;
guac_terminal* term = client_data->term;
/* Send mouse if not searching for password or username */
if (client_data->password_regex == NULL && client_data->username_regex == NULL)
guac_terminal_send_mouse(term, x, y, mask);
return 0;
}
int guac_telnet_client_key_handler(guac_client* client, int keysym, int pressed) {
guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data;
guac_terminal* term = client_data->term;
/* Stop searching for password */
if (client_data->password_regex != NULL) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Stopping password prompt search due to user input.");
regfree(client_data->password_regex);
free(client_data->password_regex);
client_data->password_regex = NULL;
}
/* Stop searching for username */
if (client_data->username_regex != NULL) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Stopping username prompt search due to user input.");
regfree(client_data->username_regex);
free(client_data->username_regex);
client_data->username_regex = NULL;
}
/* Intercept and handle Pause / Break / Ctrl+0 as "IAC BRK" */
if (pressed && (
keysym == 0xFF13 /* Pause */
|| keysym == 0xFF6B /* Break */
|| (term->mod_ctrl && keysym == '0') /* Ctrl + 0 */
)) {
/* Send IAC BRK */
telnet_iac(client_data->telnet, TELNET_BREAK);
return 0;
}
/* Send key */
guac_terminal_send_key(term, keysym, pressed);
return 0;
}
int guac_telnet_client_size_handler(guac_client* client, int width, int height) {
/* Get terminal */
guac_telnet_client_data* guac_client_data = (guac_telnet_client_data*) client->data;
guac_terminal* terminal = guac_client_data->term;
/* Resize terminal */
guac_terminal_resize(terminal, width, height);
/* Update terminal window size if connected */
if (guac_client_data->telnet != NULL && guac_client_data->naws_enabled)
guac_telnet_send_naws(guac_client_data->telnet, terminal->term_width, terminal->term_height);
return 0;
}
int guac_telnet_client_free_handler(guac_client* client) {
guac_telnet_client_data* guac_client_data = (guac_telnet_client_data*) client->data;
/* Close telnet connection */
if (guac_client_data->socket_fd != -1)
close(guac_client_data->socket_fd);
/* Kill terminal */
guac_terminal_free(guac_client_data->term);
/* Wait for and free telnet session, if connected */
if (guac_client_data->telnet != NULL) {
pthread_join(guac_client_data->client_thread, NULL);
telnet_free(guac_client_data->telnet);
}
/* Free password regex */
if (guac_client_data->password_regex != NULL) {
regfree(guac_client_data->password_regex);
free(guac_client_data->password_regex);
}
free(client->data);
return 0;
}

View File

@ -0,0 +1,120 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "input.h"
#include "terminal.h"
#include "telnet.h"
#include <guacamole/client.h>
#include <guacamole/user.h>
#include <libtelnet.h>
#include <regex.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int guac_telnet_user_mouse_handler(guac_user* user, int x, int y, int mask) {
guac_client* client = user->client;
guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
guac_telnet_settings* settings = telnet_client->settings;
guac_terminal* term = telnet_client->term;
/* Send mouse if not searching for password or username */
if (settings->password_regex == NULL && settings->username_regex == NULL)
guac_terminal_send_mouse(term, user, x, y, mask);
return 0;
}
int guac_telnet_user_key_handler(guac_user* user, int keysym, int pressed) {
guac_client* client = user->client;
guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
guac_telnet_settings* settings = telnet_client->settings;
guac_terminal* term = telnet_client->term;
/* 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" */
if (pressed && (
keysym == 0xFF13 /* Pause */
|| keysym == 0xFF6B /* Break */
|| (term->mod_ctrl && keysym == '0') /* Ctrl + 0 */
)) {
/* Send IAC BRK */
telnet_iac(telnet_client->telnet, TELNET_BREAK);
return 0;
}
/* Send key */
guac_terminal_send_key(term, keysym, pressed);
return 0;
}
int guac_telnet_user_size_handler(guac_user* user, int width, int height) {
/* Get terminal */
guac_client* client = user->client;
guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
guac_terminal* terminal = telnet_client->term;
/* Resize terminal */
guac_terminal_resize(terminal, width, height);
/* Update terminal window size if connected */
if (telnet_client->telnet != NULL && telnet_client->naws_enabled)
guac_telnet_send_naws(telnet_client->telnet, terminal->term_width,
terminal->term_height);
return 0;
}

View File

@ -20,43 +20,30 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#ifndef GUAC_TELNET__GUAC_HANDLERS_H #ifndef GUAC_TELNET_INPUT_H
#define GUAC_TELNET__GUAC_HANDLERS_H #define GUAC_TELNET_INPUT_H
#include "config.h" #include "config.h"
#include <guacamole/client.h> #include <guacamole/user.h>
/**
* Generic handler for sending outbound messages. Required by libguac and
* called periodically by guacd when the client is ready for more graphical
* updates.
*/
int guac_telnet_client_handle_messages(guac_client* client);
/** /**
* Handler for key events. Required by libguac and called whenever key events * Handler for key events. Required by libguac and called whenever key events
* are received. * are received.
*/ */
int guac_telnet_client_key_handler(guac_client* client, int keysym, int pressed); guac_user_key_handler guac_telnet_user_key_handler;
/** /**
* Handler for mouse events. Required by libguac and called whenever mouse * Handler for mouse events. Required by libguac and called whenever mouse
* events are received. * events are received.
*/ */
int guac_telnet_client_mouse_handler(guac_client* client, int x, int y, int mask); guac_user_mouse_handler guac_telnet_user_mouse_handler;
/** /**
* Handler for size events. Required by libguac and called whenever the remote * Handler for size events. Required by libguac and called whenever the remote
* display (window) is resized. * display (window) is resized.
*/ */
int guac_telnet_client_size_handler(guac_client* client, int width, int height); guac_user_size_handler guac_telnet_user_size_handler;
/**
* Free handler. Required by libguac and called when the guac_client is
* disconnected and must be cleaned up.
*/
int guac_telnet_client_free_handler(guac_client* client);
#endif #endif

View File

@ -0,0 +1,270 @@
/*
* Copyright (C) 2016 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "settings.h"
#include <guacamole/user.h>
#include <sys/types.h>
#include <regex.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* Client plugin arguments */
const char* GUAC_TELNET_CLIENT_ARGS[] = {
"hostname",
"port",
"username",
"username-regex",
"password",
"password-regex",
"font-name",
"font-size",
"color-scheme",
"typescript-path",
"typescript-name",
"create-typescript-path",
NULL
};
enum TELNET_ARGS_IDX {
/**
* The hostname to connect to. Required.
*/
IDX_HOSTNAME,
/**
* The port to connect to. Optional.
*/
IDX_PORT,
/**
* The name of the user to login as. Optional.
*/
IDX_USERNAME,
/**
* 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.
*/
IDX_FONT_NAME,
/**
* The size of the font to use within the terminal, in points.
*/
IDX_FONT_SIZE,
/**
* The name of the color scheme to use. Currently valid color schemes are:
* "black-white", "white-black", "gray-black", and "green-black", each
* following the "foreground-background" pattern. By default, this will be
* "gray-black".
*/
IDX_COLOR_SCHEME,
/**
* The full absolute path to the directory in which typescripts should be
* written.
*/
IDX_TYPESCRIPT_PATH,
/**
* The name that should be given to typescripts which are written in the
* given path. Each typescript will consist of two files: "NAME" and
* "NAME.timing".
*/
IDX_TYPESCRIPT_NAME,
/**
* Whether the specified typescript path should automatically be created
* if it does not yet exist.
*/
IDX_CREATE_TYPESCRIPT_PATH,
TELNET_ARGS_COUNT
};
/**
* Compiles the given regular expression, returning NULL if compilation fails.
*/
static regex_t* guac_telnet_compile_regex(guac_user* user, char* pattern) {
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;
}
guac_telnet_settings* guac_telnet_parse_args(guac_user* user,
int argc, const char** argv) {
/* Validate arg count */
if (argc != TELNET_ARGS_COUNT) {
guac_user_log(user, GUAC_LOG_WARNING, "Incorrect number of connection "
"parameters provided: expected %i, got %i.",
TELNET_ARGS_COUNT, argc);
return NULL;
}
guac_telnet_settings* settings = calloc(1, sizeof(guac_telnet_settings));
/* Read parameters */
settings->hostname =
guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv,
IDX_HOSTNAME, "");
/* Read username */
settings->username =
guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv,
IDX_USERNAME, NULL);
/* Read username regex only if password 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,
IDX_USERNAME_REGEX, GUAC_TELNET_DEFAULT_USERNAME_REGEX));
}
/* Read password */
settings->password =
guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv,
IDX_PASSWORD, NULL);
/* Read password regex only if password is specified */
if (settings->password != NULL) {
settings->password_regex = guac_telnet_compile_regex(user,
guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv,
IDX_PASSWORD_REGEX, GUAC_TELNET_DEFAULT_PASSWORD_REGEX));
}
/* Read font name */
settings->font_name =
guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv,
IDX_FONT_NAME, GUAC_TELNET_DEFAULT_FONT_NAME);
/* Read font size */
settings->font_size =
guac_user_parse_args_int(user, GUAC_TELNET_CLIENT_ARGS, argv,
IDX_FONT_SIZE, GUAC_TELNET_DEFAULT_FONT_SIZE);
/* Copy requested color scheme */
settings->color_scheme =
guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv,
IDX_COLOR_SCHEME, "");
/* Pull width/height/resolution directly from user */
settings->width = user->info.optimal_width;
settings->height = user->info.optimal_height;
settings->resolution = user->info.optimal_resolution;
/* Read port */
settings->port =
guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv,
IDX_PORT, GUAC_TELNET_DEFAULT_PORT);
/* Read typescript path */
settings->typescript_path =
guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv,
IDX_TYPESCRIPT_PATH, NULL);
/* Read typescript name */
settings->typescript_name =
guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv,
IDX_TYPESCRIPT_NAME, GUAC_TELNET_DEFAULT_TYPESCRIPT_NAME);
/* Parse path creation flag */
settings->create_typescript_path =
guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv,
IDX_CREATE_TYPESCRIPT_PATH, false);
/* Parsing was successful */
return settings;
}
void guac_telnet_settings_free(guac_telnet_settings* settings) {
/* Free network connection information */
free(settings->hostname);
free(settings->port);
/* Free credentials */
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 display preferences */
free(settings->font_name);
free(settings->color_scheme);
/* Free typescript settings */
free(settings->typescript_name);
free(settings->typescript_path);
/* Free overall structure */
free(settings);
}

View File

@ -0,0 +1,199 @@
/*
* Copyright (C) 2016 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUAC_TELNET_SETTINGS_H
#define GUAC_TELNET_SETTINGS_H
#include "config.h"
#include <guacamole/user.h>
#include <sys/types.h>
#include <regex.h>
#include <stdbool.h>
/**
* The name of the font to use for the terminal if no name is specified.
*/
#define GUAC_TELNET_DEFAULT_FONT_NAME "monospace"
/**
* The size of the font to use for the terminal if no font size is specified,
* in points.
*/
#define GUAC_TELNET_DEFAULT_FONT_SIZE 12
/**
* The port to connect to when initiating any telnet connection, if no other
* port is specified.
*/
#define GUAC_TELNET_DEFAULT_PORT "23"
/**
* The filename to use for the typescript, if not specified.
*/
#define GUAC_TELNET_DEFAULT_TYPESCRIPT_NAME "typescript"
/**
* The regular expression to use when searching for the username/login prompt
* if no other regular expression is specified.
*/
#define GUAC_TELNET_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_TELNET_DEFAULT_PASSWORD_REGEX "[Pp]assword:"
/**
* Settings for the telnet connection. The values for this structure are parsed
* from the arguments given during the Guacamole protocol handshake using the
* guac_telnet_parse_args() function.
*/
typedef struct guac_telnet_settings {
/**
* The hostname of the telnet server to connect to.
*/
char* hostname;
/**
* The port of the telnet server to connect to.
*/
char* port;
/**
* The name of the user to login as, if any. If no username is specified,
* this will be NULL.
*/
char* username;
/**
* 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 name of the font to use for display rendering.
*/
char* font_name;
/**
* The size of the font to use, in points.
*/
int font_size;
/**
* The name of the color scheme to use.
*/
char* color_scheme;
/**
* The desired width of the terminal display, in pixels.
*/
int width;
/**
* The desired height of the terminal display, in pixels.
*/
int height;
/**
* The desired screen resolution, in DPI.
*/
int resolution;
/**
* The path in which the typescript should be saved, if enabled. If no
* typescript should be saved, this will be NULL.
*/
char* typescript_path;
/**
* The filename to use for the typescript, if enabled.
*/
char* typescript_name;
/**
* Whether the typescript path should be automatically created if it does
* not already exist.
*/
bool create_typescript_path;
} guac_telnet_settings;
/**
* Parses all given args, storing them in a newly-allocated settings object. If
* the args fail to parse, NULL is returned.
*
* @param user
* The user who submitted the given arguments while joining the
* connection.
*
* @param argc
* The number of arguments within the argv array.
*
* @param argv
* The values of all arguments provided by the user.
*
* @return
* A newly-allocated settings object which must be freed with
* guac_telnet_settings_free() when no longer needed. If the arguments fail
* to parse, NULL is returned.
*/
guac_telnet_settings* guac_telnet_parse_args(guac_user* user,
int argc, const char** argv);
/**
* Frees the given guac_telnet_settings object, having been previously
* allocated via guac_telnet_parse_args().
*
* @param settings
* The settings object to free.
*/
void guac_telnet_settings_free(guac_telnet_settings* settings);
/**
* NULL-terminated array of accepted client args.
*/
extern const char* GUAC_TELNET_CLIENT_ARGS[];
#endif

View File

@ -21,8 +21,7 @@
*/ */
#include "config.h" #include "config.h"
#include "client.h" #include "telnet.h"
#include "telnet_client.h"
#include "terminal.h" #include "terminal.h"
#include <guacamole/client.h> #include <guacamole/client.h>
@ -95,7 +94,7 @@ static bool __guac_telnet_regex_search(guac_client* client, regex_t* regex, char
static char line_buffer[1024] = {0}; static char line_buffer[1024] = {0};
static int length = 0; static int length = 0;
guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
int i; int i;
const char* current; const char* current;
@ -126,9 +125,9 @@ 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(client_data->term, value); guac_terminal_send_string(telnet_client->term, value);
guac_terminal_send_key(client_data->term, 0xFF0D, 1); guac_terminal_send_key(telnet_client->term, 0xFF0D, 1);
guac_terminal_send_key(client_data->term, 0xFF0D, 0); guac_terminal_send_key(telnet_client->term, 0xFF0D, 0);
/* Stop searching for prompt */ /* Stop searching for prompt */
return TRUE; return TRUE;
@ -146,49 +145,52 @@ static bool __guac_telnet_regex_search(guac_client* client, regex_t* regex, char
static void __guac_telnet_event_handler(telnet_t* telnet, telnet_event_t* event, void* data) { static void __guac_telnet_event_handler(telnet_t* telnet, telnet_event_t* event, void* data) {
guac_client* client = (guac_client*) data; guac_client* client = (guac_client*) data;
guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
guac_telnet_settings* settings = telnet_client->settings;
switch (event->type) { switch (event->type) {
/* Terminal output received */ /* Terminal output received */
case TELNET_EV_DATA: case TELNET_EV_DATA:
guac_terminal_write_stdout(client_data->term, event->data.buffer, event->data.size); guac_terminal_write_stdout(telnet_client->term, event->data.buffer, event->data.size);
/* Continue search for username prompt */ /* Continue search for username prompt */
if (client_data->username_regex != NULL) { if (settings->username_regex != NULL) {
if (__guac_telnet_regex_search(client, client_data->username_regex, client_data->username, if (__guac_telnet_regex_search(client,
event->data.buffer, event->data.size)) { settings->username_regex, settings->username,
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(client_data->username_regex); regfree(settings->username_regex);
free(client_data->username_regex); free(settings->username_regex);
client_data->username_regex = NULL; settings->username_regex = NULL;
} }
} }
/* Continue search for password prompt */ /* Continue search for password prompt */
if (client_data->password_regex != NULL) { if (settings->password_regex != NULL) {
if (__guac_telnet_regex_search(client, client_data->password_regex, client_data->password, if (__guac_telnet_regex_search(client,
event->data.buffer, event->data.size)) { settings->password_regex, settings->password,
event->data.buffer, event->data.size)) {
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 once password is sent */
if (client_data->username_regex != NULL) { if (settings->username_regex != NULL) {
regfree(client_data->username_regex); regfree(settings->username_regex);
free(client_data->username_regex); free(settings->username_regex);
client_data->username_regex = NULL; settings->username_regex = NULL;
} }
regfree(client_data->password_regex); regfree(settings->password_regex);
free(client_data->password_regex); free(settings->password_regex);
client_data->password_regex = NULL; settings->password_regex = NULL;
} }
} }
break; break;
/* Data destined for remote end */ /* Data destined for remote end */
case TELNET_EV_SEND: case TELNET_EV_SEND:
if (__guac_telnet_write_all(client_data->socket_fd, event->data.buffer, event->data.size) if (__guac_telnet_write_all(telnet_client->socket_fd, event->data.buffer, event->data.size)
!= event->data.size) != event->data.size)
guac_client_stop(client); guac_client_stop(client);
break; break;
@ -196,27 +198,27 @@ static void __guac_telnet_event_handler(telnet_t* telnet, telnet_event_t* event,
/* Remote feature enabled */ /* Remote feature enabled */
case TELNET_EV_WILL: case TELNET_EV_WILL:
if (event->neg.telopt == TELNET_TELOPT_ECHO) if (event->neg.telopt == TELNET_TELOPT_ECHO)
client_data->echo_enabled = 0; /* Disable local echo, as remote will echo */ telnet_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:
if (event->neg.telopt == TELNET_TELOPT_ECHO) if (event->neg.telopt == TELNET_TELOPT_ECHO)
client_data->echo_enabled = 1; /* Enable local echo, as remote won't echo */ telnet_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) { if (event->neg.telopt == TELNET_TELOPT_NAWS) {
client_data->naws_enabled = 1; telnet_client->naws_enabled = 1;
guac_telnet_send_naws(telnet, client_data->term->term_width, client_data->term->term_height); guac_telnet_send_naws(telnet, telnet_client->term->term_width, telnet_client->term->term_height);
} }
break; break;
/* Terminal type request */ /* Terminal type request */
case TELNET_EV_TTYPE: case TELNET_EV_TTYPE:
if (event->ttype.cmd == TELNET_TTYPE_SEND) if (event->ttype.cmd == TELNET_TTYPE_SEND)
telnet_ttype_is(client_data->telnet, "linux"); telnet_ttype_is(telnet_client->telnet, "linux");
break; break;
/* Environment request */ /* Environment request */
@ -224,7 +226,7 @@ static void __guac_telnet_event_handler(telnet_t* telnet, telnet_event_t* event,
/* 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_telnet_send_user(telnet, client_data->username); guac_telnet_send_user(telnet, settings->username);
break; break;
@ -258,16 +260,16 @@ static void __guac_telnet_event_handler(telnet_t* telnet, telnet_event_t* event,
static void* __guac_telnet_input_thread(void* data) { static void* __guac_telnet_input_thread(void* data) {
guac_client* client = (guac_client*) data; guac_client* client = (guac_client*) data;
guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
char buffer[8192]; char buffer[8192];
int bytes_read; int bytes_read;
/* Write all data read */ /* Write all data read */
while ((bytes_read = guac_terminal_read_stdin(client_data->term, buffer, sizeof(buffer))) > 0) { while ((bytes_read = guac_terminal_read_stdin(telnet_client->term, buffer, sizeof(buffer))) > 0) {
telnet_send(client_data->telnet, buffer, bytes_read); telnet_send(telnet_client->telnet, buffer, bytes_read);
if (client_data->echo_enabled) if (telnet_client->echo_enabled)
guac_terminal_write_stdout(client_data->term, buffer, bytes_read); guac_terminal_write_stdout(telnet_client->term, buffer, bytes_read);
} }
return NULL; return NULL;
@ -293,7 +295,8 @@ static telnet_t* __guac_telnet_create_session(guac_client* client) {
char connected_address[1024]; char connected_address[1024];
char connected_port[64]; char connected_port[64];
guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
guac_telnet_settings* settings = telnet_client->settings;
struct addrinfo hints = { struct addrinfo hints = {
.ai_family = AF_UNSPEC, .ai_family = AF_UNSPEC,
@ -305,7 +308,7 @@ static telnet_t* __guac_telnet_create_session(guac_client* client) {
fd = socket(AF_INET, SOCK_STREAM, 0); fd = socket(AF_INET, SOCK_STREAM, 0);
/* Get addresses connection */ /* Get addresses connection */
if ((retval = getaddrinfo(client_data->hostname, client_data->port, if ((retval = getaddrinfo(settings->hostname, settings->port,
&hints, &addresses))) { &hints, &addresses))) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error parsing given address or port: %s", guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error parsing given address or port: %s",
gai_strerror(retval)); gai_strerror(retval));
@ -366,7 +369,7 @@ static telnet_t* __guac_telnet_create_session(guac_client* client) {
} }
/* Save file descriptor */ /* Save file descriptor */
client_data->socket_fd = fd; telnet_client->socket_fd = fd;
return telnet; return telnet;
@ -412,13 +415,18 @@ void guac_telnet_send_user(telnet_t* telnet, const char* username) {
telnet_begin_sb(telnet, TELNET_TELOPT_NEW_ENVIRON); telnet_begin_sb(telnet, TELNET_TELOPT_NEW_ENVIRON);
__guac_telnet_send_uint8(telnet, TELNET_ENVIRON_IS); __guac_telnet_send_uint8(telnet, TELNET_ENVIRON_IS);
/* VAR "USER" */ /* Only send username if defined */
__guac_telnet_send_uint8(telnet, TELNET_ENVIRON_VAR); if (username != NULL) {
telnet_send(telnet, "USER", 4);
/* VALUE username */ /* VAR "USER" */
__guac_telnet_send_uint8(telnet, TELNET_ENVIRON_VALUE); __guac_telnet_send_uint8(telnet, TELNET_ENVIRON_VAR);
telnet_send(telnet, username, strlen(username)); telnet_send(telnet, "USER", 4);
/* VALUE username */
__guac_telnet_send_uint8(telnet, TELNET_ENVIRON_VALUE);
telnet_send(telnet, username, strlen(username));
}
/* IAC SE */ /* IAC SE */
telnet_finish_sb(telnet); telnet_finish_sb(telnet);
@ -453,15 +461,37 @@ static int __guac_telnet_wait(int socket_fd) {
void* guac_telnet_client_thread(void* data) { void* guac_telnet_client_thread(void* data) {
guac_client* client = (guac_client*) data; guac_client* client = (guac_client*) data;
guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
guac_telnet_settings* settings = telnet_client->settings;
pthread_t input_thread; pthread_t input_thread;
char buffer[8192]; char buffer[8192];
int wait_result; int wait_result;
/* Create terminal */
telnet_client->term = guac_terminal_create(client,
settings->font_name, settings->font_size,
settings->resolution, settings->width, settings->height,
settings->color_scheme);
/* Fail if terminal init failed */
if (telnet_client->term == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Terminal initialization failed");
return NULL;
}
/* Set up typescript, if requested */
if (settings->typescript_path != NULL) {
guac_terminal_create_typescript(telnet_client->term,
settings->typescript_path,
settings->typescript_name,
settings->create_typescript_path);
}
/* Open telnet session */ /* Open telnet session */
client_data->telnet = __guac_telnet_create_session(client); telnet_client->telnet = __guac_telnet_create_session(client);
if (client_data->telnet == NULL) { if (telnet_client->telnet == NULL) {
/* Already aborted within __guac_telnet_create_session() */ /* Already aborted within __guac_telnet_create_session() */
return NULL; return NULL;
} }
@ -476,17 +506,17 @@ void* guac_telnet_client_thread(void* data) {
} }
/* While data available, write to terminal */ /* While data available, write to terminal */
while ((wait_result = __guac_telnet_wait(client_data->socket_fd)) >= 0) { while ((wait_result = __guac_telnet_wait(telnet_client->socket_fd)) >= 0) {
/* Resume waiting of no data available */ /* Resume waiting of no data available */
if (wait_result == 0) if (wait_result == 0)
continue; continue;
int bytes_read = read(client_data->socket_fd, buffer, sizeof(buffer)); int bytes_read = read(telnet_client->socket_fd, buffer, sizeof(buffer));
if (bytes_read <= 0) if (bytes_read <= 0)
break; break;
telnet_recv(client_data->telnet, buffer, bytes_read); telnet_recv(telnet_client->telnet, buffer, bytes_read);
} }

View File

@ -20,15 +20,61 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#ifndef GUAC_TELNET__TELNET_CLIENT_H #ifndef GUAC_TELNET_H
#define GUAC_TELNET__TELNET_CLIENT_H #define GUAC_TELNET_H
#include "config.h" #include "config.h"
#include "settings.h"
#include "terminal.h"
#include <libtelnet.h> #include <libtelnet.h>
#include <stdint.h> #include <stdint.h>
/**
* Telnet-specific client data.
*/
typedef struct guac_telnet_client {
/**
* Telnet connection settings.
*/
guac_telnet_settings* settings;
/**
* The telnet client thread.
*/
pthread_t client_thread;
/**
* The file descriptor of the socket connected to the telnet server,
* or -1 if no connection has been established.
*/
int socket_fd;
/**
* Telnet connection, used by the telnet client thread.
*/
telnet_t* telnet;
/**
* Whether window size should be sent when the window is resized.
*/
int naws_enabled;
/**
* Whether all user input should be automatically echoed to the
* terminal.
*/
int echo_enabled;
/**
* The terminal which will render all output from the telnet client.
*/
guac_terminal* term;
} guac_telnet_client;
/** /**
* Main telnet client thread, handling transfer of telnet output to STDOUT. * Main telnet client thread, handling transfer of telnet output to STDOUT.
*/ */

View File

@ -0,0 +1,83 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "clipboard.h"
#include "input.h"
#include "settings.h"
#include "telnet.h"
#include "terminal.h"
#include "user.h"
#include <guacamole/client.h>
#include <guacamole/socket.h>
#include <guacamole/user.h>
#include <pthread.h>
#include <string.h>
int guac_telnet_user_join_handler(guac_user* user, int argc, char** argv) {
guac_client* client = user->client;
guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
/* Connect via telnet if owner */
if (user->owner) {
/* Parse arguments into client */
guac_telnet_settings* settings = telnet_client->settings =
guac_telnet_parse_args(user, argc, (const char**) argv);
/* Fail if settings cannot be parsed */
if (settings == NULL) {
guac_user_log(user, GUAC_LOG_INFO,
"Badly formatted client arguments.");
return 1;
}
/* Start client thread */
if (pthread_create(&(telnet_client->client_thread), NULL,
guac_telnet_client_thread, (void*) client)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Unable to start telnet client thread");
return 1;
}
}
/* If not owner, synchronize with current display */
else {
guac_terminal_dup(telnet_client->term, user, user->socket);
guac_socket_flush(user->socket);
}
/* Set per-user event handlers */
user->key_handler = guac_telnet_user_key_handler;
user->mouse_handler = guac_telnet_user_mouse_handler;
user->size_handler = guac_telnet_user_size_handler;
user->clipboard_handler = guac_telnet_clipboard_handler;
return 0;
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUAC_TELNET_USER_H
#define GUAC_TELNET_USER_H
#include "config.h"
#include <guacamole/user.h>
/**
* Handler for joining users.
*/
guac_user_join_handler guac_telnet_user_join_handler;
#endif