tigervnc/rfb/VNCServerST.cxx
Constantin Kaplinsky 47ed8d321c Initial revision
git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@2 3789f03b-4d11-0410-bbf8-ca57d06f2519
2004-10-08 09:43:57 +00:00

431 lines
13 KiB
C++

/* Copyright (C) 2002-2004 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().
//
// Because we don't want to mess up any data structures which the calling code
// may maintain regarding sockets with data to process, we can't just delete a
// socket when we decide to close the connection to a client. Instead, we only
// go as far as calling shutdown() on the socket. This should ensure that
// eventually the calling code will get round to calling processSocketEvent()
// for that socket. Then we can delete the VNCSConnectionST object and its
// associated network::Socket object, and return false from that call to let
// the calling code know that the socket has been deleted. This is the only
// way that these objects get deleted.
//
// It is possible that there are platforms where calling shutdown() cannot
// guarantee that processSocketEvent() will be called - if so then it may be
// necessary to introduce some kind of "socket closure callback", but we'll
// only do that if it proves absolutely necessary.
//
// 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). So we maintain a separate list of
// closingSockets for this purpose.
#include <rfb/ServerCore.h>
#include <rfb/VNCServerST.h>
#include <rfb/VNCSConnectionST.h>
#include <rfb/ComparingUpdateTracker.h>
#include <rfb/SSecurityFactoryStandard.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),
name(strDup(name_)), pointerClient(0), comparer(0),
renderedCursorInvalid(false),
securityFactory(sf ? sf : &defaultSecurityFactory),
queryConnectionHandler(0), useEconomicTranslate(false)
{
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()->getSock();
delete clients.front();
}
while (!closingSockets.empty()) {
delete closingSockets.front();
closingSockets.pop_front();
}
// Stop the desktop object if active, *only* after deleting all clients!
if (desktopStarted) {
desktopStarted = false;
desktop->stop();
}
delete comparer;
}
// SocketServer methods
void VNCServerST::addClient(network::Socket* sock)
{
addClient(sock, false);
}
void VNCServerST::addClient(network::Socket* sock, bool reverse)
{
// - Check the connection isn't black-marked
// *** do this in getSecurity instead?
CharArray address(sock->getPeerAddress());
if ((rfb::Server::blacklistLevel == 2) &&
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;
}
VNCSConnectionST* client = new VNCSConnectionST(this, sock, reverse);
client->init();
}
bool 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) {
if ((*ci)->processMessages())
return true;
// processMessages failed, so delete the client
delete *ci;
break;
}
}
// - If no client is using the Socket then delete it
closingSockets.remove(sock);
delete sock;
// - Check that the desktop object is still required
if (authClientCount() == 0 && desktopStarted) {
slog.debug("no authenticated clients - stopping desktop");
desktopStarted = false;
desktop->stop();
}
// - Inform the caller not to continue handling the Socket
return false;
}
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());
}
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, int newHotspotX,
int newHotspotY, void* data, void* mask)
{
cursor.hotspot.x = newHotspotX;
cursor.hotspot.y = newHotspotY;
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(int x, int y)
{
if (cursorPos.x != x || cursorPos.y != y) {
cursorPos.x = x;
cursorPos.y = y;
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();
}