mirror of
https://github.com/TigerVNC/tigervnc.git
synced 2024-08-21 17:54:34 +02:00
![Constantin Kaplinsky](/assets/img/avatar_default.png)
git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@2 3789f03b-4d11-0410-bbf8-ca57d06f2519
431 lines
13 KiB
C++
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();
|
|
}
|