diff options
Diffstat (limited to 'common/rfb/VNCServerST.cxx')
-rw-r--r-- | common/rfb/VNCServerST.cxx | 531 |
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; + } + } + } +} |