From 77cde6ae4b8420afe2e0a35c9d235aa45580e8af Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 7 Jul 2015 17:02:13 -0700 Subject: [PATCH 01/15] GUAC-1171: Stub out common SSH client within build. --- Makefile.am | 7 +++++- configure.ac | 8 +++++++ src/common-ssh/Makefile.am | 37 ++++++++++++++++++++++++++++++++ src/common-ssh/guac_ssh_config.c | 28 ++++++++++++++++++++++++ src/common-ssh/guac_ssh_config.h | 33 ++++++++++++++++++++++++++++ src/protocols/ssh/Makefile.am | 4 ++-- 6 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 src/common-ssh/Makefile.am create mode 100644 src/common-ssh/guac_ssh_config.c create mode 100644 src/common-ssh/guac_ssh_config.h diff --git a/Makefile.am b/Makefile.am index 9377e66a..234ad840 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright (C) 2013 Glyptodon LLC +# Copyright (C) 2015 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 @@ -26,6 +26,7 @@ ACLOCAL_AMFLAGS = -I m4 DIST_SUBDIRS = \ src/libguac \ src/common \ + src/common-ssh \ src/terminal \ src/guacd \ src/protocols/rdp \ @@ -40,6 +41,10 @@ SUBDIRS = \ src/guacd \ tests +if ENABLE_COMMON_SSH + SUBDIRS += src/common-ssh +endif + if ENABLE_TERMINAL SUBDIRS += src/terminal endif diff --git a/configure.ac b/configure.ac index b10f5ca8..4cc773ea 100644 --- a/configure.ac +++ b/configure.ac @@ -117,6 +117,10 @@ AC_SUBST([LIBGUAC_INCLUDE], '-I$(top_srcdir)/src/libguac') AC_SUBST([COMMON_LTLIB], '$(top_builddir)/src/common/libguac_common.la') AC_SUBST([COMMON_INCLUDE], '-I$(top_srcdir)/src/common') +# Common base SSH client +AC_SUBST([COMMON_SSH_LTLIB], '$(top_builddir)/src/common-ssh/libguac_common_ssh.la') +AC_SUBST([COMMON_SSH_INCLUDE], '-I$(top_srcdir)/src/common-ssh') + # Terminal emulator AC_SUBST([TERMINAL_LTLIB], '$(top_builddir)/src/terminal/libguac_terminal.la') AC_SUBST([TERMINAL_INCLUDE], '-I$(top_srcdir)/src/terminal $(PANGO_CFLAGS) $(PANGOCAIRO_CFLAGS) $(COMMON_INCLUDE)') @@ -834,6 +838,9 @@ then [have_libssh2=no]) fi +AM_CONDITIONAL([ENABLE_COMMON_SSH], [test "x${have_libssh2}" = "xyes" \ + -a "x${have_ssl}" = "xyes"]) + AM_CONDITIONAL([ENABLE_SSH], [test "x${have_libssh2}" = "xyes" \ -a "x${have_terminal}" = "xyes" \ -a "x${have_ssl}" = "xyes"]) @@ -920,6 +927,7 @@ AC_SUBST(TELNET_LIBS) AC_CONFIG_FILES([Makefile tests/Makefile src/common/Makefile + src/common-ssh/Makefile src/terminal/Makefile src/libguac/Makefile src/guacd/Makefile diff --git a/src/common-ssh/Makefile.am b/src/common-ssh/Makefile.am new file mode 100644 index 00000000..2720ed50 --- /dev/null +++ b/src/common-ssh/Makefile.am @@ -0,0 +1,37 @@ +# +# Copyright (C) 2015 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 +AM_CFLAGS = -Werror -Wall -pedantic @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@ + +noinst_LTLIBRARIES = libguac_common_ssh.la + +libguac_common_ssh_la_SOURCES = \ + guac_ssh_config.c + +noinst_HEADERS = \ + guac_ssh_config.h + +libguac_common_ssh_la_LIBADD = @LIBGUAC_LTLIB@ +libguac_common_ssh_la_LDFLAGS = @SSH_LIBS@ @SSL_LIBS@ @PTHREAD_LIBS@ + diff --git a/src/common-ssh/guac_ssh_config.c b/src/common-ssh/guac_ssh_config.c new file mode 100644 index 00000000..65aa76b8 --- /dev/null +++ b/src/common-ssh/guac_ssh_config.c @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 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 "guac_ssh_config.h" + +void guac_common_ssh_connect() { + /* STUB */ +} + diff --git a/src/common-ssh/guac_ssh_config.h b/src/common-ssh/guac_ssh_config.h new file mode 100644 index 00000000..7c2b6f0d --- /dev/null +++ b/src/common-ssh/guac_ssh_config.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2015 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_COMMON_SSH_CONFIG_H +#define GUAC_COMMON_SSH_CONFIG_H + +/** + * STUB + */ +void guac_common_ssh_connect(); + +#endif + diff --git a/src/protocols/ssh/Makefile.am b/src/protocols/ssh/Makefile.am index 28dd9d8f..65a7e538 100644 --- a/src/protocols/ssh/Makefile.am +++ b/src/protocols/ssh/Makefile.am @@ -50,7 +50,7 @@ libguac_client_ssh_la_SOURCES += ssh_agent.c noinst_HEADERS += ssh_agent.h endif -libguac_client_ssh_la_CFLAGS = -Werror -Wall -Iinclude @LIBGUAC_INCLUDE@ @TERMINAL_INCLUDE@ -libguac_client_ssh_la_LIBADD = @LIBGUAC_LTLIB@ @TERMINAL_LTLIB@ +libguac_client_ssh_la_CFLAGS = -Werror -Wall -Iinclude @LIBGUAC_INCLUDE@ @TERMINAL_INCLUDE@ @COMMON_SSH_INCLUDE@ +libguac_client_ssh_la_LIBADD = @LIBGUAC_LTLIB@ @TERMINAL_LTLIB@ @COMMON_SSH_LTLIB@ libguac_client_ssh_la_LDFLAGS = -version-info 0:0:0 @SSH_LIBS@ @SSL_LIBS@ @PTHREAD_LIBS@ From c199cfc8d7a2ac156afc0a5e4cc9544c3cead315 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 8 Jul 2015 15:16:40 -0700 Subject: [PATCH 02/15] GUAC-1171: Adopt cleaner style for Makefile.am. --- Makefile.am | 5 +- src/common-ssh/Makefile.am | 15 +- src/common/Makefile.am | 8 +- src/guacd/Makefile.am | 40 +++-- src/libguac/Makefile.am | 22 ++- src/protocols/rdp/Makefile.am | 254 ++++++++++++++++++++----------- src/protocols/ssh/Makefile.am | 23 ++- src/protocols/telnet/Makefile.am | 20 ++- src/protocols/vnc/Makefile.am | 24 +-- src/terminal/Makefile.am | 21 ++- tests/Makefile.am | 51 ++++--- 11 files changed, 324 insertions(+), 159 deletions(-) diff --git a/Makefile.am b/Makefile.am index 234ad840..9100f8f7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -65,5 +65,8 @@ if ENABLE_VNC SUBDIRS += src/protocols/vnc endif -EXTRA_DIST = LICENSE doc/Doxyfile bin/guacctl +EXTRA_DIST = \ + LICENSE \ + bin/guacctl \ + doc/Doxyfile diff --git a/src/common-ssh/Makefile.am b/src/common-ssh/Makefile.am index 2720ed50..33a808c4 100644 --- a/src/common-ssh/Makefile.am +++ b/src/common-ssh/Makefile.am @@ -22,7 +22,6 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 -AM_CFLAGS = -Werror -Wall -pedantic @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@ noinst_LTLIBRARIES = libguac_common_ssh.la @@ -32,6 +31,16 @@ libguac_common_ssh_la_SOURCES = \ noinst_HEADERS = \ guac_ssh_config.h -libguac_common_ssh_la_LIBADD = @LIBGUAC_LTLIB@ -libguac_common_ssh_la_LDFLAGS = @SSH_LIBS@ @SSL_LIBS@ @PTHREAD_LIBS@ +libguac_common_ssh_la_CFLAGS = \ + -Werror -Wall -pedantic \ + @COMMON_INCLUDE@ \ + @LIBGUAC_INCLUDE@ + +libguac_common_ssh_la_LIBADD = \ + @LIBGUAC_LTLIB@ + +libguac_common_ssh_la_LDFLAGS = \ + @PTHREAD_LIBS@ \ + @SSH_LIBS@ \ + @SSL_LIBS@ diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 785d0f22..4f4b02c8 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -22,7 +22,6 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 -AM_CFLAGS = -Werror -Wall -pedantic @LIBGUAC_INCLUDE@ noinst_LTLIBRARIES = libguac_common.la @@ -50,5 +49,10 @@ libguac_common_la_SOURCES = \ guac_string.c \ guac_surface.c -libguac_common_la_LIBADD = @LIBGUAC_LTLIB@ +libguac_common_la_CFLAGS = \ + -Werror -Wall -pedantic \ + @LIBGUAC_INCLUDE@ + +libguac_common_la_LIBADD = \ + @LIBGUAC_LTLIB@ diff --git a/src/guacd/Makefile.am b/src/guacd/Makefile.am index 230ef47a..b68e4956 100644 --- a/src/guacd/Makefile.am +++ b/src/guacd/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright (C) 2013 Glyptodon LLC +# Copyright (C) 2015 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 @@ -22,8 +22,6 @@ AUTOMAKE_OPTIONS = foreign -AM_CFLAGS = -Werror -Wall -pedantic @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@ - sbin_PROGRAMS = guacd man_MANS = \ @@ -40,17 +38,31 @@ noinst_HEADERS = \ guacd_SOURCES = \ daemon.c \ - client.c \ - client-map.c \ - conf-args.c \ - conf-file.c \ - conf-parse.c \ - log.c + client.c \ + client-map.c \ + conf-args.c \ + conf-file.c \ + conf-parse.c \ + log.c -guacd_LDADD = @LIBGUAC_LTLIB@ @COMMON_LTLIB@ -guacd_LDFLAGS = @PTHREAD_LIBS@ @SSL_LIBS@ +guacd_CFLAGS = \ + -Werror -Wall -pedantic \ + @COMMON_INCLUDE@ \ + @LIBGUAC_INCLUDE@ + +guacd_LDADD = \ + @COMMON_LTLIB@ \ + @LIBGUAC_LTLIB@ + +guacd_LDFLAGS = \ + @PTHREAD_LIBS@ \ + @SSL_LIBS@ + +EXTRA_DIST = \ + init.d/guacd.in \ + man/guacd.8 \ + man/guacd.conf.5 -EXTRA_DIST = init.d/guacd.in man/guacd.8 man/guacd.conf.5 CLEANFILES = $(init_SCRIPTS) # SSL support @@ -65,7 +77,7 @@ initdir = @init_dir@ init_SCRIPTS = init.d/guacd init.d/guacd: init.d/guacd.in - sed -e 's,[@]sbindir[@],$(sbindir),g' < init.d/guacd.in > init.d/guacd - chmod +x init.d/guacd + sed -e 's,[@]sbindir[@],$(sbindir),g' < init.d/guacd.in > init.d/guacd + chmod +x init.d/guacd endif diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index f6298a33..bdef017c 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright (C) 2013 Glyptodon LLC +# Copyright (C) 2015 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 @@ -22,9 +22,11 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 -AM_CFLAGS = -Werror -Wall -pedantic -Iguacamole + +lib_LTLIBRARIES = libguac.la libguacincdir = $(includedir)/guacamole + libguacinc_HEADERS = \ guacamole/audio.h \ guacamole/audio-fntypes.h \ @@ -89,7 +91,17 @@ libguac_la_SOURCES += ogg_encoder.c noinst_HEADERS += ogg_encoder.h endif -lib_LTLIBRARIES = libguac.la -libguac_la_LDFLAGS = -version-info 9:0:0 @PTHREAD_LIBS@ @CAIRO_LIBS@ @PNG_LIBS@ @VORBIS_LIBS@ @UUID_LIBS@ -libguac_la_LIBADD = @LIBADD_DLOPEN@ +libguac_la_CFLAGS = \ + -Werror -Wall -pedantic -Iguacamole + +libguac_la_LDFLAGS = \ + -version-info 9:0:0 \ + @CAIRO_LIBS@ \ + @PNG_LIBS@ \ + @PTHREAD_LIBS@ \ + @UUID_LIBS@ \ + @VORBIS_LIBS@ + +libguac_la_LIBADD = \ + @LIBADD_DLOPEN@ diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 95ac7426..d67c9976 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright (C) 2013 Glyptodon LLC +# Copyright (C) 2015 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 @@ -21,83 +21,81 @@ # AUTOMAKE_OPTIONS = foreign - ACLOCAL_AMFLAGS = -I m4 -AM_CFLAGS = -Werror -Wall -Iinclude @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@ lib_LTLIBRARIES = libguac-client-rdp.la libguac_client_rdp_la_SOURCES = \ - _generated_keymaps.c \ - client.c \ - guac_handlers.c \ - rdp_bitmap.c \ - rdp_cliprdr.c \ - rdp_color.c \ - rdp_fs.c \ - rdp_gdi.c \ - rdp_glyph.c \ - rdp_keymap.c \ - rdp_pointer.c \ - rdp_rail.c \ - rdp_settings.c \ - rdp_stream.c \ - rdp_svc.c \ - resolution.c \ - unicode.c + _generated_keymaps.c \ + client.c \ + guac_handlers.c \ + rdp_bitmap.c \ + rdp_cliprdr.c \ + rdp_color.c \ + rdp_fs.c \ + rdp_gdi.c \ + rdp_glyph.c \ + rdp_keymap.c \ + rdp_pointer.c \ + rdp_rail.c \ + rdp_settings.c \ + rdp_stream.c \ + rdp_svc.c \ + resolution.c \ + unicode.c guacsvc_sources = \ - guac_svc/svc_service.c \ - rdp_svc.c + guac_svc/svc_service.c \ + rdp_svc.c guacsnd_sources = \ - guac_rdpsnd/rdpsnd_messages.c \ - guac_rdpsnd/rdpsnd_service.c + guac_rdpsnd/rdpsnd_messages.c \ + guac_rdpsnd/rdpsnd_service.c guacdr_sources = \ - guac_rdpdr/rdpdr_fs_messages.c \ - guac_rdpdr/rdpdr_fs_messages_dir_info.c \ - guac_rdpdr/rdpdr_fs_messages_file_info.c \ - guac_rdpdr/rdpdr_fs_messages_vol_info.c \ - guac_rdpdr/rdpdr_fs_service.c \ - guac_rdpdr/rdpdr_messages.c \ - guac_rdpdr/rdpdr_printer.c \ - guac_rdpdr/rdpdr_service.c \ - rdp_fs.c \ - rdp_stream.c \ - unicode.c + guac_rdpdr/rdpdr_fs_messages.c \ + guac_rdpdr/rdpdr_fs_messages_dir_info.c \ + guac_rdpdr/rdpdr_fs_messages_file_info.c \ + guac_rdpdr/rdpdr_fs_messages_vol_info.c \ + guac_rdpdr/rdpdr_fs_service.c \ + guac_rdpdr/rdpdr_messages.c \ + guac_rdpdr/rdpdr_printer.c \ + guac_rdpdr/rdpdr_service.c \ + rdp_fs.c \ + rdp_stream.c \ + unicode.c noinst_HEADERS = \ - compat/client-cliprdr.h \ - compat/rail.h \ - guac_rdpdr/rdpdr_fs_messages.h \ - guac_rdpdr/rdpdr_fs_messages_dir_info.h \ - guac_rdpdr/rdpdr_fs_messages_file_info.h \ - guac_rdpdr/rdpdr_fs_messages_vol_info.h \ - guac_rdpdr/rdpdr_fs_service.h \ - guac_rdpdr/rdpdr_messages.h \ - guac_rdpdr/rdpdr_printer.h \ - guac_rdpdr/rdpdr_service.h \ - guac_rdpsnd/rdpsnd_messages.h \ - guac_rdpsnd/rdpsnd_service.h \ - guac_svc/svc_service.h \ - client.h \ - guac_handlers.h \ - rdp_bitmap.h \ - rdp_cliprdr.h \ - rdp_color.h \ - rdp_fs.h \ - rdp_gdi.h \ - rdp_glyph.h \ - rdp_keymap.h \ - rdp_pointer.h \ - rdp_rail.h \ - rdp_settings.h \ - rdp_status.h \ - rdp_stream.h \ - rdp_svc.h \ - resolution.h \ - unicode.h + compat/client-cliprdr.h \ + compat/rail.h \ + guac_rdpdr/rdpdr_fs_messages.h \ + guac_rdpdr/rdpdr_fs_messages_dir_info.h \ + guac_rdpdr/rdpdr_fs_messages_file_info.h \ + guac_rdpdr/rdpdr_fs_messages_vol_info.h \ + guac_rdpdr/rdpdr_fs_service.h \ + guac_rdpdr/rdpdr_messages.h \ + guac_rdpdr/rdpdr_printer.h \ + guac_rdpdr/rdpdr_service.h \ + guac_rdpsnd/rdpsnd_messages.h \ + guac_rdpsnd/rdpsnd_service.h \ + guac_svc/svc_service.h \ + client.h \ + guac_handlers.h \ + rdp_bitmap.h \ + rdp_cliprdr.h \ + rdp_color.h \ + rdp_fs.h \ + rdp_gdi.h \ + rdp_glyph.h \ + rdp_keymap.h \ + rdp_pointer.h \ + rdp_rail.h \ + rdp_settings.h \ + rdp_status.h \ + rdp_stream.h \ + rdp_svc.h \ + resolution.h \ + unicode.h # Add compatibility layer for WinPR if not available if ! ENABLE_WINPR @@ -114,17 +112,83 @@ noinst_HEADERS += rdp_disp.h libguac_client_rdp_la_SOURCES += rdp_disp.c endif -libguac_client_rdp_la_LDFLAGS = -version-info 0:0:0 @RDP_LIBS@ @PTHREAD_LIBS@ @CAIRO_LIBS@ -guacsvc_ldflags = -module -avoid-version -shared @RDP_LIBS@ @PTHREAD_LIBS@ -guacsnd_ldflags = -module -avoid-version -shared @RDP_LIBS@ @PTHREAD_LIBS@ -guacdr_ldflags = -module -avoid-version -shared @RDP_LIBS@ @PTHREAD_LIBS@ +# +# Main RDP client library +# -libguac_client_rdp_la_LIBADD = @LIBGUAC_LTLIB@ @COMMON_LTLIB@ -guacsvc_libadd = @LIBGUAC_LTLIB@ @COMMON_LTLIB@ -guacsnd_libadd = @LIBGUAC_LTLIB@ -guacdr_libadd = @LIBGUAC_LTLIB@ +libguac_client_rdp_la_CFLAGS = \ + -Werror -Wall -Iinclude \ + @COMMON_INCLUDE@ \ + @LIBGUAC_INCLUDE@ +libguac_client_rdp_la_LDFLAGS = \ + -version-info 0:0:0 \ + @CAIRO_LIBS@ \ + @PTHREAD_LIBS@ \ + @RDP_LIBS@ + +libguac_client_rdp_la_LIBADD = \ + @COMMON_LTLIB@ \ + @LIBGUAC_LTLIB@ + +# +# RDPDR +# + +guacdr_cflags = \ + -Werror -Wall -Iinclude \ + @COMMON_INCLUDE@ \ + @LIBGUAC_INCLUDE@ + +guacdr_ldflags = \ + -module -avoid-version -shared \ + @PTHREAD_LIBS@ \ + @RDP_LIBS@ + +guacdr_libadd = \ + @COMMON_LTLIB@ \ + @LIBGUAC_LTLIB@ + +# +# RDPSND +# + +guacsnd_cflags = \ + -Werror -Wall -Iinclude \ + @COMMON_INCLUDE@ \ + @LIBGUAC_INCLUDE@ + +guacsnd_ldflags = \ + -module -avoid-version -shared \ + @PTHREAD_LIBS@ \ + @RDP_LIBS@ + +guacsnd_libadd = \ + @COMMON_LTLIB@ \ + @LIBGUAC_LTLIB@ + +# +# Static Virtual Channels +# + +guacsvc_cflags = \ + -Werror -Wall -Iinclude \ + @COMMON_INCLUDE@ \ + @LIBGUAC_INCLUDE@ + +guacsvc_ldflags = \ + -module -avoid-version -shared \ + @PTHREAD_LIBS@ \ + @RDP_LIBS@ + +guacsvc_libadd = \ + @COMMON_LTLIB@ \ + @LIBGUAC_LTLIB@ + +# # Autogenerate keymaps +# + CLEANFILES = _generated_keymaps.c BUILT_SOURCES = _generated_keymaps.c @@ -140,41 +204,55 @@ rdp_keymaps = \ _generated_keymaps.c: $(rdp_keymaps) keymaps/generate.pl $(rdp_keymaps) -EXTRA_DIST = $(rdp_keymaps) keymaps/generate.pl +EXTRA_DIST = \ + $(rdp_keymaps) \ + keymaps/generate.pl if LEGACY_FREERDP_EXTENSIONS # FreeRDP 1.0-style extensions -freerdp_LTLIBRARIES = guacsvc.la guacsnd.la guacdr.la +freerdp_LTLIBRARIES = \ + guacdr.la \ + guacsnd.la \ + guacsvc.la -guacsvc_la_SOURCES = ${guacsvc_sources} -guacsvc_la_LDFLAGS = ${guacsvc_ldflags} -guacsvc_la_LIBADD = ${guacsvc_libadd} +guacdr_la_SOURCES = ${guacdr_sources} +guacdr_la_CFLAGS = ${guacdr_cflags} +guacdr_la_LDFLAGS = ${guacdr_ldflags} +guacdr_la_LIBADD = ${guacdr_libadd} guacsnd_la_SOURCES = ${guacsnd_sources} +guacsnd_la_CFLAGS = ${guacsnd_cflags} guacsnd_la_LDFLAGS = ${guacsnd_ldflags} guacsnd_la_LIBADD = ${guacsnd_libadd} -guacdr_la_SOURCES = ${guacdr_sources} -guacdr_la_LDFLAGS = ${guacdr_ldflags} -guacdr_la_LIBADD = ${guacdr_libadd} +guacsvc_la_SOURCES = ${guacsvc_sources} +guacsvc_la_CFLAGS = ${guacsvc_cflags} +guacsvc_la_LDFLAGS = ${guacsvc_ldflags} +guacsvc_la_LIBADD = ${guacsvc_libadd} else # FreeRDP 1.1 (and hopefully onward) extensions -freerdp_LTLIBRARIES = guacsvc-client.la guacsnd-client.la guacdr-client.la +freerdp_LTLIBRARIES = \ + guacdr-client.la \ + guacsnd-client.la \ + guacsvc-client.la -guacsvc_client_la_SOURCES = ${guacsvc_sources} -guacsvc_client_la_LDFLAGS = ${guacsvc_ldflags} -guacsvc_client_la_LIBADD = ${guacsvc_libadd} +guacdr_client_la_SOURCES = ${guacdr_sources} +guacdr_client_la_CFLAGS = ${guacdr_cflags} +guacdr_client_la_LDFLAGS = ${guacdr_ldflags} +guacdr_client_la_LIBADD = ${guacdr_libadd} guacsnd_client_la_SOURCES = ${guacsnd_sources} +guacsnd_client_la_CFLAGS = ${guacsnd_cflags} guacsnd_client_la_LDFLAGS = ${guacsnd_ldflags} guacsnd_client_la_LIBADD = ${guacsnd_libadd} -guacdr_client_la_SOURCES = ${guacdr_sources} -guacdr_client_la_LDFLAGS = ${guacdr_ldflags} -guacdr_client_la_LIBADD = ${guacdr_libadd} +guacsvc_client_la_SOURCES = ${guacsvc_sources} +guacsvc_client_la_CFLAGS = ${guacsvc_cflags} +guacsvc_client_la_LDFLAGS = ${guacsvc_ldflags} +guacsvc_client_la_LIBADD = ${guacsvc_libadd} endif diff --git a/src/protocols/ssh/Makefile.am b/src/protocols/ssh/Makefile.am index 65a7e538..e13bc253 100644 --- a/src/protocols/ssh/Makefile.am +++ b/src/protocols/ssh/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright (C) 2013 Glyptodon LLC +# Copyright (C) 2015 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 @@ -21,7 +21,6 @@ # AUTOMAKE_OPTIONS = foreign - ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = libguac-client-ssh.la @@ -50,7 +49,21 @@ libguac_client_ssh_la_SOURCES += ssh_agent.c noinst_HEADERS += ssh_agent.h endif -libguac_client_ssh_la_CFLAGS = -Werror -Wall -Iinclude @LIBGUAC_INCLUDE@ @TERMINAL_INCLUDE@ @COMMON_SSH_INCLUDE@ -libguac_client_ssh_la_LIBADD = @LIBGUAC_LTLIB@ @TERMINAL_LTLIB@ @COMMON_SSH_LTLIB@ -libguac_client_ssh_la_LDFLAGS = -version-info 0:0:0 @SSH_LIBS@ @SSL_LIBS@ @PTHREAD_LIBS@ +libguac_client_ssh_la_CFLAGS = \ + -Werror -Wall -Iinclude \ + @COMMON_SSH_INCLUDE@ \ + @LIBGUAC_INCLUDE@ \ + @TERMINAL_INCLUDE@ + +libguac_client_ssh_la_LIBADD = \ + @COMMON_LTLIB@ \ + @COMMON_SSH_LTLIB@ \ + @LIBGUAC_LTLIB@ \ + @TERMINAL_LTLIB@ + +libguac_client_ssh_la_LDFLAGS = \ + -version-info 0:0:0 \ + @PTHREAD_LIBS@ \ + @SSH_LIBS@ \ + @SSL_LIBS@ diff --git a/src/protocols/telnet/Makefile.am b/src/protocols/telnet/Makefile.am index 58323b8e..fdaf00f0 100644 --- a/src/protocols/telnet/Makefile.am +++ b/src/protocols/telnet/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright (C) 2014 Glyptodon LLC +# Copyright (C) 2015 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 @@ -21,7 +21,6 @@ # AUTOMAKE_OPTIONS = foreign - ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = libguac-client-telnet.la @@ -38,7 +37,18 @@ noinst_HEADERS = \ 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@ +libguac_client_telnet_la_CFLAGS = \ + -Werror -Wall -Iinclude \ + @LIBGUAC_INCLUDE@ \ + @TERMINAL_INCLUDE@ + +libguac_client_telnet_la_LIBADD = \ + @COMMON_LTLIB@ \ + @LIBGUAC_LTLIB@ \ + @TERMINAL_LTLIB@ + +libguac_client_telnet_la_LDFLAGS = \ + -version-info 0:0:0 \ + @PTHREAD_LIBS@ \ + @TELNET_LIBS@ diff --git a/src/protocols/vnc/Makefile.am b/src/protocols/vnc/Makefile.am index 17222a70..39b38e41 100644 --- a/src/protocols/vnc/Makefile.am +++ b/src/protocols/vnc/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright (C) 2013 Glyptodon LLC +# Copyright (C) 2015 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 @@ -21,11 +21,7 @@ # AUTOMAKE_OPTIONS = foreign - ACLOCAL_AMFLAGS = -I m4 -AM_CFLAGS = -Werror -Wall -pedantic -Iinclude \ - @COMMON_INCLUDE@ \ - @LIBGUAC_INCLUDE@ lib_LTLIBRARIES = libguac-client-vnc.la @@ -47,10 +43,18 @@ libguac_client_vnc_la_SOURCES += pulse.c noinst_HEADERS += pulse.h endif -libguac_client_vnc_la_LDFLAGS = -version-info 0:0:0 \ - @CAIRO_LIBS@ \ - @PULSE_LIBS@ \ - @VNC_LIBS@ +libguac_client_vnc_la_CFLAGS = \ + -Werror -Wall -pedantic -Iinclude \ + @COMMON_INCLUDE@ \ + @LIBGUAC_INCLUDE@ -libguac_client_vnc_la_LIBADD = @LIBGUAC_LTLIB@ @COMMON_LTLIB@ +libguac_client_vnc_la_LDFLAGS = \ + -version-info 0:0:0 \ + @CAIRO_LIBS@ \ + @PULSE_LIBS@ \ + @VNC_LIBS@ + +libguac_client_vnc_la_LIBADD = \ + @COMMON_LTLIB@ \ + @LIBGUAC_LTLIB@ diff --git a/src/terminal/Makefile.am b/src/terminal/Makefile.am index 1e7ca72d..40aca246 100644 --- a/src/terminal/Makefile.am +++ b/src/terminal/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright (C) 2013 Glyptodon LLC +# Copyright (C) 2015 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 @@ -22,7 +22,6 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 -AM_CFLAGS = -Werror -Wall -pedantic @PANGO_CFLAGS@ @PANGOCAIRO_CFLAGS@ @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@ noinst_LTLIBRARIES = libguac_terminal.la @@ -55,6 +54,20 @@ libguac_terminal_la_SOURCES = \ terminal.c \ terminal_handlers.c -libguac_terminal_la_LIBADD = @LIBGUAC_LTLIB@ @COMMON_LTLIB@ -libguac_terminal_la_LDFLAGS = @PTHREAD_LIBS@ @PANGO_LIBS@ @PANGOCAIRO_LIBS@ @CAIRO_LIBS@ @MATH_LIBS@ +libguac_terminal_la_CFLAGS = \ + -Werror -Wall -pedantic \ + @COMMON_INCLUDE@ \ + @LIBGUAC_INCLUDE@ \ + @PANGO_CFLAGS@ \ + @PANGOCAIRO_CFLAGS@ + +libguac_terminal_la_LIBADD = \ + @LIBGUAC_LTLIB@ + +libguac_terminal_la_LDFLAGS = \ + @CAIRO_LIBS@ \ + @MATH_LIBS@ \ + @PANGO_LIBS@ \ + @PANGOCAIRO_LIBS@ \ + @PTHREAD_LIBS@ diff --git a/tests/Makefile.am b/tests/Makefile.am index 9e2de545..0007ef02 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright (C) 2013 Glyptodon LLC +# Copyright (C) 2015 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 @@ -22,34 +22,41 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 -AM_CFLAGS = -Werror -Wall -pedantic @LIBGUAC_INCLUDE@ @COMMON_INCLUDE@ TESTS = test_libguac check_PROGRAMS = test_libguac noinst_HEADERS = \ - client/client_suite.h \ - common/common_suite.h \ - protocol/suite.h \ - util/util_suite.h + client/client_suite.h \ + common/common_suite.h \ + protocol/suite.h \ + util/util_suite.h test_libguac_SOURCES = \ test_libguac.c \ - client/client_suite.c \ - client/buffer_pool.c \ - client/layer_pool.c \ - common/common_suite.c \ - common/guac_iconv.c \ - common/guac_string.c \ - protocol/suite.c \ - protocol/base64_decode.c \ - protocol/instruction_parse.c \ - protocol/instruction_read.c \ - protocol/instruction_write.c \ - protocol/nest_write.c \ - util/util_suite.c \ - util/guac_pool.c \ - util/guac_unicode.c + client/client_suite.c \ + client/buffer_pool.c \ + client/layer_pool.c \ + common/common_suite.c \ + common/guac_iconv.c \ + common/guac_string.c \ + protocol/suite.c \ + protocol/base64_decode.c \ + protocol/instruction_parse.c \ + protocol/instruction_read.c \ + protocol/instruction_write.c \ + protocol/nest_write.c \ + util/util_suite.c \ + util/guac_pool.c \ + util/guac_unicode.c -test_libguac_LDADD = @LIBGUAC_LTLIB@ @CUNIT_LIBS@ @COMMON_LTLIB@ +test_libguac_CFLAGS = \ + -Werror -Wall -pedantic \ + @COMMON_INCLUDE@ \ + @LIBGUAC_INCLUDE@ + +test_libguac_LDADD = \ + @COMMON_LTLIB@ \ + @CUNIT_LIBS@ \ + @LIBGUAC_LTLIB@ From cc6a56234c384b1458d8e7a977da909bf3cac615 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 9 Jul 2015 01:29:17 -0700 Subject: [PATCH 03/15] GUAC-1171: Stub common SSH API. --- src/common-ssh/Makefile.am | 4 +- .../{guac_ssh_config.c => guac_ssh.c} | 38 +++++- src/common-ssh/guac_ssh.h | 125 ++++++++++++++++++ src/common-ssh/guac_ssh_config.h | 33 ----- 4 files changed, 163 insertions(+), 37 deletions(-) rename src/common-ssh/{guac_ssh_config.c => guac_ssh.c} (58%) create mode 100644 src/common-ssh/guac_ssh.h delete mode 100644 src/common-ssh/guac_ssh_config.h diff --git a/src/common-ssh/Makefile.am b/src/common-ssh/Makefile.am index 33a808c4..f389179a 100644 --- a/src/common-ssh/Makefile.am +++ b/src/common-ssh/Makefile.am @@ -26,10 +26,10 @@ ACLOCAL_AMFLAGS = -I m4 noinst_LTLIBRARIES = libguac_common_ssh.la libguac_common_ssh_la_SOURCES = \ - guac_ssh_config.c + guac_ssh.c noinst_HEADERS = \ - guac_ssh_config.h + guac_ssh.h libguac_common_ssh_la_CFLAGS = \ -Werror -Wall -pedantic \ diff --git a/src/common-ssh/guac_ssh_config.c b/src/common-ssh/guac_ssh.c similarity index 58% rename from src/common-ssh/guac_ssh_config.c rename to src/common-ssh/guac_ssh.c index 65aa76b8..694806b5 100644 --- a/src/common-ssh/guac_ssh_config.c +++ b/src/common-ssh/guac_ssh.c @@ -20,9 +20,43 @@ * THE SOFTWARE. */ -#include "guac_ssh_config.h" +#include "guac_ssh.h" -void guac_common_ssh_connect() { +#include +#include +#include + +void guac_common_ssh_init() { + /* STUB */ +} + +LIBSSH2_SESSION* guac_common_ssh_connect_password(const char* hostname, + int port, const char* username, const char* password) { + + /* STUB */ + return NULL; + +} + +LIBSSH2_SESSION* guac_common_ssh_connect_private_key(const char* hostname, + int port, const char* username, const char* private_key, + const char* passphrase) { + + /* STUB */ + return NULL; + +} + +guac_object* guac_common_ssh_create_sftp_filesystem(guac_client* client, + const char* name, LIBSSH2_SESSION* session) { + + /* STUB */ + return NULL; + +} + +void guac_common_ssh_destroy_sftp_filesystem(guac_client* client, + guac_object* filesystem) { /* STUB */ } diff --git a/src/common-ssh/guac_ssh.h b/src/common-ssh/guac_ssh.h new file mode 100644 index 00000000..eea8ca8a --- /dev/null +++ b/src/common-ssh/guac_ssh.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2015 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_COMMON_SSH_H +#define GUAC_COMMON_SSH_H + +#include +#include +#include + +/** + * Initializes the underlying SSH and encryption libraries used by Guacamole. + * This function must be called before any other guac_common_ssh_*() functions + * are called. + */ +void guac_common_ssh_init(); + +/** + * Connects to the SSH server running at the given hostname and port using the + * given username and password for authentication. + * + * @param hostname + * The hostname of the SSH server to connect to. + * + * @param port + * The port to connect to on the given hostname. + * + * @param username + * The username to authenticate as. + * + * @param password + * The password to provide when authenticating as the given user. + * + * @return + * A new SSH session if the connection and authentication succeed, or + * NULL if the connection or authentication were not successful. + */ +LIBSSH2_SESSION* guac_common_ssh_connect_password(const char* hostname, + int port, const char* username, const char* password); + +/** + * Connects to the SSH server running at the given hostname and port using the + * given username and private key for authentication. + * + * @param hostname + * The hostname of the SSH server to connect to. + * + * @param port + * The port to connect to on the given hostname. + * + * @param username + * The username to authenticate as. + * + * @param private_key + * The base64-encoded private key to use when authenticating. + * + * @param passphrase + * The passphrase to use when importing the private key, if any, or NULL + * if no passphrase should be used. + * + * @return + * A new SSH session if the connection and authentication succeed, or + * NULL if the connection or authentication were not successful. + */ +LIBSSH2_SESSION* guac_common_ssh_connect_private_key(const char* hostname, + int port, const char* username, const char* private_key, + const char* passphrase); + +/** + * Creates a new Guacamole filesystem object which provides access to files + * and directories via SFTP using the given SSH session. When the filesystem + * will no longer be used, it must be explicitly destroyed with + * guac_common_ssh_destroy_sftp_filesystem(). + * + * @param client + * The Guacamole client which will be associated with the new filesystem + * object. + * + * @param name + * The name to send as the name of the filesystem. + * + * @param session + * The session to use to provide SFTP. + * + * @return + * A new Guacamole filesystem object, already configured to use SFTP for + * uploading and downloading files. + */ +guac_object* guac_common_ssh_create_sftp_filesystem(guac_client* client, + const char* name, LIBSSH2_SESSION* session); + +/** + * Destroys the given filesystem object, disconnecting from SFTP and freeing + * and associated resources. + * + * @param client + * The client associated with the filesystem object. + * + * @param object + * The filesystem object to destroy. + */ +void guac_common_ssh_destroy_sftp_filesystem(guac_client* client, + guac_object* filesystem); + +#endif + diff --git a/src/common-ssh/guac_ssh_config.h b/src/common-ssh/guac_ssh_config.h deleted file mode 100644 index 7c2b6f0d..00000000 --- a/src/common-ssh/guac_ssh_config.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2015 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_COMMON_SSH_CONFIG_H -#define GUAC_COMMON_SSH_CONFIG_H - -/** - * STUB - */ -void guac_common_ssh_connect(); - -#endif - From fbcf8a1e0d2ee9258d4b1573534746529dfdd2f7 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 9 Jul 2015 12:02:31 -0700 Subject: [PATCH 04/15] GUAC-1171: Move initialization of SSH, OpenSSL, etc. to common SSH base. --- src/common-ssh/guac_ssh.c | 104 ++++++++++++++++++++++++++++++++- src/common-ssh/guac_ssh.h | 15 ++++- src/protocols/ssh/ssh_client.c | 75 ++---------------------- 3 files changed, 120 insertions(+), 74 deletions(-) diff --git a/src/common-ssh/guac_ssh.c b/src/common-ssh/guac_ssh.c index 694806b5..6a85decd 100644 --- a/src/common-ssh/guac_ssh.c +++ b/src/common-ssh/guac_ssh.c @@ -26,8 +26,108 @@ #include #include -void guac_common_ssh_init() { - /* STUB */ +#ifdef LIBSSH2_USES_GCRYPT +#include +#endif + +#include +#include + +#include + +#ifdef LIBSSH2_USES_GCRYPT +GCRY_THREAD_OPTION_PTHREAD_IMPL; +#endif + +/** + * Array of mutexes, used by OpenSSL. + */ +static pthread_mutex_t* guac_common_ssh_openssl_locks; + +/** + * Called by OpenSSL when locking or unlocking the Nth mutex. + */ +static void guac_common_ssh_openssl_locking_callback(int mode, int n, + const char* file, int line){ + + /* Lock given mutex upon request */ + if (mode & CRYPTO_LOCK) + pthread_mutex_lock(&(guac_common_ssh_openssl_locks[n])); + + /* Unlock given mutex upon request */ + else if (mode & CRYPTO_UNLOCK) + pthread_mutex_unlock(&(guac_common_ssh_openssl_locks[n])); + +} + +/** + * Called by OpenSSL when determining the current thread ID. + */ +static unsigned long guac_common_ssh_openssl_id_callback() { + return (unsigned long) pthread_self(); +} + +/** + * Creates the given number of mutexes, such that OpenSSL will have at least + * this number of mutexes at its disposal. + */ +static void guac_common_ssh_openssl_init_locks(int count) { + + int i; + + /* Allocate required number of locks */ + guac_common_ssh_openssl_locks = + malloc(sizeof(pthread_mutex_t) * CRYPTO_num_locks()); + + /* Initialize each lock */ + for (i=0; i < count; i++) + pthread_mutex_init(&(guac_common_ssh_openssl_locks[i]), NULL); + +} + +/** + * Frees the given number of mutexes. + */ +static void guac_common_ssh_openssl_free_locks(int count) { + + int i; + + /* Free all locks */ + for (i=0; i < count; i++) + pthread_mutex_destroy(&(guac_common_ssh_openssl_locks[i])); + +} + +int guac_common_ssh_init(guac_client* client) { + +#ifdef LIBSSH2_USES_GCRYPT + /* Init threadsafety in libgcrypt */ + gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); + if (!gcry_check_version(GCRYPT_VERSION)) { + guac_client_log(client, GUAC_LOG_ERROR, "libgcrypt version mismatch."); + return 1; + } +#endif + + /* Init threadsafety in OpenSSL */ + guac_common_ssh_openssl_init_locks(CRYPTO_num_locks()); + CRYPTO_set_id_callback(guac_common_ssh_openssl_id_callback); + CRYPTO_set_locking_callback(guac_common_ssh_openssl_locking_callback); + + /* Init OpenSSL */ + SSL_library_init(); + ERR_load_crypto_strings(); + + /* Init libssh2 */ + libssh2_init(0); + + /* Success */ + return 0; + +} + +void guac_common_ssh_uninit() { + guac_common_ssh_openssl_free_locks(CRYPTO_num_locks()); } LIBSSH2_SESSION* guac_common_ssh_connect_password(const char* hostname, diff --git a/src/common-ssh/guac_ssh.h b/src/common-ssh/guac_ssh.h index eea8ca8a..302d6f0e 100644 --- a/src/common-ssh/guac_ssh.h +++ b/src/common-ssh/guac_ssh.h @@ -31,8 +31,21 @@ * Initializes the underlying SSH and encryption libraries used by Guacamole. * This function must be called before any other guac_common_ssh_*() functions * are called. + * + * @param client + * The Guacamole client that will be using SSH. + * + * @return + * Zero if initialization, or non-zero if an error occurs. */ -void guac_common_ssh_init(); +int guac_common_ssh_init(guac_client* client); + +/** + * Cleans up the underlying SSH and encryption libraries used by Guacamole. + * This function must be called once no other guac_common_ssh_*() functions + * will be used. + */ +void guac_common_ssh_uninit(); /** * Connects to the SSH server running at the given hostname and port using the diff --git a/src/protocols/ssh/ssh_client.c b/src/protocols/ssh/ssh_client.c index 24f5fd8d..301f83e0 100644 --- a/src/protocols/ssh/ssh_client.c +++ b/src/protocols/ssh/ssh_client.c @@ -23,6 +23,7 @@ #include "config.h" #include "client.h" +#include "guac_ssh.h" #include "sftp.h" #include "ssh_key.h" #include "terminal.h" @@ -270,59 +271,6 @@ static LIBSSH2_SESSION* __guac_ssh_create_session(guac_client* client, } -#ifdef LIBSSH2_USES_GCRYPT -GCRY_THREAD_OPTION_PTHREAD_IMPL; -#endif - -/** - * Array of mutexes, used by OpenSSL. - */ -static pthread_mutex_t* __openssl_locks; - -/** - * Called by OpenSSL when locking or unlocking the Nth mutex. - */ -static void __openssl_locking_callback(int mode, int n, const char* file, int line){ - if (mode & CRYPTO_LOCK) - pthread_mutex_lock(&(__openssl_locks[n])); - else if (mode & CRYPTO_UNLOCK) - pthread_mutex_unlock(&(__openssl_locks[n])); -} - -/** - * Called by OpenSSL when determining the current thread ID. - */ -static unsigned long __openssl_id_callback() { - return (unsigned long) pthread_self(); -} - -/** - * Creates the given number of mutexes, such that OpenSSL will have at least - * this number of mutexes at its disposal. - */ -static void __openssl_init_locks(int count) { - - int i; - - __openssl_locks = malloc(sizeof(pthread_mutex_t) * CRYPTO_num_locks()); - - for (i=0; iusername[0] == 0) @@ -574,8 +507,8 @@ void* ssh_client_thread(void* data) { guac_client_stop(client); pthread_join(input_thread, NULL); - __openssl_free_locks(CRYPTO_num_locks()); pthread_mutex_destroy(&client_data->term_channel_lock); + guac_common_ssh_uninit(); guac_client_log(client, GUAC_LOG_INFO, "SSH connection ended."); return NULL; From ed7028176632704c35c395c31c3534f30456d6c0 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 9 Jul 2015 12:05:57 -0700 Subject: [PATCH 05/15] GUAC-1171: Move SFTP functions to own header. --- src/common-ssh/Makefile.am | 2 ++ src/common-ssh/guac_sftp.c | 41 +++++++++++++++++++++++ src/common-ssh/guac_sftp.h | 67 ++++++++++++++++++++++++++++++++++++++ src/common-ssh/guac_ssh.c | 14 -------- src/common-ssh/guac_ssh.h | 37 --------------------- 5 files changed, 110 insertions(+), 51 deletions(-) create mode 100644 src/common-ssh/guac_sftp.c create mode 100644 src/common-ssh/guac_sftp.h diff --git a/src/common-ssh/Makefile.am b/src/common-ssh/Makefile.am index f389179a..79607a7c 100644 --- a/src/common-ssh/Makefile.am +++ b/src/common-ssh/Makefile.am @@ -26,9 +26,11 @@ ACLOCAL_AMFLAGS = -I m4 noinst_LTLIBRARIES = libguac_common_ssh.la libguac_common_ssh_la_SOURCES = \ + guac_sftp.c \ guac_ssh.c noinst_HEADERS = \ + guac_sftp.h \ guac_ssh.h libguac_common_ssh_la_CFLAGS = \ diff --git a/src/common-ssh/guac_sftp.c b/src/common-ssh/guac_sftp.c new file mode 100644 index 00000000..a2524eaf --- /dev/null +++ b/src/common-ssh/guac_sftp.c @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 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 "guac_ssh.h" + +#include +#include +#include + +guac_object* guac_common_ssh_create_sftp_filesystem(guac_client* client, + const char* name, LIBSSH2_SESSION* session) { + + /* STUB */ + return NULL; + +} + +void guac_common_ssh_destroy_sftp_filesystem(guac_client* client, + guac_object* filesystem) { + /* STUB */ +} + diff --git a/src/common-ssh/guac_sftp.h b/src/common-ssh/guac_sftp.h new file mode 100644 index 00000000..ee6f3885 --- /dev/null +++ b/src/common-ssh/guac_sftp.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 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_COMMON_SSH_SFTP_H +#define GUAC_COMMON_SSH_SFTP_H + +#include +#include +#include + +/** + * Creates a new Guacamole filesystem object which provides access to files + * and directories via SFTP using the given SSH session. When the filesystem + * will no longer be used, it must be explicitly destroyed with + * guac_common_ssh_destroy_sftp_filesystem(). + * + * @param client + * The Guacamole client which will be associated with the new filesystem + * object. + * + * @param name + * The name to send as the name of the filesystem. + * + * @param session + * The session to use to provide SFTP. + * + * @return + * A new Guacamole filesystem object, already configured to use SFTP for + * uploading and downloading files. + */ +guac_object* guac_common_ssh_create_sftp_filesystem(guac_client* client, + const char* name, LIBSSH2_SESSION* session); + +/** + * Destroys the given filesystem object, disconnecting from SFTP and freeing + * and associated resources. + * + * @param client + * The client associated with the filesystem object. + * + * @param object + * The filesystem object to destroy. + */ +void guac_common_ssh_destroy_sftp_filesystem(guac_client* client, + guac_object* filesystem); + +#endif + diff --git a/src/common-ssh/guac_ssh.c b/src/common-ssh/guac_ssh.c index 6a85decd..105b9fcf 100644 --- a/src/common-ssh/guac_ssh.c +++ b/src/common-ssh/guac_ssh.c @@ -23,7 +23,6 @@ #include "guac_ssh.h" #include -#include #include #ifdef LIBSSH2_USES_GCRYPT @@ -147,16 +146,3 @@ LIBSSH2_SESSION* guac_common_ssh_connect_private_key(const char* hostname, } -guac_object* guac_common_ssh_create_sftp_filesystem(guac_client* client, - const char* name, LIBSSH2_SESSION* session) { - - /* STUB */ - return NULL; - -} - -void guac_common_ssh_destroy_sftp_filesystem(guac_client* client, - guac_object* filesystem) { - /* STUB */ -} - diff --git a/src/common-ssh/guac_ssh.h b/src/common-ssh/guac_ssh.h index 302d6f0e..44af5905 100644 --- a/src/common-ssh/guac_ssh.h +++ b/src/common-ssh/guac_ssh.h @@ -24,7 +24,6 @@ #define GUAC_COMMON_SSH_H #include -#include #include /** @@ -98,41 +97,5 @@ LIBSSH2_SESSION* guac_common_ssh_connect_private_key(const char* hostname, int port, const char* username, const char* private_key, const char* passphrase); -/** - * Creates a new Guacamole filesystem object which provides access to files - * and directories via SFTP using the given SSH session. When the filesystem - * will no longer be used, it must be explicitly destroyed with - * guac_common_ssh_destroy_sftp_filesystem(). - * - * @param client - * The Guacamole client which will be associated with the new filesystem - * object. - * - * @param name - * The name to send as the name of the filesystem. - * - * @param session - * The session to use to provide SFTP. - * - * @return - * A new Guacamole filesystem object, already configured to use SFTP for - * uploading and downloading files. - */ -guac_object* guac_common_ssh_create_sftp_filesystem(guac_client* client, - const char* name, LIBSSH2_SESSION* session); - -/** - * Destroys the given filesystem object, disconnecting from SFTP and freeing - * and associated resources. - * - * @param client - * The client associated with the filesystem object. - * - * @param object - * The filesystem object to destroy. - */ -void guac_common_ssh_destroy_sftp_filesystem(guac_client* client, - guac_object* filesystem); - #endif From 5b627ae5cccd833d4b66f9d1a5ed4813045928aa Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 9 Jul 2015 12:28:46 -0700 Subject: [PATCH 06/15] GUAC-1171: Implement SSH connection (without auth). --- src/common-ssh/guac_ssh.c | 159 ++++++++++++++++++++++++++++++++++++-- src/common-ssh/guac_ssh.h | 34 ++++++-- 2 files changed, 178 insertions(+), 15 deletions(-) diff --git a/src/common-ssh/guac_ssh.c b/src/common-ssh/guac_ssh.c index 105b9fcf..62bcc440 100644 --- a/src/common-ssh/guac_ssh.c +++ b/src/common-ssh/guac_ssh.c @@ -32,7 +32,14 @@ #include #include +#include +#include +#include #include +#include +#include +#include +#include #ifdef LIBSSH2_USES_GCRYPT GCRY_THREAD_OPTION_PTHREAD_IMPL; @@ -129,20 +136,156 @@ void guac_common_ssh_uninit() { guac_common_ssh_openssl_free_locks(CRYPTO_num_locks()); } -LIBSSH2_SESSION* guac_common_ssh_connect_password(const char* hostname, - int port, const char* username, const char* password) { +/** + * Connects to the SSH server running at the given hostname and port, but does + * not perform any authentication. Authentication must be immediately performed + * after creation of the session for the session to become usable. If an error + * occurs while connecting, the Guacamole client will automatically and fatally + * abort. + * + * @param client + * The Guacamole client that will be using SSH. + * + * @param hostname + * The hostname of the SSH server to connect to. + * + * @param port + * The port to connect to on the given hostname. + * + * @param socket_fd + * A pointer to an integer in which the newly-allocated file descriptor for + * the SSH socket should be stored. + * + * @return + * A new SSH session if the connection succeeds, or NULL if the connection + * was not successful. + */ +static LIBSSH2_SESSION* guac_common_ssh_connect(guac_client* client, + const char* hostname, const char* port, int* socket_fd) { - /* STUB */ - return NULL; + int retval; + + int fd; + struct addrinfo* addresses; + struct addrinfo* current_address; + + char connected_address[1024]; + char connected_port[64]; + + 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(hostname, 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) { + + /* 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(client, GUAC_LOG_DEBUG, + "Unable to resolve host: %s", gai_strerror(retval)); + + /* Connect */ + if (connect(fd, current_address->ai_addr, + current_address->ai_addrlen) == 0) { + + guac_client_log(client, GUAC_LOG_DEBUG, + "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(client, GUAC_LOG_DEBUG, "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 SSH session */ + LIBSSH2_SESSION* session = libssh2_session_init_ex(NULL, NULL, + NULL, client); + if (session == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Session allocation failed."); + return NULL; + } + + /* Perform handshake */ + if (libssh2_session_handshake(session, fd)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + "SSH handshake failed."); + return NULL; + } + + /* Save file descriptor */ + if (socket_fd != NULL) + *socket_fd = fd; + + /* Return created session */ + return session; } -LIBSSH2_SESSION* guac_common_ssh_connect_private_key(const char* hostname, - int port, const char* username, const char* private_key, - const char* passphrase) { +LIBSSH2_SESSION* guac_common_ssh_connect_password(guac_client* client, + const char* hostname, const char* port, + const char* username, const char* password, + int* socket_fd) { + + LIBSSH2_SESSION* session = guac_common_ssh_connect(client, + hostname, port, socket_fd); /* STUB */ - return NULL; + + return session; + +} + +LIBSSH2_SESSION* guac_common_ssh_connect_private_key(guac_client* client, + const char* hostname, const char* port, + const char* username, const char* private_key, + const char* passphrase, + int* socket_fd) { + + LIBSSH2_SESSION* session = guac_common_ssh_connect(client, + hostname, port, socket_fd); + + /* STUB */ + + return session; } diff --git a/src/common-ssh/guac_ssh.h b/src/common-ssh/guac_ssh.h index 44af5905..a50a62ec 100644 --- a/src/common-ssh/guac_ssh.h +++ b/src/common-ssh/guac_ssh.h @@ -48,7 +48,11 @@ void guac_common_ssh_uninit(); /** * Connects to the SSH server running at the given hostname and port using the - * given username and password for authentication. + * given username and password for authentication. If an error occurs while + * connecting, the Guacamole client will automatically and fatally abort. + * + * @param client + * The Guacamole client that will be using SSH. * * @param hostname * The hostname of the SSH server to connect to. @@ -62,16 +66,26 @@ void guac_common_ssh_uninit(); * @param password * The password to provide when authenticating as the given user. * + * @param socket_fd + * A pointer to an integer in which the newly-allocated file descriptor for + * the SSH socket should be stored. + * * @return * A new SSH session if the connection and authentication succeed, or * NULL if the connection or authentication were not successful. */ -LIBSSH2_SESSION* guac_common_ssh_connect_password(const char* hostname, - int port, const char* username, const char* password); +LIBSSH2_SESSION* guac_common_ssh_connect_password(guac_client* client, + const char* hostname, const char* port, + const char* username, const char* password, + int* socket_fd); /** * Connects to the SSH server running at the given hostname and port using the - * given username and private key for authentication. + * given username and private key for authentication. If an error occurs while + * connecting, the Guacamole client will automatically and fatally abort. + * + * @param client + * The Guacamole client that will be using SSH. * * @param hostname * The hostname of the SSH server to connect to. @@ -89,13 +103,19 @@ LIBSSH2_SESSION* guac_common_ssh_connect_password(const char* hostname, * The passphrase to use when importing the private key, if any, or NULL * if no passphrase should be used. * + * @param socket_fd + * A pointer to an integer in which the newly-allocated file descriptor for + * the SSH socket should be stored. + * * @return * A new SSH session if the connection and authentication succeed, or * NULL if the connection or authentication were not successful. */ -LIBSSH2_SESSION* guac_common_ssh_connect_private_key(const char* hostname, - int port, const char* username, const char* private_key, - const char* passphrase); +LIBSSH2_SESSION* guac_common_ssh_connect_private_key(guac_client* client, + const char* hostname, const char* port, + const char* username, const char* private_key, + const char* passphrase, + int* socket_fd); #endif From 0a015b2843c7ae0513a91ef4caf4820ae4a10b8b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 9 Jul 2015 16:17:47 -0700 Subject: [PATCH 07/15] GUAC-1171: Add key and buffer helpers. Partially implement private key auth. --- src/common-ssh/Makefile.am | 12 +- src/common-ssh/guac_ssh.c | 94 ++++++++++++- src/common-ssh/guac_ssh.h | 3 +- src/common-ssh/guac_ssh_buffer.c | 136 +++++++++++++++++++ src/common-ssh/guac_ssh_buffer.h | 81 ++++++++++++ src/common-ssh/guac_ssh_key.c | 218 +++++++++++++++++++++++++++++++ src/common-ssh/guac_ssh_key.h | 138 +++++++++++++++++++ 7 files changed, 673 insertions(+), 9 deletions(-) create mode 100644 src/common-ssh/guac_ssh_buffer.c create mode 100644 src/common-ssh/guac_ssh_buffer.h create mode 100644 src/common-ssh/guac_ssh_key.c create mode 100644 src/common-ssh/guac_ssh_key.h diff --git a/src/common-ssh/Makefile.am b/src/common-ssh/Makefile.am index 79607a7c..8d0f9612 100644 --- a/src/common-ssh/Makefile.am +++ b/src/common-ssh/Makefile.am @@ -27,11 +27,15 @@ noinst_LTLIBRARIES = libguac_common_ssh.la libguac_common_ssh_la_SOURCES = \ guac_sftp.c \ - guac_ssh.c + guac_ssh.c \ + guac_ssh_buffer.c \ + guac_ssh_key.c -noinst_HEADERS = \ - guac_sftp.h \ - guac_ssh.h +noinst_HEADERS = \ + guac_sftp.h \ + guac_ssh.h \ + guac_ssh_buffer.h \ + guac_ssh_key.h libguac_common_ssh_la_CFLAGS = \ -Werror -Wall -pedantic \ diff --git a/src/common-ssh/guac_ssh.c b/src/common-ssh/guac_ssh.c index 62bcc440..87347943 100644 --- a/src/common-ssh/guac_ssh.c +++ b/src/common-ssh/guac_ssh.c @@ -21,6 +21,7 @@ */ #include "guac_ssh.h" +#include "guac_ssh_key.h" #include #include @@ -274,17 +275,104 @@ LIBSSH2_SESSION* guac_common_ssh_connect_password(guac_client* client, } +static int __sign_callback(LIBSSH2_SESSION* session, + unsigned char** sig, size_t* sig_len, + const unsigned char* data, size_t data_len, void **abstract) { + + guac_common_ssh_key* key = (guac_common_ssh_key*) abstract; + int length; + + /* Allocate space for signature */ + *sig = malloc(4096); + + /* Sign with key */ + length = guac_common_ssh_key_sign(key, (const char*) data, data_len, *sig); + if (length < 0) + return 1; + + *sig_len = length; + return 0; +} + LIBSSH2_SESSION* guac_common_ssh_connect_private_key(guac_client* client, const char* hostname, const char* port, - const char* username, const char* private_key, - const char* passphrase, + const char* username, char* private_key, char* passphrase, int* socket_fd) { LIBSSH2_SESSION* session = guac_common_ssh_connect(client, hostname, port, socket_fd); - /* STUB */ + guac_client_log(client, GUAC_LOG_DEBUG, + "Attempting private key import (WITHOUT passphrase)"); + /* Attempt to read key without passphrase */ + guac_common_ssh_key* key = guac_common_ssh_key_alloc(private_key, + strlen(private_key), ""); + +#if 0 + /* On failure, attempt with passphrase */ + if (key == NULL) { + + /* Log failure of initial attempt */ + guac_client_log(client, GUAC_LOG_DEBUG, + "Initial import failed: %s", guac_common_ssh_key_error()); + + guac_client_log(client, GUAC_LOG_DEBUG, + "Re-attempting private key import (WITH passphrase)"); + + /* Prompt for passphrase if missing */ + if (client_data->key_passphrase[0] == 0) + guac_terminal_prompt(client_data->term, "Key passphrase: ", + client_data->key_passphrase, sizeof(client_data->key_passphrase), false); + + /* Import key with passphrase */ + client_data->key = guac_common_ssh_key_alloc(client_data->key_base64, + strlen(client_data->key_base64), + client_data->key_passphrase); + + /* If still failing, give up */ + if (client_data->key == NULL) { + guac_client_abort(client, + GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Auth key import failed: %s", guac_common_ssh_key_error()); + return NULL; + } + + } /* end decrypt key with passphrase */ +#endif + + /* Success */ + guac_client_log(client, GUAC_LOG_INFO, "Auth key successfully imported."); + + /* Get list of suported authentication methods */ + char* user_authlist = libssh2_userauth_list(session, username, + strlen(username)); + guac_client_log(client, GUAC_LOG_DEBUG, + "Supported authentication methods: %s", user_authlist); + + /* Check if public key auth is suported on the server */ + if (strstr(user_authlist, "publickey") == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Public key authentication not suported"); + return NULL; + } + + /* Attempt public key auth */ + if (libssh2_userauth_publickey(session, username, + (unsigned char*) key->public_key, key->public_key_length, + __sign_callback, (void**) key)) { + + /* Abort on failure */ + char* error_message; + libssh2_session_last_error(session, &error_message, NULL, 0); + guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Public key authentication failed: %s", error_message); + + return NULL; + + } + + /* Return new session on success */ return session; } diff --git a/src/common-ssh/guac_ssh.h b/src/common-ssh/guac_ssh.h index a50a62ec..7027e043 100644 --- a/src/common-ssh/guac_ssh.h +++ b/src/common-ssh/guac_ssh.h @@ -113,8 +113,7 @@ LIBSSH2_SESSION* guac_common_ssh_connect_password(guac_client* client, */ LIBSSH2_SESSION* guac_common_ssh_connect_private_key(guac_client* client, const char* hostname, const char* port, - const char* username, const char* private_key, - const char* passphrase, + const char* username, char* private_key, char* passphrase, int* socket_fd); #endif diff --git a/src/common-ssh/guac_ssh_buffer.c b/src/common-ssh/guac_ssh_buffer.c new file mode 100644 index 00000000..cd07a307 --- /dev/null +++ b/src/common-ssh/guac_ssh_buffer.c @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2015 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 +#include + +#include +#include +#include + +void guac_common_buffer_write_byte(char** buffer, uint8_t value) { + + uint8_t* data = (uint8_t*) *buffer; + *data = value; + + (*buffer)++; + +} + +void guac_common_buffer_write_uint32(char** buffer, uint32_t value) { + + uint8_t* data = (uint8_t*) *buffer; + + data[0] = (value & 0xFF000000) >> 24; + data[1] = (value & 0x00FF0000) >> 16; + data[2] = (value & 0x0000FF00) >> 8; + data[3] = value & 0x000000FF; + + *buffer += 4; + +} + +void guac_common_buffer_write_data(char** buffer, const char* data, int length) { + memcpy(*buffer, data, length); + *buffer += length; +} + +void guac_common_buffer_write_bignum(char** buffer, BIGNUM* value) { + + unsigned char* bn_buffer; + int length; + + /* If zero, just write zero length */ + if (BN_is_zero(value)) { + guac_common_buffer_write_uint32(buffer, 0); + return; + } + + /* Allocate output buffer, add padding byte */ + length = BN_num_bytes(value); + bn_buffer = malloc(length); + + /* Convert BIGNUM */ + BN_bn2bin(value, bn_buffer); + + /* If first byte has high bit set, write padding byte */ + if (bn_buffer[0] & 0x80) { + guac_common_buffer_write_uint32(buffer, length+1); + guac_common_buffer_write_byte(buffer, 0); + } + else + guac_common_buffer_write_uint32(buffer, length); + + /* Write data */ + memcpy(*buffer, bn_buffer, length); + *buffer += length; + + free(bn_buffer); + +} + +void guac_common_buffer_write_string(char** buffer, const char* string, int length) { + guac_common_buffer_write_uint32(buffer, length); + guac_common_buffer_write_data(buffer, string, length); +} + +uint8_t guac_common_buffer_read_byte(char** buffer) { + + uint8_t* data = (uint8_t*) *buffer; + uint8_t value = *data; + + (*buffer)++; + + return value; + +} + +uint32_t guac_common_buffer_read_uint32(char** buffer) { + + uint8_t* data = (uint8_t*) *buffer; + uint32_t value = + (data[0] << 24) + | (data[1] << 16) + | (data[2] << 8) + | data[3]; + + *buffer += 4; + + return value; + +} + +char* guac_common_buffer_read_string(char** buffer, int* length) { + + char* value; + + *length = guac_common_buffer_read_uint32(buffer); + value = *buffer; + + *buffer += *length; + + return value; + +} + diff --git a/src/common-ssh/guac_ssh_buffer.h b/src/common-ssh/guac_ssh_buffer.h new file mode 100644 index 00000000..df9a519e --- /dev/null +++ b/src/common-ssh/guac_ssh_buffer.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015 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_COMMON_SSH_BUFFER_H +#define GUAC_COMMON_SSH_BUFFER_H + +#include "config.h" + +#include +#include + +/** + * Writes the given byte to the given buffer, advancing the buffer pointer by + * one byte. + */ +void guac_common_buffer_write_byte(char** buffer, uint8_t value); + +/** + * Writes the given integer to the given buffer, advancing the buffer pointer + * four bytes. + */ +void guac_common_buffer_write_uint32(char** buffer, uint32_t value); + +/** + * Writes the given string and its length to the given buffer, advancing the + * buffer pointer by the size of the length (four bytes) and the size of the + * string. + */ +void guac_common_buffer_write_string(char** buffer, const char* string, int length); + +/** + * Writes the given BIGNUM the given buffer, advancing the buffer pointer by + * the size of the length (four bytes) and the size of the BIGNUM. + */ +void guac_common_buffer_write_bignum(char** buffer, BIGNUM* value); + +/** + * Writes the given data the given buffer, advancing the buffer pointer by the + * given length. + */ +void guac_common_buffer_write_data(char** buffer, const char* data, int length); + +/** + * Reads a single byte from the given buffer, advancing the buffer by one byte. + */ +uint8_t guac_common_buffer_read_byte(char** buffer); + +/** + * Reads an integer from the given buffer, advancing the buffer by four bytes. + */ +uint32_t guac_common_buffer_read_uint32(char** buffer); + +/** + * Reads a string and its length from the given buffer, advancing the buffer + * by the size of the length (four bytes) and the size of the string, and + * returning a pointer to the buffer. The length of the string is stored in + * the given int. + */ +char* guac_common_buffer_read_string(char** buffer, int* length); + +#endif + diff --git a/src/common-ssh/guac_ssh_key.c b/src/common-ssh/guac_ssh_key.c new file mode 100644 index 00000000..fb9d0f78 --- /dev/null +++ b/src/common-ssh/guac_ssh_key.c @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2015 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 "guac_ssh_buffer.h" +#include "guac_ssh_key.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length, + char* passphrase) { + + guac_common_ssh_key* key; + BIO* key_bio; + + char* public_key; + char* pos; + + /* Create BIO for reading key from memory */ + key_bio = BIO_new_mem_buf(data, length); + + /* If RSA key, load RSA */ + if (length > sizeof(SSH_RSA_KEY_HEADER)-1 + && memcmp(SSH_RSA_KEY_HEADER, data, + sizeof(SSH_RSA_KEY_HEADER)-1) == 0) { + + RSA* rsa_key; + + /* Read key */ + rsa_key = PEM_read_bio_RSAPrivateKey(key_bio, NULL, NULL, passphrase); + if (rsa_key == NULL) + return NULL; + + /* Allocate key */ + key = malloc(sizeof(guac_common_ssh_key)); + key->rsa = rsa_key; + + /* Set type */ + key->type = SSH_KEY_RSA; + + /* Allocate space for public key */ + public_key = malloc(4096); + pos = public_key; + + /* Derive public key */ + guac_common_buffer_write_string(&pos, "ssh-rsa", sizeof("ssh-rsa")-1); + guac_common_buffer_write_bignum(&pos, rsa_key->e); + guac_common_buffer_write_bignum(&pos, rsa_key->n); + + /* Save public key to structure */ + key->public_key = public_key; + key->public_key_length = pos - public_key; + + } + + /* If DSA key, load DSA */ + else if (length > sizeof(SSH_DSA_KEY_HEADER)-1 + && memcmp(SSH_DSA_KEY_HEADER, data, + sizeof(SSH_DSA_KEY_HEADER)-1) == 0) { + + DSA* dsa_key; + + /* Read key */ + dsa_key = PEM_read_bio_DSAPrivateKey(key_bio, NULL, NULL, passphrase); + if (dsa_key == NULL) + return NULL; + + /* Allocate key */ + key = malloc(sizeof(guac_common_ssh_key)); + key->dsa = dsa_key; + + /* Set type */ + key->type = SSH_KEY_DSA; + + /* Allocate space for public key */ + public_key = malloc(4096); + pos = public_key; + + /* Derive public key */ + guac_common_buffer_write_string(&pos, "ssh-dss", sizeof("ssh-dss")-1); + guac_common_buffer_write_bignum(&pos, dsa_key->p); + guac_common_buffer_write_bignum(&pos, dsa_key->q); + guac_common_buffer_write_bignum(&pos, dsa_key->g); + guac_common_buffer_write_bignum(&pos, dsa_key->pub_key); + + /* Save public key to structure */ + key->public_key = public_key; + key->public_key_length = pos - public_key; + + } + + /* Otherwise, unsupported type */ + else { + BIO_free(key_bio); + return NULL; + } + + /* Copy private key to structure */ + key->private_key_length = length; + key->private_key = malloc(length); + memcpy(key->private_key, data, length); + + BIO_free(key_bio); + return key; + +} + +const char* guac_common_ssh_key_error() { + + /* Return static error string */ + return ERR_reason_error_string(ERR_get_error()); + +} + +void guac_common_ssh_key_free(guac_common_ssh_key* key) { + + /* Free key-specific data */ + if (key->type == SSH_KEY_RSA) + RSA_free(key->rsa); + else if (key->type == SSH_KEY_DSA) + DSA_free(key->dsa); + + free(key->public_key); + free(key); +} + +int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data, + int length, unsigned char* sig) { + + const EVP_MD* md; + EVP_MD_CTX md_ctx; + + unsigned char digest[EVP_MAX_MD_SIZE]; + unsigned int dlen, len; + + /* Get SHA1 digest */ + if ((md = EVP_get_digestbynid(NID_sha1)) == NULL) + return -1; + + /* Digest data */ + EVP_DigestInit(&md_ctx, md); + EVP_DigestUpdate(&md_ctx, data, length); + EVP_DigestFinal(&md_ctx, digest, &dlen); + + /* Sign with key */ + switch (key->type) { + + case SSH_KEY_RSA: + if (RSA_sign(NID_sha1, digest, dlen, sig, &len, key->rsa) == 1) + return len; + + case SSH_KEY_DSA: { + + DSA_SIG* dsa_sig = DSA_do_sign(digest, dlen, key->dsa); + if (dsa_sig != NULL) { + + /* Compute size of each half of signature */ + int rlen = BN_num_bytes(dsa_sig->r); + int slen = BN_num_bytes(dsa_sig->s); + + /* Ensure each number is within the required size */ + if (rlen > DSA_SIG_NUMBER_SIZE || slen > DSA_SIG_NUMBER_SIZE) + return -1; + + /* Init to all zeroes */ + memset(sig, 0, DSA_SIG_SIZE); + + /* Add R at the end of the first block of the signature */ + BN_bn2bin(dsa_sig->r, sig + DSA_SIG_SIZE + - DSA_SIG_NUMBER_SIZE - rlen); + + /* Add S at the end of the second block of the signature */ + BN_bn2bin(dsa_sig->s, sig + DSA_SIG_SIZE - slen); + + /* Done */ + DSA_SIG_free(dsa_sig); + return DSA_SIG_SIZE; + + } + + } + + } + + return -1; + +} + diff --git a/src/common-ssh/guac_ssh_key.h b/src/common-ssh/guac_ssh_key.h new file mode 100644 index 00000000..393c7c1d --- /dev/null +++ b/src/common-ssh/guac_ssh_key.h @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2015 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_COMMON_SSH_KEY_H +#define GUAC_COMMON_SSH_KEY_H + +#include "config.h" + +#include + +/** + * The expected header of RSA private keys. + */ +#define SSH_RSA_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----" + +/** + * The expected header of DSA private keys. + */ +#define SSH_DSA_KEY_HEADER "-----BEGIN DSA PRIVATE KEY-----" + +/** + * The size of single number within a DSA signature, in bytes. + */ +#define DSA_SIG_NUMBER_SIZE 20 + +/** + * The size of a DSA signature, in bytes. + */ +#define DSA_SIG_SIZE DSA_SIG_NUMBER_SIZE*2 + +/** + * The type of an SSH key. + */ +typedef enum guac_common_ssh_key_type { + + /** + * RSA key. + */ + SSH_KEY_RSA, + + /** + * DSA key. + */ + SSH_KEY_DSA + +} guac_common_ssh_key_type; + +/** + * Abstraction of a key used for SSH authentication. + */ +typedef struct guac_common_ssh_key { + + /** + * The type of this key. + */ + guac_common_ssh_key_type type; + + /** + * Underlying RSA private key, if any. + */ + RSA* rsa; + + /** + * Underlying DSA private key, if any. + */ + DSA* dsa; + + /** + * The associated public key, encoded as necessary for SSH. + */ + char* public_key; + + /** + * The length of the public key, in bytes. + */ + int public_key_length; + + /** + * The private key, encoded as necessary for SSH. + */ + char* private_key; + + /** + * The length of the private key, in bytes. + */ + int private_key_length; + +} guac_common_ssh_key; + +/** + * Allocates a new key containing the given private key data and specified + * passphrase. If unable to read the key, NULL is returned. + */ +guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length, + char* passphrase); + +/** + * Returns a statically-allocated string describing the most recent SSH key + * error. + * + * @return + * A statically-allocated string describing the most recent SSH key error. + */ +const char* guac_common_ssh_key_error(); + +/** + * Frees all memory associated with the given key. + */ +void guac_common_ssh_key_free(guac_common_ssh_key* key); + +/** + * Signs the given data using the given key, returning the length of the + * signature in bytes, or a value less than zero on error. + */ +int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data, + int length, unsigned char* sig); + +#endif + From ff287aee52ff0fc0e810d3666fcde228d2516bbb Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 10 Jul 2015 01:31:15 -0700 Subject: [PATCH 08/15] GUAC-1171: Separate key/password management from SSH connection. Maintain connection information within session object. --- src/common-ssh/Makefile.am | 6 +- src/common-ssh/guac_ssh.c | 129 ++++++++------------------------- src/common-ssh/guac_ssh.h | 99 +++++++++++++------------ src/common-ssh/guac_ssh_user.c | 84 +++++++++++++++++++++ src/common-ssh/guac_ssh_user.h | 111 ++++++++++++++++++++++++++++ 5 files changed, 281 insertions(+), 148 deletions(-) create mode 100644 src/common-ssh/guac_ssh_user.c create mode 100644 src/common-ssh/guac_ssh_user.h diff --git a/src/common-ssh/Makefile.am b/src/common-ssh/Makefile.am index 8d0f9612..9d6b671f 100644 --- a/src/common-ssh/Makefile.am +++ b/src/common-ssh/Makefile.am @@ -29,13 +29,15 @@ libguac_common_ssh_la_SOURCES = \ guac_sftp.c \ guac_ssh.c \ guac_ssh_buffer.c \ - guac_ssh_key.c + guac_ssh_key.c \ + guac_ssh_user.c noinst_HEADERS = \ guac_sftp.h \ guac_ssh.h \ guac_ssh_buffer.h \ - guac_ssh_key.h + guac_ssh_key.h \ + guac_ssh_user.h libguac_common_ssh_la_CFLAGS = \ -Werror -Wall -pedantic \ diff --git a/src/common-ssh/guac_ssh.c b/src/common-ssh/guac_ssh.c index 87347943..ef101f25 100644 --- a/src/common-ssh/guac_ssh.c +++ b/src/common-ssh/guac_ssh.c @@ -137,32 +137,8 @@ void guac_common_ssh_uninit() { guac_common_ssh_openssl_free_locks(CRYPTO_num_locks()); } -/** - * Connects to the SSH server running at the given hostname and port, but does - * not perform any authentication. Authentication must be immediately performed - * after creation of the session for the session to become usable. If an error - * occurs while connecting, the Guacamole client will automatically and fatally - * abort. - * - * @param client - * The Guacamole client that will be using SSH. - * - * @param hostname - * The hostname of the SSH server to connect to. - * - * @param port - * The port to connect to on the given hostname. - * - * @param socket_fd - * A pointer to an integer in which the newly-allocated file descriptor for - * the SSH socket should be stored. - * - * @return - * A new SSH session if the connection succeeds, or NULL if the connection - * was not successful. - */ -static LIBSSH2_SESSION* guac_common_ssh_connect(guac_client* client, - const char* hostname, const char* port, int* socket_fd) { +guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, + const char* hostname, const char* port) { int retval; @@ -252,30 +228,27 @@ static LIBSSH2_SESSION* guac_common_ssh_connect(guac_client* client, return NULL; } - /* Save file descriptor */ - if (socket_fd != NULL) - *socket_fd = fd; + /* Allocate new session */ + guac_common_ssh_session* common_session = + malloc(sizeof(guac_common_ssh_session)); + + /* Store basic session data */ + common_session->client = client; + common_session->session = session; + common_session->fd = fd; /* Return created session */ - return session; + return common_session; } -LIBSSH2_SESSION* guac_common_ssh_connect_password(guac_client* client, - const char* hostname, const char* port, - const char* username, const char* password, - int* socket_fd) { - - LIBSSH2_SESSION* session = guac_common_ssh_connect(client, - hostname, port, socket_fd); - - /* STUB */ - - return session; - +void guac_common_ssh_destroy_session(guac_common_ssh_session* session) { + libssh2_session_disconnect(session->session, "Bye"); + libssh2_session_free(session->session); + free(session); } -static int __sign_callback(LIBSSH2_SESSION* session, +static int guac_common_ssh_sign_callback(LIBSSH2_SESSION* session, unsigned char** sig, size_t* sig_len, const unsigned char* data, size_t data_len, void **abstract) { @@ -294,73 +267,33 @@ static int __sign_callback(LIBSSH2_SESSION* session, return 0; } -LIBSSH2_SESSION* guac_common_ssh_connect_private_key(guac_client* client, - const char* hostname, const char* port, - const char* username, char* private_key, char* passphrase, - int* socket_fd) { +int guac_common_ssh_authenticate(guac_common_ssh_session* common_session, + guac_common_ssh_user* user) { - LIBSSH2_SESSION* session = guac_common_ssh_connect(client, - hostname, port, socket_fd); + guac_client* client = common_session->client; + LIBSSH2_SESSION* session = common_session->session; - guac_client_log(client, GUAC_LOG_DEBUG, - "Attempting private key import (WITHOUT passphrase)"); + /* Get user credentials */ + char* username = user->username; + guac_common_ssh_key* key = user->private_key; - /* Attempt to read key without passphrase */ - guac_common_ssh_key* key = guac_common_ssh_key_alloc(private_key, - strlen(private_key), ""); - -#if 0 - /* On failure, attempt with passphrase */ - if (key == NULL) { - - /* Log failure of initial attempt */ - guac_client_log(client, GUAC_LOG_DEBUG, - "Initial import failed: %s", guac_common_ssh_key_error()); - - guac_client_log(client, GUAC_LOG_DEBUG, - "Re-attempting private key import (WITH passphrase)"); - - /* Prompt for passphrase if missing */ - if (client_data->key_passphrase[0] == 0) - guac_terminal_prompt(client_data->term, "Key passphrase: ", - client_data->key_passphrase, sizeof(client_data->key_passphrase), false); - - /* Import key with passphrase */ - client_data->key = guac_common_ssh_key_alloc(client_data->key_base64, - strlen(client_data->key_base64), - client_data->key_passphrase); - - /* If still failing, give up */ - if (client_data->key == NULL) { - guac_client_abort(client, - GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, - "Auth key import failed: %s", guac_common_ssh_key_error()); - return NULL; - } - - } /* end decrypt key with passphrase */ -#endif - - /* Success */ - guac_client_log(client, GUAC_LOG_INFO, "Auth key successfully imported."); - - /* Get list of suported authentication methods */ + /* Get list of supported authentication methods */ char* user_authlist = libssh2_userauth_list(session, username, strlen(username)); guac_client_log(client, GUAC_LOG_DEBUG, "Supported authentication methods: %s", user_authlist); - /* Check if public key auth is suported on the server */ + /* Check if public key auth is supported on the server */ if (strstr(user_authlist, "publickey") == NULL) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, - "Public key authentication not suported"); - return NULL; + "Public key authentication not supported"); + return 1; } /* Attempt public key auth */ if (libssh2_userauth_publickey(session, username, (unsigned char*) key->public_key, key->public_key_length, - __sign_callback, (void**) key)) { + guac_common_ssh_sign_callback, (void**) key)) { /* Abort on failure */ char* error_message; @@ -368,12 +301,12 @@ LIBSSH2_SESSION* guac_common_ssh_connect_private_key(guac_client* client, guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, "Public key authentication failed: %s", error_message); - return NULL; + return 1; } - /* Return new session on success */ - return session; + /* Authentication succeeded */ + return 0; } diff --git a/src/common-ssh/guac_ssh.h b/src/common-ssh/guac_ssh.h index 7027e043..90ccca4c 100644 --- a/src/common-ssh/guac_ssh.h +++ b/src/common-ssh/guac_ssh.h @@ -23,9 +23,34 @@ #ifndef GUAC_COMMON_SSH_H #define GUAC_COMMON_SSH_H +#include "guac_ssh_user.h" + #include #include +/** + * An SSH session, backed by libssh2 and associated with a particular + * Guacamole client. + */ +typedef struct guac_common_ssh_session { + + /** + * The Guacamole client using this SSH session. + */ + guac_client* client; + + /** + * The underlying SSH session from libssh2. + */ + LIBSSH2_SESSION* session; + + /** + * The file descriptor of the socket being used for the SSH connection. + */ + int fd; + +} guac_common_ssh_session; + /** * Initializes the underlying SSH and encryption libraries used by Guacamole. * This function must be called before any other guac_common_ssh_*() functions @@ -47,9 +72,9 @@ int guac_common_ssh_init(guac_client* client); void guac_common_ssh_uninit(); /** - * Connects to the SSH server running at the given hostname and port using the - * given username and password for authentication. If an error occurs while - * connecting, the Guacamole client will automatically and fatally abort. + * Connects to the SSH server running at the given hostname and port but does + * not perform any authentication. If an error occurs while connecting, the + * Guacamole client will automatically and fatally abort. * * @param client * The Guacamole client that will be using SSH. @@ -60,61 +85,39 @@ void guac_common_ssh_uninit(); * @param port * The port to connect to on the given hostname. * - * @param username - * The username to authenticate as. - * - * @param password - * The password to provide when authenticating as the given user. - * - * @param socket_fd - * A pointer to an integer in which the newly-allocated file descriptor for - * the SSH socket should be stored. - * * @return - * A new SSH session if the connection and authentication succeed, or - * NULL if the connection or authentication were not successful. + * A new SSH session if the connection succeeds, or NULL if the connection + * was not successful. */ -LIBSSH2_SESSION* guac_common_ssh_connect_password(guac_client* client, - const char* hostname, const char* port, - const char* username, const char* password, - int* socket_fd); +guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, + const char* hostname, const char* port); /** - * Connects to the SSH server running at the given hostname and port using the - * given username and private key for authentication. If an error occurs while - * connecting, the Guacamole client will automatically and fatally abort. + * Disconnects and destroys the given SSH session, freeing all associated + * resources. * - * @param client - * The Guacamole client that will be using SSH. + * @param session + * The SSH session to destroy. + */ +void guac_common_ssh_destroy_session(guac_common_ssh_session* session); + +/** + * Authenticates the given user with the given, existing SSH session. If + * authentication fails, the Guacamole client will automatically and fatally + * abort. * - * @param hostname - * The hostname of the SSH server to connect to. + * @param session + * The SSH session to authenticate with. * - * @param port - * The port to connect to on the given hostname. - * - * @param username - * The username to authenticate as. - * - * @param private_key - * The base64-encoded private key to use when authenticating. - * - * @param passphrase - * The passphrase to use when importing the private key, if any, or NULL - * if no passphrase should be used. - * - * @param socket_fd - * A pointer to an integer in which the newly-allocated file descriptor for - * the SSH socket should be stored. + * @param user + * The user object describing the current user and their associated + * credentials. * * @return - * A new SSH session if the connection and authentication succeed, or - * NULL if the connection or authentication were not successful. + * Zero if authentication succeeds, non-zero if authentication fails. */ -LIBSSH2_SESSION* guac_common_ssh_connect_private_key(guac_client* client, - const char* hostname, const char* port, - const char* username, char* private_key, char* passphrase, - int* socket_fd); +int guac_common_ssh_authenticate(guac_common_ssh_session* session, + guac_common_ssh_user* user); #endif diff --git a/src/common-ssh/guac_ssh_user.c b/src/common-ssh/guac_ssh_user.c new file mode 100644 index 00000000..84aad9c5 --- /dev/null +++ b/src/common-ssh/guac_ssh_user.c @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2015 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 "guac_ssh_key.h" +#include "guac_ssh_user.h" + +#include +#include + +guac_common_ssh_user* guac_common_ssh_create_user(const char* username) { + + guac_common_ssh_user* user = malloc(sizeof(guac_common_ssh_user)); + + /* Init user */ + user->username = strdup(username); + user->password = NULL; + user->private_key = NULL; + + return user; + +} + +void guac_common_ssh_destroy_user(guac_common_ssh_user* user) { + + /* Free private key, if present */ + if (user->private_key != NULL) + guac_common_ssh_key_free(user->private_key); + + /* Free all other data */ + free(user->password); + free(user->username); + +} + +void guac_common_ssh_user_set_password(guac_common_ssh_user* user, + const char* password) { + + /* Replace current password with given value */ + free(user->password); + user->password = strdup(password); + +} + +int guac_common_ssh_user_import_key(guac_common_ssh_user* user, + char* private_key, char* passphrase) { + + /* Free existing private key, if present */ + if (user->private_key != NULL) + guac_common_ssh_key_free(user->private_key); + + /* Attempt to read key without passphrase if none given */ + if (passphrase == NULL) + user->private_key = guac_common_ssh_key_alloc(private_key, + strlen(private_key), ""); + + /* Otherwise, use provided passphrase */ + else + user->private_key = guac_common_ssh_key_alloc(private_key, + strlen(private_key), passphrase); + + /* Fail if key could not be read */ + return user->private_key == NULL; + +} + diff --git a/src/common-ssh/guac_ssh_user.h b/src/common-ssh/guac_ssh_user.h new file mode 100644 index 00000000..760523a5 --- /dev/null +++ b/src/common-ssh/guac_ssh_user.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2015 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_COMMON_SSH_USER_H +#define GUAC_COMMON_SSH_USER_H + +#include "guac_ssh_key.h" + +/** + * Data describing an SSH user, including their credentials. + */ +typedef struct guac_common_ssh_user { + + /** + * The username of this user. + */ + char* username; + + /** + * The password which should be used to authenticate this user, if any, or + * NULL if a private key will be used instead. + */ + char* password; + + /** + * The private key which should be used to authenticate this user, if any, + * or NULL if a password will be used instead. + */ + guac_common_ssh_key* private_key; + +} guac_common_ssh_user; + +/** + * Creates a new SSH user with the given username. When additionally populated + * with a password or private key, this user can then be used for + * authentication. + * + * @param username + * The username of the user being created. + * + * @return + * A new SSH user having the given username, but no associated password + * or private key. + */ +guac_common_ssh_user* guac_common_ssh_create_user(const char* username); + +/** + * Destroys the given user object, releasing all associated resources. + * + * @param user + * The user to destroy. + */ +void guac_common_ssh_destroy_user(guac_common_ssh_user* user); + +/** + * Associates the given user with the given password, such that that password + * is used for future authentication attempts. + * + * @param user + * The user to associate with the given password. + * + * @param password + * The password to associate with the given user. + */ +void guac_common_ssh_user_set_password(guac_common_ssh_user* user, + const char* password); + +/** + * Imports the given private key, associating that key with the given user. If + * necessary to decrypt the key, a passphrase may be specified. The private key + * must be provided in base64 form. If the private key is imported + * successfully, it will be used for future authentication attempts. + * + * @param user + * The user to associate with the given private key. + * + * @param private_key + * The base64-encoded private key to import. + * + * @param passphrase + * The passphrase to use to decrypt the given private key, or NULL if no + * passphrase should be used. + * + * @return + * Zero if the private key is successfully imported, or non-zero if the + * private key could not be imported due to an error. + */ +int guac_common_ssh_user_import_key(guac_common_ssh_user* user, + char* private_key, char* passphrase); + +#endif + From 98503a9fb7703863fbb930a491e141143d21d27e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 10 Jul 2015 02:00:23 -0700 Subject: [PATCH 09/15] GUAC-1171: Initial refactor to require that user credentials be provided prior to connect. --- src/common-ssh/guac_ssh.c | 201 +++++++++++++++++++++++++------------- src/common-ssh/guac_ssh.h | 38 +++---- 2 files changed, 147 insertions(+), 92 deletions(-) diff --git a/src/common-ssh/guac_ssh.c b/src/common-ssh/guac_ssh.c index ef101f25..76b293bc 100644 --- a/src/common-ssh/guac_ssh.c +++ b/src/common-ssh/guac_ssh.c @@ -137,8 +137,126 @@ void guac_common_ssh_uninit() { guac_common_ssh_openssl_free_locks(CRYPTO_num_locks()); } +static int guac_common_ssh_sign_callback(LIBSSH2_SESSION* session, + unsigned char** sig, size_t* sig_len, + const unsigned char* data, size_t data_len, void **abstract) { + + guac_common_ssh_key* key = (guac_common_ssh_key*) abstract; + int length; + + /* Allocate space for signature */ + *sig = malloc(4096); + + /* Sign with key */ + length = guac_common_ssh_key_sign(key, (const char*) data, data_len, *sig); + if (length < 0) + return 1; + + *sig_len = length; + return 0; +} + +/** + * Callback for the keyboard-interactive authentication method. Currently + * suports just one prompt for the password. + */ +static void guac_common_ssh_kbd_callback(const char *name, int name_len, + const char *instruction, int instruction_len, int num_prompts, + const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, + LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, + void **abstract) { + + guac_common_ssh_session* common_session = + (guac_common_ssh_session*) *abstract; + + guac_client* client = common_session->client; + + /* Send password if only one prompt */ + if (num_prompts == 1) { + char* password = common_session->user->password; + responses[0].text = strdup(password); + responses[0].length = strlen(password); + } + + /* If more than one prompt, a single password is not enough */ + else + guac_client_log(client, GUAC_LOG_WARNING, + "Unsupported number of keyboard-interactive prompts: %i", + num_prompts); + +} + +static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) { + + guac_client* client = common_session->client; + guac_common_ssh_user* user = common_session->user; + LIBSSH2_SESSION* session = common_session->session; + + /* Get user credentials */ + char* username = user->username; + char* password = user->password; + guac_common_ssh_key* key = user->private_key; + + /* Get list of supported authentication methods */ + char* user_authlist = libssh2_userauth_list(session, username, + strlen(username)); + guac_client_log(client, GUAC_LOG_DEBUG, + "Supported authentication methods: %s", user_authlist); + + /* Authenticate with private key, if provided */ + if (key != NULL) { + + /* Check if public key auth is supported on the server */ + if (strstr(user_authlist, "publickey") == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Public key authentication not supported"); + return 1; + } + + /* Attempt public key auth */ + if (libssh2_userauth_publickey(session, username, + (unsigned char*) key->public_key, key->public_key_length, + guac_common_ssh_sign_callback, (void**) key)) { + + /* Abort on failure */ + char* error_message; + libssh2_session_last_error(session, &error_message, NULL, 0); + guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Public key authentication failed: %s", error_message); + + return 1; + + } + + /* Private key authentication succeeded */ + return 0; + + } + + /* Authenticate with password */ + if (strstr(user_authlist, "password") != NULL) { + guac_client_log(client, GUAC_LOG_DEBUG, + "Using password authentication method"); + return libssh2_userauth_password(session, username, password); + } + + /* Authenticate with password via keyboard-interactive auth */ + if (strstr(user_authlist, "keyboard-interactive") != NULL) { + guac_client_log(client, GUAC_LOG_DEBUG, + "Using keyboard-interactive authentication method"); + return libssh2_userauth_keyboard_interactive(session, username, + &guac_common_ssh_kbd_callback); + } + + /* No known authentication types available */ + guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_BAD_TYPE, + "No known authentication methods"); + return 1; + +} + guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, - const char* hostname, const char* port) { + const char* hostname, const char* port, guac_common_ssh_user* user) { int retval; @@ -212,12 +330,17 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, /* Free addrinfo */ freeaddrinfo(addresses); + /* Allocate new session */ + guac_common_ssh_session* common_session = + malloc(sizeof(guac_common_ssh_session)); + /* Open SSH session */ LIBSSH2_SESSION* session = libssh2_session_init_ex(NULL, NULL, - NULL, client); + NULL, common_session); if (session == NULL) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Session allocation failed."); + free(common_session); return NULL; } @@ -225,18 +348,22 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, if (libssh2_session_handshake(session, fd)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "SSH handshake failed."); + free(common_session); return NULL; } - /* Allocate new session */ - guac_common_ssh_session* common_session = - malloc(sizeof(guac_common_ssh_session)); - /* Store basic session data */ common_session->client = client; + common_session->user = user; common_session->session = session; common_session->fd = fd; + /* Attempt authentication */ + if (guac_common_ssh_authenticate(common_session)) { + free(common_session); + return NULL; + } + /* Return created session */ return common_session; @@ -248,65 +375,3 @@ void guac_common_ssh_destroy_session(guac_common_ssh_session* session) { free(session); } -static int guac_common_ssh_sign_callback(LIBSSH2_SESSION* session, - unsigned char** sig, size_t* sig_len, - const unsigned char* data, size_t data_len, void **abstract) { - - guac_common_ssh_key* key = (guac_common_ssh_key*) abstract; - int length; - - /* Allocate space for signature */ - *sig = malloc(4096); - - /* Sign with key */ - length = guac_common_ssh_key_sign(key, (const char*) data, data_len, *sig); - if (length < 0) - return 1; - - *sig_len = length; - return 0; -} - -int guac_common_ssh_authenticate(guac_common_ssh_session* common_session, - guac_common_ssh_user* user) { - - guac_client* client = common_session->client; - LIBSSH2_SESSION* session = common_session->session; - - /* Get user credentials */ - char* username = user->username; - guac_common_ssh_key* key = user->private_key; - - /* Get list of supported authentication methods */ - char* user_authlist = libssh2_userauth_list(session, username, - strlen(username)); - guac_client_log(client, GUAC_LOG_DEBUG, - "Supported authentication methods: %s", user_authlist); - - /* Check if public key auth is supported on the server */ - if (strstr(user_authlist, "publickey") == NULL) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, - "Public key authentication not supported"); - return 1; - } - - /* Attempt public key auth */ - if (libssh2_userauth_publickey(session, username, - (unsigned char*) key->public_key, key->public_key_length, - guac_common_ssh_sign_callback, (void**) key)) { - - /* Abort on failure */ - char* error_message; - libssh2_session_last_error(session, &error_message, NULL, 0); - guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, - "Public key authentication failed: %s", error_message); - - return 1; - - } - - /* Authentication succeeded */ - return 0; - -} - diff --git a/src/common-ssh/guac_ssh.h b/src/common-ssh/guac_ssh.h index 90ccca4c..cf9e535e 100644 --- a/src/common-ssh/guac_ssh.h +++ b/src/common-ssh/guac_ssh.h @@ -39,6 +39,11 @@ typedef struct guac_common_ssh_session { */ guac_client* client; + /** + * The user that will be authenticating via SSH. + */ + guac_common_ssh_user* user; + /** * The underlying SSH session from libssh2. */ @@ -72,9 +77,9 @@ int guac_common_ssh_init(guac_client* client); void guac_common_ssh_uninit(); /** - * Connects to the SSH server running at the given hostname and port but does - * not perform any authentication. If an error occurs while connecting, the - * Guacamole client will automatically and fatally abort. + * Connects to the SSH server running at the given hostname and port, and + * authenticates as the given user. If an error occurs while connecting or + * authenticating, the Guacamole client will automatically and fatally abort. * * @param client * The Guacamole client that will be using SSH. @@ -85,12 +90,15 @@ void guac_common_ssh_uninit(); * @param port * The port to connect to on the given hostname. * + * @param user + * The user to authenticate as, once connected. + * * @return - * A new SSH session if the connection succeeds, or NULL if the connection - * was not successful. + * A new SSH session if the connection and authentication succeed, or NULL + * if the connection or authentication were not successful. */ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, - const char* hostname, const char* port); + const char* hostname, const char* port, guac_common_ssh_user* user); /** * Disconnects and destroys the given SSH session, freeing all associated @@ -101,23 +109,5 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, */ void guac_common_ssh_destroy_session(guac_common_ssh_session* session); -/** - * Authenticates the given user with the given, existing SSH session. If - * authentication fails, the Guacamole client will automatically and fatally - * abort. - * - * @param session - * The SSH session to authenticate with. - * - * @param user - * The user object describing the current user and their associated - * credentials. - * - * @return - * Zero if authentication succeeds, non-zero if authentication fails. - */ -int guac_common_ssh_authenticate(guac_common_ssh_session* session, - guac_common_ssh_user* user); - #endif From 0fcea2738bc2d58a930c930c6a7bab72a914c87e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 10 Jul 2015 12:43:35 -0700 Subject: [PATCH 10/15] GUAC-1171: Migrate to common SSH within client. --- src/protocols/ssh/Makefile.am | 8 +- src/protocols/ssh/client.c | 1 - src/protocols/ssh/client.h | 11 +- src/protocols/ssh/guac_handlers.c | 15 +- src/protocols/ssh/ssh_buffer.c | 136 ----------- src/protocols/ssh/ssh_buffer.h | 82 ------- src/protocols/ssh/ssh_client.c | 382 +++++++++--------------------- src/protocols/ssh/ssh_key.c | 216 ----------------- src/protocols/ssh/ssh_key.h | 137 ----------- 9 files changed, 121 insertions(+), 867 deletions(-) delete mode 100644 src/protocols/ssh/ssh_buffer.c delete mode 100644 src/protocols/ssh/ssh_buffer.h delete mode 100644 src/protocols/ssh/ssh_key.c delete mode 100644 src/protocols/ssh/ssh_key.h diff --git a/src/protocols/ssh/Makefile.am b/src/protocols/ssh/Makefile.am index e13bc253..ad1a2f56 100644 --- a/src/protocols/ssh/Makefile.am +++ b/src/protocols/ssh/Makefile.am @@ -30,18 +30,14 @@ libguac_client_ssh_la_SOURCES = \ clipboard.c \ guac_handlers.c \ sftp.c \ - ssh_buffer.c \ - ssh_client.c \ - ssh_key.c + ssh_client.c noinst_HEADERS = \ client.h \ clipboard.h \ guac_handlers.h \ sftp.h \ - ssh_buffer.h \ - ssh_client.h \ - ssh_key.h + ssh_client.h # Add agent sources if enabled if ENABLE_SSH_AGENT diff --git a/src/protocols/ssh/client.c b/src/protocols/ssh/client.c index b3c5bf26..63c5f2c8 100644 --- a/src/protocols/ssh/client.c +++ b/src/protocols/ssh/client.c @@ -142,7 +142,6 @@ int guac_client_init(guac_client* client, int argc, char** argv) { strcpy(client_data->password, argv[IDX_PASSWORD]); /* Init public key auth information */ - client_data->key = NULL; strcpy(client_data->key_base64, argv[IDX_PRIVATE_KEY]); strcpy(client_data->key_passphrase, argv[IDX_PASSPHRASE]); diff --git a/src/protocols/ssh/client.h b/src/protocols/ssh/client.h index 39d8d544..40f97ab2 100644 --- a/src/protocols/ssh/client.h +++ b/src/protocols/ssh/client.h @@ -26,8 +26,8 @@ #include "config.h" +#include "guac_ssh.h" #include "sftp.h" -#include "ssh_key.h" #include "terminal.h" #include @@ -77,11 +77,6 @@ typedef struct ssh_guac_client_data { */ char key_passphrase[1024]; - /** - * The private key to use for authentication, if any. - */ - ssh_key* key; - /** * The name of the font to use for display rendering. */ @@ -117,12 +112,12 @@ typedef struct ssh_guac_client_data { /** * SSH session, used by the SSH client thread. */ - LIBSSH2_SESSION* session; + guac_common_ssh_session* session; /** * The distinct SSH session used for SFTP. */ - LIBSSH2_SESSION* sftp_ssh_session; + guac_common_ssh_session* sftp_ssh_session; /** * SFTP session, used for file transfers. diff --git a/src/protocols/ssh/guac_handlers.c b/src/protocols/ssh/guac_handlers.c index ea01e884..8293afe2 100644 --- a/src/protocols/ssh/guac_handlers.c +++ b/src/protocols/ssh/guac_handlers.c @@ -24,7 +24,7 @@ #include "client.h" #include "guac_handlers.h" -#include "ssh_key.h" +#include "guac_ssh.h" #include "terminal.h" #include @@ -106,10 +106,8 @@ int ssh_guac_client_free_handler(guac_client* client) { libssh2_sftp_shutdown(guac_client_data->sftp_session); /* Disconnect SSH session corresponding to the SFTP session */ - if (guac_client_data->sftp_ssh_session) { - libssh2_session_disconnect(guac_client_data->sftp_ssh_session, "Bye"); - libssh2_session_free(guac_client_data->sftp_ssh_session); - } + if (guac_client_data->sftp_ssh_session) + guac_common_ssh_destroy_session(guac_client_data->sftp_ssh_session); /* Clean up the SFTP filesystem object */ if (guac_client_data->sftp_filesystem) @@ -117,15 +115,12 @@ int ssh_guac_client_free_handler(guac_client* client) { /* Free session */ if (guac_client_data->session != NULL) - libssh2_session_free(guac_client_data->session); - - /* Free auth key */ - if (guac_client_data->key != NULL) - ssh_key_free(guac_client_data->key); + guac_common_ssh_destroy_session(guac_client_data->session); /* Free generic data struct */ free(client->data); + guac_common_ssh_uninit(); return 0; } diff --git a/src/protocols/ssh/ssh_buffer.c b/src/protocols/ssh/ssh_buffer.c deleted file mode 100644 index 4d4d852a..00000000 --- a/src/protocols/ssh/ssh_buffer.c +++ /dev/null @@ -1,136 +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 -#include - -#include -#include -#include - -void buffer_write_byte(char** buffer, uint8_t value) { - - uint8_t* data = (uint8_t*) *buffer; - *data = value; - - (*buffer)++; - -} - -void buffer_write_uint32(char** buffer, uint32_t value) { - - uint8_t* data = (uint8_t*) *buffer; - - data[0] = (value & 0xFF000000) >> 24; - data[1] = (value & 0x00FF0000) >> 16; - data[2] = (value & 0x0000FF00) >> 8; - data[3] = value & 0x000000FF; - - *buffer += 4; - -} - -void buffer_write_data(char** buffer, const char* data, int length) { - memcpy(*buffer, data, length); - *buffer += length; -} - -void buffer_write_bignum(char** buffer, BIGNUM* value) { - - unsigned char* bn_buffer; - int length; - - /* If zero, just write zero length */ - if (BN_is_zero(value)) { - buffer_write_uint32(buffer, 0); - return; - } - - /* Allocate output buffer, add padding byte */ - length = BN_num_bytes(value); - bn_buffer = malloc(length); - - /* Convert BIGNUM */ - BN_bn2bin(value, bn_buffer); - - /* If first byte has high bit set, write padding byte */ - if (bn_buffer[0] & 0x80) { - buffer_write_uint32(buffer, length+1); - buffer_write_byte(buffer, 0); - } - else - buffer_write_uint32(buffer, length); - - /* Write data */ - memcpy(*buffer, bn_buffer, length); - *buffer += length; - - free(bn_buffer); - -} - -void buffer_write_string(char** buffer, const char* string, int length) { - buffer_write_uint32(buffer, length); - buffer_write_data(buffer, string, length); -} - -uint8_t buffer_read_byte(char** buffer) { - - uint8_t* data = (uint8_t*) *buffer; - uint8_t value = *data; - - (*buffer)++; - - return value; - -} - -uint32_t buffer_read_uint32(char** buffer) { - - uint8_t* data = (uint8_t*) *buffer; - uint32_t value = - (data[0] << 24) - | (data[1] << 16) - | (data[2] << 8) - | data[3]; - - *buffer += 4; - - return value; - -} - -char* buffer_read_string(char** buffer, int* length) { - - char* value; - - *length = buffer_read_uint32(buffer); - value = *buffer; - - *buffer += *length; - - return value; - -} - diff --git a/src/protocols/ssh/ssh_buffer.h b/src/protocols/ssh/ssh_buffer.h deleted file mode 100644 index cb7a6c13..00000000 --- a/src/protocols/ssh/ssh_buffer.h +++ /dev/null @@ -1,82 +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. - */ - - -#ifndef _GUAC_SSH_BUFFER_H -#define _GUAC_SSH_BUFFER_H - -#include "config.h" - -#include -#include - -/** - * Writes the given byte to the given buffer, advancing the buffer pointer by - * one byte. - */ -void buffer_write_byte(char** buffer, uint8_t value); - -/** - * Writes the given integer to the given buffer, advancing the buffer pointer - * four bytes. - */ -void buffer_write_uint32(char** buffer, uint32_t value); - -/** - * Writes the given string and its length to the given buffer, advancing the - * buffer pointer by the size of the length (four bytes) and the size of the - * string. - */ -void buffer_write_string(char** buffer, const char* string, int length); - -/** - * Writes the given BIGNUM the given buffer, advancing the buffer pointer by - * the size of the length (four bytes) and the size of the BIGNUM. - */ -void buffer_write_bignum(char** buffer, BIGNUM* value); - -/** - * Writes the given data the given buffer, advancing the buffer pointer by the - * given length. - */ -void buffer_write_data(char** buffer, const char* data, int length); - -/** - * Reads a single byte from the given buffer, advancing the buffer by one byte. - */ -uint8_t buffer_read_byte(char** buffer); - -/** - * Reads an integer from the given buffer, advancing the buffer by four bytes. - */ -uint32_t buffer_read_uint32(char** buffer); - -/** - * Reads a string and its length from the given buffer, advancing the buffer - * by the size of the length (four bytes) and the size of the string, and - * returning a pointer to the buffer. The length of the string is stored in - * the given int. - */ -char* buffer_read_string(char** buffer, int* length); - -#endif - diff --git a/src/protocols/ssh/ssh_client.c b/src/protocols/ssh/ssh_client.c index 301f83e0..1ac672be 100644 --- a/src/protocols/ssh/ssh_client.c +++ b/src/protocols/ssh/ssh_client.c @@ -25,7 +25,6 @@ #include "client.h" #include "guac_ssh.h" #include "sftp.h" -#include "ssh_key.h" #include "terminal.h" #ifdef ENABLE_SSH_AGENT @@ -57,6 +56,96 @@ #include #include +/** + * Produces a new user object containing a username and password or private + * key, prompting the user as necessary to obtain that information. + * + * @param client + * The Guacamole client containing any existing user data, as well as the + * terminal to use when prompting the user. + * + * @return + * A new user object containing the user's username and other credentials. + */ +static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) { + + ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; + + guac_common_ssh_user* user; + + /* Get username */ + if (client_data->username[0] == 0) + guac_terminal_prompt(client_data->term, "Login as: ", + client_data->username, sizeof(client_data->username), true); + + /* Create user object from username */ + user = guac_common_ssh_create_user(client_data->username); + + /* If key specified, import */ + if (client_data->key_base64[0] != 0) { + + guac_client_log(client, GUAC_LOG_DEBUG, + "Attempting private key import (WITHOUT passphrase)"); + + /* Attempt to read key without passphrase */ + if (guac_common_ssh_user_import_key(user, + client_data->key_base64, NULL)) { + + /* Log failure of initial attempt */ + guac_client_log(client, GUAC_LOG_DEBUG, + "Initial import failed: %s", + guac_common_ssh_key_error()); + + guac_client_log(client, GUAC_LOG_DEBUG, + "Re-attempting private key import (WITH passphrase)"); + + /* Prompt for passphrase if missing */ + if (client_data->key_passphrase[0] == 0) + guac_terminal_prompt(client_data->term, "Key passphrase: ", + client_data->key_passphrase, + sizeof(client_data->key_passphrase), false); + + /* Reattempt import with passphrase */ + if (guac_common_ssh_user_import_key(user, + client_data->key_base64, + client_data->key_passphrase)) { + + /* If still failing, give up */ + guac_client_abort(client, + GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Auth key import failed: %s", + guac_common_ssh_key_error()); + + guac_common_ssh_destroy_user(user); + return NULL; + + } + + } /* end decrypt key with passphrase */ + + /* Success */ + guac_client_log(client, GUAC_LOG_INFO, + "Auth key successfully imported."); + + } /* end if key given */ + + /* Otherwise, get password if not provided */ + else if (client_data->password[0] == 0) { + + guac_terminal_prompt(client_data->term, "Password: ", + client_data->password, sizeof(client_data->password), false); + + guac_common_ssh_user_set_password(user, client_data->password); + + } + + /* Clear screen of any prompts */ + guac_terminal_printf(client_data->term, "\x1B[H\x1B[J"); + + return user; + +} + void* ssh_input_thread(void* data) { guac_client* client = (guac_client*) data; @@ -76,213 +165,13 @@ void* ssh_input_thread(void* data) { } -static int __sign_callback(LIBSSH2_SESSION* session, - unsigned char** sig, size_t* sig_len, - const unsigned char* data, size_t data_len, void **abstract) { - - ssh_key* key = (ssh_key*) abstract; - int length; - - /* Allocate space for signature */ - *sig = malloc(4096); - - /* Sign with key */ - length = ssh_key_sign(key, (const char*) data, data_len, *sig); - if (length < 0) - return 1; - - *sig_len = length; - return 0; -} - -/** - * Callback for the keyboard-interactive authentication method. Currently - * suports just one prompt for the password. - */ -static void __kbd_callback(const char *name, int name_len, - const char *instruction, int instruction_len, - int num_prompts, - const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, - LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, - void **abstract) { - - guac_client* client = (guac_client*) *abstract; - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - - if (num_prompts == 1) { - responses[0].text = strdup(client_data->password); - responses[0].length = strlen(client_data->password); - } - else - guac_client_log(client, GUAC_LOG_WARNING, - "Unsupported number of keyboard-interactive prompts: %i", - num_prompts); - -} - -static LIBSSH2_SESSION* __guac_ssh_create_session(guac_client* client, - int* socket_fd) { - - int retval; - - int fd; - struct addrinfo* addresses; - struct addrinfo* current_address; - - char connected_address[1024]; - char connected_port[64]; - char *user_authlist; - - ssh_guac_client_data* client_data = (ssh_guac_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(client, GUAC_LOG_DEBUG, "Unable to resolve host: %s", gai_strerror(retval)); - - /* Connect */ - if (connect(fd, current_address->ai_addr, - current_address->ai_addrlen) == 0) { - - guac_client_log(client, GUAC_LOG_DEBUG, "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(client, GUAC_LOG_DEBUG, "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 SSH session */ - LIBSSH2_SESSION* session = libssh2_session_init_ex(NULL, NULL, - NULL, client); - if (session == NULL) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Session allocation failed."); - return NULL; - } - - /* Perform handshake */ - if (libssh2_session_handshake(session, fd)) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "SSH handshake failed."); - return NULL; - } - - /* Save file descriptor */ - if (socket_fd != NULL) - *socket_fd = fd; - - /* Get list of suported authentication methods */ - user_authlist = libssh2_userauth_list(session, client_data->username, strlen(client_data->username)); - guac_client_log(client, GUAC_LOG_DEBUG, "Supported authentication methods: %s", user_authlist); - - /* Authenticate with key if available */ - if (client_data->key != NULL) { - - /* Check if public key auth is suported on the server */ - if (strstr(user_authlist, "publickey") == NULL) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, - "Public key authentication not suported"); - return NULL; - } - - if (!libssh2_userauth_publickey(session, client_data->username, - (unsigned char*) client_data->key->public_key, - client_data->key->public_key_length, - __sign_callback, (void**) client_data->key)) - return session; - else { - char* error_message; - libssh2_session_last_error(session, &error_message, NULL, 0); - guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, - "Public key authentication failed: %s", error_message); - return NULL; - } - } - - /* Authenticate with password */ - if (strstr(user_authlist, "password") != NULL) { - guac_client_log(client, GUAC_LOG_DEBUG, "Using password authentication method"); - retval = libssh2_userauth_password(session, client_data->username, client_data->password); - } - else if (strstr(user_authlist, "keyboard-interactive") != NULL) { - guac_client_log(client, GUAC_LOG_DEBUG, "Using keyboard-interactive authentication method"); - retval = libssh2_userauth_keyboard_interactive(session, client_data->username, &__kbd_callback); - } - else { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_BAD_TYPE, "No known authentication methods"); - return NULL; - } - - if (retval == 0) - return session; - - else { - char* error_message; - libssh2_session_last_error(session, &error_message, NULL, 0); - guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, - "Password authentication failed: %s", error_message); - return NULL; - } - -} - void* ssh_client_thread(void* data) { guac_client* client = (guac_client*) data; ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - char name[1024]; - guac_socket* socket = client->socket; char buffer[8192]; - int bytes_read = -1234; - - int socket_fd; pthread_t input_thread; @@ -290,81 +179,31 @@ void* ssh_client_thread(void* data) { if (guac_common_ssh_init(client)) return NULL; - /* Get username */ - if (client_data->username[0] == 0) - guac_terminal_prompt(client_data->term, "Login as: ", - client_data->username, sizeof(client_data->username), true); + /* Get user and credentials */ + guac_common_ssh_user* user = guac_ssh_get_user(client); /* Send new name */ - snprintf(name, sizeof(name)-1, "%s@%s", client_data->username, client_data->hostname); + char name[1024]; + snprintf(name, sizeof(name)-1, "%s@%s", + client_data->username, client_data->hostname); guac_protocol_send_name(socket, name); - /* If key specified, import */ - if (client_data->key_base64[0] != 0) { - - guac_client_log(client, GUAC_LOG_DEBUG, - "Attempting private key import (WITHOUT passphrase)"); - - /* Attempt to read key without passphrase */ - client_data->key = ssh_key_alloc(client_data->key_base64, - strlen(client_data->key_base64), ""); - - /* On failure, attempt with passphrase */ - if (client_data->key == NULL) { - - /* Log failure of initial attempt */ - guac_client_log(client, GUAC_LOG_DEBUG, - "Initial import failed: %s", ssh_key_error()); - - guac_client_log(client, GUAC_LOG_DEBUG, - "Re-attempting private key import (WITH passphrase)"); - - /* Prompt for passphrase if missing */ - if (client_data->key_passphrase[0] == 0) - guac_terminal_prompt(client_data->term, "Key passphrase: ", - client_data->key_passphrase, sizeof(client_data->key_passphrase), false); - - /* Import key with passphrase */ - client_data->key = ssh_key_alloc(client_data->key_base64, - strlen(client_data->key_base64), - client_data->key_passphrase); - - /* If still failing, give up */ - if (client_data->key == NULL) { - guac_client_abort(client, - GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, - "Auth key import failed: %s", ssh_key_error()); - return NULL; - } - - } /* end decrypt key with passphrase */ - - /* Success */ - guac_client_log(client, GUAC_LOG_INFO, "Auth key successfully imported."); - - } /* end if key given */ - - /* Otherwise, get password if not provided */ - else if (client_data->password[0] == 0) - guac_terminal_prompt(client_data->term, "Password: ", - client_data->password, sizeof(client_data->password), false); - - /* Clear screen */ - guac_terminal_printf(client_data->term, "\x1B[H\x1B[J"); - /* Open SSH session */ - client_data->session = __guac_ssh_create_session(client, &socket_fd); + client_data->session = guac_common_ssh_create_session(client, + client_data->hostname, client_data->port, user); if (client_data->session == NULL) { - /* Already aborted within __guac_ssh_create_session() */ + /* Already aborted within guac_common_ssh_create_session() */ return NULL; } pthread_mutex_init(&client_data->term_channel_lock, NULL); /* Open channel for terminal */ - client_data->term_channel = libssh2_channel_open_session(client_data->session); + client_data->term_channel = + libssh2_channel_open_session(client_data->session->session); if (client_data->term_channel == NULL) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to open terminal channel."); + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + "Unable to open terminal channel."); return NULL; } @@ -393,14 +232,15 @@ void* ssh_client_thread(void* data) { /* Create SSH session specific for SFTP */ guac_client_log(client, GUAC_LOG_DEBUG, "Reconnecting for SFTP..."); - client_data->sftp_ssh_session = __guac_ssh_create_session(client, NULL); + client_data->sftp_ssh_session = guac_common_ssh_create_session(client, + client_data->hostname, client_data->port, user); if (client_data->sftp_ssh_session == NULL) { - /* Already aborted within __guac_ssh_create_session() */ + /* Already aborted within guac_common_ssh_create_session() */ return NULL; } /* Request SFTP */ - client_data->sftp_session = libssh2_sftp_init(client_data->sftp_ssh_session); + client_data->sftp_session = libssh2_sftp_init(client_data->sftp_ssh_session->session); if (client_data->sftp_session == NULL) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to start SFTP session."); return NULL; @@ -439,10 +279,10 @@ void* ssh_client_thread(void* data) { } /* Set non-blocking */ - libssh2_session_set_blocking(client_data->session, 0); + libssh2_session_set_blocking(client_data->session->session, 0); /* While data available, write to terminal */ - bytes_read = 0; + int bytes_read = 0; for (;;) { /* Track total amount of data read */ @@ -491,13 +331,14 @@ void* ssh_client_thread(void* data) { struct timeval timeout; FD_ZERO(&fds); - FD_SET(socket_fd, &fds); + FD_SET(client_data->session->fd, &fds); /* Wait for one second */ timeout.tv_sec = 1; timeout.tv_usec = 0; - if (select(socket_fd+1, &fds, NULL, NULL, &timeout) < 0) + if (select(client_data->session->fd + 1, &fds, + NULL, NULL, &timeout) < 0) break; } @@ -508,7 +349,6 @@ void* ssh_client_thread(void* data) { pthread_join(input_thread, NULL); pthread_mutex_destroy(&client_data->term_channel_lock); - guac_common_ssh_uninit(); guac_client_log(client, GUAC_LOG_INFO, "SSH connection ended."); return NULL; diff --git a/src/protocols/ssh/ssh_key.c b/src/protocols/ssh/ssh_key.c deleted file mode 100644 index 4e532af8..00000000 --- a/src/protocols/ssh/ssh_key.c +++ /dev/null @@ -1,216 +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 "ssh_buffer.h" -#include "ssh_key.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -ssh_key* ssh_key_alloc(char* data, int length, char* passphrase) { - - ssh_key* key; - BIO* key_bio; - - char* public_key; - char* pos; - - /* Create BIO for reading key from memory */ - key_bio = BIO_new_mem_buf(data, length); - - /* If RSA key, load RSA */ - if (length > sizeof(SSH_RSA_KEY_HEADER)-1 - && memcmp(SSH_RSA_KEY_HEADER, data, - sizeof(SSH_RSA_KEY_HEADER)-1) == 0) { - - RSA* rsa_key; - - /* Read key */ - rsa_key = PEM_read_bio_RSAPrivateKey(key_bio, NULL, NULL, passphrase); - if (rsa_key == NULL) - return NULL; - - /* Allocate key */ - key = malloc(sizeof(ssh_key)); - key->rsa = rsa_key; - - /* Set type */ - key->type = SSH_KEY_RSA; - - /* Allocate space for public key */ - public_key = malloc(4096); - pos = public_key; - - /* Derive public key */ - buffer_write_string(&pos, "ssh-rsa", sizeof("ssh-rsa")-1); - buffer_write_bignum(&pos, rsa_key->e); - buffer_write_bignum(&pos, rsa_key->n); - - /* Save public key to structure */ - key->public_key = public_key; - key->public_key_length = pos - public_key; - - } - - /* If DSA key, load DSA */ - else if (length > sizeof(SSH_DSA_KEY_HEADER)-1 - && memcmp(SSH_DSA_KEY_HEADER, data, - sizeof(SSH_DSA_KEY_HEADER)-1) == 0) { - - DSA* dsa_key; - - /* Read key */ - dsa_key = PEM_read_bio_DSAPrivateKey(key_bio, NULL, NULL, passphrase); - if (dsa_key == NULL) - return NULL; - - /* Allocate key */ - key = malloc(sizeof(ssh_key)); - key->dsa = dsa_key; - - /* Set type */ - key->type = SSH_KEY_DSA; - - /* Allocate space for public key */ - public_key = malloc(4096); - pos = public_key; - - /* Derive public key */ - buffer_write_string(&pos, "ssh-dss", sizeof("ssh-dss")-1); - buffer_write_bignum(&pos, dsa_key->p); - buffer_write_bignum(&pos, dsa_key->q); - buffer_write_bignum(&pos, dsa_key->g); - buffer_write_bignum(&pos, dsa_key->pub_key); - - /* Save public key to structure */ - key->public_key = public_key; - key->public_key_length = pos - public_key; - - } - - /* Otherwise, unsupported type */ - else { - BIO_free(key_bio); - return NULL; - } - - /* Copy private key to structure */ - key->private_key_length = length; - key->private_key = malloc(length); - memcpy(key->private_key, data, length); - - BIO_free(key_bio); - return key; - -} - -const char* ssh_key_error() { - - /* Return static error string */ - return ERR_reason_error_string(ERR_get_error()); - -} - -void ssh_key_free(ssh_key* key) { - - /* Free key-specific data */ - if (key->type == SSH_KEY_RSA) - RSA_free(key->rsa); - else if (key->type == SSH_KEY_DSA) - DSA_free(key->dsa); - - free(key->public_key); - free(key); -} - -int ssh_key_sign(ssh_key* key, const char* data, int length, unsigned char* sig) { - - const EVP_MD* md; - EVP_MD_CTX md_ctx; - - unsigned char digest[EVP_MAX_MD_SIZE]; - unsigned int dlen, len; - - /* Get SHA1 digest */ - if ((md = EVP_get_digestbynid(NID_sha1)) == NULL) - return -1; - - /* Digest data */ - EVP_DigestInit(&md_ctx, md); - EVP_DigestUpdate(&md_ctx, data, length); - EVP_DigestFinal(&md_ctx, digest, &dlen); - - /* Sign with key */ - switch (key->type) { - - case SSH_KEY_RSA: - if (RSA_sign(NID_sha1, digest, dlen, sig, &len, key->rsa) == 1) - return len; - - case SSH_KEY_DSA: { - - DSA_SIG* dsa_sig = DSA_do_sign(digest, dlen, key->dsa); - if (dsa_sig != NULL) { - - /* Compute size of each half of signature */ - int rlen = BN_num_bytes(dsa_sig->r); - int slen = BN_num_bytes(dsa_sig->s); - - /* Ensure each number is within the required size */ - if (rlen > DSA_SIG_NUMBER_SIZE || slen > DSA_SIG_NUMBER_SIZE) - return -1; - - /* Init to all zeroes */ - memset(sig, 0, DSA_SIG_SIZE); - - /* Add R at the end of the first block of the signature */ - BN_bn2bin(dsa_sig->r, sig + DSA_SIG_SIZE - - DSA_SIG_NUMBER_SIZE - rlen); - - /* Add S at the end of the second block of the signature */ - BN_bn2bin(dsa_sig->s, sig + DSA_SIG_SIZE - slen); - - /* Done */ - DSA_SIG_free(dsa_sig); - return DSA_SIG_SIZE; - - } - - } - - } - - return -1; - -} - diff --git a/src/protocols/ssh/ssh_key.h b/src/protocols/ssh/ssh_key.h deleted file mode 100644 index 1e7e7970..00000000 --- a/src/protocols/ssh/ssh_key.h +++ /dev/null @@ -1,137 +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. - */ - - -#ifndef _GUAC_SSH_KEY_H -#define _GUAC_SSH_KEY_H - -#include "config.h" - -#include - -/** - * The expected header of RSA private keys. - */ -#define SSH_RSA_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----" - -/** - * The expected header of DSA private keys. - */ -#define SSH_DSA_KEY_HEADER "-----BEGIN DSA PRIVATE KEY-----" - -/** - * The size of single number within a DSA signature, in bytes. - */ -#define DSA_SIG_NUMBER_SIZE 20 - -/** - * The size of a DSA signature, in bytes. - */ -#define DSA_SIG_SIZE DSA_SIG_NUMBER_SIZE*2 - -/** - * The type of an SSH key. - */ -typedef enum ssh_key_type { - - /** - * RSA key. - */ - SSH_KEY_RSA, - - /** - * DSA key. - */ - SSH_KEY_DSA - -} ssh_key_type; - -/** - * Abstraction of a key used for SSH authentication. - */ -typedef struct ssh_key { - - /** - * The type of this key. - */ - ssh_key_type type; - - /** - * Underlying RSA private key, if any. - */ - RSA* rsa; - - /** - * Underlying DSA private key, if any. - */ - DSA* dsa; - - /** - * The associated public key, encoded as necessary for SSH. - */ - char* public_key; - - /** - * The length of the public key, in bytes. - */ - int public_key_length; - - /** - * The private key, encoded as necessary for SSH. - */ - char* private_key; - - /** - * The length of the private key, in bytes. - */ - int private_key_length; - -} ssh_key; - -/** - * Allocates a new key containing the given private key data and specified - * passphrase. If unable to read the key, NULL is returned. - */ -ssh_key* ssh_key_alloc(char* data, int length, char* passphrase); - -/** - * Returns a statically-allocated string describing the most recent SSH key - * error. - * - * @return - * A statically-allocated string describing the most recent SSH key error. - */ -const char* ssh_key_error(); - -/** - * Frees all memory associated with the given key. - */ -void ssh_key_free(ssh_key* key); - -/** - * Signs the given data using the given key, returning the length of the - * signature in bytes, or a value less than zero on error. - */ -int ssh_key_sign(ssh_key* key, const char* data, int length, unsigned char* sig); - -#endif - From 87be5d43ea90cb3c6e4854e57501f2700412ea35 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 10 Jul 2015 14:20:16 -0700 Subject: [PATCH 11/15] GUAC-1171: Add missing comments around static functions. Add param and return annotations to existing comments. --- src/common-ssh/guac_ssh.c | 100 ++++++++++++++++++++++++++++++- src/common-ssh/guac_ssh_buffer.h | 58 +++++++++++++++++- src/common-ssh/guac_ssh_key.h | 35 +++++++++++ 3 files changed, 191 insertions(+), 2 deletions(-) diff --git a/src/common-ssh/guac_ssh.c b/src/common-ssh/guac_ssh.c index 76b293bc..7bbd789d 100644 --- a/src/common-ssh/guac_ssh.c +++ b/src/common-ssh/guac_ssh.c @@ -53,6 +53,20 @@ static pthread_mutex_t* guac_common_ssh_openssl_locks; /** * Called by OpenSSL when locking or unlocking the Nth mutex. + * + * @param mode + * A bitmask denoting the action to be taken on the Nth lock, such as + * CRYPTO_LOCK or CRYPTO_UNLOCK. + * + * @param n + * The index of the lock to lock or unlock. + * + * @param file + * The filename of the function setting the lock, for debugging purposes. + * + * @param line + * The line number of the function setting the lock, for debugging + * purposes. */ static void guac_common_ssh_openssl_locking_callback(int mode, int n, const char* file, int line){ @@ -69,6 +83,9 @@ static void guac_common_ssh_openssl_locking_callback(int mode, int n, /** * Called by OpenSSL when determining the current thread ID. + * + * @return + * An ID which uniquely identifies the current thread. */ static unsigned long guac_common_ssh_openssl_id_callback() { return (unsigned long) pthread_self(); @@ -77,6 +94,9 @@ static unsigned long guac_common_ssh_openssl_id_callback() { /** * Creates the given number of mutexes, such that OpenSSL will have at least * this number of mutexes at its disposal. + * + * @param count + * The number of mutexes (locks) to create. */ static void guac_common_ssh_openssl_init_locks(int count) { @@ -94,6 +114,9 @@ static void guac_common_ssh_openssl_init_locks(int count) { /** * Frees the given number of mutexes. + * + * @param count + * The number of mutexes (locks) to free. */ static void guac_common_ssh_openssl_free_locks(int count) { @@ -137,6 +160,36 @@ void guac_common_ssh_uninit() { guac_common_ssh_openssl_free_locks(CRYPTO_num_locks()); } +/** + * Callback invoked by libssh2 when libssh2_userauth_publickkey() is invoked. + * This callback must sign the given data, returning the signature as newly- + * allocated buffer space. + * + * @param session + * The SSH session for which the signature is being generated. + * + * @param sig + * A pointer to the buffer space containing the signature. This callback + * MUST allocate and assign this space. + * + * @param sig_len + * The length of the signature within the allocated buffer space, in bytes. + * This value must be set to the size of the signature after the signing + * operation completes. + * + * @param data + * The arbitrary data that must be signed. + * + * @param data_len + * The length of the arbitrary data to be signed, in bytes. + * + * @param abstract + * The value of the abstract parameter provided with the corresponding call + * to libssh2_userauth_publickey(). + * + * @return + * Zero on success, non-zero if the signing operation failed. + */ static int guac_common_ssh_sign_callback(LIBSSH2_SESSION* session, unsigned char** sig, size_t* sig_len, const unsigned char* data, size_t data_len, void **abstract) { @@ -158,7 +211,40 @@ static int guac_common_ssh_sign_callback(LIBSSH2_SESSION* session, /** * Callback for the keyboard-interactive authentication method. Currently - * suports just one prompt for the password. + * supports just one prompt for the password. This callback is invoked as + * needed to fullfill a call to libssh2_userauth_keyboard_interactive(). + * + * @param name + * An arbitrary name which should be printed to the terminal for the + * benefit of the user. This is currently ignored. + * + * @param name_len + * The length of the name string, in bytes. + * + * @param instruction + * Arbitrary instructions which should be printed to the terminal for the + * benefit of the user. This is currently ignored. + * + * @param instruction_len + * The length of the instruction string, in bytes. + * + * @param num_prompts + * The number of keyboard-interactive prompts for which responses are + * requested. This callback currently only supports one prompt, and assumes + * that this prompt is requesting the password. + * + * @param prompts + * An array of all keyboard-interactive prompts for which responses are + * requested. + * + * @param responses + * A parallel array into which all prompt responses should be stored. Each + * entry within this array corresponds to the entry in the prompts array + * with the same index. + * + * @param abstract + * The value of the abstract parameter provided when the SSH session was + * created with libssh2_session_init_ex(). */ static void guac_common_ssh_kbd_callback(const char *name, int name_len, const char *instruction, int instruction_len, int num_prompts, @@ -186,6 +272,18 @@ static void guac_common_ssh_kbd_callback(const char *name, int name_len, } +/** + * Authenticates the user associated with the given session over SSH. All + * required credentials must already be present within the user object + * associated with the given session. + * + * @param session + * The session associated with the user to be authenticated. + * + * @return + * Zero if authentication succeeds, or non-zero if authentication has + * failed. + */ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) { guac_client* client = common_session->client; diff --git a/src/common-ssh/guac_ssh_buffer.h b/src/common-ssh/guac_ssh_buffer.h index df9a519e..750c625e 100644 --- a/src/common-ssh/guac_ssh_buffer.h +++ b/src/common-ssh/guac_ssh_buffer.h @@ -31,12 +31,24 @@ /** * Writes the given byte to the given buffer, advancing the buffer pointer by * one byte. + * + * @param buffer + * The buffer to write to. + * + * @param value + * The value to write. */ void guac_common_buffer_write_byte(char** buffer, uint8_t value); /** * Writes the given integer to the given buffer, advancing the buffer pointer * four bytes. + * + * @param buffer + * The buffer to write to. + * + * @param value + * The value to write. */ void guac_common_buffer_write_uint32(char** buffer, uint32_t value); @@ -44,28 +56,62 @@ void guac_common_buffer_write_uint32(char** buffer, uint32_t value); * Writes the given string and its length to the given buffer, advancing the * buffer pointer by the size of the length (four bytes) and the size of the * string. + * + * @param buffer + * The buffer to write to. + * + * @param string + * The string value to write. + * + * @param length + * The length of the string to write, in bytes. */ -void guac_common_buffer_write_string(char** buffer, const char* string, int length); +void guac_common_buffer_write_string(char** buffer, const char* string, + int length); /** * Writes the given BIGNUM the given buffer, advancing the buffer pointer by * the size of the length (four bytes) and the size of the BIGNUM. + * + * @param buffer + * The buffer to write to. + * + * @param value + * The value to write. */ void guac_common_buffer_write_bignum(char** buffer, BIGNUM* value); /** * Writes the given data the given buffer, advancing the buffer pointer by the * given length. + * + * @param data + * The arbitrary data to write. + * + * @param length + * The length of data to write, in bytes. */ void guac_common_buffer_write_data(char** buffer, const char* data, int length); /** * Reads a single byte from the given buffer, advancing the buffer by one byte. + * + * @param buffer + * The buffer to read from. + * + * @return + * The value read from the buffer. */ uint8_t guac_common_buffer_read_byte(char** buffer); /** * Reads an integer from the given buffer, advancing the buffer by four bytes. + * + * @param buffer + * The buffer to read from. + * + * @return + * The value read from the buffer. */ uint32_t guac_common_buffer_read_uint32(char** buffer); @@ -74,6 +120,16 @@ uint32_t guac_common_buffer_read_uint32(char** buffer); * by the size of the length (four bytes) and the size of the string, and * returning a pointer to the buffer. The length of the string is stored in * the given int. + * + * @param buffer + * The buffer to read from. + * + * @param length + * A pointer to an integer into which the length of the read string will + * be stored. + * + * @return + * A pointer to the value within the buffer. */ char* guac_common_buffer_read_string(char** buffer, int* length); diff --git a/src/common-ssh/guac_ssh_key.h b/src/common-ssh/guac_ssh_key.h index 393c7c1d..e76ac067 100644 --- a/src/common-ssh/guac_ssh_key.h +++ b/src/common-ssh/guac_ssh_key.h @@ -109,6 +109,20 @@ typedef struct guac_common_ssh_key { /** * Allocates a new key containing the given private key data and specified * passphrase. If unable to read the key, NULL is returned. + * + * @param data + * The base64-encoded data to decode when reading the key. + * + * @param length + * The length of the provided data, in bytes. + * + * @param passphrase + * The passphrase to use when decrypting the key, if any, or an empty + * string or NULL if no passphrase is needed. + * + * @return + * The decoded, decrypted private key, or NULL if the key could not be + * decoded. */ guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length, char* passphrase); @@ -124,12 +138,33 @@ const char* guac_common_ssh_key_error(); /** * Frees all memory associated with the given key. + * + * @param key + * The key to free. */ void guac_common_ssh_key_free(guac_common_ssh_key* key); /** * Signs the given data using the given key, returning the length of the * signature in bytes, or a value less than zero on error. + * + * @param key + * The key to use when signing the given data. + * + * @param data + * The arbitrary data to sign. + * + * @param length + * The length of the arbitrary data being signed, in bytes. + * + * @param sig + * The buffer into which the signature should be written. The buffer must + * be at least DSA_SIG_SIZE for DSA keys. For RSA keys, the signature size + * is dependent only on key size, and is equal to the length of the + * modulus, in bytes. + * + * @return + * The number of bytes in the resulting signature. */ int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data, int length, unsigned char* sig); From a6b46a0ea0b1c695376ab3e724edc7b4bdf08631 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 10 Jul 2015 15:54:44 -0700 Subject: [PATCH 12/15] GUAC-1171: Add and document SFTP functionality within common SSH. --- src/common-ssh/guac_sftp.c | 689 ++++++++++++++++++++++++++++++++++++- src/common-ssh/guac_sftp.h | 139 +++++++- src/protocols/ssh/client.c | 3 - src/protocols/ssh/client.h | 16 - 4 files changed, 808 insertions(+), 39 deletions(-) diff --git a/src/common-ssh/guac_sftp.c b/src/common-ssh/guac_sftp.c index a2524eaf..54cfa804 100644 --- a/src/common-ssh/guac_sftp.c +++ b/src/common-ssh/guac_sftp.c @@ -20,22 +20,697 @@ * THE SOFTWARE. */ +#include "guac_sftp.h" #include "guac_ssh.h" #include #include +#include +#include #include -guac_object* guac_common_ssh_create_sftp_filesystem(guac_client* client, - const char* name, LIBSSH2_SESSION* session) { +#include +#include +#include +#include - /* STUB */ - return NULL; +/** + * Concatenates the given filename with the given path, separating the two + * with a single forward slash. The full result must be no more than + * GUAC_COMMON_SSH_SFTP_MAX_PATH bytes long, counting null terminator. + * + * @param fullpath + * The buffer to store the result within. This buffer must be at least + * GUAC_COMMON_SSH_SFTP_MAX_PATH bytes long. + * + * @param path + * The path to append the filename to. + * + * @param filename + * The filename to append to the path. + * + * @return + * Non-zero if the filename is valid and was successfully appended to the + * path, zero otherwise. + */ +static int guac_ssh_append_filename(char* fullpath, const char* path, + const char* filename) { + + int i; + + /* Disallow "." as a filename */ + if (strcmp(filename, ".") == 0) + return 0; + + /* Disallow ".." as a filename */ + if (strcmp(filename, "..") == 0) + return 0; + + /* Copy path, append trailing slash */ + for (i=0; i 0 && path[i-1] != '/') + fullpath[i++] = '/'; + break; + } + + /* Copy character if not end of string */ + fullpath[i] = c; + + } + + /* Append filename */ + for (; idata; + + /* Attempt write */ + if (libssh2_sftp_write(file, data, length) == length) { + guac_client_log(client, GUAC_LOG_DEBUG, "%i bytes written", length); + guac_protocol_send_ack(client->socket, stream, "SFTP: OK", + GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(client->socket); + } + + /* Inform of any errors */ + else { + guac_client_log(client, GUAC_LOG_INFO, "Unable to write to file"); + guac_protocol_send_ack(client->socket, stream, "SFTP: Write failed", + GUAC_PROTOCOL_STATUS_SERVER_ERROR); + guac_socket_flush(client->socket); + } + + return 0; + +} + +/** + * Handler for end messages which terminate an inbound SFTP data transfer + * (upload). The data associated with the given stream is expected to be a + * pointer to an open LIBSSH2_SFTP_HANDLE for the file to which the data + * has been written and which should now be closed. + * + * @param client + * The client receiving the end message. + * + * @param stream + * The Guacamole protocol stream associated with the received end message. + * + * @return + * Zero if the file is closed successfully, or non-zero on error. + */ +static int guac_common_ssh_sftp_end_handler(guac_client* client, + guac_stream* stream) { + + /* Pull file from stream */ + LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data; + + /* Attempt to close file */ + if (libssh2_sftp_close(file) == 0) { + guac_client_log(client, GUAC_LOG_DEBUG, "File closed"); + guac_protocol_send_ack(client->socket, stream, "SFTP: OK", + GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(client->socket); + } + else { + guac_client_log(client, GUAC_LOG_INFO, "Unable to close file"); + guac_protocol_send_ack(client->socket, stream, "SFTP: Close failed", + GUAC_PROTOCOL_STATUS_SERVER_ERROR); + guac_socket_flush(client->socket); + } + + return 0; + +} + +int guac_common_ssh_sftp_handle_file_stream(guac_object* filesystem, + guac_stream* stream, char* mimetype, char* filename) { + + guac_common_ssh_sftp_data* sftp_data = + (guac_common_ssh_sftp_data*) filesystem->data; + + guac_client* client = sftp_data->ssh_session->client; + + char fullpath[GUAC_COMMON_SSH_SFTP_MAX_PATH]; + LIBSSH2_SFTP_HANDLE* file; + + /* Concatenate filename with path */ + if (!guac_ssh_append_filename(fullpath, sftp_data->upload_path, + filename)) { + + guac_client_log(client, GUAC_LOG_DEBUG, + "Filename \"%s\" is invalid or resulting path is too long", + filename); + + /* Abort transfer - invalid filename */ + guac_protocol_send_ack(client->socket, stream, + "SFTP: Illegal filename", + GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST); + + guac_socket_flush(client->socket); + return 0; + } + + /* Open file via SFTP */ + file = libssh2_sftp_open(sftp_data->sftp_session, fullpath, + LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC, + S_IRUSR | S_IWUSR); + + /* Inform of status */ + if (file != NULL) { + + guac_client_log(client, GUAC_LOG_DEBUG, + "File \"%s\" opened", + fullpath); + + guac_protocol_send_ack(client->socket, stream, "SFTP: File opened", + GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(client->socket); + } + else { + guac_client_log(client, GUAC_LOG_INFO, "Unable to open file \"%s\": %s", + fullpath, libssh2_sftp_last_error(sftp_data->sftp_session)); + guac_protocol_send_ack(client->socket, stream, "SFTP: Open failed", + GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND); + guac_socket_flush(client->socket); + } + + /* Set handlers for file stream */ + stream->blob_handler = guac_common_ssh_sftp_blob_handler; + stream->end_handler = guac_common_ssh_sftp_end_handler; + + /* Store file within stream */ + stream->data = file; + return 0; + +} + +/** + * Handler for ack messages which continue an outbound SFTP data transfer + * (download), signalling the current status and requesting additional data. + * The data associated with the given stream is expected to be a pointer to an + * open LIBSSH2_SFTP_HANDLE for the file from which the data is to be read. + * + * @param client + * The client receiving the ack message. + * + * @param stream + * The Guacamole protocol stream associated with the received ack message. + * + * @param message + * An arbitrary human-readable message describing the nature of the + * success or failure denoted by the ack message. + * + * @param status + * The status code associated with the ack message, which may indicate + * success or an error. + * + * @return + * Zero if the file is read from successfully, or non-zero on error. + */ +static int guac_common_ssh_sftp_ack_handler(guac_client* client, + guac_stream* stream, char* message, guac_protocol_status status) { + + /* Pull file from stream */ + LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data; + + /* If successful, read data */ + if (status == GUAC_PROTOCOL_STATUS_SUCCESS) { + + /* Attempt read into buffer */ + char buffer[4096]; + int bytes_read = libssh2_sftp_read(file, buffer, sizeof(buffer)); + + /* If bytes read, send as blob */ + if (bytes_read > 0) { + guac_protocol_send_blob(client->socket, stream, + buffer, bytes_read); + + guac_client_log(client, GUAC_LOG_DEBUG, "%i bytes sent to client", + bytes_read); + + } + + /* If EOF, send end */ + else if (bytes_read == 0) { + guac_client_log(client, GUAC_LOG_DEBUG, "File sent"); + guac_protocol_send_end(client->socket, stream); + guac_client_free_stream(client, stream); + } + + /* Otherwise, fail stream */ + else { + guac_client_log(client, GUAC_LOG_INFO, "Error reading file"); + guac_protocol_send_end(client->socket, stream); + guac_client_free_stream(client, stream); + } + + guac_socket_flush(client->socket); + + } + + /* Otherwise, return stream to client */ + else + guac_client_free_stream(client, stream); + + return 0; +} + +guac_stream* guac_common_ssh_sftp_download_file(guac_object* filesystem, + char* filename) { + + guac_common_ssh_sftp_data* sftp_data = + (guac_common_ssh_sftp_data*) filesystem->data; + + guac_client* client = sftp_data->ssh_session->client; + + guac_stream* stream; + LIBSSH2_SFTP_HANDLE* file; + + /* Attempt to open file for reading */ + file = libssh2_sftp_open(sftp_data->sftp_session, filename, + LIBSSH2_FXF_READ, 0); + if (file == NULL) { + guac_client_log(client, GUAC_LOG_INFO, + "Unable to read file \"%s\": %s", + filename, libssh2_sftp_last_error(sftp_data->sftp_session)); + return NULL; + } + + /* Allocate stream */ + stream = guac_client_alloc_stream(client); + stream->ack_handler = guac_common_ssh_sftp_ack_handler; + stream->data = file; + + /* Send stream start, strip name */ + filename = basename(filename); + guac_protocol_send_file(client->socket, stream, + "application/octet-stream", filename); + guac_socket_flush(client->socket); + + guac_client_log(client, GUAC_LOG_DEBUG, "Sending file \"%s\"", filename); + return stream; + +} + +void guac_common_ssh_sftp_set_upload_path(guac_object* filesystem, + const char* path) { + + guac_common_ssh_sftp_data* sftp_data = + (guac_common_ssh_sftp_data*) filesystem->data; + + guac_client* client = sftp_data->ssh_session->client; + + /* Ignore requests which exceed maximum-allowed path */ + int length = strnlen(path, GUAC_COMMON_SSH_SFTP_MAX_PATH)+1; + if (length > GUAC_COMMON_SSH_SFTP_MAX_PATH) { + guac_client_log(client, GUAC_LOG_ERROR, + "Submitted path exceeds limit of %i bytes", + GUAC_COMMON_SSH_SFTP_MAX_PATH); + return; + } + + /* Copy path */ + memcpy(sftp_data->upload_path, path, length); + guac_client_log(client, GUAC_LOG_DEBUG, "Upload path set to \"%s\"", path); + +} + +/** + * Handler for ack messages received due to receipt of a "body" or "blob" + * instruction associated with a SFTP directory list operation. + * + * @param client + * The client receiving the ack message. + * + * @param stream + * The Guacamole protocol stream associated with the received ack message. + * + * @param message + * An arbitrary human-readable message describing the nature of the + * success or failure denoted by this ack message. + * + * @param status + * The status code associated with this ack message, which may indicate + * success or an error. + * + * @return + * Zero on success, non-zero on error. + */ +static int guac_common_ssh_sftp_ls_ack_handler(guac_client* client, + guac_stream* stream, char* message, guac_protocol_status status) { + + int bytes_read; + int blob_written = 0; + + char filename[GUAC_COMMON_SSH_SFTP_MAX_PATH]; + LIBSSH2_SFTP_ATTRIBUTES attributes; + + guac_common_ssh_sftp_ls_state* list_state = + (guac_common_ssh_sftp_ls_state*) stream->data; + + guac_common_ssh_sftp_data* sftp_data = list_state->sftp_data; + + LIBSSH2_SFTP* sftp = sftp_data->sftp_session; + + /* If unsuccessful, free stream and abort */ + if (status != GUAC_PROTOCOL_STATUS_SUCCESS) { + libssh2_sftp_closedir(list_state->directory); + guac_client_free_stream(client, stream); + free(list_state); + return 0; + } + + /* While directory entries remain */ + while ((bytes_read = libssh2_sftp_readdir(list_state->directory, + filename, sizeof(filename), &attributes)) > 0 + && !blob_written) { + + char absolute_path[GUAC_COMMON_SSH_SFTP_MAX_PATH]; + + /* Skip current and parent directory entries */ + if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) + continue; + + /* Concatenate into absolute path - skip if invalid */ + if (!guac_ssh_append_filename(absolute_path, + list_state->directory_name, filename)) { + + guac_client_log(client, GUAC_LOG_DEBUG, + "Skipping filename \"%s\" - filename is invalid or " + "resulting path is too long", filename); + + continue; + } + + /* Stat explicitly if symbolic link (might point to directory) */ + if (LIBSSH2_SFTP_S_ISLNK(attributes.permissions)) + libssh2_sftp_stat(sftp, absolute_path, &attributes); + + /* Determine mimetype */ + const char* mimetype; + if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions)) + mimetype = GUAC_CLIENT_STREAM_INDEX_MIMETYPE; + else + mimetype = "application/octet-stream"; + + /* Write entry */ + blob_written |= guac_common_json_write_property(client, stream, + &list_state->json_state, absolute_path, mimetype); + + } + + /* Complete JSON and cleanup at end of directory */ + if (bytes_read <= 0) { + + /* Complete JSON object */ + guac_common_json_end_object(client, stream, &list_state->json_state); + guac_common_json_flush(client, stream, &list_state->json_state); + + /* Clean up resources */ + libssh2_sftp_closedir(list_state->directory); + free(list_state); + + /* Signal of stream */ + guac_protocol_send_end(client->socket, stream); + guac_client_free_stream(client, stream); + + } + + guac_socket_flush(client->socket); + return 0; + +} + +/** + * Handler for get messages. In context of SFTP and the filesystem exposed via + * the Guacamole protocol, get messages request the body of a file within the + * filesystem. + * + * @param client + * The client receiving the get message. + * + * @param object + * The Guacamole protocol object associated with the get request itself. + * + * @param name + * The name of the input stream (file) being requested. + * + * @return + * Zero on success, non-zero on error. + */ +static int guac_common_ssh_sftp_get_handler(guac_client* client, + guac_object* object, char* name) { + + guac_common_ssh_sftp_data* sftp_data = + (guac_common_ssh_sftp_data*) object->data; + + LIBSSH2_SFTP* sftp = sftp_data->sftp_session; + LIBSSH2_SFTP_ATTRIBUTES attributes; + + /* Attempt to read file information */ + if (libssh2_sftp_stat(sftp, name, &attributes)) { + guac_client_log(client, GUAC_LOG_INFO, "Unable to read file \"%s\"", + name); + return 0; + } + + /* If directory, send contents of directory */ + if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions)) { + + /* Open as directory */ + LIBSSH2_SFTP_HANDLE* dir = libssh2_sftp_opendir(sftp, name); + if (dir == NULL) { + guac_client_log(client, GUAC_LOG_INFO, + "Unable to read directory \"%s\": %s", + name, libssh2_sftp_last_error(sftp)); + return 0; + } + + /* Init directory listing state */ + guac_common_ssh_sftp_ls_state* list_state = + malloc(sizeof(guac_common_ssh_sftp_ls_state)); + + list_state->directory = dir; + list_state->sftp_data = sftp_data; + strncpy(list_state->directory_name, name, + sizeof(list_state->directory_name)); + + /* Allocate stream for body */ + guac_stream* stream = guac_client_alloc_stream(client); + stream->ack_handler = guac_common_ssh_sftp_ls_ack_handler; + stream->data = list_state; + + /* Init JSON object state */ + guac_common_json_begin_object(client, stream, &list_state->json_state); + + /* Associate new stream with get request */ + guac_protocol_send_body(client->socket, object, stream, + GUAC_CLIENT_STREAM_INDEX_MIMETYPE, name); + + } + + /* Otherwise, send file contents */ + else { + + /* Open as normal file */ + LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, name, + LIBSSH2_FXF_READ, 0); + if (file == NULL) { + guac_client_log(client, GUAC_LOG_INFO, + "Unable to read file \"%s\": %s", + name, libssh2_sftp_last_error(sftp)); + return 0; + } + + /* Allocate stream for body */ + guac_stream* stream = guac_client_alloc_stream(client); + stream->ack_handler = guac_common_ssh_sftp_ack_handler; + stream->data = file; + + /* Associate new stream with get request */ + guac_protocol_send_body(client->socket, object, stream, + "application/octet-stream", name); + + } + + guac_socket_flush(client->socket); + return 0; +} + +/** + * Handler for put messages. In context of SFTP and the filesystem exposed via + * the Guacamole protocol, put messages request write access to a file within + * the filesystem. + * + * @param client + * The client receiving the put message. + * + * @param object + * The Guacamole protocol object associated with the put request itself. + * + * @param stream + * The Guacamole protocol stream along which the client will be sending + * file data. + * + * @param mimetype + * The mimetype of the data being send along the stream. + * + * @param name + * The name of the input stream (file) being requested. + * + * @return + * Zero on success, non-zero on error. + */ +static int guac_common_ssh_sftp_put_handler(guac_client* client, + guac_object* object, guac_stream* stream, char* mimetype, char* name) { + + guac_common_ssh_sftp_data* sftp_data = + (guac_common_ssh_sftp_data*) object->data; + + LIBSSH2_SFTP* sftp = sftp_data->sftp_session; + + /* Open file via SFTP */ + LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, name, + LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC, + S_IRUSR | S_IWUSR); + + /* Acknowledge stream if successful */ + if (file != NULL) { + guac_client_log(client, GUAC_LOG_DEBUG, "File \"%s\" opened", name); + guac_protocol_send_ack(client->socket, stream, "SFTP: File opened", + GUAC_PROTOCOL_STATUS_SUCCESS); + } + + /* Abort on failure */ + else { + guac_client_log(client, GUAC_LOG_INFO, "Unable to open file \"%s\": %s", + name, libssh2_sftp_last_error(sftp)); + guac_protocol_send_ack(client->socket, stream, "SFTP: Open failed", + GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND); + } + + /* Set handlers for file stream */ + stream->blob_handler = guac_common_ssh_sftp_blob_handler; + stream->end_handler = guac_common_ssh_sftp_end_handler; + + /* Store file within stream */ + stream->data = file; + + guac_socket_flush(client->socket); + return 0; +} + +guac_object* guac_common_ssh_create_sftp_filesystem( + guac_common_ssh_session* session, + const char* name) { + + guac_client* client = session->client; + + /* Allocate data for SFTP session */ + guac_common_ssh_sftp_data* sftp_data = + malloc(sizeof(guac_common_ssh_sftp_data)); + + /* Associate SSH session with SFTP data */ + sftp_data->ssh_session = session; + + /* Initially upload files to current directory */ + strcpy(sftp_data->upload_path, "."); + + /* Request SFTP */ + sftp_data->sftp_session = libssh2_sftp_init(session->session); + if (sftp_data->sftp_session == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + "Unable to start SFTP session."); + return NULL; + } + + /* Init filesystem */ + guac_object* filesystem = guac_client_alloc_object(client); + filesystem->get_handler = guac_common_ssh_sftp_get_handler; + filesystem->put_handler = guac_common_ssh_sftp_put_handler; + filesystem->data = sftp_data; + + /* Send filesystem to client */ + guac_protocol_send_filesystem(client->socket, filesystem, "/"); + guac_socket_flush(client->socket); + + /* Return allocated filesystem */ + return filesystem; + +} + +void guac_common_ssh_destroy_sftp_filesystem(guac_object* filesystem) { + + guac_common_ssh_sftp_data* sftp_data = + (guac_common_ssh_sftp_data*) filesystem->data; + + /* Shutdown SFTP session */ + libssh2_sftp_shutdown(sftp_data->sftp_session); + + /* Clean up the SFTP filesystem object */ + guac_client_free_object(sftp_data->ssh_session->client, filesystem); + + /* Disconnect SSH session corresponding to the SFTP session */ + guac_common_ssh_destroy_session(sftp_data->ssh_session); + } diff --git a/src/common-ssh/guac_sftp.h b/src/common-ssh/guac_sftp.h index ee6f3885..13511115 100644 --- a/src/common-ssh/guac_sftp.h +++ b/src/common-ssh/guac_sftp.h @@ -23,9 +23,69 @@ #ifndef GUAC_COMMON_SSH_SFTP_H #define GUAC_COMMON_SSH_SFTP_H +#include "guac_json.h" +#include "guac_ssh.h" + #include #include #include +#include + +/** + * Maximum number of bytes per path. + */ +#define GUAC_COMMON_SSH_SFTP_MAX_PATH 2048 + +/** + * Data associated with an SFTP-driven filesystem object. + */ +typedef struct guac_common_ssh_sftp_data { + + /** + * The distinct SSH session used for SFTP. + */ + guac_common_ssh_session* ssh_session; + + /** + * SFTP session, used for file transfers. + */ + LIBSSH2_SFTP* sftp_session; + + /** + * The path files will be sent to, if uploaded directly via a "file" + * instruction. + */ + char upload_path[GUAC_COMMON_SSH_SFTP_MAX_PATH]; + +} guac_common_ssh_sftp_data; + +/** + * The current state of a directory listing operation. + */ +typedef struct guac_common_ssh_sftp_ls_state { + + /** + * Data associated with the current SFTP session. + */ + guac_common_ssh_sftp_data* sftp_data; + + /** + * Reference to the directory currently being listed over SFTP. This + * directory must already be open from a call to libssh2_sftp_opendir(). + */ + LIBSSH2_SFTP_HANDLE* directory; + + /** + * The absolute path of the directory being listed. + */ + char directory_name[GUAC_COMMON_SSH_SFTP_MAX_PATH]; + + /** + * The current state of the JSON directory object being written. + */ + guac_common_json_state json_state; + +} guac_common_ssh_sftp_ls_state; /** * Creates a new Guacamole filesystem object which provides access to files @@ -33,35 +93,88 @@ * will no longer be used, it must be explicitly destroyed with * guac_common_ssh_destroy_sftp_filesystem(). * - * @param client - * The Guacamole client which will be associated with the new filesystem - * object. + * @param session + * The session to use to provide SFTP. * * @param name * The name to send as the name of the filesystem. * - * @param session - * The session to use to provide SFTP. - * * @return * A new Guacamole filesystem object, already configured to use SFTP for * uploading and downloading files. */ -guac_object* guac_common_ssh_create_sftp_filesystem(guac_client* client, - const char* name, LIBSSH2_SESSION* session); +guac_object* guac_common_ssh_create_sftp_filesystem( + guac_common_ssh_session* session, + const char* name); /** * Destroys the given filesystem object, disconnecting from SFTP and freeing * and associated resources. * - * @param client - * The client associated with the filesystem object. - * * @param object * The filesystem object to destroy. */ -void guac_common_ssh_destroy_sftp_filesystem(guac_client* client, - guac_object* filesystem); +void guac_common_ssh_destroy_sftp_filesystem(guac_object* filesystem); + +/** + * Initiates an SFTP file download to the user via the Guacamole "file" + * instruction. The download will be automatically monitored and continued + * after this function terminates in response to "ack" instructions received by + * the client. + * + * @param filesystem + * The filesystem containing the file to be downloaded. + * + * @param filename + * The filename of the file to download, relative to the given filesystem. + * + * @return + * The file stream created for the file download, already configured to + * properly handle "ack" responses, etc. from the client. + */ +guac_stream* guac_common_ssh_sftp_download_file(guac_object* filesystem, + char* filename); + +/** + * Handles an incoming stream from a Guacamole "file" instruction, saving the + * contents of that stream to the file having the given name within the + * upload directory set by guac_common_ssh_sftp_set_upload_path(). + * + * @param filesystem + * The filesystem that should receive the uploaded file. + * + * @param stream + * The stream through which the uploaded file data will be received. + * + * @param mimetype + * The mimetype of the data being received. + * + * @param filename + * The filename of the file to write to. This filename will always be taken + * relative to the upload path set by + * guac_common_ssh_sftp_set_upload_path(). + * + * @return + * Zero if the incoming stream has been handled successfully, non-zero on + * failure. + */ +int guac_common_ssh_sftp_handle_file_stream(guac_object* filesystem, + guac_stream* stream, char* mimetype, char* filename); + +/** + * Set the destination directory for future uploads submitted via + * guac_common_ssh_sftp_handle_file_stream(). This function has no bearing + * on the destination directories of files uploaded with "put" instructions. + * + * @param filesystem + * The filesystem to set the upload path of. + * + * @param path + * The path to use for future uploads submitted via the + * guac_common_ssh_sftp_handle_file_stream() function. + */ +void guac_common_ssh_sftp_set_upload_path(guac_object* filesystem, + const char* path); #endif diff --git a/src/protocols/ssh/client.c b/src/protocols/ssh/client.c index 63c5f2c8..d231fa7f 100644 --- a/src/protocols/ssh/client.c +++ b/src/protocols/ssh/client.c @@ -159,10 +159,7 @@ int guac_client_init(guac_client* client, int argc, char** argv) { /* Parse SFTP enable */ client_data->enable_sftp = strcmp(argv[IDX_ENABLE_SFTP], "true") == 0; - client_data->sftp_session = NULL; - client_data->sftp_ssh_session = NULL; client_data->sftp_filesystem = NULL; - strcpy(client_data->sftp_upload_path, "."); #ifdef ENABLE_SSH_AGENT client_data->enable_agent = strcmp(argv[IDX_ENABLE_AGENT], "true") == 0; diff --git a/src/protocols/ssh/client.h b/src/protocols/ssh/client.h index 40f97ab2..99d3257b 100644 --- a/src/protocols/ssh/client.h +++ b/src/protocols/ssh/client.h @@ -114,27 +114,11 @@ typedef struct ssh_guac_client_data { */ guac_common_ssh_session* session; - /** - * The distinct SSH session used for SFTP. - */ - guac_common_ssh_session* sftp_ssh_session; - - /** - * SFTP session, used for file transfers. - */ - LIBSSH2_SFTP* sftp_session; - /** * The filesystem object exposed for the SFTP session. */ guac_object* sftp_filesystem; - /** - * The path files will be sent to, if uploaded directly via a "file" - * instruction. - */ - char sftp_upload_path[GUAC_SFTP_MAX_PATH]; - /** * SSH terminal channel, used by the SSH client thread. */ From 3d1d2ea3345009f9fb4a49c625542cdbdfed83d7 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 10 Jul 2015 17:21:58 -0700 Subject: [PATCH 13/15] GUAC-1171: Migrate to common SSH for SFTP transfers. --- src/protocols/ssh/guac_handlers.c | 11 +- src/protocols/ssh/sftp.c | 496 +----------------------------- src/protocols/ssh/sftp.h | 184 +++-------- src/protocols/ssh/ssh_client.c | 26 +- 4 files changed, 68 insertions(+), 649 deletions(-) diff --git a/src/protocols/ssh/guac_handlers.c b/src/protocols/ssh/guac_handlers.c index 8293afe2..29fcd404 100644 --- a/src/protocols/ssh/guac_handlers.c +++ b/src/protocols/ssh/guac_handlers.c @@ -24,6 +24,7 @@ #include "client.h" #include "guac_handlers.h" +#include "guac_sftp.h" #include "guac_ssh.h" #include "terminal.h" @@ -101,17 +102,9 @@ int ssh_guac_client_free_handler(guac_client* client) { /* Free channels */ libssh2_channel_free(guac_client_data->term_channel); - /* Shutdown SFTP session, if any */ - if (guac_client_data->sftp_session) - libssh2_sftp_shutdown(guac_client_data->sftp_session); - - /* Disconnect SSH session corresponding to the SFTP session */ - if (guac_client_data->sftp_ssh_session) - guac_common_ssh_destroy_session(guac_client_data->sftp_ssh_session); - /* Clean up the SFTP filesystem object */ if (guac_client_data->sftp_filesystem) - guac_client_free_object(client, guac_client_data->sftp_filesystem); + guac_common_ssh_destroy_sftp_filesystem(guac_client_data->sftp_filesystem); /* Free session */ if (guac_client_data->session != NULL) diff --git a/src/protocols/ssh/sftp.c b/src/protocols/ssh/sftp.c index c7fa463b..af6be7bd 100644 --- a/src/protocols/ssh/sftp.c +++ b/src/protocols/ssh/sftp.c @@ -22,517 +22,43 @@ #include "config.h" -#include "guac_json.h" - #include "client.h" +#include "guac_sftp.h" #include "sftp.h" -#include -#include -#include -#include -#include -#include - -#include #include -#include -#include -#include #include -/** - * Concatenates the given filename with the given path, separating the two - * with a single forward slash. The full result must be no more than - * GUAC_SFTP_MAX_PATH bytes long, counting null terminator. - * - * @param fullpath - * The buffer to store the result within. This buffer must be at least - * GUAC_SFTP_MAX_PATH bytes long. - * - * @param path - * The path to append the filename to. - * - * @param filename - * The filename to append to the path. - * - * @return - * true if the filename is valid and was successfully appended to the path, - * false otherwise. - */ -static bool guac_ssh_append_filename(char* fullpath, const char* path, - const char* filename) { - - int i; - - /* Disallow "." as a filename */ - if (strcmp(filename, ".") == 0) - return false; - - /* Disallow ".." as a filename */ - if (strcmp(filename, "..") == 0) - return false; - - /* Copy path, append trailing slash */ - for (i=0; i 0 && path[i-1] != '/') - fullpath[i++] = '/'; - break; - } - - /* Copy character if not end of string */ - fullpath[i] = c; - - } - - /* Append filename */ - for (; idata; - char fullpath[GUAC_SFTP_MAX_PATH]; - LIBSSH2_SFTP_HANDLE* file; + guac_object* filesystem = client_data->sftp_filesystem; - /* Concatenate filename with path */ - if (!guac_ssh_append_filename(fullpath, - client_data->sftp_upload_path, filename)) { + /* Handle file upload */ + return guac_common_ssh_sftp_handle_file_stream(filesystem, stream, + mimetype, filename); - guac_client_log(client, GUAC_LOG_DEBUG, - "Filename \"%s\" is invalid or resulting path is too long", - filename); - - /* Abort transfer - invalid filename */ - guac_protocol_send_ack(client->socket, stream, - "SFTP: Illegal filename", - GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST); - - guac_socket_flush(client->socket); - return 0; - } - - /* Open file via SFTP */ - file = libssh2_sftp_open(client_data->sftp_session, fullpath, - LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC, - S_IRUSR | S_IWUSR); - - /* Inform of status */ - if (file != NULL) { - - guac_client_log(client, GUAC_LOG_DEBUG, - "File \"%s\" opened", - fullpath); - - guac_protocol_send_ack(client->socket, stream, "SFTP: File opened", GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(client->socket); - } - else { - guac_client_log(client, GUAC_LOG_INFO, "Unable to open file \"%s\": %s", - fullpath, libssh2_sftp_last_error(client_data->sftp_session)); - guac_protocol_send_ack(client->socket, stream, "SFTP: Open failed", GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND); - guac_socket_flush(client->socket); - } - - /* Set handlers for file stream */ - stream->blob_handler = guac_sftp_blob_handler; - stream->end_handler = guac_sftp_end_handler; - - /* Store file within stream */ - stream->data = file; - return 0; - -} - -int guac_sftp_blob_handler(guac_client* client, guac_stream* stream, - void* data, int length) { - - /* Pull file from stream */ - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data; - - /* Attempt write */ - if (libssh2_sftp_write(file, data, length) == length) { - guac_client_log(client, GUAC_LOG_DEBUG, "%i bytes written", length); - guac_protocol_send_ack(client->socket, stream, "SFTP: OK", GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(client->socket); - } - - /* Inform of any errors */ - else { - guac_client_log(client, GUAC_LOG_INFO, "Unable to write to file: %s", - libssh2_sftp_last_error(client_data->sftp_session)); - guac_protocol_send_ack(client->socket, stream, "SFTP: Write failed", GUAC_PROTOCOL_STATUS_SERVER_ERROR); - guac_socket_flush(client->socket); - } - - return 0; - -} - -int guac_sftp_end_handler(guac_client* client, guac_stream* stream) { - - /* Pull file from stream */ - LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data; - - /* Attempt to close file */ - if (libssh2_sftp_close(file) == 0) { - guac_client_log(client, GUAC_LOG_DEBUG, "File closed"); - guac_protocol_send_ack(client->socket, stream, "SFTP: OK", GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(client->socket); - } - else { - guac_client_log(client, GUAC_LOG_INFO, "Unable to close file"); - guac_protocol_send_ack(client->socket, stream, "SFTP: Close failed", GUAC_PROTOCOL_STATUS_SERVER_ERROR); - guac_socket_flush(client->socket); - } - - return 0; - -} - -int guac_sftp_ack_handler(guac_client* client, guac_stream* stream, - char* message, guac_protocol_status status) { - - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data; - - /* If successful, read data */ - if (status == GUAC_PROTOCOL_STATUS_SUCCESS) { - - /* Attempt read into buffer */ - char buffer[4096]; - int bytes_read = libssh2_sftp_read(file, buffer, sizeof(buffer)); - - /* If bytes read, send as blob */ - if (bytes_read > 0) { - guac_protocol_send_blob(client->socket, stream, - buffer, bytes_read); - - guac_client_log(client, GUAC_LOG_DEBUG, "%i bytes sent to client", - bytes_read); - - } - - /* If EOF, send end */ - else if (bytes_read == 0) { - guac_client_log(client, GUAC_LOG_DEBUG, "File sent"); - guac_protocol_send_end(client->socket, stream); - guac_client_free_stream(client, stream); - } - - /* Otherwise, fail stream */ - else { - guac_client_log(client, GUAC_LOG_INFO, "Error reading file: %s", - libssh2_sftp_last_error(client_data->sftp_session)); - guac_protocol_send_end(client->socket, stream); - guac_client_free_stream(client, stream); - } - - guac_socket_flush(client->socket); - - } - - /* Otherwise, return stream to client */ - else - guac_client_free_stream(client, stream); - - return 0; } guac_stream* guac_sftp_download_file(guac_client* client, char* filename) { ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - guac_stream* stream; - LIBSSH2_SFTP_HANDLE* file; + guac_object* filesystem = client_data->sftp_filesystem; - /* Attempt to open file for reading */ - file = libssh2_sftp_open(client_data->sftp_session, filename, - LIBSSH2_FXF_READ, 0); - if (file == NULL) { - guac_client_log(client, GUAC_LOG_INFO, "Unable to read file \"%s\": %s", - filename, - libssh2_sftp_last_error(client_data->sftp_session)); - return NULL; - } - - /* Allocate stream */ - stream = guac_client_alloc_stream(client); - stream->ack_handler = guac_sftp_ack_handler; - stream->data = file; - - /* Send stream start, strip name */ - filename = basename(filename); - guac_protocol_send_file(client->socket, stream, - "application/octet-stream", filename); - guac_socket_flush(client->socket); - - guac_client_log(client, GUAC_LOG_DEBUG, "Sending file \"%s\"", filename); - return stream; + /* Initiate download of requested file */ + return guac_common_ssh_sftp_download_file(filesystem, filename); } void guac_sftp_set_upload_path(guac_client* client, char* path) { ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - int length = strnlen(path, GUAC_SFTP_MAX_PATH); + guac_object* filesystem = client_data->sftp_filesystem; - /* Ignore requests which exceed maximum-allowed path */ - if (length > GUAC_SFTP_MAX_PATH) { - guac_client_log(client, GUAC_LOG_ERROR, - "Submitted path exceeds limit of %i bytes", - GUAC_SFTP_MAX_PATH); - return; - } - - /* Copy path */ - memcpy(client_data->sftp_upload_path, path, length); - guac_client_log(client, GUAC_LOG_DEBUG, "Upload path set to \"%s\"", path); + /* Set upload path as specified */ + guac_common_ssh_sftp_set_upload_path(filesystem, path); } -guac_object* guac_sftp_expose_filesystem(guac_client* client) { - - /* Init filesystem */ - guac_object* filesystem = guac_client_alloc_object(client); - filesystem->get_handler = guac_sftp_get_handler; - filesystem->put_handler = guac_sftp_put_handler; - - /* Send filesystem to client */ - guac_protocol_send_filesystem(client->socket, filesystem, "/"); - guac_socket_flush(client->socket); - - /* Return allocated filesystem */ - return filesystem; - -} - -int guac_sftp_ls_ack_handler(guac_client* client, guac_stream* stream, - char* message, guac_protocol_status status) { - - int bytes_read; - bool blob_written = false; - - char filename[GUAC_SFTP_MAX_PATH]; - LIBSSH2_SFTP_ATTRIBUTES attributes; - - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - LIBSSH2_SFTP* sftp = client_data->sftp_session; - guac_sftp_ls_state* list_state = (guac_sftp_ls_state*) stream->data; - - /* If unsuccessful, free stream and abort */ - if (status != GUAC_PROTOCOL_STATUS_SUCCESS) { - libssh2_sftp_closedir(list_state->directory); - guac_client_free_stream(client, stream); - free(list_state); - return 0; - } - - /* While directory entries remain */ - while ((bytes_read = libssh2_sftp_readdir(list_state->directory, - filename, sizeof(filename), &attributes)) > 0 - && !blob_written) { - - char absolute_path[GUAC_SFTP_MAX_PATH]; - - /* Skip current and parent directory entries */ - if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) - continue; - - /* Concatenate into absolute path - skip if invalid */ - if (!guac_ssh_append_filename(absolute_path, - list_state->directory_name, filename)) { - - guac_client_log(client, GUAC_LOG_DEBUG, - "Skipping filename \"%s\" - filename is invalid or " - "resulting path is too long", filename); - - continue; - } - - /* Stat explicitly if symbolic link (might point to directory) */ - if (LIBSSH2_SFTP_S_ISLNK(attributes.permissions)) - libssh2_sftp_stat(sftp, absolute_path, &attributes); - - /* Determine mimetype */ - const char* mimetype; - if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions)) - mimetype = GUAC_CLIENT_STREAM_INDEX_MIMETYPE; - else - mimetype = "application/octet-stream"; - - /* Write entry */ - blob_written |= guac_common_json_write_property(client, stream, - &list_state->json_state, absolute_path, mimetype); - - } - - /* Complete JSON and cleanup at end of directory */ - if (bytes_read <= 0) { - - /* Complete JSON object */ - guac_common_json_end_object(client, stream, &list_state->json_state); - guac_common_json_flush(client, stream, &list_state->json_state); - - /* Clean up resources */ - libssh2_sftp_closedir(list_state->directory); - free(list_state); - - /* Signal of stream */ - guac_protocol_send_end(client->socket, stream); - guac_client_free_stream(client, stream); - - } - - guac_socket_flush(client->socket); - return 0; - -} - -int guac_sftp_get_handler(guac_client* client, guac_object* object, - char* name) { - - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - LIBSSH2_SFTP* sftp = client_data->sftp_session; - LIBSSH2_SFTP_ATTRIBUTES attributes; - - /* Attempt to read file information */ - if (libssh2_sftp_stat(sftp, name, &attributes)) { - guac_client_log(client, GUAC_LOG_INFO, "Unable to read file \"%s\"", - name); - return 0; - } - - /* If directory, send contents of directory */ - if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions)) { - - /* Open as directory */ - LIBSSH2_SFTP_HANDLE* dir = libssh2_sftp_opendir(sftp, name); - if (dir == NULL) { - guac_client_log(client, GUAC_LOG_INFO, - "Unable to read directory \"%s\": %s", - name, libssh2_sftp_last_error(sftp)); - return 0; - } - - /* Init directory listing state */ - guac_sftp_ls_state* list_state = malloc(sizeof(guac_sftp_ls_state)); - - list_state->directory = dir; - strncpy(list_state->directory_name, name, - sizeof(list_state->directory_name)); - - /* Allocate stream for body */ - guac_stream* stream = guac_client_alloc_stream(client); - stream->ack_handler = guac_sftp_ls_ack_handler; - stream->data = list_state; - - /* Init JSON object state */ - guac_common_json_begin_object(client, stream, &list_state->json_state); - - /* Associate new stream with get request */ - guac_protocol_send_body(client->socket, object, stream, - GUAC_CLIENT_STREAM_INDEX_MIMETYPE, name); - - } - - /* Otherwise, send file contents */ - else { - - /* Open as normal file */ - LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, name, - LIBSSH2_FXF_READ, 0); - if (file == NULL) { - guac_client_log(client, GUAC_LOG_INFO, - "Unable to read file \"%s\": %s", - name, libssh2_sftp_last_error(sftp)); - return 0; - } - - /* Allocate stream for body */ - guac_stream* stream = guac_client_alloc_stream(client); - stream->ack_handler = guac_sftp_ack_handler; - stream->data = file; - - /* Associate new stream with get request */ - guac_protocol_send_body(client->socket, object, stream, - "application/octet-stream", name); - - } - - guac_socket_flush(client->socket); - return 0; -} - -int guac_sftp_put_handler(guac_client* client, guac_object* object, - guac_stream* stream, char* mimetype, char* name) { - - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - LIBSSH2_SFTP* sftp = client_data->sftp_session; - - /* Open file via SFTP */ - LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, name, - LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC, - S_IRUSR | S_IWUSR); - - /* Acknowledge stream if successful */ - if (file != NULL) { - guac_client_log(client, GUAC_LOG_DEBUG, "File \"%s\" opened", name); - guac_protocol_send_ack(client->socket, stream, "SFTP: File opened", - GUAC_PROTOCOL_STATUS_SUCCESS); - } - - /* Abort on failure */ - else { - guac_client_log(client, GUAC_LOG_INFO, "Unable to open file \"%s\": %s", - name, libssh2_sftp_last_error(sftp)); - guac_protocol_send_ack(client->socket, stream, "SFTP: Open failed", - GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND); - } - - /* Set handlers for file stream */ - stream->blob_handler = guac_sftp_blob_handler; - stream->end_handler = guac_sftp_end_handler; - - /* Store file within stream */ - stream->data = file; - - guac_socket_flush(client->socket); - return 0; -} - diff --git a/src/protocols/ssh/sftp.h b/src/protocols/ssh/sftp.h index ecb96714..59cd52e8 100644 --- a/src/protocols/ssh/sftp.h +++ b/src/protocols/ssh/sftp.h @@ -26,161 +26,65 @@ #include "config.h" -#include "guac_json.h" - -#include -#include - #include -#include -#include #include /** - * Maximum number of bytes per path. - */ -#define GUAC_SFTP_MAX_PATH 2048 - -/** - * The current state of a directory listing operation. - */ -typedef struct guac_sftp_ls_state { - - /** - * Reference to the directory currently being listed over SFTP. This - * directory must already be open from a call to libssh2_sftp_opendir(). - */ - LIBSSH2_SFTP_HANDLE* directory; - - /** - * The absolute path of the directory being listed. - */ - char directory_name[GUAC_SFTP_MAX_PATH]; - - /** - * The current state of the JSON directory object being written. - */ - guac_common_json_state json_state; - -} guac_sftp_ls_state; - -/** - * Handler for file messages which begins an SFTP data transfer (upload). + * Handles an incoming stream from a Guacamole "file" instruction, saving the + * contents of that stream to the file having the given name within the + * upload directory set by guac_sftp_set_upload_path(). + * + * @param client + * The client receiving the uploaded file. + * + * @param stream + * The stream through which the uploaded file data will be received. + * + * @param mimetype + * The mimetype of the data being received. + * + * @param filename + * The filename of the file to write to. This filename will always be taken + * relative to the upload path set by + * guac_common_ssh_sftp_set_upload_path(). + * + * @return + * Zero if the incoming stream has been handled successfully, non-zero on + * failure. */ int guac_sftp_file_handler(guac_client* client, guac_stream* stream, char* mimetype, char* filename); /** - * Handler for blob messages which continues an SFTP data transfer (upload). - */ -int guac_sftp_blob_handler(guac_client* client, guac_stream* stream, - void* data, int length); - -/** - * Handler for end messages which ends an SFTP data transfer (upload). - */ -int guac_sftp_end_handler(guac_client* client, guac_stream* stream); - -/** - * Handler for ack messages which continues an SFTP download. - */ -int guac_sftp_ack_handler(guac_client* client, guac_stream* stream, - char* message, guac_protocol_status status); - -/** - * Begins (and automatically continues) an SFTP file download to the user. + * Initiates an SFTP file download to the user via the Guacamole "file" + * instruction. The download will be automatically monitored and continued + * after this function terminates in response to "ack" instructions received by + * the client. + * + * @param client + * The client receiving the file. + * + * @param filename + * The filename of the file to download, relative to the given filesystem. + * + * @return + * The file stream created for the file download, already configured to + * properly handle "ack" responses, etc. from the client. */ guac_stream* guac_sftp_download_file(guac_client* client, char* filename); /** - * Set the destination directory for future uploads. + * Sets the destination directory for future uploads submitted via Guacamole + * "file" instruction. This function has no bearing on the destination + * directories of files uploaded with "put" instructions. + * + * @param client + * The client setting the upload path. + * + * @param path + * The path to use for future uploads submitted via "file" instruction. */ void guac_sftp_set_upload_path(guac_client* client, char* path); -/** - * Exposes access to SFTP via a filesystem object, returning that object. The - * object returned must eventually be explicitly freed through a call to - * guac_client_free_object(). - * - * @param client - * The Guacamole client to expose the filesystem to. - * - * @return - * The resulting Guacamole filesystem object, initialized and exposed to - * the client. - */ -guac_object* guac_sftp_expose_filesystem(guac_client* client); - -/** - * Handler for get messages. In context of SFTP and the filesystem exposed via - * the Guacamole protocol, get messages request the body of a file within the - * filesystem. - * - * @param client - * The client receiving the get message. - * - * @param object - * The Guacamole protocol object associated with the get request itself. - * - * @param name - * The name of the input stream (file) being requested. - * - * @return - * Zero on success, non-zero on error. - */ -int guac_sftp_get_handler(guac_client* client, guac_object* object, - char* name); - -/** - * Handler for put messages. In context of SFTP and the filesystem exposed via - * the Guacamole protocol, put messages request write access to a file within - * the filesystem. - * - * @param client - * The client receiving the put message. - * - * @param object - * The Guacamole protocol object associated with the put request itself. - * - * @param stream - * The Guacamole protocol stream along which the client will be sending - * file data. - * - * @param mimetype - * The mimetype of the data being send along the stream. - * - * @param name - * The name of the input stream (file) being requested. - * - * @return - * Zero on success, non-zero on error. - */ -int guac_sftp_put_handler(guac_client* client, guac_object* object, - guac_stream* stream, char* mimetype, char* name); - -/** - * Handler for ack messages received due to receipt of a "body" or "blob" - * instruction associated with a SFTP directory list operation. - * - * @param client - * The client receiving the ack message. - * - * @param stream - * The Guacamole protocol stream associated with the received ack message. - * - * @param message - * An arbitrary human-readable message describing the nature of the - * success or failure denoted by this ack message. - * - * @param status - * The status code associated with this ack message, which may indicate - * success or an error. - * - * @return - * Zero on success, non-zero on error. - */ -int guac_sftp_ls_ack_handler(guac_client* client, guac_stream* stream, - char* message, guac_protocol_status status); - #endif diff --git a/src/protocols/ssh/ssh_client.c b/src/protocols/ssh/ssh_client.c index 1ac672be..93797993 100644 --- a/src/protocols/ssh/ssh_client.c +++ b/src/protocols/ssh/ssh_client.c @@ -23,6 +23,7 @@ #include "config.h" #include "client.h" +#include "guac_sftp.h" #include "guac_ssh.h" #include "sftp.h" #include "terminal.h" @@ -226,31 +227,26 @@ void* ssh_client_thread(void* data) { /* Start SFTP session as well, if enabled */ if (client_data->enable_sftp) { - /* Init handlers for Guacamole-specific console codes */ - client_data->term->upload_path_handler = guac_sftp_set_upload_path; - client_data->term->file_download_handler = guac_sftp_download_file; - /* Create SSH session specific for SFTP */ guac_client_log(client, GUAC_LOG_DEBUG, "Reconnecting for SFTP..."); - client_data->sftp_ssh_session = guac_common_ssh_create_session(client, - client_data->hostname, client_data->port, user); - if (client_data->sftp_ssh_session == NULL) { + guac_common_ssh_session* sftp_ssh_session = + guac_common_ssh_create_session(client, client_data->hostname, + client_data->port, user); + if (sftp_ssh_session == NULL) { /* Already aborted within guac_common_ssh_create_session() */ return NULL; } /* Request SFTP */ - client_data->sftp_session = libssh2_sftp_init(client_data->sftp_ssh_session->session); - if (client_data->sftp_session == NULL) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to start SFTP session."); - return NULL; - } + client_data->sftp_filesystem = + guac_common_ssh_create_sftp_filesystem(sftp_ssh_session, "/"); - /* Set file handler */ + /* Set generic (non-filesystem) file upload handler */ client->file_handler = guac_sftp_file_handler; - /* Expose filesystem */ - client_data->sftp_filesystem = guac_sftp_expose_filesystem(client); + /* Init handlers for Guacamole-specific console codes */ + client_data->term->upload_path_handler = guac_sftp_set_upload_path; + client_data->term->file_download_handler = guac_sftp_download_file; guac_client_log(client, GUAC_LOG_DEBUG, "SFTP session initialized"); From 5f547a9974db6f8c43e85443add1fb17f2722507 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 10 Jul 2015 17:26:04 -0700 Subject: [PATCH 14/15] GUAC-1171: Fix naming of guac_common_ssh_buffer_*() functions. --- src/common-ssh/guac_ssh_buffer.c | 32 +++++++++++++++++--------------- src/common-ssh/guac_ssh_buffer.h | 16 ++++++++-------- src/common-ssh/guac_ssh_key.c | 16 ++++++++-------- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/common-ssh/guac_ssh_buffer.c b/src/common-ssh/guac_ssh_buffer.c index cd07a307..0390c403 100644 --- a/src/common-ssh/guac_ssh_buffer.c +++ b/src/common-ssh/guac_ssh_buffer.c @@ -29,7 +29,7 @@ #include #include -void guac_common_buffer_write_byte(char** buffer, uint8_t value) { +void guac_common_ssh_buffer_write_byte(char** buffer, uint8_t value) { uint8_t* data = (uint8_t*) *buffer; *data = value; @@ -38,7 +38,7 @@ void guac_common_buffer_write_byte(char** buffer, uint8_t value) { } -void guac_common_buffer_write_uint32(char** buffer, uint32_t value) { +void guac_common_ssh_buffer_write_uint32(char** buffer, uint32_t value) { uint8_t* data = (uint8_t*) *buffer; @@ -51,19 +51,20 @@ void guac_common_buffer_write_uint32(char** buffer, uint32_t value) { } -void guac_common_buffer_write_data(char** buffer, const char* data, int length) { +void guac_common_ssh_buffer_write_data(char** buffer, const char* data, + int length) { memcpy(*buffer, data, length); *buffer += length; } -void guac_common_buffer_write_bignum(char** buffer, BIGNUM* value) { +void guac_common_ssh_buffer_write_bignum(char** buffer, BIGNUM* value) { unsigned char* bn_buffer; int length; /* If zero, just write zero length */ if (BN_is_zero(value)) { - guac_common_buffer_write_uint32(buffer, 0); + guac_common_ssh_buffer_write_uint32(buffer, 0); return; } @@ -76,11 +77,11 @@ void guac_common_buffer_write_bignum(char** buffer, BIGNUM* value) { /* If first byte has high bit set, write padding byte */ if (bn_buffer[0] & 0x80) { - guac_common_buffer_write_uint32(buffer, length+1); - guac_common_buffer_write_byte(buffer, 0); + guac_common_ssh_buffer_write_uint32(buffer, length+1); + guac_common_ssh_buffer_write_byte(buffer, 0); } else - guac_common_buffer_write_uint32(buffer, length); + guac_common_ssh_buffer_write_uint32(buffer, length); /* Write data */ memcpy(*buffer, bn_buffer, length); @@ -90,12 +91,13 @@ void guac_common_buffer_write_bignum(char** buffer, BIGNUM* value) { } -void guac_common_buffer_write_string(char** buffer, const char* string, int length) { - guac_common_buffer_write_uint32(buffer, length); - guac_common_buffer_write_data(buffer, string, length); +void guac_common_ssh_buffer_write_string(char** buffer, const char* string, + int length) { + guac_common_ssh_buffer_write_uint32(buffer, length); + guac_common_ssh_buffer_write_data(buffer, string, length); } -uint8_t guac_common_buffer_read_byte(char** buffer) { +uint8_t guac_common_ssh_buffer_read_byte(char** buffer) { uint8_t* data = (uint8_t*) *buffer; uint8_t value = *data; @@ -106,7 +108,7 @@ uint8_t guac_common_buffer_read_byte(char** buffer) { } -uint32_t guac_common_buffer_read_uint32(char** buffer) { +uint32_t guac_common_ssh_buffer_read_uint32(char** buffer) { uint8_t* data = (uint8_t*) *buffer; uint32_t value = @@ -121,11 +123,11 @@ uint32_t guac_common_buffer_read_uint32(char** buffer) { } -char* guac_common_buffer_read_string(char** buffer, int* length) { +char* guac_common_ssh_buffer_read_string(char** buffer, int* length) { char* value; - *length = guac_common_buffer_read_uint32(buffer); + *length = guac_common_ssh_buffer_read_uint32(buffer); value = *buffer; *buffer += *length; diff --git a/src/common-ssh/guac_ssh_buffer.h b/src/common-ssh/guac_ssh_buffer.h index 750c625e..0c77bba8 100644 --- a/src/common-ssh/guac_ssh_buffer.h +++ b/src/common-ssh/guac_ssh_buffer.h @@ -38,7 +38,7 @@ * @param value * The value to write. */ -void guac_common_buffer_write_byte(char** buffer, uint8_t value); +void guac_common_ssh_buffer_write_byte(char** buffer, uint8_t value); /** * Writes the given integer to the given buffer, advancing the buffer pointer @@ -50,7 +50,7 @@ void guac_common_buffer_write_byte(char** buffer, uint8_t value); * @param value * The value to write. */ -void guac_common_buffer_write_uint32(char** buffer, uint32_t value); +void guac_common_ssh_buffer_write_uint32(char** buffer, uint32_t value); /** * Writes the given string and its length to the given buffer, advancing the @@ -66,7 +66,7 @@ void guac_common_buffer_write_uint32(char** buffer, uint32_t value); * @param length * The length of the string to write, in bytes. */ -void guac_common_buffer_write_string(char** buffer, const char* string, +void guac_common_ssh_buffer_write_string(char** buffer, const char* string, int length); /** @@ -79,7 +79,7 @@ void guac_common_buffer_write_string(char** buffer, const char* string, * @param value * The value to write. */ -void guac_common_buffer_write_bignum(char** buffer, BIGNUM* value); +void guac_common_ssh_buffer_write_bignum(char** buffer, BIGNUM* value); /** * Writes the given data the given buffer, advancing the buffer pointer by the @@ -91,7 +91,7 @@ void guac_common_buffer_write_bignum(char** buffer, BIGNUM* value); * @param length * The length of data to write, in bytes. */ -void guac_common_buffer_write_data(char** buffer, const char* data, int length); +void guac_common_ssh_buffer_write_data(char** buffer, const char* data, int length); /** * Reads a single byte from the given buffer, advancing the buffer by one byte. @@ -102,7 +102,7 @@ void guac_common_buffer_write_data(char** buffer, const char* data, int length); * @return * The value read from the buffer. */ -uint8_t guac_common_buffer_read_byte(char** buffer); +uint8_t guac_common_ssh_buffer_read_byte(char** buffer); /** * Reads an integer from the given buffer, advancing the buffer by four bytes. @@ -113,7 +113,7 @@ uint8_t guac_common_buffer_read_byte(char** buffer); * @return * The value read from the buffer. */ -uint32_t guac_common_buffer_read_uint32(char** buffer); +uint32_t guac_common_ssh_buffer_read_uint32(char** buffer); /** * Reads a string and its length from the given buffer, advancing the buffer @@ -131,7 +131,7 @@ uint32_t guac_common_buffer_read_uint32(char** buffer); * @return * A pointer to the value within the buffer. */ -char* guac_common_buffer_read_string(char** buffer, int* length); +char* guac_common_ssh_buffer_read_string(char** buffer, int* length); #endif diff --git a/src/common-ssh/guac_ssh_key.c b/src/common-ssh/guac_ssh_key.c index fb9d0f78..30594492 100644 --- a/src/common-ssh/guac_ssh_key.c +++ b/src/common-ssh/guac_ssh_key.c @@ -73,9 +73,9 @@ guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length, pos = public_key; /* Derive public key */ - guac_common_buffer_write_string(&pos, "ssh-rsa", sizeof("ssh-rsa")-1); - guac_common_buffer_write_bignum(&pos, rsa_key->e); - guac_common_buffer_write_bignum(&pos, rsa_key->n); + guac_common_ssh_buffer_write_string(&pos, "ssh-rsa", sizeof("ssh-rsa")-1); + guac_common_ssh_buffer_write_bignum(&pos, rsa_key->e); + guac_common_ssh_buffer_write_bignum(&pos, rsa_key->n); /* Save public key to structure */ key->public_key = public_key; @@ -107,11 +107,11 @@ guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length, pos = public_key; /* Derive public key */ - guac_common_buffer_write_string(&pos, "ssh-dss", sizeof("ssh-dss")-1); - guac_common_buffer_write_bignum(&pos, dsa_key->p); - guac_common_buffer_write_bignum(&pos, dsa_key->q); - guac_common_buffer_write_bignum(&pos, dsa_key->g); - guac_common_buffer_write_bignum(&pos, dsa_key->pub_key); + guac_common_ssh_buffer_write_string(&pos, "ssh-dss", sizeof("ssh-dss")-1); + guac_common_ssh_buffer_write_bignum(&pos, dsa_key->p); + guac_common_ssh_buffer_write_bignum(&pos, dsa_key->q); + guac_common_ssh_buffer_write_bignum(&pos, dsa_key->g); + guac_common_ssh_buffer_write_bignum(&pos, dsa_key->pub_key); /* Save public key to structure */ key->public_key = public_key; From bfe4895408a4bf598b3df6e76152ed33079be102 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 10 Jul 2015 21:01:17 -0700 Subject: [PATCH 15/15] GUAC-1171: Remove extra trailing spaces from parameter comments. --- src/protocols/ssh/sftp.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/protocols/ssh/sftp.h b/src/protocols/ssh/sftp.h index 59cd52e8..af97c98b 100644 --- a/src/protocols/ssh/sftp.h +++ b/src/protocols/ssh/sftp.h @@ -34,7 +34,7 @@ * contents of that stream to the file having the given name within the * upload directory set by guac_sftp_set_upload_path(). * - * @param client + * @param client * The client receiving the uploaded file. * * @param stream @@ -61,7 +61,7 @@ int guac_sftp_file_handler(guac_client* client, guac_stream* stream, * after this function terminates in response to "ack" instructions received by * the client. * - * @param client + * @param client * The client receiving the file. * * @param filename @@ -78,7 +78,7 @@ guac_stream* guac_sftp_download_file(guac_client* client, char* filename); * "file" instruction. This function has no bearing on the destination * directories of files uploaded with "put" instructions. * - * @param client + * @param client * The client setting the upload path. * * @param path