diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..5bf2f83a8 --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: LLVM +IndentWidth: 4 \ No newline at end of file diff --git a/AUTHORS b/AUTHORS index 22e9964d4..e373cdae7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -48,7 +48,9 @@ Fabrice Fontaine, Cédric Georges, Stefan Weil, Rolf Eike Beer, Andreas Weigel, Gaurav Ujjwal, Tobias Junghans, Efim Bushmanov, Jonas Ådahl, Andri Yngvason, Tim Gates, Dinglan Peng, Ryo Ota, Kyle Evans, Kang Lin, Derek Schrock, Alex Converse, Thorsten Scherer, Marco Fortina, Antenore Gatta, Maxim Devaev -and Maksym Sobolyev. +Maksym Sobolyev, Huben Chang, Peter Vicman, Johann Obermayr, Nicolas Morais, +Volodymyr Samokhatko, Mingjie Shen, Joris Hans Meijer, Sérgio Basto, +Jeffrey Knockel and Christian Hitz. Probably we forgot quite a few people sending a patch here and there, which really made a difference. Without those, some obscure bugs still would diff --git a/CMakeLists.txt b/CMakeLists.txt index ff8b540d9..1797e4669 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,13 @@ cmake_minimum_required(VERSION 3.4) -project(LibVNCServer VERSION 0.9.14 LANGUAGES C) +set(PROJECT_LANGUAGES C) + +if(DEFINED CMAKE_CXX_COMPILER) + set(CMAKE_CXX_STANDARD 17) + list(APPEND PROJECT_LANGUAGES CXX) +endif(DEFINED CMAKE_CXX_COMPILER) + +project(LibVNCServer VERSION 0.9.15 LANGUAGES ${PROJECT_LANGUAGES}) include(CheckFunctionExists) include(CheckSymbolExists) include(CheckIncludeFile) @@ -25,6 +32,7 @@ set(TESTS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/test) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/") set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + if(CMAKE_GENERATOR MATCHES "Unix Makefiles|Ninja") # some LSP servers expect compile_commands.json in the project root add_custom_target( @@ -51,7 +59,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) @@ -67,7 +75,7 @@ option(WITH_SASL "Build with SASL support" ON) option(WITH_XCB "Build with XCB support" ON) option(WITH_EXAMPLES "Build examples" ON) option(WITH_TESTS "Build tests" ON) - +option(WITH_QT "Build the Qt client example" ON) if(WITH_ZLIB) find_package(ZLIB) @@ -125,6 +133,8 @@ if(WITH_JPEG) file(STRINGS ${JPEGLIB_H_PATH} FOUND_LIBJPEG_TURBO REGEX "JCS_EXT_RGB") if(NOT FOUND_LIBJPEG_TURBO) message(WARNING "*** The libjpeg library you are building against is not libjpeg-turbo. Performance will be reduced. You can obtain libjpeg-turbo from: https://sourceforge.net/projects/libjpeg-turbo/files/ ***") + else() + message(STATUS "Detected libjpeg-turbo via ${JPEGLIB_H_PATH}") endif() endif() endif(JPEG_FOUND) @@ -144,9 +154,18 @@ if(WITH_GTK) find_package(GTK2) endif(WITH_GTK) -if(WITH_LIBSSH2) - find_package(LibSSH2) -endif(WITH_LIBSSH2) +if(WITH_QT) + find_package(Qt5 COMPONENTS Core Widgets QUIET) +endif(WITH_QT) + +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) @@ -580,16 +599,6 @@ if(WITH_THREADS AND WITH_TIGHTVNC_FILETRANSFER AND CMAKE_USE_PTHREADS_INIT) ) endif(WITH_THREADS AND WITH_TIGHTVNC_FILETRANSFER AND CMAKE_USE_PTHREADS_INIT) -if(APPLE AND NOT IOS AND WITH_THREADS AND CMAKE_USE_PTHREADS_INIT) - set(LIBVNCSERVER_EXAMPLES - ${LIBVNCSERVER_EXAMPLES} - mac - ) - find_library(CARBON_LIBRARY Carbon) - find_library(IOKIT_LIBRARY IOKit) - find_library(IOSURFACE_LIBRARY IOSurface) -endif(APPLE AND NOT IOS AND WITH_THREADS AND CMAKE_USE_PTHREADS_INIT) - if(ANDROID) set(LIBVNCSERVER_EXAMPLES ${LIBVNCSERVER_EXAMPLES} @@ -630,19 +639,17 @@ 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}) set(LIBVNCCLIENT_EXAMPLES ${LIBVNCCLIENT_EXAMPLES} vnc2mpg @@ -654,15 +661,26 @@ if(WITH_EXAMPLES) add_executable(examples_${e} ${LIBVNCSRVEXAMPLE_DIR}/${e}.c) set_target_properties(examples_${e} PROPERTIES OUTPUT_NAME ${e}) set_target_properties(examples_${e} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/examples/server) - target_link_libraries(examples_${e} vncserver ${CMAKE_THREAD_LIBS_INIT} ${CARBON_LIBRARY} ${IOKIT_LIBRARY} ${IOSURFACE_LIBRARY} ${X11_xcb_LIB} ${X11_xcb_xtest_LIB} ${X11_xcb_keysyms_LIB}) + target_link_libraries(examples_${e} vncserver ${CMAKE_THREAD_LIBS_INIT} ${X11_xcb_LIB} ${X11_xcb_xtest_LIB} ${X11_xcb_keysyms_LIB}) endforeach(e ${LIBVNCSERVER_EXAMPLES}) foreach(e ${LIBVNCCLIENT_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}) + + #This example must have its own building instructions, + #apart from the other examples because it is written in + #C++, so it has a distinct file extension and depends on + #a C++ compiler + if(Qt5Widgets_FOUND AND WITH_QT AND DEFINED CMAKE_CXX_COMPILER) + add_executable(client_examples_qt5client ${LIBVNCCLIEXAMPLE_DIR}/qt5client.cpp ${LIBVNCCLIEXAMPLE_DIR}/${qt5client_EXTRA_SOURCES}) + set_target_properties(client_examples_qt5client PROPERTIES OUTPUT_NAME qt5client) + set_target_properties(client_examples_qt5client PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/examples/client) + target_link_libraries(client_examples_qt5client vncclient ${CMAKE_THREAD_LIBS_INIT} ${Qt5Widgets_LIBRARIES}) + endif(Qt5Widgets_FOUND AND WITH_QT AND DEFINED CMAKE_CXX_COMPILER) endif(WITH_EXAMPLES) # @@ -674,7 +692,7 @@ if(WITH_TESTS) # First fuzzing if(DEFINED ENV{LIB_FUZZING_ENGINE}) add_executable(fuzz_server ${TESTS_DIR}/fuzz_server.c) - target_link_libraries(fuzz_server vncserver ${CMAKE_THREAD_LIBS_INIT} ${CARBON_LIBRARY} ${IOKIT_LIBRARY} ${IOSURFACE_LIBRARY} $ENV{LIB_FUZZING_ENGINE}) + target_link_libraries(fuzz_server vncserver ${CMAKE_THREAD_LIBS_INIT} $ENV{LIB_FUZZING_ENGINE}) endif() if(UNIX) diff --git a/NEWS.md b/NEWS.md index 664a8db1b..72adb742c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,9 @@ ## Overall changes: * Added fuzzing with OSS-Fuzz thanks to Catena Cyber. + * Improved build system to have files where they are expected in contemporary open source projects. + Also split out Mac OS server example to own repo at https://github.com/LibVNC/macVNC + * Added Windows CI on GitHub. ## LibVNCServer/LibVNCClient: @@ -12,10 +15,15 @@ * Fixed LibVNCClient handling of UltraVNC MSLogonII when built with OpenSSL. * Added UTF8 clipboard handling. + * Added API to allow the client to specify a subregion of the server's framebuffer and + have LibVNCClient only ask for this, not the whole framebuffer. + * Fixed Tight decoding endianness issues. + * Added a Qt-based client example. ## LibVNCServer: * Added a proof-of-concept X11 example server. + * Improved SSH example by having it use [libsshtunnel](https://github.com/bk138/libsshtunnel/) instead of custom code. # 2022-12-18: Version 0.9.14 diff --git a/README.md b/README.md index 2005912b8..6cb614eec 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ LibVNCServer: A library for easy implementation of a VNC server. Copyright (C) 2001-2003 Johannes E. Schindelin +If you have a general question, it's best to [ask in the community chat](https://gitter.im/LibVNC/libvncserver). If your concern is about a bug or feature request instead, please use [the issue tracker](https://github.com/LibVNC/libvncserver/issues). + If you already used LibVNCServer, you probably want to read [NEWS](NEWS.md). What is it? @@ -53,7 +55,6 @@ RFB Protocol Support Status | Hextile | 5 | ✔ | ✔ | | Zlib | 6 | ✔ | ✔ | | Tight | 7 | ✔ | ✔ | -| Zlibhex | 8 | ✔ | | | Ultra | 9 | ✔ | ✔ | | TRLE | 15 | | ✔ | | ZRLE | 16 | ✔ | ✔ | @@ -123,7 +124,9 @@ Tested with MinGW-w64 on Debian, which you should install via `sudo apt install You can make use of the [provided toolchainfile](cmake/Toolchain-cross-mingw32-linux.cmake). It sets CMake to expect (optional) win32 dependencies like libjpeg and friends in the `deps` directory. Note that you need (probably self-built) development packages for -win32, the `-dev` packages coming with your distribution won't work. +win32, the `-dev` packages coming with your distribution won't work. Also note that you'll +need to put `libwinpthread-1.dll` in the build dir to run the examples. You can find this DLL +on your Linux build machine via `locate libwinpthread-1.dll`. mkdir build @@ -228,10 +231,10 @@ high latency or both. On a high-latency link, try asking for framebuffer updates continously, as RFB is client-pull per default, not server-push. One example implementation -can be found [here](https://github.com/bk138/multivnc/blob/master/src/VNCConn.cpp#L1112) +can be found [here](https://github.com/bk138/multivnc/blob/6251169ed11835ed709c0c191599937759856dda/src/VNCConn.cpp#L1112) and it definitely improves responsiveness. -There also is the [ContinuousUpdates RFB extension](https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#continuousupdates-pseudo-encoding), +There also is the [ContinuousUpdates RFB extension](https://github.com/rfbproto/rfbproto/blob/513c651fff1b213188daa5069444145a63e71617/rfbproto.rst#L4052), but that one is not supported by LibVNC (yet). ### Tackling Low Throughput @@ -250,7 +253,7 @@ bytes that get sent per framebuffer update: * Send a scaled-down version of your framebuffer. You can do the scaling in your application feeding data into LibVNCServer's framebuffer (would affect all clients) or let LibVNCServer do the work for you if your client requests a scaled screen - via a [SetScale or SetScaleFactor message](https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#74client-to-server-messages) + via a [SetScale or SetScaleFactor message](https://github.com/rfbproto/rfbproto/blob/513c651fff1b213188daa5069444145a63e71617/rfbproto.rst#L1344) (this is per-client scaling - UltraVNC viewers can request this). 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/qt5client.cpp b/examples/client/qt5client.cpp new file mode 100644 index 000000000..6dfaf4f9c --- /dev/null +++ b/examples/client/qt5client.cpp @@ -0,0 +1,149 @@ +/* + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + + /* + * This is an example on how to make a simple VNC client with + * Qt5 Widgets. It suitable for desktop apps, but may not be + * good for mobile. + * It does not implement any form of cryptography, + * authentication support, client-side cursors or framebuffer + * resizing. If you want to make this a part of your + * application, please notice that you may need to change + * the while(true) loop to disconnect the client. + * + * To build this example with all the other components of + * libvncserver, you may need to explicitly define a C++ + * compiler and the path to Qt libs when calling CMake. + * e.g. cmake -DCMAKE_PREFIX_PATH=~/Qt/5.15.2/gcc_64 + * -DCMAKE_CXX_COMPILER=g++ + */ + +#include +#include +#include +#include +#include +#include "rfb/rfbclient.h" +#include + +class VncViewer : public QWidget +{ + //if you want to make use of signals/slots, uncomment the line bellow: + //Q_OBJECT +public: + VncViewer(QWidget *parent = nullptr) {}; + virtual ~VncViewer() {}; + void start(); + std::string serverIp; + int serverPort; + std::thread *vncThread() const; + void paintEvent(QPaintEvent *event) override; + +private: + QImage m_image; + rfbClient *cl; + std::thread *m_vncThread; + static void finishedFramebufferUpdateStatic(rfbClient *cl); + void finishedFramebufferUpdate(rfbClient *cl); +}; + +void VncViewer::finishedFramebufferUpdateStatic(rfbClient *cl) +{ + VncViewer *ptr = static_cast(rfbClientGetClientData(cl, nullptr)); + ptr->finishedFramebufferUpdate(cl); +} + +void VncViewer::finishedFramebufferUpdate(rfbClient *cl) +{ + m_image = QImage(cl->frameBuffer, cl->width, cl->height, QImage::Format_RGB16); + + update(); +} +void VncViewer::paintEvent(QPaintEvent *event) +{ + event->accept(); + + QPainter painter(this); + painter.drawImage(this->rect(), m_image); +} + +void VncViewer::start() +{ + cl = rfbGetClient(8, 3, 4); + cl->format.depth = 24; + cl->format.depth = 16; + cl->format.bitsPerPixel = 16; + cl->format.redShift = 11; + cl->format.greenShift = 5; + cl->format.blueShift = 0; + cl->format.redMax = 0x1f; + cl->format.greenMax = 0x3f; + cl->format.blueMax = 0x1f; + cl->appData.compressLevel = 9; + cl->appData.qualityLevel = 1; + cl->appData.encodingsString = "tight ultra"; + cl->FinishedFrameBufferUpdate = finishedFramebufferUpdateStatic; + cl->serverHost = strdup(serverIp.c_str()); + cl->serverPort = serverPort; + cl->appData.useRemoteCursor = TRUE; + + rfbClientSetClientData(cl, nullptr, this); + + if (rfbInitClient(cl, 0, nullptr)) { + } else { + std::cout << "[INFO] disconnected" << std::endl; + return; + } + + m_vncThread = new std::thread([this]() { + while (true) { + int i = WaitForMessage(cl, 500); + if (i < 0) { + std::cout << "[INFO] disconnected" << std::endl; + rfbClientCleanup(cl); + break; + } + + if (i && !HandleRFBServerMessage(cl)) { + std::cout << "[INFO] disconnected" << std::endl; + rfbClientCleanup(cl); + break; + } + }; + }); +} + +int main(int argc, char *argv[]) +{ + if(argc < 3) { + std::cout << "Usage: " + << argv[0] + << " " + << " " + << "\n"; + return 1; + } + + QApplication a(argc, argv); + VncViewer vncViewer; + vncViewer.serverIp = std::string{argv[1]}; + vncViewer.serverPort = std::atoi(argv[2]); + vncViewer.show(); + vncViewer.start(); + return a.exec(); +} 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; } diff --git a/examples/server/README.md b/examples/server/README.md new file mode 100644 index 000000000..4d73512e7 --- /dev/null +++ b/examples/server/README.md @@ -0,0 +1,7 @@ +This directory contains simple examples showcasing certain parts of LibVNCServer's functionality. + +Some more full-blown implementations are: + +* [x11vnc](https://github.com/LibVNC/x11vnc) for X11 +* [droidVNC-NG](https://github.com/bk138/droidVNC-NG) for Android +* [macVNC](https://github.com/LibVNC/macVNC) for macOS diff --git a/examples/server/mac.c b/examples/server/mac.c deleted file mode 100644 index be5e98abd..000000000 --- a/examples/server/mac.c +++ /dev/null @@ -1,726 +0,0 @@ - -/* - * OSXvnc Copyright (C) 2001 Dan McGuirk . - * Original Xvnc code Copyright (C) 1999 AT&T Laboratories Cambridge. - * All Rights Reserved. - * - * Cut in two parts by Johannes Schindelin (2001): libvncserver and OSXvnc. - * - * Completely revamped and adapted to work with contemporary APIs by Christian Beier (2020). - * - * This file implements every system specific function for Mac OS X. - * - * It includes the keyboard function: - * - void KbdAddEvent(down, keySym, cl) - rfbBool down; - rfbKeySym keySym; - rfbClientPtr cl; - * - * the mouse function: - * - void PtrAddEvent(buttonMask, x, y, cl) - int buttonMask; - int x; - int y; - rfbClientPtr cl; - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -/* The main LibVNCServer screen object */ -rfbScreenInfoPtr rfbScreen; -/* Operation modes set by CLI options */ -rfbBool viewOnly = FALSE, sharedMode = FALSE; - -/* Two framebuffers. */ -void *frameBufferOne; -void *frameBufferTwo; - -/* Pointer to the current backbuffer. */ -void *backBuffer; - -/* The multi-sceen display number chosen by the user */ -int displayNumber = -1; -/* The corresponding multi-sceen display ID */ -CGDirectDisplayID displayID; - -/* The server's private event source */ -CGEventSourceRef eventSource; - -/* Screen (un)dimming machinery */ -rfbBool preventDimming = FALSE; -rfbBool preventSleep = TRUE; -static pthread_mutex_t dimming_mutex; -static unsigned long dim_time; -static unsigned long sleep_time; -static mach_port_t master_dev_port; -static io_connect_t power_mgt; -static rfbBool initialized = FALSE; -static rfbBool dim_time_saved = FALSE; -static rfbBool sleep_time_saved = FALSE; - -/* some variables to enable special behaviour */ -int startTime = -1, maxSecsToConnect = 0; -rfbBool disconnectAfterFirstClient = TRUE; - -/* a dictionary mapping characters to keycodes */ -CFMutableDictionaryRef charKeyMap; - -/* a dictionary mapping characters obtained by Shift to keycodes */ -CFMutableDictionaryRef charShiftKeyMap; - -/* a dictionary mapping characters obtained by Alt-Gr to keycodes */ -CFMutableDictionaryRef charAltGrKeyMap; - -/* a dictionary mapping characters obtained by Shift+Alt-Gr to keycodes */ -CFMutableDictionaryRef charShiftAltGrKeyMap; - -/* a table mapping special keys to keycodes. static as these are layout-independent */ -static int specialKeyMap[] = { - /* "Special" keys */ - XK_space, 49, /* Space */ - XK_Return, 36, /* Return */ - XK_Delete, 117, /* Delete */ - XK_Tab, 48, /* Tab */ - XK_Escape, 53, /* Esc */ - XK_Caps_Lock, 57, /* Caps Lock */ - XK_Num_Lock, 71, /* Num Lock */ - XK_Scroll_Lock, 107, /* Scroll Lock */ - XK_Pause, 113, /* Pause */ - XK_BackSpace, 51, /* Backspace */ - XK_Insert, 114, /* Insert */ - - /* Cursor movement */ - XK_Up, 126, /* Cursor Up */ - XK_Down, 125, /* Cursor Down */ - XK_Left, 123, /* Cursor Left */ - XK_Right, 124, /* Cursor Right */ - XK_Page_Up, 116, /* Page Up */ - XK_Page_Down, 121, /* Page Down */ - XK_Home, 115, /* Home */ - XK_End, 119, /* End */ - - /* Numeric keypad */ - XK_KP_0, 82, /* KP 0 */ - XK_KP_1, 83, /* KP 1 */ - XK_KP_2, 84, /* KP 2 */ - XK_KP_3, 85, /* KP 3 */ - XK_KP_4, 86, /* KP 4 */ - XK_KP_5, 87, /* KP 5 */ - XK_KP_6, 88, /* KP 6 */ - XK_KP_7, 89, /* KP 7 */ - XK_KP_8, 91, /* KP 8 */ - XK_KP_9, 92, /* KP 9 */ - XK_KP_Enter, 76, /* KP Enter */ - XK_KP_Decimal, 65, /* KP . */ - XK_KP_Add, 69, /* KP + */ - XK_KP_Subtract, 78, /* KP - */ - XK_KP_Multiply, 67, /* KP * */ - XK_KP_Divide, 75, /* KP / */ - - /* Function keys */ - XK_F1, 122, /* F1 */ - XK_F2, 120, /* F2 */ - XK_F3, 99, /* F3 */ - XK_F4, 118, /* F4 */ - XK_F5, 96, /* F5 */ - XK_F6, 97, /* F6 */ - XK_F7, 98, /* F7 */ - XK_F8, 100, /* F8 */ - XK_F9, 101, /* F9 */ - XK_F10, 109, /* F10 */ - XK_F11, 103, /* F11 */ - XK_F12, 111, /* F12 */ - - /* Modifier keys */ - XK_Shift_L, 56, /* Shift Left */ - XK_Shift_R, 56, /* Shift Right */ - XK_Control_L, 59, /* Ctrl Left */ - XK_Control_R, 59, /* Ctrl Right */ - XK_Meta_L, 58, /* Logo Left (-> Option) */ - XK_Meta_R, 58, /* Logo Right (-> Option) */ - XK_Alt_L, 55, /* Alt Left (-> Command) */ - XK_Alt_R, 55, /* Alt Right (-> Command) */ - XK_ISO_Level3_Shift, 61, /* Alt-Gr (-> Option Right) */ - 0x1008FF2B, 63, /* Fn */ - - /* Weirdness I can't figure out */ -#if 0 - XK_3270_PrintScreen, 105, /* PrintScrn */ - ??? 94, 50, /* International */ - XK_Menu, 50, /* Menu (-> International) */ -#endif -}; - -/* Global shifting modifier states */ -rfbBool isShiftDown; -rfbBool isAltGrDown; - - -static int -saveDimSettings(void) -{ - if (IOPMGetAggressiveness(power_mgt, - kPMMinutesToDim, - &dim_time) != kIOReturnSuccess) - return -1; - - dim_time_saved = TRUE; - return 0; -} - -static int -restoreDimSettings(void) -{ - if (!dim_time_saved) - return -1; - - if (IOPMSetAggressiveness(power_mgt, - kPMMinutesToDim, - dim_time) != kIOReturnSuccess) - return -1; - - dim_time_saved = FALSE; - dim_time = 0; - return 0; -} - -static int -saveSleepSettings(void) -{ - if (IOPMGetAggressiveness(power_mgt, - kPMMinutesToSleep, - &sleep_time) != kIOReturnSuccess) - return -1; - - sleep_time_saved = TRUE; - return 0; -} - -static int -restoreSleepSettings(void) -{ - if (!sleep_time_saved) - return -1; - - if (IOPMSetAggressiveness(power_mgt, - kPMMinutesToSleep, - sleep_time) != kIOReturnSuccess) - return -1; - - sleep_time_saved = FALSE; - sleep_time = 0; - return 0; -} - - -int -dimmingInit(void) -{ - pthread_mutex_init(&dimming_mutex, NULL); - - if (IOMasterPort(bootstrap_port, &master_dev_port) != kIOReturnSuccess) - return -1; - - if (!(power_mgt = IOPMFindPowerManagement(master_dev_port))) - return -1; - - if (preventDimming) { - if (saveDimSettings() < 0) - return -1; - if (IOPMSetAggressiveness(power_mgt, - kPMMinutesToDim, 0) != kIOReturnSuccess) - return -1; - } - - if (preventSleep) { - if (saveSleepSettings() < 0) - return -1; - if (IOPMSetAggressiveness(power_mgt, - kPMMinutesToSleep, 0) != kIOReturnSuccess) - return -1; - } - - initialized = TRUE; - return 0; -} - - -int -undim(void) -{ - int result = -1; - - pthread_mutex_lock(&dimming_mutex); - - if (!initialized) - goto DONE; - - if (!preventDimming) { - if (saveDimSettings() < 0) - goto DONE; - if (IOPMSetAggressiveness(power_mgt, kPMMinutesToDim, 0) != kIOReturnSuccess) - goto DONE; - if (restoreDimSettings() < 0) - goto DONE; - } - - if (!preventSleep) { - if (saveSleepSettings() < 0) - goto DONE; - if (IOPMSetAggressiveness(power_mgt, kPMMinutesToSleep, 0) != kIOReturnSuccess) - goto DONE; - if (restoreSleepSettings() < 0) - goto DONE; - } - - result = 0; - - DONE: - pthread_mutex_unlock(&dimming_mutex); - return result; -} - - -int -dimmingShutdown(void) -{ - int result = -1; - - if (!initialized) - goto DONE; - - pthread_mutex_lock(&dimming_mutex); - if (dim_time_saved) - if (restoreDimSettings() < 0) - goto DONE; - if (sleep_time_saved) - if (restoreSleepSettings() < 0) - goto DONE; - - result = 0; - - DONE: - pthread_mutex_unlock(&dimming_mutex); - return result; -} - -void serverShutdown(rfbClientPtr cl); - -/* - Synthesize a keyboard event. This is not called on the main thread due to rfbRunEventLoop(..,..,TRUE), but it works. - We first look up the incoming keysym in the keymap for special keys (and save state of the shifting modifiers). - If the incoming keysym does not map to a special key, the char keymaps pertaining to the respective shifting modifier are used - in order to allow for keyboard combos with other modifiers. - As a last resort, the incoming keysym is simply used as a Unicode value. This way MacOS does not support any modifiers though. -*/ -void -KbdAddEvent(rfbBool down, rfbKeySym keySym, struct _rfbClientRec* cl) -{ - int i; - CGKeyCode keyCode = -1; - CGEventRef keyboardEvent; - int specialKeyFound = 0; - - undim(); - - /* look for special key */ - for (i = 0; i < (sizeof(specialKeyMap) / sizeof(int)); i += 2) { - if (specialKeyMap[i] == keySym) { - keyCode = specialKeyMap[i+1]; - specialKeyFound = 1; - break; - } - } - - if(specialKeyFound) { - /* keycode for special key found */ - keyboardEvent = CGEventCreateKeyboardEvent(eventSource, keyCode, down); - /* save state of shifting modifiers */ - if(keySym == XK_ISO_Level3_Shift) - isAltGrDown = down; - if(keySym == XK_Shift_L || keySym == XK_Shift_R) - isShiftDown = down; - - } else { - /* look for char key */ - size_t keyCodeFromDict; - CFStringRef charStr = CFStringCreateWithCharacters(kCFAllocatorDefault, (UniChar*)&keySym, 1); - CFMutableDictionaryRef keyMap = charKeyMap; - if(isShiftDown && !isAltGrDown) - keyMap = charShiftKeyMap; - if(!isShiftDown && isAltGrDown) - keyMap = charAltGrKeyMap; - if(isShiftDown && isAltGrDown) - keyMap = charShiftAltGrKeyMap; - - if (CFDictionaryGetValueIfPresent(keyMap, charStr, (const void **)&keyCodeFromDict)) { - /* keycode for ASCII key found */ - keyboardEvent = CGEventCreateKeyboardEvent(eventSource, keyCodeFromDict, down); - } else { - /* last resort: use the symbol's utf-16 value, does not support modifiers though */ - keyboardEvent = CGEventCreateKeyboardEvent(eventSource, 0, down); - CGEventKeyboardSetUnicodeString(keyboardEvent, 1, (UniChar*)&keySym); - } - - CFRelease(charStr); - } - - /* Set the Shift modifier explicitly as MacOS sometimes gets internal state wrong and Shift stuck. */ - CGEventSetFlags(keyboardEvent, CGEventGetFlags(keyboardEvent) & (isShiftDown ? kCGEventFlagMaskShift : ~kCGEventFlagMaskShift)); - - CGEventPost(kCGSessionEventTap, keyboardEvent); - CFRelease(keyboardEvent); -} - -/* Synthesize a mouse event. This is not called on the main thread due to rfbRunEventLoop(..,..,TRUE), but it works. */ -void -PtrAddEvent(buttonMask, x, y, cl) - int buttonMask; - int x; - int y; - rfbClientPtr cl; -{ - CGPoint position; - CGRect displayBounds = CGDisplayBounds(displayID); - CGEventRef mouseEvent = NULL; - - undim(); - - position.x = x + displayBounds.origin.x; - position.y = y + displayBounds.origin.y; - - /* map buttons 4 5 6 7 to scroll events as per https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#745pointerevent */ - if(buttonMask & (1 << 3)) - mouseEvent = CGEventCreateScrollWheelEvent(eventSource, kCGScrollEventUnitLine, 2, 1, 0); - if(buttonMask & (1 << 4)) - mouseEvent = CGEventCreateScrollWheelEvent(eventSource, kCGScrollEventUnitLine, 2, -1, 0); - if(buttonMask & (1 << 5)) - mouseEvent = CGEventCreateScrollWheelEvent(eventSource, kCGScrollEventUnitLine, 2, 0, 1); - if(buttonMask & (1 << 6)) - mouseEvent = CGEventCreateScrollWheelEvent(eventSource, kCGScrollEventUnitLine, 2, 0, -1); - - if (mouseEvent) { - CGEventPost(kCGSessionEventTap, mouseEvent); - CFRelease(mouseEvent); - } - else { - /* - Use the deprecated CGPostMouseEvent API here as we get a buttonmask plus position which is pretty low-level - whereas CGEventCreateMouseEvent is expecting higher-level events. This allows for direct injection of - double clicks and drags whereas we would need to synthesize these events for the high-level API. - */ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGPostMouseEvent(position, TRUE, 3, - (buttonMask & (1 << 0)) ? TRUE : FALSE, - (buttonMask & (1 << 2)) ? TRUE : FALSE, - (buttonMask & (1 << 1)) ? TRUE : FALSE); -#pragma clang diagnostic pop - } -} - - -/* - Initialises keyboard handling: - This creates four keymaps mapping UniChars to keycodes for the current keyboard layout with no shifting modifiers, Shift, Alt-Gr and Shift+Alt-Gr applied, respectively. - */ -rfbBool keyboardInit() -{ - size_t i, keyCodeCount=128; - TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); - const UCKeyboardLayout *keyboardLayout; - - if(!currentKeyboard) { - fprintf(stderr, "Could not get current keyboard info\n"); - return FALSE; - } - - keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData)); - - printf("Found keyboard layout '%s'\n", CFStringGetCStringPtr(TISGetInputSourceProperty(currentKeyboard, kTISPropertyInputSourceID), kCFStringEncodingUTF8)); - - charKeyMap = CFDictionaryCreateMutable(kCFAllocatorDefault, keyCodeCount, &kCFCopyStringDictionaryKeyCallBacks, NULL); - charShiftKeyMap = CFDictionaryCreateMutable(kCFAllocatorDefault, keyCodeCount, &kCFCopyStringDictionaryKeyCallBacks, NULL); - charAltGrKeyMap = CFDictionaryCreateMutable(kCFAllocatorDefault, keyCodeCount, &kCFCopyStringDictionaryKeyCallBacks, NULL); - charShiftAltGrKeyMap = CFDictionaryCreateMutable(kCFAllocatorDefault, keyCodeCount, &kCFCopyStringDictionaryKeyCallBacks, NULL); - - if(!charKeyMap || !charShiftKeyMap || !charAltGrKeyMap || !charShiftAltGrKeyMap) { - fprintf(stderr, "Could not create keymaps\n"); - return FALSE; - } - - /* Loop through every keycode to find the character it is mapping to. */ - for (i = 0; i < keyCodeCount; ++i) { - UInt32 deadKeyState = 0; - UniChar chars[4]; - UniCharCount realLength; - UInt32 m, modifiers[] = {0, kCGEventFlagMaskShift, kCGEventFlagMaskAlternate, kCGEventFlagMaskShift|kCGEventFlagMaskAlternate}; - - /* do this for no modifier, shift and alt-gr applied */ - for(m = 0; m < sizeof(modifiers) / sizeof(modifiers[0]); ++m) { - UCKeyTranslate(keyboardLayout, - i, - kUCKeyActionDisplay, - (modifiers[m] >> 16) & 0xff, - LMGetKbdType(), - kUCKeyTranslateNoDeadKeysBit, - &deadKeyState, - sizeof(chars) / sizeof(chars[0]), - &realLength, - chars); - - CFStringRef string = CFStringCreateWithCharacters(kCFAllocatorDefault, chars, 1); - if(string) { - switch(modifiers[m]) { - case 0: - CFDictionaryAddValue(charKeyMap, string, (const void *)i); - break; - case kCGEventFlagMaskShift: - CFDictionaryAddValue(charShiftKeyMap, string, (const void *)i); - break; - case kCGEventFlagMaskAlternate: - CFDictionaryAddValue(charAltGrKeyMap, string, (const void *)i); - break; - case kCGEventFlagMaskShift|kCGEventFlagMaskAlternate: - CFDictionaryAddValue(charShiftAltGrKeyMap, string, (const void *)i); - break; - } - - CFRelease(string); - } - } - } - - CFRelease(currentKeyboard); - - return TRUE; -} - - -rfbBool -ScreenInit(int argc, char**argv) -{ - int bitsPerSample = 8; - CGDisplayCount displayCount; - CGDirectDisplayID displays[32]; - - /* grab the active displays */ - CGGetActiveDisplayList(32, displays, &displayCount); - for (int i=0; iserverFormat.redShift = bitsPerSample*2; - rfbScreen->serverFormat.greenShift = bitsPerSample*1; - rfbScreen->serverFormat.blueShift = 0; - - gethostname(rfbScreen->thisHost, 255); - - frameBufferOne = malloc(CGDisplayPixelsWide(displayID) * CGDisplayPixelsHigh(displayID) * 4); - frameBufferTwo = malloc(CGDisplayPixelsWide(displayID) * CGDisplayPixelsHigh(displayID) * 4); - - /* back buffer */ - backBuffer = frameBufferOne; - /* front buffer */ - rfbScreen->frameBuffer = frameBufferTwo; - - /* we already capture the cursor in the framebuffer */ - rfbScreen->cursor = NULL; - - rfbScreen->ptrAddEvent = PtrAddEvent; - rfbScreen->kbdAddEvent = KbdAddEvent; - - if(sharedMode) { - rfbScreen->alwaysShared = TRUE; - } - - dispatch_queue_t dispatchQueue = dispatch_queue_create("libvncserver.examples.mac", NULL); - CGDisplayStreamRef stream = CGDisplayStreamCreateWithDispatchQueue(displayID, - CGDisplayPixelsWide(displayID), - CGDisplayPixelsHigh(displayID), - 'BGRA', - nil, - dispatchQueue, - ^(CGDisplayStreamFrameStatus status, - uint64_t displayTime, - IOSurfaceRef frameSurface, - CGDisplayStreamUpdateRef updateRef) { - - if (status == kCGDisplayStreamFrameStatusFrameComplete && frameSurface != NULL) { - rfbClientIteratorPtr iterator; - rfbClientPtr cl; - const CGRect *updatedRects; - size_t updatedRectsCount; - size_t r; - - if(startTime>0 && time(0)>startTime+maxSecsToConnect) - serverShutdown(0); - - /* - Copy new frame to back buffer. - */ - IOSurfaceLock(frameSurface, kIOSurfaceLockReadOnly, NULL); - - memcpy(backBuffer, - IOSurfaceGetBaseAddress(frameSurface), - CGDisplayPixelsWide(displayID) * CGDisplayPixelsHigh(displayID) * 4); - - IOSurfaceUnlock(frameSurface, kIOSurfaceLockReadOnly, NULL); - - /* Lock out client reads. */ - iterator=rfbGetClientIterator(rfbScreen); - while((cl=rfbClientIteratorNext(iterator))) { - LOCK(cl->sendMutex); - } - rfbReleaseClientIterator(iterator); - - /* Swap framebuffers. */ - if (backBuffer == frameBufferOne) { - backBuffer = frameBufferTwo; - rfbScreen->frameBuffer = frameBufferOne; - } else { - backBuffer = frameBufferOne; - rfbScreen->frameBuffer = frameBufferTwo; - } - - /* Mark modified rects in new framebuffer. */ - updatedRects = CGDisplayStreamUpdateGetRects(updateRef, kCGDisplayStreamUpdateDirtyRects, &updatedRectsCount); - for(r=0; rsendMutex); - } - rfbReleaseClientIterator(iterator); - } - - }); - if(stream) { - CGDisplayStreamStart(stream); - } else { - rfbErr("Could not get screen contents. Check if the program has been given screen recording permissions in 'System Preferences'->'Security & Privacy'->'Privacy'->'Screen Recording'.\n"); - return FALSE; - } - - rfbInitServer(rfbScreen); - - return TRUE; -} - - -void clientGone(rfbClientPtr cl) -{ - serverShutdown(cl); -} - -enum rfbNewClientAction newClient(rfbClientPtr cl) -{ - if(startTime>0 && time(0)>startTime+maxSecsToConnect) - serverShutdown(cl); - - if(disconnectAfterFirstClient) - cl->clientGoneHook = clientGone; - - cl->viewOnly = viewOnly; - - return(RFB_CLIENT_ACCEPT); -} - -int main(int argc,char *argv[]) -{ - int i; - - for(i=argc-1;i>0;i--) - if(i'Security & Privacy'->'Privacy'->'Accessibility'.\n"); - exit(1); - } - - dimmingInit(); - - /* Create a private event source for the server. This helps a lot with modifier keys getting stuck on the OS side - (but does not completely mitigate the issue: For this, we keep track of modifier key state and set it specifically - for the generated keyboard event in the keyboard event handler). */ - eventSource = CGEventSourceCreate(kCGEventSourceStatePrivate); - - if(!keyboardInit()) - exit(1); - - if(!ScreenInit(argc,argv)) - exit(1); - rfbScreen->newClientHook = newClient; - - rfbRunEventLoop(rfbScreen,-1,TRUE); - - /* - The VNC machinery is in the background now and framebuffer updating happens on another thread as well. - */ - while(1) { - /* Nothing left to do on the main thread. */ - sleep(1); - } - - dimmingShutdown(); - - return(0); /* never ... */ -} - -void serverShutdown(rfbClientPtr cl) -{ - rfbScreenCleanup(rfbScreen); - dimmingShutdown(); - exit(0); -} diff --git a/include/rfb/rfb.h b/include/rfb/rfb.h index 779092588..e7da41a9f 100644 --- a/include/rfb/rfb.h +++ b/include/rfb/rfb.h @@ -379,6 +379,9 @@ typedef struct _rfbScreenInfo rfbSetXCutTextUTF8ProcPtr setXCutTextUTF8; #endif + /* Timeout value for select() calls, mainly used for multithreaded servers. */ + int select_timeout_usec; + /* multicast stuff */ diff --git a/include/rfb/threading.h b/include/rfb/threading.h index 2a497814b..a587a2951 100644 --- a/include/rfb/threading.h +++ b/include/rfb/threading.h @@ -57,6 +57,7 @@ #define THREAD_ROUTINE_RETURN_VALUE NULL #define THREAD_SLEEP_MS(ms) usleep(ms*1000) #define THREAD_JOIN(thread) pthread_join(thread, NULL) +#define THREAD_DETACH(thread) pthread_detach(thread) #define CURRENT_THREAD_ID pthread_self() #endif #elif defined(LIBVNCSERVER_HAVE_WIN32THREADS) @@ -77,6 +78,7 @@ #define THREAD_ROUTINE_RETURN_VALUE #define THREAD_SLEEP_MS(ms) Sleep(ms) #define THREAD_JOIN(thread) WaitForSingleObject((HANDLE)thread, INFINITE) +#define THREAD_DETACH(thread) CloseHandle((HANDLE)thread) #define CURRENT_THREAD_ID GetCurrentThreadId() #else #define LOCK(mutex) diff --git a/src/common/crypto_openssl.c b/src/common/crypto_openssl.c index dc1ee093b..28f042721 100644 --- a/src/common/crypto_openssl.c +++ b/src/common/crypto_openssl.c @@ -115,24 +115,45 @@ int encrypt_rfbdes(void *out, int *out_len, const unsigned char key[8], const vo int decrypt_rfbdes(void *out, int *out_len, const unsigned char key[8], const void *in, const size_t in_len) { int result = 0; - EVP_CIPHER_CTX *des; + EVP_CIPHER_CTX *des = NULL; unsigned char mungedkey[8]; int i; +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) + OSSL_PROVIDER *providerLegacy = NULL; + OSSL_PROVIDER *providerDefault = NULL; +#endif for (i = 0; i < 8; i++) mungedkey[i] = reverseByte(key[i]); +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) + /* Load Multiple providers into the default (NULL) library context */ + if (!(providerLegacy = OSSL_PROVIDER_load(NULL, "legacy"))) + goto out; + if (!(providerDefault = OSSL_PROVIDER_load(NULL, "default"))) + goto out; +#endif + if(!(des = EVP_CIPHER_CTX_new())) goto out; if(!EVP_DecryptInit_ex(des, EVP_des_ecb(), NULL, mungedkey, NULL)) goto out; + if(!EVP_CIPHER_CTX_set_padding(des, 0)) + goto out; if(!EVP_DecryptUpdate(des, out, out_len, in, in_len)) goto out; result = 1; out: - EVP_CIPHER_CTX_free(des); + if (des) + EVP_CIPHER_CTX_free(des); +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) + if (providerLegacy) + OSSL_PROVIDER_unload(providerLegacy); + if (providerDefault) + OSSL_PROVIDER_unload(providerDefault); +#endif return result; } diff --git a/src/common/sockets.c b/src/common/sockets.c index de66fa768..4fe3634cd 100644 --- a/src/common/sockets.c +++ b/src/common/sockets.c @@ -32,8 +32,8 @@ rfbBool sock_set_nonblocking(rfbSocket sock, rfbBool non_blocking, void (*log)(const char *format, ...)) { #ifdef WIN32 - unsigned long block = non_blocking ? 0 : 1; - if(ioctlsocket(sock, FIONBIO, &block) == SOCKET_ERROR) { + unsigned long non_blocking_ulong = non_blocking; + if(ioctlsocket(sock, FIONBIO, &non_blocking_ulong) == SOCKET_ERROR) { errno=WSAGetLastError(); #else int flags = fcntl(sock, F_GETFL); diff --git a/src/libvncclient/tight.c b/src/libvncclient/tight.c index 5b922daed..57a3715b9 100644 --- a/src/libvncclient/tight.c +++ b/src/libvncclient/tight.c @@ -54,24 +54,62 @@ #ifndef RGB_TO_PIXEL -#define RGB_TO_PIXEL(bpp,r,g,b) \ - (((CARD##bpp)(r) & client->format.redMax) << client->format.redShift | \ - ((CARD##bpp)(g) & client->format.greenMax) << client->format.greenShift | \ - ((CARD##bpp)(b) & client->format.blueMax) << client->format.blueShift) - -#define RGB24_TO_PIXEL(bpp,r,g,b) \ - ((((CARD##bpp)(r) & 0xFF) * client->format.redMax + 127) / 255 \ - << client->format.redShift | \ - (((CARD##bpp)(g) & 0xFF) * client->format.greenMax + 127) / 255 \ - << client->format.greenShift | \ - (((CARD##bpp)(b) & 0xFF) * client->format.blueMax + 127) / 255 \ - << client->format.blueShift) - -#define RGB24_TO_PIXEL32(r,g,b) \ - (((uint32_t)(r) & 0xFF) << client->format.redShift | \ - ((uint32_t)(g) & 0xFF) << client->format.greenShift | \ +/* + * Unfortunately we cannot use rfbClientSwap16IfLE() and related macros here as + * rfbClientSwap16IfLE() swaps when the host byte order of the client is + * little-endian, but we want to swap when the host byte order of the client is + * different from the byte order of the pixel format it is using. In other + * words, rfbClientSwap16IfLE() will do the right thing only when the pixel + * format is big-endian. + */ +#define Swap8(c) (c) +#define Swap16(s) ((((s) & 0xff) << 8) | (((s) >> 8) & 0xff)) +#define Swap32(l) ((((l) >> 24) & 0x000000ff)| \ + (((l) & 0x00ff0000) >> 8) | \ + (((l) & 0x0000ff00) << 8) | \ + (((l) & 0x000000ff) << 24)) + +#ifdef LIBVNCSERVER_WORDS_BIGENDIAN +#define NEED_SWAP (!client->format.bigEndian) +#else +#define NEED_SWAP (client->format.bigEndian) +#endif + +#define RGB_TO_PIXEL_NOSWAP(card,r,g,b) \ + (((card)(r) & client->format.redMax) << client->format.redShift | \ + ((card)(g) & client->format.greenMax) << client->format.greenShift | \ + ((card)(b) & client->format.blueMax) << client->format.blueShift) + +#define RGB_TO_PIXEL(bpp,r,g,b) NEED_SWAP ? \ + CONCAT2(Swap,bpp)(RGB_TO_PIXEL_NOSWAP(CARD##bpp,r,g,b)) : \ + RGB_TO_PIXEL_NOSWAP(CARD##bpp,r,g,b) + +#define RGB24_TO_PIXEL_NOSWAP(card,r,g,b) \ + ((((card)(r) & 0xFF) * client->format.redMax + 127) / 255 \ + << client->format.redShift | \ + (((card)(g) & 0xFF) * client->format.greenMax + 127) / 255 \ + << client->format.greenShift | \ + (((card)(b) & 0xFF) * client->format.blueMax + 127) / 255 \ + << client->format.blueShift) + +#define RGB24_TO_PIXEL(bpp,r,g,b) NEED_SWAP ? \ + CONCAT2(Swap,bpp)(RGB24_TO_PIXEL_NOSWAP(CARD##bpp,r,g,b)) : \ + RGB24_TO_PIXEL_NOSWAP(CARD##bpp,r,g,b) + +#define RGB24_TO_PIXEL32_NOSWAP(r,g,b) \ + (((uint32_t)(r) & 0xFF) << client->format.redShift | \ + ((uint32_t)(g) & 0xFF) << client->format.greenShift | \ ((uint32_t)(b) & 0xFF) << client->format.blueShift) +#define RGB24_TO_PIXEL32_SWAP(r,g,b) \ + (((uint32_t)(r) & 0xFF) << (24 - client->format.redShift) | \ + ((uint32_t)(g) & 0xFF) << (24 - client->format.greenShift) | \ + ((uint32_t)(b) & 0xFF) << (24 - client->format.blueShift)) + +#define RGB24_TO_PIXEL32(r,g,b) NEED_SWAP ? \ + RGB24_TO_PIXEL32_SWAP(r,g,b) : \ + RGB24_TO_PIXEL32_NOSWAP(r,g,b) + #endif /* Type declarations */ diff --git a/src/libvncclient/tls_gnutls.c b/src/libvncclient/tls_gnutls.c index 10a2cec14..734976de5 100644 --- a/src/libvncclient/tls_gnutls.c +++ b/src/libvncclient/tls_gnutls.c @@ -25,7 +25,7 @@ #include "tls.h" -static const char *rfbTLSPriority = "NORMAL:+DHE-DSS:+RSA:+DHE-RSA:+SRP"; +static const char *rfbTLSPriority = "NORMAL:+DHE-DSS:+RSA:+DHE-RSA"; static const char *rfbAnonTLSPriority = "NORMAL:+ANON-ECDH:+ANON-DH"; #define DH_BITS 1024 diff --git a/src/libvncserver/cargs.c b/src/libvncserver/cargs.c index 6a327a774..d0e2013b9 100644 --- a/src/libvncserver/cargs.c +++ b/src/libvncserver/cargs.c @@ -89,7 +89,7 @@ rfbProcessArguments(rfbScreenInfoPtr rfbScreen,int* argc, char *argv[]) if(!argc) return TRUE; for (i = i1 = 1; i < *argc;) { - if (strcmp(argv[i], "-help") == 0) { + if (strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { rfbUsage(); return FALSE; } else if (strcmp(argv[i], "-rfbport") == 0) { /* -rfbport port */ diff --git a/src/libvncserver/httpd.c b/src/libvncserver/httpd.c index 96a6eb2b9..2186ae547 100644 --- a/src/libvncserver/httpd.c +++ b/src/libvncserver/httpd.c @@ -150,6 +150,11 @@ void rfbHttpShutdownSockets(rfbScreenInfoPtr rfbScreen) { /* * httpCheckFds is called from ProcessInputEvents to check for input on the * HTTP socket(s). If there is input to process, httpProcessInput is called. + * TODO When a new client connects, the active HTTP connection is abruptly + * terminated, the ongoing download or data transfer for the active client will + * be cut off because the server closes the socket without waiting for the + * transfer to complete. The new client then takes over the single httpSock, and + * the previous client loses its connection. */ void @@ -198,7 +203,8 @@ rfbHttpCheckFds(rfbScreenInfoPtr rfbScreen) httpProcessInput(rfbScreen); } - if (FD_ISSET(rfbScreen->httpListenSock, &fds) || FD_ISSET(rfbScreen->httpListen6Sock, &fds)) { + if ((rfbScreen->httpListenSock != RFB_INVALID_SOCKET && FD_ISSET(rfbScreen->httpListenSock, &fds)) + || (rfbScreen->httpListen6Sock != RFB_INVALID_SOCKET && FD_ISSET(rfbScreen->httpListen6Sock, &fds))) { if (rfbScreen->httpSock != RFB_INVALID_SOCKET) rfbCloseSocket(rfbScreen->httpSock); if(FD_ISSET(rfbScreen->httpListenSock, &fds)) { diff --git a/src/libvncserver/main.c b/src/libvncserver/main.c index 32aebdfe4..c4dfcd25d 100644 --- a/src/libvncserver/main.c +++ b/src/libvncserver/main.c @@ -698,6 +698,7 @@ listenerRun(void *data) rfbClientPtr cl = NULL; socklen_t len; fd_set listen_fds; /* temp file descriptor list for select() */ + struct timeval tv; /* Only checking socket state here and not using rfbIsActive() @@ -709,9 +710,9 @@ listenerRun(void *data) return true, not ending the listener, making the join in rfbShutdownServer() wait forever... */ - /* TODO: HTTP is not handled */ while (screen->socketState != RFB_SOCKET_SHUTDOWN) { client_fd = -1; + cl = NULL; FD_ZERO(&listen_fds); if(screen->listenSock != RFB_INVALID_SOCKET) FD_SET(screen->listenSock, &listen_fds); @@ -722,7 +723,9 @@ listenerRun(void *data) screen->maxFd = rfbMax(screen->maxFd, screen->pipe_notify_listener_thread[0]); #endif - if (select(screen->maxFd+1, &listen_fds, NULL, NULL, NULL) == -1) { + tv.tv_sec = 0; + tv.tv_usec = screen->select_timeout_usec; + if (select(screen->maxFd+1, &listen_fds, NULL, NULL, &tv) == -1) { rfbLogPerror("listenerRun: error in select"); return THREAD_ROUTINE_RETURN_VALUE; } @@ -738,17 +741,20 @@ listenerRun(void *data) } #endif - /* there is something on the listening sockets, handle new connections */ + /* If there is something on the listening sockets, handle new connections */ len = sizeof (peer); - if (FD_ISSET(screen->listenSock, &listen_fds)) + if (screen->listenSock != RFB_INVALID_SOCKET && FD_ISSET(screen->listenSock, &listen_fds)) client_fd = accept(screen->listenSock, (struct sockaddr*)&peer, &len); - else if (FD_ISSET(screen->listen6Sock, &listen_fds)) + else if (screen->listen6Sock != RFB_INVALID_SOCKET && FD_ISSET(screen->listen6Sock, &listen_fds)) client_fd = accept(screen->listen6Sock, (struct sockaddr*)&peer, &len); if(client_fd >= 0) cl = rfbNewClient(screen,client_fd); if (cl && !cl->onHold ) rfbStartOnHoldClient(cl); + + /* handle HTTP */ + rfbHttpCheckFds(screen); } return THREAD_ROUTINE_RETURN_VALUE; } @@ -1248,11 +1254,11 @@ void rfbNewFramebuffer(rfbScreenInfoPtr screen, char *framebuffer, void rfbScreenCleanup(rfbScreenInfoPtr screen) { rfbClientIteratorPtr i=rfbGetClientIterator(screen); - rfbClientPtr cl,cl1=rfbClientIteratorNext(i); - while(cl1) { - cl=rfbClientIteratorNext(i); - rfbClientConnectionGone(cl1); - cl1=cl; + rfbClientPtr nextCl,currentCl=rfbClientIteratorNext(i); + while(currentCl) { + nextCl=rfbClientIteratorNext(i); + rfbClientConnectionGone(currentCl); + currentCl=nextCl; } rfbReleaseClientIterator(i); @@ -1484,6 +1490,11 @@ rfbBool rfbIsActive(rfbScreenInfoPtr screenInfo) { void rfbRunEventLoop(rfbScreenInfoPtr screen, long usec, rfbBool runInBackground) { + if(usec<0) + usec=screen->deferUpdateTime*1000; + + screen->select_timeout_usec = usec; + if(runInBackground) { #ifdef LIBVNCSERVER_HAVE_LIBPTHREAD screen->backgroundLoop = TRUE; @@ -1506,9 +1517,6 @@ void rfbRunEventLoop(rfbScreenInfoPtr screen, long usec, rfbBool runInBackground #endif } - if(usec<0) - usec=screen->deferUpdateTime*1000; - while(rfbIsActive(screen)) rfbProcessEvents(screen,usec); } diff --git a/test/blooptest.c b/test/blooptest.c deleted file mode 100644 index a2661e83c..000000000 --- a/test/blooptest.c +++ /dev/null @@ -1,2 +0,0 @@ -#define BACKGROUND_LOOP_TEST -#include "../examples/example.c" diff --git a/webclients/novnc b/webclients/novnc index 90455eef0..7fcf9dcfe 160000 --- a/webclients/novnc +++ b/webclients/novnc @@ -1 +1 @@ -Subproject commit 90455eef0692d2e35276fd31286114d0955016b0 +Subproject commit 7fcf9dcfe0cc5b14e3841a4429dc091a6ffca861