diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e2b9931a..9079641c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,7 +51,7 @@ option(WITH_JPEG "Search for the libjpeg compression library to support addition option(WITH_PNG "Search for the PNG compression library to support additional encodings" ON) option(WITH_SDL "Search for the Simple Direct Media Layer library to build an example SDL vnc client" ON) option(WITH_GTK "Search for the GTK library to build an example GTK vnc client" ON) -option(WITH_LIBSSH2 "Search for libssh2 to build an example ssh-tunneled client" ON) +option(WITH_LIBSSHTUNNEL "Search for libsshtunnel to build an example ssh-tunneled client" ON) option(WITH_THREADS "Search for a threading library to build with multithreading support" ON) option(PREFER_WIN32THREADS "When searching for a threading library, prefer win32 threads if they are found" OFF) option(WITH_GNUTLS "Search for the GnuTLS secure communications library to support TLS" ON) @@ -145,9 +145,14 @@ if(WITH_GTK) find_package(GTK2) endif(WITH_GTK) -if(WITH_LIBSSH2) - find_package(LibSSH2) -endif(WITH_LIBSSH2) +if(WITH_LIBSSHTUNNEL) + find_path(LIBSSHTUNNEL_INCLUDE_DIR libsshtunnel.h) + find_library(LIBSSHTUNNEL_LIBRARY sshtunnel) + if("${LIBSSHTUNNEL_LIBRARY}" MATCHES ".*NOTFOUND.*") + # would otherwise contain -NOTFOUND, confusing target_link_libraries() + set(LIBSSHTUNNEL_LIBRARY "") + endif() +endif(WITH_LIBSSHTUNNEL) if(WITH_THREADS) find_package(Threads) @@ -628,17 +633,14 @@ if(GTK2_FOUND) ) endif(GTK2_FOUND) -if(LIBSSH2_FOUND AND (CMAKE_USE_PTHREADS_INIT OR CMAKE_USE_WIN32_THREADS_INIT)) - include_directories(${LIBSSH2_INCLUDE_DIR}) +if(WITH_LIBSSHTUNNEL AND LIBSSHTUNNEL_LIBRARY AND LIBSSHTUNNEL_INCLUDE_DIR) + message(STATUS "Building with libsshtunnel: ${LIBSSHTUNNEL_LIBRARY} and ${LIBSSHTUNNEL_INCLUDE_DIR}") + include_directories(${LIBSSHTUNNEL_INCLUDE_DIR}) set(LIBVNCCLIENT_EXAMPLES ${LIBVNCCLIENT_EXAMPLES} sshtunnel ) endif() -# if not found, set lib var to empty, otherwise CMake complains -if(NOT LIBSSH2_FOUND) - set(LIBSSH2_LIBRARY "") -endif() if(FFMPEG_FOUND) include_directories(${FFMPEG_INCLUDE_DIRS}) @@ -660,7 +662,7 @@ if(WITH_EXAMPLES) add_executable(client_examples_${e} ${LIBVNCCLIEXAMPLE_DIR}/${e}.c ${LIBVNCCLIEXAMPLE_DIR}/${${e}_EXTRA_SOURCES} ) set_target_properties(client_examples_${e} PROPERTIES OUTPUT_NAME ${e}) set_target_properties(client_examples_${e} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/examples/client) - target_link_libraries(client_examples_${e} vncclient ${CMAKE_THREAD_LIBS_INIT} ${SDL2_LIBRARY} ${GTK2_LIBRARIES} ${FFMPEG_LIBRARIES} ${LIBSSH2_LIBRARY}) + target_link_libraries(client_examples_${e} vncclient ${CMAKE_THREAD_LIBS_INIT} ${SDL2_LIBRARY} ${GTK2_LIBRARIES} ${FFMPEG_LIBRARIES} ${LIBSSHTUNNEL_LIBRARY}) endforeach(e ${LIBVNCCLIENT_EXAMPLES}) endif(WITH_EXAMPLES) diff --git a/cmake/Modules/FindLibSSH2.cmake b/cmake/Modules/FindLibSSH2.cmake deleted file mode 100644 index ce46a408b..000000000 --- a/cmake/Modules/FindLibSSH2.cmake +++ /dev/null @@ -1,43 +0,0 @@ -#*************************************************************************** -# _ _ ____ _ -# Project ___| | | | _ \| | -# / __| | | | |_) | | -# | (__| |_| | _ <| |___ -# \___|\___/|_| \_\_____| -# -# Copyright (C) 1998 - 2020, Daniel Stenberg, , et al. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at https://curl.se/docs/copyright.html. -# -# You may opt to use, copy, modify, merge, publish, distribute and/or sell -# copies of the Software, and permit persons to whom the Software is -# furnished to do so, under the terms of the COPYING file. -# -# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY -# KIND, either express or implied. -# -########################################################################### -# - Try to find the libssh2 library -# Once done this will define -# -# LIBSSH2_FOUND - system has the libssh2 library -# LIBSSH2_INCLUDE_DIR - the libssh2 include directory -# LIBSSH2_LIBRARY - the libssh2 library name - -find_path(LIBSSH2_INCLUDE_DIR libssh2.h) - -find_library(LIBSSH2_LIBRARY NAMES ssh2 libssh2) - -if(LIBSSH2_INCLUDE_DIR) - file(STRINGS "${LIBSSH2_INCLUDE_DIR}/libssh2.h" libssh2_version_str REGEX "^#define[\t ]+LIBSSH2_VERSION[\t ]+\"(.*)\"") - string(REGEX REPLACE "^.*\"([^\"]+)\"" "\\1" LIBSSH2_VERSION "${libssh2_version_str}") -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LibSSH2 - REQUIRED_VARS LIBSSH2_LIBRARY LIBSSH2_INCLUDE_DIR - VERSION_VAR LIBSSH2_VERSION) - -mark_as_advanced(LIBSSH2_INCLUDE_DIR LIBSSH2_LIBRARY) diff --git a/examples/client/sshtunnel.c b/examples/client/sshtunnel.c index d5a9946a8..5b179cac8 100644 --- a/examples/client/sshtunnel.c +++ b/examples/client/sshtunnel.c @@ -1,403 +1,111 @@ /** * @example sshtunnel.c - * An example of an RFB client tunneled through SSH by using libssh2. - * This is based on https://www.libssh2.org/examples/direct_tcpip.html - * with the following changes: - * - the listening is split out into a separate thread function - * - the listener gets closed immediately once a connection was accepted - * - the listening port is chosen by the OS, SO_REUSEADDR removed - * - global variables moved into SshData helper structure - * - added name resolution for the ssh host + * An example of an RFB client tunneled through SSH by using https://github.com/bk138/libsshtunnel */ #include -#include -#include -#include +#include #include -#include -#ifdef LIBVNCSERVER_HAVE_SYS_TYPES_H -#include -#endif -#ifdef LIBVNCSERVER_HAVE_SYS_SOCKET_H -#include -#endif -#ifdef LIBVNCSERVER_HAVE_UNISTD_H -#include -#endif /* The one global bool that's global so we can set it via a signal handler... */ int maintain_connection = 1; -typedef struct -{ - rfbClient *client; - LIBSSH2_SESSION *session; -#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD - pthread_t thread; -#elif defined(LIBVNCSERVER_HAVE_WIN32THREADS) - uintptr_t thread; -#endif - int ssh_sock; - int local_listensock; - int local_listenport; - const char *remote_desthost; - int remote_destport; -} SshData; - - -THREAD_ROUTINE_RETURN_TYPE ssh_proxy_loop(void *arg) -{ - SshData *data = arg; - int rc, i; - struct sockaddr_in sin; - socklen_t sinlen; - LIBSSH2_CHANNEL *channel = NULL; - const char *shost; - int sport; - fd_set fds; - struct timeval tv; - ssize_t len, wr; - char buf[16384]; - int proxy_sock = RFB_INVALID_SOCKET; - - proxy_sock = accept(data->local_listensock, (struct sockaddr *)&sin, &sinlen); - if(proxy_sock == RFB_INVALID_SOCKET) { - fprintf(stderr, "ssh_proxy_loop: accept: %s\n", strerror(errno)); - goto shutdown; - } - - /* Close listener once a connection got accepted */ - rfbCloseSocket(data->local_listensock); - - shost = inet_ntoa(sin.sin_addr); - sport = ntohs(sin.sin_port); - - printf("ssh_proxy_loop: forwarding connection from %s:%d here to remote %s:%d\n", - shost, sport, data->remote_desthost, data->remote_destport); - - channel = libssh2_channel_direct_tcpip_ex(data->session, data->remote_desthost, - data->remote_destport, shost, sport); - if(!channel) { - fprintf(stderr, "ssh_proxy_loop: Could not open the direct-tcpip channel!\n" - "(Note that this can be a problem at the server!" - " Please review the server logs.)\n"); - goto shutdown; - } - - /* Must use non-blocking IO hereafter due to the current libssh2 API */ - libssh2_session_set_blocking(data->session, 0); - - while(1) { - FD_ZERO(&fds); - FD_SET(proxy_sock, &fds); - tv.tv_sec = 0; - tv.tv_usec = 100000; - rc = select(proxy_sock + 1, &fds, NULL, NULL, &tv); - if(-1 == rc) { - fprintf(stderr, "ssh_proxy_loop: select: %s\n", strerror(errno)); - goto shutdown; - } - if(rc && FD_ISSET(proxy_sock, &fds)) { - len = recv(proxy_sock, buf, sizeof(buf), 0); - if(len < 0) { - fprintf(stderr, "read: %s\n", strerror(errno)); - goto shutdown; - } - else if(0 == len) { - fprintf(stderr, "ssh_proxy_loop: the client at %s:%d disconnected!\n", shost, - sport); - goto shutdown; - } - wr = 0; - while(wr < len) { - i = libssh2_channel_write(channel, buf + wr, len - wr); - if(LIBSSH2_ERROR_EAGAIN == i) { - continue; - } - if(i < 0) { - fprintf(stderr, "ssh_proxy_loop: libssh2_channel_write: %d\n", i); - goto shutdown; - } - wr += i; - } - } - while(1) { - len = libssh2_channel_read(channel, buf, sizeof(buf)); - if(LIBSSH2_ERROR_EAGAIN == len) - break; - else if(len < 0) { - fprintf(stderr, "ssh_proxy_loop: libssh2_channel_read: %d\n", (int)len); - goto shutdown; - } - wr = 0; - while(wr < len) { - i = send(proxy_sock, buf + wr, len - wr, 0); - if(i <= 0) { - fprintf(stderr, "ssh_proxy_loop: write: %s\n", strerror(errno)); - goto shutdown; - } - wr += i; - } - if(libssh2_channel_eof(channel)) { - fprintf(stderr, "ssh_proxy_loop: the server at %s:%d disconnected!\n", - data->remote_desthost, data->remote_destport); - goto shutdown; - } - } - } - - shutdown: - - printf("ssh_proxy_loop: shutting down\n"); - - rfbCloseSocket(proxy_sock); - - if(channel) - libssh2_channel_free(channel); - - libssh2_session_disconnect(data->session, "Client disconnecting normally"); - libssh2_session_free(data->session); - - rfbCloseSocket(data->ssh_sock); +void intHandler(int dummy) { + maintain_connection = 0; +} - return THREAD_ROUTINE_RETURN_VALUE; +void ssh_signal_error(void *client, + ssh_tunnel_error_t error_code, + const char *error_message) { + fprintf(stderr, "libsshtunnel error: %s", error_message); } -/** - Decide whether or not the SSH tunnel setup should continue - based on the current host and its fingerprint. - Business logic is up to the implementer in a real app, i.e. - compare keys, ask user etc... - @return -1 if tunnel setup should be aborted - 0 if tunnel setup should continue - */ -int ssh_fingerprint_check(const char *fingerprint, size_t fingerprint_len, - const char *host, rfbClient *client) -{ - size_t i; +int ssh_fingerprint_check(void* client, + const char *fingerprint, + int fingerprint_len, + const char *host) { fprintf(stderr, "ssh_fingerprint_check: host %s has ", host); - for(i = 0; i < fingerprint_len; i++) - printf("%02X ", (unsigned char)fingerprint[i]); + for(int i = 0; i < fingerprint_len; i++) + printf("%02X ", (unsigned char)fingerprint[i]); printf("\n"); return 0; } -/** - Creates an SSH tunnel and a local proxy and returns the port the proxy is listening on. - @return A pointer to an SshData structure or NULL on error. - */ -SshData* ssh_tunnel_open(const char *ssh_host, - const char *ssh_user, - const char *ssh_password, - const char *ssh_pub_key_path, - const char *ssh_priv_key_path, - const char *ssh_priv_key_password, - const char *rfb_host, - int rfb_port, - rfbClient *client) +int main(int argc, char *argv[]) { - int rc, i; - struct sockaddr_in sin; - socklen_t sinlen; - const char *fingerprint; - char *userauthlist; - struct addrinfo hints, *res; - SshData *data; - - /* Sanity checks */ - if(!ssh_host || !ssh_user || !rfb_host) /* these must be set */ - return NULL; - - data = calloc(1, sizeof(SshData)); - - data->client = client; - data->remote_desthost = rfb_host; /* resolved by the server */ - data->remote_destport = rfb_port; - - /* Connect to SSH server */ - data->ssh_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); - if(data->ssh_sock == RFB_INVALID_SOCKET) { - fprintf(stderr, "ssh_tunnel_open: socket: %s\n", strerror(errno)); - goto error; - } - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - - if ((rc = getaddrinfo(ssh_host, NULL, &hints, &res)) == 0) { - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = (((struct sockaddr_in *)res->ai_addr)->sin_addr.s_addr); - freeaddrinfo(res); - } else { - fprintf(stderr, "ssh_tunnel_open: getaddrinfo: %s\n", gai_strerror(rc)); - goto error; - } - - sin.sin_port = htons(22); - if(connect(data->ssh_sock, (struct sockaddr*)(&sin), sizeof(struct sockaddr_in)) != 0) { - fprintf(stderr, "ssh_tunnel_open: failed to connect to SSH server!\n"); - goto error; - } - - /* Create a session instance */ - data->session = libssh2_session_init(); - if(!data->session) { - fprintf(stderr, "ssh_tunnel_open: could not initialize SSH session!\n"); - goto error; - } + rfbClient *client = rfbGetClient(8,3,4); - /* ... start it up. This will trade welcome banners, exchange keys, - * and setup crypto, compression, and MAC layers + /* + Get args and create SSH tunnel */ - rc = libssh2_session_handshake(data->session, data->ssh_sock); + int rc = ssh_tunnel_init(); if(rc) { - fprintf(stderr, "ssh_tunnel_open: error when starting up SSH session: %d\n", rc); - goto error; - } - - /* At this point we havn't yet authenticated. The first thing to do - * is check the hostkey's fingerprint against our known hosts Your app - * may have it hard coded, may go to a file, may present it to the - * user, that's your call - */ - fingerprint = libssh2_hostkey_hash(data->session, LIBSSH2_HOSTKEY_HASH_SHA256); - if(ssh_fingerprint_check(fingerprint, 32, ssh_host, data->client) == -1) { - fprintf(stderr, "ssh_tunnel_open: fingerprint check indicated tunnel setup stop\n"); - goto error; - } - - /* check what authentication methods are available */ - userauthlist = libssh2_userauth_list(data->session, ssh_user, strlen(ssh_user)); - printf("ssh_tunnel_open: authentication methods: %s\n", userauthlist); - - if(ssh_password && strstr(userauthlist, "password")) { - if(libssh2_userauth_password(data->session, ssh_user, ssh_password)) { - fprintf(stderr, "ssh_tunnel_open: authentication by password failed.\n"); - goto error; - } - } - else if(ssh_priv_key_path && ssh_priv_key_password && strstr(userauthlist, "publickey")) { - if(libssh2_userauth_publickey_fromfile(data->session, ssh_user, ssh_pub_key_path, - ssh_priv_key_path, ssh_priv_key_password)) { - fprintf(stderr, "ssh_tunnel_open: authentication by public key failed!\n"); - goto error; - } - } - else { - fprintf(stderr, "ssh_tunnel_open: no supported authentication methods found!\n"); - goto error; - } - - /* Create and bind the local listening socket */ - data->local_listensock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); - if(data->local_listensock == RFB_INVALID_SOCKET) { - fprintf(stderr, "ssh_tunnel_open: socket: %s\n", strerror(errno)); - return NULL; - } - sin.sin_family = AF_INET; - sin.sin_port = htons(0); /* let the OS choose the port */ - sin.sin_addr.s_addr = inet_addr("127.0.0.1"); - if(INADDR_NONE == sin.sin_addr.s_addr) { - fprintf(stderr, "ssh_tunnel_open: inet_addr: %s\n", strerror(errno)); - goto error; - } - sinlen = sizeof(sin); - if(-1 == bind(data->local_listensock, (struct sockaddr *)&sin, sinlen)) { - fprintf(stderr, "bind: %s\n", strerror(errno)); - goto error; - } - if(-1 == listen(data->local_listensock, 1)) { - fprintf(stderr, "listen: %s\n", strerror(errno)); - goto error; - } - - /* get info back from OS */ - if (getsockname(data->local_listensock, (struct sockaddr *)&sin, &sinlen ) == -1){ - fprintf(stderr, "ssh_tunnel_open: getsockname: %s\n", strerror(errno)); - goto error; - } - - data->local_listenport = ntohs(sin.sin_port); - - printf("ssh_tunnel_open: waiting for TCP connection on %s:%d...\n", - inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); - - - /* Create the proxy thread */ -#if defined(LIBVNCSERVER_HAVE_LIBPTHREAD) - if (pthread_create(&data->thread, NULL, ssh_proxy_loop, data) != 0) { -#elif defined(LIBVNCSERVER_HAVE_WIN32THREADS) - if(data->thread = _beginthread(proxy_loop, 0, data) == 0); -#endif - fprintf(stderr, "ssh_tunnel_open: proxy thread creation failed\n"); - goto error; - } - - return data; - - error: - if (data->session) { - libssh2_session_disconnect(data->session, "Error in SSH tunnel setup"); - libssh2_session_free(data->session); + fprintf(stderr, "Tunnel initialization failed (%d)\n", rc); + return EXIT_FAILURE; } - rfbCloseSocket(data->local_listensock); - rfbCloseSocket(data->ssh_sock); - - free(data); + ssh_tunnel_t *data; + if (argc == 6) { + /* SSH tunnel w/ password */ + data = ssh_tunnel_open_with_password(argv[1], argv[2], argv[3], argv[4], atoi(argv[5]), client, ssh_fingerprint_check, ssh_signal_error); + } else if (argc == 7) { + /* SSH tunnel w/ privkey */ + FILE *privkey_file; + char *privkey_buffer; + long privkey_buffer_len; - return NULL; -} + // Open the file in binary mode for reading + privkey_file = fopen(argv[3], "rb"); + if (privkey_file == NULL) { + perror("Error opening privkey"); + return EXIT_FAILURE; + } -void ssh_tunnel_close(SshData *data) { - if(!data) - return; + // Determine the size of the file + fseek(privkey_file, 0, SEEK_END); + privkey_buffer_len = ftell(privkey_file); + fseek(privkey_file, 0, SEEK_SET); - /* the proxy thread does the internal cleanup as it can be - ended due to external reasons */ - THREAD_JOIN(data->thread); + if (privkey_buffer_len == -1) { + perror("Error getting file size of privkey"); + fclose(privkey_file); + return EXIT_FAILURE; + } - free(data); + // Allocate memory for the buffer + privkey_buffer = (char *)malloc(privkey_buffer_len); - printf("ssh_tunnel_close: done\n"); -} + if (privkey_buffer == NULL) { + perror("Error allocating memory for privkey"); + fclose(privkey_file); + return EXIT_FAILURE; + } + // Read the content of the file into the buffer + fread(privkey_buffer, 1, privkey_buffer_len, privkey_file); -void intHandler(int dummy) { - maintain_connection = 0; -} + if (ferror(privkey_file) != 0) { + perror("Error reading privkey"); + fclose(privkey_file); + free(privkey_buffer); + return EXIT_FAILURE; + } + // Close the file + fclose(privkey_file); -int main(int argc, char *argv[]) -{ - rfbClient *client = rfbGetClient(8,3,4); + data = ssh_tunnel_open_with_privkey(argv[1], argv[2], privkey_buffer, privkey_buffer_len, argv[4], argv[5], atoi(argv[6]), client, ssh_fingerprint_check, ssh_signal_error); - /* - Get args and create SSH tunnel - */ - int rc = libssh2_init(0); - if(rc) { - fprintf(stderr, "libssh2 initialization failed (%d)\n", rc); - return EXIT_FAILURE; - } + free(privkey_buffer); - SshData *data; - if (argc == 6) { - /* SSH tunnel w/ password */ - data = ssh_tunnel_open(argv[1], argv[2], argv[3], NULL, NULL, NULL, argv[4], atoi(argv[5]), client); - } else if (argc == 8) { - /* SSH tunnel w/ privkey */ - data = ssh_tunnel_open(argv[1], argv[2], NULL, argv[3], argv[4], argv[5], argv[6], atoi(argv[7]), client); } else { fprintf(stderr, - "Usage (w/ password): %s \n" - "Usage (w/ privkey): %s \n", + "Usage (w/ password): %s \n" + "Usage (w/ privkey): %s \n", argv[0], argv[0]); return(EXIT_FAILURE); } @@ -408,7 +116,7 @@ int main(int argc, char *argv[]) */ client->serverHost = strdup("127.0.0.1"); if(data) // might be NULL if ssh setup failed - client->serverPort = data->local_listenport; + client->serverPort = ssh_tunnel_get_port(data); rfbClientSetClientData(client, (void*)42, data); if (!data || !rfbInitClient(client,NULL,NULL)) @@ -437,8 +145,8 @@ int main(int argc, char *argv[]) /* free client */ rfbClientCleanup(client); - /* Teardown libssh2 */ - libssh2_exit(); + /* Teardown ssh tunnel machinery */ + ssh_tunnel_exit(); return EXIT_SUCCESS; }