KasmVNC/common/rfb/VNCServerST.cxx

1276 lines
36 KiB
C++

/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright 2009-2018 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.
*/
// -=- 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 <assert.h>
#include <stdlib.h>
#include <network/GetAPI.h>
#include <network/Udp.h>
#include <rfb/cpuid.h>
#include <rfb/ComparingUpdateTracker.h>
#include <rfb/KeyRemapper.h>
#include <rfb/ListConnInfo.h>
#include <rfb/Security.h>
#include <rfb/ServerCore.h>
#include <rfb/VNCServerST.h>
#include <rfb/VNCSConnectionST.h>
#include <rfb/Watermark.h>
#include <rfb/util.h>
#include <rfb/ledStates.h>
#include <rdr/types.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <wordexp.h>
using namespace rfb;
static LogWriter slog("VNCServerST");
LogWriter VNCServerST::connectionsLog("Connections");
EncCache VNCServerST::encCache;
void SelfBench();
//
// -=- VNCServerST Implementation
//
static char kasmpasswdpath[4096];
// -=- Constructors/Destructor
static void mixedPercentages() {
slog.error("Mixing percentages and absolute values in DLP_Region is not allowed");
exit(1);
}
static void parseRegionPart(const bool percents, rdr::U16 &pcdest, int &dest,
char **inptr) {
char *nextptr, *ptr;
ptr = *inptr;
int val = strtol(ptr, &nextptr, 10);
if (!*ptr || ptr == nextptr) {
slog.error("Invalid value for DLP_Region");
exit(1);
}
ptr = nextptr;
if (*ptr == '%') {
if (!percents)
mixedPercentages();
pcdest = val;
if (val < 0 || val > 100) {
slog.error("Percent must be 0-100");
exit(1);
}
ptr++;
} else if (percents) {
mixedPercentages();
}
dest = val;
for (; *ptr && *ptr == ','; ptr++);
*inptr = ptr;
}
VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_)
: blHosts(&blacklist), desktop(desktop_), desktopStarted(false),
blockCounter(0), pb(0), blackedpb(0), ledState(ledUnknown),
name(strDup(name_)), pointerClient(0), clipboardClient(0),
comparer(0), cursor(new Cursor(0, 0, Point(), NULL)),
renderedCursorInvalid(false),
queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance),
lastConnectionTime(0), disableclients(false),
frameTimer(this), apimessager(NULL), trackingFrameStats(0),
clipboardId(0), sendWatermark(false)
{
lastUserInputTime = lastDisconnectTime = time(0);
slog.debug("creating single-threaded server %s", name.buf);
slog.info("CPU capability: SSE2 %s, AVX512f %s",
supportsSSE2() ? "yes" : "no",
supportsAVX512f() ? "yes" : "no");
DLPRegion.enabled = DLPRegion.percents = false;
if (Server::DLP_Region[0]) {
unsigned len = strlen(Server::DLP_Region);
unsigned i;
unsigned commas = 0;
int val;
char *ptr, *nextptr;
for (i = 0; i < len; i++) {
if (Server::DLP_Region[i] == ',')
commas++;
}
if (commas != 3) {
slog.error("DLP_Region must contain four values");
exit(1);
}
ptr = (char *) (const char *) Server::DLP_Region;
val = strtol(ptr, &nextptr, 10);
if (!*ptr || ptr == nextptr) {
slog.error("Invalid value for DLP_Region");
exit(1);
}
ptr = nextptr;
if (*ptr == '%') {
DLPRegion.percents = true;
DLPRegion.pcx1 = val;
ptr++;
}
DLPRegion.x1 = val;
for (; *ptr && *ptr == ','; ptr++);
parseRegionPart(DLPRegion.percents, DLPRegion.pcy1, DLPRegion.y1,
&ptr);
parseRegionPart(DLPRegion.percents, DLPRegion.pcx2, DLPRegion.x2,
&ptr);
parseRegionPart(DLPRegion.percents, DLPRegion.pcy2, DLPRegion.y2,
&ptr);
// Validity checks
if (!DLPRegion.percents) {
if (DLPRegion.x1 > 0 && DLPRegion.x2 > 0 && DLPRegion.x2 <= DLPRegion.x1) {
slog.error("DLP_Region x2 must be > x1");
exit(1);
}
if (DLPRegion.y1 > 0 && DLPRegion.y2 > 0 && DLPRegion.y2 <= DLPRegion.y1) {
slog.error("DLP_Region y2 must be > y1");
exit(1);
}
}
DLPRegion.enabled = 1;
}
kasmpasswdpath[0] = '\0';
wordexp_t wexp;
if (!wordexp(rfb::Server::kasmPasswordFile, &wexp, WRDE_NOCMD))
strncpy(kasmpasswdpath, wexp.we_wordv[0], 4096);
kasmpasswdpath[4095] = '\0';
wordfree(&wexp);
if (kasmpasswdpath[0] && access(kasmpasswdpath, R_OK) == 0) {
// Set up a watch on the password file
inotifyfd = inotify_init();
if (inotifyfd < 0)
slog.error("Failed to init inotify");
int flags = fcntl(inotifyfd, F_GETFL, 0);
fcntl(inotifyfd, F_SETFL, flags | O_NONBLOCK);
if (inotify_add_watch(inotifyfd, kasmpasswdpath, IN_CLOSE_WRITE | IN_DELETE_SELF) < 0)
slog.error("Failed to set watch");
}
trackingClient[0] = 0;
if (watermarkData)
sendWatermark = true;
if (Server::selfBench)
SelfBench();
}
VNCServerST::~VNCServerST()
{
slog.debug("shutting down server %s", name.buf);
// Close any active clients, with appropriate logging & cleanup
closeClients("Server shutdown");
// Stop trying to render things
stopFrameClock();
// 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!
stopDesktop();
if (comparer)
comparer->logStats();
delete comparer;
delete cursor;
}
// 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();
if (watermarkData)
sendWatermark = true;
}
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) {
if (clipboardClient == *ci)
handleClipboardAnnounce(*ci, false);
clipboardRequestors.remove(*ci);
// - Delete the per-Socket resources
delete *ci;
// - Check that the desktop object is still required
if (authClientCount() == 0)
stopDesktop();
if (comparer)
comparer->logStats();
return;
}
}
// - If the Socket has no resources, it may have been a closingSocket
closingSockets.remove(sock);
}
void VNCServerST::processSocketReadEvent(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");
}
void VNCServerST::processSocketWriteEvent(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)->flushSocket();
return;
}
}
throw rdr::Exception("invalid Socket in VNCServerST");
}
int VNCServerST::checkTimeouts()
{
int timeout = 0;
std::list<VNCSConnectionST*>::iterator ci, ci_next;
soonestTimeout(&timeout, Timer::checkTimeouts());
for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
ci_next = ci; ci_next++;
soonestTimeout(&timeout, (*ci)->checkIdleTimeout());
}
int timeLeft;
time_t 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::blockUpdates()
{
blockCounter++;
stopFrameClock();
}
void VNCServerST::unblockUpdates()
{
assert(blockCounter > 0);
blockCounter--;
// Restart the frame clock if we have updates
if (blockCounter == 0) {
if (!comparer->is_empty())
startFrameClock();
}
}
void VNCServerST::setPixelBuffer(PixelBuffer* pb_, const ScreenSet& layout)
{
if (comparer)
comparer->logStats();
pb = pb_;
delete comparer;
comparer = 0;
screenLayout = layout;
if (!pb) {
screenLayout = ScreenSet();
if (desktopStarted)
throw Exception("setPixelBuffer: null PixelBuffer when desktopStarted?");
return;
}
// Assume the framebuffer contents wasn't saved and reset everything
// that tracks its contents
comparer = new ComparingUpdateTracker(pb);
renderedCursorInvalid = true;
add_changed(pb->getRect());
// Make sure that we have at least one screen
if (screenLayout.num_screens() == 0)
screenLayout.add_screen(Screen(0, 0, 0, pb->width(), pb->height(), 0));
std::list<VNCSConnectionST*>::iterator ci, ci_next;
for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
ci_next = ci; ci_next++;
(*ci)->pixelBufferChange();
// Since the new pixel buffer means an ExtendedDesktopSize needs to
// be sent anyway, we don't need to call screenLayoutChange.
}
}
void VNCServerST::setPixelBuffer(PixelBuffer* pb_)
{
ScreenSet layout = screenLayout;
// Check that the screen layout is still valid
if (pb_ && !layout.validate(pb_->width(), pb_->height())) {
Rect fbRect;
ScreenSet::iterator iter, iter_next;
fbRect.setXYWH(0, 0, pb_->width(), pb_->height());
for (iter = layout.begin();iter != layout.end();iter = iter_next) {
iter_next = iter; ++iter_next;
if (iter->dimensions.enclosed_by(fbRect))
continue;
iter->dimensions = iter->dimensions.intersect(fbRect);
if (iter->dimensions.is_empty()) {
slog.info("Removing screen %d (%x) as it is completely outside the new framebuffer",
(int)iter->id, (unsigned)iter->id);
layout.remove_screen(iter->id);
}
}
}
setPixelBuffer(pb_, layout);
}
void VNCServerST::setScreenLayout(const ScreenSet& layout)
{
if (!pb)
throw Exception("setScreenLayout: new screen layout without a PixelBuffer");
if (!layout.validate(pb->width(), pb->height()))
throw Exception("setScreenLayout: invalid screen layout");
screenLayout = layout;
std::list<VNCSConnectionST*>::iterator ci, ci_next;
for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
ci_next = ci; ci_next++;
(*ci)->screenLayoutChangeOrClose(reasonServer);
}
}
void VNCServerST::announceClipboard(bool available)
{
std::list<VNCSConnectionST*>::iterator ci, ci_next;
if (available)
clipboardClient = NULL;
clipboardRequestors.clear();
for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
ci_next = ci; ci_next++;
(*ci)->announceClipboard(available);
}
}
void VNCServerST::sendBinaryClipboardData(const char* mime, const unsigned char *data,
const unsigned len)
{
std::list<VNCSConnectionST*>::iterator ci, ci_next;
for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
ci_next = ci; ci_next++;
(*ci)->sendBinaryClipboardDataOrClose(mime, data, len, clipboardId);
}
clipboardId++;
}
void VNCServerST::getBinaryClipboardData(const char* mime, const unsigned char **data,
unsigned *len)
{
if (!clipboardClient)
return;
clipboardClient->getBinaryClipboardData(mime, data, len);
}
void VNCServerST::clearBinaryClipboardData()
{
std::list<VNCSConnectionST*>::iterator ci, ci_next;
for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
ci_next = ci; ci_next++;
(*ci)->clearBinaryClipboardData();
}
}
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)->bellOrClose();
}
}
void VNCServerST::setName(const char* name_)
{
name.replaceBuf(strDup(name_));
std::list<VNCSConnectionST*>::iterator ci, ci_next;
for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
ci_next = ci; ci_next++;
(*ci)->setDesktopNameOrClose(name_);
}
}
void VNCServerST::add_changed(const Region& region)
{
if (comparer == NULL)
return;
comparer->add_changed(region);
startFrameClock();
}
void VNCServerST::add_copied(const Region& dest, const Point& delta)
{
if (comparer == NULL)
return;
comparer->add_copied(dest, delta);
startFrameClock();
}
void VNCServerST::setCursor(int width, int height, const Point& newHotspot,
const rdr::U8* data, const bool resizing)
{
delete cursor;
cursor = new Cursor(width, height, newHotspot, data);
cursor->crop();
renderedCursorInvalid = true;
// If an app has an animated cursor on the resized edge, X internals
// will call for it to be rendered. Unlucky for us, the VNC screen
// is currently pointing to freed memory, and a cursor change
// would want to send a screen update. So, don't do that.
if (resizing)
return;
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, bool warped)
{
if (!cursorPos.equals(pos)) {
cursorPos = pos;
renderedCursorInvalid = true;
std::list<VNCSConnectionST*>::iterator ci;
for (ci = clients.begin(); ci != clients.end(); ci++) {
(*ci)->renderedCursorChange();
if (warped)
(*ci)->cursorPositionChange();
}
}
}
void VNCServerST::setLEDState(unsigned int state)
{
std::list<VNCSConnectionST*>::iterator ci, ci_next;
if (state == ledState)
return;
ledState = state;
for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
ci_next = ci; ci_next++;
(*ci)->setLEDStateOrClose(state);
}
}
// 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;
}
bool VNCServerST::handleTimeout(Timer* t)
{
if (t == &frameTimer) {
// We keep running until we go a full interval without any updates
if (comparer->is_empty())
return false;
writeUpdate();
// If this is the first iteration then we need to adjust the timeout
if (frameTimer.getTimeoutMs() != 1000/rfb::Server::frameRate) {
frameTimer.start(1000/rfb::Server::frameRate);
return false;
}
return true;
}
return false;
}
// -=- Internal methods
void VNCServerST::startDesktop()
{
if (!desktopStarted) {
slog.debug("starting desktop");
desktop->start(this);
if (!pb)
throw Exception("SDesktop::start() did not set a valid PixelBuffer");
desktopStarted = true;
// The tracker might have accumulated changes whilst we were
// stopped, so flush those out
if (!comparer->is_empty())
writeUpdate();
}
}
void VNCServerST::stopDesktop()
{
if (desktopStarted) {
slog.debug("stopping desktop");
desktopStarted = false;
desktop->stop();
stopFrameClock();
}
}
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;
}
void VNCServerST::startFrameClock()
{
if (frameTimer.isStarted())
return;
if (blockCounter > 0)
return;
if (!desktopStarted)
return;
// The first iteration will be just half a frame as we get a very
// unstable update rate if we happen to be perfectly in sync with
// the application's update rate
frameTimer.start(1000/rfb::Server::frameRate/2);
}
void VNCServerST::stopFrameClock()
{
frameTimer.stop();
}
int VNCServerST::msToNextUpdate()
{
// FIXME: If the application is updating slower than frameRate then
// we could allow the clients more time here
if (!frameTimer.isStarted())
return 1000/rfb::Server::frameRate/2;
else
return frameTimer.getRemainingMs();
}
static void upgradeClientToUdp(const network::GetAPIMessager::action_data &act,
std::list<VNCSConnectionST*> &clients)
{
std::list<VNCSConnectionST*>::iterator ci, ci_next;
for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
ci_next = ci; ci_next++;
if (!(*ci)->upgradingToUdp)
continue;
char buf[32];
inet_ntop(AF_INET, &act.udp.ip, buf, 32);
const char * const who = (*ci)->getPeerEndpoint();
const char *start = strrchr(who, '@');
if (!start)
continue;
start++;
// Slightly inaccurate, if several clients on the same IP try to upgrade at the same time
if (strncmp(start, buf, strlen(buf)))
continue;
(*ci)->upgradingToUdp = false;
(*ci)->cp.useCopyRect = false;
((network::UdpStream *)(*ci)->getOutStream(true))->setClient((WuClient *) act.udp.client);
(*ci)->cp.supportsUdp = true;
slog.info("%s upgraded to UDP", who);
return;
}
}
void VNCServerST::checkAPIMessages(network::GetAPIMessager *apimessager,
rdr::U8 &trackingFrameStats, char trackingClient[])
{
if (pthread_mutex_lock(&apimessager->userMutex))
return;
const unsigned num = apimessager->actionQueue.size();
unsigned i;
for (i = 0; i < num; i++) {
slog.info("Main thread processing user API request %u/%u", i + 1, num);
const network::GetAPIMessager::action_data &act = apimessager->actionQueue[i];
switch (act.action) {
case network::GetAPIMessager::NONE:
slog.info("Empty request (bug!)");
break;
case network::GetAPIMessager::WANT_FRAME_STATS_SERVERONLY:
trackingFrameStats = act.action;
break;
case network::GetAPIMessager::WANT_FRAME_STATS_ALL:
trackingFrameStats = act.action;
break;
case network::GetAPIMessager::WANT_FRAME_STATS_OWNER:
trackingFrameStats = act.action;
break;
case network::GetAPIMessager::WANT_FRAME_STATS_SPECIFIC:
trackingFrameStats = act.action;
memcpy(trackingClient, act.data.password, 128);
break;
case network::GetAPIMessager::UDP_UPGRADE:
upgradeClientToUdp(act, clients);
break;
case network::GetAPIMessager::CLEAR_CLIPBOARD:
clearBinaryClipboardData();
clipboardClient = NULL;
desktop->handleClipboardAnnounceBinary(0, NULL);
sendBinaryClipboardData("text/plain", NULL, 0);
desktop->clearLocalClipboards();
break;
}
}
apimessager->actionQueue.clear();
pthread_mutex_unlock(&apimessager->userMutex);
}
void VNCServerST::translateDLPRegion(rdr::U16 &x1, rdr::U16 &y1, rdr::U16 &x2, rdr::U16 &y2) const
{
if (DLPRegion.percents) {
x1 = DLPRegion.pcx1 ? DLPRegion.pcx1 * pb->getRect().width() / 100 : 0;
y1 = DLPRegion.pcy1 ? DLPRegion.pcy1 * pb->getRect().height() / 100 : 0;
x2 = DLPRegion.pcx2 ? (100 - DLPRegion.pcx2) * pb->getRect().width() / 100 : pb->getRect().width();
y2 = DLPRegion.pcy2 ? (100 - DLPRegion.pcy2) * pb->getRect().height() / 100 : pb->getRect().height();
} else {
x1 = abs(DLPRegion.x1);
y1 = abs(DLPRegion.y1);
x2 = pb->getRect().width();
y2 = pb->getRect().height();
if (DLPRegion.x2 < 0)
x2 += DLPRegion.x2;
else if (DLPRegion.x2 > 0)
x2 = DLPRegion.x2;
if (DLPRegion.y2 < 0)
y2 += DLPRegion.y2;
else if (DLPRegion.y2 > 0)
y2 = DLPRegion.y2;
}
if (y2 > pb->getRect().height())
y2 = pb->getRect().height() - 1;
if (x2 > pb->getRect().width())
x2 = pb->getRect().width() - 1;
//slog.info("DLP_Region vals %u,%u %u,%u", x1, y1, x2, y2);
}
void VNCServerST::blackOut()
{
// Compute the region, since the resolution may have changed
rdr::U16 x1, y1, x2, y2;
translateDLPRegion(x1, y1, x2, y2);
if (blackedpb)
delete blackedpb;
blackedpb = new ManagedPixelBuffer(pb->getPF(), pb->getRect().width(), pb->getRect().height());
int stride;
const rdr::U8 *src = pb->getBuffer(pb->getRect(), &stride);
rdr::U8 *data = blackedpb->getBufferRW(pb->getRect(), &stride);
stride *= 4;
memcpy(data, src, stride * pb->getRect().height());
rdr::U16 y;
const rdr::U16 w = pb->getRect().width();
const rdr::U16 h = pb->getRect().height();
for (y = 0; y < h; y++) {
if (y < y1 || y > y2) {
memset(data, 0, stride);
} else {
if (x1)
memset(data, 0, x1 * 4);
if (x2)
memset(&data[x2 * 4], 0, (w - x2) * 4);
}
data += stride;
}
}
// writeUpdate() is called on a regular interval in order 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::writeUpdate()
{
UpdateInfo ui;
Region toCheck;
std::list<VNCSConnectionST*>::iterator ci, ci_next;
assert(blockCounter == 0);
assert(desktopStarted);
struct timeval start;
gettimeofday(&start, NULL);
if (DLPRegion.enabled) {
comparer->enable_copyrect(false);
blackOut();
}
if (watermarkData && Server::DLP_WatermarkText[0] && watermarkTextNeedsUpdate(true)) {
// The text may have changed
sendWatermark = true;
}
comparer->getUpdateInfo(&ui, pb->getRect());
toCheck = ui.changed.union_(ui.copied);
Region cursorReg;
if (needRenderedCursor()) {
Rect clippedCursorRect = Rect(0, 0, cursor->width(), cursor->height())
.translate(cursorPos.subtract(cursor->hotspot()))
.intersect(pb->getRect());
if (!toCheck.intersect(clippedCursorRect).is_empty())
renderedCursorInvalid = true;
cursorReg = clippedCursorRect;
}
pb->grabRegion(toCheck);
if (getComparerState())
comparer->enable();
else
comparer->disable();
struct timeval beforeAnalysis;
gettimeofday(&beforeAnalysis, NULL);
// Skip scroll detection if the client is slow, and didn't get the previous one yet
if (comparer->compare(clients.size() == 1 && (*clients.begin())->has_copypassed(),
cursorReg))
comparer->getUpdateInfo(&ui, pb->getRect());
comparer->clear();
const unsigned analysisMs = msSince(&beforeAnalysis);
encCache.clear();
encCache.enabled = clients.size() > 1;
// Check if the password file was updated
bool permcheck = false;
if (inotifyfd >= 0) {
char buf[256];
int ret = read(inotifyfd, buf, 256);
int pos = 0;
while (ret > 0) {
const struct inotify_event * const ev = (struct inotify_event *) &buf[pos];
if (ev->mask & IN_IGNORED) {
// file was deleted, set new watch
if (inotify_add_watch(inotifyfd, kasmpasswdpath, IN_CLOSE_WRITE | IN_DELETE_SELF) < 0)
slog.error("Failed to set watch");
}
permcheck = true;
ret -= sizeof(struct inotify_event) - ev->len;
pos += sizeof(struct inotify_event) - ev->len;
}
}
unsigned shottime = 0;
if (apimessager) {
struct timeval shotstart;
gettimeofday(&shotstart, NULL);
apimessager->mainUpdateScreen(pb);
shottime = msSince(&shotstart);
trackingFrameStats = 0;
checkAPIMessages(apimessager, trackingFrameStats, trackingClient);
}
const rdr::U8 origtrackingFrameStats = trackingFrameStats;
EncodeManager::codecstats_t jpegstats, webpstats;
unsigned enctime = 0, scaletime = 0;
memset(&jpegstats, 0, sizeof(EncodeManager::codecstats_t));
memset(&webpstats, 0, sizeof(EncodeManager::codecstats_t));
if (watermarkData)
updateWatermark();
for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
ci_next = ci; ci_next++;
if (permcheck)
(*ci)->recheckPerms();
if (trackingFrameStats == network::GetAPIMessager::WANT_FRAME_STATS_ALL ||
(trackingFrameStats == network::GetAPIMessager::WANT_FRAME_STATS_OWNER &&
(*ci)->is_owner()) ||
(trackingFrameStats == network::GetAPIMessager::WANT_FRAME_STATS_SPECIFIC &&
strstr((*ci)->getPeerEndpoint(), trackingClient))) {
(*ci)->setFrameTracking();
// Only one owner
if (trackingFrameStats == network::GetAPIMessager::WANT_FRAME_STATS_OWNER)
trackingFrameStats = network::GetAPIMessager::WANT_FRAME_STATS_SERVERONLY;
}
(*ci)->add_copied(ui.copied, ui.copy_delta);
(*ci)->add_copypassed(ui.copypassed);
(*ci)->add_changed(ui.changed);
(*ci)->writeFramebufferUpdateOrClose();
if (((network::UdpStream *)(*ci)->getOutStream(true))->isFailed()) {
((network::UdpStream *)(*ci)->getOutStream(true))->clearFailed();
(*ci)->udpDowngrade(true);
}
if (apimessager) {
(*ci)->sendStats(false);
const EncodeManager::codecstats_t subjpeg = (*ci)->getJpegStats();
const EncodeManager::codecstats_t subwebp = (*ci)->getWebpStats();
jpegstats.ms += subjpeg.ms;
jpegstats.area += subjpeg.area;
jpegstats.rects += subjpeg.rects;
webpstats.ms += subwebp.ms;
webpstats.area += subwebp.area;
webpstats.rects += subwebp.rects;
enctime += (*ci)->getEncodingTime();
scaletime += (*ci)->getScalingTime();
}
}
sendWatermark = false; // the client now caches it, only send once
if (trackingFrameStats) {
if (enctime) {
const unsigned totalMs = msSince(&start);
if (apimessager)
apimessager->mainUpdateServerFrameStats(comparer->changedPerc, totalMs,
jpegstats.ms, webpstats.ms,
analysisMs,
jpegstats.area, webpstats.area,
jpegstats.rects, webpstats.rects,
enctime, scaletime, shottime,
pb->getRect().width(),
pb->getRect().height());
} else {
// Zero encoding time means this was a no-data frame; restore the stats request
if (apimessager && pthread_mutex_lock(&apimessager->userMutex) == 0) {
network::GetAPIMessager::action_data act;
act.action = (network::GetAPIMessager::USER_ACTION) origtrackingFrameStats;
memcpy(act.data.password, trackingClient, 128);
apimessager->actionQueue.push_back(act);
pthread_mutex_unlock(&apimessager->userMutex);
}
}
}
}
// checkUpdate() is called by clients to see if it is safe to read from
// the framebuffer at this time.
Region VNCServerST::getPendingRegion()
{
UpdateInfo ui;
// Block clients as the frame buffer cannot be safely accessed
if (blockCounter > 0)
return pb->getRect();
// Block client from updating if there are pending updates
if (comparer->is_empty())
return Region();
comparer->getUpdateInfo(&ui, pb->getRect());
return ui.changed.union_(ui.copied);
}
const RenderedCursor* VNCServerST::getRenderedCursor()
{
if (renderedCursorInvalid) {
renderedCursor.update(pb, cursor, cursorPos);
renderedCursorInvalid = false;
}
return &renderedCursor;
}
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;
}
}
}
}
void VNCServerST::notifyScreenLayoutChange(VNCSConnectionST* requester)
{
std::list<VNCSConnectionST*>::iterator ci, ci_next;
for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
ci_next = ci; ci_next++;
if ((*ci) == requester)
continue;
(*ci)->screenLayoutChangeOrClose(reasonOtherClient);
}
}
bool VNCServerST::getComparerState()
{
if (rfb::Server::compareFB == 0)
return false;
if (rfb::Server::compareFB != 2)
return true;
std::list<VNCSConnectionST*>::iterator ci, ci_next;
for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
ci_next = ci; ci_next++;
if ((*ci)->getComparerState())
return true;
}
return false;
}
void VNCServerST::handleClipboardAnnounce(VNCSConnectionST* client,
bool available)
{
if (available)
clipboardClient = client;
else {
if (client != clipboardClient)
return;
clipboardClient = NULL;
}
desktop->handleClipboardAnnounce(available);
}
void VNCServerST::handleClipboardAnnounceBinary(VNCSConnectionST* client,
const unsigned num,
const char mimes[][32])
{
clipboardClient = client;
desktop->handleClipboardAnnounceBinary(num, mimes);
}
void VNCServerST::refreshClients()
{
add_changed(pb->getRect());
std::list<VNCSConnectionST*>::iterator i;
for (i = clients.begin(); i != clients.end(); i++) {
(*i)->add_changed_all();
}
}
void VNCServerST::sendUnixRelayData(const char name[],
const unsigned char *buf, const unsigned len)
{
// For each client subscribed to this channel, send the data to them
std::list<VNCSConnectionST*>::iterator i;
for (i = clients.begin(); i != clients.end(); i++) {
if ((*i)->isSubscribedToUnixRelay(name)) {
(*i)->sendUnixRelayData(name, buf, len);
}
}
}