mirror of
https://github.com/kasmtech/KasmVNC.git
synced 2024-11-28 11:04:12 +01:00
493 lines
12 KiB
C++
493 lines
12 KiB
C++
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
|
|
* Copyright 2011-2017 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.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <rfb/Exception.h>
|
|
#include <rfb/clipboardTypes.h>
|
|
#include <rfb/fenceTypes.h>
|
|
#include <rfb/CMsgReader.h>
|
|
#include <rfb/CMsgWriter.h>
|
|
#include <rfb/CSecurity.h>
|
|
#include <rfb/Security.h>
|
|
#include <rfb/SecurityClient.h>
|
|
#include <rfb/CConnection.h>
|
|
#include <rfb/util.h>
|
|
|
|
#include <rfb/LogWriter.h>
|
|
|
|
#include <rdr/InStream.h>
|
|
#include <rdr/OutStream.h>
|
|
|
|
using namespace rfb;
|
|
|
|
static LogWriter vlog("CConnection");
|
|
|
|
CConnection::CConnection()
|
|
: csecurity(0), is(0), os(0), reader_(0), writer_(0),
|
|
shared(false),
|
|
state_(RFBSTATE_UNINITIALISED), useProtocol3_3(false),
|
|
framebuffer(NULL), decoder(this),
|
|
serverClipboard(NULL), hasLocalClipboard(false)
|
|
{
|
|
}
|
|
|
|
CConnection::~CConnection()
|
|
{
|
|
setFramebuffer(NULL);
|
|
if (csecurity) csecurity->destroy();
|
|
delete reader_;
|
|
reader_ = 0;
|
|
delete writer_;
|
|
writer_ = 0;
|
|
strFree(serverClipboard);
|
|
}
|
|
|
|
void CConnection::setStreams(rdr::InStream* is_, rdr::OutStream* os_)
|
|
{
|
|
is = is_;
|
|
os = os_;
|
|
}
|
|
|
|
void CConnection::setFramebuffer(ModifiablePixelBuffer* fb)
|
|
{
|
|
decoder.flush();
|
|
|
|
if ((framebuffer != NULL) && (fb != NULL)) {
|
|
Rect rect;
|
|
|
|
const rdr::U8* data;
|
|
int stride;
|
|
|
|
const rdr::U8 black[4] = { 0, 0, 0, 0 };
|
|
|
|
// Copy still valid area
|
|
|
|
rect.setXYWH(0, 0,
|
|
__rfbmin(fb->width(), framebuffer->width()),
|
|
__rfbmin(fb->height(), framebuffer->height()));
|
|
data = framebuffer->getBuffer(framebuffer->getRect(), &stride);
|
|
fb->imageRect(rect, data, stride);
|
|
|
|
// Black out any new areas
|
|
|
|
if (fb->width() > framebuffer->width()) {
|
|
rect.setXYWH(framebuffer->width(), 0,
|
|
fb->width() - framebuffer->width(),
|
|
fb->height());
|
|
fb->fillRect(rect, black);
|
|
}
|
|
|
|
if (fb->height() > framebuffer->height()) {
|
|
rect.setXYWH(0, framebuffer->height(),
|
|
fb->width(),
|
|
fb->height() - framebuffer->height());
|
|
fb->fillRect(rect, black);
|
|
}
|
|
}
|
|
|
|
delete framebuffer;
|
|
framebuffer = fb;
|
|
}
|
|
|
|
void CConnection::initialiseProtocol()
|
|
{
|
|
state_ = RFBSTATE_PROTOCOL_VERSION;
|
|
}
|
|
|
|
void CConnection::processMsg()
|
|
{
|
|
switch (state_) {
|
|
|
|
case RFBSTATE_PROTOCOL_VERSION: processVersionMsg(); break;
|
|
case RFBSTATE_SECURITY_TYPES: processSecurityTypesMsg(); break;
|
|
case RFBSTATE_SECURITY: processSecurityMsg(); break;
|
|
case RFBSTATE_SECURITY_RESULT: processSecurityResultMsg(); break;
|
|
case RFBSTATE_INITIALISATION: processInitMsg(); break;
|
|
case RFBSTATE_NORMAL: reader_->readMsg(); break;
|
|
case RFBSTATE_UNINITIALISED:
|
|
throw Exception("CConnection::processMsg: not initialised yet?");
|
|
default:
|
|
throw Exception("CConnection::processMsg: invalid state");
|
|
}
|
|
}
|
|
|
|
void CConnection::processVersionMsg()
|
|
{
|
|
vlog.debug("reading protocol version");
|
|
bool done;
|
|
if (!cp.readVersion(is, &done)) {
|
|
state_ = RFBSTATE_INVALID;
|
|
throw Exception("reading version failed: not an RFB server?");
|
|
}
|
|
if (!done) return;
|
|
|
|
vlog.info("Server supports RFB protocol version %d.%d",
|
|
cp.majorVersion, cp.minorVersion);
|
|
|
|
// The only official RFB protocol versions are currently 3.3, 3.7 and 3.8
|
|
if (cp.beforeVersion(3,3)) {
|
|
vlog.error("Server gave unsupported RFB protocol version %d.%d",
|
|
cp.majorVersion, cp.minorVersion);
|
|
state_ = RFBSTATE_INVALID;
|
|
throw Exception("Server gave unsupported RFB protocol version %d.%d",
|
|
cp.majorVersion, cp.minorVersion);
|
|
} else if (useProtocol3_3 || cp.beforeVersion(3,7)) {
|
|
cp.setVersion(3,3);
|
|
} else if (cp.afterVersion(3,8)) {
|
|
cp.setVersion(3,8);
|
|
}
|
|
|
|
cp.writeVersion(os);
|
|
state_ = RFBSTATE_SECURITY_TYPES;
|
|
|
|
vlog.info("Using RFB protocol version %d.%d",
|
|
cp.majorVersion, cp.minorVersion);
|
|
}
|
|
|
|
|
|
void CConnection::processSecurityTypesMsg()
|
|
{
|
|
vlog.debug("processing security types message");
|
|
|
|
int secType = secTypeInvalid;
|
|
|
|
std::list<rdr::U8> secTypes;
|
|
secTypes = security.GetEnabledSecTypes();
|
|
|
|
if (cp.isVersion(3,3)) {
|
|
|
|
// legacy 3.3 server may only offer "vnc authentication" or "none"
|
|
|
|
secType = is->readU32();
|
|
if (secType == secTypeInvalid) {
|
|
throwConnFailedException();
|
|
|
|
} else if (secType == secTypeNone || secType == secTypeVncAuth) {
|
|
std::list<rdr::U8>::iterator i;
|
|
for (i = secTypes.begin(); i != secTypes.end(); i++)
|
|
if (*i == secType) {
|
|
secType = *i;
|
|
break;
|
|
}
|
|
|
|
if (i == secTypes.end())
|
|
secType = secTypeInvalid;
|
|
} else {
|
|
vlog.error("Unknown 3.3 security type %d", secType);
|
|
throw Exception("Unknown 3.3 security type");
|
|
}
|
|
|
|
} else {
|
|
|
|
// >=3.7 server will offer us a list
|
|
|
|
int nServerSecTypes = is->readU8();
|
|
if (nServerSecTypes == 0)
|
|
throwConnFailedException();
|
|
|
|
std::list<rdr::U8>::iterator j;
|
|
|
|
for (int i = 0; i < nServerSecTypes; i++) {
|
|
rdr::U8 serverSecType = is->readU8();
|
|
vlog.debug("Server offers security type %s(%d)",
|
|
secTypeName(serverSecType), serverSecType);
|
|
|
|
/*
|
|
* Use the first type sent by server which matches client's type.
|
|
* It means server's order specifies priority.
|
|
*/
|
|
if (secType == secTypeInvalid) {
|
|
for (j = secTypes.begin(); j != secTypes.end(); j++)
|
|
if (*j == serverSecType) {
|
|
secType = *j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Inform the server of our decision
|
|
if (secType != secTypeInvalid) {
|
|
os->writeU8(secType);
|
|
os->flush();
|
|
vlog.info("Choosing security type %s(%d)",secTypeName(secType),secType);
|
|
}
|
|
}
|
|
|
|
if (secType == secTypeInvalid) {
|
|
state_ = RFBSTATE_INVALID;
|
|
vlog.error("No matching security types");
|
|
throw Exception("No matching security types");
|
|
}
|
|
|
|
state_ = RFBSTATE_SECURITY;
|
|
csecurity = security.GetCSecurity(secType);
|
|
processSecurityMsg();
|
|
}
|
|
|
|
void CConnection::processSecurityMsg()
|
|
{
|
|
vlog.debug("processing security message");
|
|
if (csecurity->processMsg(this)) {
|
|
state_ = RFBSTATE_SECURITY_RESULT;
|
|
processSecurityResultMsg();
|
|
}
|
|
}
|
|
|
|
void CConnection::processSecurityResultMsg()
|
|
{
|
|
vlog.debug("processing security result message");
|
|
int result;
|
|
if (cp.beforeVersion(3,8) && csecurity->getType() == secTypeNone) {
|
|
result = secResultOK;
|
|
} else {
|
|
if (!is->checkNoWait(1)) return;
|
|
result = is->readU32();
|
|
}
|
|
switch (result) {
|
|
case secResultOK:
|
|
securityCompleted();
|
|
return;
|
|
case secResultFailed:
|
|
vlog.debug("auth failed");
|
|
break;
|
|
case secResultTooMany:
|
|
vlog.debug("auth failed - too many tries");
|
|
break;
|
|
default:
|
|
throw Exception("Unknown security result from server");
|
|
}
|
|
state_ = RFBSTATE_INVALID;
|
|
if (cp.beforeVersion(3,8))
|
|
throw AuthFailureException();
|
|
CharArray reason(is->readString());
|
|
throw AuthFailureException(reason.buf);
|
|
}
|
|
|
|
void CConnection::processInitMsg()
|
|
{
|
|
vlog.debug("reading server initialisation");
|
|
reader_->readServerInit();
|
|
}
|
|
|
|
void CConnection::throwConnFailedException()
|
|
{
|
|
state_ = RFBSTATE_INVALID;
|
|
CharArray reason;
|
|
reason.buf = is->readString();
|
|
throw ConnFailedException(reason.buf);
|
|
}
|
|
|
|
void CConnection::securityCompleted()
|
|
{
|
|
state_ = RFBSTATE_INITIALISATION;
|
|
reader_ = new CMsgReader(this, is);
|
|
writer_ = new CMsgWriter(&cp, os);
|
|
vlog.debug("Authentication success!");
|
|
authSuccess();
|
|
writer_->writeClientInit(shared);
|
|
}
|
|
|
|
void CConnection::setDesktopSize(int w, int h)
|
|
{
|
|
decoder.flush();
|
|
|
|
CMsgHandler::setDesktopSize(w,h);
|
|
}
|
|
|
|
void CConnection::setExtendedDesktopSize(unsigned reason,
|
|
unsigned result,
|
|
int w, int h,
|
|
const ScreenSet& layout)
|
|
{
|
|
decoder.flush();
|
|
|
|
CMsgHandler::setExtendedDesktopSize(reason, result, w, h, layout);
|
|
}
|
|
|
|
void CConnection::readAndDecodeRect(const Rect& r, int encoding,
|
|
ModifiablePixelBuffer* pb)
|
|
{
|
|
decoder.decodeRect(r, encoding, pb);
|
|
decoder.flush();
|
|
}
|
|
|
|
void CConnection::framebufferUpdateStart()
|
|
{
|
|
CMsgHandler::framebufferUpdateStart();
|
|
}
|
|
|
|
void CConnection::framebufferUpdateEnd()
|
|
{
|
|
decoder.flush();
|
|
|
|
CMsgHandler::framebufferUpdateEnd();
|
|
}
|
|
|
|
void CConnection::dataRect(const Rect& r, int encoding)
|
|
{
|
|
decoder.decodeRect(r, encoding, framebuffer);
|
|
}
|
|
|
|
void CConnection::serverCutText(const char* str)
|
|
{
|
|
hasLocalClipboard = false;
|
|
|
|
strFree(serverClipboard);
|
|
serverClipboard = NULL;
|
|
|
|
serverClipboard = latin1ToUTF8(str);
|
|
|
|
handleClipboardAnnounce(true);
|
|
}
|
|
|
|
void CConnection::handleClipboardCaps(rdr::U32 flags,
|
|
const rdr::U32* lengths)
|
|
{
|
|
rdr::U32 sizes[] = { 0 };
|
|
|
|
CMsgHandler::handleClipboardCaps(flags, lengths);
|
|
|
|
writer()->writeClipboardCaps(rfb::clipboardUTF8 |
|
|
rfb::clipboardRequest |
|
|
rfb::clipboardPeek |
|
|
rfb::clipboardNotify |
|
|
rfb::clipboardProvide,
|
|
sizes);
|
|
}
|
|
|
|
void CConnection::handleClipboardRequest(rdr::U32 flags)
|
|
{
|
|
if (!(flags & rfb::clipboardUTF8))
|
|
return;
|
|
if (!hasLocalClipboard)
|
|
return;
|
|
handleClipboardRequest();
|
|
}
|
|
|
|
void CConnection::handleClipboardPeek(rdr::U32 flags)
|
|
{
|
|
if (!hasLocalClipboard)
|
|
return;
|
|
if (cp.clipboardFlags() & rfb::clipboardNotify)
|
|
writer()->writeClipboardNotify(rfb::clipboardUTF8);
|
|
}
|
|
|
|
void CConnection::handleClipboardNotify(rdr::U32 flags)
|
|
{
|
|
strFree(serverClipboard);
|
|
serverClipboard = NULL;
|
|
|
|
if (flags & rfb::clipboardUTF8) {
|
|
hasLocalClipboard = false;
|
|
handleClipboardAnnounce(true);
|
|
} else {
|
|
handleClipboardAnnounce(false);
|
|
}
|
|
}
|
|
|
|
void CConnection::handleClipboardProvide(rdr::U32 flags,
|
|
const size_t* lengths,
|
|
const rdr::U8* const* data)
|
|
{
|
|
if (!(flags & rfb::clipboardUTF8))
|
|
return;
|
|
|
|
strFree(serverClipboard);
|
|
serverClipboard = NULL;
|
|
|
|
serverClipboard = convertLF((const char*)data[0], lengths[0]);
|
|
|
|
// FIXME: Should probably verify that this data was actually requested
|
|
handleClipboardData(serverClipboard);
|
|
}
|
|
|
|
void CConnection::authSuccess()
|
|
{
|
|
}
|
|
|
|
void CConnection::serverInit()
|
|
{
|
|
state_ = RFBSTATE_NORMAL;
|
|
vlog.debug("initialisation done");
|
|
}
|
|
|
|
void CConnection::fence(rdr::U32 flags, unsigned len, const char data[])
|
|
{
|
|
CMsgHandler::fence(flags, len, data);
|
|
|
|
if (!(flags & fenceFlagRequest))
|
|
return;
|
|
|
|
// We cannot guarantee any synchronisation at this level
|
|
flags = 0;
|
|
|
|
writer()->writeFence(flags, len, data);
|
|
}
|
|
|
|
void CConnection::handleClipboardRequest()
|
|
{
|
|
}
|
|
|
|
void CConnection::handleClipboardAnnounce(bool available)
|
|
{
|
|
}
|
|
|
|
void CConnection::handleClipboardData(const char* data)
|
|
{
|
|
}
|
|
|
|
void CConnection::requestClipboard()
|
|
{
|
|
if (serverClipboard != NULL) {
|
|
handleClipboardData(serverClipboard);
|
|
return;
|
|
}
|
|
|
|
if (cp.clipboardFlags() & rfb::clipboardRequest)
|
|
writer()->writeClipboardRequest(rfb::clipboardUTF8);
|
|
}
|
|
|
|
void CConnection::announceClipboard(bool available)
|
|
{
|
|
hasLocalClipboard = available;
|
|
|
|
if (cp.clipboardFlags() & rfb::clipboardNotify)
|
|
writer()->writeClipboardNotify(available ? rfb::clipboardUTF8 : 0);
|
|
else {
|
|
if (available)
|
|
handleClipboardRequest();
|
|
}
|
|
}
|
|
|
|
void CConnection::sendClipboardData(const char* data)
|
|
{
|
|
if (cp.clipboardFlags() & rfb::clipboardProvide) {
|
|
CharArray filtered(convertCRLF(data));
|
|
size_t sizes[1] = { strlen(filtered.buf) + 1 };
|
|
const rdr::U8* data[1] = { (const rdr::U8*)filtered.buf };
|
|
writer()->writeClipboardProvide(rfb::clipboardUTF8, sizes, data);
|
|
} else {
|
|
CharArray latin1(utf8ToLatin1(data));
|
|
|
|
writer()->writeClientCutText(latin1.buf, strlen(latin1.buf));
|
|
}
|
|
}
|
|
|