diff --git a/protocols/ssh/Makefile.am b/protocols/ssh/Makefile.am index c728c364..015802a6 100644 --- a/protocols/ssh/Makefile.am +++ b/protocols/ssh/Makefile.am @@ -40,7 +40,7 @@ ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = libguac-client-ssh.la -libguac_client_ssh_la_SOURCES = src/ssh_client.c +libguac_client_ssh_la_SOURCES = src/ssh_client.c src/ssh_handlers.c src/ssh_terminal.c libguac_client_ssh_la_CFLAGS = -Werror -Wall -pedantic -Iinclude @PANGO_CFLAGS@ @PANGOCAIRO_CFLAGS@ libguac_client_ssh_la_LIBADD = @PANGO_LIBS@ @PANGOCAIRO_LIBS@ diff --git a/protocols/ssh/include/ssh_client.h b/protocols/ssh/include/ssh_client.h new file mode 100644 index 00000000..326140e5 --- /dev/null +++ b/protocols/ssh/include/ssh_client.h @@ -0,0 +1,70 @@ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is libguac-client-ssh. + * + * The Initial Developer of the Original Code is + * Michael Jumper. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef _SSH_GUAC_CLIENT_H +#define _SSH_GUAC_CLIENT_H + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include "ssh_client.h" +#include "ssh_handlers.h" +#include "ssh_terminal.h" + +typedef struct ssh_guac_client_data { + + ssh_session session; + ssh_channel term_channel; + + ssh_guac_terminal* term; + +} ssh_guac_client_data; + +int ssh_guac_client_auth(guac_client* client, const char* password); + +#endif + diff --git a/protocols/ssh/include/ssh_handlers.h b/protocols/ssh/include/ssh_handlers.h new file mode 100644 index 00000000..379a4043 --- /dev/null +++ b/protocols/ssh/include/ssh_handlers.h @@ -0,0 +1,47 @@ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is libguac-client-ssh. + * + * The Initial Developer of the Original Code is + * Michael Jumper. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef _SSH_GUAC_HANDLERS_H +#define _SSH_GUAC_HANDLERS_H + +#include + +int ssh_guac_client_handle_messages(guac_client* client); +int ssh_guac_client_key_handler(guac_client* client, int keysym, int pressed); + +#endif + diff --git a/protocols/ssh/include/ssh_terminal.h b/protocols/ssh/include/ssh_terminal.h new file mode 100644 index 00000000..ab96aee4 --- /dev/null +++ b/protocols/ssh/include/ssh_terminal.h @@ -0,0 +1,89 @@ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is libguac-client-ssh. + * + * The Initial Developer of the Original Code is + * Michael Jumper. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef _SSH_GUAC_TERMINAL_H +#define _SSH_GUAC_TERMINAL_H + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#define SSH_TERM_STATE_NULL 0 +#define SSH_TERM_STATE_ECHO 1 +#define SSH_TERM_STATE_ESC 2 +#define SSH_TERM_STATE_CSI 3 +#define SSH_TERM_STATE_OSC 4 +#define SSH_TERM_STATE_CHARSET 5 + +typedef struct ssh_guac_terminal { + + PangoFontDescription* font_desc; + + guac_client* client; + guac_layer* glyphs[256]; + + int char_width; + int char_height; + + int term_width; + int term_height; + int term_state; + + int term_seq_argc; + int term_seq_argv[16]; + char term_seq_argv_buffer[16]; + int term_seq_argv_buffer_current; + + int cursor_row; + int cursor_col; + +} ssh_guac_terminal; + +ssh_guac_terminal* ssh_guac_terminal_create(guac_client* client); +void ssh_guac_terminal_free(ssh_guac_terminal* term); + +int ssh_guac_terminal_write(ssh_guac_terminal* term, const char* c, int size); + +#endif + diff --git a/protocols/ssh/src/ssh_client.c b/protocols/ssh/src/ssh_client.c index 91749e21..4c992a44 100644 --- a/protocols/ssh/src/ssh_client.c +++ b/protocols/ssh/src/ssh_client.c @@ -40,14 +40,15 @@ #include -#include -#include - #include #include #include #include +#include "ssh_client.h" +#include "ssh_handlers.h" +#include "ssh_terminal.h" + #define SSH_TERM_STATE_NULL 0 #define SSH_TERM_STATE_ECHO 1 #define SSH_TERM_STATE_ESC 2 @@ -63,32 +64,6 @@ const char* GUAC_CLIENT_ARGS[] = { NULL }; -typedef struct ssh_guac_client_data { - - ssh_session session; - ssh_channel term_channel; - - PangoFontDescription* font_desc; - - guac_layer* glyphs[256]; - - int char_width; - int char_height; - - int term_width; - int term_height; - int term_state; - - int term_seq_argc; - int term_seq_argv[16]; - char term_seq_argv_buffer[16]; - int term_seq_argv_buffer_current; - - int cursor_row; - int cursor_col; - -} ssh_guac_client_data; - int ssh_guac_client_handle_messages(guac_client* client); int ssh_guac_client_key_handler(guac_client* client, int keysym, int pressed); int ssh_guac_client_send_glyph(guac_client* client, int row, int col, char c); @@ -98,55 +73,18 @@ int guac_client_init(guac_client* client, int argc, char** argv) { GUACIO* io = client->io; - PangoFontMap* font_map; - PangoFont* font; - PangoFontMetrics* metrics; - PangoContext* context; - ssh_guac_client_data* client_data = malloc(sizeof(ssh_guac_client_data)); + ssh_guac_terminal* term = ssh_guac_terminal_create(client); - client_data->cursor_row = 0; - client_data->cursor_col = 0; - - client_data->term_width = 160; - client_data->term_height = 50; - client_data->term_state = SSH_TERM_STATE_ECHO; - - /* Get font */ - client_data->font_desc = pango_font_description_new(); - pango_font_description_set_family(client_data->font_desc, "monospace"); - pango_font_description_set_weight(client_data->font_desc, PANGO_WEIGHT_NORMAL); - pango_font_description_set_size(client_data->font_desc, 8*PANGO_SCALE); - - font_map = pango_cairo_font_map_get_default(); - context = pango_font_map_create_context(font_map); - - font = pango_font_map_load_font(font_map, context, client_data->font_desc); - if (font == NULL) { - guac_log_error("Unable to get font."); - return 1; - } - - metrics = pango_font_get_metrics(font, NULL); - if (metrics == NULL) { - guac_log_error("Unable to get font metrics."); - return 1; - } - - /* Calculate character dimensions */ - client_data->char_width = - pango_font_metrics_get_approximate_digit_width(metrics) / PANGO_SCALE; - client_data->char_height = - (pango_font_metrics_get_descent(metrics) - + pango_font_metrics_get_ascent(metrics)) / PANGO_SCALE; - + /* Init client data */ client->data = client_data; + client_data->term = term; /* Send name and dimensions */ guac_send_name(io, "SSH TEST"); guac_send_size(io, - client_data->char_width * client_data->term_width, - client_data->char_height * client_data->term_height); + term->char_width * term->term_width, + term->char_height * term->term_height); guac_flush(io); @@ -169,8 +107,27 @@ int guac_client_init(guac_client* client, int argc, char** argv) { return 1; } + /* If password provided, authenticate now */ + if (argv[2][0] != '\0') + return ssh_guac_client_auth(client, argv[2]); + + /* Otherwise, prompt for password */ + else { + } + + /* Success */ + return 0; + +} + +int ssh_guac_client_auth(guac_client* client, const char* password) { + + GUACIO* io = client->io; + ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; + ssh_guac_terminal* term = client_data->term; + /* Authenticate */ - if (ssh_userauth_password(client_data->session, NULL, argv[2]) != SSH_AUTH_SUCCESS) { + if (ssh_userauth_password(client_data->session, NULL, password) != SSH_AUTH_SUCCESS) { guac_send_error(io, "SSH auth failed."); guac_flush(io); return 1; @@ -199,7 +156,7 @@ int guac_client_init(guac_client* client, int argc, char** argv) { } /* Request PTY size */ - if (channel_change_pty_size(client_data->term_channel, client_data->term_width, client_data->term_height) != SSH_OK) { + if (channel_change_pty_size(client_data->term_channel, term->term_width, term->term_height) != SSH_OK) { guac_send_error(io, "Unable to change PTY size."); guac_flush(io); return 1; @@ -223,419 +180,4 @@ int guac_client_init(guac_client* client, int argc, char** argv) { } -guac_layer* ssh_guac_client_get_glyph(guac_client* client, char c) { - GUACIO* io = client->io; - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - guac_layer* glyph; - - cairo_surface_t* surface; - cairo_t* cairo; - - PangoLayout* layout; - - /* Return glyph if exists */ - if (client_data->glyphs[(int) c]) - return client_data->glyphs[(int) c]; - - /* Otherwise, draw glyph */ - surface = cairo_image_surface_create( - CAIRO_FORMAT_ARGB32, - client_data->char_width, client_data->char_height); - cairo = cairo_create(surface); - - /* Get layout */ - layout = pango_cairo_create_layout(cairo); - pango_layout_set_font_description(layout, client_data->font_desc); - pango_layout_set_text(layout, &c, 1); - - /* Draw */ - cairo_set_source_rgba(cairo, 1.0, 1.0, 1.0, 1.0); - cairo_move_to(cairo, 0.0, 0.0); - pango_cairo_show_layout(cairo, layout); - - /* Free all */ - g_object_unref(layout); - cairo_destroy(cairo); - - /* Send glyph and save */ - glyph = guac_client_alloc_buffer(client); - guac_send_png(io, GUAC_COMP_OVER, glyph, 0, 0, surface); - client_data->glyphs[(int) c] = glyph; - - guac_flush(io); - cairo_surface_destroy(surface); - - /* Return glyph */ - return glyph; - -} - -int ssh_guac_client_handle_messages(guac_client* client) { - - GUACIO* io = client->io; - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - char buffer[8192]; - - /* While data available, write to terminal */ - int bytes_read = 0; - while (channel_is_open(client_data->term_channel) - && !channel_is_eof(client_data->term_channel) - && (bytes_read = channel_read_nonblocking(client_data->term_channel, buffer, sizeof(buffer), 0)) > 0) { - - ssh_guac_client_write(client, buffer, bytes_read); - guac_flush(io); - - } - - /* Notify on error */ - if (bytes_read < 0) { - guac_send_error(io, "Error reading data."); - guac_flush(io); - return 1; - } - - return 0; - -} - -int ssh_guac_client_send_glyph(guac_client* client, int row, int col, char c) { - - GUACIO* io = client->io; - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - guac_layer* glyph = ssh_guac_client_get_glyph(client, c); - - return guac_send_copy(io, - glyph, 0, 0, client_data->char_width, client_data->char_height, - GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, - client_data->char_width * col, - client_data->char_height * row); - -} - -int ssh_guac_client_write(guac_client* client, const char* c, int size) { - - GUACIO* io = client->io; - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - - while (size > 0) { - - switch (client_data->term_state) { - - case SSH_TERM_STATE_NULL: - break; - - case SSH_TERM_STATE_ECHO: - - /* Wrap if necessary */ - if (client_data->cursor_col >= client_data->term_width) { - client_data->cursor_col = 0; - client_data->cursor_row++; - } - - /* Scroll up if necessary */ - if (client_data->cursor_row >= client_data->term_height) { - client_data->cursor_row = client_data->term_height - 1; - - /* Copy screen up by one row */ - guac_send_copy(io, - GUAC_DEFAULT_LAYER, 0, client_data->char_height, - client_data->char_width * client_data->term_width, - client_data->char_height * (client_data->term_height - 1), - GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, 0, 0); - - /* Fill bottom row with background */ - guac_send_rect(io, - GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, - 0, client_data->char_height * (client_data->term_height - 1), - client_data->char_width * client_data->term_width, - client_data->char_height * client_data->term_height, - 0, 0, 0, 255); - - } - - - - switch (*c) { - - /* Bell */ - case 0x07: - break; - - /* Backspace */ - case 0x08: - if (client_data->cursor_col >= 1) - client_data->cursor_col--; - break; - - /* Carriage return */ - case '\r': - client_data->cursor_col = 0; - break; - - /* Line feed */ - case '\n': - client_data->cursor_row++; - break; - - /* ESC */ - case 0x1B: - client_data->term_state = SSH_TERM_STATE_ESC; - break; - - /* Displayable chars */ - default: - ssh_guac_client_send_glyph(client, - client_data->cursor_row, - client_data->cursor_col, - *c); - - /* Advance cursor */ - client_data->cursor_col++; - } - - /* End of SSH_TERM_STATE_ECHO */ - break; - - case SSH_TERM_STATE_CHARSET: - client_data->term_state = SSH_TERM_STATE_ECHO; - break; - - case SSH_TERM_STATE_ESC: - - switch (*c) { - - case '(': - client_data->term_state = SSH_TERM_STATE_CHARSET; - break; - - case ']': - client_data->term_state = SSH_TERM_STATE_OSC; - client_data->term_seq_argc = 0; - client_data->term_seq_argv_buffer_current = 0; - break; - - case '[': - client_data->term_state = SSH_TERM_STATE_CSI; - client_data->term_seq_argc = 0; - client_data->term_seq_argv_buffer_current = 0; - break; - - default: - guac_log_info("Unhandled ESC sequence: %c", *c); - client_data->term_state = SSH_TERM_STATE_ECHO; - - } - - /* End of SSH_TERM_STATE_ESC */ - break; - - case SSH_TERM_STATE_OSC: - - /* TODO: Implement OSC */ - if (*c == 0x9C || *c == 0x5C || *c == 0x07) /* ECMA-48 ST (String Terminator */ - client_data->term_state = SSH_TERM_STATE_ECHO; - - /* End of SSH_TERM_STATE_OSC */ - break; - - case SSH_TERM_STATE_CSI: - - /* FIXME: "The sequence of parameters may be preceded by a single question mark. */ - if (*c == '?') - break; /* Ignore question marks for now... */ - - /* Digits get concatenated into argv */ - if (*c >= '0' && *c <= '9') { - - /* Concatenate digit if there is space in buffer */ - if (client_data->term_seq_argv_buffer_current < - sizeof(client_data->term_seq_argv_buffer)) { - - client_data->term_seq_argv_buffer[ - client_data->term_seq_argv_buffer_current++ - ] = *c; - } - - } - - /* Any non-digit stops the parameter, and possibly the sequence */ - else { - - /* At most 16 parameters */ - if (client_data->term_seq_argc < 16) { - /* Finish parameter */ - client_data->term_seq_argv_buffer[client_data->term_seq_argv_buffer_current] = 0; - client_data->term_seq_argv[client_data->term_seq_argc++] = - atoi(client_data->term_seq_argv_buffer); - - /* Prepare for next parameter */ - client_data->term_seq_argv_buffer_current = 0; - } - - /* Handle CSI functions */ - switch (*c) { - - /* H: Move cursor */ - case 'H': - client_data->cursor_row = client_data->term_seq_argv[0] - 1; - client_data->cursor_col = client_data->term_seq_argv[1] - 1; - break; - - /* J: Erase display */ - case 'J': - - /* Erase from cursor to end of display */ - if (client_data->term_seq_argv[0] == 0) { - - /* Until end of line */ - guac_send_rect(io, - GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, - client_data->cursor_col * client_data->char_width, - client_data->cursor_row * client_data->char_height, - (client_data->term_width - client_data->cursor_col) * client_data->char_width, - client_data->char_height, - 0, 0, 0, 255); /* Background color */ - - /* Until end of display */ - if (client_data->cursor_row < client_data->term_height - 1) { - guac_send_rect(io, - GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, - 0, - (client_data->cursor_row+1) * client_data->char_height, - client_data->term_width * client_data->char_width, - client_data->term_height * client_data->char_height, - 0, 0, 0, 255); /* Background color */ - } - - } - - /* Erase from start to cursor */ - else if (client_data->term_seq_argv[0] == 1) { - - /* Until start of line */ - guac_send_rect(io, - GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, - 0, - client_data->cursor_row * client_data->char_height, - client_data->cursor_col * client_data->char_width, - client_data->char_height, - 0, 0, 0, 255); /* Background color */ - - /* From start */ - if (client_data->cursor_row >= 1) { - guac_send_rect(io, - GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, - 0, - 0, - client_data->term_width * client_data->char_width, - (client_data->cursor_row-1) * client_data->char_height, - 0, 0, 0, 255); /* Background color */ - } - - } - - /* Entire screen */ - else if (client_data->term_seq_argv[0] == 2) { - guac_send_rect(io, - GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, - 0, - 0, - client_data->term_width * client_data->char_width, - client_data->term_height * client_data->char_height, - 0, 0, 0, 255); /* Background color */ - } - - break; - - /* K: Erase line */ - case 'K': - - /* Erase from cursor to end of line */ - if (client_data->term_seq_argv[0] == 0) { - guac_send_rect(io, - GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, - client_data->cursor_col * client_data->char_width, - client_data->cursor_row * client_data->char_height, - (client_data->term_width - client_data->cursor_col) * client_data->char_width, - client_data->char_height, - 0, 0, 0, 255); /* Background color */ - } - - /* Erase from start to cursor */ - else if (client_data->term_seq_argv[0] == 1) { - guac_send_rect(io, - GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, - 0, - client_data->cursor_row * client_data->char_height, - client_data->cursor_col * client_data->char_width, - client_data->char_height, - 0, 0, 0, 255); /* Background color */ - } - - /* Erase line */ - else if (client_data->term_seq_argv[0] == 2) { - guac_send_rect(io, - GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, - 0, - client_data->cursor_row * client_data->char_height, - client_data->term_width * client_data->char_width, - client_data->char_height, - 0, 0, 0, 255); /* Background color */ - } - - break; - - /* Warn of unhandled codes */ - default: - if (*c != ';') - guac_log_info("Unhandled CSI sequence: %c", *c); - - } - - /* If not a semicolon, end of CSI sequence */ - if (*c != ';') - client_data->term_state = SSH_TERM_STATE_ECHO; - - } - - /* End of SSH_TERM_STATE_CSI */ - break; - - } - - c++; - size--; - } - - return 0; - -} - -int ssh_guac_client_key_handler(guac_client* client, int keysym, int pressed) { - - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - - /* If key pressed */ - if (pressed) { - - char data; - - /* If simple ASCII key */ - if (keysym >= 0x00 && keysym <= 0xFF) - data = (char) keysym; - - else if (keysym == 0xFF08) data = 0x08; - else if (keysym == 0xFF09) data = 0x09; - else if (keysym == 0xFF0D) data = 0x0D; - - else - return 0; - - return channel_write(client_data->term_channel, &data, 1); - - } - - return 0; - -} diff --git a/protocols/ssh/src/ssh_handlers.c b/protocols/ssh/src/ssh_handlers.c new file mode 100644 index 00000000..ddb1b939 --- /dev/null +++ b/protocols/ssh/src/ssh_handlers.c @@ -0,0 +1,125 @@ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is libguac-client-ssh. + * + * The Initial Developer of the Original Code is + * Michael Jumper. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include "ssh_handlers.h" +#include "ssh_client.h" + +int ssh_guac_client_handle_messages(guac_client* client) { + + GUACIO* io = client->io; + ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; + char buffer[8192]; + + ssh_channel read_channels[2]; + struct timeval timeout; + + /* Channels to read */ + read_channels[0] = client_data->term_channel; + read_channels[1] = NULL; + + /* Time to wait */ + timeout.tv_sec = GUAC_SYNC_FREQUENCY / 1000; + timeout.tv_usec = (GUAC_SYNC_FREQUENCY % 1000) * 1000; + + /* Wait for data to be available */ + if (channel_select(read_channels, NULL, NULL, &timeout) == SSH_OK) { + + int bytes_read = 0; + + /* While data available, write to terminal */ + while (channel_is_open(client_data->term_channel) + && !channel_is_eof(client_data->term_channel) + && (bytes_read = channel_read_nonblocking(client_data->term_channel, buffer, sizeof(buffer), 0)) > 0) { + + ssh_guac_terminal_write(client_data->term, buffer, bytes_read); + guac_flush(io); + + } + + /* Notify on error */ + if (bytes_read < 0) { + guac_send_error(io, "Error reading data."); + guac_flush(io); + return 1; + } + } + + return 0; + +} + +int ssh_guac_client_key_handler(guac_client* client, int keysym, int pressed) { + + ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; + + /* If key pressed */ + if (pressed) { + + char data; + + /* If simple ASCII key */ + if (keysym >= 0x00 && keysym <= 0xFF) + data = (char) keysym; + + else if (keysym == 0xFF08) data = 0x08; + else if (keysym == 0xFF09) data = 0x09; + else if (keysym == 0xFF0D) data = 0x0D; + + else + return 0; + + return channel_write(client_data->term_channel, &data, 1); + + } + + return 0; + +} + diff --git a/protocols/ssh/src/ssh_terminal.c b/protocols/ssh/src/ssh_terminal.c new file mode 100644 index 00000000..d55a0383 --- /dev/null +++ b/protocols/ssh/src/ssh_terminal.c @@ -0,0 +1,461 @@ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is libguac-client-ssh. + * + * The Initial Developer of the Original Code is + * Michael Jumper. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "ssh_terminal.h" + +ssh_guac_terminal* ssh_guac_terminal_create(guac_client* client) { + + PangoFontMap* font_map; + PangoFont* font; + PangoFontMetrics* metrics; + PangoContext* context; + + ssh_guac_terminal* term = malloc(sizeof(ssh_guac_terminal)); + term->client = client; + + term->cursor_row = 0; + term->cursor_col = 0; + + term->term_width = 160; + term->term_height = 50; + term->term_state = SSH_TERM_STATE_ECHO; + + /* Get font */ + term->font_desc = pango_font_description_new(); + pango_font_description_set_family(term->font_desc, "monospace"); + pango_font_description_set_weight(term->font_desc, PANGO_WEIGHT_NORMAL); + pango_font_description_set_size(term->font_desc, 8*PANGO_SCALE); + + font_map = pango_cairo_font_map_get_default(); + context = pango_font_map_create_context(font_map); + + font = pango_font_map_load_font(font_map, context, term->font_desc); + if (font == NULL) { + guac_log_error("Unable to get font."); + return NULL; + } + + metrics = pango_font_get_metrics(font, NULL); + if (metrics == NULL) { + guac_log_error("Unable to get font metrics."); + return NULL; + } + + /* Calculate character dimensions */ + term->char_width = + pango_font_metrics_get_approximate_digit_width(metrics) / PANGO_SCALE; + term->char_height = + (pango_font_metrics_get_descent(metrics) + + pango_font_metrics_get_ascent(metrics)) / PANGO_SCALE; + + return term; + +} + +void ssh_guac_terminal_free(ssh_guac_terminal* term) { + /* STUB */ +} + +guac_layer* __ssh_guac_terminal_get_glyph(ssh_guac_terminal* term, char c) { + + GUACIO* io = term->client->io; + guac_layer* glyph; + + cairo_surface_t* surface; + cairo_t* cairo; + + PangoLayout* layout; + + /* Return glyph if exists */ + if (term->glyphs[(int) c]) + return term->glyphs[(int) c]; + + /* Otherwise, draw glyph */ + surface = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, + term->char_width, term->char_height); + cairo = cairo_create(surface); + + /* Get layout */ + layout = pango_cairo_create_layout(cairo); + pango_layout_set_font_description(layout, term->font_desc); + pango_layout_set_text(layout, &c, 1); + + /* Draw */ + cairo_set_source_rgba(cairo, 1.0, 1.0, 1.0, 1.0); + cairo_move_to(cairo, 0.0, 0.0); + pango_cairo_show_layout(cairo, layout); + + /* Free all */ + g_object_unref(layout); + cairo_destroy(cairo); + + /* Send glyph and save */ + glyph = guac_client_alloc_buffer(term->client); + guac_send_png(io, GUAC_COMP_OVER, glyph, 0, 0, surface); + term->glyphs[(int) c] = glyph; + + guac_flush(io); + cairo_surface_destroy(surface); + + /* Return glyph */ + return glyph; + +} + +int __ssh_guac_terminal_send_glyph(ssh_guac_terminal* term, int row, int col, char c) { + + GUACIO* io = term->client->io; + guac_layer* glyph = __ssh_guac_terminal_get_glyph(term, c); + + return guac_send_copy(io, + glyph, 0, 0, term->char_width, term->char_height, + GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, + term->char_width * col, + term->char_height * row); + +} + +int ssh_guac_terminal_write(ssh_guac_terminal* term, const char* c, int size) { + + GUACIO* io = term->client->io; + + while (size > 0) { + + switch (term->term_state) { + + case SSH_TERM_STATE_NULL: + break; + + case SSH_TERM_STATE_ECHO: + + /* Wrap if necessary */ + if (term->cursor_col >= term->term_width) { + term->cursor_col = 0; + term->cursor_row++; + } + + /* Scroll up if necessary */ + if (term->cursor_row >= term->term_height) { + term->cursor_row = term->term_height - 1; + + /* Copy screen up by one row */ + guac_send_copy(io, + GUAC_DEFAULT_LAYER, 0, term->char_height, + term->char_width * term->term_width, + term->char_height * (term->term_height - 1), + GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, 0, 0); + + /* Fill bottom row with background */ + guac_send_rect(io, + GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, + 0, term->char_height * (term->term_height - 1), + term->char_width * term->term_width, + term->char_height * term->term_height, + 0, 0, 0, 255); + + } + + + + switch (*c) { + + /* Bell */ + case 0x07: + break; + + /* Backspace */ + case 0x08: + if (term->cursor_col >= 1) + term->cursor_col--; + break; + + /* Carriage return */ + case '\r': + term->cursor_col = 0; + break; + + /* Line feed */ + case '\n': + term->cursor_row++; + break; + + /* ESC */ + case 0x1B: + term->term_state = SSH_TERM_STATE_ESC; + break; + + /* Displayable chars */ + default: + __ssh_guac_terminal_send_glyph(term, + term->cursor_row, + term->cursor_col, + *c); + + /* Advance cursor */ + term->cursor_col++; + } + + /* End of SSH_TERM_STATE_ECHO */ + break; + + case SSH_TERM_STATE_CHARSET: + term->term_state = SSH_TERM_STATE_ECHO; + break; + + case SSH_TERM_STATE_ESC: + + switch (*c) { + + case '(': + term->term_state = SSH_TERM_STATE_CHARSET; + break; + + case ']': + term->term_state = SSH_TERM_STATE_OSC; + term->term_seq_argc = 0; + term->term_seq_argv_buffer_current = 0; + break; + + case '[': + term->term_state = SSH_TERM_STATE_CSI; + term->term_seq_argc = 0; + term->term_seq_argv_buffer_current = 0; + break; + + default: + guac_log_info("Unhandled ESC sequence: %c", *c); + term->term_state = SSH_TERM_STATE_ECHO; + + } + + /* End of SSH_TERM_STATE_ESC */ + break; + + case SSH_TERM_STATE_OSC: + + /* TODO: Implement OSC */ + if (*c == 0x9C || *c == 0x5C || *c == 0x07) /* ECMA-48 ST (String Terminator */ + term->term_state = SSH_TERM_STATE_ECHO; + + /* End of SSH_TERM_STATE_OSC */ + break; + + case SSH_TERM_STATE_CSI: + + /* FIXME: "The sequence of parameters may be preceded by a single question mark. */ + if (*c == '?') + break; /* Ignore question marks for now... */ + + /* Digits get concatenated into argv */ + if (*c >= '0' && *c <= '9') { + + /* Concatenate digit if there is space in buffer */ + if (term->term_seq_argv_buffer_current < + sizeof(term->term_seq_argv_buffer)) { + + term->term_seq_argv_buffer[ + term->term_seq_argv_buffer_current++ + ] = *c; + } + + } + + /* Any non-digit stops the parameter, and possibly the sequence */ + else { + + /* At most 16 parameters */ + if (term->term_seq_argc < 16) { + /* Finish parameter */ + term->term_seq_argv_buffer[term->term_seq_argv_buffer_current] = 0; + term->term_seq_argv[term->term_seq_argc++] = + atoi(term->term_seq_argv_buffer); + + /* Prepare for next parameter */ + term->term_seq_argv_buffer_current = 0; + } + + /* Handle CSI functions */ + switch (*c) { + + /* H: Move cursor */ + case 'H': + term->cursor_row = term->term_seq_argv[0] - 1; + term->cursor_col = term->term_seq_argv[1] - 1; + break; + + /* J: Erase display */ + case 'J': + + /* Erase from cursor to end of display */ + if (term->term_seq_argv[0] == 0) { + + /* Until end of line */ + guac_send_rect(io, + GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, + term->cursor_col * term->char_width, + term->cursor_row * term->char_height, + (term->term_width - term->cursor_col) * term->char_width, + term->char_height, + 0, 0, 0, 255); /* Background color */ + + /* Until end of display */ + if (term->cursor_row < term->term_height - 1) { + guac_send_rect(io, + GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, + 0, + (term->cursor_row+1) * term->char_height, + term->term_width * term->char_width, + term->term_height * term->char_height, + 0, 0, 0, 255); /* Background color */ + } + + } + + /* Erase from start to cursor */ + else if (term->term_seq_argv[0] == 1) { + + /* Until start of line */ + guac_send_rect(io, + GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, + 0, + term->cursor_row * term->char_height, + term->cursor_col * term->char_width, + term->char_height, + 0, 0, 0, 255); /* Background color */ + + /* From start */ + if (term->cursor_row >= 1) { + guac_send_rect(io, + GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, + 0, + 0, + term->term_width * term->char_width, + (term->cursor_row-1) * term->char_height, + 0, 0, 0, 255); /* Background color */ + } + + } + + /* Entire screen */ + else if (term->term_seq_argv[0] == 2) { + guac_send_rect(io, + GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, + 0, + 0, + term->term_width * term->char_width, + term->term_height * term->char_height, + 0, 0, 0, 255); /* Background color */ + } + + break; + + /* K: Erase line */ + case 'K': + + /* Erase from cursor to end of line */ + if (term->term_seq_argv[0] == 0) { + guac_send_rect(io, + GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, + term->cursor_col * term->char_width, + term->cursor_row * term->char_height, + (term->term_width - term->cursor_col) * term->char_width, + term->char_height, + 0, 0, 0, 255); /* Background color */ + } + + /* Erase from start to cursor */ + else if (term->term_seq_argv[0] == 1) { + guac_send_rect(io, + GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, + 0, + term->cursor_row * term->char_height, + term->cursor_col * term->char_width, + term->char_height, + 0, 0, 0, 255); /* Background color */ + } + + /* Erase line */ + else if (term->term_seq_argv[0] == 2) { + guac_send_rect(io, + GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, + 0, + term->cursor_row * term->char_height, + term->term_width * term->char_width, + term->char_height, + 0, 0, 0, 255); /* Background color */ + } + + break; + + /* Warn of unhandled codes */ + default: + if (*c != ';') + guac_log_info("Unhandled CSI sequence: %c", *c); + + } + + /* If not a semicolon, end of CSI sequence */ + if (*c != ';') + term->term_state = SSH_TERM_STATE_ECHO; + + } + + /* End of SSH_TERM_STATE_CSI */ + break; + + } + + c++; + size--; + } + + return 0; + +} +