diff options
Diffstat (limited to 'common')
50 files changed, 1194 insertions, 1654 deletions
diff --git a/common/core/CMakeLists.txt b/common/core/CMakeLists.txt index 1932e5db..7e58acc0 100644 --- a/common/core/CMakeLists.txt +++ b/common/core/CMakeLists.txt @@ -5,10 +5,8 @@ add_library(core STATIC Logger_file.cxx Logger_stdio.cxx LogWriter.cxx - Mutex.cxx Region.cxx Timer.cxx - Thread.cxx string.cxx time.cxx xdgdirs.cxx) @@ -16,11 +14,9 @@ add_library(core STATIC target_include_directories(core PUBLIC ${CMAKE_SOURCE_DIR}/common) target_include_directories(core SYSTEM PUBLIC ${PIXMAN_INCLUDE_DIRS}) target_link_libraries(core ${PIXMAN_LIBRARIES}) -target_link_directories(core PUBLIC ${PIXMAN_LIBRARY_DIRS}) if(UNIX) target_sources(core PRIVATE Logger_syslog.cxx) - target_link_libraries(core pthread) endif() if(WIN32) diff --git a/common/core/Configuration.cxx b/common/core/Configuration.cxx index 129d1b9e..e6affb06 100644 --- a/common/core/Configuration.cxx +++ b/common/core/Configuration.cxx @@ -570,6 +570,10 @@ bool ListParameter<ValueType>::setParam(const char* v) entry.erase(0, entry.find_first_not_of(" \f\n\r\t\v")); entry.erase(entry.find_last_not_of(" \f\n\r\t\v")+1); + // Special case, entire v was just whitespace + if (entry.empty() && (entries.size() == 1)) + break; + if (!decodeEntry(entry.c_str(), &e)) { vlog.error("List parameter %s: Invalid value '%s'", getName(), entry.c_str()); diff --git a/common/core/Configuration.h b/common/core/Configuration.h index 57bc48e1..431dd0c5 100644 --- a/common/core/Configuration.h +++ b/common/core/Configuration.h @@ -86,6 +86,8 @@ namespace core { std::list<VoidParameter*>::iterator begin() { return params.begin(); } std::list<VoidParameter*>::iterator end() { return params.end(); } + // - Returns the number of parameters + int size() { return params.size(); } // - Get the Global Configuration object // NB: This call does NOT lock the Configuration system. diff --git a/common/core/Mutex.cxx b/common/core/Mutex.cxx deleted file mode 100644 index 6f72581e..00000000 --- a/common/core/Mutex.cxx +++ /dev/null @@ -1,157 +0,0 @@ -/* Copyright 2015 Pierre Ossman for Cendio AB - * - * This 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 software 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 software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#ifdef WIN32 -#include <windows.h> -#else -#include <pthread.h> -#endif - -#include <core/Exception.h> -#include <core/Mutex.h> - -using namespace core; - -Mutex::Mutex() -{ -#ifdef WIN32 - systemMutex = new CRITICAL_SECTION; - InitializeCriticalSection((CRITICAL_SECTION*)systemMutex); -#else - int ret; - - systemMutex = new pthread_mutex_t; - ret = pthread_mutex_init((pthread_mutex_t*)systemMutex, nullptr); - if (ret != 0) - throw posix_error("Failed to create mutex", ret); -#endif -} - -Mutex::~Mutex() -{ -#ifdef WIN32 - DeleteCriticalSection((CRITICAL_SECTION*)systemMutex); - delete (CRITICAL_SECTION*)systemMutex; -#else - pthread_mutex_destroy((pthread_mutex_t*)systemMutex); - delete (pthread_mutex_t*)systemMutex; -#endif -} - -void Mutex::lock() -{ -#ifdef WIN32 - EnterCriticalSection((CRITICAL_SECTION*)systemMutex); -#else - int ret; - - ret = pthread_mutex_lock((pthread_mutex_t*)systemMutex); - if (ret != 0) - throw posix_error("Failed to lock mutex", ret); -#endif -} - -void Mutex::unlock() -{ -#ifdef WIN32 - LeaveCriticalSection((CRITICAL_SECTION*)systemMutex); -#else - int ret; - - ret = pthread_mutex_unlock((pthread_mutex_t*)systemMutex); - if (ret != 0) - throw posix_error("Failed to unlock mutex", ret); -#endif -} - -Condition::Condition(Mutex* mutex_) -{ - this->mutex = mutex_; - -#ifdef WIN32 - systemCondition = new CONDITION_VARIABLE; - InitializeConditionVariable((CONDITION_VARIABLE*)systemCondition); -#else - int ret; - - systemCondition = new pthread_cond_t; - ret = pthread_cond_init((pthread_cond_t*)systemCondition, nullptr); - if (ret != 0) - throw posix_error("Failed to create condition variable", ret); -#endif -} - -Condition::~Condition() -{ -#ifdef WIN32 - delete (CONDITION_VARIABLE*)systemCondition; -#else - pthread_cond_destroy((pthread_cond_t*)systemCondition); - delete (pthread_cond_t*)systemCondition; -#endif -} - -void Condition::wait() -{ -#ifdef WIN32 - BOOL ret; - - ret = SleepConditionVariableCS((CONDITION_VARIABLE*)systemCondition, - (CRITICAL_SECTION*)mutex->systemMutex, - INFINITE); - if (!ret) - throw win32_error("Failed to wait on condition variable", GetLastError()); -#else - int ret; - - ret = pthread_cond_wait((pthread_cond_t*)systemCondition, - (pthread_mutex_t*)mutex->systemMutex); - if (ret != 0) - throw posix_error("Failed to wait on condition variable", ret); -#endif -} - -void Condition::signal() -{ -#ifdef WIN32 - WakeConditionVariable((CONDITION_VARIABLE*)systemCondition); -#else - int ret; - - ret = pthread_cond_signal((pthread_cond_t*)systemCondition); - if (ret != 0) - throw posix_error("Failed to signal condition variable", ret); -#endif -} - -void Condition::broadcast() -{ -#ifdef WIN32 - WakeAllConditionVariable((CONDITION_VARIABLE*)systemCondition); -#else - int ret; - - ret = pthread_cond_broadcast((pthread_cond_t*)systemCondition); - if (ret != 0) - throw posix_error("Failed to broadcast condition variable", ret); -#endif -} diff --git a/common/core/Mutex.h b/common/core/Mutex.h deleted file mode 100644 index 17a97d1b..00000000 --- a/common/core/Mutex.h +++ /dev/null @@ -1,64 +0,0 @@ -/* Copyright 2015 Pierre Ossman for Cendio AB - * - * This 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 software 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 software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -#ifndef __CORE_MUTEX_H__ -#define __CORE_MUTEX_H__ - -namespace core { - class Condition; - - class Mutex { - public: - Mutex(); - ~Mutex(); - - void lock(); - void unlock(); - - private: - friend class Condition; - - void* systemMutex; - }; - - class AutoMutex { - public: - AutoMutex(Mutex* mutex) { m = mutex; m->lock(); } - ~AutoMutex() { m->unlock(); } - private: - Mutex* m; - }; - - class Condition { - public: - Condition(Mutex* mutex); - ~Condition(); - - void wait(); - - void signal(); - void broadcast(); - - private: - Mutex* mutex; - void* systemCondition; - }; - -} - -#endif diff --git a/common/core/Thread.cxx b/common/core/Thread.cxx deleted file mode 100644 index be518cd4..00000000 --- a/common/core/Thread.cxx +++ /dev/null @@ -1,172 +0,0 @@ -/* Copyright 2015 Pierre Ossman for Cendio AB - * - * This 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 software 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 software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#ifdef WIN32 -#include <windows.h> -#else -#include <pthread.h> -#include <signal.h> -#include <unistd.h> -#endif - -#include <core/Exception.h> -#include <core/Mutex.h> -#include <core/Thread.h> - -using namespace core; - -Thread::Thread() : running(false), threadId(nullptr) -{ - mutex = new Mutex; - -#ifdef WIN32 - threadId = new HANDLE; -#else - threadId = new pthread_t; -#endif -} - -Thread::~Thread() -{ -#ifdef WIN32 - delete (HANDLE*)threadId; -#else - if (isRunning()) - pthread_cancel(*(pthread_t*)threadId); - delete (pthread_t*)threadId; -#endif - - delete mutex; -} - -void Thread::start() -{ - AutoMutex a(mutex); - -#ifdef WIN32 - *(HANDLE*)threadId = CreateThread(nullptr, 0, startRoutine, this, 0, nullptr); - if (*(HANDLE*)threadId == nullptr) - throw win32_error("Failed to create thread", GetLastError()); -#else - int ret; - sigset_t all, old; - - // Creating threads from libraries is a bit evil, so mitigate the - // issue by at least avoiding signals on these threads - sigfillset(&all); - ret = pthread_sigmask(SIG_SETMASK, &all, &old); - if (ret != 0) - throw posix_error("Failed to mask signals", ret); - - ret = pthread_create((pthread_t*)threadId, nullptr, startRoutine, this); - - pthread_sigmask(SIG_SETMASK, &old, nullptr); - - if (ret != 0) - throw posix_error("Failed to create thread", ret); -#endif - - running = true; -} - -void Thread::wait() -{ - if (!isRunning()) - return; - -#ifdef WIN32 - DWORD ret; - - ret = WaitForSingleObject(*(HANDLE*)threadId, INFINITE); - if (ret != WAIT_OBJECT_0) - throw win32_error("Failed to join thread", GetLastError()); -#else - int ret; - - ret = pthread_join(*(pthread_t*)threadId, nullptr); - if (ret != 0) - throw posix_error("Failed to join thread", ret); -#endif -} - -bool Thread::isRunning() -{ - AutoMutex a(mutex); - - return running; -} - -size_t Thread::getSystemCPUCount() -{ -#ifdef WIN32 - SYSTEM_INFO si; - size_t count; - DWORD mask; - - GetSystemInfo(&si); - - count = 0; - for (mask = si.dwActiveProcessorMask;mask != 0;mask >>= 1) { - if (mask & 0x1) - count++; - } - - if (count > si.dwNumberOfProcessors) - count = si.dwNumberOfProcessors; - - return count; -#else - long ret; - - ret = sysconf(_SC_NPROCESSORS_ONLN); - if (ret == -1) - return 0; - - return ret; -#endif -} - -#ifdef WIN32 -long unsigned __stdcall Thread::startRoutine(void* data) -#else -void* Thread::startRoutine(void* data) -#endif -{ - Thread *self; - - self = (Thread*)data; - - try { - self->worker(); - } catch(...) { - } - - self->mutex->lock(); - self->running = false; - self->mutex->unlock(); - -#ifdef WIN32 - return 0; -#else - return nullptr; -#endif -} diff --git a/common/core/Thread.h b/common/core/Thread.h deleted file mode 100644 index 53c5ef18..00000000 --- a/common/core/Thread.h +++ /dev/null @@ -1,58 +0,0 @@ -/* Copyright 2015 Pierre Ossman for Cendio AB - * - * This 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 software 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 software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -#ifndef __CORE_THREAD_H__ -#define __CORE_THREAD_H__ - -#include <stddef.h> - -namespace core { - class Mutex; - - class Thread { - public: - Thread(); - virtual ~Thread(); - - void start(); - void wait(); - - bool isRunning(); - - public: - static size_t getSystemCPUCount(); - - protected: - virtual void worker() = 0; - - private: -#ifdef WIN32 - static long unsigned __stdcall startRoutine(void* data); -#else - static void* startRoutine(void* data); -#endif - - private: - Mutex *mutex; - bool running; - - void *threadId; - }; -} - -#endif diff --git a/common/core/string.cxx b/common/core/string.cxx index 49501a9f..091836db 100644 --- a/common/core/string.cxx +++ b/common/core/string.cxx @@ -64,6 +64,9 @@ namespace core { std::vector<std::string> out; const char *start, *stop; + if (src[0] == '\0') + return out; + start = src; do { stop = strchr(start, delimiter); diff --git a/common/network/Socket.cxx b/common/network/Socket.cxx index f5b44239..7fc39d1e 100644 --- a/common/network/Socket.cxx +++ b/common/network/Socket.cxx @@ -120,7 +120,7 @@ void Socket::shutdown() } isShutdown_ = true; - ::shutdown(getFd(), SHUT_RDWR); + ::shutdown(getFd(), SHUT_WR); } bool Socket::isShutdown() const diff --git a/common/network/TcpSocket.cxx b/common/network/TcpSocket.cxx index e941aa67..bf3a224c 100644 --- a/common/network/TcpSocket.cxx +++ b/common/network/TcpSocket.cxx @@ -698,7 +698,7 @@ TcpFilter::Pattern TcpFilter::parsePattern(const char* p) { if (parts.size() > 2) throw std::invalid_argument("Invalid filter specified"); - if (parts[0].empty()) { + if (parts.empty() || parts[0].empty()) { // Match any address memset (&pattern.address, 0, sizeof (pattern.address)); pattern.address.u.sa.sa_family = AF_UNSPEC; diff --git a/common/rdr/CMakeLists.txt b/common/rdr/CMakeLists.txt index 55c69132..526b2971 100644 --- a/common/rdr/CMakeLists.txt +++ b/common/rdr/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(rdr STATIC TLSException.cxx TLSInStream.cxx TLSOutStream.cxx + TLSSocket.cxx ZlibInStream.cxx ZlibOutStream.cxx) @@ -27,7 +28,6 @@ endif() if (NETTLE_FOUND) target_include_directories(rdr SYSTEM PUBLIC ${NETTLE_INCLUDE_DIRS}) target_link_libraries(rdr ${NETTLE_LIBRARIES}) - target_link_directories(rdr PUBLIC ${NETTLE_LIBRARY_DIRS}) endif() if(WIN32) target_link_libraries(rdr ws2_32) diff --git a/common/rdr/InStream.h b/common/rdr/InStream.h index 5bec276d..7ad4996f 100644 --- a/common/rdr/InStream.h +++ b/common/rdr/InStream.h @@ -187,9 +187,7 @@ namespace rdr { private: const uint8_t* restorePoint; -#ifdef RFB_INSTREAM_CHECK size_t checkedBytes; -#endif inline void check(size_t bytes) { #ifdef RFB_INSTREAM_CHECK @@ -209,11 +207,7 @@ namespace rdr { protected: - InStream() : restorePoint(nullptr) -#ifdef RFB_INSTREAM_CHECK - ,checkedBytes(0) -#endif - {} + InStream() : restorePoint(nullptr), checkedBytes(0) {} const uint8_t* ptr; const uint8_t* end; }; diff --git a/common/rdr/TLSException.cxx b/common/rdr/TLSException.cxx index a1896af4..8c93a3d3 100644 --- a/common/rdr/TLSException.cxx +++ b/common/rdr/TLSException.cxx @@ -35,11 +35,28 @@ using namespace rdr; #ifdef HAVE_GNUTLS -tls_error::tls_error(const char* s, int err_) noexcept +tls_error::tls_error(const char* s, int err_, int alert_) noexcept : std::runtime_error(core::format("%s: %s (%d)", s, - gnutls_strerror(err_), err_)), - err(err_) + strerror(err_, alert_), err_)), + err(err_), alert(alert_) { } + +const char* tls_error::strerror(int err_, int alert_) const noexcept +{ + const char* msg; + + msg = nullptr; + + if ((alert_ != -1) && + ((err_ == GNUTLS_E_WARNING_ALERT_RECEIVED) || + (err_ == GNUTLS_E_FATAL_ALERT_RECEIVED))) + msg = gnutls_alert_get_name((gnutls_alert_description_t)alert_); + + if (msg == nullptr) + msg = gnutls_strerror(err_); + + return msg; +} #endif /* HAVE_GNUTLS */ diff --git a/common/rdr/TLSException.h b/common/rdr/TLSException.h index b35a675f..75ee94f5 100644 --- a/common/rdr/TLSException.h +++ b/common/rdr/TLSException.h @@ -27,8 +27,10 @@ namespace rdr { class tls_error : public std::runtime_error { public: - int err; - tls_error(const char* s, int err_) noexcept; + int err, alert; + tls_error(const char* s, int err_, int alert_=-1) noexcept; + private: + const char* strerror(int err_, int alert_) const noexcept; }; } diff --git a/common/rdr/TLSInStream.cxx b/common/rdr/TLSInStream.cxx index 223e3ee6..3e5ea2be 100644 --- a/common/rdr/TLSInStream.cxx +++ b/common/rdr/TLSInStream.cxx @@ -1,7 +1,7 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright (C) 2005 Martin Koegler * Copyright (C) 2010 TigerVNC Team - * Copyright (C) 2012-2021 Pierre Ossman for Cendio AB + * Copyright 2012-2025 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,73 +23,25 @@ #include <config.h> #endif -#include <core/Exception.h> -#include <core/LogWriter.h> - -#include <rdr/TLSException.h> #include <rdr/TLSInStream.h> +#include <rdr/TLSSocket.h> -#include <errno.h> +#ifdef HAVE_GNUTLS -#ifdef HAVE_GNUTLS using namespace rdr; -static core::LogWriter vlog("TLSInStream"); - -ssize_t TLSInStream::pull(gnutls_transport_ptr_t str, void* data, size_t size) -{ - TLSInStream* self= (TLSInStream*) str; - InStream *in = self->in; - - self->streamEmpty = false; - self->saved_exception = nullptr; - - try { - if (!in->hasData(1)) { - self->streamEmpty = true; - gnutls_transport_set_errno(self->session, EAGAIN); - return -1; - } - - if (in->avail() < size) - size = in->avail(); - - in->readBytes((uint8_t*)data, size); - } catch (end_of_stream&) { - return 0; - } catch (std::exception& e) { - core::socket_error* se; - vlog.error("Failure reading TLS data: %s", e.what()); - se = dynamic_cast<core::socket_error*>(&e); - if (se) - gnutls_transport_set_errno(self->session, se->err); - else - gnutls_transport_set_errno(self->session, EINVAL); - self->saved_exception = std::current_exception(); - return -1; - } - - return size; -} - -TLSInStream::TLSInStream(InStream* _in, gnutls_session_t _session) - : session(_session), in(_in) +TLSInStream::TLSInStream(TLSSocket* sock_) + : sock(sock_) { - gnutls_transport_ptr_t recv, send; - - gnutls_transport_set_pull_function(session, pull); - gnutls_transport_get_ptr2(session, &recv, &send); - gnutls_transport_set_ptr2(session, this, send); } TLSInStream::~TLSInStream() { - gnutls_transport_set_pull_function(session, nullptr); } bool TLSInStream::fillBuffer() { - size_t n = readTLS((uint8_t*) end, availSpace()); + size_t n = sock->readTLS((uint8_t*) end, availSpace()); if (n == 0) return false; end += n; @@ -97,35 +49,4 @@ bool TLSInStream::fillBuffer() return true; } -size_t TLSInStream::readTLS(uint8_t* buf, size_t len) -{ - int n; - - while (true) { - streamEmpty = false; - n = gnutls_record_recv(session, (void *) buf, len); - if (n == GNUTLS_E_INTERRUPTED || n == GNUTLS_E_AGAIN) { - // GnuTLS returns GNUTLS_E_AGAIN for a bunch of other scenarios - // other than the pull function returning EAGAIN, so we have to - // double check that the underlying stream really is empty - if (!streamEmpty) - continue; - else - return 0; - } - break; - }; - - if (n == GNUTLS_E_PULL_ERROR) - std::rethrow_exception(saved_exception); - - if (n < 0) - throw tls_error("readTLS", n); - - if (n == 0) - throw end_of_stream(); - - return n; -} - #endif diff --git a/common/rdr/TLSInStream.h b/common/rdr/TLSInStream.h index bc9c74ba..94266e50 100644 --- a/common/rdr/TLSInStream.h +++ b/common/rdr/TLSInStream.h @@ -1,5 +1,6 @@ /* Copyright (C) 2005 Martin Koegler * Copyright (C) 2010 TigerVNC Team + * Copyright 2012-2025 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,28 +23,25 @@ #ifdef HAVE_GNUTLS -#include <gnutls/gnutls.h> #include <rdr/BufferedInStream.h> namespace rdr { + class TLSSocket; + class TLSInStream : public BufferedInStream { public: - TLSInStream(InStream* in, gnutls_session_t session); + TLSInStream(TLSSocket* sock); virtual ~TLSInStream(); private: bool fillBuffer() override; - size_t readTLS(uint8_t* buf, size_t len); - static ssize_t pull(gnutls_transport_ptr_t str, void* data, size_t size); - - gnutls_session_t session; - InStream* in; - bool streamEmpty; - std::exception_ptr saved_exception; + TLSSocket* sock; }; -}; + +} #endif + #endif diff --git a/common/rdr/TLSOutStream.cxx b/common/rdr/TLSOutStream.cxx index c3ae2d0a..ba9d182f 100644 --- a/common/rdr/TLSOutStream.cxx +++ b/common/rdr/TLSOutStream.cxx @@ -1,7 +1,7 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright (C) 2005 Martin Koegler * Copyright (C) 2010 TigerVNC Team - * Copyright (C) 2012-2021 Pierre Ossman for Cendio AB + * Copyright 2012-2025 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,103 +23,42 @@ #include <config.h> #endif -#include <core/Exception.h> -#include <core/LogWriter.h> - -#include <rdr/TLSException.h> #include <rdr/TLSOutStream.h> - -#include <errno.h> +#include <rdr/TLSSocket.h> #ifdef HAVE_GNUTLS -using namespace rdr; -static core::LogWriter vlog("TLSOutStream"); +using namespace rdr; -ssize_t TLSOutStream::push(gnutls_transport_ptr_t str, const void* data, - size_t size) +TLSOutStream::TLSOutStream(TLSSocket* sock_) + : sock(sock_) { - TLSOutStream* self= (TLSOutStream*) str; - OutStream *out = self->out; - - self->saved_exception = nullptr; - - try { - out->writeBytes((const uint8_t*)data, size); - out->flush(); - } catch (std::exception& e) { - core::socket_error* se; - vlog.error("Failure sending TLS data: %s", e.what()); - se = dynamic_cast<core::socket_error*>(&e); - if (se) - gnutls_transport_set_errno(self->session, se->err); - else - gnutls_transport_set_errno(self->session, EINVAL); - self->saved_exception = std::current_exception(); - return -1; - } - - return size; -} - -TLSOutStream::TLSOutStream(OutStream* _out, gnutls_session_t _session) - : session(_session), out(_out) -{ - gnutls_transport_ptr_t recv, send; - - gnutls_transport_set_push_function(session, push); - gnutls_transport_get_ptr2(session, &recv, &send); - gnutls_transport_set_ptr2(session, recv, this); } TLSOutStream::~TLSOutStream() { -#if 0 - try { -// flush(); - } catch (Exception&) { - } -#endif - gnutls_transport_set_push_function(session, nullptr); } void TLSOutStream::flush() { BufferedOutStream::flush(); - out->flush(); + sock->out->flush(); } void TLSOutStream::cork(bool enable) { BufferedOutStream::cork(enable); - out->cork(enable); + sock->out->cork(enable); } bool TLSOutStream::flushBuffer() { while (sentUpTo < ptr) { - size_t n = writeTLS(sentUpTo, ptr - sentUpTo); + size_t n = sock->writeTLS(sentUpTo, ptr - sentUpTo); sentUpTo += n; } return true; } -size_t TLSOutStream::writeTLS(const uint8_t* data, size_t length) -{ - int n; - - n = gnutls_record_send(session, data, length); - if (n == GNUTLS_E_INTERRUPTED || n == GNUTLS_E_AGAIN) - return 0; - - if (n == GNUTLS_E_PUSH_ERROR) - std::rethrow_exception(saved_exception); - - if (n < 0) - throw tls_error("writeTLS", n); - - return n; -} - #endif diff --git a/common/rdr/TLSOutStream.h b/common/rdr/TLSOutStream.h index 0ae9c460..aa9572ba 100644 --- a/common/rdr/TLSOutStream.h +++ b/common/rdr/TLSOutStream.h @@ -21,14 +21,16 @@ #define __RDR_TLSOUTSTREAM_H__ #ifdef HAVE_GNUTLS -#include <gnutls/gnutls.h> + #include <rdr/BufferedOutStream.h> namespace rdr { + class TLSSocket; + class TLSOutStream : public BufferedOutStream { public: - TLSOutStream(OutStream* out, gnutls_session_t session); + TLSOutStream(TLSSocket* out); virtual ~TLSOutStream(); void flush() override; @@ -36,15 +38,12 @@ namespace rdr { private: bool flushBuffer() override; - size_t writeTLS(const uint8_t* data, size_t length); - static ssize_t push(gnutls_transport_ptr_t str, const void* data, size_t size); - - gnutls_session_t session; - OutStream* out; - std::exception_ptr saved_exception; + TLSSocket* sock; }; -}; + +} #endif + #endif diff --git a/common/rdr/TLSSocket.cxx b/common/rdr/TLSSocket.cxx new file mode 100644 index 00000000..a29e41c1 --- /dev/null +++ b/common/rdr/TLSSocket.cxx @@ -0,0 +1,228 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2005 Martin Koegler + * Copyright (C) 2010 TigerVNC Team + * Copyright (C) 2012-2025 Pierre Ossman for Cendio AB + * + * This 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 software 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 software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <core/Exception.h> +#include <core/LogWriter.h> + +#include <rdr/InStream.h> +#include <rdr/OutStream.h> +#include <rdr/TLSException.h> +#include <rdr/TLSSocket.h> + +#include <errno.h> + +#ifdef HAVE_GNUTLS + +using namespace rdr; + +static core::LogWriter vlog("TLSSocket"); + +TLSSocket::TLSSocket(InStream* in_, OutStream* out_, + gnutls_session_t session_) + : session(session_), in(in_), out(out_), tlsin(this), tlsout(this) +{ + gnutls_transport_set_pull_function( + session, [](gnutls_transport_ptr_t sock, void* data, size_t size) { + return ((TLSSocket*)sock)->pull(data, size); + }); + gnutls_transport_set_push_function( + session, [](gnutls_transport_ptr_t sock, const void* data, size_t size) { + return ((TLSSocket*)sock)->push(data, size); + }); + gnutls_transport_set_ptr(session, this); +} + +TLSSocket::~TLSSocket() +{ + gnutls_transport_set_pull_function(session, nullptr); + gnutls_transport_set_push_function(session, nullptr); + gnutls_transport_set_ptr(session, nullptr); +} + +bool TLSSocket::handshake() +{ + int err; + + err = gnutls_handshake(session); + if (err != GNUTLS_E_SUCCESS) { + gnutls_alert_description_t alert; + const char* msg; + + if ((err == GNUTLS_E_PULL_ERROR) || (err == GNUTLS_E_PUSH_ERROR)) + std::rethrow_exception(saved_exception); + + alert = gnutls_alert_get(session); + msg = nullptr; + + if ((err == GNUTLS_E_WARNING_ALERT_RECEIVED) || + (err == GNUTLS_E_FATAL_ALERT_RECEIVED)) + msg = gnutls_alert_get_name(alert); + + if (msg == nullptr) + msg = gnutls_strerror(err); + + if (!gnutls_error_is_fatal(err)) { + vlog.debug("Deferring completion of TLS handshake: %s", msg); + return false; + } + + vlog.error("TLS Handshake failed: %s\n", msg); + gnutls_alert_send_appropriate(session, err); + throw rdr::tls_error("TLS Handshake failed", err, alert); + } + + return true; +} + +void TLSSocket::shutdown() +{ + int ret; + + try { + if (tlsout.hasBufferedData()) { + tlsout.cork(false); + tlsout.flush(); + if (tlsout.hasBufferedData()) + vlog.error("Failed to flush remaining socket data on close"); + } + } catch (std::exception& e) { + vlog.error("Failed to flush remaining socket data on close: %s", e.what()); + } + + // FIXME: We can't currently wait for the response, so we only send + // our close and hope for the best + ret = gnutls_bye(session, GNUTLS_SHUT_WR); + if ((ret != GNUTLS_E_SUCCESS) && (ret != GNUTLS_E_INVALID_SESSION)) + vlog.error("TLS shutdown failed: %s", gnutls_strerror(ret)); +} + +size_t TLSSocket::readTLS(uint8_t* buf, size_t len) +{ + int n; + + while (true) { + streamEmpty = false; + n = gnutls_record_recv(session, (void *) buf, len); + if (n == GNUTLS_E_INTERRUPTED || n == GNUTLS_E_AGAIN) { + // GnuTLS returns GNUTLS_E_AGAIN for a bunch of other scenarios + // other than the pull function returning EAGAIN, so we have to + // double check that the underlying stream really is empty + if (!streamEmpty) + continue; + else + return 0; + } + break; + }; + + if (n == GNUTLS_E_PULL_ERROR) + std::rethrow_exception(saved_exception); + + if (n < 0) { + gnutls_alert_send_appropriate(session, n); + throw tls_error("readTLS", n, gnutls_alert_get(session)); + } + + if (n == 0) + throw end_of_stream(); + + return n; +} + +size_t TLSSocket::writeTLS(const uint8_t* data, size_t length) +{ + int n; + + n = gnutls_record_send(session, data, length); + if (n == GNUTLS_E_INTERRUPTED || n == GNUTLS_E_AGAIN) + return 0; + + if (n == GNUTLS_E_PUSH_ERROR) + std::rethrow_exception(saved_exception); + + if (n < 0) { + gnutls_alert_send_appropriate(session, n); + throw tls_error("writeTLS", n, gnutls_alert_get(session)); + } + + return n; +} + +ssize_t TLSSocket::pull(void* data, size_t size) +{ + streamEmpty = false; + saved_exception = nullptr; + + try { + if (!in->hasData(1)) { + streamEmpty = true; + gnutls_transport_set_errno(session, EAGAIN); + return -1; + } + + if (in->avail() < size) + size = in->avail(); + + in->readBytes((uint8_t*)data, size); + } catch (end_of_stream&) { + return 0; + } catch (std::exception& e) { + core::socket_error* se; + vlog.error("Failure reading TLS data: %s", e.what()); + se = dynamic_cast<core::socket_error*>(&e); + if (se) + gnutls_transport_set_errno(session, se->err); + else + gnutls_transport_set_errno(session, EINVAL); + saved_exception = std::current_exception(); + return -1; + } + + return size; +} + +ssize_t TLSSocket::push(const void* data, size_t size) +{ + saved_exception = nullptr; + + try { + out->writeBytes((const uint8_t*)data, size); + out->flush(); + } catch (std::exception& e) { + core::socket_error* se; + vlog.error("Failure sending TLS data: %s", e.what()); + se = dynamic_cast<core::socket_error*>(&e); + if (se) + gnutls_transport_set_errno(session, se->err); + else + gnutls_transport_set_errno(session, EINVAL); + saved_exception = std::current_exception(); + return -1; + } + + return size; +} + +#endif diff --git a/common/rdr/TLSSocket.h b/common/rdr/TLSSocket.h new file mode 100644 index 00000000..ca29f8bc --- /dev/null +++ b/common/rdr/TLSSocket.h @@ -0,0 +1,81 @@ +/* Copyright (C) 2005 Martin Koegler + * Copyright (C) 2010 TigerVNC Team + * Copyright 2012-2025 Pierre Ossman for Cendio AB + * + * This 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 software 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 software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef __RDR_TLSSOCKET_H__ +#define __RDR_TLSSOCKET_H__ + +#ifdef HAVE_GNUTLS + +#include <exception> + +#include <gnutls/gnutls.h> + +#include <rdr/TLSInStream.h> +#include <rdr/TLSOutStream.h> + +namespace rdr { + + class InStream; + class OutStream; + + class TLSInStream; + class TLSOutStream; + + class TLSSocket { + public: + TLSSocket(InStream* in, OutStream* out, gnutls_session_t session); + virtual ~TLSSocket(); + + TLSInStream& inStream() { return tlsin; } + TLSOutStream& outStream() { return tlsout; } + + bool handshake(); + void shutdown(); + + protected: + /* Used by the stream classes */ + size_t readTLS(uint8_t* buf, size_t len); + size_t writeTLS(const uint8_t* data, size_t length); + + friend TLSInStream; + friend TLSOutStream; + + private: + ssize_t pull(void* data, size_t size); + ssize_t push(const void* data, size_t size); + + gnutls_session_t session; + + InStream* in; + OutStream* out; + + TLSInStream tlsin; + TLSOutStream tlsout; + + bool streamEmpty; + + std::exception_ptr saved_exception; + }; + +} + +#endif + +#endif diff --git a/common/rfb/CConnection.cxx b/common/rfb/CConnection.cxx index cbb62fe3..bbeef385 100644 --- a/common/rfb/CConnection.cxx +++ b/common/rfb/CConnection.cxx @@ -37,6 +37,7 @@ #include <rfb/CMsgReader.h> #include <rfb/CMsgWriter.h> #include <rfb/CSecurity.h> +#include <rfb/Cursor.h> #include <rfb/Decoder.h> #include <rfb/KeysymStr.h> #include <rfb/PixelBuffer.h> @@ -379,7 +380,6 @@ void CConnection::securityCompleted() reader_ = new CMsgReader(this, is); writer_ = new CMsgWriter(&server, os); vlog.debug("Authentication success!"); - authSuccess(); writer_->writeClientInit(shared); } @@ -410,7 +410,7 @@ void CConnection::setDesktopSize(int w, int h) { decoder.flush(); - CMsgHandler::setDesktopSize(w,h); + server.setDimensions(w, h); if (continuousUpdates) writer()->writeEnableContinuousUpdates(true, 0, 0, @@ -430,7 +430,10 @@ void CConnection::setExtendedDesktopSize(unsigned reason, { decoder.flush(); - CMsgHandler::setExtendedDesktopSize(reason, result, w, h, layout); + server.supportsSetDesktopSize = true; + + if ((reason != reasonClient) || (result == resultSuccess)) + server.setDimensions(w, h, layout); if ((reason == reasonClient) && (result != resultSuccess)) { vlog.error("SetDesktopSize failed: %d", result); @@ -448,9 +451,41 @@ void CConnection::setExtendedDesktopSize(unsigned reason, assert(framebuffer->height() == server.height()); } +void CConnection::setCursor(int width, int height, + const core::Point& hotspot, + const uint8_t* data) +{ + Cursor cursor(width, height, hotspot, data); + server.setCursor(cursor); +} + +void CConnection::setCursorPos(const core::Point& /*pos*/) +{ +} + +void CConnection::setName(const char* name) +{ + server.setName(name); +} + +void CConnection::fence(uint32_t flags, unsigned len, const uint8_t data[]) +{ + server.supportsFence = true; + + if (flags & fenceFlagRequest) { + // FIXME: We handle everything synchronously, and we assume anything + // using us also does so, which means we automatically handle + // these flags + flags = flags & (fenceFlagBlockBefore | fenceFlagBlockAfter); + + writer()->writeFence(flags, len, data); + return; + } +} + void CConnection::endOfContinuousUpdates() { - CMsgHandler::endOfContinuousUpdates(); + server.supportsContinuousUpdates = true; // We've gotten the marker for a format change, so make the pending // one active @@ -464,11 +499,23 @@ void CConnection::endOfContinuousUpdates() } } +void CConnection::supportsQEMUKeyEvent() +{ + server.supportsQEMUKeyEvent = true; +} + +void CConnection::supportsExtendedMouseButtons() +{ + server.supportsExtendedMouseButtons = true; +} + void CConnection::serverInit(int width, int height, const PixelFormat& pf, const char* name) { - CMsgHandler::serverInit(width, height, pf, name); + server.setDimensions(width, height); + server.setPF(pf); + server.setName(name); state_ = RFBSTATE_NORMAL; vlog.debug("Initialisation done"); @@ -502,8 +549,6 @@ bool CConnection::readAndDecodeRect(const core::Rect& r, int encoding, void CConnection::framebufferUpdateStart() { - CMsgHandler::framebufferUpdateStart(); - assert(framebuffer != nullptr); // Note: This might not be true if continuous updates are supported @@ -516,8 +561,6 @@ void CConnection::framebufferUpdateEnd() { decoder.flush(); - CMsgHandler::framebufferUpdateEnd(); - // A format change has been scheduled and we are now past the update // with the old format. Time to active the new one. if (pendingPFChange && !continuousUpdates) { @@ -543,6 +586,13 @@ bool CConnection::dataRect(const core::Rect& r, int encoding) return decoder.decodeRect(r, encoding, framebuffer); } +void CConnection::setColourMapEntries(int /*firstColour*/, + int /*nColours*/, + uint16_t* /*rgbs*/) +{ + vlog.error("Invalid SetColourMapEntries from server!"); +} + void CConnection::serverCutText(const char* str) { hasLocalClipboard = false; @@ -553,12 +603,53 @@ void CConnection::serverCutText(const char* str) handleClipboardAnnounce(true); } +void CConnection::setLEDState(unsigned int state) +{ + server.setLEDState(state); +} + void CConnection::handleClipboardCaps(uint32_t flags, const uint32_t* lengths) { + int i; uint32_t sizes[] = { 0 }; - CMsgHandler::handleClipboardCaps(flags, lengths); + vlog.debug("Got server clipboard capabilities:"); + for (i = 0;i < 16;i++) { + if (flags & (1 << i)) { + const char *type; + + switch (1 << i) { + case clipboardUTF8: + type = "Plain text"; + break; + case clipboardRTF: + type = "Rich text"; + break; + case clipboardHTML: + type = "HTML"; + break; + case clipboardDIB: + type = "Images"; + break; + case clipboardFiles: + type = "Files"; + break; + default: + vlog.debug(" Unknown format 0x%x", 1 << i); + continue; + } + + if (lengths[i] == 0) + vlog.debug(" %s (only notify)", type); + else { + vlog.debug(" %s (automatically send up to %s)", + type, core::iecPrefix(lengths[i], "B").c_str()); + } + } + } + + server.setClipboardCaps(flags, lengths); writer()->writeClipboardCaps(rfb::clipboardUTF8 | rfb::clipboardRequest | @@ -620,10 +711,6 @@ void CConnection::handleClipboardProvide(uint32_t flags, handleClipboardData(serverClipboard.c_str()); } -void CConnection::authSuccess() -{ -} - void CConnection::initDone() { } @@ -820,6 +907,11 @@ void CConnection::setCompressLevel(int level) encodingChange = true; } +int CConnection::getCompressLevel() +{ + return compressLevel; +} + void CConnection::setQualityLevel(int level) { if (qualityLevel == level) @@ -829,6 +921,11 @@ void CConnection::setQualityLevel(int level) encodingChange = true; } +int CConnection::getQualityLevel() +{ + return qualityLevel; +} + void CConnection::setPF(const PixelFormat& pf) { if (server.pf() == pf && !formatChange) @@ -843,19 +940,6 @@ bool CConnection::isSecure() const return csecurity ? csecurity->isSecure() : false; } -void CConnection::fence(uint32_t flags, unsigned len, const uint8_t data[]) -{ - CMsgHandler::fence(flags, len, data); - - if (!(flags & fenceFlagRequest)) - return; - - // We cannot guarantee any synchronisation at this level - flags = 0; - - writer()->writeFence(flags, len, data); -} - // requestNewUpdate() requests an update from the server, having set the // format and encoding appropriately. void CConnection::requestNewUpdate() diff --git a/common/rfb/CConnection.h b/common/rfb/CConnection.h index 9ffbd2c5..b28c5aa9 100644 --- a/common/rfb/CConnection.h +++ b/common/rfb/CConnection.h @@ -105,89 +105,6 @@ namespace rfb { // connection void close(); - - // Methods overridden from CMsgHandler - - // Note: These must be called by any deriving classes - - void setDesktopSize(int w, int h) override; - void setExtendedDesktopSize(unsigned reason, unsigned result, - int w, int h, - const ScreenSet& layout) override; - - void endOfContinuousUpdates() override; - - void serverInit(int width, int height, const PixelFormat& pf, - const char* name) override; - - bool readAndDecodeRect(const core::Rect& r, int encoding, - ModifiablePixelBuffer* pb) override; - - void framebufferUpdateStart() override; - void framebufferUpdateEnd() override; - bool dataRect(const core::Rect& r, int encoding) override; - - void serverCutText(const char* str) override; - - void handleClipboardCaps(uint32_t flags, - const uint32_t* lengths) override; - void handleClipboardRequest(uint32_t flags) override; - void handleClipboardPeek() override; - void handleClipboardNotify(uint32_t flags) override; - void handleClipboardProvide(uint32_t flags, const size_t* lengths, - const uint8_t* const* data) override; - - - // Methods to be overridden in a derived class - - // getUserPasswd() gets the username and password. This might - // involve a dialog, getpass(), etc. The user buffer pointer can be - // null, in which case no user name will be retrieved. - virtual void getUserPasswd(bool secure, std::string* user, - std::string* password) = 0; - - // showMsgBox() displays a message box with the specified style and - // contents. The return value is true if the user clicked OK/Yes. - virtual bool showMsgBox(MsgBoxFlags flags, const char *title, - const char *text) = 0; - - // authSuccess() is called when authentication has succeeded. - virtual void authSuccess(); - - // initDone() is called when the connection is fully established - // and standard messages can be sent. This is called before the - // initial FramebufferUpdateRequest giving a derived class the - // chance to modify pixel format and settings. The derived class - // must also make sure it has provided a valid framebuffer before - // returning. - virtual void initDone() = 0; - - // resizeFramebuffer() is called whenever the framebuffer - // dimensions or the screen layout changes. A subclass must make - // sure the pixel buffer has been updated once this call returns. - virtual void resizeFramebuffer(); - - // handleClipboardRequest() is called whenever the server requests - // the client to send over its clipboard data. It will only be - // called after the client has first announced a clipboard change - // via announceClipboard(). - virtual void handleClipboardRequest(); - - // handleClipboardAnnounce() is called to indicate a change in the - // clipboard on the server. Call requestClipboard() to access the - // actual data. - virtual void handleClipboardAnnounce(bool available); - - // handleClipboardData() is called when the server has sent over - // the clipboard data as a result of a previous call to - // requestClipboard(). Note that this function might never be - // called if the clipboard data was no longer available when the - // server received the request. - virtual void handleClipboardData(const char* data); - - - // Other methods - // requestClipboard() will result in a request to the server to // transfer its clipboard data. A call to handleClipboardData() // will be made once the data is available. @@ -227,7 +144,9 @@ namespace rfb { // setCompressLevel()/setQualityLevel() controls the encoding hints // sent to the server void setCompressLevel(int level); + int getCompressLevel(); void setQualityLevel(int level); + int getQualityLevel(); // setPF() controls the pixel format requested from the server. // server.pf() will automatically be adjusted once the new format // is active. @@ -260,8 +179,107 @@ namespace rfb { stateEnum state() { return state_; } + // Methods used by SSecurity classes + + // getUserPasswd() gets the username and password. This might + // involve a dialog, getpass(), etc. The user buffer pointer can be + // null, in which case no user name will be retrieved. + virtual void getUserPasswd(bool secure, std::string* user, + std::string* password) = 0; + + // showMsgBox() displays a message box with the specified style and + // contents. The return value is true if the user clicked OK/Yes. + virtual bool showMsgBox(MsgBoxFlags flags, const char *title, + const char *text) = 0; + + protected: + + // Methods overridden from CMsgHandler + + // Note: These must be called by any deriving classes + + void setDesktopSize(int w, int h) override; + void setExtendedDesktopSize(unsigned reason, unsigned result, + int w, int h, + const ScreenSet& layout) override; + + void setCursor(int width, int height, const core::Point& hotspot, + const uint8_t* data) override; + void setCursorPos(const core::Point& pos) override; + + void setName(const char* name) override; + + void fence(uint32_t flags, unsigned len, const uint8_t data[]) override; + + void endOfContinuousUpdates() override; + + void supportsQEMUKeyEvent() override; + + void supportsExtendedMouseButtons() override; + + void serverInit(int width, int height, const PixelFormat& pf, + const char* name) override; + + bool readAndDecodeRect(const core::Rect& r, int encoding, + ModifiablePixelBuffer* pb) override; + + void framebufferUpdateStart() override; + void framebufferUpdateEnd() override; + bool dataRect(const core::Rect& r, int encoding) override; + + void setColourMapEntries(int firstColour, int nColours, + uint16_t* rgbs) override; + + void serverCutText(const char* str) override; + + void setLEDState(unsigned int state) override; + + void handleClipboardCaps(uint32_t flags, + const uint32_t* lengths) override; + void handleClipboardRequest(uint32_t flags) override; + void handleClipboardPeek() override; + void handleClipboardNotify(uint32_t flags) override; + void handleClipboardProvide(uint32_t flags, const size_t* lengths, + const uint8_t* const* data) override; + + + // Methods to be overridden in a derived class + + // initDone() is called when the connection is fully established + // and standard messages can be sent. This is called before the + // initial FramebufferUpdateRequest giving a derived class the + // chance to modify pixel format and settings. The derived class + // must also make sure it has provided a valid framebuffer before + // returning. + virtual void initDone() = 0; + + // resizeFramebuffer() is called whenever the framebuffer + // dimensions or the screen layout changes. A subclass must make + // sure the pixel buffer has been updated once this call returns. + virtual void resizeFramebuffer(); + + // handleClipboardRequest() is called whenever the server requests + // the client to send over its clipboard data. It will only be + // called after the client has first announced a clipboard change + // via announceClipboard(). + virtual void handleClipboardRequest(); + + // handleClipboardAnnounce() is called to indicate a change in the + // clipboard on the server. Call requestClipboard() to access the + // actual data. + virtual void handleClipboardAnnounce(bool available); + + // handleClipboardData() is called when the server has sent over + // the clipboard data as a result of a previous call to + // requestClipboard(). Note that this function might never be + // called if the clipboard data was no longer available when the + // server received the request. + virtual void handleClipboardData(const char* data); + + protected: CSecurity *csecurity; SecurityClient security; + protected: void setState(stateEnum s) { state_ = s; } @@ -279,13 +297,6 @@ namespace rfb { bool supportsLEDState; private: - // This is a default implementation of fences that automatically - // responds to requests, stating no support for synchronisation. - // When overriding, call CMsgHandler::fence() directly in order to - // state correct support for fence flags. - void fence(uint32_t flags, unsigned len, const uint8_t data[]) override; - - private: bool processVersionMsg(); bool processSecurityTypesMsg(); bool processSecurityMsg(); diff --git a/common/rfb/CMakeLists.txt b/common/rfb/CMakeLists.txt index 2b5eea68..d7467421 100644 --- a/common/rfb/CMakeLists.txt +++ b/common/rfb/CMakeLists.txt @@ -3,7 +3,6 @@ add_library(rfb STATIC Blacklist.cxx Congestion.cxx CConnection.cxx - CMsgHandler.cxx CMsgReader.cxx CMsgWriter.cxx CSecurityPlain.cxx @@ -32,7 +31,6 @@ add_library(rfb STATIC RawDecoder.cxx RawEncoder.cxx SConnection.cxx - SMsgHandler.cxx SMsgReader.cxx SMsgWriter.cxx ServerCore.cxx @@ -69,7 +67,6 @@ if(ENABLE_H264 AND NOT H264_LIBS STREQUAL "NONE") endif() target_include_directories(rfb SYSTEM PUBLIC ${H264_INCLUDE_DIRS}) target_link_libraries(rfb ${H264_LIBRARIES}) - target_link_directories(rfb PUBLIC ${H264_LIBRARY_DIRS}) endif() if(WIN32) @@ -78,8 +75,9 @@ if(WIN32) endif(WIN32) if(UNIX AND NOT APPLE) - target_sources(rfb PRIVATE UnixPasswordValidator.cxx pam.c) - target_link_libraries(rfb ${PAM_LIBS}) + target_sources(rfb PRIVATE UnixPasswordValidator.cxx) + target_include_directories(rfb SYSTEM PRIVATE ${PAM_INCLUDE_DIRS}) + target_link_libraries(rfb ${PAM_LIBRARIES}) endif() if(GNUTLS_FOUND) @@ -95,12 +93,8 @@ endif() if (NETTLE_FOUND) target_sources(rfb PRIVATE CSecurityDH.cxx CSecurityMSLogonII.cxx CSecurityRSAAES.cxx SSecurityRSAAES.cxx) - target_include_directories(rfb SYSTEM PUBLIC ${NETTLE_INCLUDE_DIRS} - ${GMP_INCLUDE_DIRS}) - target_link_libraries(rfb ${HOGWEED_LIBRARIES} - ${NETTLE_LIBRARIES} ${GMP_LIBRARIES}) - target_link_directories(rfb PUBLIC ${HOGWEED_LIBRARY_DIRS} - ${NETTLE_LIBRARY_DIRS} ${GMP_LIBRARY_DIRS}) + target_include_directories(rfb SYSTEM PUBLIC ${NETTLE_INCLUDE_DIRS}) + target_link_libraries(rfb ${HOGWEED_LIBRARIES} ${NETTLE_LIBRARIES}) endif() if(UNIX) diff --git a/common/rfb/CMsgHandler.cxx b/common/rfb/CMsgHandler.cxx deleted file mode 100644 index 2a88d867..00000000 --- a/common/rfb/CMsgHandler.cxx +++ /dev/null @@ -1,168 +0,0 @@ -/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2019 Pierre Ossman for Cendio AB - * - * This 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 software 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 software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <stdio.h> - -#include <core/LogWriter.h> -#include <core/string.h> - -#include <rfb/CMsgHandler.h> -#include <rfb/clipboardTypes.h> -#include <rfb/screenTypes.h> - -static core::LogWriter vlog("CMsgHandler"); - -using namespace rfb; - -CMsgHandler::CMsgHandler() -{ -} - -CMsgHandler::~CMsgHandler() -{ -} - -void CMsgHandler::setDesktopSize(int width, int height) -{ - server.setDimensions(width, height); -} - -void CMsgHandler::setExtendedDesktopSize(unsigned reason, unsigned result, - int width, int height, - const ScreenSet& layout) -{ - server.supportsSetDesktopSize = true; - - if ((reason == reasonClient) && (result != resultSuccess)) - return; - - server.setDimensions(width, height, layout); -} - -void CMsgHandler::setName(const char* name) -{ - server.setName(name); -} - -void CMsgHandler::fence(uint32_t /*flags*/, unsigned /*len*/, - const uint8_t /*data*/ []) -{ - server.supportsFence = true; -} - -void CMsgHandler::endOfContinuousUpdates() -{ - server.supportsContinuousUpdates = true; -} - -void CMsgHandler::supportsExtendedMouseButtons() -{ - server.supportsExtendedMouseButtons = true; -} - -void CMsgHandler::supportsQEMUKeyEvent() -{ - server.supportsQEMUKeyEvent = true; -} - -void CMsgHandler::serverInit(int width, int height, - const PixelFormat& pf, - const char* name) -{ - server.setDimensions(width, height); - server.setPF(pf); - server.setName(name); -} - -void CMsgHandler::framebufferUpdateStart() -{ -} - -void CMsgHandler::framebufferUpdateEnd() -{ -} - -void CMsgHandler::setLEDState(unsigned int state) -{ - server.setLEDState(state); -} - -void CMsgHandler::handleClipboardCaps(uint32_t flags, const uint32_t* lengths) -{ - int i; - - vlog.debug("Got server clipboard capabilities:"); - for (i = 0;i < 16;i++) { - if (flags & (1 << i)) { - const char *type; - - switch (1 << i) { - case clipboardUTF8: - type = "Plain text"; - break; - case clipboardRTF: - type = "Rich text"; - break; - case clipboardHTML: - type = "HTML"; - break; - case clipboardDIB: - type = "Images"; - break; - case clipboardFiles: - type = "Files"; - break; - default: - vlog.debug(" Unknown format 0x%x", 1 << i); - continue; - } - - if (lengths[i] == 0) - vlog.debug(" %s (only notify)", type); - else { - vlog.debug(" %s (automatically send up to %s)", - type, core::iecPrefix(lengths[i], "B").c_str()); - } - } - } - - server.setClipboardCaps(flags, lengths); -} - -void CMsgHandler::handleClipboardRequest(uint32_t /*flags*/) -{ -} - -void CMsgHandler::handleClipboardPeek() -{ -} - -void CMsgHandler::handleClipboardNotify(uint32_t /*flags*/) -{ -} - -void CMsgHandler::handleClipboardProvide(uint32_t /*flags*/, - const size_t* /*lengths*/, - const uint8_t* const* /*data*/) -{ -} diff --git a/common/rfb/CMsgHandler.h b/common/rfb/CMsgHandler.h index e7ba2abc..d267ae47 100644 --- a/common/rfb/CMsgHandler.h +++ b/common/rfb/CMsgHandler.h @@ -33,8 +33,6 @@ namespace core { struct Rect; } -namespace rdr { class InStream; } - namespace rfb { class ModifiablePixelBuffer; @@ -42,29 +40,23 @@ namespace rfb { class CMsgHandler { public: - CMsgHandler(); - virtual ~CMsgHandler(); - // The following methods are called as corresponding messages are - // read. A derived class should override these methods as desired. - // Note that for the setDesktopSize(), setExtendedDesktopSize(), - // setName(), serverInit() and handleClipboardCaps() methods, a - // derived class should call on to CMsgHandler's methods to set the - // members of "server" appropriately. + // read. A derived class must override these methods. - virtual void setDesktopSize(int w, int h); + virtual void setDesktopSize(int w, int h) = 0; virtual void setExtendedDesktopSize(unsigned reason, unsigned result, int w, int h, - const ScreenSet& layout); + const ScreenSet& layout) = 0; virtual void setCursor(int width, int height, const core::Point& hotspot, const uint8_t* data) = 0; virtual void setCursorPos(const core::Point& pos) = 0; - virtual void setName(const char* name); - virtual void fence(uint32_t flags, unsigned len, const uint8_t data[]); - virtual void endOfContinuousUpdates(); - virtual void supportsQEMUKeyEvent(); - virtual void supportsExtendedMouseButtons(); + virtual void setName(const char* name) = 0; + virtual void fence(uint32_t flags, unsigned len, + const uint8_t data[]) = 0; + virtual void endOfContinuousUpdates() = 0; + virtual void supportsQEMUKeyEvent() = 0; + virtual void supportsExtendedMouseButtons() = 0; virtual void serverInit(int width, int height, const PixelFormat& pf, const char* name) = 0; @@ -72,8 +64,8 @@ namespace rfb { virtual bool readAndDecodeRect(const core::Rect& r, int encoding, ModifiablePixelBuffer* pb) = 0; - virtual void framebufferUpdateStart(); - virtual void framebufferUpdateEnd(); + virtual void framebufferUpdateStart() = 0; + virtual void framebufferUpdateEnd() = 0; virtual bool dataRect(const core::Rect& r, int encoding) = 0; virtual void setColourMapEntries(int firstColour, int nColours, @@ -81,16 +73,16 @@ namespace rfb { virtual void bell() = 0; virtual void serverCutText(const char* str) = 0; - virtual void setLEDState(unsigned int state); + virtual void setLEDState(unsigned int state) = 0; virtual void handleClipboardCaps(uint32_t flags, - const uint32_t* lengths); - virtual void handleClipboardRequest(uint32_t flags); - virtual void handleClipboardPeek(); - virtual void handleClipboardNotify(uint32_t flags); + const uint32_t* lengths) = 0; + virtual void handleClipboardRequest(uint32_t flags) = 0; + virtual void handleClipboardPeek() = 0; + virtual void handleClipboardNotify(uint32_t flags) = 0; virtual void handleClipboardProvide(uint32_t flags, const size_t* lengths, - const uint8_t* const* data); + const uint8_t* const* data) = 0; ServerParams server; }; diff --git a/common/rfb/CSecurityTLS.cxx b/common/rfb/CSecurityTLS.cxx index 44f738b4..6eefe73b 100644 --- a/common/rfb/CSecurityTLS.cxx +++ b/common/rfb/CSecurityTLS.cxx @@ -3,7 +3,7 @@ * Copyright (C) 2005 Martin Koegler * Copyright (C) 2010 TigerVNC Team * Copyright (C) 2010 m-privacy GmbH - * Copyright (C) 2012-2021 Pierre Ossman for Cendio AB + * Copyright 2012-2025 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -43,8 +43,7 @@ #include <rfb/Exception.h> #include <rdr/TLSException.h> -#include <rdr/TLSInStream.h> -#include <rdr/TLSOutStream.h> +#include <rdr/TLSSocket.h> #include <gnutls/x509.h> @@ -75,7 +74,7 @@ static const char* configdirfn(const char* fn) CSecurityTLS::CSecurityTLS(CConnection* cc_, bool _anon) : CSecurity(cc_), session(nullptr), anon_cred(nullptr), cert_cred(nullptr), - anon(_anon), tlsis(nullptr), tlsos(nullptr), + anon(_anon), tlssock(nullptr), rawis(nullptr), rawos(nullptr) { int err = gnutls_global_init(); @@ -85,27 +84,8 @@ CSecurityTLS::CSecurityTLS(CConnection* cc_, bool _anon) void CSecurityTLS::shutdown() { - if (tlsos) { - try { - if (tlsos->hasBufferedData()) { - tlsos->cork(false); - tlsos->flush(); - if (tlsos->hasBufferedData()) - vlog.error("Failed to flush remaining socket data on close"); - } - } catch (std::exception& e) { - vlog.error("Failed to flush remaining socket data on close: %s", e.what()); - } - } - - if (session) { - int ret; - // FIXME: We can't currently wait for the response, so we only send - // our close and hope for the best - ret = gnutls_bye(session, GNUTLS_SHUT_WR); - if ((ret != GNUTLS_E_SUCCESS) && (ret != GNUTLS_E_INVALID_SESSION)) - vlog.error("TLS shutdown failed: %s", gnutls_strerror(ret)); - } + if (tlssock) + tlssock->shutdown(); if (anon_cred) { gnutls_anon_free_client_credentials(anon_cred); @@ -123,13 +103,9 @@ void CSecurityTLS::shutdown() rawos = nullptr; } - if (tlsis) { - delete tlsis; - tlsis = nullptr; - } - if (tlsos) { - delete tlsos; - tlsos = nullptr; + if (tlssock) { + delete tlssock; + tlssock = nullptr; } if (session) { @@ -171,26 +147,18 @@ bool CSecurityTLS::processMsg() setParam(); - // Create these early as they set up the push/pull functions - // for GnuTLS - tlsis = new rdr::TLSInStream(is, session); - tlsos = new rdr::TLSOutStream(os, session); + tlssock = new rdr::TLSSocket(is, os, session); rawis = is; rawos = os; } - int err; - err = gnutls_handshake(session); - if (err != GNUTLS_E_SUCCESS) { - if (!gnutls_error_is_fatal(err)) { - vlog.debug("Deferring completion of TLS handshake: %s", gnutls_strerror(err)); + try { + if (!tlssock->handshake()) return false; - } - - vlog.error("TLS Handshake failed: %s\n", gnutls_strerror (err)); + } catch (std::exception&) { shutdown(); - throw rdr::tls_error("TLS Handshake failed", err); + throw; } vlog.debug("TLS handshake completed with %s", @@ -198,33 +166,29 @@ bool CSecurityTLS::processMsg() checkSession(); - cc->setStreams(tlsis, tlsos); + cc->setStreams(&tlssock->inStream(), &tlssock->outStream()); return true; } void CSecurityTLS::setParam() { - static const char kx_anon_priority[] = ":+ANON-ECDH:+ANON-DH"; + static const char kx_anon_priority[] = "+ANON-ECDH:+ANON-DH"; int ret; // Custom priority string specified? if (strcmp(Security::GnuTLSPriority, "") != 0) { - char *prio; + std::string prio; const char *err; - prio = new char[strlen(Security::GnuTLSPriority) + - strlen(kx_anon_priority) + 1]; - - strcpy(prio, Security::GnuTLSPriority); - if (anon) - strcat(prio, kx_anon_priority); - - ret = gnutls_priority_set_direct(session, prio, &err); - - delete [] prio; + prio = (const char*)Security::GnuTLSPriority; + if (anon) { + prio += ":"; + prio += kx_anon_priority; + } + ret = gnutls_priority_set_direct(session, prio.c_str(), &err); if (ret != GNUTLS_E_SUCCESS) { if (ret == GNUTLS_E_INVALID_REQUEST) vlog.error("GnuTLS priority syntax error at: %s", err); @@ -234,30 +198,22 @@ void CSecurityTLS::setParam() const char *err; #if GNUTLS_VERSION_NUMBER >= 0x030603 - // gnutls_set_default_priority_appends() expects a normal priority string that - // doesn't start with ":". - ret = gnutls_set_default_priority_append(session, kx_anon_priority + 1, &err, 0); + ret = gnutls_set_default_priority_append(session, kx_anon_priority, &err, 0); if (ret != GNUTLS_E_SUCCESS) { if (ret == GNUTLS_E_INVALID_REQUEST) vlog.error("GnuTLS priority syntax error at: %s", err); throw rdr::tls_error("gnutls_set_default_priority_append()", ret); } #else + std::string prio; + // We don't know what the system default priority is, so we guess // it's what upstream GnuTLS has - static const char gnutls_default_priority[] = "NORMAL"; - char *prio; - - prio = new char[malloc(strlen(gnutls_default_priority) + - strlen(kx_anon_priority) + 1]; - - strcpy(prio, gnutls_default_priority); - strcat(prio, kx_anon_priority); - - ret = gnutls_priority_set_direct(session, prio, &err); - - delete [] prio; + prio = "NORMAL"; + prio += ":"; + prio += kx_anon_priority; + ret = gnutls_priority_set_direct(session, prio.c_str(), &err); if (ret != GNUTLS_E_SUCCESS) { if (ret == GNUTLS_E_INVALID_REQUEST) vlog.error("GnuTLS priority syntax error at: %s", err); @@ -277,6 +233,10 @@ void CSecurityTLS::setParam() vlog.debug("Anonymous session has been set"); } else { + const char* hostname; + size_t len; + bool valid; + ret = gnutls_certificate_allocate_credentials(&cert_cred); if (ret != GNUTLS_E_SUCCESS) throw rdr::tls_error("gnutls_certificate_allocate_credentials()", ret); @@ -294,10 +254,22 @@ void CSecurityTLS::setParam() if (ret != GNUTLS_E_SUCCESS) throw rdr::tls_error("gnutls_credentials_set()", ret); - if (gnutls_server_name_set(session, GNUTLS_NAME_DNS, - client->getServerName(), - strlen(client->getServerName())) != GNUTLS_E_SUCCESS) - vlog.error("Failed to configure the server name for TLS handshake"); + // Only DNS hostnames are allowed, and some servers will reject the + // connection if we provide anything else (e.g. an IPv6 address) + hostname = client->getServerName(); + len = strlen(hostname); + valid = true; + for (size_t i = 0; i < len; i++) { + if (!isalnum(hostname[i]) && hostname[i] != '.') + valid = false; + } + + if (valid) { + if (gnutls_server_name_set(session, GNUTLS_NAME_DNS, + client->getServerName(), + strlen(client->getServerName())) != GNUTLS_E_SUCCESS) + vlog.error("Failed to configure the server name for TLS handshake"); + } vlog.debug("X509 session has been set"); } @@ -324,12 +296,16 @@ void CSecurityTLS::checkSession() if (anon) return; - if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) + if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_UNSUPPORTED_CERTIFICATE); throw protocol_error("Unsupported certificate type"); + } err = gnutls_certificate_verify_peers2(session, &status); if (err != 0) { vlog.error("Server certificate verification failed: %s", gnutls_strerror(err)); + gnutls_alert_send_appropriate(session, err); throw rdr::tls_error("Server certificate verification()", err); } @@ -346,13 +322,17 @@ void CSecurityTLS::checkSession() GNUTLS_CRT_X509, &status_str, 0); - if (err != GNUTLS_E_SUCCESS) + if (err != GNUTLS_E_SUCCESS) { + gnutls_alert_send_appropriate(session, err); throw rdr::tls_error("Failed to get certificate error description", err); + } error = (const char*)status_str.data; gnutls_free(status_str.data); + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw protocol_error( core::format("Invalid server certificate: %s", error.c_str())); } @@ -361,8 +341,10 @@ void CSecurityTLS::checkSession() GNUTLS_CRT_X509, &status_str, 0); - if (err != GNUTLS_E_SUCCESS) + if (err != GNUTLS_E_SUCCESS) { + gnutls_alert_send_appropriate(session, err); throw rdr::tls_error("Failed to get certificate error description", err); + } vlog.info("Server certificate errors: %s", status_str.data); @@ -372,16 +354,21 @@ void CSecurityTLS::checkSession() /* Process overridable errors later */ cert_list = gnutls_certificate_get_peers(session, &cert_list_size); - if (!cert_list_size) + if (!cert_list_size) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_UNSUPPORTED_CERTIFICATE); throw protocol_error("Empty certificate chain"); + } /* Process only server's certificate, not issuer's certificate */ gnutls_x509_crt_t crt; gnutls_x509_crt_init(&crt); err = gnutls_x509_crt_import(crt, &cert_list[0], GNUTLS_X509_FMT_DER); - if (err != GNUTLS_E_SUCCESS) + if (err != GNUTLS_E_SUCCESS) { + gnutls_alert_send_appropriate(session, err); throw rdr::tls_error("Failed to decode server certificate", err); + } if (gnutls_x509_crt_check_hostname(crt, client->getServerName()) == 0) { vlog.info("Server certificate doesn't match given server name"); @@ -420,12 +407,15 @@ void CSecurityTLS::checkSession() if ((known != GNUTLS_E_NO_CERTIFICATE_FOUND) && (known != GNUTLS_E_CERTIFICATE_KEY_MISMATCH)) { + gnutls_alert_send_appropriate(session, known); throw rdr::tls_error("Could not load known hosts database", known); } err = gnutls_x509_crt_print(crt, GNUTLS_CRT_PRINT_ONELINE, &info); - if (err != GNUTLS_E_SUCCESS) + if (err != GNUTLS_E_SUCCESS) { + gnutls_alert_send_appropriate(session, known); throw rdr::tls_error("Could not find certificate to display", err); + } len = strlen((char*)info.data); for (size_t i = 0; i < len - 1; i++) { @@ -456,8 +446,11 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Unknown certificate issuer", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_UNKNOWN_CA); throw auth_cancelled(); + } status &= ~(GNUTLS_CERT_INVALID | GNUTLS_CERT_SIGNER_NOT_FOUND | @@ -478,8 +471,11 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Certificate is not yet valid", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw auth_cancelled(); + } status &= ~GNUTLS_CERT_NOT_ACTIVATED; } @@ -498,8 +494,11 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Expired certificate", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw auth_cancelled(); + } status &= ~GNUTLS_CERT_EXPIRED; } @@ -518,14 +517,19 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Insecure certificate algorithm", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw auth_cancelled(); + } status &= ~GNUTLS_CERT_INSECURE_ALGORITHM; } if (status != 0) { vlog.error("Unhandled certificate problems: 0x%x", status); + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw std::logic_error("Unhandled certificate problems"); } @@ -544,8 +548,11 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Certificate hostname mismatch", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw auth_cancelled(); + } } } else if (known == GNUTLS_E_CERTIFICATE_KEY_MISMATCH) { std::string text; @@ -571,8 +578,11 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Unexpected server certificate", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_UNKNOWN_CA); throw auth_cancelled(); + } status &= ~(GNUTLS_CERT_INVALID | GNUTLS_CERT_SIGNER_NOT_FOUND | @@ -594,8 +604,11 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Unexpected server certificate", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw auth_cancelled(); + } status &= ~GNUTLS_CERT_NOT_ACTIVATED; } @@ -615,8 +628,11 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Unexpected server certificate", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw auth_cancelled(); + } status &= ~GNUTLS_CERT_EXPIRED; } @@ -636,14 +652,19 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Unexpected server certificate", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw auth_cancelled(); + } status &= ~GNUTLS_CERT_INSECURE_ALGORITHM; } if (status != 0) { vlog.error("Unhandled certificate problems: 0x%x", status); + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw std::logic_error("Unhandled certificate problems"); } @@ -663,8 +684,11 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Unexpected server certificate", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw auth_cancelled(); + } } } diff --git a/common/rfb/CSecurityTLS.h b/common/rfb/CSecurityTLS.h index 9b70366d..51b7dac1 100644 --- a/common/rfb/CSecurityTLS.h +++ b/common/rfb/CSecurityTLS.h @@ -2,6 +2,7 @@ * Copyright (C) 2004 Red Hat Inc. * Copyright (C) 2005 Martin Koegler * Copyright (C) 2010 TigerVNC Team + * Copyright 2012-2025 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -34,8 +35,7 @@ namespace rdr { class InStream; class OutStream; - class TLSInStream; - class TLSOutStream; + class TLSSocket; } namespace rfb { @@ -63,8 +63,7 @@ namespace rfb { gnutls_certificate_credentials_t cert_cred; bool anon; - rdr::TLSInStream* tlsis; - rdr::TLSOutStream* tlsos; + rdr::TLSSocket* tlssock; rdr::InStream* rawis; rdr::OutStream* rawos; diff --git a/common/rfb/ClientParams.cxx b/common/rfb/ClientParams.cxx index 972b89e8..514b0b4e 100644 --- a/common/rfb/ClientParams.cxx +++ b/common/rfb/ClientParams.cxx @@ -24,6 +24,7 @@ #include <stdexcept> +#include <core/LogWriter.h> #include <core/string.h> #include <rfb/encodings.h> @@ -35,6 +36,8 @@ using namespace rfb; +static core::LogWriter vlog("ClientParams"); + ClientParams::ClientParams() : majorVersion(0), minorVersion(0), compressLevel(2), qualityLevel(-1), fineQualityLevel(-1), @@ -72,8 +75,14 @@ void ClientParams::setDimensions(int width, int height) void ClientParams::setDimensions(int width, int height, const ScreenSet& layout) { - if (!layout.validate(width, height)) + if (!layout.validate(width, height)) { + char buffer[2048]; + vlog.debug("Invalid screen layout for %dx%d:", width, height); + layout.print(buffer, sizeof(buffer)); + vlog.debug("%s", buffer); + throw std::invalid_argument("Attempted to configure an invalid screen layout"); + } width_ = width; height_ = height; @@ -247,4 +256,4 @@ bool ClientParams::supportsExtendedMouseButtons() const if (supportsEncoding(pseudoEncodingExtendedMouseButtons)) return true; return false; -}
\ No newline at end of file +} diff --git a/common/rfb/DecodeManager.cxx b/common/rfb/DecodeManager.cxx index 94908f86..48181f94 100644 --- a/common/rfb/DecodeManager.cxx +++ b/common/rfb/DecodeManager.cxx @@ -24,7 +24,6 @@ #include <string.h> #include <core/LogWriter.h> -#include <core/Mutex.h> #include <core/Region.h> #include <core/string.h> @@ -48,11 +47,7 @@ DecodeManager::DecodeManager(CConnection *conn_) : memset(stats, 0, sizeof(stats)); - queueMutex = new core::Mutex(); - producerCond = new core::Condition(queueMutex); - consumerCond = new core::Condition(queueMutex); - - cpuCount = core::Thread::getSystemCPUCount(); + cpuCount = std::thread::hardware_concurrency(); if (cpuCount == 0) { vlog.error("Unable to determine the number of CPU cores on this system"); cpuCount = 1; @@ -90,10 +85,6 @@ DecodeManager::~DecodeManager() freeBuffers.pop_back(); } - delete consumerCond; - delete producerCond; - delete queueMutex; - for (Decoder* decoder : decoders) delete decoder; } @@ -125,29 +116,25 @@ bool DecodeManager::decodeRect(const core::Rect& r, int encoding, decoder = decoders[encoding]; // Wait for an available memory buffer - queueMutex->lock(); + std::unique_lock<std::mutex> lock(queueMutex); // FIXME: Should we return and let other things run here? while (freeBuffers.empty()) - producerCond->wait(); + producerCond.wait(lock); // Don't pop the buffer in case we throw an exception // whilst reading bufferStream = freeBuffers.front(); - queueMutex->unlock(); + lock.unlock(); // First check if any thread has encountered a problem throwThreadException(); // Read the rect bufferStream->clear(); - try { - if (!decoder->readRect(r, conn->getInStream(), conn->server, bufferStream)) - return false; - } catch (std::exception& e) { - throw std::runtime_error(core::format("Error reading rect: %s", e.what())); - } + if (!decoder->readRect(r, conn->getInStream(), conn->server, bufferStream)) + return false; stats[encoding].rects++; stats[encoding].bytes += 12 + bufferStream->length(); @@ -170,7 +157,7 @@ bool DecodeManager::decodeRect(const core::Rect& r, int encoding, bufferStream->length(), conn->server, &entry->affectedRegion); - queueMutex->lock(); + lock.lock(); // The workers add buffers to the end so it's safe to assume // the front is still the same buffer @@ -180,21 +167,21 @@ bool DecodeManager::decodeRect(const core::Rect& r, int encoding, // We only put a single entry on the queue so waking a single // thread is sufficient - consumerCond->signal(); + consumerCond.notify_one(); - queueMutex->unlock(); + lock.unlock(); return true; } void DecodeManager::flush() { - queueMutex->lock(); + std::unique_lock<std::mutex> lock(queueMutex); while (!workQueue.empty()) - producerCond->wait(); + producerCond.wait(lock); - queueMutex->unlock(); + lock.unlock(); throwThreadException(); } @@ -242,7 +229,7 @@ void DecodeManager::logStats() void DecodeManager::setThreadException() { - core::AutoMutex a(queueMutex); + const std::lock_guard<std::mutex> lock(queueMutex); if (threadException) return; @@ -252,7 +239,7 @@ void DecodeManager::setThreadException() void DecodeManager::throwThreadException() { - core::AutoMutex a(queueMutex); + const std::lock_guard<std::mutex> lock(queueMutex); if (!threadException) return; @@ -266,7 +253,7 @@ void DecodeManager::throwThreadException() } DecodeManager::DecodeThread::DecodeThread(DecodeManager* manager_) - : manager(manager_), stopRequested(false) + : manager(manager_), thread(nullptr), stopRequested(false) { start(); } @@ -274,25 +261,35 @@ DecodeManager::DecodeThread::DecodeThread(DecodeManager* manager_) DecodeManager::DecodeThread::~DecodeThread() { stop(); - wait(); + if (thread != nullptr) { + thread->join(); + delete thread; + } +} + +void DecodeManager::DecodeThread::start() +{ + assert(thread == nullptr); + + thread = new std::thread(&DecodeThread::worker, this); } void DecodeManager::DecodeThread::stop() { - core::AutoMutex a(manager->queueMutex); + const std::lock_guard<std::mutex> lock(manager->queueMutex); - if (!isRunning()) + if (thread == nullptr) return; stopRequested = true; // We can't wake just this thread, so wake everyone - manager->consumerCond->broadcast(); + manager->consumerCond.notify_all(); } void DecodeManager::DecodeThread::worker() { - manager->queueMutex->lock(); + std::unique_lock<std::mutex> lock(manager->queueMutex); while (!stopRequested) { DecodeManager::QueueEntry *entry; @@ -301,14 +298,14 @@ void DecodeManager::DecodeThread::worker() entry = findEntry(); if (entry == nullptr) { // Wait and try again - manager->consumerCond->wait(); + manager->consumerCond.wait(lock); continue; } // This is ours now entry->active = true; - manager->queueMutex->unlock(); + lock.unlock(); // Do the actual decoding try { @@ -321,7 +318,7 @@ void DecodeManager::DecodeThread::worker() assert(false); } - manager->queueMutex->lock(); + lock.lock(); // Remove the entry from the queue and give back the memory buffer manager->freeBuffers.push_back(entry->bufferStream); @@ -329,14 +326,12 @@ void DecodeManager::DecodeThread::worker() delete entry; // Wake the main thread in case it is waiting for a memory buffer - manager->producerCond->signal(); + manager->producerCond.notify_one(); // This rect might have been blocking multiple other rects, so // wake up every worker thread if (manager->workQueue.size() > 1) - manager->consumerCond->broadcast(); + manager->consumerCond.notify_all(); } - - manager->queueMutex->unlock(); } DecodeManager::QueueEntry* DecodeManager::DecodeThread::findEntry() diff --git a/common/rfb/DecodeManager.h b/common/rfb/DecodeManager.h index 95d3ceca..146bf8ae 100644 --- a/common/rfb/DecodeManager.h +++ b/common/rfb/DecodeManager.h @@ -19,17 +19,17 @@ #ifndef __RFB_DECODEMANAGER_H__ #define __RFB_DECODEMANAGER_H__ +#include <condition_variable> #include <exception> #include <list> +#include <mutex> +#include <thread> #include <core/Region.h> -#include <core/Thread.h> #include <rfb/encodings.h> namespace core { - class Condition; - class Mutex; struct Rect; } @@ -86,25 +86,27 @@ namespace rfb { std::list<rdr::MemOutStream*> freeBuffers; std::list<QueueEntry*> workQueue; - core::Mutex* queueMutex; - core::Condition* producerCond; - core::Condition* consumerCond; + std::mutex queueMutex; + std::condition_variable producerCond; + std::condition_variable consumerCond; private: - class DecodeThread : public core::Thread { + class DecodeThread { public: DecodeThread(DecodeManager* manager); ~DecodeThread(); + void start(); void stop(); protected: - void worker() override; + void worker(); DecodeManager::QueueEntry* findEntry(); private: DecodeManager* manager; + std::thread* thread; bool stopRequested; }; diff --git a/common/rfb/EncodeManager.cxx b/common/rfb/EncodeManager.cxx index 7ec70e69..6a63fa6f 100644 --- a/common/rfb/EncodeManager.cxx +++ b/common/rfb/EncodeManager.cxx @@ -278,6 +278,13 @@ void EncodeManager::pruneLosslessRefresh(const core::Region& limits) pendingRefreshRegion.assign_intersect(limits); } +void EncodeManager::forceRefresh(const core::Region& req) +{ + lossyRegion.assign_union(req); + if (!recentChangeTimer.isStarted()) + pendingRefreshRegion.assign_union(req); +} + void EncodeManager::writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb, const RenderedCursor* renderedCursor) { diff --git a/common/rfb/EncodeManager.h b/common/rfb/EncodeManager.h index 959c13d6..4ce6d0ce 100644 --- a/common/rfb/EncodeManager.h +++ b/common/rfb/EncodeManager.h @@ -54,6 +54,8 @@ namespace rfb { void pruneLosslessRefresh(const core::Region& limits); + void forceRefresh(const core::Region& req); + void writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb, const RenderedCursor* renderedCursor); diff --git a/common/rfb/SConnection.cxx b/common/rfb/SConnection.cxx index c269fac0..c698b991 100644 --- a/common/rfb/SConnection.cxx +++ b/common/rfb/SConnection.cxx @@ -154,8 +154,6 @@ bool SConnection::processVersionMsg() client.majorVersion,client.minorVersion); } - versionReceived(); - std::list<uint8_t> secTypes; std::list<uint8_t>::iterator i; secTypes = security.GetEnabledSecTypes(); @@ -346,6 +344,8 @@ bool SConnection::accessCheck(AccessRights ar) const void SConnection::setEncodings(int nEncodings, const int32_t* encodings) { int i; + bool firstFence, firstContinuousUpdates, firstLEDState, + firstQEMUKeyEvent, firstExtMouseButtonsEvent; preferredEncoding = encodingRaw; for (i = 0;i < nEncodings;i++) { @@ -355,7 +355,26 @@ void SConnection::setEncodings(int nEncodings, const int32_t* encodings) } } - SMsgHandler::setEncodings(nEncodings, encodings); + firstFence = !client.supportsFence(); + firstContinuousUpdates = !client.supportsContinuousUpdates(); + firstLEDState = !client.supportsLEDState(); + firstQEMUKeyEvent = !client.supportsEncoding(pseudoEncodingQEMUKeyEvent); + firstExtMouseButtonsEvent = !client.supportsEncoding(pseudoEncodingExtendedMouseButtons); + + client.setEncodings(nEncodings, encodings); + + supportsLocalCursor(); + + if (client.supportsFence() && firstFence) + supportsFence(); + if (client.supportsContinuousUpdates() && firstContinuousUpdates) + supportsContinuousUpdates(); + if (client.supportsLEDState() && firstLEDState) + supportsLEDState(); + if (client.supportsEncoding(pseudoEncodingQEMUKeyEvent) && firstQEMUKeyEvent) + writer()->writeQEMUKeyEvent(); + if (client.supportsEncoding(pseudoEncodingExtendedMouseButtons) && firstExtMouseButtonsEvent) + writer()->writeExtendedMouseButtonsSupport(); if (client.supportsEncoding(pseudoEncodingExtendedClipboard)) { uint32_t sizes[] = { 0 }; @@ -375,9 +394,54 @@ void SConnection::clientCutText(const char* str) clientClipboard = str; hasRemoteClipboard = true; + if (!accessCheck(AccessCutText)) + return; + handleClipboardAnnounce(true); } +void SConnection::handleClipboardCaps(uint32_t flags, const uint32_t* lengths) +{ + int i; + + vlog.debug("Got client clipboard capabilities:"); + for (i = 0;i < 16;i++) { + if (flags & (1 << i)) { + const char *type; + + switch (1 << i) { + case clipboardUTF8: + type = "Plain text"; + break; + case clipboardRTF: + type = "Rich text"; + break; + case clipboardHTML: + type = "HTML"; + break; + case clipboardDIB: + type = "Images"; + break; + case clipboardFiles: + type = "Files"; + break; + default: + vlog.debug(" Unknown format 0x%x", 1 << i); + continue; + } + + if (lengths[i] == 0) + vlog.debug(" %s (only notify)", type); + else { + vlog.debug(" %s (automatically send up to %s)", + type, core::iecPrefix(lengths[i], "B").c_str()); + } + } + } + + client.setClipboardCaps(flags, lengths); +} + void SConnection::handleClipboardRequest(uint32_t flags) { if (!(flags & rfb::clipboardUTF8)) { @@ -388,6 +452,8 @@ void SConnection::handleClipboardRequest(uint32_t flags) vlog.debug("Ignoring unexpected clipboard request"); return; } + if (!accessCheck(AccessCutText)) + return; handleClipboardRequest(); } @@ -403,10 +469,15 @@ void SConnection::handleClipboardNotify(uint32_t flags) if (flags & rfb::clipboardUTF8) { hasLocalClipboard = false; + if (!accessCheck(AccessCutText)) + return; handleClipboardAnnounce(true); } else { + if (!accessCheck(AccessCutText)) + return; handleClipboardAnnounce(false); } + } void SConnection::handleClipboardProvide(uint32_t flags, @@ -426,21 +497,26 @@ void SConnection::handleClipboardProvide(uint32_t flags, clientClipboard = core::convertLF((const char*)data[0], lengths[0]); hasRemoteClipboard = true; + if (!accessCheck(AccessCutText)) + return; + // FIXME: Should probably verify that this data was actually requested handleClipboardData(clientClipboard.c_str()); } -void SConnection::supportsQEMUKeyEvent() +void SConnection::supportsLocalCursor() +{ +} + +void SConnection::supportsFence() { - writer()->writeQEMUKeyEvent(); } -void SConnection::supportsExtendedMouseButtons() +void SConnection::supportsContinuousUpdates() { - writer()->writeExtendedMouseButtonsSupport(); } -void SConnection::versionReceived() +void SConnection::supportsLEDState() { } @@ -502,7 +578,7 @@ void SConnection::close(const char* /*reason*/) void SConnection::setPixelFormat(const PixelFormat& pf) { - SMsgHandler::setPixelFormat(pf); + client.setPF(pf); readyForSetColourMapEntries = true; if (!pf.trueColour) writeFakeColourMap(); @@ -551,6 +627,9 @@ void SConnection::handleClipboardData(const char* /*data*/) void SConnection::requestClipboard() { + if (!accessCheck(AccessCutText)) + return; + if (hasRemoteClipboard) { handleClipboardData(clientClipboard.c_str()); return; @@ -563,6 +642,9 @@ void SConnection::requestClipboard() void SConnection::announceClipboard(bool available) { + if (!accessCheck(AccessCutText)) + return; + hasLocalClipboard = available; unsolicitedClipboardAttempt = false; @@ -589,6 +671,9 @@ void SConnection::announceClipboard(bool available) void SConnection::sendClipboardData(const char* data) { + if (!accessCheck(AccessCutText)) + return; + if (client.supportsEncoding(pseudoEncodingExtendedClipboard) && (client.clipboardFlags() & rfb::clipboardProvide)) { // FIXME: This conversion magic should be in SMsgWriter diff --git a/common/rfb/SConnection.h b/common/rfb/SConnection.h index 0f4de5a5..a90b37ca 100644 --- a/common/rfb/SConnection.h +++ b/common/rfb/SConnection.h @@ -87,6 +87,59 @@ namespace rfb { // cleanup of the SConnection object by the server virtual void close(const char* reason); + // requestClipboard() will result in a request to the client to + // transfer its clipboard data. A call to handleClipboardData() + // will be made once the data is available. + virtual void requestClipboard(); + + // announceClipboard() informs the client of changes to the + // clipboard on the server. The client may later request the + // clipboard data via handleClipboardRequest(). + virtual void announceClipboard(bool available); + + // sendClipboardData() transfers the clipboard data to the client + // and should be called whenever the client has requested the + // clipboard via handleClipboardRequest(). + virtual void sendClipboardData(const char* data); + + // getAccessRights() returns the access rights of a SConnection to the server. + AccessRights getAccessRights() { return accessRights; } + + // setAccessRights() allows a security package to limit the access rights + // of a SConnection to the server. How the access rights are treated + // is up to the derived class. + virtual void setAccessRights(AccessRights ar); + virtual bool accessCheck(AccessRights ar) const; + + // authenticated() returns true if the client has authenticated + // successfully. + bool authenticated() { return (state_ == RFBSTATE_INITIALISATION || + state_ == RFBSTATE_NORMAL); } + + SMsgReader* reader() { return reader_; } + SMsgWriter* writer() { return writer_; } + + rdr::InStream* getInStream() { return is; } + rdr::OutStream* getOutStream() { return os; } + + enum stateEnum { + RFBSTATE_UNINITIALISED, + RFBSTATE_PROTOCOL_VERSION, + RFBSTATE_SECURITY_TYPE, + RFBSTATE_SECURITY, + RFBSTATE_SECURITY_FAILURE, + RFBSTATE_QUERYING, + RFBSTATE_INITIALISATION, + RFBSTATE_NORMAL, + RFBSTATE_CLOSING, + RFBSTATE_INVALID + }; + + stateEnum state() { return state_; } + + int32_t getPreferredEncoding() { return preferredEncoding; } + + protected: // Overridden from SMsgHandler @@ -94,23 +147,38 @@ namespace rfb { void clientCutText(const char* str) override; + void handleClipboardCaps(uint32_t flags, + const uint32_t* lengths) override; void handleClipboardRequest(uint32_t flags) override; void handleClipboardPeek() override; void handleClipboardNotify(uint32_t flags) override; void handleClipboardProvide(uint32_t flags, const size_t* lengths, const uint8_t* const* data) override; - void supportsQEMUKeyEvent() override; - - virtual void supportsExtendedMouseButtons() override; - - // Methods to be overridden in a derived class - // versionReceived() indicates that the version number has just been read - // from the client. The version will already have been "cooked" - // to deal with unknown/bogus viewer protocol numbers. - virtual void versionReceived(); + // supportsLocalCursor() is called whenever the status of + // cp.supportsLocalCursor has changed. At the moment this happens on a + // setEncodings message, but in the future this may be due to a message + // specially for this purpose. + virtual void supportsLocalCursor(); + + // supportsFence() is called the first time we detect support for fences + // in the client. A fence message should be sent at this point to notify + // the client of server support. + virtual void supportsFence(); + + // supportsContinuousUpdates() is called the first time we detect that + // the client wants the continuous updates extension. A + // EndOfContinuousUpdates message should be sent back to the client at + // this point if it is supported. + virtual void supportsContinuousUpdates(); + + // supportsLEDState() is called the first time we detect that the + // client supports the LED state extension. A LEDState message + // should be sent back to the client to inform it of the current + // server state. + virtual void supportsLEDState(); // authSuccess() is called when authentication has succeeded. virtual void authSuccess(); @@ -166,62 +234,6 @@ namespace rfb { // client received the request. virtual void handleClipboardData(const char* data); - - // Other methods - - // requestClipboard() will result in a request to the client to - // transfer its clipboard data. A call to handleClipboardData() - // will be made once the data is available. - virtual void requestClipboard(); - - // announceClipboard() informs the client of changes to the - // clipboard on the server. The client may later request the - // clipboard data via handleClipboardRequest(). - virtual void announceClipboard(bool available); - - // sendClipboardData() transfers the clipboard data to the client - // and should be called whenever the client has requested the - // clipboard via handleClipboardRequest(). - virtual void sendClipboardData(const char* data); - - // getAccessRights() returns the access rights of a SConnection to the server. - AccessRights getAccessRights() { return accessRights; } - - // setAccessRights() allows a security package to limit the access rights - // of a SConnection to the server. How the access rights are treated - // is up to the derived class. - virtual void setAccessRights(AccessRights ar); - virtual bool accessCheck(AccessRights ar) const; - - // authenticated() returns true if the client has authenticated - // successfully. - bool authenticated() { return (state_ == RFBSTATE_INITIALISATION || - state_ == RFBSTATE_NORMAL); } - - SMsgReader* reader() { return reader_; } - SMsgWriter* writer() { return writer_; } - - rdr::InStream* getInStream() { return is; } - rdr::OutStream* getOutStream() { return os; } - - enum stateEnum { - RFBSTATE_UNINITIALISED, - RFBSTATE_PROTOCOL_VERSION, - RFBSTATE_SECURITY_TYPE, - RFBSTATE_SECURITY, - RFBSTATE_SECURITY_FAILURE, - RFBSTATE_QUERYING, - RFBSTATE_INITIALISATION, - RFBSTATE_NORMAL, - RFBSTATE_CLOSING, - RFBSTATE_INVALID - }; - - stateEnum state() { return state_; } - - int32_t getPreferredEncoding() { return preferredEncoding; } - - protected: // failConnection() prints a message to the log, sends a connection // failed message to the client (if possible) and throws an // Exception. diff --git a/common/rfb/SMsgHandler.cxx b/common/rfb/SMsgHandler.cxx deleted file mode 100644 index 9eb5ae08..00000000 --- a/common/rfb/SMsgHandler.cxx +++ /dev/null @@ -1,176 +0,0 @@ -/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2019 Pierre Ossman for Cendio AB - * - * This 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 software 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 software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <core/LogWriter.h> -#include <core/string.h> - -#include <rfb/SMsgHandler.h> -#include <rfb/ScreenSet.h> -#include <rfb/clipboardTypes.h> -#include <rfb/encodings.h> - -using namespace rfb; - -static core::LogWriter vlog("SMsgHandler"); - -SMsgHandler::SMsgHandler() -{ -} - -SMsgHandler::~SMsgHandler() -{ -} - -void SMsgHandler::clientInit(bool /*shared*/) -{ -} - -void SMsgHandler::setPixelFormat(const PixelFormat& pf) -{ - client.setPF(pf); -} - -void SMsgHandler::setEncodings(int nEncodings, const int32_t* encodings) -{ - bool firstFence, firstContinuousUpdates, firstLEDState, - firstQEMUKeyEvent, firstExtMouseButtonsEvent; - - firstFence = !client.supportsFence(); - firstContinuousUpdates = !client.supportsContinuousUpdates(); - firstLEDState = !client.supportsLEDState(); - firstQEMUKeyEvent = !client.supportsEncoding(pseudoEncodingQEMUKeyEvent); - firstExtMouseButtonsEvent = !client.supportsEncoding(pseudoEncodingExtendedMouseButtons); - - client.setEncodings(nEncodings, encodings); - - supportsLocalCursor(); - - if (client.supportsFence() && firstFence) - supportsFence(); - if (client.supportsContinuousUpdates() && firstContinuousUpdates) - supportsContinuousUpdates(); - if (client.supportsLEDState() && firstLEDState) - supportsLEDState(); - if (client.supportsEncoding(pseudoEncodingQEMUKeyEvent) && firstQEMUKeyEvent) - supportsQEMUKeyEvent(); - if (client.supportsEncoding(pseudoEncodingExtendedMouseButtons) && firstExtMouseButtonsEvent) - supportsExtendedMouseButtons(); -} - -void SMsgHandler::keyEvent(uint32_t /*keysym*/, uint32_t /*keycode*/, - bool /*down*/) -{ -} - -void SMsgHandler::pointerEvent(const core::Point& /*pos*/, - uint16_t /*buttonMask*/) -{ -} - -void SMsgHandler::clientCutText(const char* /*str*/) -{ -} - -void SMsgHandler::handleClipboardCaps(uint32_t flags, const uint32_t* lengths) -{ - int i; - - vlog.debug("Got client clipboard capabilities:"); - for (i = 0;i < 16;i++) { - if (flags & (1 << i)) { - const char *type; - - switch (1 << i) { - case clipboardUTF8: - type = "Plain text"; - break; - case clipboardRTF: - type = "Rich text"; - break; - case clipboardHTML: - type = "HTML"; - break; - case clipboardDIB: - type = "Images"; - break; - case clipboardFiles: - type = "Files"; - break; - default: - vlog.debug(" Unknown format 0x%x", 1 << i); - continue; - } - - if (lengths[i] == 0) - vlog.debug(" %s (only notify)", type); - else { - vlog.debug(" %s (automatically send up to %s)", - type, core::iecPrefix(lengths[i], "B").c_str()); - } - } - } - - client.setClipboardCaps(flags, lengths); -} - -void SMsgHandler::handleClipboardRequest(uint32_t /*flags*/) -{ -} - -void SMsgHandler::handleClipboardPeek() -{ -} - -void SMsgHandler::handleClipboardNotify(uint32_t /*flags*/) -{ -} - -void SMsgHandler::handleClipboardProvide(uint32_t /*flags*/, - const size_t* /*lengths*/, - const uint8_t* const* /*data*/) -{ -} - -void SMsgHandler::supportsLocalCursor() -{ -} - -void SMsgHandler::supportsFence() -{ -} - -void SMsgHandler::supportsContinuousUpdates() -{ -} - -void SMsgHandler::supportsLEDState() -{ -} - -void SMsgHandler::supportsQEMUKeyEvent() -{ -} - -void SMsgHandler::supportsExtendedMouseButtons() -{ -}
\ No newline at end of file diff --git a/common/rfb/SMsgHandler.h b/common/rfb/SMsgHandler.h index f5f5b769..d14a21c0 100644 --- a/common/rfb/SMsgHandler.h +++ b/common/rfb/SMsgHandler.h @@ -27,80 +27,45 @@ #include <rfb/ClientParams.h> -namespace rdr { class InStream; } - namespace rfb { class SMsgHandler { public: - SMsgHandler(); - virtual ~SMsgHandler(); + virtual ~SMsgHandler() {} - // The following methods are called as corresponding messages are read. A - // derived class should override these methods as desired. Note that for - // the setPixelFormat(), setEncodings() and clipboardCaps() methods, a - // derived class must call on to SMsgHandler's methods. + // The following methods are called as corresponding messages are + // read. A derived class must override these methods. - virtual void clientInit(bool shared); + virtual void clientInit(bool shared) = 0; - virtual void setPixelFormat(const PixelFormat& pf); - virtual void setEncodings(int nEncodings, const int32_t* encodings); + virtual void setPixelFormat(const PixelFormat& pf) = 0; + virtual void setEncodings(int nEncodings, + const int32_t* encodings) = 0; virtual void framebufferUpdateRequest(const core::Rect& r, bool incremental) = 0; virtual void setDesktopSize(int fb_width, int fb_height, const ScreenSet& layout) = 0; - virtual void fence(uint32_t flags, unsigned len, const uint8_t data[]) = 0; + virtual void fence(uint32_t flags, unsigned len, + const uint8_t data[]) = 0; virtual void enableContinuousUpdates(bool enable, - int x, int y, int w, int h) = 0; + int x, int y, + int w, int h) = 0; virtual void keyEvent(uint32_t keysym, uint32_t keycode, - bool down); + bool down) = 0; virtual void pointerEvent(const core::Point& pos, - uint16_t buttonMask); + uint16_t buttonMask) = 0; - virtual void clientCutText(const char* str); + virtual void clientCutText(const char* str) = 0; virtual void handleClipboardCaps(uint32_t flags, - const uint32_t* lengths); - virtual void handleClipboardRequest(uint32_t flags); - virtual void handleClipboardPeek(); - virtual void handleClipboardNotify(uint32_t flags); + const uint32_t* lengths) = 0; + virtual void handleClipboardRequest(uint32_t flags) = 0; + virtual void handleClipboardPeek() = 0; + virtual void handleClipboardNotify(uint32_t flags) = 0; virtual void handleClipboardProvide(uint32_t flags, const size_t* lengths, - const uint8_t* const* data); - - // supportsLocalCursor() is called whenever the status of - // cp.supportsLocalCursor has changed. At the moment this happens on a - // setEncodings message, but in the future this may be due to a message - // specially for this purpose. - virtual void supportsLocalCursor(); - - // supportsFence() is called the first time we detect support for fences - // in the client. A fence message should be sent at this point to notify - // the client of server support. - virtual void supportsFence(); - - // supportsContinuousUpdates() is called the first time we detect that - // the client wants the continuous updates extension. A - // EndOfContinuousUpdates message should be sent back to the client at - // this point if it is supported. - virtual void supportsContinuousUpdates(); - - // supportsLEDState() is called the first time we detect that the - // client supports the LED state extension. A LEDState message - // should be sent back to the client to inform it of the current - // server state. - virtual void supportsLEDState(); - - // supportsQEMUKeyEvent() is called the first time we detect that the - // client wants the QEMU Extended Key Event extension. The default - // handler will send a pseudo-rect back, signalling server support. - virtual void supportsQEMUKeyEvent(); - - // supportsExtendedMouseButtons() is called the first time we detect that the - // client supports sending 16 bit mouse button state. This lets us pass more button - // states between server and client. - virtual void supportsExtendedMouseButtons(); + const uint8_t* const* data) = 0; ClientParams client; }; diff --git a/common/rfb/SSecurityPlain.cxx b/common/rfb/SSecurityPlain.cxx index 06631f81..4fa63250 100644 --- a/common/rfb/SSecurityPlain.cxx +++ b/common/rfb/SSecurityPlain.cxx @@ -113,8 +113,9 @@ bool SSecurityPlain::processMsg() password[plen] = 0; username[ulen] = 0; plen = 0; - if (!valid->validate(sc, username, password)) - throw auth_error("Authentication failed"); + std::string msg = "Authentication failed"; + if (!valid->validate(sc, username, password, msg)) + throw auth_error(msg); } return true; diff --git a/common/rfb/SSecurityPlain.h b/common/rfb/SSecurityPlain.h index f2bc3483..4c030455 100644 --- a/common/rfb/SSecurityPlain.h +++ b/common/rfb/SSecurityPlain.h @@ -29,14 +29,20 @@ namespace rfb { class PasswordValidator { public: - bool validate(SConnection* sc, const char *username, const char *password) - { return validUser(username) ? validateInternal(sc, username, password) : false; } + bool validate(SConnection* sc, + const char *username, + const char *password, + std::string &msg) + { return validUser(username) ? validateInternal(sc, username, password, msg) : false; } static core::StringListParameter plainUsers; virtual ~PasswordValidator() { } protected: - virtual bool validateInternal(SConnection* sc, const char *username, const char *password)=0; + virtual bool validateInternal(SConnection* sc, + const char *username, + const char *password, + std::string &msg) = 0; static bool validUser(const char* username); }; diff --git a/common/rfb/SSecurityRSAAES.cxx b/common/rfb/SSecurityRSAAES.cxx index 6afb52dd..405005ab 100644 --- a/common/rfb/SSecurityRSAAES.cxx +++ b/common/rfb/SSecurityRSAAES.cxx @@ -583,9 +583,10 @@ void SSecurityRSAAES::verifyUserPass() #elif !defined(__APPLE__) UnixPasswordValidator *valid = new UnixPasswordValidator(); #endif - if (!valid->validate(sc, username, password)) { + std::string msg = "Authentication failed"; + if (!valid->validate(sc, username, password, msg)) { delete valid; - throw auth_error("Authentication failed"); + throw auth_error(msg); } delete valid; #else diff --git a/common/rfb/SSecurityTLS.cxx b/common/rfb/SSecurityTLS.cxx index 2e173771..17497b8e 100644 --- a/common/rfb/SSecurityTLS.cxx +++ b/common/rfb/SSecurityTLS.cxx @@ -2,7 +2,7 @@ * Copyright (C) 2004 Red Hat Inc. * Copyright (C) 2005 Martin Koegler * Copyright (C) 2010 TigerVNC Team - * Copyright (C) 2012-2021 Pierre Ossman for Cendio AB + * Copyright 2012-2025 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -37,8 +37,7 @@ #include <rfb/Exception.h> #include <rdr/TLSException.h> -#include <rdr/TLSInStream.h> -#include <rdr/TLSOutStream.h> +#include <rdr/TLSSocket.h> #include <gnutls/x509.h> @@ -72,7 +71,7 @@ static core::LogWriter vlog("TLS"); SSecurityTLS::SSecurityTLS(SConnection* sc_, bool _anon) : SSecurity(sc_), session(nullptr), anon_cred(nullptr), - cert_cred(nullptr), anon(_anon), tlsis(nullptr), tlsos(nullptr), + cert_cred(nullptr), anon(_anon), tlssock(nullptr), rawis(nullptr), rawos(nullptr) { int ret; @@ -88,32 +87,13 @@ SSecurityTLS::SSecurityTLS(SConnection* sc_, bool _anon) void SSecurityTLS::shutdown() { - if (tlsos) { - try { - if (tlsos->hasBufferedData()) { - tlsos->cork(false); - tlsos->flush(); - if (tlsos->hasBufferedData()) - vlog.error("Failed to flush remaining socket data on close"); - } - } catch (std::exception& e) { - vlog.error("Failed to flush remaining socket data on close: %s", e.what()); - } - } - - if (session) { - int ret; - // FIXME: We can't currently wait for the response, so we only send - // our close and hope for the best - ret = gnutls_bye(session, GNUTLS_SHUT_WR); - if ((ret != GNUTLS_E_SUCCESS) && (ret != GNUTLS_E_INVALID_SESSION)) - vlog.error("TLS shutdown failed: %s", gnutls_strerror(ret)); - } + if (tlssock) + tlssock->shutdown(); #if defined (SSECURITYTLS__USE_DEPRECATED_DH) if (dh_params) { gnutls_dh_params_deinit(dh_params); - dh_params = 0; + dh_params = nullptr; } #endif @@ -133,13 +113,9 @@ void SSecurityTLS::shutdown() rawos = nullptr; } - if (tlsis) { - delete tlsis; - tlsis = nullptr; - } - if (tlsos) { - delete tlsos; - tlsos = nullptr; + if (tlssock) { + delete tlssock; + tlssock = nullptr; } if (session) { @@ -185,56 +161,46 @@ bool SSecurityTLS::processMsg() os->writeU8(1); os->flush(); - // Create these early as they set up the push/pull functions - // for GnuTLS - tlsis = new rdr::TLSInStream(is, session); - tlsos = new rdr::TLSOutStream(os, session); + tlssock = new rdr::TLSSocket(is, os, session); rawis = is; rawos = os; } - err = gnutls_handshake(session); - if (err != GNUTLS_E_SUCCESS) { - if (!gnutls_error_is_fatal(err)) { - vlog.debug("Deferring completion of TLS handshake: %s", gnutls_strerror(err)); + try { + if (!tlssock->handshake()) return false; - } - vlog.error("TLS Handshake failed: %s", gnutls_strerror (err)); + } catch (std::exception&) { shutdown(); - throw rdr::tls_error("TLS Handshake failed", err); + throw; } vlog.debug("TLS handshake completed with %s", gnutls_session_get_desc(session)); - sc->setStreams(tlsis, tlsos); + sc->setStreams(&tlssock->inStream(), &tlssock->outStream()); return true; } void SSecurityTLS::setParams() { - static const char kx_anon_priority[] = ":+ANON-ECDH:+ANON-DH"; + static const char kx_anon_priority[] = "+ANON-ECDH:+ANON-DH"; int ret; // Custom priority string specified? if (strcmp(Security::GnuTLSPriority, "") != 0) { - char *prio; + std::string prio; const char *err; - prio = new char[strlen(Security::GnuTLSPriority) + - strlen(kx_anon_priority) + 1]; - - strcpy(prio, Security::GnuTLSPriority); - if (anon) - strcat(prio, kx_anon_priority); - - ret = gnutls_priority_set_direct(session, prio, &err); - - delete [] prio; + prio = (const char*)Security::GnuTLSPriority; + if (anon) { + prio += ":"; + prio += kx_anon_priority; + } + ret = gnutls_priority_set_direct(session, prio.c_str(), &err); if (ret != GNUTLS_E_SUCCESS) { if (ret == GNUTLS_E_INVALID_REQUEST) vlog.error("GnuTLS priority syntax error at: %s", err); @@ -244,30 +210,22 @@ void SSecurityTLS::setParams() const char *err; #if GNUTLS_VERSION_NUMBER >= 0x030603 - // gnutls_set_default_priority_appends() expects a normal priority string that - // doesn't start with ":". - ret = gnutls_set_default_priority_append(session, kx_anon_priority + 1, &err, 0); + ret = gnutls_set_default_priority_append(session, kx_anon_priority, &err, 0); if (ret != GNUTLS_E_SUCCESS) { if (ret == GNUTLS_E_INVALID_REQUEST) vlog.error("GnuTLS priority syntax error at: %s", err); throw rdr::tls_error("gnutls_set_default_priority_append()", ret); } #else + std::string prio; + // We don't know what the system default priority is, so we guess // it's what upstream GnuTLS has - static const char gnutls_default_priority[] = "NORMAL"; - char *prio; - - prio = new char[strlen(gnutls_default_priority) + - strlen(kx_anon_priority) + 1]; - - strcpy(prio, gnutls_default_priority); - strcat(prio, kx_anon_priority); - - ret = gnutls_priority_set_direct(session, prio, &err); - - delete [] prio; + prio = "NORMAL"; + prio += ":"; + prio += kx_anon_priority; + ret = gnutls_priority_set_direct(session, prio.c_str(), &err); if (ret != GNUTLS_E_SUCCESS) { if (ret == GNUTLS_E_INVALID_REQUEST) vlog.error("GnuTLS priority syntax error at: %s", err); diff --git a/common/rfb/SSecurityTLS.h b/common/rfb/SSecurityTLS.h index 7e80117c..61eec2a8 100644 --- a/common/rfb/SSecurityTLS.h +++ b/common/rfb/SSecurityTLS.h @@ -2,6 +2,7 @@ * Copyright (C) 2004 Red Hat Inc. * Copyright (C) 2005 Martin Koegler * Copyright (C) 2010 TigerVNC Team + * Copyright 2012-2025 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -41,8 +42,7 @@ namespace rdr { class InStream; class OutStream; - class TLSInStream; - class TLSOutStream; + class TLSSocket; } namespace rfb { @@ -72,8 +72,7 @@ namespace rfb { bool anon; - rdr::TLSInStream* tlsis; - rdr::TLSOutStream* tlsos; + rdr::TLSSocket* tlssock; rdr::InStream* rawis; rdr::OutStream* rawos; diff --git a/common/rfb/ServerParams.cxx b/common/rfb/ServerParams.cxx index e4691e19..4b8f6136 100644 --- a/common/rfb/ServerParams.cxx +++ b/common/rfb/ServerParams.cxx @@ -24,6 +24,7 @@ #include <stdexcept> +#include <core/LogWriter.h> #include <core/string.h> #include <rfb/ledStates.h> @@ -33,6 +34,8 @@ using namespace rfb; +static core::LogWriter vlog("ServerParams"); + ServerParams::ServerParams() : majorVersion(0), minorVersion(0), supportsQEMUKeyEvent(false), @@ -67,8 +70,14 @@ void ServerParams::setDimensions(int width, int height) void ServerParams::setDimensions(int width, int height, const ScreenSet& layout) { - if (!layout.validate(width, height)) + if (!layout.validate(width, height)) { + char buffer[2048]; + vlog.debug("Invalid screen layout for %dx%d:", width, height); + layout.print(buffer, sizeof(buffer)); + vlog.debug("%s", buffer); + throw std::invalid_argument("Attempted to configure an invalid screen layout"); + } width_ = width; height_ = height; diff --git a/common/rfb/UnixPasswordValidator.cxx b/common/rfb/UnixPasswordValidator.cxx index 36b8dd7a..8239463a 100644 --- a/common/rfb/UnixPasswordValidator.cxx +++ b/common/rfb/UnixPasswordValidator.cxx @@ -22,24 +22,119 @@ #include <config.h> #endif +#include <assert.h> +#include <string.h> +#include <security/pam_appl.h> + #include <core/Configuration.h> +#include <core/LogWriter.h> #include <rfb/UnixPasswordValidator.h> -#include <rfb/pam.h> using namespace rfb; +static core::LogWriter vlog("UnixPasswordValidator"); + static core::StringParameter pamService ("PAMService", "Service name for PAM password validation", "vnc"); core::AliasParameter pam_service("pam_service", "Alias for PAMService", &pamService); -int do_pam_auth(const char *service, const char *username, - const char *password); +std::string UnixPasswordValidator::displayName; + +typedef struct +{ + const char *username; + const char *password; + std::string &msg; +} AuthData; + +#if defined(__sun) +static int pam_callback(int count, struct pam_message **in, + struct pam_response **out, void *ptr) +#else +static int pam_callback(int count, const struct pam_message **in, + struct pam_response **out, void *ptr) +#endif +{ + int i; + AuthData *auth = (AuthData *) ptr; + struct pam_response *resp = + (struct pam_response *) malloc (sizeof (struct pam_response) * count); + + if (!resp && count) + return PAM_CONV_ERR; + + for (i = 0; i < count; i++) { + resp[i].resp_retcode = PAM_SUCCESS; + switch (in[i]->msg_style) { + case PAM_TEXT_INFO: + vlog.info("%s info: %s", (const char *) pamService, in[i]->msg); + auth->msg = in[i]->msg; + resp[i].resp = nullptr; + break; + case PAM_ERROR_MSG: + vlog.error("%s error: %s", (const char *) pamService, in[i]->msg); + auth->msg = in[i]->msg; + resp[i].resp = nullptr; + break; + case PAM_PROMPT_ECHO_ON: /* Send Username */ + resp[i].resp = strdup(auth->username); + break; + case PAM_PROMPT_ECHO_OFF: /* Send Password */ + resp[i].resp = strdup(auth->password); + break; + default: + free(resp); + return PAM_CONV_ERR; + } + } -bool UnixPasswordValidator::validateInternal(SConnection * /*sc*/, + *out = resp; + return PAM_SUCCESS; +} + +bool UnixPasswordValidator::validateInternal(SConnection * /* sc */, const char *username, - const char *password) + const char *password, + std::string &msg) { - return do_pam_auth(pamService, username, password); + int ret; + AuthData auth = { username, password, msg }; + struct pam_conv conv = { + pam_callback, + &auth + }; + pam_handle_t *pamh = nullptr; + ret = pam_start(pamService, username, &conv, &pamh); + if (ret != PAM_SUCCESS) { + /* Can't call pam_strerror() here because the content of pamh undefined */ + vlog.error("pam_start(%s) failed: %d", (const char *) pamService, ret); + return false; + } +#ifdef PAM_XDISPLAY + /* At this point, displayName should never be empty */ + assert(displayName.length() > 0); + /* Pass the display name to PAM modules but PAM_XDISPLAY may not be + * recognized by modules built with old versions of PAM */ + ret = pam_set_item(pamh, PAM_XDISPLAY, displayName.c_str()); + if (ret != PAM_SUCCESS && ret != PAM_BAD_ITEM) { + vlog.error("pam_set_item(PAM_XDISPLAY) failed: %d (%s)", ret, pam_strerror(pamh, ret)); + goto error; + } +#endif + ret = pam_authenticate(pamh, 0); + if (ret != PAM_SUCCESS) { + vlog.error("pam_authenticate() failed: %d (%s)", ret, pam_strerror(pamh, ret)); + goto error; + } + ret = pam_acct_mgmt(pamh, 0); + if (ret != PAM_SUCCESS) { + vlog.error("pam_acct_mgmt() failed: %d (%s)", ret, pam_strerror(pamh, ret)); + goto error; + } + return true; +error: + pam_end(pamh, ret); + return false; } diff --git a/common/rfb/UnixPasswordValidator.h b/common/rfb/UnixPasswordValidator.h index 4d623d6c..a2cc89c5 100644 --- a/common/rfb/UnixPasswordValidator.h +++ b/common/rfb/UnixPasswordValidator.h @@ -26,9 +26,19 @@ namespace rfb { class UnixPasswordValidator: public PasswordValidator { + public: + static void setDisplayName(const std::string& display) { + displayName = display; + } + protected: - bool validateInternal(SConnection * sc, const char *username, - const char *password) override; + bool validateInternal(SConnection *sc, + const char *username, + const char *password, + std::string &msg) override; + + private: + static std::string displayName; }; } diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index 0b3537ad..2d77fae6 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -314,8 +314,6 @@ void VNCSConnectionST::requestClipboardOrClose() { try { if (state() != RFBSTATE_NORMAL) return; - if (!accessCheck(AccessCutText)) return; - if (!rfb::Server::acceptCutText) return; requestClipboard(); } catch(std::exception& e) { close(e.what()); @@ -326,8 +324,6 @@ void VNCSConnectionST::announceClipboardOrClose(bool available) { try { if (state() != RFBSTATE_NORMAL) return; - if (!accessCheck(AccessCutText)) return; - if (!rfb::Server::sendCutText) return; announceClipboard(available); } catch(std::exception& e) { close(e.what()); @@ -338,8 +334,6 @@ void VNCSConnectionST::sendClipboardDataOrClose(const char* data) { try { if (state() != RFBSTATE_NORMAL) return; - if (!accessCheck(AccessCutText)) return; - if (!rfb::Server::sendCutText) return; sendClipboardData(data); } catch(std::exception& e) { close(e.what()); @@ -467,6 +461,7 @@ void VNCSConnectionST::setPixelFormat(const PixelFormat& pf) pf.print(buffer, 256); vlog.info("Client pixel format %s", buffer); setCursor(); + encodeManager.forceRefresh(server->getPixelBuffer()->getRect()); } void VNCSConnectionST::pointerEvent(const core::Point& pos, @@ -476,7 +471,6 @@ void VNCSConnectionST::pointerEvent(const core::Point& pos, idleTimer.start(core::secsToMillis(rfb::Server::idleTimeout)); pointerEventTime = time(nullptr); if (!accessCheck(AccessPtrEvents)) return; - if (!rfb::Server::acceptPointerEvents) return; pointerEventPos = pos; server->pointerEvent(this, pointerEventPos, buttonMask); } @@ -509,6 +503,8 @@ void VNCSConnectionST::keyEvent(uint32_t keysym, uint32_t keycode, bool down) { if (rfb::Server::idleTimeout) idleTimer.start(core::secsToMillis(rfb::Server::idleTimeout)); if (!accessCheck(AccessKeyEvents)) return; + // FIXME: This check isn't strictly needed, but we get a lot of + // confusing debug logging without it if (!rfb::Server::acceptKeyEvents) return; if (down) @@ -662,8 +658,7 @@ void VNCSConnectionST::setDesktopSize(int fb_width, int fb_height, layout.print(buffer, sizeof(buffer)); vlog.debug("%s", buffer); - if (!accessCheck(AccessSetDesktopSize) || - !rfb::Server::acceptSetDesktopSize) { + if (!accessCheck(AccessSetDesktopSize)) { vlog.debug("Rejecting unauthorized framebuffer resize request"); result = resultProhibited; } else { @@ -744,21 +739,16 @@ void VNCSConnectionST::enableContinuousUpdates(bool enable, void VNCSConnectionST::handleClipboardRequest() { - if (!accessCheck(AccessCutText)) return; server->handleClipboardRequest(this); } void VNCSConnectionST::handleClipboardAnnounce(bool available) { - if (!accessCheck(AccessCutText)) return; - if (!rfb::Server::acceptCutText) return; server->handleClipboardAnnounce(this, available); } void VNCSConnectionST::handleClipboardData(const char* data) { - if (!accessCheck(AccessCutText)) return; - if (!rfb::Server::acceptCutText) return; server->handleClipboardData(this, data); } diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index cce04164..77d652b9 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -364,6 +364,9 @@ void VNCServerST::setScreenLayout(const ScreenSet& layout) void VNCServerST::requestClipboard() { + if (!rfb::Server::acceptCutText) + return; + if (clipboardClient == nullptr) { slog.debug("Got request for client clipboard but no client currently owns the clipboard"); return; @@ -378,6 +381,9 @@ void VNCServerST::announceClipboard(bool available) clipboardRequestors.clear(); + if (!rfb::Server::sendCutText) + return; + for (ci = clients.begin(); ci != clients.end(); ++ci) (*ci)->announceClipboardOrClose(available); } @@ -386,6 +392,9 @@ void VNCServerST::sendClipboardData(const char* data) { std::list<VNCSConnectionST*>::iterator ci; + if (!rfb::Server::sendCutText) + return; + if (strchr(data, '\r') != nullptr) throw std::invalid_argument("Invalid carriage return in clipboard data"); @@ -478,6 +487,9 @@ void VNCServerST::setLEDState(unsigned int state) void VNCServerST::keyEvent(uint32_t keysym, uint32_t keycode, bool down) { + if (!rfb::Server::acceptKeyEvents) + return; + if (rfb::Server::maxIdleTime) idleTimer.start(core::secsToMillis(rfb::Server::maxIdleTime)); @@ -500,6 +512,10 @@ void VNCServerST::pointerEvent(VNCSConnectionST* client, uint16_t buttonMask) { time_t now = time(nullptr); + + if (!rfb::Server::acceptPointerEvents) + return; + if (rfb::Server::maxIdleTime) idleTimer.start(core::secsToMillis(rfb::Server::maxIdleTime)); @@ -529,9 +545,11 @@ void VNCServerST::handleClipboardRequest(VNCSConnectionST* client) void VNCServerST::handleClipboardAnnounce(VNCSConnectionST* client, bool available) { - if (available) + if (available) { + if (!rfb::Server::acceptCutText) + return; clipboardClient = client; - else { + } else { if (client != clipboardClient) return; clipboardClient = nullptr; @@ -542,6 +560,8 @@ void VNCServerST::handleClipboardAnnounce(VNCSConnectionST* client, void VNCServerST::handleClipboardData(VNCSConnectionST* client, const char* data) { + if (!rfb::Server::acceptCutText) + return; if (client != clipboardClient) { slog.debug("Ignoring unexpected clipboard data"); return; @@ -556,6 +576,11 @@ unsigned int VNCServerST::setDesktopSize(VNCSConnectionST* requester, unsigned int result; std::list<VNCSConnectionST*>::iterator ci; + if (!rfb::Server::acceptSetDesktopSize) { + slog.debug("Rejecting unauthorized framebuffer resize request"); + return resultProhibited; + } + // We can't handle a framebuffer larger than this, so don't let a // client set one (see PixelBuffer.cxx) if ((fb_width > 16384) || (fb_height > 16384)) { diff --git a/common/rfb/WinPasswdValidator.cxx b/common/rfb/WinPasswdValidator.cxx index 84832e81..a6281950 100644 --- a/common/rfb/WinPasswdValidator.cxx +++ b/common/rfb/WinPasswdValidator.cxx @@ -30,7 +30,8 @@ using namespace rfb; // This method will only work for Windows NT, 2000, and XP (and possibly Vista) bool WinPasswdValidator::validateInternal(rfb::SConnection* /*sc*/, const char* username, - const char* password) + const char* password, + std::string & /* msg */) { HANDLE handle; diff --git a/common/rfb/WinPasswdValidator.h b/common/rfb/WinPasswdValidator.h index 340a6234..993cafea 100644 --- a/common/rfb/WinPasswdValidator.h +++ b/common/rfb/WinPasswdValidator.h @@ -21,6 +21,7 @@ #ifndef __RFB_WINPASSWDVALIDATOR_H__ #define __RFB_WINPASSWDVALIDATOR_H__ +#include <string> #include <rfb/SSecurityPlain.h> namespace rfb @@ -30,7 +31,10 @@ namespace rfb WinPasswdValidator() {}; virtual ~WinPasswdValidator() {}; protected: - bool validateInternal(SConnection *sc, const char* username, const char* password) override; + bool validateInternal(SConnection *sc, + const char *username, + const char *password, + std::string &msg) override; }; } diff --git a/common/rfb/pam.c b/common/rfb/pam.c deleted file mode 100644 index f9e5ce74..00000000 --- a/common/rfb/pam.c +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2006 Martin Koegler - * Copyright (C) 2010 TigerVNC Team - * - * This 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 software 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 software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <stdlib.h> -#include <string.h> -#include <security/pam_appl.h> - -#include <rfb/pam.h> - -typedef struct -{ - const char *username; - const char *password; -} AuthData; - -#if defined(__sun) -static int pam_callback(int count, struct pam_message **in, - struct pam_response **out, void *ptr) -#else -static int pam_callback(int count, const struct pam_message **in, - struct pam_response **out, void *ptr) -#endif -{ - int i; - AuthData *auth = (AuthData *) ptr; - struct pam_response *resp = - (struct pam_response *) malloc (sizeof (struct pam_response) * count); - - if (!resp && count) - return PAM_CONV_ERR; - - for (i = 0; i < count; i++) { - resp[i].resp_retcode = PAM_SUCCESS; - switch (in[i]->msg_style) { - case PAM_TEXT_INFO: - case PAM_ERROR_MSG: - resp[i].resp = 0; - break; - case PAM_PROMPT_ECHO_ON: /* Send Username */ - resp[i].resp = strdup(auth->username); - break; - case PAM_PROMPT_ECHO_OFF: /* Send Password */ - resp[i].resp = strdup(auth->password); - break; - default: - free(resp); - return PAM_CONV_ERR; - } - } - - *out = resp; - return PAM_SUCCESS; -} - - -int do_pam_auth(const char *service, const char *username, const char *password) -{ - int ret; - AuthData auth = { username, password }; - struct pam_conv conv = { - pam_callback, - &auth - }; - pam_handle_t *h = 0; - ret = pam_start(service, username, &conv, &h); - if (ret == PAM_SUCCESS) - ret = pam_authenticate(h, 0); - if (ret == PAM_SUCCESS) - ret = pam_acct_mgmt(h, 0); - pam_end(h, ret); - - return ret == PAM_SUCCESS ? 1 : 0; -} - diff --git a/common/rfb/pam.h b/common/rfb/pam.h deleted file mode 100644 index d378d19c..00000000 --- a/common/rfb/pam.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2006 Martin Koegler - * Copyright (C) 2010 TigerVNC Team - * - * This 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 software 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 software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -#ifndef __RFB_PAM_H__ -#define __RFB_PAM_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -int do_pam_auth(const char *service, const char *username, const char *password); - -#ifdef __cplusplus -} -#endif - -#endif |