diff --git a/Makefile.am b/Makefile.am index c6a56269..9377e66a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,6 +30,7 @@ DIST_SUBDIRS = \ src/guacd \ src/protocols/rdp \ src/protocols/ssh \ + src/protocols/telnet \ src/protocols/vnc \ tests @@ -51,6 +52,10 @@ if ENABLE_SSH SUBDIRS += src/protocols/ssh endif +if ENABLE_TELNET + SUBDIRS += src/protocols/telnet +endif + if ENABLE_VNC SUBDIRS += src/protocols/vnc endif diff --git a/configure.ac b/configure.ac index cc11d0ca..ccdd0f90 100644 --- a/configure.ac +++ b/configure.ac @@ -609,6 +609,23 @@ AM_CONDITIONAL([ENABLE_SSH_AGENT], [test "x${have_ssh_agent}" = "xyes" \ -a "x${enable_ssh_agent}" = "xyes"]) +# +# libtelnet +# + +have_libtelnet=yes +SSH_LIBS= + +AC_CHECK_LIB([telnet], [telnet_init], + [TELNET_LIBS="$TELNET_LIBS -ltelnet"], + [have_libtelnet=no]) +AM_CONDITIONAL([ENABLE_TELNET], [test "x${have_libtelnet}" = "xyes" \ + -a "x${have_terminal}" = "xyes" \ + -a "x${have_ssl}" = "xyes"]) + +AC_SUBST(TELNET_LIBS) + + AC_CONFIG_FILES([Makefile tests/Makefile src/common/Makefile @@ -617,12 +634,14 @@ AC_CONFIG_FILES([Makefile src/guacd/Makefile src/protocols/rdp/Makefile src/protocols/ssh/Makefile + src/protocols/telnet/Makefile src/protocols/vnc/Makefile]) AC_OUTPUT -AM_COND_IF([ENABLE_RDP], [build_rdp=yes], [build_rdp=no]) -AM_COND_IF([ENABLE_SSH], [build_ssh=yes], [build_ssh=no]) -AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no]) +AM_COND_IF([ENABLE_RDP], [build_rdp=yes], [build_rdp=no]) +AM_COND_IF([ENABLE_SSH], [build_ssh=yes], [build_ssh=no]) +AM_COND_IF([ENABLE_TELNET], [build_telnet=yes], [build_telnet=no]) +AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no]) AM_COND_IF([ENABLE_INIT], [build_init="${init_dir}"], [build_init=no]) @@ -637,6 +656,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION pango ............... ${have_pango} libssh2 ............. ${have_libssh2} libssl .............. ${have_ssl} + libtelnet ........... ${have_libtelnet} libVNCServer ........ ${have_libvncserver} libvorbis ........... ${have_vorbis} libpulse ............ ${have_pulse} @@ -645,6 +665,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION RDP ....... ${build_rdp} SSH ....... ${build_ssh} + Telnet .... ${build_telnet} VNC ....... ${build_vnc} Init scripts: ${build_init} diff --git a/src/protocols/telnet/.gitignore b/src/protocols/telnet/.gitignore new file mode 100644 index 00000000..3f725332 --- /dev/null +++ b/src/protocols/telnet/.gitignore @@ -0,0 +1,36 @@ + +# Object code +*.o +*.so +*.lo +*.la + +# Backup files +*~ + +# Release files +*.tar.gz + +# Files currently being edited by vim or vi +*.swp + +# automake/autoconf +.deps/ +.libs/ +Makefile +Makefile.in +aclocal.m4 +autom4te.cache/ +m4/* +!README +config.guess +config.log +config.status +config.sub +configure +depcomp +install-sh +libtool +ltmain.sh +missing + diff --git a/src/protocols/telnet/Makefile.am b/src/protocols/telnet/Makefile.am new file mode 100644 index 00000000..58323b8e --- /dev/null +++ b/src/protocols/telnet/Makefile.am @@ -0,0 +1,44 @@ +# +# Copyright (C) 2014 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. +# + +AUTOMAKE_OPTIONS = foreign + +ACLOCAL_AMFLAGS = -I m4 + +lib_LTLIBRARIES = libguac-client-telnet.la + +libguac_client_telnet_la_SOURCES = \ + client.c \ + clipboard.c \ + guac_handlers.c \ + telnet_client.c + +noinst_HEADERS = \ + client.h \ + clipboard.h \ + guac_handlers.h \ + telnet_client.h + +libguac_client_telnet_la_CFLAGS = -Werror -Wall -Iinclude @LIBGUAC_INCLUDE@ @TERMINAL_INCLUDE@ +libguac_client_telnet_la_LIBADD = @LIBGUAC_LTLIB@ @TERMINAL_LTLIB@ +libguac_client_telnet_la_LDFLAGS = -version-info 0:0:0 @TELNET_LIBS@ @PTHREAD_LIBS@ + diff --git a/src/protocols/telnet/client.c b/src/protocols/telnet/client.c new file mode 100644 index 00000000..92754733 --- /dev/null +++ b/src/protocols/telnet/client.c @@ -0,0 +1,150 @@ +/* + * 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 "clipboard.h" +#include "guac_handlers.h" +#include "telnet_client.h" +#include "terminal.h" + +#include +#include +#include +#include + +#include +#include +#include + +#define GUAC_TELNET_DEFAULT_FONT_NAME "monospace" +#define GUAC_TELNET_DEFAULT_FONT_SIZE 12 +#define GUAC_TELNET_DEFAULT_PORT "23" + +/* Client plugin arguments */ +const char* GUAC_CLIENT_ARGS[] = { + "hostname", + "port", + "font-name", + "font-size", + 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 font to use within the terminal. + */ + IDX_FONT_NAME, + + /** + * The size of the font to use within the terminal, in points. + */ + IDX_FONT_SIZE, + + TELNET_ARGS_COUNT +}; + +int guac_client_init(guac_client* client, int argc, char** argv) { + + guac_socket* socket = client->socket; + + telnet_client_data* client_data = malloc(sizeof(telnet_client_data)); + + /* Init client data */ + client->data = client_data; + client_data->telnet = NULL; + client_data->socket_fd = -1; + + if (argc != TELNET_ARGS_COUNT) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Wrong number of arguments"); + return -1; + } + + /* Read parameters */ + strcpy(client_data->hostname, argv[IDX_HOSTNAME]); + + /* 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); + + /* Fail if terminal init failed */ + if (client_data->term == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Terminal initialization failed"); + return -1; + } + + /* Send initial name */ + guac_protocol_send_name(socket, client_data->hostname); + + guac_socket_flush(socket); + + /* Set basic handlers */ + client->handle_messages = telnet_guac_client_handle_messages; + client->key_handler = telnet_guac_client_key_handler; + client->mouse_handler = telnet_guac_client_mouse_handler; + client->size_handler = telnet_guac_client_size_handler; + client->free_handler = telnet_guac_client_free_handler; + client->clipboard_handler = guac_telnet_clipboard_handler; + + /* Start client thread */ + if (pthread_create(&(client_data->client_thread), NULL, telnet_client_thread, (void*) client)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start telnet client thread"); + return -1; + } + + /* Success */ + return 0; + +} + diff --git a/src/protocols/telnet/client.h b/src/protocols/telnet/client.h new file mode 100644 index 00000000..9b002f87 --- /dev/null +++ b/src/protocols/telnet/client.h @@ -0,0 +1,83 @@ +/* + * 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. + */ + + +#ifndef _TELNET_GUAC_CLIENT_H +#define _TELNET_GUAC_CLIENT_H + +#include "config.h" + +#include "terminal.h" + +#include +#include + +/** + * Telnet-specific client data. + */ +typedef struct telnet_client_data { + + /** + * 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 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; + + /** + * The terminal which will render all output from the telnet client. + */ + guac_terminal* term; + +} telnet_client_data; + +#endif + diff --git a/src/protocols/telnet/clipboard.c b/src/protocols/telnet/clipboard.c new file mode 100644 index 00000000..e64f2a77 --- /dev/null +++ b/src/protocols/telnet/clipboard.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 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 "clipboard.h" + +int guac_telnet_clipboard_handler(guac_client* client, guac_stream* stream, + char* mimetype) { + + /* Clear clipboard and prepare for new data */ + telnet_client_data* client_data = (telnet_client_data*) client->data; + guac_terminal_clipboard_reset(client_data->term, mimetype); + + /* Set handlers for clipboard stream */ + stream->blob_handler = guac_telnet_clipboard_blob_handler; + stream->end_handler = guac_telnet_clipboard_end_handler; + + return 0; +} + +int guac_telnet_clipboard_blob_handler(guac_client* client, guac_stream* stream, + void* data, int length) { + + /* Append new data */ + telnet_client_data* client_data = (telnet_client_data*) client->data; + guac_terminal_clipboard_append(client_data->term, data, length); + + return 0; +} + +int guac_telnet_clipboard_end_handler(guac_client* client, guac_stream* stream) { + + /* Nothing to do - clipboard is implemented within client */ + + return 0; +} + diff --git a/src/protocols/telnet/clipboard.h b/src/protocols/telnet/clipboard.h new file mode 100644 index 00000000..6de7c5c4 --- /dev/null +++ b/src/protocols/telnet/clipboard.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2014 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_CLIPBOARD_H +#define _GUAC_TELNET_CLIPBOARD_H + +#include "config.h" + +#include +#include + +/** + * Handler for inbound clipboard data. + */ +int guac_telnet_clipboard_handler(guac_client* client, guac_stream* stream, + char* mimetype); + +/** + * Handler for stream data related to clipboard. + */ +int guac_telnet_clipboard_blob_handler(guac_client* client, guac_stream* stream, + void* data, int length); + +/** + * Handler for end-of-stream related to clipboard. + */ +int guac_telnet_clipboard_end_handler(guac_client* client, guac_stream* stream); + +#endif + diff --git a/src/protocols/telnet/guac_handlers.c b/src/protocols/telnet/guac_handlers.c new file mode 100644 index 00000000..1b364ee4 --- /dev/null +++ b/src/protocols/telnet/guac_handlers.c @@ -0,0 +1,111 @@ +/* + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +int telnet_guac_client_handle_messages(guac_client* client) { + + telnet_client_data* client_data = (telnet_client_data*) client->data; + return guac_terminal_render_frame(client_data->term); + +} + +int telnet_guac_client_mouse_handler(guac_client* client, int x, int y, int mask) { + + telnet_client_data* client_data = (telnet_client_data*) client->data; + guac_terminal* term = client_data->term; + + /* Send mouse event */ + guac_terminal_send_mouse(term, x, y, mask); + return 0; + +} + +int telnet_guac_client_key_handler(guac_client* client, int keysym, int pressed) { + + telnet_client_data* client_data = (telnet_client_data*) client->data; + guac_terminal* term = client_data->term; + + /* Send key */ + guac_terminal_send_key(term, keysym, pressed); + return 0; + +} + +int telnet_guac_client_size_handler(guac_client* client, int width, int height) { + + /* STUB */ + +#if 0 + /* Get terminal */ + telnet_client_data* guac_client_data = (telnet_client_data*) client->data; + guac_terminal* terminal = guac_client_data->term; + + /* Resize terminal */ + guac_terminal_resize(terminal, width, height); + + /* Update SSH pty size if connected */ + if (guac_client_data->term_channel != NULL) + libssh2_channel_request_pty_size(guac_client_data->term_channel, + terminal->term_width, terminal->term_height); +#endif + + return 0; +} + +int telnet_guac_client_free_handler(guac_client* client) { + + telnet_client_data* guac_client_data = (telnet_client_data*) client->data; + + /* Close telnet connection */ + if (guac_client_data->socket_fd != -1) + close(guac_client_data->socket_fd); + + /* 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); + } + + guac_terminal_free(guac_client_data->term); + free(client->data); + + return 0; +} + diff --git a/src/protocols/telnet/guac_handlers.h b/src/protocols/telnet/guac_handlers.h new file mode 100644 index 00000000..ce8d8a10 --- /dev/null +++ b/src/protocols/telnet/guac_handlers.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 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 _TELNET_GUAC_HANDLERS_H +#define _TELNET_GUAC_HANDLERS_H + +#include "config.h" + +#include + +int telnet_guac_client_handle_messages(guac_client* client); +int telnet_guac_client_key_handler(guac_client* client, int keysym, int pressed); +int telnet_guac_client_mouse_handler(guac_client* client, int x, int y, int mask); +int telnet_guac_client_size_handler(guac_client* client, int width, int height); +int telnet_guac_client_free_handler(guac_client* client); + +#endif + diff --git a/src/protocols/telnet/telnet_client.c b/src/protocols/telnet/telnet_client.c new file mode 100644 index 00000000..2a322d11 --- /dev/null +++ b/src/protocols/telnet/telnet_client.c @@ -0,0 +1,258 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static const telnet_telopt_t __telnet_options[] = { + { TELNET_TELOPT_ECHO, TELNET_WONT, TELNET_DONT }, + { TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DONT }, + { TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DO }, + { TELNET_TELOPT_MSSP, TELNET_WONT, TELNET_DO }, + { -1, 0, 0 } +}; + +static int __write_all(int fd, const char* buffer, int size) { + + int remaining = size; + while (remaining > 0) { + + /* Attempt to write data */ + int ret_val = write(fd, buffer, remaining); + if (ret_val <= 0) + return -1; + + /* If successful, contine with what data remains (if any) */ + remaining -= ret_val; + buffer += ret_val; + + } + + return size; + +} + +static void __guac_telnet_event_handler(telnet_t* telnet, telnet_event_t* event, void* data) { + + guac_client* client = (guac_client*) data; + telnet_client_data* client_data = (telnet_client_data*) client->data; + + switch (event->type) { + + /* User input received */ + case TELNET_EV_DATA: + guac_terminal_write_stdout(client_data->term, event->data.buffer, event->data.size); + break; + + /* Data destined for remote end */ + case TELNET_EV_SEND: + if (__write_all(client_data->socket_fd, event->data.buffer, event->data.size) != event->data.size) + guac_client_stop(client); + break; + + /* Terminal type request */ + case TELNET_EV_TTYPE: + if (event->ttype.cmd == TELNET_TTYPE_SEND) + telnet_ttype_is(client_data->telnet, "linux"); + break; + + /* Connection warnings */ + case TELNET_EV_WARNING: + guac_client_log_info(client, "%s", event->error.msg); + break; + + /* Connection errors */ + case TELNET_EV_ERROR: + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + "Telnet connection closing with error: %s", event->error.msg); + break; + + /* Ignore other events */ + default: + break; + + } + +} + +void* telnet_input_thread(void* data) { + + guac_client* client = (guac_client*) data; + telnet_client_data* client_data = (telnet_client_data*) client->data; + + char buffer[8192]; + int bytes_read; + + /* Write all data read */ + while ((bytes_read = guac_terminal_read_stdin(client_data->term, buffer, sizeof(buffer))) > 0) + telnet_send(client_data->telnet, buffer, bytes_read); + + return NULL; + +} + +static telnet_t* __guac_telnet_create_session(guac_client* client) { + + int retval; + + int fd; + struct addrinfo* addresses; + struct addrinfo* current_address; + + char connected_address[1024]; + char connected_port[64]; + + telnet_client_data* client_data = (telnet_client_data*) client->data; + + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP + }; + + /* Get socket */ + fd = socket(AF_INET, SOCK_STREAM, 0); + + /* Get addresses connection */ + if ((retval = getaddrinfo(client_data->hostname, client_data->port, + &hints, &addresses))) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error parsing given address or port: %s", + gai_strerror(retval)); + return NULL; + + } + + /* Attempt connection to each address until success */ + current_address = addresses; + while (current_address != NULL) { + + int retval; + + /* Resolve hostname */ + if ((retval = getnameinfo(current_address->ai_addr, + current_address->ai_addrlen, + connected_address, sizeof(connected_address), + connected_port, sizeof(connected_port), + NI_NUMERICHOST | NI_NUMERICSERV))) + guac_client_log_info(client, "Unable to resolve host: %s", gai_strerror(retval)); + + /* Connect */ + if (connect(fd, current_address->ai_addr, + current_address->ai_addrlen) == 0) { + + guac_client_log_info(client, "Successfully connected to " + "host %s, port %s", connected_address, connected_port); + + /* Done if successful connect */ + break; + + } + + /* Otherwise log information regarding bind failure */ + else + guac_client_log_info(client, "Unable to connect to " + "host %s, port %s: %s", + connected_address, connected_port, strerror(errno)); + + current_address = current_address->ai_next; + + } + + /* If unable to connect to anything, fail */ + if (current_address == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to connect to any addresses."); + return NULL; + } + + /* Free addrinfo */ + freeaddrinfo(addresses); + + /* Open telnet session */ + telnet_t* telnet = telnet_init(__telnet_options, __guac_telnet_event_handler, 0, client); + if (telnet == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Telnet client allocation failed."); + return NULL; + } + + /* Save file descriptor */ + client_data->socket_fd = fd; + + return telnet; + +} + +void* telnet_client_thread(void* data) { + + guac_client* client = (guac_client*) data; + telnet_client_data* client_data = (telnet_client_data*) client->data; + + pthread_t input_thread; + char buffer[8192]; + int bytes_read; + + /* Open telnet session */ + client_data->telnet = __guac_telnet_create_session(client); + if (client_data->telnet == NULL) { + /* Already aborted within __guac_telnet_create_session() */ + return NULL; + } + + /* Logged in */ + guac_client_log_info(client, "Telnet connection successful."); + + /* Start input thread */ + if (pthread_create(&(input_thread), NULL, telnet_input_thread, (void*) client)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start input thread"); + return NULL; + } + + /* While data available, write to terminal */ + while ((bytes_read = read(client_data->socket_fd, buffer, sizeof(buffer))) > 0) + telnet_recv(client_data->telnet, buffer, bytes_read); + + /* Kill client and Wait for input thread to die */ + guac_client_stop(client); + pthread_join(input_thread, NULL); + + guac_client_log_info(client, "Telnet connection ended."); + return NULL; + +} + diff --git a/src/protocols/telnet/telnet_client.h b/src/protocols/telnet/telnet_client.h new file mode 100644 index 00000000..bf5823a4 --- /dev/null +++ b/src/protocols/telnet/telnet_client.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + + +#ifndef __TELNET_CLIENT_H +#define __TELNET_CLIENT_H + +#include "config.h" + +#include + +/** + * Main telnet client thread, handling transfer of telnet output to STDOUT. + */ +void* telnet_client_thread(void* data); + +#endif +