You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3612 lines
110 KiB
3612 lines
110 KiB
/*
|
|
* rfbserver.c - deal with server-side of the RFB protocol.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2011-2012 D. R. Commander
|
|
* Copyright (C) 2005 Rohit Kumar, Johannes E. Schindelin
|
|
* Copyright (C) 2002 RealVNC Ltd.
|
|
* OSXvnc Copyright (C) 2001 Dan McGuirk <mcguirk@incompleteness.net>.
|
|
* Original Xvnc code Copyright (C) 1999 AT&T Laboratories Cambridge.
|
|
* 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.
|
|
*/
|
|
|
|
#ifdef __STRICT_ANSI__
|
|
#define _BSD_SOURCE
|
|
#define _POSIX_SOURCE
|
|
#define _XOPEN_SOURCE 600
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <rfb/rfb.h>
|
|
#include <rfb/rfbregion.h>
|
|
#include "private.h"
|
|
#include "rfb/rfbconfig.h"
|
|
|
|
#ifdef LIBVNCSERVER_HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#include <io.h>
|
|
#define write(sock,buf,len) send(sock,buf,len,0)
|
|
#else
|
|
#ifdef LIBVNCSERVER_HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#include <pwd.h>
|
|
#ifdef LIBVNCSERVER_HAVE_SYS_SOCKET_H
|
|
#include <sys/socket.h>
|
|
#endif
|
|
#ifdef LIBVNCSERVER_HAVE_NETINET_IN_H
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <netdb.h>
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef DEBUGPROTO
|
|
#undef DEBUGPROTO
|
|
#define DEBUGPROTO(x) x
|
|
#else
|
|
#define DEBUGPROTO(x)
|
|
#endif
|
|
#include <stdarg.h>
|
|
#include <scale.h>
|
|
/* stst() */
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#if LIBVNCSERVER_HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifndef WIN32
|
|
/* readdir() */
|
|
#include <dirent.h>
|
|
#endif
|
|
|
|
/* errno */
|
|
#include <errno.h>
|
|
/* strftime() */
|
|
#include <time.h>
|
|
|
|
#ifdef LIBVNCSERVER_WITH_WEBSOCKETS
|
|
#include "rfbssl.h"
|
|
#endif
|
|
|
|
#ifdef _MSC_VER
|
|
#define snprintf _snprintf /* Missing in MSVC */
|
|
/* Prevent POSIX deprecation warnings */
|
|
#define close _close
|
|
#define strdup _strdup
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
#include <direct.h>
|
|
#ifdef __MINGW32__
|
|
#define mkdir(path, perms) mkdir(path) /* Omit the perms argument to match POSIX signature */
|
|
#else /* MSVC and other windows compilers */
|
|
#define mkdir(path, perms) _mkdir(path) /* Omit the perms argument to match POSIX signature */
|
|
#endif /* __MINGW32__ else... */
|
|
#ifndef S_ISDIR
|
|
#define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR)
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef LIBVNCSERVER_HAVE_LIBJPEG
|
|
/*
|
|
* Map of quality levels to provide compatibility with TightVNC/TigerVNC
|
|
* clients. This emulates the behavior of the TigerVNC Server.
|
|
*/
|
|
|
|
static const int tight2turbo_qual[10] = {
|
|
15, 29, 41, 42, 62, 77, 79, 86, 92, 100
|
|
};
|
|
|
|
static const int tight2turbo_subsamp[10] = {
|
|
1, 1, 1, 2, 2, 2, 0, 0, 0, 0
|
|
};
|
|
#endif
|
|
|
|
static void rfbProcessClientProtocolVersion(rfbClientPtr cl);
|
|
static void rfbProcessClientNormalMessage(rfbClientPtr cl);
|
|
static void rfbProcessClientInitMessage(rfbClientPtr cl);
|
|
|
|
#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
|
|
void rfbIncrClientRef(rfbClientPtr cl)
|
|
{
|
|
LOCK(cl->refCountMutex);
|
|
cl->refCount++;
|
|
UNLOCK(cl->refCountMutex);
|
|
}
|
|
|
|
void rfbDecrClientRef(rfbClientPtr cl)
|
|
{
|
|
LOCK(cl->refCountMutex);
|
|
cl->refCount--;
|
|
if(cl->refCount<=0) /* just to be sure also < 0 */
|
|
TSIGNAL(cl->deleteCond);
|
|
UNLOCK(cl->refCountMutex);
|
|
}
|
|
#else
|
|
void rfbIncrClientRef(rfbClientPtr cl) {}
|
|
void rfbDecrClientRef(rfbClientPtr cl) {}
|
|
#endif
|
|
|
|
#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
|
|
static MUTEX(rfbClientListMutex);
|
|
#endif
|
|
|
|
struct rfbClientIterator {
|
|
rfbClientPtr next;
|
|
rfbScreenInfoPtr screen;
|
|
rfbBool closedToo;
|
|
};
|
|
|
|
void
|
|
rfbClientListInit(rfbScreenInfoPtr rfbScreen)
|
|
{
|
|
if(sizeof(rfbBool)!=1) {
|
|
/* a sanity check */
|
|
fprintf(stderr,"rfbBool's size is not 1 (%d)!\n",(int)sizeof(rfbBool));
|
|
/* we cannot continue, because rfbBool is supposed to be char everywhere */
|
|
exit(1);
|
|
}
|
|
rfbScreen->clientHead = NULL;
|
|
INIT_MUTEX(rfbClientListMutex);
|
|
}
|
|
|
|
rfbClientIteratorPtr
|
|
rfbGetClientIterator(rfbScreenInfoPtr rfbScreen)
|
|
{
|
|
rfbClientIteratorPtr i =
|
|
(rfbClientIteratorPtr)malloc(sizeof(struct rfbClientIterator));
|
|
i->next = NULL;
|
|
i->screen = rfbScreen;
|
|
i->closedToo = FALSE;
|
|
return i;
|
|
}
|
|
|
|
rfbClientIteratorPtr
|
|
rfbGetClientIteratorWithClosed(rfbScreenInfoPtr rfbScreen)
|
|
{
|
|
rfbClientIteratorPtr i =
|
|
(rfbClientIteratorPtr)malloc(sizeof(struct rfbClientIterator));
|
|
i->next = NULL;
|
|
i->screen = rfbScreen;
|
|
i->closedToo = TRUE;
|
|
return i;
|
|
}
|
|
|
|
rfbClientPtr
|
|
rfbClientIteratorHead(rfbClientIteratorPtr i)
|
|
{
|
|
#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
|
|
if(i->next != 0) {
|
|
rfbDecrClientRef(i->next);
|
|
rfbIncrClientRef(i->screen->clientHead);
|
|
}
|
|
#endif
|
|
LOCK(rfbClientListMutex);
|
|
i->next = i->screen->clientHead;
|
|
UNLOCK(rfbClientListMutex);
|
|
return i->next;
|
|
}
|
|
|
|
rfbClientPtr
|
|
rfbClientIteratorNext(rfbClientIteratorPtr i)
|
|
{
|
|
if(i->next == 0) {
|
|
LOCK(rfbClientListMutex);
|
|
i->next = i->screen->clientHead;
|
|
UNLOCK(rfbClientListMutex);
|
|
} else {
|
|
IF_PTHREADS(rfbClientPtr cl = i->next);
|
|
i->next = i->next->next;
|
|
IF_PTHREADS(rfbDecrClientRef(cl));
|
|
}
|
|
|
|
#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
|
|
if(!i->closedToo)
|
|
while(i->next && i->next->sock<0)
|
|
i->next = i->next->next;
|
|
if(i->next)
|
|
rfbIncrClientRef(i->next);
|
|
#endif
|
|
|
|
return i->next;
|
|
}
|
|
|
|
void
|
|
rfbReleaseClientIterator(rfbClientIteratorPtr iterator)
|
|
{
|
|
IF_PTHREADS(if(iterator->next) rfbDecrClientRef(iterator->next));
|
|
free(iterator);
|
|
}
|
|
|
|
|
|
/*
|
|
* rfbNewClientConnection is called from sockets.c when a new connection
|
|
* comes in.
|
|
*/
|
|
|
|
void
|
|
rfbNewClientConnection(rfbScreenInfoPtr rfbScreen,
|
|
int sock)
|
|
{
|
|
rfbNewClient(rfbScreen,sock);
|
|
}
|
|
|
|
|
|
/*
|
|
* rfbReverseConnection is called to make an outward
|
|
* connection to a "listening" RFB client.
|
|
*/
|
|
|
|
rfbClientPtr
|
|
rfbReverseConnection(rfbScreenInfoPtr rfbScreen,
|
|
char *host,
|
|
int port)
|
|
{
|
|
int sock;
|
|
rfbClientPtr cl;
|
|
|
|
if ((sock = rfbConnect(rfbScreen, host, port)) < 0)
|
|
return (rfbClientPtr)NULL;
|
|
|
|
cl = rfbNewClient(rfbScreen, sock);
|
|
|
|
if (cl) {
|
|
cl->reverseConnection = TRUE;
|
|
}
|
|
|
|
return cl;
|
|
}
|
|
|
|
|
|
void
|
|
rfbSetProtocolVersion(rfbScreenInfoPtr rfbScreen, int major_, int minor_)
|
|
{
|
|
/* Permit the server to set the version to report */
|
|
/* TODO: sanity checking */
|
|
if ((major_==3) && (minor_ > 2 && minor_ < 9))
|
|
{
|
|
rfbScreen->protocolMajorVersion = major_;
|
|
rfbScreen->protocolMinorVersion = minor_;
|
|
}
|
|
else
|
|
rfbLog("rfbSetProtocolVersion(%d,%d) set to invalid values\n", major_, minor_);
|
|
}
|
|
|
|
/*
|
|
* rfbNewClient is called when a new connection has been made by whatever
|
|
* means.
|
|
*/
|
|
|
|
static rfbClientPtr
|
|
rfbNewTCPOrUDPClient(rfbScreenInfoPtr rfbScreen,
|
|
int sock,
|
|
rfbBool isUDP)
|
|
{
|
|
rfbProtocolVersionMsg pv;
|
|
rfbClientIteratorPtr iterator;
|
|
rfbClientPtr cl,cl_;
|
|
#ifdef LIBVNCSERVER_IPv6
|
|
struct sockaddr_storage addr;
|
|
#else
|
|
struct sockaddr_in addr;
|
|
#endif
|
|
socklen_t addrlen = sizeof(addr);
|
|
rfbProtocolExtension* extension;
|
|
|
|
cl = (rfbClientPtr)calloc(sizeof(rfbClientRec),1);
|
|
|
|
cl->screen = rfbScreen;
|
|
cl->sock = sock;
|
|
cl->viewOnly = FALSE;
|
|
/* setup pseudo scaling */
|
|
cl->scaledScreen = rfbScreen;
|
|
cl->scaledScreen->scaledScreenRefCount++;
|
|
|
|
rfbResetStats(cl);
|
|
|
|
cl->clientData = NULL;
|
|
cl->clientGoneHook = rfbDoNothingWithClient;
|
|
|
|
if(isUDP) {
|
|
rfbLog(" accepted UDP client\n");
|
|
} else {
|
|
#ifdef LIBVNCSERVER_IPv6
|
|
char host[1024];
|
|
#endif
|
|
int one=1;
|
|
|
|
getpeername(sock, (struct sockaddr *)&addr, &addrlen);
|
|
#ifdef LIBVNCSERVER_IPv6
|
|
if(getnameinfo((struct sockaddr*)&addr, addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST) != 0) {
|
|
rfbLogPerror("rfbNewClient: error in getnameinfo");
|
|
cl->host = strdup("");
|
|
}
|
|
else
|
|
cl->host = strdup(host);
|
|
#else
|
|
cl->host = strdup(inet_ntoa(addr.sin_addr));
|
|
#endif
|
|
|
|
rfbLog(" other clients:\n");
|
|
iterator = rfbGetClientIterator(rfbScreen);
|
|
while ((cl_ = rfbClientIteratorNext(iterator)) != NULL) {
|
|
rfbLog(" %s\n",cl_->host);
|
|
}
|
|
rfbReleaseClientIterator(iterator);
|
|
|
|
if(!rfbSetNonBlocking(sock)) {
|
|
close(sock);
|
|
return NULL;
|
|
}
|
|
|
|
if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
|
|
(char *)&one, sizeof(one)) < 0) {
|
|
rfbLogPerror("setsockopt failed: can't set TCP_NODELAY flag, non TCP socket?");
|
|
}
|
|
|
|
FD_SET(sock,&(rfbScreen->allFds));
|
|
rfbScreen->maxFd = rfbMax(sock,rfbScreen->maxFd);
|
|
|
|
INIT_MUTEX(cl->outputMutex);
|
|
INIT_MUTEX(cl->refCountMutex);
|
|
INIT_MUTEX(cl->sendMutex);
|
|
INIT_COND(cl->deleteCond);
|
|
|
|
cl->state = RFB_PROTOCOL_VERSION;
|
|
|
|
cl->reverseConnection = FALSE;
|
|
cl->readyForSetColourMapEntries = FALSE;
|
|
cl->useCopyRect = FALSE;
|
|
cl->preferredEncoding = -1;
|
|
cl->correMaxWidth = 48;
|
|
cl->correMaxHeight = 48;
|
|
#ifdef LIBVNCSERVER_HAVE_LIBZ
|
|
cl->zrleData = NULL;
|
|
#endif
|
|
|
|
cl->copyRegion = sraRgnCreate();
|
|
cl->copyDX = 0;
|
|
cl->copyDY = 0;
|
|
|
|
cl->modifiedRegion =
|
|
sraRgnCreateRect(0,0,rfbScreen->width,rfbScreen->height);
|
|
|
|
INIT_MUTEX(cl->updateMutex);
|
|
INIT_COND(cl->updateCond);
|
|
|
|
cl->requestedRegion = sraRgnCreate();
|
|
|
|
cl->format = cl->screen->serverFormat;
|
|
cl->translateFn = rfbTranslateNone;
|
|
cl->translateLookupTable = NULL;
|
|
|
|
LOCK(rfbClientListMutex);
|
|
|
|
IF_PTHREADS(cl->refCount = 0);
|
|
cl->next = rfbScreen->clientHead;
|
|
cl->prev = NULL;
|
|
if (rfbScreen->clientHead)
|
|
rfbScreen->clientHead->prev = cl;
|
|
|
|
rfbScreen->clientHead = cl;
|
|
UNLOCK(rfbClientListMutex);
|
|
|
|
#if defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG)
|
|
cl->tightQualityLevel = -1;
|
|
#ifdef LIBVNCSERVER_HAVE_LIBJPEG
|
|
cl->tightCompressLevel = TIGHT_DEFAULT_COMPRESSION;
|
|
cl->turboSubsampLevel = TURBO_DEFAULT_SUBSAMP;
|
|
{
|
|
int i;
|
|
for (i = 0; i < 4; i++)
|
|
cl->zsActive[i] = FALSE;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
cl->fileTransfer.fd = -1;
|
|
|
|
cl->enableCursorShapeUpdates = FALSE;
|
|
cl->enableCursorPosUpdates = FALSE;
|
|
cl->useRichCursorEncoding = FALSE;
|
|
cl->enableLastRectEncoding = FALSE;
|
|
cl->enableKeyboardLedState = FALSE;
|
|
cl->enableSupportedMessages = FALSE;
|
|
cl->enableSupportedEncodings = FALSE;
|
|
cl->enableServerIdentity = FALSE;
|
|
cl->lastKeyboardLedState = -1;
|
|
cl->cursorX = rfbScreen->cursorX;
|
|
cl->cursorY = rfbScreen->cursorY;
|
|
cl->useNewFBSize = FALSE;
|
|
|
|
#ifdef LIBVNCSERVER_HAVE_LIBZ
|
|
cl->compStreamInited = FALSE;
|
|
cl->compStream.total_in = 0;
|
|
cl->compStream.total_out = 0;
|
|
cl->compStream.zalloc = Z_NULL;
|
|
cl->compStream.zfree = Z_NULL;
|
|
cl->compStream.opaque = Z_NULL;
|
|
|
|
cl->zlibCompressLevel = 5;
|
|
#endif
|
|
|
|
cl->progressiveSliceY = 0;
|
|
|
|
cl->extensions = NULL;
|
|
|
|
cl->lastPtrX = -1;
|
|
|
|
#ifdef LIBVNCSERVER_WITH_WEBSOCKETS
|
|
/*
|
|
* Wait a few ms for the client to send one of:
|
|
* - Flash policy request
|
|
* - WebSockets connection (TLS/SSL or plain)
|
|
*/
|
|
if (!webSocketsCheck(cl)) {
|
|
/* Error reporting handled in webSocketsHandshake */
|
|
rfbCloseClient(cl);
|
|
rfbClientConnectionGone(cl);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
sprintf(pv,rfbProtocolVersionFormat,rfbScreen->protocolMajorVersion,
|
|
rfbScreen->protocolMinorVersion);
|
|
|
|
if (rfbWriteExact(cl, pv, sz_rfbProtocolVersionMsg) < 0) {
|
|
rfbLogPerror("rfbNewClient: write");
|
|
rfbCloseClient(cl);
|
|
rfbClientConnectionGone(cl);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
for(extension = rfbGetExtensionIterator(); extension;
|
|
extension=extension->next) {
|
|
void* data = NULL;
|
|
/* if the extension does not have a newClient method, it wants
|
|
* to be initialized later. */
|
|
if(extension->newClient && extension->newClient(cl, &data))
|
|
rfbEnableExtension(cl, extension, data);
|
|
}
|
|
rfbReleaseExtensionIterator();
|
|
|
|
switch (cl->screen->newClientHook(cl)) {
|
|
case RFB_CLIENT_ON_HOLD:
|
|
cl->onHold = TRUE;
|
|
break;
|
|
case RFB_CLIENT_ACCEPT:
|
|
cl->onHold = FALSE;
|
|
break;
|
|
case RFB_CLIENT_REFUSE:
|
|
rfbCloseClient(cl);
|
|
rfbClientConnectionGone(cl);
|
|
cl = NULL;
|
|
break;
|
|
}
|
|
return cl;
|
|
}
|
|
|
|
rfbClientPtr
|
|
rfbNewClient(rfbScreenInfoPtr rfbScreen,
|
|
int sock)
|
|
{
|
|
return(rfbNewTCPOrUDPClient(rfbScreen,sock,FALSE));
|
|
}
|
|
|
|
rfbClientPtr
|
|
rfbNewUDPClient(rfbScreenInfoPtr rfbScreen)
|
|
{
|
|
return((rfbScreen->udpClient=
|
|
rfbNewTCPOrUDPClient(rfbScreen,rfbScreen->udpSock,TRUE)));
|
|
}
|
|
|
|
/*
|
|
* rfbClientConnectionGone is called from sockets.c just after a connection
|
|
* has gone away.
|
|
*/
|
|
|
|
void
|
|
rfbClientConnectionGone(rfbClientPtr cl)
|
|
{
|
|
#if defined(LIBVNCSERVER_HAVE_LIBZ) && defined(LIBVNCSERVER_HAVE_LIBJPEG)
|
|
int i;
|
|
#endif
|
|
|
|
LOCK(rfbClientListMutex);
|
|
|
|
if (cl->prev)
|
|
cl->prev->next = cl->next;
|
|
else
|
|
cl->screen->clientHead = cl->next;
|
|
if (cl->next)
|
|
cl->next->prev = cl->prev;
|
|
|
|
UNLOCK(rfbClientListMutex);
|
|
|
|
#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
|
|
if(cl->screen->backgroundLoop != FALSE) {
|
|
int i;
|
|
do {
|
|
LOCK(cl->refCountMutex);
|
|
i=cl->refCount;
|
|
if(i>0)
|
|
WAIT(cl->deleteCond,cl->refCountMutex);
|
|
UNLOCK(cl->refCountMutex);
|
|
} while(i>0);
|
|
}
|
|
#endif
|
|
|
|
if(cl->sock>=0)
|
|
close(cl->sock);
|
|
|
|
if (cl->scaledScreen!=NULL)
|
|
cl->scaledScreen->scaledScreenRefCount--;
|
|
|
|
#ifdef LIBVNCSERVER_HAVE_LIBZ
|
|
rfbFreeZrleData(cl);
|
|
#endif
|
|
|
|
rfbFreeUltraData(cl);
|
|
|
|
/* free buffers holding pixel data before and after encoding */
|
|
free(cl->beforeEncBuf);
|
|
free(cl->afterEncBuf);
|
|
|
|
if(cl->sock>=0)
|
|
FD_CLR(cl->sock,&(cl->screen->allFds));
|
|
|
|
cl->clientGoneHook(cl);
|
|
|
|
rfbLog("Client %s gone\n",cl->host);
|
|
free(cl->host);
|
|
|
|
#ifdef LIBVNCSERVER_HAVE_LIBZ
|
|
/* Release the compression state structures if any. */
|
|
if ( cl->compStreamInited ) {
|
|
deflateEnd( &(cl->compStream) );
|
|
}
|
|
|
|
#ifdef LIBVNCSERVER_HAVE_LIBJPEG
|
|
for (i = 0; i < 4; i++) {
|
|
if (cl->zsActive[i])
|
|
deflateEnd(&cl->zsStruct[i]);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
if (cl->screen->pointerClient == cl)
|
|
cl->screen->pointerClient = NULL;
|
|
|
|
sraRgnDestroy(cl->modifiedRegion);
|
|
sraRgnDestroy(cl->requestedRegion);
|
|
sraRgnDestroy(cl->copyRegion);
|
|
|
|
if (cl->translateLookupTable) free(cl->translateLookupTable);
|
|
|
|
TINI_COND(cl->updateCond);
|
|
TINI_MUTEX(cl->updateMutex);
|
|
|
|
/* make sure outputMutex is unlocked before destroying */
|
|
LOCK(cl->outputMutex);
|
|
UNLOCK(cl->outputMutex);
|
|
TINI_MUTEX(cl->outputMutex);
|
|
|
|
LOCK(cl->sendMutex);
|
|
UNLOCK(cl->sendMutex);
|
|
TINI_MUTEX(cl->sendMutex);
|
|
|
|
#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
|
|
close(cl->pipe_notify_client_thread[0]);
|
|
close(cl->pipe_notify_client_thread[1]);
|
|
#endif
|
|
|
|
rfbPrintStats(cl);
|
|
rfbResetStats(cl);
|
|
|
|
free(cl);
|
|
}
|
|
|
|
|
|
/*
|
|
* rfbProcessClientMessage is called when there is data to read from a client.
|
|
*/
|
|
|
|
void
|
|
rfbProcessClientMessage(rfbClientPtr cl)
|
|
{
|
|
switch (cl->state) {
|
|
case RFB_PROTOCOL_VERSION:
|
|
rfbProcessClientProtocolVersion(cl);
|
|
return;
|
|
case RFB_SECURITY_TYPE:
|
|
rfbProcessClientSecurityType(cl);
|
|
return;
|
|
case RFB_AUTHENTICATION:
|
|
rfbAuthProcessClientMessage(cl);
|
|
return;
|
|
case RFB_INITIALISATION:
|
|
case RFB_INITIALISATION_SHARED:
|
|
rfbProcessClientInitMessage(cl);
|
|
return;
|
|
default:
|
|
rfbProcessClientNormalMessage(cl);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* rfbProcessClientProtocolVersion is called when the client sends its
|
|
* protocol version.
|
|
*/
|
|
|
|
static void
|
|
rfbProcessClientProtocolVersion(rfbClientPtr cl)
|
|
{
|
|
rfbProtocolVersionMsg pv;
|
|
int n, major_, minor_;
|
|
|
|
if ((n = rfbReadExact(cl, pv, sz_rfbProtocolVersionMsg)) <= 0) {
|
|
if (n == 0)
|
|
rfbLog("rfbProcessClientProtocolVersion: client gone\n");
|
|
else
|
|
rfbLogPerror("rfbProcessClientProtocolVersion: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
pv[sz_rfbProtocolVersionMsg] = 0;
|
|
if (sscanf(pv,rfbProtocolVersionFormat,&major_,&minor_) != 2) {
|
|
rfbErr("rfbProcessClientProtocolVersion: not a valid RFB client: %s\n", pv);
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
rfbLog("Client Protocol Version %d.%d\n", major_, minor_);
|
|
|
|
if (major_ != rfbProtocolMajorVersion) {
|
|
rfbErr("RFB protocol version mismatch - server %d.%d, client %d.%d",
|
|
cl->screen->protocolMajorVersion, cl->screen->protocolMinorVersion,
|
|
major_,minor_);
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
/* Check for the minor version use either of the two standard version of RFB */
|
|
/*
|
|
* UltraVNC Viewer detects FileTransfer compatible servers via rfb versions
|
|
* 3.4, 3.6, 3.14, 3.16
|
|
* It's a bad method, but it is what they use to enable features...
|
|
* maintaining RFB version compatibility across multiple servers is a pain
|
|
* Should use something like ServerIdentity encoding
|
|
*/
|
|
cl->protocolMajorVersion = major_;
|
|
cl->protocolMinorVersion = minor_;
|
|
|
|
rfbLog("Protocol version sent %d.%d, using %d.%d\n",
|
|
major_, minor_, rfbProtocolMajorVersion, cl->protocolMinorVersion);
|
|
|
|
rfbAuthNewClient(cl);
|
|
}
|
|
|
|
|
|
void
|
|
rfbClientSendString(rfbClientPtr cl, const char *reason)
|
|
{
|
|
char *buf;
|
|
int len = strlen(reason);
|
|
|
|
rfbLog("rfbClientSendString(\"%s\")\n", reason);
|
|
|
|
buf = (char *)malloc(4 + len);
|
|
((uint32_t *)buf)[0] = Swap32IfLE(len);
|
|
memcpy(buf + 4, reason, len);
|
|
|
|
if (rfbWriteExact(cl, buf, 4 + len) < 0)
|
|
rfbLogPerror("rfbClientSendString: write");
|
|
free(buf);
|
|
|
|
rfbCloseClient(cl);
|
|
}
|
|
|
|
/*
|
|
* rfbClientConnFailed is called when a client connection has failed either
|
|
* because it talks the wrong protocol or it has failed authentication.
|
|
*/
|
|
|
|
void
|
|
rfbClientConnFailed(rfbClientPtr cl,
|
|
const char *reason)
|
|
{
|
|
char *buf;
|
|
int len = strlen(reason);
|
|
|
|
rfbLog("rfbClientConnFailed(\"%s\")\n", reason);
|
|
|
|
buf = (char *)malloc(8 + len);
|
|
((uint32_t *)buf)[0] = Swap32IfLE(rfbConnFailed);
|
|
((uint32_t *)buf)[1] = Swap32IfLE(len);
|
|
memcpy(buf + 8, reason, len);
|
|
|
|
if (rfbWriteExact(cl, buf, 8 + len) < 0)
|
|
rfbLogPerror("rfbClientConnFailed: write");
|
|
free(buf);
|
|
|
|
rfbCloseClient(cl);
|
|
}
|
|
|
|
|
|
/*
|
|
* rfbProcessClientInitMessage is called when the client sends its
|
|
* initialisation message.
|
|
*/
|
|
|
|
static void
|
|
rfbProcessClientInitMessage(rfbClientPtr cl)
|
|
{
|
|
rfbClientInitMsg ci;
|
|
union {
|
|
char buf[256];
|
|
rfbServerInitMsg si;
|
|
} u;
|
|
int len, n;
|
|
rfbClientIteratorPtr iterator;
|
|
rfbClientPtr otherCl;
|
|
rfbExtensionData* extension;
|
|
|
|
if (cl->state == RFB_INITIALISATION_SHARED) {
|
|
/* In this case behave as though an implicit ClientInit message has
|
|
* already been received with a shared-flag of true. */
|
|
ci.shared = 1;
|
|
/* Avoid the possibility of exposing the RFB_INITIALISATION_SHARED
|
|
* state to calling software. */
|
|
cl->state = RFB_INITIALISATION;
|
|
} else {
|
|
if ((n = rfbReadExact(cl, (char *)&ci,sz_rfbClientInitMsg)) <= 0) {
|
|
if (n == 0)
|
|
rfbLog("rfbProcessClientInitMessage: client gone\n");
|
|
else
|
|
rfbLogPerror("rfbProcessClientInitMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
}
|
|
|
|
memset(u.buf,0,sizeof(u.buf));
|
|
|
|
u.si.framebufferWidth = Swap16IfLE(cl->screen->width);
|
|
u.si.framebufferHeight = Swap16IfLE(cl->screen->height);
|
|
u.si.format = cl->screen->serverFormat;
|
|
u.si.format.redMax = Swap16IfLE(u.si.format.redMax);
|
|
u.si.format.greenMax = Swap16IfLE(u.si.format.greenMax);
|
|
u.si.format.blueMax = Swap16IfLE(u.si.format.blueMax);
|
|
|
|
strncpy(u.buf + sz_rfbServerInitMsg, cl->screen->desktopName, 127);
|
|
len = strlen(u.buf + sz_rfbServerInitMsg);
|
|
u.si.nameLength = Swap32IfLE(len);
|
|
|
|
if (rfbWriteExact(cl, u.buf, sz_rfbServerInitMsg + len) < 0) {
|
|
rfbLogPerror("rfbProcessClientInitMessage: write");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
for(extension = cl->extensions; extension;) {
|
|
rfbExtensionData* next = extension->next;
|
|
if(extension->extension->init &&
|
|
!extension->extension->init(cl, extension->data))
|
|
/* extension requested that it be removed */
|
|
rfbDisableExtension(cl, extension->extension);
|
|
extension = next;
|
|
}
|
|
|
|
cl->state = RFB_NORMAL;
|
|
|
|
if (!cl->reverseConnection &&
|
|
(cl->screen->neverShared || (!cl->screen->alwaysShared && !ci.shared))) {
|
|
|
|
if (cl->screen->dontDisconnect) {
|
|
iterator = rfbGetClientIterator(cl->screen);
|
|
while ((otherCl = rfbClientIteratorNext(iterator)) != NULL) {
|
|
if ((otherCl != cl) && (otherCl->state == RFB_NORMAL)) {
|
|
rfbLog("-dontdisconnect: Not shared & existing client\n");
|
|
rfbLog(" refusing new client %s\n", cl->host);
|
|
rfbCloseClient(cl);
|
|
rfbReleaseClientIterator(iterator);
|
|
return;
|
|
}
|
|
}
|
|
rfbReleaseClientIterator(iterator);
|
|
} else {
|
|
iterator = rfbGetClientIterator(cl->screen);
|
|
while ((otherCl = rfbClientIteratorNext(iterator)) != NULL) {
|
|
if ((otherCl != cl) && (otherCl->state == RFB_NORMAL)) {
|
|
rfbLog("Not shared - closing connection to client %s\n",
|
|
otherCl->host);
|
|
rfbCloseClient(otherCl);
|
|
}
|
|
}
|
|
rfbReleaseClientIterator(iterator);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The values come in based on the scaled screen, we need to convert them to
|
|
* values based on the man screen's coordinate system
|
|
*/
|
|
static rfbBool rectSwapIfLEAndClip(uint16_t* x,uint16_t* y,uint16_t* w,uint16_t* h,
|
|
rfbClientPtr cl)
|
|
{
|
|
int x1=Swap16IfLE(*x);
|
|
int y1=Swap16IfLE(*y);
|
|
int w1=Swap16IfLE(*w);
|
|
int h1=Swap16IfLE(*h);
|
|
|
|
rfbScaledCorrection(cl->scaledScreen, cl->screen, &x1, &y1, &w1, &h1, "rectSwapIfLEAndClip");
|
|
*x = x1;
|
|
*y = y1;
|
|
*w = w1;
|
|
*h = h1;
|
|
|
|
if(*w>cl->screen->width-*x)
|
|
*w=cl->screen->width-*x;
|
|
/* possible underflow */
|
|
if(*w>cl->screen->width-*x)
|
|
return FALSE;
|
|
if(*h>cl->screen->height-*y)
|
|
*h=cl->screen->height-*y;
|
|
if(*h>cl->screen->height-*y)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Send keyboard state (PointerPos pseudo-encoding).
|
|
*/
|
|
|
|
rfbBool
|
|
rfbSendKeyboardLedState(rfbClientPtr cl)
|
|
{
|
|
rfbFramebufferUpdateRectHeader rect;
|
|
|
|
if (cl->ublen + sz_rfbFramebufferUpdateRectHeader > UPDATE_BUF_SIZE) {
|
|
if (!rfbSendUpdateBuf(cl))
|
|
return FALSE;
|
|
}
|
|
|
|
rect.encoding = Swap32IfLE(rfbEncodingKeyboardLedState);
|
|
rect.r.x = Swap16IfLE(cl->lastKeyboardLedState);
|
|
rect.r.y = 0;
|
|
rect.r.w = 0;
|
|
rect.r.h = 0;
|
|
|
|
memcpy(&cl->updateBuf[cl->ublen], (char *)&rect,
|
|
sz_rfbFramebufferUpdateRectHeader);
|
|
cl->ublen += sz_rfbFramebufferUpdateRectHeader;
|
|
|
|
rfbStatRecordEncodingSent(cl, rfbEncodingKeyboardLedState, sz_rfbFramebufferUpdateRectHeader, sz_rfbFramebufferUpdateRectHeader);
|
|
|
|
if (!rfbSendUpdateBuf(cl))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
#define rfbSetBit(buffer, position) (buffer[(position & 255) / 8] |= (1 << (position % 8)))
|
|
|
|
/*
|
|
* Send rfbEncodingSupportedMessages.
|
|
*/
|
|
|
|
rfbBool
|
|
rfbSendSupportedMessages(rfbClientPtr cl)
|
|
{
|
|
rfbFramebufferUpdateRectHeader rect;
|
|
rfbSupportedMessages msgs;
|
|
|
|
if (cl->ublen + sz_rfbFramebufferUpdateRectHeader
|
|
+ sz_rfbSupportedMessages > UPDATE_BUF_SIZE) {
|
|
if (!rfbSendUpdateBuf(cl))
|
|
return FALSE;
|
|
}
|
|
|
|
rect.encoding = Swap32IfLE(rfbEncodingSupportedMessages);
|
|
rect.r.x = 0;
|
|
rect.r.y = 0;
|
|
rect.r.w = Swap16IfLE(sz_rfbSupportedMessages);
|
|
rect.r.h = 0;
|
|
|
|
memcpy(&cl->updateBuf[cl->ublen], (char *)&rect,
|
|
sz_rfbFramebufferUpdateRectHeader);
|
|
cl->ublen += sz_rfbFramebufferUpdateRectHeader;
|
|
|
|
memset((char *)&msgs, 0, sz_rfbSupportedMessages);
|
|
rfbSetBit(msgs.client2server, rfbSetPixelFormat);
|
|
rfbSetBit(msgs.client2server, rfbFixColourMapEntries);
|
|
rfbSetBit(msgs.client2server, rfbSetEncodings);
|
|
rfbSetBit(msgs.client2server, rfbFramebufferUpdateRequest);
|
|
rfbSetBit(msgs.client2server, rfbKeyEvent);
|
|
rfbSetBit(msgs.client2server, rfbPointerEvent);
|
|
rfbSetBit(msgs.client2server, rfbClientCutText);
|
|
rfbSetBit(msgs.client2server, rfbFileTransfer);
|
|
rfbSetBit(msgs.client2server, rfbSetScale);
|
|
/*rfbSetBit(msgs.client2server, rfbSetServerInput); */
|
|
/*rfbSetBit(msgs.client2server, rfbSetSW); */
|
|
/*rfbSetBit(msgs.client2server, rfbTextChat); */
|
|
rfbSetBit(msgs.client2server, rfbPalmVNCSetScaleFactor);
|
|
|
|
rfbSetBit(msgs.server2client, rfbFramebufferUpdate);
|
|
rfbSetBit(msgs.server2client, rfbSetColourMapEntries);
|
|
rfbSetBit(msgs.server2client, rfbBell);
|
|
rfbSetBit(msgs.server2client, rfbServerCutText);
|
|
rfbSetBit(msgs.server2client, rfbResizeFrameBuffer);
|
|
rfbSetBit(msgs.server2client, rfbPalmVNCReSizeFrameBuffer);
|
|
|
|
if (cl->screen->xvpHook) {
|
|
rfbSetBit(msgs.client2server, rfbXvp);
|
|
rfbSetBit(msgs.server2client, rfbXvp);
|
|
}
|
|
|
|
memcpy(&cl->updateBuf[cl->ublen], (char *)&msgs, sz_rfbSupportedMessages);
|
|
cl->ublen += sz_rfbSupportedMessages;
|
|
|
|
rfbStatRecordEncodingSent(cl, rfbEncodingSupportedMessages,
|
|
sz_rfbFramebufferUpdateRectHeader+sz_rfbSupportedMessages,
|
|
sz_rfbFramebufferUpdateRectHeader+sz_rfbSupportedMessages);
|
|
if (!rfbSendUpdateBuf(cl))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Send rfbEncodingSupportedEncodings.
|
|
*/
|
|
|
|
rfbBool
|
|
rfbSendSupportedEncodings(rfbClientPtr cl)
|
|
{
|
|
rfbFramebufferUpdateRectHeader rect;
|
|
static uint32_t supported[] = {
|
|
rfbEncodingRaw,
|
|
rfbEncodingCopyRect,
|
|
rfbEncodingRRE,
|
|
rfbEncodingCoRRE,
|
|
rfbEncodingHextile,
|
|
#ifdef LIBVNCSERVER_HAVE_LIBZ
|
|
rfbEncodingZlib,
|
|
rfbEncodingZRLE,
|
|
rfbEncodingZYWRLE,
|
|
#endif
|
|
#ifdef LIBVNCSERVER_HAVE_LIBJPEG
|
|
rfbEncodingTight,
|
|
#endif
|
|
#ifdef LIBVNCSERVER_HAVE_LIBPNG
|
|
rfbEncodingTightPng,
|
|
#endif
|
|
rfbEncodingUltra,
|
|
rfbEncodingUltraZip,
|
|
rfbEncodingXCursor,
|
|
rfbEncodingRichCursor,
|
|
rfbEncodingPointerPos,
|
|
rfbEncodingLastRect,
|
|
rfbEncodingNewFBSize,
|
|
rfbEncodingKeyboardLedState,
|
|
rfbEncodingSupportedMessages,
|
|
rfbEncodingSupportedEncodings,
|
|
rfbEncodingServerIdentity,
|
|
};
|
|
uint32_t nEncodings = sizeof(supported) / sizeof(supported[0]), i;
|
|
|
|
/* think rfbSetEncodingsMsg */
|
|
|
|
if (cl->ublen + sz_rfbFramebufferUpdateRectHeader
|
|
+ (nEncodings * sizeof(uint32_t)) > UPDATE_BUF_SIZE) {
|
|
if (!rfbSendUpdateBuf(cl))
|
|
return FALSE;
|
|
}
|
|
|
|
rect.encoding = Swap32IfLE(rfbEncodingSupportedEncodings);
|
|
rect.r.x = 0;
|
|
rect.r.y = 0;
|
|
rect.r.w = Swap16IfLE(nEncodings * sizeof(uint32_t));
|
|
rect.r.h = Swap16IfLE(nEncodings);
|
|
|
|
memcpy(&cl->updateBuf[cl->ublen], (char *)&rect,
|
|
sz_rfbFramebufferUpdateRectHeader);
|
|
cl->ublen += sz_rfbFramebufferUpdateRectHeader;
|
|
|
|
for (i = 0; i < nEncodings; i++) {
|
|
uint32_t encoding = Swap32IfLE(supported[i]);
|
|
memcpy(&cl->updateBuf[cl->ublen], (char *)&encoding, sizeof(encoding));
|
|
cl->ublen += sizeof(encoding);
|
|
}
|
|
|
|
rfbStatRecordEncodingSent(cl, rfbEncodingSupportedEncodings,
|
|
sz_rfbFramebufferUpdateRectHeader+(nEncodings * sizeof(uint32_t)),
|
|
sz_rfbFramebufferUpdateRectHeader+(nEncodings * sizeof(uint32_t)));
|
|
|
|
if (!rfbSendUpdateBuf(cl))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
void
|
|
rfbSetServerVersionIdentity(rfbScreenInfoPtr screen, char *fmt, ...)
|
|
{
|
|
char buffer[256];
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(buffer, sizeof(buffer)-1, fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (screen->versionString!=NULL) free(screen->versionString);
|
|
screen->versionString = strdup(buffer);
|
|
}
|
|
|
|
/*
|
|
* Send rfbEncodingServerIdentity.
|
|
*/
|
|
|
|
rfbBool
|
|
rfbSendServerIdentity(rfbClientPtr cl)
|
|
{
|
|
rfbFramebufferUpdateRectHeader rect;
|
|
char buffer[512];
|
|
|
|
/* tack on our library version */
|
|
snprintf(buffer,sizeof(buffer)-1, "%s (%s)",
|
|
(cl->screen->versionString==NULL ? "unknown" : cl->screen->versionString),
|
|
LIBVNCSERVER_PACKAGE_STRING);
|
|
|
|
if (cl->ublen + sz_rfbFramebufferUpdateRectHeader
|
|
+ (strlen(buffer)+1) > UPDATE_BUF_SIZE) {
|
|
if (!rfbSendUpdateBuf(cl))
|
|
return FALSE;
|
|
}
|
|
|
|
rect.encoding = Swap32IfLE(rfbEncodingServerIdentity);
|
|
rect.r.x = 0;
|
|
rect.r.y = 0;
|
|
rect.r.w = Swap16IfLE(strlen(buffer)+1);
|
|
rect.r.h = 0;
|
|
|
|
memcpy(&cl->updateBuf[cl->ublen], (char *)&rect,
|
|
sz_rfbFramebufferUpdateRectHeader);
|
|
cl->ublen += sz_rfbFramebufferUpdateRectHeader;
|
|
|
|
memcpy(&cl->updateBuf[cl->ublen], buffer, strlen(buffer)+1);
|
|
cl->ublen += strlen(buffer)+1;
|
|
|
|
rfbStatRecordEncodingSent(cl, rfbEncodingServerIdentity,
|
|
sz_rfbFramebufferUpdateRectHeader+strlen(buffer)+1,
|
|
sz_rfbFramebufferUpdateRectHeader+strlen(buffer)+1);
|
|
|
|
|
|
if (!rfbSendUpdateBuf(cl))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Send an xvp server message
|
|
*/
|
|
|
|
rfbBool
|
|
rfbSendXvp(rfbClientPtr cl, uint8_t version, uint8_t code)
|
|
{
|
|
rfbXvpMsg xvp;
|
|
|
|
xvp.type = rfbXvp;
|
|
xvp.pad = 0;
|
|
xvp.version = version;
|
|
xvp.code = code;
|
|
|
|
LOCK(cl->sendMutex);
|
|
if (rfbWriteExact(cl, (char *)&xvp, sz_rfbXvpMsg) < 0) {
|
|
rfbLogPerror("rfbSendXvp: write");
|
|
rfbCloseClient(cl);
|
|
}
|
|
UNLOCK(cl->sendMutex);
|
|
|
|
rfbStatRecordMessageSent(cl, rfbXvp, sz_rfbXvpMsg, sz_rfbXvpMsg);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
rfbBool rfbSendTextChatMessage(rfbClientPtr cl, uint32_t length, char *buffer)
|
|
{
|
|
rfbTextChatMsg tc;
|
|
int bytesToSend=0;
|
|
|
|
memset((char *)&tc, 0, sizeof(tc));
|
|
tc.type = rfbTextChat;
|
|
tc.length = Swap32IfLE(length);
|
|
|
|
switch(length) {
|
|
case rfbTextChatOpen:
|
|
case rfbTextChatClose:
|
|
case rfbTextChatFinished:
|
|
bytesToSend=0;
|
|
break;
|
|
default:
|
|
bytesToSend=length;
|
|
if (bytesToSend>rfbTextMaxSize)
|
|
bytesToSend=rfbTextMaxSize;
|
|
}
|
|
|
|
if (cl->ublen + sz_rfbTextChatMsg + bytesToSend > UPDATE_BUF_SIZE) {
|
|
if (!rfbSendUpdateBuf(cl))
|
|
return FALSE;
|
|
}
|
|
|
|
memcpy(&cl->updateBuf[cl->ublen], (char *)&tc, sz_rfbTextChatMsg);
|
|
cl->ublen += sz_rfbTextChatMsg;
|
|
if (bytesToSend>0) {
|
|
memcpy(&cl->updateBuf[cl->ublen], buffer, bytesToSend);
|
|
cl->ublen += bytesToSend;
|
|
}
|
|
rfbStatRecordMessageSent(cl, rfbTextChat, sz_rfbTextChatMsg+bytesToSend, sz_rfbTextChatMsg+bytesToSend);
|
|
|
|
if (!rfbSendUpdateBuf(cl))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#define FILEXFER_ALLOWED_OR_CLOSE_AND_RETURN(msg, cl, ret) \
|
|
if ((cl->screen->getFileTransferPermission != NULL \
|
|
&& cl->screen->getFileTransferPermission(cl) != TRUE) \
|
|
|| cl->screen->permitFileTransfer != TRUE) { \
|
|
rfbLog("%sUltra File Transfer is disabled, dropping client: %s\n", msg, cl->host); \
|
|
rfbCloseClient(cl); \
|
|
return ret; \
|
|
}
|
|
|
|
int DB = 1;
|
|
|
|
rfbBool rfbSendFileTransferMessage(rfbClientPtr cl, uint8_t contentType, uint8_t contentParam, uint32_t size, uint32_t length, const char *buffer)
|
|
{
|
|
rfbFileTransferMsg ft;
|
|
ft.type = rfbFileTransfer;
|
|
ft.contentType = contentType;
|
|
ft.contentParam = contentParam;
|
|
ft.pad = 0; /* UltraVNC did not Swap16LE(ft.contentParam) (Looks like it might be BigEndian) */
|
|
ft.size = Swap32IfLE(size);
|
|
ft.length = Swap32IfLE(length);
|
|
|
|
FILEXFER_ALLOWED_OR_CLOSE_AND_RETURN("", cl, FALSE);
|
|
/*
|
|
rfbLog("rfbSendFileTransferMessage( %dtype, %dparam, %dsize, %dlen, %p)\n", contentType, contentParam, size, length, buffer);
|
|
*/
|
|
LOCK(cl->sendMutex);
|
|
if (rfbWriteExact(cl, (char *)&ft, sz_rfbFileTransferMsg) < 0) {
|
|
rfbLogPerror("rfbSendFileTransferMessage: write");
|
|
rfbCloseClient(cl);
|
|
UNLOCK(cl->sendMutex);
|
|
return FALSE;
|
|
}
|
|
|
|
if (length>0)
|
|
{
|
|
if (rfbWriteExact(cl, buffer, length) < 0) {
|
|
rfbLogPerror("rfbSendFileTransferMessage: write");
|
|
rfbCloseClient(cl);
|
|
UNLOCK(cl->sendMutex);
|
|
return FALSE;
|
|
}
|
|
}
|
|
UNLOCK(cl->sendMutex);
|
|
|
|
rfbStatRecordMessageSent(cl, rfbFileTransfer, sz_rfbFileTransferMsg+length, sz_rfbFileTransferMsg+length);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*
|
|
* UltraVNC uses Windows Structures
|
|
*/
|
|
#define MAX_PATH 260
|
|
|
|
typedef struct {
|
|
uint32_t dwLowDateTime;
|
|
uint32_t dwHighDateTime;
|
|
} RFB_FILETIME;
|
|
|
|
typedef struct {
|
|
uint32_t dwFileAttributes;
|
|
RFB_FILETIME ftCreationTime;
|
|
RFB_FILETIME ftLastAccessTime;
|
|
RFB_FILETIME ftLastWriteTime;
|
|
uint32_t nFileSizeHigh;
|
|
uint32_t nFileSizeLow;
|
|
uint32_t dwReserved0;
|
|
uint32_t dwReserved1;
|
|
uint8_t cFileName[ MAX_PATH ];
|
|
uint8_t cAlternateFileName[ 14 ];
|
|
} RFB_FIND_DATA;
|
|
|
|
#define RFB_FILE_ATTRIBUTE_READONLY 0x1
|
|
#define RFB_FILE_ATTRIBUTE_HIDDEN 0x2
|
|
#define RFB_FILE_ATTRIBUTE_SYSTEM 0x4
|
|
#define RFB_FILE_ATTRIBUTE_DIRECTORY 0x10
|
|
#define RFB_FILE_ATTRIBUTE_ARCHIVE 0x20
|
|
#define RFB_FILE_ATTRIBUTE_NORMAL 0x80
|
|
#define RFB_FILE_ATTRIBUTE_TEMPORARY 0x100
|
|
#define RFB_FILE_ATTRIBUTE_COMPRESSED 0x800
|
|
|
|
rfbBool rfbFilenameTranslate2UNIX(rfbClientPtr cl, /* in */ char *path, /* out */ char *unixPath, size_t unixPathMaxLen)
|
|
{
|
|
int x;
|
|
char *home=NULL;
|
|
|
|
FILEXFER_ALLOWED_OR_CLOSE_AND_RETURN("", cl, FALSE);
|
|
|
|
/*
|
|
* Do not use strncpy() - truncating the file name would probably have undesirable side effects
|
|
* Instead check if destination buffer is big enough
|
|
*/
|
|
if (strlen(path) >= unixPathMaxLen)
|
|
return FALSE;
|
|
|
|
/* C: */
|
|
if (path[0]=='C' && path[1]==':')
|
|
strcpy(unixPath, &path[2]);
|
|
else
|
|
{
|
|
home = getenv("HOME");
|
|
if (home!=NULL)
|
|
{
|
|
/* Re-check buffer size */
|
|
if ((strlen(path) + strlen(home) + 1) >= unixPathMaxLen)
|
|
return FALSE;
|
|
|
|
strcpy(unixPath, home);
|
|
strcat(unixPath,"/");
|
|
strcat(unixPath, path);
|
|
}
|
|
else
|
|
strcpy(unixPath, path);
|
|
}
|
|
for (x=0;x<strlen(unixPath);x++)
|
|
if (unixPath[x]=='\\') unixPath[x]='/';
|
|
return TRUE;
|
|
}
|
|
|
|
rfbBool rfbFilenameTranslate2DOS(rfbClientPtr cl, char *unixPath, char *path)
|
|
{
|
|
int x;
|
|
|
|
FILEXFER_ALLOWED_OR_CLOSE_AND_RETURN("", cl, FALSE);
|
|
|
|
sprintf(path,"C:%s", unixPath);
|
|
for (x=2;x<strlen(path);x++)
|
|
if (path[x]=='/') path[x]='\\';
|
|
return TRUE;
|
|
}
|
|
|
|
rfbBool rfbSendDirContent(rfbClientPtr cl, int length, char *buffer)
|
|
{
|
|
char retfilename[MAX_PATH];
|
|
char path[MAX_PATH];
|
|
struct stat statbuf;
|
|
RFB_FIND_DATA win32filename;
|
|
int nOptLen = 0, retval=0;
|
|
#ifdef WIN32
|
|
WIN32_FIND_DATAA winFindData;
|
|
HANDLE findHandle;
|
|
int pathLen, basePathLength;
|
|
char *basePath;
|
|
#else
|
|
DIR *dirp=NULL;
|
|
struct dirent *direntp=NULL;
|
|
#endif
|
|
|
|
FILEXFER_ALLOWED_OR_CLOSE_AND_RETURN("", cl, FALSE);
|
|
|
|
/* Client thinks we are Winblows */
|
|
if (!rfbFilenameTranslate2UNIX(cl, buffer, path, sizeof(path)))
|
|
return FALSE;
|
|
|
|
if (DB) rfbLog("rfbProcessFileTransfer() rfbDirContentRequest: rfbRDirContent: \"%s\"->\"%s\"\n",buffer, path);
|
|
|
|
#ifdef WIN32
|
|
// Create a search string, like C:\folder\*
|
|
|
|
pathLen = strlen(path);
|
|
basePath = malloc(pathLen + 3);
|
|
memcpy(basePath, path, pathLen);
|
|
basePathLength = pathLen;
|
|
basePath[basePathLength] = '\\';
|
|
basePath[basePathLength + 1] = '*';
|
|
basePath[basePathLength + 2] = '\0';
|
|
|
|
// Start a search
|
|
memset(&winFindData, 0, sizeof(winFindData));
|
|
findHandle = FindFirstFileA(path, &winFindData);
|
|
free(basePath);
|
|
|
|
if (findHandle == INVALID_HANDLE_VALUE)
|
|
#else
|
|
dirp=opendir(path);
|
|
if (dirp==NULL)
|
|
#endif
|
|
return rfbSendFileTransferMessage(cl, rfbDirPacket, rfbADirectory, 0, 0, NULL);
|
|
|
|
/* send back the path name (necessary for links) */
|
|
if (rfbSendFileTransferMessage(cl, rfbDirPacket, rfbADirectory, 0, length, buffer)==FALSE) return FALSE;
|
|
|
|
#ifdef WIN32
|
|
while (findHandle != INVALID_HANDLE_VALUE)
|
|
#else
|
|
for (direntp=readdir(dirp); direntp!=NULL; direntp=readdir(dirp))
|
|
#endif
|
|
{
|
|
/* get stats */
|
|
#ifdef WIN32
|
|
snprintf(retfilename,sizeof(retfilename),"%s/%s", path, winFindData.cFileName);
|
|
#else
|
|
snprintf(retfilename,sizeof(retfilename),"%s/%s", path, direntp->d_name);
|
|
#endif
|
|
retval = stat(retfilename, &statbuf);
|
|
|
|
if (retval==0)
|
|
{
|
|
memset((char *)&win32filename, 0, sizeof(win32filename));
|
|
#ifdef WIN32
|
|
win32filename.dwFileAttributes = winFindData.dwFileAttributes;
|
|
win32filename.ftCreationTime.dwLowDateTime = winFindData.ftCreationTime.dwLowDateTime;
|
|
win32filename.ftCreationTime.dwHighDateTime = winFindData.ftCreationTime.dwHighDateTime;
|
|
win32filename.ftLastAccessTime.dwLowDateTime = winFindData.ftLastAccessTime.dwLowDateTime;
|
|
win32filename.ftLastAccessTime.dwHighDateTime = winFindData.ftLastAccessTime.dwHighDateTime;
|
|
win32filename.ftLastWriteTime.dwLowDateTime = winFindData.ftLastWriteTime.dwLowDateTime;
|
|
win32filename.ftLastWriteTime.dwHighDateTime = winFindData.ftLastWriteTime.dwHighDateTime;
|
|
win32filename.nFileSizeLow = winFindData.nFileSizeLow;
|
|
win32filename.nFileSizeHigh = winFindData.nFileSizeHigh;
|
|
win32filename.dwReserved0 = winFindData.dwReserved0;
|
|
win32filename.dwReserved1 = winFindData.dwReserved1;
|
|
strcpy((char *)win32filename.cFileName, winFindData.cFileName);
|
|
strcpy((char *)win32filename.cAlternateFileName, winFindData.cAlternateFileName);
|
|
#else
|
|
win32filename.dwFileAttributes = Swap32IfBE(RFB_FILE_ATTRIBUTE_NORMAL);
|
|
if (S_ISDIR(statbuf.st_mode))
|
|
win32filename.dwFileAttributes = Swap32IfBE(RFB_FILE_ATTRIBUTE_DIRECTORY);
|
|
win32filename.ftCreationTime.dwLowDateTime = Swap32IfBE(statbuf.st_ctime); /* Intel Order */
|
|
win32filename.ftCreationTime.dwHighDateTime = 0;
|
|
win32filename.ftLastAccessTime.dwLowDateTime = Swap32IfBE(statbuf.st_atime); /* Intel Order */
|
|
win32filename.ftLastAccessTime.dwHighDateTime = 0;
|
|
win32filename.ftLastWriteTime.dwLowDateTime = Swap32IfBE(statbuf.st_mtime); /* Intel Order */
|
|
win32filename.ftLastWriteTime.dwHighDateTime = 0;
|
|
win32filename.nFileSizeLow = Swap32IfBE(statbuf.st_size); /* Intel Order */
|
|
win32filename.nFileSizeHigh = 0;
|
|
win32filename.dwReserved0 = 0;
|
|
win32filename.dwReserved1 = 0;
|
|
|
|
/* If this had the full path, we would need to translate to DOS format ("C:\") */
|
|
/* rfbFilenameTranslate2DOS(cl, retfilename, win32filename.cFileName); */
|
|
strcpy((char *)win32filename.cFileName, direntp->d_name);
|
|
#endif
|
|
|
|
/* Do not show hidden files (but show how to move up the tree) */
|
|
if ((strcmp((char *)win32filename.cFileName, "..")==0) || (win32filename.cFileName[0]!='.'))
|
|
{
|
|
nOptLen = sizeof(RFB_FIND_DATA) - MAX_PATH - 14 + strlen((char *)win32filename.cFileName);
|
|
/*
|
|
rfbLog("rfbProcessFileTransfer() rfbDirContentRequest: rfbRDirContent: Sending \"%s\"\n", (char *)win32filename.cFileName);
|
|
*/
|
|
if (rfbSendFileTransferMessage(cl, rfbDirPacket, rfbADirectory, 0, nOptLen, (char *)&win32filename)==FALSE)
|
|
{
|
|
#ifdef WIN32
|
|
FindClose(findHandle);
|
|
#else
|
|
closedir(dirp);
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef WIN32
|
|
if (FindNextFileA(findHandle, &winFindData) == 0)
|
|
{
|
|
FindClose(findHandle);
|
|
findHandle = INVALID_HANDLE_VALUE;
|
|
}
|
|
#endif
|
|
}
|
|
#ifdef WIN32
|
|
if (findHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
FindClose(findHandle);
|
|
}
|
|
#else
|
|
closedir(dirp);
|
|
#endif
|
|
/* End of the transfer */
|
|
return rfbSendFileTransferMessage(cl, rfbDirPacket, 0, 0, 0, NULL);
|
|
}
|
|
|
|
|
|
char *rfbProcessFileTransferReadBuffer(rfbClientPtr cl, uint32_t length)
|
|
{
|
|
char *buffer=NULL;
|
|
int n=0;
|
|
|
|
FILEXFER_ALLOWED_OR_CLOSE_AND_RETURN("", cl, NULL);
|
|
/*
|
|
rfbLog("rfbProcessFileTransferReadBuffer(%dlen)\n", length);
|
|
*/
|
|
if (length>0) {
|
|
buffer=malloc(length+1);
|
|
if (buffer!=NULL) {
|
|
if ((n = rfbReadExact(cl, (char *)buffer, length)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessFileTransferReadBuffer: read");
|
|
rfbCloseClient(cl);
|
|
/* NOTE: don't forget to free(buffer) if you return early! */
|
|
if (buffer!=NULL) free(buffer);
|
|
return NULL;
|
|
}
|
|
/* Null Terminate */
|
|
buffer[length]=0;
|
|
}
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
|
|
rfbBool rfbSendFileTransferChunk(rfbClientPtr cl)
|
|
{
|
|
/* Allocate buffer for compression */
|
|
unsigned char readBuf[sz_rfbBlockSize];
|
|
int bytesRead=0;
|
|
int retval=0;
|
|
fd_set wfds;
|
|
struct timeval tv;
|
|
int n;
|
|
#ifdef LIBVNCSERVER_HAVE_LIBZ
|
|
unsigned char compBuf[sz_rfbBlockSize + 1024];
|
|
unsigned long nMaxCompSize = sizeof(compBuf);
|
|
int nRetC = 0;
|
|
#endif
|
|
|
|
/*
|
|
* Don't close the client if we get into this one because
|
|
* it is called from many places to service file transfers.
|
|
* Note that permitFileTransfer is checked first.
|
|
*/
|
|
if (cl->screen->permitFileTransfer != TRUE ||
|
|
(cl->screen->getFileTransferPermission != NULL
|
|
&& cl->screen->getFileTransferPermission(cl) != TRUE)) {
|
|
return TRUE;
|
|
}
|
|
|
|
/* If not sending, or no file open... Return as if we sent something! */
|
|
if ((cl->fileTransfer.fd!=-1) && (cl->fileTransfer.sending==1))
|
|
{
|
|
FD_ZERO(&wfds);
|
|
FD_SET(cl->sock, &wfds);
|
|
|
|
/* return immediately */
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 0;
|
|
n = select(cl->sock + 1, NULL, &wfds, NULL, &tv);
|
|
|
|
if (n<0) {
|
|
#ifdef WIN32
|
|
errno=WSAGetLastError();
|
|
#endif
|
|
rfbLog("rfbSendFileTransferChunk() select failed: %s\n", strerror(errno));
|
|
}
|
|
/* We have space on the transmit queue */
|
|
if (n > 0)
|
|
{
|
|
bytesRead = read(cl->fileTransfer.fd, readBuf, sz_rfbBlockSize);
|
|
switch (bytesRead) {
|
|
case 0:
|
|
/*
|
|
rfbLog("rfbSendFileTransferChunk(): End-Of-File Encountered\n");
|
|
*/
|
|
retval = rfbSendFileTransferMessage(cl, rfbEndOfFile, 0, 0, 0, NULL);
|
|
close(cl->fileTransfer.fd);
|
|
cl->fileTransfer.fd = -1;
|
|
cl->fileTransfer.sending = 0;
|
|
cl->fileTransfer.receiving = 0;
|
|
return retval;
|
|
case -1:
|
|
/* TODO : send an error msg to the client... */
|
|
#ifdef WIN32
|
|
errno=WSAGetLastError();
|
|
#endif
|
|
rfbLog("rfbSendFileTransferChunk(): %s\n",strerror(errno));
|
|
retval = rfbSendFileTransferMessage(cl, rfbAbortFileTransfer, 0, 0, 0, NULL);
|
|
close(cl->fileTransfer.fd);
|
|
cl->fileTransfer.fd = -1;
|
|
cl->fileTransfer.sending = 0;
|
|
cl->fileTransfer.receiving = 0;
|
|
return retval;
|
|
default:
|
|
/*
|
|
rfbLog("rfbSendFileTransferChunk(): Read %d bytes\n", bytesRead);
|
|
*/
|
|
if (!cl->fileTransfer.compressionEnabled)
|
|
return rfbSendFileTransferMessage(cl, rfbFilePacket, 0, 0, bytesRead, (char *)readBuf);
|
|
else
|
|
{
|
|
#ifdef LIBVNCSERVER_HAVE_LIBZ
|
|
nRetC = compress(compBuf, &nMaxCompSize, readBuf, bytesRead);
|
|
/*
|
|
rfbLog("Compressed the packet from %d -> %d bytes\n", nMaxCompSize, bytesRead);
|
|
*/
|
|
|
|
if ((nRetC==0) && (nMaxCompSize<bytesRead))
|
|
return rfbSendFileTransferMessage(cl, rfbFilePacket, 0, 1, nMaxCompSize, (char *)compBuf);
|
|
else
|
|
return rfbSendFileTransferMessage(cl, rfbFilePacket, 0, 0, bytesRead, (char *)readBuf);
|
|
#else
|
|
/* We do not support compression of the data stream */
|
|
return rfbSendFileTransferMessage(cl, rfbFilePacket, 0, 0, bytesRead, (char *)readBuf);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
rfbBool rfbProcessFileTransfer(rfbClientPtr cl, uint8_t contentType, uint8_t contentParam, uint32_t size, uint32_t length)
|
|
{
|
|
char *buffer=NULL, *p=NULL;
|
|
int retval=0;
|
|
char filename1[MAX_PATH];
|
|
char filename2[MAX_PATH];
|
|
char szFileTime[MAX_PATH];
|
|
struct stat statbuf;
|
|
uint32_t sizeHtmp=0;
|
|
int n=0;
|
|
char timespec[64];
|
|
#ifdef LIBVNCSERVER_HAVE_LIBZ
|
|
unsigned char compBuff[sz_rfbBlockSize];
|
|
unsigned long nRawBytes = sz_rfbBlockSize;
|
|
int nRet = 0;
|
|
#endif
|
|
|
|
FILEXFER_ALLOWED_OR_CLOSE_AND_RETURN("", cl, FALSE);
|
|
|
|
/*
|
|
rfbLog("rfbProcessFileTransfer(%dtype, %dparam, %dsize, %dlen)\n", contentType, contentParam, size, length);
|
|
*/
|
|
|
|
switch (contentType) {
|
|
case rfbDirContentRequest:
|
|
switch (contentParam) {
|
|
case rfbRDrivesList: /* Client requests the List of Local Drives */
|
|
/*
|
|
rfbLog("rfbProcessFileTransfer() rfbDirContentRequest: rfbRDrivesList:\n");
|
|
*/
|
|
/* Format when filled : "C:\<NULL>D:\<NULL>....Z:\<NULL><NULL>
|
|
*
|
|
* We replace the "\" char following the drive letter and ":"
|
|
* with a char corresponding to the type of drive
|
|
* We obtain something like "C:l<NULL>D:c<NULL>....Z:n\<NULL><NULL>"
|
|
* Isn't it ugly ?
|
|
* DRIVE_FIXED = 'l' (local?)
|
|
* DRIVE_REMOVABLE = 'f' (floppy?)
|
|
* DRIVE_CDROM = 'c'
|
|
* DRIVE_REMOTE = 'n'
|
|
*/
|
|
|
|
/* in unix, there are no 'drives' (We could list mount points though)
|
|
* We fake the root as a "C:" for the Winblows users
|
|
*/
|
|
filename2[0]='C';
|
|
filename2[1]=':';
|
|
filename2[2]='l';
|
|
filename2[3]=0;
|
|
filename2[4]=0;
|
|
retval = rfbSendFileTransferMessage(cl, rfbDirPacket, rfbADrivesList, 0, 5, filename2);
|
|
if (buffer!=NULL) free(buffer);
|
|
return retval;
|
|
break;
|
|
case rfbRDirContent: /* Client requests the content of a directory */
|
|
/*
|
|
rfbLog("rfbProcessFileTransfer() rfbDirContentRequest: rfbRDirContent\n");
|
|
*/
|
|
if ((buffer = rfbProcessFileTransferReadBuffer(cl, length))==NULL) return FALSE;
|
|
retval = rfbSendDirContent(cl, length, buffer);
|
|
if (buffer!=NULL) free(buffer);
|
|
return retval;
|
|
}
|
|
break;
|
|
|
|
case rfbDirPacket:
|
|
rfbLog("rfbProcessFileTransfer() rfbDirPacket\n");
|
|
break;
|
|
case rfbFileAcceptHeader:
|
|
rfbLog("rfbProcessFileTransfer() rfbFileAcceptHeader\n");
|
|
break;
|
|
case rfbCommandReturn:
|
|
rfbLog("rfbProcessFileTransfer() rfbCommandReturn\n");
|
|
break;
|
|
case rfbFileChecksums:
|
|
/* Destination file already exists - the viewer sends the checksums */
|
|
rfbLog("rfbProcessFileTransfer() rfbFileChecksums\n");
|
|
break;
|
|
case rfbFileTransferAccess:
|
|
rfbLog("rfbProcessFileTransfer() rfbFileTransferAccess\n");
|
|
break;
|
|
|
|
/*
|
|
* sending from the server to the viewer
|
|
*/
|
|
|
|
case rfbFileTransferRequest:
|
|
/*
|
|
rfbLog("rfbProcessFileTransfer() rfbFileTransferRequest:\n");
|
|
*/
|
|
/* add some space to the end of the buffer as we will be adding a timespec to it */
|
|
if ((buffer = rfbProcessFileTransferReadBuffer(cl, length))==NULL) return FALSE;
|
|
/* The client requests a File */
|
|
if (!rfbFilenameTranslate2UNIX(cl, buffer, filename1, sizeof(filename1)))
|
|
goto fail;
|
|
cl->fileTransfer.fd=open(filename1, O_RDONLY, 0744);
|
|
|
|
/*
|
|
*/
|
|
if (DB) rfbLog("rfbProcessFileTransfer() rfbFileTransferRequest(\"%s\"->\"%s\") Open: %s fd=%d\n", buffer, filename1, (cl->fileTransfer.fd==-1?"Failed":"Success"), cl->fileTransfer.fd);
|
|
|
|
if (cl->fileTransfer.fd!=-1) {
|
|
if (fstat(cl->fileTransfer.fd, &statbuf)!=0) {
|
|
close(cl->fileTransfer.fd);
|
|
cl->fileTransfer.fd=-1;
|
|
}
|
|
else
|
|
{
|
|
/* Add the File Time Stamp to the filename */
|
|
strftime(timespec, sizeof(timespec), "%m/%d/%Y %H:%M",gmtime(&statbuf.st_ctime));
|
|
buffer=realloc(buffer, length + strlen(timespec) + 2); /* comma, and Null term */
|
|
if (buffer==NULL) {
|
|
rfbLog("rfbProcessFileTransfer() rfbFileTransferRequest: Failed to malloc %d bytes\n", length + strlen(timespec) + 2);
|
|
return FALSE;
|
|
}
|
|
strcat(buffer,",");
|
|
strcat(buffer, timespec);
|
|
length = strlen(buffer);
|
|
if (DB) rfbLog("rfbProcessFileTransfer() buffer is now: \"%s\"\n", buffer);
|
|
}
|
|
}
|
|
|
|
/* The viewer supports compression if size==1 */
|
|
cl->fileTransfer.compressionEnabled = (size==1);
|
|
|
|
/*
|
|
rfbLog("rfbProcessFileTransfer() rfbFileTransferRequest(\"%s\"->\"%s\")%s\n", buffer, filename1, (size==1?" <Compression Enabled>":""));
|
|
*/
|
|
|
|
/* File Size in bytes, 0xFFFFFFFF (-1) means error */
|
|
retval = rfbSendFileTransferMessage(cl, rfbFileHeader, 0, (cl->fileTransfer.fd==-1 ? -1 : statbuf.st_size), length, buffer);
|
|
|
|
if (cl->fileTransfer.fd==-1)
|
|
{
|
|
if (buffer!=NULL) free(buffer);
|
|
return retval;
|
|
}
|
|
/* setup filetransfer stuff */
|
|
cl->fileTransfer.fileSize = statbuf.st_size;
|
|
cl->fileTransfer.numPackets = statbuf.st_size / sz_rfbBlockSize;
|
|
cl->fileTransfer.receiving = 0;
|
|
cl->fileTransfer.sending = 0; /* set when we receive a rfbFileHeader: */
|
|
|
|
/* TODO: finish 64-bit file size support */
|
|
sizeHtmp = 0;
|
|
LOCK(cl->sendMutex);
|
|
if (rfbWriteExact(cl, (char *)&sizeHtmp, 4) < 0) {
|
|
rfbLogPerror("rfbProcessFileTransfer: write");
|
|
rfbCloseClient(cl);
|
|
UNLOCK(cl->sendMutex);
|
|
if (buffer!=NULL) free(buffer);
|
|
return FALSE;
|
|
}
|
|
UNLOCK(cl->sendMutex);
|
|
break;
|
|
|
|
case rfbFileHeader:
|
|
/* Destination file (viewer side) is ready for reception (size > 0) or not (size = -1) */
|
|
if (size==-1) {
|
|
rfbLog("rfbProcessFileTransfer() rfbFileHeader (error, aborting)\n");
|
|
close(cl->fileTransfer.fd);
|
|
cl->fileTransfer.fd=-1;
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
rfbLog("rfbProcessFileTransfer() rfbFileHeader (%d bytes of a file)\n", size);
|
|
*/
|
|
|
|
/* Starts the transfer! */
|
|
cl->fileTransfer.sending=1;
|
|
return rfbSendFileTransferChunk(cl);
|
|
break;
|
|
|
|
|
|
/*
|
|
* sending from the viewer to the server
|
|
*/
|
|
|
|
case rfbFileTransferOffer:
|
|
/* client is sending a file to us */
|
|
/* buffer contains full path name (plus FileTime) */
|
|
/* size contains size of the file */
|
|
/*
|
|
rfbLog("rfbProcessFileTransfer() rfbFileTransferOffer:\n");
|
|
*/
|
|
if ((buffer = rfbProcessFileTransferReadBuffer(cl, length))==NULL) return FALSE;
|
|
|
|
/* Parse the FileTime */
|
|
p = strrchr(buffer, ',');
|
|
if (p!=NULL) {
|
|
*p = '\0';
|
|
strncpy(szFileTime, p+1, sizeof(szFileTime));
|
|
szFileTime[sizeof(szFileTime)-1] = '\x00'; /* ensure NULL terminating byte is present, even if copy overflowed */
|
|
} else
|
|
szFileTime[0]=0;
|
|
|
|
|
|
|
|
/* Need to read in sizeHtmp */
|
|
if ((n = rfbReadExact(cl, (char *)&sizeHtmp, 4)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessFileTransfer: read sizeHtmp");
|
|
rfbCloseClient(cl);
|
|
/* NOTE: don't forget to free(buffer) if you return early! */
|
|
if (buffer!=NULL) free(buffer);
|
|
return FALSE;
|
|
}
|
|
sizeHtmp = Swap32IfLE(sizeHtmp);
|
|
|
|
if (!rfbFilenameTranslate2UNIX(cl, buffer, filename1, sizeof(filename1)))
|
|
goto fail;
|
|
|
|
/* If the file exists... We can send a rfbFileChecksums back to the client before we send an rfbFileAcceptHeader */
|
|
/* TODO: Delta Transfer */
|
|
|
|
cl->fileTransfer.fd=open(filename1, O_CREAT|O_WRONLY|O_TRUNC, 0744);
|
|
if (DB) rfbLog("rfbProcessFileTransfer() rfbFileTransferOffer(\"%s\"->\"%s\") %s %s fd=%d\n", buffer, filename1, (cl->fileTransfer.fd==-1?"Failed":"Success"), (cl->fileTransfer.fd==-1?strerror(errno):""), cl->fileTransfer.fd);
|
|
/*
|
|
*/
|
|
|
|
/* File Size in bytes, 0xFFFFFFFF (-1) means error */
|
|
retval = rfbSendFileTransferMessage(cl, rfbFileAcceptHeader, 0, (cl->fileTransfer.fd==-1 ? -1 : 0), length, buffer);
|
|
if (cl->fileTransfer.fd==-1) {
|
|
free(buffer);
|
|
return retval;
|
|
}
|
|
|
|
/* setup filetransfer stuff */
|
|
cl->fileTransfer.fileSize = size;
|
|
cl->fileTransfer.numPackets = size / sz_rfbBlockSize;
|
|
cl->fileTransfer.receiving = 1;
|
|
cl->fileTransfer.sending = 0;
|
|
break;
|
|
|
|
case rfbFilePacket:
|
|
/*
|
|
rfbLog("rfbProcessFileTransfer() rfbFilePacket:\n");
|
|
*/
|
|
if ((buffer = rfbProcessFileTransferReadBuffer(cl, length))==NULL) return FALSE;
|
|
if (cl->fileTransfer.fd!=-1) {
|
|
/* buffer contains the contents of the file */
|
|
if (size==0)
|
|
retval=write(cl->fileTransfer.fd, buffer, length);
|
|
else
|
|
{
|
|
#ifdef LIBVNCSERVER_HAVE_LIBZ
|
|
/* compressed packet */
|
|
nRet = uncompress(compBuff,&nRawBytes,(const unsigned char*)buffer, length);
|
|
if(nRet == Z_OK)
|
|
retval=write(cl->fileTransfer.fd, (char*)compBuff, nRawBytes);
|
|
else
|
|
retval = -1;
|
|
#else
|
|
/* Write the file out as received... */
|
|
retval=write(cl->fileTransfer.fd, buffer, length);
|
|
#endif
|
|
}
|
|
if (retval==-1)
|
|
{
|
|
close(cl->fileTransfer.fd);
|
|
cl->fileTransfer.fd=-1;
|
|
cl->fileTransfer.sending = 0;
|
|
cl->fileTransfer.receiving = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case rfbEndOfFile:
|
|
if (DB) rfbLog("rfbProcessFileTransfer() rfbEndOfFile\n");
|
|
/*
|
|
*/
|
|
if (cl->fileTransfer.fd!=-1)
|
|
close(cl->fileTransfer.fd);
|
|
cl->fileTransfer.fd=-1;
|
|
cl->fileTransfer.sending = 0;
|
|
cl->fileTransfer.receiving = 0;
|
|
break;
|
|
|
|
case rfbAbortFileTransfer:
|
|
if (DB) rfbLog("rfbProcessFileTransfer() rfbAbortFileTransfer\n");
|
|
/*
|
|
*/
|
|
if (cl->fileTransfer.fd!=-1)
|
|
{
|
|
close(cl->fileTransfer.fd);
|
|
cl->fileTransfer.fd=-1;
|
|
cl->fileTransfer.sending = 0;
|
|
cl->fileTransfer.receiving = 0;
|
|
}
|
|
else
|
|
{
|
|
/* We use this message for FileTransfer rights (<=RC18 versions)
|
|
* The client asks for FileTransfer permission
|
|
*/
|
|
if (contentParam == 0)
|
|
{
|
|
rfbLog("rfbProcessFileTransfer() File Transfer Permission DENIED! (Client Version <=RC18)\n");
|
|
/* Old method for FileTransfer handshake perimssion (<=RC18) (Deny it)*/
|
|
return rfbSendFileTransferMessage(cl, rfbAbortFileTransfer, 0, -1, 0, "");
|
|
}
|
|
/* New method is allowed */
|
|
if (cl->screen->getFileTransferPermission!=NULL)
|
|
{
|
|
if (cl->screen->getFileTransferPermission(cl)==TRUE)
|
|
{
|
|
rfbLog("rfbProcessFileTransfer() File Transfer Permission Granted!\n");
|
|
return rfbSendFileTransferMessage(cl, rfbFileTransferAccess, 0, 1 , 0, ""); /* Permit */
|
|
}
|
|
else
|
|
{
|
|
rfbLog("rfbProcessFileTransfer() File Transfer Permission DENIED!\n");
|
|
return rfbSendFileTransferMessage(cl, rfbFileTransferAccess, 0, -1 , 0, ""); /* Deny */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (cl->screen->permitFileTransfer)
|
|
{
|
|
rfbLog("rfbProcessFileTransfer() File Transfer Permission Granted!\n");
|
|
return rfbSendFileTransferMessage(cl, rfbFileTransferAccess, 0, 1 , 0, ""); /* Permit */
|
|
}
|
|
else
|
|
{
|
|
rfbLog("rfbProcessFileTransfer() File Transfer Permission DENIED by default!\n");
|
|
return rfbSendFileTransferMessage(cl, rfbFileTransferAccess, 0, -1 , 0, ""); /* DEFAULT: DENY (for security) */
|
|
}
|
|
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
case rfbCommand:
|
|
/*
|
|
rfbLog("rfbProcessFileTransfer() rfbCommand:\n");
|
|
*/
|
|
if ((buffer = rfbProcessFileTransferReadBuffer(cl, length))==NULL) return FALSE;
|
|
switch (contentParam) {
|
|
case rfbCDirCreate: /* Client requests the creation of a directory */
|
|
if (!rfbFilenameTranslate2UNIX(cl, buffer, filename1, sizeof(filename1)))
|
|
goto fail;
|
|
retval = mkdir(filename1, 0755);
|
|
if (DB) rfbLog("rfbProcessFileTransfer() rfbCommand: rfbCDirCreate(\"%s\"->\"%s\") %s\n", buffer, filename1, (retval==-1?"Failed":"Success"));
|
|
/*
|
|
*/
|
|
retval = rfbSendFileTransferMessage(cl, rfbCommandReturn, rfbADirCreate, retval, length, buffer);
|
|
if (buffer!=NULL) free(buffer);
|
|
return retval;
|
|
case rfbCFileDelete: /* Client requests the deletion of a file */
|
|
if (!rfbFilenameTranslate2UNIX(cl, buffer, filename1, sizeof(filename1)))
|
|
goto fail;
|
|
if (stat(filename1,&statbuf)==0)
|
|
{
|
|
if (S_ISDIR(statbuf.st_mode))
|
|
retval = rmdir(filename1);
|
|
else
|
|
retval = unlink(filename1);
|
|
}
|
|
else retval=-1;
|
|
retval = rfbSendFileTransferMessage(cl, rfbCommandReturn, rfbAFileDelete, retval, length, buffer);
|
|
if (buffer!=NULL) free(buffer);
|
|
return retval;
|
|
case rfbCFileRename: /* Client requests the Renaming of a file/directory */
|
|
p = strrchr(buffer, '*');
|
|
if (p != NULL)
|
|
{
|
|
/* Split into 2 filenames ('*' is a seperator) */
|
|
*p = '\0';
|
|
if (!rfbFilenameTranslate2UNIX(cl, buffer, filename1, sizeof(filename1)))
|
|
goto fail;
|
|
if (!rfbFilenameTranslate2UNIX(cl, p+1, filename2, sizeof(filename2)))
|
|
goto fail;
|
|
retval = rename(filename1,filename2);
|
|
if (DB) rfbLog("rfbProcessFileTransfer() rfbCommand: rfbCFileRename(\"%s\"->\"%s\" -->> \"%s\"->\"%s\") %s\n", buffer, filename1, p+1, filename2, (retval==-1?"Failed":"Success"));
|
|
/*
|
|
*/
|
|
/* Restore the buffer so the reply is good */
|
|
*p = '*';
|
|
retval = rfbSendFileTransferMessage(cl, rfbCommandReturn, rfbAFileRename, retval, length, buffer);
|
|
if (buffer!=NULL) free(buffer);
|
|
return retval;
|
|
}
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* NOTE: don't forget to free(buffer) if you return early! */
|
|
if (buffer!=NULL) free(buffer);
|
|
return TRUE;
|
|
|
|
fail:
|
|
if (buffer!=NULL) free(buffer);
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* rfbProcessClientNormalMessage is called when the client has sent a normal
|
|
* protocol message.
|
|
*/
|
|
|
|
static void
|
|
rfbProcessClientNormalMessage(rfbClientPtr cl)
|
|
{
|
|
int n=0;
|
|
rfbClientToServerMsg msg;
|
|
char *str;
|
|
int i;
|
|
uint32_t enc=0;
|
|
uint32_t lastPreferredEncoding = -1;
|
|
char encBuf[64];
|
|
char encBuf2[64];
|
|
|
|
if ((n = rfbReadExact(cl, (char *)&msg, 1)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
switch (msg.type) {
|
|
|
|
case rfbSetPixelFormat:
|
|
|
|
if ((n = rfbReadExact(cl, ((char *)&msg) + 1,
|
|
sz_rfbSetPixelFormatMsg - 1)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
cl->format.bitsPerPixel = msg.spf.format.bitsPerPixel;
|
|
cl->format.depth = msg.spf.format.depth;
|
|
cl->format.bigEndian = (msg.spf.format.bigEndian ? TRUE : FALSE);
|
|
cl->format.trueColour = (msg.spf.format.trueColour ? TRUE : FALSE);
|
|
cl->format.redMax = Swap16IfLE(msg.spf.format.redMax);
|
|
cl->format.greenMax = Swap16IfLE(msg.spf.format.greenMax);
|
|
cl->format.blueMax = Swap16IfLE(msg.spf.format.blueMax);
|
|
cl->format.redShift = msg.spf.format.redShift;
|
|
cl->format.greenShift = msg.spf.format.greenShift;
|
|
cl->format.blueShift = msg.spf.format.blueShift;
|
|
|
|
cl->readyForSetColourMapEntries = TRUE;
|
|
cl->screen->setTranslateFunction(cl);
|
|
|
|
rfbStatRecordMessageRcvd(cl, msg.type, sz_rfbSetPixelFormatMsg, sz_rfbSetPixelFormatMsg);
|
|
|
|
return;
|
|
|
|
|
|
case rfbFixColourMapEntries:
|
|
if ((n = rfbReadExact(cl, ((char *)&msg) + 1,
|
|
sz_rfbFixColourMapEntriesMsg - 1)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
rfbStatRecordMessageRcvd(cl, msg.type, sz_rfbSetPixelFormatMsg, sz_rfbSetPixelFormatMsg);
|
|
rfbLog("rfbProcessClientNormalMessage: %s",
|
|
"FixColourMapEntries unsupported\n");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
|
|
|
|
/* NOTE: Some clients send us a set of encodings (ie: PointerPos) designed to enable/disable features...
|
|
* We may want to look into this...
|
|
* Example:
|
|
* case rfbEncodingXCursor:
|
|
* cl->enableCursorShapeUpdates = TRUE;
|
|
*
|
|
* Currently: cl->enableCursorShapeUpdates can *never* be turned off...
|
|
*/
|
|
case rfbSetEncodings:
|
|
{
|
|
|
|
if ((n = rfbReadExact(cl, ((char *)&msg) + 1,
|
|
sz_rfbSetEncodingsMsg - 1)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
msg.se.nEncodings = Swap16IfLE(msg.se.nEncodings);
|
|
|
|
rfbStatRecordMessageRcvd(cl, msg.type, sz_rfbSetEncodingsMsg+(msg.se.nEncodings*4),sz_rfbSetEncodingsMsg+(msg.se.nEncodings*4));
|
|
|
|
/*
|
|
* UltraVNC Client has the ability to adapt to changing network environments
|
|
* So, let's give it a change to tell us what it wants now!
|
|
*/
|
|
if (cl->preferredEncoding!=-1)
|
|
lastPreferredEncoding = cl->preferredEncoding;
|
|
|
|
/* Reset all flags to defaults (allows us to switch between PointerPos and Server Drawn Cursors) */
|
|
cl->preferredEncoding=-1;
|
|
cl->useCopyRect = FALSE;
|
|
cl->useNewFBSize = FALSE;
|
|
cl->cursorWasChanged = FALSE;
|
|
cl->useRichCursorEncoding = FALSE;
|
|
cl->enableCursorPosUpdates = FALSE;
|
|
cl->enableCursorShapeUpdates = FALSE;
|
|
cl->enableCursorShapeUpdates = FALSE;
|
|
cl->enableLastRectEncoding = FALSE;
|
|
cl->enableKeyboardLedState = FALSE;
|
|
cl->enableSupportedMessages = FALSE;
|
|
cl->enableSupportedEncodings = FALSE;
|
|
cl->enableServerIdentity = FALSE;
|
|
#if defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG)
|
|
cl->tightQualityLevel = -1;
|
|
#ifdef LIBVNCSERVER_HAVE_LIBJPEG
|
|
cl->tightCompressLevel = TIGHT_DEFAULT_COMPRESSION;
|
|
cl->turboSubsampLevel = TURBO_DEFAULT_SUBSAMP;
|
|
cl->turboQualityLevel = -1;
|
|
#endif
|
|
#endif
|
|
|
|
|
|
for (i = 0; i < msg.se.nEncodings; i++) {
|
|
if ((n = rfbReadExact(cl, (char *)&enc, 4)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
enc = Swap32IfLE(enc);
|
|
|
|
switch (enc) {
|
|
|
|
case rfbEncodingCopyRect:
|
|
cl->useCopyRect = TRUE;
|
|
break;
|
|
case rfbEncodingRaw:
|
|
case rfbEncodingRRE:
|
|
case rfbEncodingCoRRE:
|
|
case rfbEncodingHextile:
|
|
case rfbEncodingUltra:
|
|
#ifdef LIBVNCSERVER_HAVE_LIBZ
|
|
case rfbEncodingZlib:
|
|
case rfbEncodingZRLE:
|
|
case rfbEncodingZYWRLE:
|
|
#ifdef LIBVNCSERVER_HAVE_LIBJPEG
|
|
case rfbEncodingTight:
|
|
#endif
|
|
#endif
|
|
#ifdef LIBVNCSERVER_HAVE_LIBPNG
|
|
case rfbEncodingTightPng:
|
|
#endif
|
|
/* The first supported encoding is the 'preferred' encoding */
|
|
if (cl->preferredEncoding == -1)
|
|
cl->preferredEncoding = enc;
|
|
|
|
|
|
break;
|
|
case rfbEncodingXCursor:
|
|
if(!cl->screen->dontConvertRichCursorToXCursor) {
|
|
rfbLog("Enabling X-style cursor updates for client %s\n",
|
|
cl->host);
|
|
/* if cursor was drawn, hide the cursor */
|
|
if(!cl->enableCursorShapeUpdates)
|
|
rfbRedrawAfterHideCursor(cl,NULL);
|
|
|
|
cl->enableCursorShapeUpdates = TRUE;
|
|
cl->cursorWasChanged = TRUE;
|
|
}
|
|
break;
|
|
case rfbEncodingRichCursor:
|
|
rfbLog("Enabling full-color cursor updates for client %s\n",
|
|
cl->host);
|
|
/* if cursor was drawn, hide the cursor */
|
|
if(!cl->enableCursorShapeUpdates)
|
|
rfbRedrawAfterHideCursor(cl,NULL);
|
|
|
|
cl->enableCursorShapeUpdates = TRUE;
|
|
cl->useRichCursorEncoding = TRUE;
|
|
cl->cursorWasChanged = TRUE;
|
|
break;
|
|
case rfbEncodingPointerPos:
|
|
if (!cl->enableCursorPosUpdates) {
|
|
rfbLog("Enabling cursor position updates for client %s\n",
|
|
cl->host);
|
|
cl->enableCursorPosUpdates = TRUE;
|
|
cl->cursorWasMoved = TRUE;
|
|
}
|
|
break;
|
|
case rfbEncodingLastRect:
|
|
if (!cl->enableLastRectEncoding) {
|
|
rfbLog("Enabling LastRect protocol extension for client "
|
|
"%s\n", cl->host);
|
|
cl->enableLastRectEncoding = TRUE;
|
|
}
|
|
break;
|
|
case rfbEncodingNewFBSize:
|
|
if (!cl->useNewFBSize) {
|
|
rfbLog("Enabling NewFBSize protocol extension for client "
|
|
"%s\n", cl->host);
|
|
cl->useNewFBSize = TRUE;
|
|
}
|
|
break;
|
|
case rfbEncodingKeyboardLedState:
|
|
if (!cl->enableKeyboardLedState) {
|
|
rfbLog("Enabling KeyboardLedState protocol extension for client "
|
|
"%s\n", cl->host);
|
|
cl->enableKeyboardLedState = TRUE;
|
|
}
|
|
break;
|
|
case rfbEncodingSupportedMessages:
|
|
if (!cl->enableSupportedMessages) {
|
|
rfbLog("Enabling SupportedMessages protocol extension for client "
|
|
"%s\n", cl->host);
|
|
cl->enableSupportedMessages = TRUE;
|
|
}
|
|
break;
|
|
case rfbEncodingSupportedEncodings:
|
|
if (!cl->enableSupportedEncodings) {
|
|
rfbLog("Enabling SupportedEncodings protocol extension for client "
|
|
"%s\n", cl->host);
|
|
cl->enableSupportedEncodings = TRUE;
|
|
}
|
|
break;
|
|
case rfbEncodingServerIdentity:
|
|
if (!cl->enableServerIdentity) {
|
|
rfbLog("Enabling ServerIdentity protocol extension for client "
|
|
"%s\n", cl->host);
|
|
cl->enableServerIdentity = TRUE;
|
|
}
|
|
break;
|
|
case rfbEncodingXvp:
|
|
if (cl->screen->xvpHook) {
|
|
rfbLog("Enabling Xvp protocol extension for client "
|
|
"%s\n", cl->host);
|
|
if (!rfbSendXvp(cl, 1, rfbXvp_Init)) {
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
#if defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG)
|
|
if ( enc >= (uint32_t)rfbEncodingCompressLevel0 &&
|
|
enc <= (uint32_t)rfbEncodingCompressLevel9 ) {
|
|
cl->zlibCompressLevel = enc & 0x0F;
|
|
#ifdef LIBVNCSERVER_HAVE_LIBJPEG
|
|
cl->tightCompressLevel = enc & 0x0F;
|
|
rfbLog("Using compression level %d for client %s\n",
|
|
cl->tightCompressLevel, cl->host);
|
|
#endif
|
|
} else if ( enc >= (uint32_t)rfbEncodingQualityLevel0 &&
|
|
enc <= (uint32_t)rfbEncodingQualityLevel9 ) {
|
|
cl->tightQualityLevel = enc & 0x0F;
|
|
rfbLog("Using image quality level %d for client %s\n",
|
|
cl->tightQualityLevel, cl->host);
|
|
#ifdef LIBVNCSERVER_HAVE_LIBJPEG
|
|
cl->turboQualityLevel = tight2turbo_qual[enc & 0x0F];
|
|
cl->turboSubsampLevel = tight2turbo_subsamp[enc & 0x0F];
|
|
rfbLog("Using JPEG subsampling %d, Q%d for client %s\n",
|
|
cl->turboSubsampLevel, cl->turboQualityLevel, cl->host);
|
|
} else if ( enc >= (uint32_t)rfbEncodingFineQualityLevel0 + 1 &&
|
|
enc <= (uint32_t)rfbEncodingFineQualityLevel100 ) {
|
|
cl->turboQualityLevel = enc & 0xFF;
|
|
rfbLog("Using fine quality level %d for client %s\n",
|
|
cl->turboQualityLevel, cl->host);
|
|
} else if ( enc >= (uint32_t)rfbEncodingSubsamp1X &&
|
|
enc <= (uint32_t)rfbEncodingSubsampGray ) {
|
|
cl->turboSubsampLevel = enc & 0xFF;
|
|
rfbLog("Using subsampling level %d for client %s\n",
|
|
cl->turboSubsampLevel, cl->host);
|
|
#endif
|
|
} else
|
|
#endif
|
|
{
|
|
rfbExtensionData* e;
|
|
for(e = cl->extensions; e;) {
|
|
rfbExtensionData* next = e->next;
|
|
if(e->extension->enablePseudoEncoding &&
|
|
e->extension->enablePseudoEncoding(cl,
|
|
&e->data, (int)enc))
|
|
/* ext handles this encoding */
|
|
break;
|
|
e = next;
|
|
}
|
|
if(e == NULL) {
|
|
rfbBool handled = FALSE;
|
|
/* if the pseudo encoding is not handled by the
|
|
enabled extensions, search through all
|
|
extensions. */
|
|
rfbProtocolExtension* e;
|
|
|
|
for(e = rfbGetExtensionIterator(); e;) {
|
|
int* encs = e->pseudoEncodings;
|
|
while(encs && *encs!=0) {
|
|
if(*encs==(int)enc) {
|
|
void* data = NULL;
|
|
if(!e->enablePseudoEncoding(cl, &data, (int)enc)) {
|
|
rfbLog("Installed extension pretends to handle pseudo encoding 0x%x, but does not!\n",(int)enc);
|
|
} else {
|
|
rfbEnableExtension(cl, e, data);
|
|
handled = TRUE;
|
|
e = NULL;
|
|
break;
|
|
}
|
|
}
|
|
encs++;
|
|
}
|
|
|
|
if(e)
|
|
e = e->next;
|
|
}
|
|
rfbReleaseExtensionIterator();
|
|
|
|
if(!handled)
|
|
rfbLog("rfbProcessClientNormalMessage: "
|
|
"ignoring unsupported encoding type %s\n",
|
|
encodingName(enc,encBuf,sizeof(encBuf)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (cl->preferredEncoding == -1) {
|
|
if (lastPreferredEncoding==-1) {
|
|
cl->preferredEncoding = rfbEncodingRaw;
|
|
rfbLog("Defaulting to %s encoding for client %s\n", encodingName(cl->preferredEncoding,encBuf,sizeof(encBuf)),cl->host);
|
|
}
|
|
else {
|
|
cl->preferredEncoding = lastPreferredEncoding;
|
|
rfbLog("Sticking with %s encoding for client %s\n", encodingName(cl->preferredEncoding,encBuf,sizeof(encBuf)),cl->host);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (lastPreferredEncoding==-1) {
|
|
rfbLog("Using %s encoding for client %s\n", encodingName(cl->preferredEncoding,encBuf,sizeof(encBuf)),cl->host);
|
|
} else {
|
|
rfbLog("Switching from %s to %s Encoding for client %s\n",
|
|
encodingName(lastPreferredEncoding,encBuf2,sizeof(encBuf2)),
|
|
encodingName(cl->preferredEncoding,encBuf,sizeof(encBuf)), cl->host);
|
|
}
|
|
}
|
|
|
|
if (cl->enableCursorPosUpdates && !cl->enableCursorShapeUpdates) {
|
|
rfbLog("Disabling cursor position updates for client %s\n",
|
|
cl->host);
|
|
cl->enableCursorPosUpdates = FALSE;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
case rfbFramebufferUpdateRequest:
|
|
{
|
|
sraRegionPtr tmpRegion;
|
|
|
|
if ((n = rfbReadExact(cl, ((char *)&msg) + 1,
|
|
sz_rfbFramebufferUpdateRequestMsg-1)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
rfbStatRecordMessageRcvd(cl, msg.type, sz_rfbFramebufferUpdateRequestMsg,sz_rfbFramebufferUpdateRequestMsg);
|
|
|
|
/* The values come in based on the scaled screen, we need to convert them to
|
|
* values based on the main screen's coordinate system
|
|
*/
|
|
if(!rectSwapIfLEAndClip(&msg.fur.x,&msg.fur.y,&msg.fur.w,&msg.fur.h,cl))
|
|
{
|
|
rfbLog("Warning, ignoring rfbFramebufferUpdateRequest: %dXx%dY-%dWx%dH\n",msg.fur.x, msg.fur.y, msg.fur.w, msg.fur.h);
|
|
return;
|
|
}
|
|
|
|
|
|
tmpRegion =
|
|
sraRgnCreateRect(msg.fur.x,
|
|
msg.fur.y,
|
|
msg.fur.x+msg.fur.w,
|
|
msg.fur.y+msg.fur.h);
|
|
|
|
LOCK(cl->updateMutex);
|
|
sraRgnOr(cl->requestedRegion,tmpRegion);
|
|
|
|
if (!cl->readyForSetColourMapEntries) {
|
|
/* client hasn't sent a SetPixelFormat so is using server's */
|
|
cl->readyForSetColourMapEntries = TRUE;
|
|
if (!cl->format.trueColour) {
|
|
if (!rfbSetClientColourMap(cl, 0, 0)) {
|
|
sraRgnDestroy(tmpRegion);
|
|
TSIGNAL(cl->updateCond);
|
|
UNLOCK(cl->updateMutex);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!msg.fur.incremental) {
|
|
sraRgnOr(cl->modifiedRegion,tmpRegion);
|
|
sraRgnSubtract(cl->copyRegion,tmpRegion);
|
|
}
|
|
TSIGNAL(cl->updateCond);
|
|
UNLOCK(cl->updateMutex);
|
|
|
|
sraRgnDestroy(tmpRegion);
|
|
|
|
return;
|
|
}
|
|
|
|
case rfbKeyEvent:
|
|
|
|
if ((n = rfbReadExact(cl, ((char *)&msg) + 1,
|
|
sz_rfbKeyEventMsg - 1)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
rfbStatRecordMessageRcvd(cl, msg.type, sz_rfbKeyEventMsg, sz_rfbKeyEventMsg);
|
|
|
|
if(!cl->viewOnly) {
|
|
cl->screen->kbdAddEvent(msg.ke.down, (rfbKeySym)Swap32IfLE(msg.ke.key), cl);
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
case rfbPointerEvent:
|
|
|
|
if ((n = rfbReadExact(cl, ((char *)&msg) + 1,
|
|
sz_rfbPointerEventMsg - 1)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
rfbStatRecordMessageRcvd(cl, msg.type, sz_rfbPointerEventMsg, sz_rfbPointerEventMsg);
|
|
|
|
if (cl->screen->pointerClient && cl->screen->pointerClient != cl)
|
|
return;
|
|
|
|
if (msg.pe.buttonMask == 0)
|
|
cl->screen->pointerClient = NULL;
|
|
else
|
|
cl->screen->pointerClient = cl;
|
|
|
|
if(!cl->viewOnly) {
|
|
if (msg.pe.buttonMask != cl->lastPtrButtons ||
|
|
cl->screen->deferPtrUpdateTime == 0) {
|
|
cl->screen->ptrAddEvent(msg.pe.buttonMask,
|
|
ScaleX(cl->scaledScreen, cl->screen, Swap16IfLE(msg.pe.x)),
|
|
ScaleY(cl->scaledScreen, cl->screen, Swap16IfLE(msg.pe.y)),
|
|
cl);
|
|
cl->lastPtrButtons = msg.pe.buttonMask;
|
|
} else {
|
|
cl->lastPtrX = ScaleX(cl->scaledScreen, cl->screen, Swap16IfLE(msg.pe.x));
|
|
cl->lastPtrY = ScaleY(cl->scaledScreen, cl->screen, Swap16IfLE(msg.pe.y));
|
|
cl->lastPtrButtons = msg.pe.buttonMask;
|
|
}
|
|
}
|
|
return;
|
|
|
|
|
|
case rfbFileTransfer:
|
|
if ((n = rfbReadExact(cl, ((char *)&msg) + 1,
|
|
sz_rfbFileTransferMsg - 1)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
msg.ft.size = Swap32IfLE(msg.ft.size);
|
|
msg.ft.length = Swap32IfLE(msg.ft.length);
|
|
/* record statistics in rfbProcessFileTransfer as length is filled with garbage when it is not valid */
|
|
rfbProcessFileTransfer(cl, msg.ft.contentType, msg.ft.contentParam, msg.ft.size, msg.ft.length);
|
|
return;
|
|
|
|
case rfbSetSW:
|
|
if ((n = rfbReadExact(cl, ((char *)&msg) + 1,
|
|
sz_rfbSetSWMsg - 1)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
msg.sw.x = Swap16IfLE(msg.sw.x);
|
|
msg.sw.y = Swap16IfLE(msg.sw.y);
|
|
rfbStatRecordMessageRcvd(cl, msg.type, sz_rfbSetSWMsg, sz_rfbSetSWMsg);
|
|
/* msg.sw.status is not initialized in the ultraVNC viewer and contains random numbers (why???) */
|
|
|
|
rfbLog("Received a rfbSetSingleWindow(%d x, %d y)\n", msg.sw.x, msg.sw.y);
|
|
if (cl->screen->setSingleWindow!=NULL)
|
|
cl->screen->setSingleWindow(cl, msg.sw.x, msg.sw.y);
|
|
return;
|
|
|
|
case rfbSetServerInput:
|
|
if ((n = rfbReadExact(cl, ((char *)&msg) + 1,
|
|
sz_rfbSetServerInputMsg - 1)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
rfbStatRecordMessageRcvd(cl, msg.type, sz_rfbSetServerInputMsg, sz_rfbSetServerInputMsg);
|
|
|
|
/* msg.sim.pad is not initialized in the ultraVNC viewer and contains random numbers (why???) */
|
|
/* msg.sim.pad = Swap16IfLE(msg.sim.pad); */
|
|
|
|
rfbLog("Received a rfbSetServerInput(%d status)\n", msg.sim.status);
|
|
if (cl->screen->setServerInput!=NULL)
|
|
cl->screen->setServerInput(cl, msg.sim.status);
|
|
return;
|
|
|
|
case rfbTextChat:
|
|
if ((n = rfbReadExact(cl, ((char *)&msg) + 1,
|
|
sz_rfbTextChatMsg - 1)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
msg.tc.pad2 = Swap16IfLE(msg.tc.pad2);
|
|
msg.tc.length = Swap32IfLE(msg.tc.length);
|
|
|
|
switch (msg.tc.length) {
|
|
case rfbTextChatOpen:
|
|
case rfbTextChatClose:
|
|
case rfbTextChatFinished:
|
|
/* commands do not have text following */
|
|
/* Why couldn't they have used the pad byte??? */
|
|
str=NULL;
|
|
rfbStatRecordMessageRcvd(cl, msg.type, sz_rfbTextChatMsg, sz_rfbTextChatMsg);
|
|
break;
|
|
default:
|
|
if ((msg.tc.length>0) && (msg.tc.length<rfbTextMaxSize))
|
|
{
|
|
str = (char *)malloc(msg.tc.length);
|
|
if (str==NULL)
|
|
{
|
|
rfbLog("Unable to malloc %d bytes for a TextChat Message\n", msg.tc.length);
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
if ((n = rfbReadExact(cl, str, msg.tc.length)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
free(str);
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
rfbStatRecordMessageRcvd(cl, msg.type, sz_rfbTextChatMsg+msg.tc.length, sz_rfbTextChatMsg+msg.tc.length);
|
|
}
|
|
else
|
|
{
|
|
/* This should never happen */
|
|
rfbLog("client sent us a Text Message that is too big %d>%d\n", msg.tc.length, rfbTextMaxSize);
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Note: length can be commands: rfbTextChatOpen, rfbTextChatClose, and rfbTextChatFinished
|
|
* at which point, the str is NULL (as it is not sent)
|
|
*/
|
|
if (cl->screen->setTextChat!=NULL)
|
|
cl->screen->setTextChat(cl, msg.tc.length, str);
|
|
|
|
free(str);
|
|
return;
|
|
|
|
|
|
case rfbClientCutText:
|
|
|
|
if ((n = rfbReadExact(cl, ((char *)&msg) + 1,
|
|
sz_rfbClientCutTextMsg - 1)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
msg.cct.length = Swap32IfLE(msg.cct.length);
|
|
|
|
/* uint32_t input is passed to malloc()'s size_t argument,
|
|
* to rfbReadExact()'s int argument, to rfbStatRecordMessageRcvd()'s int
|
|
* argument increased of sz_rfbClientCutTextMsg, and to setXCutText()'s int
|
|
* argument. Here we impose a limit of 1 MB so that the value fits
|
|
* into all of the types to prevent from misinterpretation and thus
|
|
* from accessing uninitialized memory (CVE-2018-7225) and also to
|
|
* prevent from a denial-of-service by allocating too much memory in
|
|
* the server. */
|
|
if (msg.cct.length > 1<<20) {
|
|
rfbLog("rfbClientCutText: too big cut text length requested: %u B > 1 MB\n", (unsigned int)msg.cct.length);
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
/* Allow zero-length client cut text. */
|
|
str = (char *)calloc(msg.cct.length ? msg.cct.length : 1, 1);
|
|
if (str == NULL) {
|
|
rfbLogPerror("rfbProcessClientNormalMessage: not enough memory");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
if ((n = rfbReadExact(cl, str, msg.cct.length)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
free(str);
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
rfbStatRecordMessageRcvd(cl, msg.type, sz_rfbClientCutTextMsg+msg.cct.length, sz_rfbClientCutTextMsg+msg.cct.length);
|
|
if(!cl->viewOnly) {
|
|
cl->screen->setXCutText(str, msg.cct.length, cl);
|
|
}
|
|
free(str);
|
|
|
|
return;
|
|
|
|
case rfbPalmVNCSetScaleFactor:
|
|
cl->PalmVNC = TRUE;
|
|
if ((n = rfbReadExact(cl, ((char *)&msg) + 1,
|
|
sz_rfbSetScaleMsg - 1)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
if (msg.ssc.scale == 0) {
|
|
rfbLogPerror("rfbProcessClientNormalMessage: will not accept a scale factor of zero");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
rfbStatRecordMessageRcvd(cl, msg.type, sz_rfbSetScaleMsg, sz_rfbSetScaleMsg);
|
|
rfbLog("rfbSetScale(%d)\n", msg.ssc.scale);
|
|
rfbScalingSetup(cl,cl->screen->width/msg.ssc.scale, cl->screen->height/msg.ssc.scale);
|
|
|
|
rfbSendNewScaleSize(cl);
|
|
return;
|
|
|
|
case rfbSetScale:
|
|
|
|
if ((n = rfbReadExact(cl, ((char *)&msg) + 1,
|
|
sz_rfbSetScaleMsg - 1)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
if (msg.ssc.scale == 0) {
|
|
rfbLogPerror("rfbProcessClientNormalMessage: will not accept a scale factor of zero");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
|
|
rfbStatRecordMessageRcvd(cl, msg.type, sz_rfbSetScaleMsg, sz_rfbSetScaleMsg);
|
|
rfbLog("rfbSetScale(%d)\n", msg.ssc.scale);
|
|
rfbScalingSetup(cl,cl->screen->width/msg.ssc.scale, cl->screen->height/msg.ssc.scale);
|
|
|
|
rfbSendNewScaleSize(cl);
|
|
return;
|
|
|
|
case rfbXvp:
|
|
|
|
if ((n = rfbReadExact(cl, ((char *)&msg) + 1,
|
|
sz_rfbXvpMsg - 1)) <= 0) {
|
|
if (n != 0)
|
|
rfbLogPerror("rfbProcessClientNormalMessage: read");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
rfbStatRecordMessageRcvd(cl, msg.type, sz_rfbXvpMsg, sz_rfbXvpMsg);
|
|
|
|
/* only version when is defined, so echo back a fail */
|
|
if(msg.xvp.version != 1) {
|
|
rfbSendXvp(cl, msg.xvp.version, rfbXvp_Fail);
|
|
}
|
|
else {
|
|
/* if the hook exists and fails, send a fail msg */
|
|
if(cl->screen->xvpHook && !cl->screen->xvpHook(cl, msg.xvp.version, msg.xvp.code))
|
|
rfbSendXvp(cl, 1, rfbXvp_Fail);
|
|
}
|
|
return;
|
|
|
|
default:
|
|
{
|
|
rfbExtensionData *e,*next;
|
|
|
|
for(e=cl->extensions; e;) {
|
|
next = e->next;
|
|
if(e->extension->handleMessage &&
|
|
e->extension->handleMessage(cl, e->data, &msg))
|
|
{
|
|
rfbStatRecordMessageRcvd(cl, msg.type, 0, 0); /* Extension should handle this */
|
|
return;
|
|
}
|
|
e = next;
|
|
}
|
|
|
|
rfbLog("rfbProcessClientNormalMessage: unknown message type %d\n",
|
|
msg.type);
|
|
rfbLog(" ... closing connection\n");
|
|
rfbCloseClient(cl);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* rfbSendFramebufferUpdate - send the currently pending framebuffer update to
|
|
* the RFB client.
|
|
* givenUpdateRegion is not changed.
|
|
*/
|
|
|
|
rfbBool
|
|
rfbSendFramebufferUpdate(rfbClientPtr cl,
|
|
sraRegionPtr givenUpdateRegion)
|
|
{
|
|
sraRectangleIterator* i=NULL;
|
|
sraRect rect;
|
|
int nUpdateRegionRects;
|
|
rfbFramebufferUpdateMsg *fu = (rfbFramebufferUpdateMsg *)cl->updateBuf;
|
|
sraRegionPtr updateRegion,updateCopyRegion,tmpRegion;
|
|
int dx, dy;
|
|
rfbBool sendCursorShape = FALSE;
|
|
rfbBool sendCursorPos = FALSE;
|
|
rfbBool sendKeyboardLedState = FALSE;
|
|
rfbBool sendSupportedMessages = FALSE;
|
|
rfbBool sendSupportedEncodings = FALSE;
|
|
rfbBool sendServerIdentity = FALSE;
|
|
rfbBool result = TRUE;
|
|
|
|
|
|
if(cl->screen->displayHook)
|
|
cl->screen->displayHook(cl);
|
|
|
|
/*
|
|
* If framebuffer size was changed and the client supports NewFBSize
|
|
* encoding, just send NewFBSize marker and return.
|
|
*/
|
|
|
|
if (cl->useNewFBSize && cl->newFBSizePending) {
|
|
LOCK(cl->updateMutex);
|
|
cl->newFBSizePending = FALSE;
|
|
UNLOCK(cl->updateMutex);
|
|
fu->type = rfbFramebufferUpdate;
|
|
fu->nRects = Swap16IfLE(1);
|
|
cl->ublen = sz_rfbFramebufferUpdateMsg;
|
|
if (!rfbSendNewFBSize(cl, cl->scaledScreen->width, cl->scaledScreen->height)) {
|
|
if(cl->screen->displayFinishedHook)
|
|
cl->screen->displayFinishedHook(cl, FALSE);
|
|
return FALSE;
|
|
}
|
|
result = rfbSendUpdateBuf(cl);
|
|
if(cl->screen->displayFinishedHook)
|
|
cl->screen->displayFinishedHook(cl, result);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* If this client understands cursor shape updates, cursor should be
|
|
* removed from the framebuffer. Otherwise, make sure it's put up.
|
|
*/
|
|
|
|
if (cl->enableCursorShapeUpdates) {
|
|
if (cl->cursorWasChanged && cl->readyForSetColourMapEntries)
|
|
sendCursorShape = TRUE;
|
|
}
|
|
|
|
/*
|
|
* Do we plan to send cursor position update?
|
|
*/
|
|
|
|
if (cl->enableCursorPosUpdates && cl->cursorWasMoved)
|
|
sendCursorPos = TRUE;
|
|
|
|
/*
|
|
* Do we plan to send a keyboard state update?
|
|
*/
|
|
if ((cl->enableKeyboardLedState) &&
|
|
(cl->screen->getKeyboardLedStateHook!=NULL))
|
|
{
|
|
int x;
|
|
x=cl->screen->getKeyboardLedStateHook(cl->screen);
|
|
if (x!=cl->lastKeyboardLedState)
|
|
{
|
|
sendKeyboardLedState = TRUE;
|
|
cl->lastKeyboardLedState=x;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Do we plan to send a rfbEncodingSupportedMessages?
|
|
*/
|
|
if (cl->enableSupportedMessages)
|
|
{
|
|
sendSupportedMessages = TRUE;
|
|
/* We only send this message ONCE <per setEncodings message received>
|
|
* (We disable it here)
|
|
*/
|
|
cl->enableSupportedMessages = FALSE;
|
|
}
|
|
/*
|
|
* Do we plan to send a rfbEncodingSupportedEncodings?
|
|
*/
|
|
if (cl->enableSupportedEncodings)
|
|
{
|
|
sendSupportedEncodings = TRUE;
|
|
/* We only send this message ONCE <per setEncodings message received>
|
|
* (We disable it here)
|
|
*/
|
|
cl->enableSupportedEncodings = FALSE;
|
|
}
|
|
/*
|
|
* Do we plan to send a rfbEncodingServerIdentity?
|
|
*/
|
|
if (cl->enableServerIdentity)
|
|
{
|
|
sendServerIdentity = TRUE;
|
|
/* We only send this message ONCE <per setEncodings message received>
|
|
* (We disable it here)
|
|
*/
|
|
cl->enableServerIdentity = FALSE;
|
|
}
|
|
|
|
LOCK(cl->updateMutex);
|
|
|
|
/*
|
|
* The modifiedRegion may overlap the destination copyRegion. We remove
|
|
* any overlapping bits from the copyRegion (since they'd only be
|
|
* overwritten anyway).
|
|
*/
|
|
|
|
sraRgnSubtract(cl->copyRegion,cl->modifiedRegion);
|
|
|
|
/*
|
|
* The client is interested in the region requestedRegion. The region
|
|
* which should be updated now is the intersection of requestedRegion
|
|
* and the union of modifiedRegion and copyRegion. If it's empty then
|
|
* no update is needed.
|
|
*/
|
|
|
|
updateRegion = sraRgnCreateRgn(givenUpdateRegion);
|
|
if(cl->screen->progressiveSliceHeight>0) {
|
|
int height=cl->screen->progressiveSliceHeight,
|
|
y=cl->progressiveSliceY;
|
|
sraRegionPtr bbox=sraRgnBBox(updateRegion);
|
|
sraRect rect;
|
|
if(sraRgnPopRect(bbox,&rect,0)) {
|
|
sraRegionPtr slice;
|
|
if(y<rect.y1 || y>=rect.y2)
|
|
y=rect.y1;
|
|
slice=sraRgnCreateRect(0,y,cl->screen->width,y+height);
|
|
sraRgnAnd(updateRegion,slice);
|
|
sraRgnDestroy(slice);
|
|
}
|
|
sraRgnDestroy(bbox);
|
|
y+=height;
|
|
if(y>=cl->screen->height)
|
|
y=0;
|
|
cl->progressiveSliceY=y;
|
|
}
|
|
|
|
sraRgnOr(updateRegion,cl->copyRegion);
|
|
if(!sraRgnAnd(updateRegion,cl->requestedRegion) &&
|
|
sraRgnEmpty(updateRegion) &&
|
|
(cl->enableCursorShapeUpdates ||
|
|
(cl->cursorX == cl->screen->cursorX && cl->cursorY == cl->screen->cursorY)) &&
|
|
!sendCursorShape && !sendCursorPos && !sendKeyboardLedState &&
|
|
!sendSupportedMessages && !sendSupportedEncodings && !sendServerIdentity) {
|
|
sraRgnDestroy(updateRegion);
|
|
UNLOCK(cl->updateMutex);
|
|
if(cl->screen->displayFinishedHook)
|
|
cl->screen->displayFinishedHook(cl, TRUE);
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* We assume that the client doesn't have any pixel data outside the
|
|
* requestedRegion. In other words, both the source and destination of a
|
|
* copy must lie within requestedRegion. So the region we can send as a
|
|
* copy is the intersection of the copyRegion with both the requestedRegion
|
|
* and the requestedRegion translated by the amount of the copy. We set
|
|
* updateCopyRegion to this.
|
|
*/
|
|
|
|
updateCopyRegion = sraRgnCreateRgn(cl->copyRegion);
|
|
sraRgnAnd(updateCopyRegion,cl->requestedRegion);
|
|
tmpRegion = sraRgnCreateRgn(cl->requestedRegion);
|
|
sraRgnOffset(tmpRegion,cl->copyDX,cl->copyDY);
|
|
sraRgnAnd(updateCopyRegion,tmpRegion);
|
|
sraRgnDestroy(tmpRegion);
|
|
dx = cl->copyDX;
|
|
dy = cl->copyDY;
|
|
|
|
/*
|
|
* Next we remove updateCopyRegion from updateRegion so that updateRegion
|
|
* is the part of this update which is sent as ordinary pixel data (i.e not
|
|
* a copy).
|
|
*/
|
|
|
|
sraRgnSubtract(updateRegion,updateCopyRegion);
|
|
|
|
/*
|
|
* Finally we leave modifiedRegion to be the remainder (if any) of parts of
|
|
* the screen which are modified but outside the requestedRegion. We also
|
|
* empty both the requestedRegion and the copyRegion - note that we never
|
|
* carry over a copyRegion for a future update.
|
|
*/
|
|
|
|
sraRgnOr(cl->modifiedRegion,cl->copyRegion);
|
|
sraRgnSubtract(cl->modifiedRegion,updateRegion);
|
|
sraRgnSubtract(cl->modifiedRegion,updateCopyRegion);
|
|
|
|
sraRgnMakeEmpty(cl->requestedRegion);
|
|
sraRgnMakeEmpty(cl->copyRegion);
|
|
cl->copyDX = 0;
|
|
cl->copyDY = 0;
|
|
|
|
UNLOCK(cl->updateMutex);
|
|
|
|
if (!cl->enableCursorShapeUpdates) {
|
|
if(cl->cursorX != cl->screen->cursorX || cl->cursorY != cl->screen->cursorY) {
|
|
rfbRedrawAfterHideCursor(cl,updateRegion);
|
|
LOCK(cl->screen->cursorMutex);
|
|
cl->cursorX = cl->screen->cursorX;
|
|
cl->cursorY = cl->screen->cursorY;
|
|
UNLOCK(cl->screen->cursorMutex);
|
|
rfbRedrawAfterHideCursor(cl,updateRegion);
|
|
}
|
|
rfbShowCursor(cl);
|
|
}
|
|
|
|
/*
|
|
* Now send the update.
|
|
*/
|
|
|
|
rfbStatRecordMessageSent(cl, rfbFramebufferUpdate, 0, 0);
|
|
if (cl->preferredEncoding == rfbEncodingCoRRE) {
|
|
nUpdateRegionRects = 0;
|
|
|
|
for(i = sraRgnGetIterator(updateRegion); sraRgnIteratorNext(i,&rect);){
|
|
int x = rect.x1;
|
|
int y = rect.y1;
|
|
int w = rect.x2 - x;
|
|
int h = rect.y2 - y;
|
|
int rectsPerRow, rows;
|
|
/* We need to count the number of rects in the scaled screen */
|
|
if (cl->screen!=cl->scaledScreen)
|
|
rfbScaledCorrection(cl->screen, cl->scaledScreen, &x, &y, &w, &h, "rfbSendFramebufferUpdate");
|
|
rectsPerRow = (w-1)/cl->correMaxWidth+1;
|
|
rows = (h-1)/cl->correMaxHeight+1;
|
|
nUpdateRegionRects += rectsPerRow*rows;
|
|
}
|
|
sraRgnReleaseIterator(i); i=NULL;
|
|
} else if (cl->preferredEncoding == rfbEncodingUltra) {
|
|
nUpdateRegionRects = 0;
|
|
|
|
for(i = sraRgnGetIterator(updateRegion); sraRgnIteratorNext(i,&rect);){
|
|
int x = rect.x1;
|
|
int y = rect.y1;
|
|
int w = rect.x2 - x;
|
|
int h = rect.y2 - y;
|
|
/* We need to count the number of rects in the scaled screen */
|
|
if (cl->screen!=cl->scaledScreen)
|
|
rfbScaledCorrection(cl->screen, cl->scaledScreen, &x, &y, &w, &h, "rfbSendFramebufferUpdate");
|
|
nUpdateRegionRects += (((h-1) / (ULTRA_MAX_SIZE( w ) / w)) + 1);
|
|
}
|
|
sraRgnReleaseIterator(i); i=NULL;
|
|
#ifdef LIBVNCSERVER_HAVE_LIBZ
|
|
} else if (cl->preferredEncoding == rfbEncodingZlib) {
|
|
nUpdateRegionRects = 0;
|
|
|
|
for(i = sraRgnGetIterator(updateRegion); sraRgnIteratorNext(i,&rect);){
|
|
int x = rect.x1;
|
|
int y = rect.y1;
|
|
int w = rect.x2 - x;
|
|
int h = rect.y2 - y;
|
|
/* We need to count the number of rects in the scaled screen */
|
|
if (cl->screen!=cl->scaledScreen)
|
|
rfbScaledCorrection(cl->screen, cl->scaledScreen, &x, &y, &w, &h, "rfbSendFramebufferUpdate");
|
|
nUpdateRegionRects += (((h-1) / (ZLIB_MAX_SIZE( w ) / w)) + 1);
|
|
}
|
|
sraRgnReleaseIterator(i); i=NULL;
|
|
#ifdef LIBVNCSERVER_HAVE_LIBJPEG
|
|
} else if (cl->preferredEncoding == rfbEncodingTight) {
|
|
nUpdateRegionRects = 0;
|
|
|
|
for(i = sraRgnGetIterator(updateRegion); sraRgnIteratorNext(i,&rect);){
|
|
int x = rect.x1;
|
|
int y = rect.y1;
|
|
int w = rect.x2 - x;
|
|
int h = rect.y2 - y;
|
|
int n;
|
|
/* We need to count the number of rects in the scaled screen */
|
|
if (cl->screen!=cl->scaledScreen)
|
|
rfbScaledCorrection(cl->screen, cl->scaledScreen, &x, &y, &w, &h, "rfbSendFramebufferUpdate");
|
|
n = rfbNumCodedRectsTight(cl, x, y, w, h);
|
|
if (n == 0) {
|
|
nUpdateRegionRects = 0xFFFF;
|
|
break;
|
|
}
|
|
nUpdateRegionRects += n;
|
|
}
|
|
sraRgnReleaseIterator(i); i=NULL;
|
|
#endif
|
|
#endif
|
|
#if defined(LIBVNCSERVER_HAVE_LIBJPEG) && defined(LIBVNCSERVER_HAVE_LIBPNG)
|
|
} else if (cl->preferredEncoding == rfbEncodingTightPng) {
|
|
nUpdateRegionRects = 0;
|
|
|
|
for(i = sraRgnGetIterator(updateRegion); sraRgnIteratorNext(i,&rect);){
|
|
int x = rect.x1;
|
|
int y = rect.y1;
|
|
int w = rect.x2 - x;
|
|
int h = rect.y2 - y;
|
|
int n;
|
|
/* We need to count the number of rects in the scaled screen */
|
|
if (cl->screen!=cl->scaledScreen)
|
|
rfbScaledCorrection(cl->screen, cl->scaledScreen, &x, &y, &w, &h, "rfbSendFramebufferUpdate");
|
|
n = rfbNumCodedRectsTight(cl, x, y, w, h);
|
|
if (n == 0) {
|
|
nUpdateRegionRects = 0xFFFF;
|
|
break;
|
|
}
|
|
nUpdateRegionRects += n;
|
|
}
|
|
sraRgnReleaseIterator(i); i=NULL;
|
|
#endif
|
|
} else {
|
|
nUpdateRegionRects = sraRgnCountRects(updateRegion);
|
|
}
|
|
|
|
fu->type = rfbFramebufferUpdate;
|
|
if (nUpdateRegionRects != 0xFFFF) {
|
|
if(cl->screen->maxRectsPerUpdate>0
|
|
/* CoRRE splits the screen into smaller squares */
|
|
&& cl->preferredEncoding != rfbEncodingCoRRE
|
|
/* Ultra encoding splits rectangles up into smaller chunks */
|
|
&& cl->preferredEncoding != rfbEncodingUltra
|
|
#ifdef LIBVNCSERVER_HAVE_LIBZ
|
|
/* Zlib encoding splits rectangles up into smaller chunks */
|
|
&& cl->preferredEncoding != rfbEncodingZlib
|
|
#ifdef LIBVNCSERVER_HAVE_LIBJPEG
|
|
/* Tight encoding counts the rectangles differently */
|
|
&& cl->preferredEncoding != rfbEncodingTight
|
|
#endif
|
|
#endif
|
|
#ifdef LIBVNCSERVER_HAVE_LIBPNG
|
|
/* Tight encoding counts the rectangles differently */
|
|
&& cl->preferredEncoding != rfbEncodingTightPng
|
|
#endif
|
|
&& nUpdateRegionRects>cl->screen->maxRectsPerUpdate) {
|
|
sraRegion* newUpdateRegion = sraRgnBBox(updateRegion);
|
|
sraRgnDestroy(updateRegion);
|
|
updateRegion = newUpdateRegion;
|
|
nUpdateRegionRects = sraRgnCountRects(updateRegion);
|
|
}
|
|
fu->nRects = Swap16IfLE((uint16_t)(sraRgnCountRects(updateCopyRegion) +
|
|
nUpdateRegionRects +
|
|
!!sendCursorShape + !!sendCursorPos + !!sendKeyboardLedState +
|
|
!!sendSupportedMessages + !!sendSupportedEncodings + !!sendServerIdentity));
|
|
} else {
|
|
fu->nRects = 0xFFFF;
|
|
}
|
|
cl->ublen = sz_rfbFramebufferUpdateMsg;
|
|
|
|
if (sendCursorShape) {
|
|
cl->cursorWasChanged = FALSE;
|
|
if (!rfbSendCursorShape(cl))
|
|
goto updateFailed;
|
|
}
|
|
|
|
if (sendCursorPos) {
|
|
cl->cursorWasMoved = FALSE;
|
|
if (!rfbSendCursorPos(cl))
|
|
goto updateFailed;
|
|
}
|
|
|
|
if (sendKeyboardLedState) {
|
|
if (!rfbSendKeyboardLedState(cl))
|
|
goto updateFailed;
|
|
}
|
|
|
|
if (sendSupportedMessages) {
|
|
if (!rfbSendSupportedMessages(cl))
|
|
goto updateFailed;
|
|
}
|
|
if (sendSupportedEncodings) {
|
|
if (!rfbSendSupportedEncodings(cl))
|
|
goto updateFailed;
|
|
}
|
|
if (sendServerIdentity) {
|
|
if (!rfbSendServerIdentity(cl))
|
|
goto updateFailed;
|
|
}
|
|
|
|
if (!sraRgnEmpty(updateCopyRegion)) {
|
|
if (!rfbSendCopyRegion(cl,updateCopyRegion,dx,dy))
|
|
goto updateFailed;
|
|
}
|
|
|
|
for(i = sraRgnGetIterator(updateRegion); sraRgnIteratorNext(i,&rect);){
|
|
int x = rect.x1;
|
|
int y = rect.y1;
|
|
int w = rect.x2 - x;
|
|
int h = rect.y2 - y;
|
|
|
|
/* We need to count the number of rects in the scaled screen */
|
|
if (cl->screen!=cl->scaledScreen)
|
|
rfbScaledCorrection(cl->screen, cl->scaledScreen, &x, &y, &w, &h, "rfbSendFramebufferUpdate");
|
|
|
|
switch (cl->preferredEncoding) {
|
|
case -1:
|
|
case rfbEncodingRaw:
|
|
if (!rfbSendRectEncodingRaw(cl, x, y, w, h))
|
|
goto updateFailed;
|
|
break;
|
|
case rfbEncodingRRE:
|
|
if (!rfbSendRectEncodingRRE(cl, x, y, w, h))
|
|
goto updateFailed;
|
|
break;
|
|
case rfbEncodingCoRRE:
|
|
if (!rfbSendRectEncodingCoRRE(cl, x, y, w, h))
|
|
goto updateFailed;
|
|
break;
|
|
case rfbEncodingHextile:
|
|
if (!rfbSendRectEncodingHextile(cl, x, y, w, h))
|
|
goto updateFailed;
|
|
break;
|
|
case rfbEncodingUltra:
|
|
if (!rfbSendRectEncodingUltra(cl, x, y, w, h))
|
|
goto updateFailed;
|
|
break;
|
|
#ifdef LIBVNCSERVER_HAVE_LIBZ
|
|
case rfbEncodingZlib:
|
|
if (!rfbSendRectEncodingZlib(cl, x, y, w, h))
|
|
goto updateFailed;
|
|
break;
|
|
case rfbEncodingZRLE:
|
|
case rfbEncodingZYWRLE:
|
|
if (!rfbSendRectEncodingZRLE(cl, x, y, w, h))
|
|
goto updateFailed;
|
|
break;
|
|
#endif
|
|
#if defined(LIBVNCSERVER_HAVE_LIBJPEG) && (defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG))
|
|
case rfbEncodingTight:
|
|
if (!rfbSendRectEncodingTight(cl, x, y, w, h))
|
|
goto updateFailed;
|
|
break;
|
|
#ifdef LIBVNCSERVER_HAVE_LIBPNG
|
|
case rfbEncodingTightPng:
|
|
if (!rfbSendRectEncodingTightPng(cl, x, y, w, h))
|
|
goto updateFailed;
|
|
break;
|
|
#endif
|
|
#endif
|
|
}
|
|
}
|
|
if (i) {
|
|
sraRgnReleaseIterator(i);
|
|
i = NULL;
|
|
}
|
|
|
|
if ( nUpdateRegionRects == 0xFFFF &&
|
|
!rfbSendLastRectMarker(cl) )
|
|
goto updateFailed;
|
|
|
|
if (!rfbSendUpdateBuf(cl)) {
|
|
updateFailed:
|
|
result = FALSE;
|
|
}
|
|
|
|
if (!cl->enableCursorShapeUpdates) {
|
|
rfbHideCursor(cl);
|
|
}
|
|
|
|
if(i)
|
|
sraRgnReleaseIterator(i);
|
|
sraRgnDestroy(updateRegion);
|
|
sraRgnDestroy(updateCopyRegion);
|
|
|
|
if(cl->screen->displayFinishedHook)
|
|
cl->screen->displayFinishedHook(cl, result);
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
* Send the copy region as a string of CopyRect encoded rectangles.
|
|
* The only slightly tricky thing is that we should send the messages in
|
|
* the correct order so that an earlier CopyRect will not corrupt the source
|
|
* of a later one.
|
|
*/
|
|
|
|
rfbBool
|
|
rfbSendCopyRegion(rfbClientPtr cl,
|
|
sraRegionPtr reg,
|
|
int dx,
|
|
int dy)
|
|
{
|
|
int x, y, w, h;
|
|
rfbFramebufferUpdateRectHeader rect;
|
|
rfbCopyRect cr;
|
|
sraRectangleIterator* i;
|
|
sraRect rect1;
|
|
|
|
/* printf("copyrect: "); sraRgnPrint(reg); putchar('\n');fflush(stdout); */
|
|
i = sraRgnGetReverseIterator(reg,dx>0,dy>0);
|
|
|
|
/* correct for the scale of the screen */
|
|
dx = ScaleX(cl->screen, cl->scaledScreen, dx);
|
|
dy = ScaleX(cl->screen, cl->scaledScreen, dy);
|
|
|
|
while(sraRgnIteratorNext(i,&rect1)) {
|
|
x = rect1.x1;
|
|
y = rect1.y1;
|
|
w = rect1.x2 - x;
|
|
h = rect1.y2 - y;
|
|
|
|
/* correct for scaling (if necessary) */
|
|
rfbScaledCorrection(cl->screen, cl->scaledScreen, &x, &y, &w, &h, "copyrect");
|
|
|
|
rect.r.x = Swap16IfLE(x);
|
|
rect.r.y = Swap16IfLE(y);
|
|
rect.r.w = Swap16IfLE(w);
|
|
rect.r.h = Swap16IfLE(h);
|
|
rect.encoding = Swap32IfLE(rfbEncodingCopyRect);
|
|
|
|
memcpy(&cl->updateBuf[cl->ublen], (char *)&rect,
|
|
sz_rfbFramebufferUpdateRectHeader);
|
|
cl->ublen += sz_rfbFramebufferUpdateRectHeader;
|
|
|
|
cr.srcX = Swap16IfLE(x - dx);
|
|
cr.srcY = Swap16IfLE(y - dy);
|
|
|
|
memcpy(&cl->updateBuf[cl->ublen], (char *)&cr, sz_rfbCopyRect);
|
|
cl->ublen += sz_rfbCopyRect;
|
|
|
|
rfbStatRecordEncodingSent(cl, rfbEncodingCopyRect, sz_rfbFramebufferUpdateRectHeader + sz_rfbCopyRect,
|
|
w * h * (cl->scaledScreen->bitsPerPixel / 8));
|
|
}
|
|
sraRgnReleaseIterator(i);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Send a given rectangle in raw encoding (rfbEncodingRaw).
|
|
*/
|
|
|
|
rfbBool
|
|
rfbSendRectEncodingRaw(rfbClientPtr cl,
|
|
int x,
|
|
int y,
|
|
int w,
|
|
int h)
|
|
{
|
|
rfbFramebufferUpdateRectHeader rect;
|
|
int nlines;
|
|
int bytesPerLine = w * (cl->format.bitsPerPixel / 8);
|
|
char *fbptr = (cl->scaledScreen->frameBuffer + (cl->scaledScreen->paddedWidthInBytes * y)
|
|
+ (x * (cl->scaledScreen->bitsPerPixel / 8)));
|
|
|
|
/* Flush the buffer to guarantee correct alignment for translateFn(). */
|
|
if (cl->ublen > 0) {
|
|
if (!rfbSendUpdateBuf(cl))
|
|
return FALSE;
|
|
}
|
|
|
|
rect.r.x = Swap16IfLE(x);
|
|
rect.r.y = Swap16IfLE(y);
|
|
rect.r.w = Swap16IfLE(w);
|
|
rect.r.h = Swap16IfLE(h);
|
|
rect.encoding = Swap32IfLE(rfbEncodingRaw);
|
|
|
|
memcpy(&cl->updateBuf[cl->ublen], (char *)&rect,sz_rfbFramebufferUpdateRectHeader);
|
|
cl->ublen += sz_rfbFramebufferUpdateRectHeader;
|
|
|
|
|
|
rfbStatRecordEncodingSent(cl, rfbEncodingRaw, sz_rfbFramebufferUpdateRectHeader + bytesPerLine * h,
|
|
sz_rfbFramebufferUpdateRectHeader + bytesPerLine * h);
|
|
|
|
nlines = (UPDATE_BUF_SIZE - cl->ublen) / bytesPerLine;
|
|
|
|
while (TRUE) {
|
|
if (nlines > h)
|
|
nlines = h;
|
|
|
|
(*cl->translateFn)(cl->translateLookupTable,
|
|
&(cl->screen->serverFormat),
|
|
&cl->format, fbptr, &cl->updateBuf[cl->ublen],
|
|
cl->scaledScreen->paddedWidthInBytes, w, nlines);
|
|
|
|
cl->ublen += nlines * bytesPerLine;
|
|
h -= nlines;
|
|
|
|
if (h == 0) /* rect fitted in buffer, do next one */
|
|
return TRUE;
|
|
|
|
/* buffer full - flush partial rect and do another nlines */
|
|
|
|
if (!rfbSendUpdateBuf(cl))
|
|
return FALSE;
|
|
|
|
fbptr += (cl->scaledScreen->paddedWidthInBytes * nlines);
|
|
|
|
nlines = (UPDATE_BUF_SIZE - cl->ublen) / bytesPerLine;
|
|
if (nlines == 0) {
|
|
rfbErr("rfbSendRectEncodingRaw: send buffer too small for %d "
|
|
"bytes per line\n", bytesPerLine);
|
|
rfbCloseClient(cl);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Send an empty rectangle with encoding field set to value of
|
|
* rfbEncodingLastRect to notify client that this is the last
|
|
* rectangle in framebuffer update ("LastRect" extension of RFB
|
|
* protocol).
|
|
*/
|
|
|
|
rfbBool
|
|
rfbSendLastRectMarker(rfbClientPtr cl)
|
|
{
|
|
rfbFramebufferUpdateRectHeader rect;
|
|
|
|
if (cl->ublen + sz_rfbFramebufferUpdateRectHeader > UPDATE_BUF_SIZE) {
|
|
if (!rfbSendUpdateBuf(cl))
|
|
return FALSE;
|
|
}
|
|
|
|
rect.encoding = Swap32IfLE(rfbEncodingLastRect);
|
|
rect.r.x = 0;
|
|
rect.r.y = 0;
|
|
rect.r.w = 0;
|
|
rect.r.h = 0;
|
|
|
|
memcpy(&cl->updateBuf[cl->ublen], (char *)&rect,sz_rfbFramebufferUpdateRectHeader);
|
|
cl->ublen += sz_rfbFramebufferUpdateRectHeader;
|
|
|
|
|
|
rfbStatRecordEncodingSent(cl, rfbEncodingLastRect, sz_rfbFramebufferUpdateRectHeader, sz_rfbFramebufferUpdateRectHeader);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*
|
|
* Send NewFBSize pseudo-rectangle. This tells the client to change
|
|
* its framebuffer size.
|
|
*/
|
|
|
|
rfbBool
|
|
rfbSendNewFBSize(rfbClientPtr cl,
|
|
int w,
|
|
int h)
|
|
{
|
|
rfbFramebufferUpdateRectHeader rect;
|
|
|
|
if (cl->ublen + sz_rfbFramebufferUpdateRectHeader > UPDATE_BUF_SIZE) {
|
|
if (!rfbSendUpdateBuf(cl))
|
|
return FALSE;
|
|
}
|
|
|
|
if (cl->PalmVNC==TRUE)
|
|
rfbLog("Sending rfbEncodingNewFBSize in response to a PalmVNC style framebuffer resize (%dx%d)\n", w, h);
|
|
else
|
|
rfbLog("Sending rfbEncodingNewFBSize for resize to (%dx%d)\n", w, h);
|
|
|
|
rect.encoding = Swap32IfLE(rfbEncodingNewFBSize);
|
|
rect.r.x = 0;
|
|
rect.r.y = 0;
|
|
rect.r.w = Swap16IfLE(w);
|
|
rect.r.h = Swap16IfLE(h);
|
|
|
|
memcpy(&cl->updateBuf[cl->ublen], (char *)&rect,
|
|
sz_rfbFramebufferUpdateRectHeader);
|
|
cl->ublen += sz_rfbFramebufferUpdateRectHeader;
|
|
|
|
rfbStatRecordEncodingSent(cl, rfbEncodingNewFBSize, sz_rfbFramebufferUpdateRectHeader, sz_rfbFramebufferUpdateRectHeader);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*
|
|
* Send the contents of cl->updateBuf. Returns 1 if successful, -1 if
|
|
* not (errno should be set).
|
|
*/
|
|
|
|
rfbBool
|
|
rfbSendUpdateBuf(rfbClientPtr cl)
|
|
{
|
|
if(cl->sock<0)
|
|
return FALSE;
|
|
|
|
if (rfbWriteExact(cl, cl->updateBuf, cl->ublen) < 0) {
|
|
rfbLogPerror("rfbSendUpdateBuf: write");
|
|
rfbCloseClient(cl);
|
|
return FALSE;
|
|
}
|
|
|
|
cl->ublen = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* rfbSendSetColourMapEntries sends a SetColourMapEntries message to the
|
|
* client, using values from the currently installed colormap.
|
|
*/
|
|
|
|
rfbBool
|
|
rfbSendSetColourMapEntries(rfbClientPtr cl,
|
|
int firstColour,
|
|
int nColours)
|
|
{
|
|
char buf[sz_rfbSetColourMapEntriesMsg + 256 * 3 * 2];
|
|
char *wbuf = buf;
|
|
rfbSetColourMapEntriesMsg *scme;
|
|
uint16_t *rgb;
|
|
rfbColourMap* cm = &cl->screen->colourMap;
|
|
int i, len;
|
|
|
|
if (nColours > 256) {
|
|
/* some rare hardware has, e.g., 4096 colors cells: PseudoColor:12 */
|
|
wbuf = (char *) malloc(sz_rfbSetColourMapEntriesMsg + nColours * 3 * 2);
|
|
}
|
|
|
|
scme = (rfbSetColourMapEntriesMsg *)wbuf;
|
|
rgb = (uint16_t *)(&wbuf[sz_rfbSetColourMapEntriesMsg]);
|
|
|
|
scme->type = rfbSetColourMapEntries;
|
|
|
|
scme->firstColour = Swap16IfLE(firstColour);
|
|
scme->nColours = Swap16IfLE(nColours);
|
|
|
|
len = sz_rfbSetColourMapEntriesMsg;
|
|
|
|
for (i = 0; i < nColours; i++) {
|
|
if(i<(int)cm->count) {
|
|
if(cm->is16) {
|
|
rgb[i*3] = Swap16IfLE(cm->data.shorts[i*3]);
|
|
rgb[i*3+1] = Swap16IfLE(cm->data.shorts[i*3+1]);
|
|
rgb[i*3+2] = Swap16IfLE(cm->data.shorts[i*3+2]);
|
|
} else {
|
|
rgb[i*3] = Swap16IfLE((unsigned short)cm->data.bytes[i*3]);
|
|
rgb[i*3+1] = Swap16IfLE((unsigned short)cm->data.bytes[i*3+1]);
|
|
rgb[i*3+2] = Swap16IfLE((unsigned short)cm->data.bytes[i*3+2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
len += nColours * 3 * 2;
|
|
|
|
LOCK(cl->sendMutex);
|
|
if (rfbWriteExact(cl, wbuf, len) < 0) {
|
|
rfbLogPerror("rfbSendSetColourMapEntries: write");
|
|
rfbCloseClient(cl);
|
|
if (wbuf != buf) free(wbuf);
|
|
UNLOCK(cl->sendMutex);
|
|
return FALSE;
|
|
}
|
|
UNLOCK(cl->sendMutex);
|
|
|
|
rfbStatRecordMessageSent(cl, rfbSetColourMapEntries, len, len);
|
|
if (wbuf != buf) free(wbuf);
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* rfbSendBell sends a Bell message to all the clients.
|
|
*/
|
|
|
|
void
|
|
rfbSendBell(rfbScreenInfoPtr rfbScreen)
|
|
{
|
|
rfbClientIteratorPtr i;
|
|
rfbClientPtr cl;
|
|
rfbBellMsg b;
|
|
|
|
i = rfbGetClientIterator(rfbScreen);
|
|
while((cl=rfbClientIteratorNext(i))) {
|
|
b.type = rfbBell;
|
|
LOCK(cl->sendMutex);
|
|
if (rfbWriteExact(cl, (char *)&b, sz_rfbBellMsg) < 0) {
|
|
rfbLogPerror("rfbSendBell: write");
|
|
rfbCloseClient(cl);
|
|
}
|
|
UNLOCK(cl->sendMutex);
|
|
}
|
|
rfbStatRecordMessageSent(cl, rfbBell, sz_rfbBellMsg, sz_rfbBellMsg);
|
|
rfbReleaseClientIterator(i);
|
|
}
|
|
|
|
|
|
/*
|
|
* rfbSendServerCutText sends a ServerCutText message to all the clients.
|
|
*/
|
|
|
|
void
|
|
rfbSendServerCutText(rfbScreenInfoPtr rfbScreen,char *str, int len)
|
|
{
|
|
rfbClientPtr cl;
|
|
rfbServerCutTextMsg sct;
|
|
rfbClientIteratorPtr iterator;
|
|
|
|
iterator = rfbGetClientIterator(rfbScreen);
|
|
while ((cl = rfbClientIteratorNext(iterator)) != NULL) {
|
|
sct.type = rfbServerCutText;
|
|
sct.length = Swap32IfLE(len);
|
|
LOCK(cl->sendMutex);
|
|
if (rfbWriteExact(cl, (char *)&sct,
|
|
sz_rfbServerCutTextMsg) < 0) {
|
|
rfbLogPerror("rfbSendServerCutText: write");
|
|
rfbCloseClient(cl);
|
|
UNLOCK(cl->sendMutex);
|
|
continue;
|
|
}
|
|
if (rfbWriteExact(cl, str, len) < 0) {
|
|
rfbLogPerror("rfbSendServerCutText: write");
|
|
rfbCloseClient(cl);
|
|
}
|
|
UNLOCK(cl->sendMutex);
|
|
rfbStatRecordMessageSent(cl, rfbServerCutText, sz_rfbServerCutTextMsg+len, sz_rfbServerCutTextMsg+len);
|
|
}
|
|
rfbReleaseClientIterator(iterator);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* UDP can be used for keyboard and pointer events when the underlying
|
|
* network is highly reliable. This is really here to support ORL's
|
|
* videotile, whose TCP implementation doesn't like sending lots of small
|
|
* packets (such as 100s of pen readings per second!).
|
|
*/
|
|
|
|
static unsigned char ptrAcceleration = 50;
|
|
|
|
void
|
|
rfbNewUDPConnection(rfbScreenInfoPtr rfbScreen,
|
|
int sock)
|
|
{
|
|
if (write(sock, (char*) &ptrAcceleration, 1) < 0) {
|
|
rfbLogPerror("rfbNewUDPConnection: write");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Because UDP is a message based service, we can't read the first byte and
|
|
* then the rest of the packet separately like we do with TCP. We will always
|
|
* get a whole packet delivered in one go, so we ask read() for the maximum
|
|
* number of bytes we can possibly get.
|
|
*/
|
|
|
|
void
|
|
rfbProcessUDPInput(rfbScreenInfoPtr rfbScreen)
|
|
{
|
|
int n;
|
|
rfbClientPtr cl=rfbScreen->udpClient;
|
|
rfbClientToServerMsg msg;
|
|
|
|
if((!cl) || cl->onHold)
|
|
return;
|
|
|
|
if ((n = read(rfbScreen->udpSock, (char *)&msg, sizeof(msg))) <= 0) {
|
|
if (n < 0) {
|
|
rfbLogPerror("rfbProcessUDPInput: read");
|
|
}
|
|
rfbDisconnectUDPSock(rfbScreen);
|
|
return;
|
|
}
|
|
|
|
switch (msg.type) {
|
|
|
|
case rfbKeyEvent:
|
|
if (n != sz_rfbKeyEventMsg) {
|
|
rfbErr("rfbProcessUDPInput: key event incorrect length\n");
|
|
rfbDisconnectUDPSock(rfbScreen);
|
|
return;
|
|
}
|
|
cl->screen->kbdAddEvent(msg.ke.down, (rfbKeySym)Swap32IfLE(msg.ke.key), cl);
|
|
break;
|
|
|
|
case rfbPointerEvent:
|
|
if (n != sz_rfbPointerEventMsg) {
|
|
rfbErr("rfbProcessUDPInput: ptr event incorrect length\n");
|
|
rfbDisconnectUDPSock(rfbScreen);
|
|
return;
|
|
}
|
|
cl->screen->ptrAddEvent(msg.pe.buttonMask,
|
|
Swap16IfLE(msg.pe.x), Swap16IfLE(msg.pe.y), cl);
|
|
break;
|
|
|
|
default:
|
|
rfbErr("rfbProcessUDPInput: unknown message type %d\n",
|
|
msg.type);
|
|
rfbDisconnectUDPSock(rfbScreen);
|
|
}
|
|
}
|
|
|
|
|