summaryrefslogtreecommitdiffstats
path: root/common/rfb/VNCServerST.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'common/rfb/VNCServerST.cxx')
-rw-r--r--common/rfb/VNCServerST.cxx531
1 files changed, 531 insertions, 0 deletions
diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx
new file mode 100644
index 00000000..cc18faa3
--- /dev/null
+++ b/common/rfb/VNCServerST.cxx
@@ -0,0 +1,531 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * 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.
+ */
+
+// -=- Single-Threaded VNC Server implementation
+
+
+// Note about how sockets get closed:
+//
+// Closing sockets to clients is non-trivial because the code which calls
+// VNCServerST must explicitly know about all the sockets (so that it can block
+// on them appropriately). However, VNCServerST may want to close clients for
+// a number of reasons, and from a variety of entry points. The simplest is
+// when processSocketEvent() is called for a client, and the remote end has
+// closed its socket. A more complex reason is when processSocketEvent() is
+// called for a client which has just sent a ClientInit with the shared flag
+// set to false - in this case we want to close all other clients. Yet another
+// reason for disconnecting clients is when the desktop size has changed as a
+// result of a call to setPixelBuffer().
+//
+// The responsibility for creating and deleting sockets is entirely with the
+// calling code. When VNCServerST wants to close a connection to a client it
+// calls the VNCSConnectionST's close() method which calls shutdown() on the
+// socket. Eventually the calling code will notice that the socket has been
+// shut down and call removeSocket() so that we can delete the
+// VNCSConnectionST. Note that the socket must not be deleted by the calling
+// code until after removeSocket() has been called.
+//
+// One minor complication is that we don't allocate a VNCSConnectionST object
+// for a blacklisted host (since we want to minimise the resources used for
+// dealing with such a connection). In order to properly implement the
+// getSockets function, we must maintain a separate closingSockets list,
+// otherwise blacklisted connections might be "forgotten".
+
+
+#include <rfb/ServerCore.h>
+#include <rfb/VNCServerST.h>
+#include <rfb/VNCSConnectionST.h>
+#include <rfb/ComparingUpdateTracker.h>
+#include <rfb/SSecurityFactoryStandard.h>
+#include <rfb/KeyRemapper.h>
+#include <rfb/util.h>
+
+#include <rdr/types.h>
+
+using namespace rfb;
+
+static LogWriter slog("VNCServerST");
+LogWriter VNCServerST::connectionsLog("Connections");
+static SSecurityFactoryStandard defaultSecurityFactory;
+
+//
+// -=- VNCServerST Implementation
+//
+
+// -=- Constructors/Destructor
+
+VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_,
+ SSecurityFactory* sf)
+ : blHosts(&blacklist), desktop(desktop_), desktopStarted(false), pb(0),
+ m_pFTManager(0), name(strDup(name_)), pointerClient(0), comparer(0),
+ renderedCursorInvalid(false),
+ securityFactory(sf ? sf : &defaultSecurityFactory),
+ queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance),
+ useEconomicTranslate(false),
+ lastConnectionTime(0), disableclients(false)
+{
+ lastUserInputTime = lastDisconnectTime = time(0);
+ slog.debug("creating single-threaded server %s", name.buf);
+}
+
+VNCServerST::~VNCServerST()
+{
+ slog.debug("shutting down server %s", name.buf);
+
+ // Close any active clients, with appropriate logging & cleanup
+ closeClients("Server shutdown");
+
+ // Delete all the clients, and their sockets, and any closing sockets
+ // NB: Deleting a client implicitly removes it from the clients list
+ while (!clients.empty()) {
+ delete clients.front();
+ }
+
+ // Stop the desktop object if active, *only* after deleting all clients!
+ if (desktopStarted) {
+ desktopStarted = false;
+ desktop->stop();
+ }
+
+ delete comparer;
+}
+
+
+// SocketServer methods
+
+void VNCServerST::addSocket(network::Socket* sock, bool outgoing)
+{
+ // - Check the connection isn't black-marked
+ // *** do this in getSecurity instead?
+ CharArray address(sock->getPeerAddress());
+ if (blHosts->isBlackmarked(address.buf)) {
+ connectionsLog.error("blacklisted: %s", address.buf);
+ try {
+ SConnection::writeConnFailedFromScratch("Too many security failures",
+ &sock->outStream());
+ } catch (rdr::Exception&) {
+ }
+ sock->shutdown();
+ closingSockets.push_back(sock);
+ return;
+ }
+
+ if (clients.empty()) {
+ lastConnectionTime = time(0);
+ }
+
+ VNCSConnectionST* client = new VNCSConnectionST(this, sock, outgoing);
+ client->init();
+}
+
+void VNCServerST::removeSocket(network::Socket* sock) {
+ // - If the socket has resources allocated to it, delete them
+ std::list<VNCSConnectionST*>::iterator ci;
+ for (ci = clients.begin(); ci != clients.end(); ci++) {
+ if ((*ci)->getSock() == sock) {
+ // - Delete the per-Socket resources
+ delete *ci;
+
+ // - Check that the desktop object is still required
+ if (authClientCount() == 0 && desktopStarted) {
+ slog.debug("no authenticated clients - stopping desktop");
+ desktopStarted = false;
+ desktop->stop();
+ }
+ return;
+ }
+ }
+
+ // - If the Socket has no resources, it may have been a closingSocket
+ closingSockets.remove(sock);
+}
+
+void VNCServerST::processSocketEvent(network::Socket* sock)
+{
+ // - Find the appropriate VNCSConnectionST and process the event
+ std::list<VNCSConnectionST*>::iterator ci;
+ for (ci = clients.begin(); ci != clients.end(); ci++) {
+ if ((*ci)->getSock() == sock) {
+ (*ci)->processMessages();
+ return;
+ }
+ }
+ throw rdr::Exception("invalid Socket in VNCServerST");
+}
+
+int VNCServerST::checkTimeouts()
+{
+ int timeout = 0;
+ std::list<VNCSConnectionST*>::iterator ci, ci_next;
+ for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
+ ci_next = ci; ci_next++;
+ soonestTimeout(&timeout, (*ci)->checkIdleTimeout());
+ }
+
+ int timeLeft;
+ time_t now;
+
+ // Optimization: Only call time() if using any maxTime.
+ if (rfb::Server::maxDisconnectionTime || rfb::Server::maxConnectionTime || rfb::Server::maxIdleTime) {
+ now = time(0);
+ }
+
+ // Check MaxDisconnectionTime
+ if (rfb::Server::maxDisconnectionTime && clients.empty()) {
+ if (now < lastDisconnectTime) {
+ // Someone must have set the time backwards.
+ slog.info("Time has gone backwards - resetting lastDisconnectTime");
+ lastDisconnectTime = now;
+ }
+ timeLeft = lastDisconnectTime + rfb::Server::maxDisconnectionTime - now;
+ if (timeLeft < -60) {
+ // Someone must have set the time forwards.
+ slog.info("Time has gone forwards - resetting lastDisconnectTime");
+ lastDisconnectTime = now;
+ timeLeft = rfb::Server::maxDisconnectionTime;
+ }
+ if (timeLeft <= 0) {
+ slog.info("MaxDisconnectionTime reached, exiting");
+ exit(0);
+ }
+ soonestTimeout(&timeout, timeLeft * 1000);
+ }
+
+ // Check MaxConnectionTime
+ if (rfb::Server::maxConnectionTime && lastConnectionTime && !clients.empty()) {
+ if (now < lastConnectionTime) {
+ // Someone must have set the time backwards.
+ slog.info("Time has gone backwards - resetting lastConnectionTime");
+ lastConnectionTime = now;
+ }
+ timeLeft = lastConnectionTime + rfb::Server::maxConnectionTime - now;
+ if (timeLeft < -60) {
+ // Someone must have set the time forwards.
+ slog.info("Time has gone forwards - resetting lastConnectionTime");
+ lastConnectionTime = now;
+ timeLeft = rfb::Server::maxConnectionTime;
+ }
+ if (timeLeft <= 0) {
+ slog.info("MaxConnectionTime reached, exiting");
+ exit(0);
+ }
+ soonestTimeout(&timeout, timeLeft * 1000);
+ }
+
+
+ // Check MaxIdleTime
+ if (rfb::Server::maxIdleTime) {
+ if (now < lastUserInputTime) {
+ // Someone must have set the time backwards.
+ slog.info("Time has gone backwards - resetting lastUserInputTime");
+ lastUserInputTime = now;
+ }
+ timeLeft = lastUserInputTime + rfb::Server::maxIdleTime - now;
+ if (timeLeft < -60) {
+ // Someone must have set the time forwards.
+ slog.info("Time has gone forwards - resetting lastUserInputTime");
+ lastUserInputTime = now;
+ timeLeft = rfb::Server::maxIdleTime;
+ }
+ if (timeLeft <= 0) {
+ slog.info("MaxIdleTime reached, exiting");
+ exit(0);
+ }
+ soonestTimeout(&timeout, timeLeft * 1000);
+ }
+
+ return timeout;
+}
+
+
+// VNCServer methods
+
+void VNCServerST::setPixelBuffer(PixelBuffer* pb_)
+{
+ pb = pb_;
+ delete comparer;
+ comparer = 0;
+
+ if (pb) {
+ comparer = new ComparingUpdateTracker(pb);
+ cursor.setPF(pb->getPF());
+ renderedCursor.setPF(pb->getPF());
+
+ std::list<VNCSConnectionST*>::iterator ci, ci_next;
+ for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
+ ci_next = ci; ci_next++;
+ (*ci)->pixelBufferChange();
+ }
+ } else {
+ if (desktopStarted)
+ throw Exception("setPixelBuffer: null PixelBuffer when desktopStarted?");
+ }
+}
+
+void VNCServerST::setColourMapEntries(int firstColour, int nColours)
+{
+ std::list<VNCSConnectionST*>::iterator ci, ci_next;
+ for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
+ ci_next = ci; ci_next++;
+ (*ci)->setColourMapEntriesOrClose(firstColour, nColours);
+ }
+}
+
+void VNCServerST::bell()
+{
+ std::list<VNCSConnectionST*>::iterator ci, ci_next;
+ for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
+ ci_next = ci; ci_next++;
+ (*ci)->bell();
+ }
+}
+
+void VNCServerST::serverCutText(const char* str, int len)
+{
+ std::list<VNCSConnectionST*>::iterator ci, ci_next;
+ for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
+ ci_next = ci; ci_next++;
+ (*ci)->serverCutText(str, len);
+ }
+}
+
+void VNCServerST::add_changed(const Region& region)
+{
+ comparer->add_changed(region);
+}
+
+void VNCServerST::add_copied(const Region& dest, const Point& delta)
+{
+ comparer->add_copied(dest, delta);
+}
+
+bool VNCServerST::clientsReadyForUpdate()
+{
+ std::list<VNCSConnectionST*>::iterator ci;
+ for (ci = clients.begin(); ci != clients.end(); ci++) {
+ if ((*ci)->readyForUpdate())
+ return true;
+ }
+ return false;
+}
+
+void VNCServerST::tryUpdate()
+{
+ std::list<VNCSConnectionST*>::iterator ci, ci_next;
+ for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
+ ci_next = ci; ci_next++;
+ (*ci)->writeFramebufferUpdateOrClose();
+ }
+}
+
+void VNCServerST::setCursor(int width, int height, const Point& newHotspot,
+ void* data, void* mask)
+{
+ cursor.hotspot = newHotspot;
+ cursor.setSize(width, height);
+ memcpy(cursor.data, data, cursor.dataLen());
+ memcpy(cursor.mask.buf, mask, cursor.maskLen());
+
+ cursor.crop();
+
+ renderedCursorInvalid = true;
+
+ std::list<VNCSConnectionST*>::iterator ci, ci_next;
+ for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
+ ci_next = ci; ci_next++;
+ (*ci)->renderedCursorChange();
+ (*ci)->setCursorOrClose();
+ }
+}
+
+void VNCServerST::setCursorPos(const Point& pos)
+{
+ if (!cursorPos.equals(pos)) {
+ cursorPos = pos;
+ renderedCursorInvalid = true;
+ std::list<VNCSConnectionST*>::iterator ci;
+ for (ci = clients.begin(); ci != clients.end(); ci++)
+ (*ci)->renderedCursorChange();
+ }
+}
+
+// Other public methods
+
+void VNCServerST::approveConnection(network::Socket* sock, bool accept,
+ const char* reason)
+{
+ std::list<VNCSConnectionST*>::iterator ci;
+ for (ci = clients.begin(); ci != clients.end(); ci++) {
+ if ((*ci)->getSock() == sock) {
+ (*ci)->approveConnectionOrClose(accept, reason);
+ return;
+ }
+ }
+}
+
+void VNCServerST::closeClients(const char* reason, network::Socket* except)
+{
+ std::list<VNCSConnectionST*>::iterator i, next_i;
+ for (i=clients.begin(); i!=clients.end(); i=next_i) {
+ next_i = i; next_i++;
+ if ((*i)->getSock() != except)
+ (*i)->close(reason);
+ }
+}
+
+void VNCServerST::getSockets(std::list<network::Socket*>* sockets)
+{
+ sockets->clear();
+ std::list<VNCSConnectionST*>::iterator ci;
+ for (ci = clients.begin(); ci != clients.end(); ci++) {
+ sockets->push_back((*ci)->getSock());
+ }
+ std::list<network::Socket*>::iterator si;
+ for (si = closingSockets.begin(); si != closingSockets.end(); si++) {
+ sockets->push_back(*si);
+ }
+}
+
+SConnection* VNCServerST::getSConnection(network::Socket* sock) {
+ std::list<VNCSConnectionST*>::iterator ci;
+ for (ci = clients.begin(); ci != clients.end(); ci++) {
+ if ((*ci)->getSock() == sock)
+ return *ci;
+ }
+ return 0;
+}
+
+
+// -=- Internal methods
+
+void VNCServerST::startDesktop()
+{
+ if (!desktopStarted) {
+ slog.debug("starting desktop");
+ desktop->start(this);
+ desktopStarted = true;
+ if (!pb)
+ throw Exception("SDesktop::start() did not set a valid PixelBuffer");
+ }
+}
+
+int VNCServerST::authClientCount() {
+ int count = 0;
+ std::list<VNCSConnectionST*>::iterator ci;
+ for (ci = clients.begin(); ci != clients.end(); ci++) {
+ if ((*ci)->authenticated())
+ count++;
+ }
+ return count;
+}
+
+inline bool VNCServerST::needRenderedCursor()
+{
+ std::list<VNCSConnectionST*>::iterator ci;
+ for (ci = clients.begin(); ci != clients.end(); ci++)
+ if ((*ci)->needRenderedCursor()) return true;
+ return false;
+}
+
+// checkUpdate() is called just before sending an update. It checks to see
+// what updates are pending and propagates them to the update tracker for each
+// client. It uses the ComparingUpdateTracker's compare() method to filter out
+// areas of the screen which haven't actually changed. It also checks the
+// state of the (server-side) rendered cursor, if necessary rendering it again
+// with the correct background.
+
+void VNCServerST::checkUpdate()
+{
+ bool renderCursor = needRenderedCursor();
+
+ if (comparer->is_empty() && !(renderCursor && renderedCursorInvalid))
+ return;
+
+ Region toCheck = comparer->get_changed().union_(comparer->get_copied());
+
+ if (renderCursor) {
+ Rect clippedCursorRect
+ = cursor.getRect(cursorTL()).intersect(pb->getRect());
+
+ if (!renderedCursorInvalid && (toCheck.intersect(clippedCursorRect)
+ .is_empty())) {
+ renderCursor = false;
+ } else {
+ renderedCursorTL = clippedCursorRect.tl;
+ renderedCursor.setSize(clippedCursorRect.width(),
+ clippedCursorRect.height());
+ toCheck.assign_union(clippedCursorRect);
+ }
+ }
+
+ pb->grabRegion(toCheck);
+
+ if (rfb::Server::compareFB)
+ comparer->compare();
+
+ if (renderCursor) {
+ pb->getImage(renderedCursor.data,
+ renderedCursor.getRect(renderedCursorTL));
+ renderedCursor.maskRect(cursor.getRect(cursorTL()
+ .subtract(renderedCursorTL)),
+ cursor.data, cursor.mask.buf);
+ renderedCursorInvalid = false;
+ }
+
+ std::list<VNCSConnectionST*>::iterator ci, ci_next;
+ for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
+ ci_next = ci; ci_next++;
+ (*ci)->add_copied(comparer->get_copied(), comparer->get_delta());
+ (*ci)->add_changed(comparer->get_changed());
+ }
+
+ comparer->clear();
+}
+
+void VNCServerST::getConnInfo(ListConnInfo * listConn)
+{
+ listConn->Clear();
+ listConn->setDisable(getDisable());
+ if (clients.empty())
+ return;
+ std::list<VNCSConnectionST*>::iterator i;
+ for (i = clients.begin(); i != clients.end(); i++)
+ listConn->addInfo((void*)(*i), (*i)->getSock()->getPeerAddress(),
+ (*i)->getStartTime(), (*i)->getStatus());
+}
+
+void VNCServerST::setConnStatus(ListConnInfo* listConn)
+{
+ setDisable(listConn->getDisable());
+ if (listConn->Empty() || clients.empty()) return;
+ for (listConn->iBegin(); !listConn->iEnd(); listConn->iNext()) {
+ VNCSConnectionST* conn = (VNCSConnectionST*)listConn->iGetConn();
+ std::list<VNCSConnectionST*>::iterator i;
+ for (i = clients.begin(); i != clients.end(); i++) {
+ if ((*i) == conn) {
+ int status = listConn->iGetStatus();
+ if (status == 3) {
+ (*i)->close(0);
+ } else {
+ (*i)->setStatus(status);
+ }
+ break;
+ }
+ }
+ }
+}