diff options
Diffstat (limited to 'libvncserver')
26 files changed, 971 insertions, 876 deletions
diff --git a/libvncserver/Makefile.am b/libvncserver/Makefile.am deleted file mode 100644 index e25784b..0000000 --- a/libvncserver/Makefile.am +++ /dev/null @@ -1,80 +0,0 @@ -AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/common - -if WITH_TIGHTVNC_FILETRANSFER -TIGHTVNCFILETRANSFERHDRS=tightvnc-filetransfer/filelistinfo.h \ - tightvnc-filetransfer/filetransfermsg.h \ - tightvnc-filetransfer/handlefiletransferrequest.h \ - tightvnc-filetransfer/rfbtightproto.h - -TIGHTVNCFILETRANSFERSRCS = tightvnc-filetransfer/rfbtightserver.c \ - tightvnc-filetransfer/handlefiletransferrequest.c \ - tightvnc-filetransfer/filetransfermsg.c \ - tightvnc-filetransfer/filelistinfo.c -endif - -if WITH_WEBSOCKETS - -if HAVE_GNUTLS -WEBSOCKETSSSLSRCS = rfbssl_gnutls.c rfbcrypto_gnutls.c -WEBSOCKETSSSLLIBS = @GNUTLS_LIBS@ -else -if HAVE_LIBSSL -WEBSOCKETSSSLSRCS = rfbssl_openssl.c rfbcrypto_openssl.c -WEBSOCKETSSSLLIBS = @SSL_LIBS@ @CRYPT_LIBS@ -else -WEBSOCKETSSSLSRCS = rfbssl_none.c rfbcrypto_included.c ../common/md5.c ../common/sha1.c -endif -endif - -WEBSOCKETSSRCS = websockets.c $(WEBSOCKETSSSLSRCS) -endif - -includedir=$(prefix)/include/rfb - -include_HEADERS=../rfb/rfb.h ../rfb/rfbconfig.h \ - ../rfb/rfbproto.h ../rfb/keysym.h ../rfb/rfbregion.h ../rfb/rfbclient.h - -noinst_HEADERS=../common/d3des.h ../rfb/default8x16.h zrleoutstream.h \ - zrlepalettehelper.h zrletypes.h private.h scale.h rfbssl.h rfbcrypto.h \ - ../common/minilzo.h ../common/lzoconf.h ../common/lzodefs.h ../common/md5.h ../common/sha.h ../common/sha-private.h \ - $(TIGHTVNCFILETRANSFERHDRS) - -EXTRA_DIST=tableinit24.c tableinittctemplate.c tabletranstemplate.c \ - tableinitcmtemplate.c tabletrans24template.c \ - zrleencodetemplate.c - -if HAVE_LIBZ -ZLIBSRCS = zlib.c zrle.c zrleoutstream.c zrlepalettehelper.c ../common/zywrletemplate.c -if HAVE_LIBJPEG -TIGHTSRCS = tight.c ../common/turbojpeg.c -endif -endif - -LIB_SRCS = main.c rfbserver.c rfbregion.c auth.c sockets.c $(WEBSOCKETSSRCS) \ - stats.c corre.c hextile.c rre.c translate.c cutpaste.c \ - httpd.c cursor.c font.c \ - draw.c selbox.c ../common/d3des.c ../common/vncauth.c cargs.c ../common/minilzo.c ultra.c scale.c \ - $(ZLIBSRCS) $(TIGHTSRCS) $(TIGHTVNCFILETRANSFERSRCS) - -libvncserver_la_SOURCES=$(LIB_SRCS) -libvncserver_la_LIBADD=$(WEBSOCKETSSSLLIBS) - -if WITH_SYSTEMD -AM_CPPFLAGS += -DLIBVNCSERVER_WITH_SYSTEMD -libvncserver_la_CFLAGS = $(LIBSYSTEMD_CFLAGS) -libvncserver_la_LIBADD += $(LIBSYSTEMD_LIBS) -endif - -lib_LTLIBRARIES=libvncserver.la -libvncserver_la_LDFLAGS = -version-info 1:0:0 - -if HAVE_RPM -$(PACKAGE)-$(VERSION).tar.gz: dist - -# Rule to build RPM distribution package -rpm: $(PACKAGE)-$(VERSION).tar.gz libvncserver.spec - cp $(PACKAGE)-$(VERSION).tar.gz @RPMSOURCEDIR@ - rpmbuild -ba libvncserver.spec -endif - - diff --git a/libvncserver/cargs.c b/libvncserver/cargs.c index 4da04b5..85b937d 100644 --- a/libvncserver/cargs.c +++ b/libvncserver/cargs.c @@ -43,6 +43,10 @@ rfbUsage(void) "new non-shared\n" " connection comes in (refuse new connection " "instead)\n"); +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS + fprintf(stderr, "-sslkeyfile path set path to private key file for encrypted WebSockets connections\n"); + fprintf(stderr, "-sslcertfile path set path to certificate file for encrypted WebSockets connections\n"); +#endif fprintf(stderr, "-httpdir dir-path enable http server using dir-path home\n"); fprintf(stderr, "-httpport portnum use portnum for http connection\n"); #ifdef LIBVNCSERVER_IPv6 diff --git a/libvncserver/cursor.c b/libvncserver/cursor.c index c071dd9..8779470 100644 --- a/libvncserver/cursor.c +++ b/libvncserver/cursor.c @@ -456,10 +456,10 @@ void rfbMakeRichCursorFromXCursor(rfbScreenInfoPtr rfbScreen,rfbCursorPtr cursor fore+=4-bpp; } - background=cursor->backRed<<format->redShift| - cursor->backGreen<<format->greenShift|cursor->backBlue<<format->blueShift; - foreground=cursor->foreRed<<format->redShift| - cursor->foreGreen<<format->greenShift|cursor->foreBlue<<format->blueShift; + background=(uint32_t)cursor->backRed<<format->redShift| + (uint32_t)cursor->backGreen<<format->greenShift|(uint32_t)cursor->backBlue<<format->blueShift; + foreground=(uint32_t)cursor->foreRed<<format->redShift| + (uint32_t)cursor->foreGreen<<format->greenShift|(uint32_t)cursor->foreBlue<<format->blueShift; for(j=0;j<cursor->height;j++) for(i=0,bit=0x80;i<cursor->width;i++,bit=(bit&1)?0x80:bit>>1,cp+=bpp) diff --git a/libvncserver/font.c b/libvncserver/font.c index a9091d2..9935e91 100644 --- a/libvncserver/font.c +++ b/libvncserver/font.c @@ -174,6 +174,7 @@ rfbFontDataPtr rfbLoadConsoleFont(char *filename) if(1!=fread(p->data,4096,1,f)) { free(p->data); free(p); + fclose(f); return NULL; } fclose(f); diff --git a/libvncserver/httpd.c b/libvncserver/httpd.c index 80adbcf..a3ef74d 100644 --- a/libvncserver/httpd.c +++ b/libvncserver/httpd.c @@ -42,6 +42,7 @@ #include <winsock2.h> #include <ws2tcpip.h> #define close closesocket +#define strcasecmp _stricmp #if defined(_MSC_VER) #include <BaseTsd.h> /* For the missing ssize_t */ #define ssize_t SSIZE_T @@ -456,6 +457,8 @@ httpProcessInput(rfbScreenInfoPtr rfbScreen) contentType = "Content-Type: text/css\r\n"; else if(ext && strcasecmp(ext, ".svg") == 0) contentType = "Content-Type: image/svg+xml\r\n"; + else if(ext && strcasecmp(ext, ".js") == 0) + contentType = "Content-Type: application/javascript\r\n"; rfbWriteExact(&cl, contentType, strlen(contentType)); /* end the header */ rfbWriteExact(&cl, "\r\n", 2); diff --git a/libvncserver/main.c b/libvncserver/main.c index c185eb8..74010d0 100644 --- a/libvncserver/main.c +++ b/libvncserver/main.c @@ -32,6 +32,7 @@ extern "C" { #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> + #include <fcntl.h> #endif #include <signal.h> @@ -282,7 +283,16 @@ rfbLogProc rfbErr=rfbDefaultLog; void rfbLogPerror(const char *str) { +#ifdef WIN32 + wchar_t *s = NULL; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errno, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&s, 0, NULL); + rfbErr("%s: %S\n", str, s); + LocalFree(s); +#else rfbErr("%s: %s\n", str, strerror(errno)); +#endif } void rfbScheduleCopyRegion(rfbScreenInfoPtr rfbScreen,sraRegionPtr copyRegion,int dx,int dy) @@ -550,6 +560,7 @@ clientInput(void *data) FD_ZERO(&rfds); FD_SET(cl->sock, &rfds); + FD_SET(cl->pipe_notify_client_thread[0], &rfds); FD_ZERO(&efds); FD_SET(cl->sock, &efds); @@ -558,9 +569,13 @@ clientInput(void *data) if ((cl->fileTransfer.fd!=-1) && (cl->fileTransfer.sending==1)) FD_SET(cl->sock, &wfds); + int nfds = cl->pipe_notify_client_thread[0] > cl->sock ? cl->pipe_notify_client_thread[0] : cl->sock; + tv.tv_sec = 60; /* 1 minute */ tv.tv_usec = 0; - n = select(cl->sock + 1, &rfds, &wfds, &efds, &tv); + + n = select(nfds + 1, &rfds, &wfds, &efds, &tv); + if (n < 0) { rfbLogPerror("ReadExact: select"); break; @@ -575,6 +590,13 @@ clientInput(void *data) if (FD_ISSET(cl->sock, &wfds)) rfbSendFileTransferChunk(cl); + if (FD_ISSET(cl->pipe_notify_client_thread[0], &rfds)) + { + /* Reset the pipe */ + char buf; + while (read(cl->pipe_notify_client_thread[0], &buf, sizeof(buf)) == sizeof(buf)); + } + if (FD_ISSET(cl->sock, &rfds) || FD_ISSET(cl->sock, &efds)) { #ifdef LIBVNCSERVER_WITH_WEBSOCKETS @@ -669,8 +691,15 @@ rfbStartOnHoldClient(rfbClientPtr cl) { cl->onHold = FALSE; #ifdef LIBVNCSERVER_HAVE_LIBPTHREAD - if(cl->screen->backgroundLoop) - pthread_create(&cl->client_thread, NULL, clientInput, (void *)cl); + if(cl->screen->backgroundLoop) { + if (pipe(cl->pipe_notify_client_thread) == -1) { + cl->pipe_notify_client_thread[0] = -1; + cl->pipe_notify_client_thread[1] = -1; + } + fcntl(cl->pipe_notify_client_thread[0], F_SETFL, O_NONBLOCK); + + pthread_create(&cl->client_thread, NULL, clientInput, (void *)cl); + } #endif } @@ -1119,7 +1148,15 @@ void rfbInitServer(rfbScreenInfoPtr screen) { #ifdef WIN32 WSADATA trash; - WSAStartup(MAKEWORD(2,2),&trash); + static rfbBool WSAinitted=FALSE; + if(!WSAinitted) { + int i=WSAStartup(MAKEWORD(2,0),&trash); + if(i!=0) { + rfbErr("Couldn't init Windows Sockets\n"); + return; + } + WSAinitted=TRUE; + } #endif rfbInitSockets(screen); rfbHttpInitSockets(screen); @@ -1131,15 +1168,32 @@ void rfbInitServer(rfbScreenInfoPtr screen) void rfbShutdownServer(rfbScreenInfoPtr screen,rfbBool disconnectClients) { if(disconnectClients) { - rfbClientPtr cl; rfbClientIteratorPtr iter = rfbGetClientIterator(screen); - while( (cl = rfbClientIteratorNext(iter)) ) { - if (cl->sock > -1) { - /* we don't care about maxfd here, because the server goes away */ - rfbCloseClient(cl); - rfbClientConnectionGone(cl); + rfbClientPtr nextCl, currentCl = rfbClientIteratorNext(iter); + + while(currentCl) { + nextCl = rfbClientIteratorNext(iter); + if (currentCl->sock > -1) { + /* we don't care about maxfd here, because the server goes away */ + rfbCloseClient(currentCl); } + +#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD + /* + Notify the thread. This simply writes a NULL byte to the notify pipe in order to get past the select() + in clientInput(), the loop in there will then break because the rfbCloseClient() above has set + currentCl->sock to -1. + */ + write(currentCl->pipe_notify_client_thread[1], "\x00", 1); + /* And wait for it to finish. */ + pthread_join(currentCl->client_thread, NULL); +#else + rfbClientConnectionGone(currentCl); +#endif + + currentCl = nextCl; } + rfbReleaseClientIterator(iter); } diff --git a/libvncserver/rfbcrypto.h b/libvncserver/rfbcrypto.h deleted file mode 100644 index 9dc3e63..0000000 --- a/libvncserver/rfbcrypto.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef _RFB_CRYPTO_H -#define _RFB_CRYPTO_H 1 - -#include <sys/uio.h> - -#define SHA1_HASH_SIZE 20 -#define MD5_HASH_SIZE 16 - -void digestmd5(const struct iovec *iov, int iovcnt, void *dest); -void digestsha1(const struct iovec *iov, int iovcnt, void *dest); - -#endif diff --git a/libvncserver/rfbcrypto_gnutls.c b/libvncserver/rfbcrypto_gnutls.c deleted file mode 100644 index 2ecb2da..0000000 --- a/libvncserver/rfbcrypto_gnutls.c +++ /dev/null @@ -1,50 +0,0 @@ -/* - * rfbcrypto_gnutls.c - Crypto wrapper (gnutls version) - */ - -/* - * Copyright (C) 2011 Gernot Tenchio - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -#include <string.h> -#include <gcrypt.h> -#include "rfbcrypto.h" - -void digestmd5(const struct iovec *iov, int iovcnt, void *dest) -{ - gcry_md_hd_t c; - int i; - - gcry_md_open(&c, GCRY_MD_MD5, 0); - for (i = 0; i < iovcnt; i++) - gcry_md_write(c, iov[i].iov_base, iov[i].iov_len); - gcry_md_final(c); - memcpy(dest, gcry_md_read(c, 0), gcry_md_get_algo_dlen(GCRY_MD_MD5)); -} - -void digestsha1(const struct iovec *iov, int iovcnt, void *dest) -{ - gcry_md_hd_t c; - int i; - - gcry_md_open(&c, GCRY_MD_SHA1, 0); - for (i = 0; i < iovcnt; i++) - gcry_md_write(c, iov[i].iov_base, iov[i].iov_len); - gcry_md_final(c); - memcpy(dest, gcry_md_read(c, 0), gcry_md_get_algo_dlen(GCRY_MD_SHA1)); -} diff --git a/libvncserver/rfbcrypto_included.c b/libvncserver/rfbcrypto_included.c deleted file mode 100644 index 7feff61..0000000 --- a/libvncserver/rfbcrypto_included.c +++ /dev/null @@ -1,49 +0,0 @@ -/* - * rfbcrypto_included.c - Crypto wrapper (included version) - */ - -/* - * Copyright (C) 2011 Gernot Tenchio - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -#include <string.h> -#include "md5.h" -#include "sha.h" -#include "rfbcrypto.h" - -void digestmd5(const struct iovec *iov, int iovcnt, void *dest) -{ - struct md5_ctx c; - int i; - - __md5_init_ctx(&c); - for (i = 0; i < iovcnt; i++) - __md5_process_bytes(iov[i].iov_base, iov[i].iov_len, &c); - __md5_finish_ctx(&c, dest); -} - -void digestsha1(const struct iovec *iov, int iovcnt, void *dest) -{ - SHA1Context c; - int i; - - SHA1Reset(&c); - for (i = 0; i < iovcnt; i++) - SHA1Input(&c, iov[i].iov_base, iov[i].iov_len); - SHA1Result(&c, dest); -} diff --git a/libvncserver/rfbcrypto_openssl.c b/libvncserver/rfbcrypto_openssl.c deleted file mode 100644 index 29ec5c1..0000000 --- a/libvncserver/rfbcrypto_openssl.c +++ /dev/null @@ -1,49 +0,0 @@ -/* - * rfbcrypto_openssl.c - Crypto wrapper (openssl version) - */ - -/* - * Copyright (C) 2011 Gernot Tenchio - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -#include <string.h> -#include <openssl/sha.h> -#include <openssl/md5.h> -#include "rfbcrypto.h" - -void digestmd5(const struct iovec *iov, int iovcnt, void *dest) -{ - MD5_CTX c; - int i; - - MD5_Init(&c); - for (i = 0; i < iovcnt; i++) - MD5_Update(&c, iov[i].iov_base, iov[i].iov_len); - MD5_Final(dest, &c); -} - -void digestsha1(const struct iovec *iov, int iovcnt, void *dest) -{ - SHA_CTX c; - int i; - - SHA1_Init(&c); - for (i = 0; i < iovcnt; i++) - SHA1_Update(&c, iov[i].iov_base, iov[i].iov_len); - SHA1_Final(dest, &c); -} diff --git a/libvncserver/rfbcrypto_polarssl.c b/libvncserver/rfbcrypto_polarssl.c deleted file mode 100644 index 55e3a7b..0000000 --- a/libvncserver/rfbcrypto_polarssl.c +++ /dev/null @@ -1,26 +0,0 @@ -#include <string.h> -#include <polarssl/md5.h> -#include <polarssl/sha1.h> -#include "rfbcrypto.h" - -void digestmd5(const struct iovec *iov, int iovcnt, void *dest) -{ - md5_context c; - int i; - - md5_starts(&c); - for (i = 0; i < iovcnt; i++) - md5_update(&c, iov[i].iov_base, iov[i].iov_len); - md5_finish(&c, dest); -} - -void digestsha1(const struct iovec *iov, int iovcnt, void *dest) -{ - sha1_context c; - int i; - - sha1_starts(&c); - for (i = 0; i < iovcnt; i++) - sha1_update(&c, iov[i].iov_base, iov[i].iov_len); - sha1_finish(&c, dest); -} diff --git a/libvncserver/rfbserver.c b/libvncserver/rfbserver.c index fa8423d..182d18b 100644 --- a/libvncserver/rfbserver.c +++ b/libvncserver/rfbserver.c @@ -32,6 +32,7 @@ #include <rfb/rfb.h> #include <rfb/rfbregion.h> #include "private.h" +#include "rfb/rfbconfig.h" #ifdef LIBVNCSERVER_HAVE_FCNTL_H #include <fcntl.h> @@ -69,7 +70,9 @@ /* stst() */ #include <sys/types.h> #include <sys/stat.h> +#if LIBVNCSERVER_HAVE_UNISTD_H #include <unistd.h> +#endif #ifndef WIN32 /* readdir() */ @@ -457,9 +460,7 @@ rfbNewTCPOrUDPClient(rfbScreenInfoPtr rfbScreen, #ifdef LIBVNCSERVER_WITH_WEBSOCKETS /* - * Wait a few ms for the client to send one of: - * - Flash policy request - * - WebSockets connection (TLS/SSL or plain) + * Wait a few ms for the client to send WebSockets connection (TLS/SSL or plain) */ if (!webSocketsCheck(cl)) { /* Error reporting handled in webSocketsHandshake */ @@ -615,6 +616,11 @@ rfbClientConnectionGone(rfbClientPtr cl) 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); @@ -1457,11 +1463,21 @@ char *rfbProcessFileTransferReadBuffer(rfbClientPtr cl, uint32_t length) int n=0; FILEXFER_ALLOWED_OR_CLOSE_AND_RETURN("", cl, NULL); + /* - rfbLog("rfbProcessFileTransferReadBuffer(%dlen)\n", length); + We later alloc length+1, which might wrap around on 32-bit systems if length equals + 0XFFFFFFFF, i.e. SIZE_MAX for 32-bit systems. On 64-bit systems, a length of 0XFFFFFFFF + will safely be allocated since this check will never trigger and malloc() can digest length+1 + without problems as length is a uint32_t. */ + if(length == SIZE_MAX) { + rfbErr("rfbProcessFileTransferReadBuffer: too big file transfer length requested: %u", (unsigned int)length); + rfbCloseClient(cl); + return NULL; + } + if (length>0) { - buffer=malloc(length+1); + buffer=malloc((size_t)length+1); if (buffer!=NULL) { if ((n = rfbReadExact(cl, (char *)buffer, length)) <= 0) { if (n != 0) @@ -1993,11 +2009,6 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) char encBuf[64]; char encBuf2[64]; -#ifdef LIBVNCSERVER_WITH_WEBSOCKETS - if (cl->wsctx && webSocketCheckDisconnect(cl)) - return; -#endif - if ((n = rfbReadExact(cl, (char *)&msg, 1)) <= 0) { if (n != 0) rfbLogPerror("rfbProcessClientNormalMessage: read"); @@ -2582,7 +2593,22 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) msg.cct.length = Swap32IfLE(msg.cct.length); - str = (char *)malloc(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); diff --git a/libvncserver/scale.c b/libvncserver/scale.c index bf67f8e..7a63e86 100644 --- a/libvncserver/scale.c +++ b/libvncserver/scale.c @@ -63,7 +63,18 @@ (double) ((int) (x)) : (double) ((int) (x) + 1) ) #define FLOOR(x) ( (double) ((int) (x)) ) -static inline int pad4(int value) +#ifdef WIN32 +#define InlineX __inline +#else +# ifndef __STRICT_ANSI__ +# define InlineX inline +# else +# define InlineX +# endif +#endif + + +static InlineX int pad4(int value) { int remainder = value & 3; if (!remainder) return value; diff --git a/libvncserver/sockets.c b/libvncserver/sockets.c index e4fc52b..4556197 100644 --- a/libvncserver/sockets.c +++ b/libvncserver/sockets.c @@ -100,7 +100,13 @@ int deny_severity=LOG_WARNING; #pragma warning (disable: 4018 4761) #endif #define read(sock,buf,len) recv(sock,buf,len,0) +#ifdef EWOULDBLOCK +#undef EWOULDBLOCK +#endif #define EWOULDBLOCK WSAEWOULDBLOCK +#ifdef ETIMEDOUT +#undef ETIMEDOUT +#endif #define ETIMEDOUT WSAETIMEDOUT #define write(sock,buf,len) send(sock,buf,len,0) #else diff --git a/libvncserver/tableinit24.c b/libvncserver/tableinit24.c index 39e9920..5c5823c 100644 --- a/libvncserver/tableinit24.c +++ b/libvncserver/tableinit24.c @@ -147,11 +147,12 @@ rfbInitOneRGBTable24 (uint8_t *table, int inMax, int outMax, int outShift, for (i = 0; i < nEntries; i++) { outValue = ((i * outMax + inMax / 2) / inMax) << outShift; *(uint32_t *)&table[3*i] = outValue; - if(!rfbEndianTest) + if(!rfbEndianTest) { memmove(table+3*i,table+3*i+1,3); - if (swap) { - c = table[3*i]; table[3*i] = table[3*i+2]; - table[3*i+2] = c; - } + } + if (swap) { + c = table[3*i]; table[3*i] = table[3*i+2]; + table[3*i+2] = c; + } } } diff --git a/libvncserver/tight.c b/libvncserver/tight.c index bca374d..d6f4749 100644 --- a/libvncserver/tight.c +++ b/libvncserver/tight.c @@ -57,10 +57,11 @@ * that we resort to using thread local storage instead of having * per-client data. */ -#if LIBVNCSERVER_HAVE_LIBPTHREAD && LIBVNCSERVER_HAVE_TLS && !defined(TLS) && defined(__linux__) +#if defined(__GNUC__) #define TLS __thread -#endif -#ifndef TLS +#elif defined(_MSC_VER) +#define TLS __declspec(thread) +#else #define TLS #endif @@ -191,7 +192,6 @@ static rfbBool CheckSolidTile32 (rfbClientPtr cl, int x, int y, int w, int h, static rfbBool SendRectSimple (rfbClientPtr cl, int x, int y, int w, int h); static rfbBool SendSubrect (rfbClientPtr cl, int x, int y, int w, int h); -static rfbBool SendTightHeader (rfbClientPtr cl, int x, int y, int w, int h); static rfbBool SendSolidRect (rfbClientPtr cl); static rfbBool SendMonoRect (rfbClientPtr cl, int x, int y, int w, int h); @@ -200,8 +200,6 @@ static rfbBool SendFullColorRect (rfbClientPtr cl, int x, int y, int w, int h); static rfbBool CompressData (rfbClientPtr cl, int streamId, int dataLen, int zlibLevel, int zlibStrategy); -static rfbBool SendCompressedData (rfbClientPtr cl, char *buf, - int compressedLen); static void FillPalette8 (int count); static void FillPalette16 (int count); @@ -430,7 +428,7 @@ SendRectEncodingTight(rfbClientPtr cl, /* Send solid-color rectangle. */ - if (!SendTightHeader(cl, x_best, y_best, w_best, h_best)) + if (!rfbSendTightHeader(cl, x_best, y_best, w_best, h_best)) return FALSE; fbptr = (cl->scaledScreen->frameBuffer + @@ -683,7 +681,7 @@ SendSubrect(rfbClientPtr cl, return FALSE; } - if (!SendTightHeader(cl, x, y, w, h)) + if (!rfbSendTightHeader(cl, x, y, w, h)) return FALSE; fbptr = (cl->scaledScreen->frameBuffer @@ -767,8 +765,8 @@ SendSubrect(rfbClientPtr cl, return success; } -static rfbBool -SendTightHeader(rfbClientPtr cl, +rfbBool +rfbSendTightHeader(rfbClientPtr cl, int x, int y, int w, @@ -1044,7 +1042,7 @@ CompressData(rfbClientPtr cl, } if (zlibLevel == 0) - return SendCompressedData (cl, tightBeforeBuf, dataLen); + return rfbSendCompressedDataTight(cl, tightBeforeBuf, dataLen); pz = &cl->zsStruct[streamId]; @@ -1083,12 +1081,12 @@ CompressData(rfbClientPtr cl, return FALSE; } - return SendCompressedData(cl, tightAfterBuf, - tightAfterBufSize - pz->avail_out); + return rfbSendCompressedDataTight(cl, tightAfterBuf, + tightAfterBufSize - pz->avail_out); } -static rfbBool SendCompressedData(rfbClientPtr cl, char *buf, - int compressedLen) +rfbBool rfbSendCompressedDataTight(rfbClientPtr cl, char *buf, + int compressedLen) { int i, portionLen; @@ -1665,7 +1663,7 @@ SendJpegRect(rfbClientPtr cl, int x, int y, int w, int h, int quality) cl->updateBuf[cl->ublen++] = (char)(rfbTightJpeg << 4); rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1); - return SendCompressedData(cl, tightAfterBuf, (int)size); + return rfbSendCompressedDataTight(cl, tightAfterBuf, (int)size); } static void @@ -1899,6 +1897,6 @@ static rfbBool SendPngRect(rfbClientPtr cl, int x, int y, int w, int h) { rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1); /* rfbLog("<< SendPngRect\n"); */ - return SendCompressedData(cl, tightAfterBuf, pngDstDataLen); + return rfbSendCompressedDataTight(cl, tightAfterBuf, pngDstDataLen); } #endif diff --git a/libvncserver/tightvnc-filetransfer/filetransfermsg.c b/libvncserver/tightvnc-filetransfer/filetransfermsg.c index 153f123..0003b11 100644 --- a/libvncserver/tightvnc-filetransfer/filetransfermsg.c +++ b/libvncserver/tightvnc-filetransfer/filetransfermsg.c @@ -56,7 +56,9 @@ #endif #include <errno.h> +#if LIBVNCSERVER_HAVE_UNISTD_H #include <unistd.h> +#endif #include <sys/stat.h> #include <sys/types.h> @@ -670,7 +672,7 @@ ChkFileUploadWriteErr(rfbClientPtr cl, rfbTightClientPtr rtcp, char* pBuf) char reason[] = "Error writing file data"; int reasonLen = strlen(reason); ftm = CreateFileUploadErrMsg(reason, reasonLen); - CloseUndoneFileTransfer(cl, rtcp); + CloseUndoneFileUpload(cl, rtcp); } return ftm; } @@ -733,7 +735,7 @@ CreateFileUploadErrMsg(char* reason, unsigned int reasonLen) ******************************************************************************/ void -CloseUndoneFileTransfer(rfbClientPtr cl, rfbTightClientPtr rtcp) +CloseUndoneFileUpload(rfbClientPtr cl, rfbTightClientPtr rtcp) { /* TODO :: File Upload case is not handled currently */ /* TODO :: In case of concurrency we need to use Critical Section */ @@ -757,9 +759,19 @@ CloseUndoneFileTransfer(rfbClientPtr cl, rfbTightClientPtr rtcp) memset(rtcp->rcft.rcfu.fName, 0 , PATH_MAX); } +} + + +void +CloseUndoneFileDownload(rfbClientPtr cl, rfbTightClientPtr rtcp) +{ + if(cl == NULL) + return; if(rtcp->rcft.rcfd.downloadInProgress == TRUE) { rtcp->rcft.rcfd.downloadInProgress = FALSE; + /* the thread will return if downloadInProgress is FALSE */ + pthread_join(rtcp->rcft.rcfd.downloadThread, NULL); if(rtcp->rcft.rcfd.downloadFD != -1) { close(rtcp->rcft.rcfd.downloadFD); diff --git a/libvncserver/tightvnc-filetransfer/filetransfermsg.h b/libvncserver/tightvnc-filetransfer/filetransfermsg.h index 3b27bd0..bbb9148 100644 --- a/libvncserver/tightvnc-filetransfer/filetransfermsg.h +++ b/libvncserver/tightvnc-filetransfer/filetransfermsg.h @@ -51,7 +51,8 @@ FileTransferMsg ChkFileUploadWriteErr(rfbClientPtr cl, rfbTightClientPtr data, c void CreateDirectory(char* dirName); void FileUpdateComplete(rfbClientPtr cl, rfbTightClientPtr data); -void CloseUndoneFileTransfer(rfbClientPtr cl, rfbTightClientPtr data); +void CloseUndoneFileUpload(rfbClientPtr cl, rfbTightClientPtr data); +void CloseUndoneFileDownload(rfbClientPtr cl, rfbTightClientPtr data); void FreeFileTransferMsg(FileTransferMsg ftm); diff --git a/libvncserver/tightvnc-filetransfer/handlefiletransferrequest.c b/libvncserver/tightvnc-filetransfer/handlefiletransferrequest.c index b235fa0..71fb085 100644 --- a/libvncserver/tightvnc-filetransfer/handlefiletransferrequest.c +++ b/libvncserver/tightvnc-filetransfer/handlefiletransferrequest.c @@ -29,7 +29,9 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#if LIBVNCSERVER_HAVE_UNISTD_H #include <unistd.h> +#endif #ifndef _MSC_VER #include <dirent.h> #include <pthread.h> @@ -487,12 +489,6 @@ RunFileDownloadThread(void* client) if(rfbWriteExact(cl, fileDownloadMsg.data, fileDownloadMsg.length) < 0) { rfbLog("File [%s]: Method [%s]: Error while writing to socket \n" , __FILE__, __FUNCTION__); - - if(cl != NULL) { - rfbCloseClient(cl); - CloseUndoneFileTransfer(cl, rtcp); - } - FreeFileTransferMsg(fileDownloadMsg); return NULL; } @@ -506,7 +502,6 @@ RunFileDownloadThread(void* client) void HandleFileDownload(rfbClientPtr cl, rfbTightClientPtr rtcp) { - pthread_t fileDownloadThread; FileTransferMsg fileDownloadMsg; memset(&fileDownloadMsg, 0, sizeof(FileTransferMsg)); @@ -516,10 +511,9 @@ HandleFileDownload(rfbClientPtr cl, rfbTightClientPtr rtcp) FreeFileTransferMsg(fileDownloadMsg); return; } - rtcp->rcft.rcfd.downloadInProgress = FALSE; - rtcp->rcft.rcfd.downloadFD = -1; + CloseUndoneFileDownload(cl, rtcp); - if(pthread_create(&fileDownloadThread, NULL, RunFileDownloadThread, (void*) + if(pthread_create(&rtcp->rcft.rcfd.downloadThread, NULL, RunFileDownloadThread, (void*) cl) != 0) { FileTransferMsg ftm = GetFileDownLoadErrMsg(); @@ -583,13 +577,15 @@ HandleFileDownloadCancelRequest(rfbClientPtr cl, rfbTightClientPtr rtcp) "FileDownloadCancelMsg\n", __FILE__, __FUNCTION__); rfbCloseClient(cl); + free(reason); + return; } rfbLog("File [%s]: Method [%s]: File Download Cancel Request received:" " reason <%s>\n", __FILE__, __FUNCTION__, reason); pthread_mutex_lock(&fileDownloadMutex); - CloseUndoneFileTransfer(cl, rtcp); + CloseUndoneFileDownload(cl, rtcp); pthread_mutex_unlock(&fileDownloadMutex); if(reason != NULL) { @@ -832,7 +828,7 @@ HandleFileUploadDataRequest(rfbClientPtr cl, rfbTightClientPtr rtcp) FreeFileTransferMsg(ftm); } - CloseUndoneFileTransfer(cl, rtcp); + CloseUndoneFileUpload(cl, rtcp); if(pBuf != NULL) { free(pBuf); @@ -932,7 +928,7 @@ HandleFileUploadFailedRequest(rfbClientPtr cl, rfbTightClientPtr rtcp) rfbLog("File [%s]: Method [%s]: File Upload Failed Request received:" " reason <%s>\n", __FILE__, __FUNCTION__, reason); - CloseUndoneFileTransfer(cl, rtcp); + CloseUndoneFileUpload(cl, rtcp); if(reason != NULL) { free(reason); diff --git a/libvncserver/tightvnc-filetransfer/rfbtightproto.h b/libvncserver/tightvnc-filetransfer/rfbtightproto.h index d0fe642..30fc5f5 100644 --- a/libvncserver/tightvnc-filetransfer/rfbtightproto.h +++ b/libvncserver/tightvnc-filetransfer/rfbtightproto.h @@ -148,6 +148,7 @@ typedef struct _rfbClientFileDownload { int downloadInProgress; unsigned long mTime; int downloadFD; + pthread_t downloadThread; } rfbClientFileDownload ; typedef struct _rfbClientFileUpload { diff --git a/libvncserver/tightvnc-filetransfer/rfbtightserver.c b/libvncserver/tightvnc-filetransfer/rfbtightserver.c index 67d4cb5..651d8fb 100644 --- a/libvncserver/tightvnc-filetransfer/rfbtightserver.c +++ b/libvncserver/tightvnc-filetransfer/rfbtightserver.c @@ -26,6 +26,7 @@ #include <rfb/rfb.h> #include "rfbtightproto.h" #include "handlefiletransferrequest.h" +#include "filetransfermsg.h" /* * Get my data! @@ -448,9 +449,11 @@ rfbTightExtensionMsgHandler(struct _rfbClientRec* cl, void* data, void rfbTightExtensionClientClose(rfbClientPtr cl, void* data) { - if(data != NULL) + if(data != NULL) { + CloseUndoneFileUpload(cl, data); + CloseUndoneFileDownload(cl, data); free(data); - + } } void diff --git a/libvncserver/ultra.c b/libvncserver/ultra.c index 83bddaa..cd625a5 100644 --- a/libvncserver/ultra.c +++ b/libvncserver/ultra.c @@ -8,7 +8,11 @@ */ #include <rfb/rfb.h> +#ifdef LIBVNCSERVER_HAVE_LZO +#include <lzo/lzo1x.h> +#else #include "minilzo.h" +#endif /* * cl->beforeEncBuf contains pixel data in the client's format. diff --git a/libvncserver/websockets.c b/libvncserver/websockets.c index f5dd120..4361bdd 100644 --- a/libvncserver/websockets.c +++ b/libvncserver/websockets.c @@ -30,10 +30,6 @@ /* errno */ #include <errno.h> -#ifndef _MSC_VER -#include <resolv.h> /* __b64_ntop */ -#endif - #ifdef LIBVNCSERVER_HAVE_ENDIAN_H #include <endian.h> #elif LIBVNCSERVER_HAVE_SYS_ENDIAN_H @@ -45,24 +41,14 @@ #endif #include <string.h> +#if LIBVNCSERVER_UNISTD_H #include <unistd.h> +#endif #include "rfb/rfbconfig.h" #include "rfbssl.h" #include "rfbcrypto.h" - -#define WS_NTOH64(n) htobe64(n) -#define WS_NTOH32(n) htobe32(n) -#define WS_NTOH16(n) htobe16(n) -#define WS_HTON64(n) htobe64(n) -#define WS_HTON16(n) htobe16(n) - -#define B64LEN(__x) (((__x + 2) / 3) * 12 / 3) -#define WSHLENMAX 14 /* 2 + sizeof(uint64_t) + sizeof(uint32_t) */ - -enum { - WEBSOCKETS_VERSION_HIXIE, - WEBSOCKETS_VERSION_HYBI -}; +#include "ws_decode.h" +#include "base64.h" #if 0 #include <sys/syscall.h> @@ -71,77 +57,12 @@ static int gettid() { } #endif -typedef int (*wsEncodeFunc)(rfbClientPtr cl, const char *src, int len, char **dst); -typedef int (*wsDecodeFunc)(rfbClientPtr cl, char *dst, int len); - -typedef struct ws_ctx_s { - char codeBufDecode[B64LEN(UPDATE_BUF_SIZE) + WSHLENMAX]; /* base64 + maximum frame header length */ - char codeBufEncode[B64LEN(UPDATE_BUF_SIZE) + WSHLENMAX]; /* base64 + maximum frame header length */ - char readbuf[8192]; - int readbufstart; - int readbuflen; - int dblen; - char carryBuf[3]; /* For base64 carry-over */ - int carrylen; - int version; - int base64; - wsEncodeFunc encode; - wsDecodeFunc decode; -} ws_ctx_t; - -typedef union ws_mask_s { - char c[4]; - uint32_t u; -} ws_mask_t; - -/* XXX: The union and the structs do not need to be named. - * We are working around a bug present in GCC < 4.6 which prevented - * it from recognizing anonymous structs and unions. - * See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=4784 - */ -typedef struct __attribute__ ((__packed__)) ws_header_s { - unsigned char b0; - unsigned char b1; - union { - struct __attribute__ ((__packed__)) { - uint16_t l16; - ws_mask_t m16; - } s16; - struct __attribute__ ((__packed__)) { - uint64_t l64; - ws_mask_t m64; - } s64; - ws_mask_t m; - } u; -} ws_header_t; - -enum -{ - WS_OPCODE_CONTINUATION = 0x0, - WS_OPCODE_TEXT_FRAME, - WS_OPCODE_BINARY_FRAME, - WS_OPCODE_CLOSE = 0x8, - WS_OPCODE_PING, - WS_OPCODE_PONG -}; - -#define FLASH_POLICY_RESPONSE "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>\n" -#define SZ_FLASH_POLICY_RESPONSE 93 - /* * draft-ietf-hybi-thewebsocketprotocol-10 * 5.2.2. Sending the Server's Opening Handshake */ #define GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" -#define SERVER_HANDSHAKE_HIXIE "HTTP/1.1 101 Web Socket Protocol Handshake\r\n\ -Upgrade: WebSocket\r\n\ -Connection: Upgrade\r\n\ -%sWebSocket-Origin: %s\r\n\ -%sWebSocket-Location: %s://%s%s\r\n\ -%sWebSocket-Protocol: %s\r\n\ -\r\n%s" - #define SERVER_HANDSHAKE_HYBI "HTTP/1.1 101 Switching Protocols\r\n\ Upgrade: websocket\r\n\ Connection: Upgrade\r\n\ @@ -168,12 +89,11 @@ struct timeval #endif static rfbBool webSocketsHandshake(rfbClientPtr cl, char *scheme); -void webSocketsGenMd5(char * target, char *key1, char *key2, char *key3); static int webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst); -static int webSocketsEncodeHixie(rfbClientPtr cl, const char *src, int len, char **dst); -static int webSocketsDecodeHybi(rfbClientPtr cl, char *dst, int len); -static int webSocketsDecodeHixie(rfbClientPtr cl, char *dst, int len); + +static int ws_read(void *cl, char *buf, size_t len); + static int min (int a, int b) { @@ -190,8 +110,8 @@ static void webSocketsGenSha1Key(char *target, int size, char *key) iov[1].iov_base = GUID; iov[1].iov_len = sizeof(GUID) - 1; digestsha1(iov, 2, hash); - if (-1 == __b64_ntop(hash, sizeof(hash), target, size)) - rfbErr("b64_ntop failed\n"); + if (-1 == rfbBase64NtoP(hash, sizeof(hash), target, size)) + rfbErr("rfbBase64NtoP failed\n"); } /* @@ -214,13 +134,9 @@ webSocketsCheck (rfbClientPtr cl) return FALSE; } - if (strncmp(bbuf, "<", 1) == 0) { - rfbLog("Got Flash policy request, sending response\n"); - if (rfbWriteExact(cl, FLASH_POLICY_RESPONSE, - SZ_FLASH_POLICY_RESPONSE) < 0) { - rfbErr("webSocketsHandshake: failed sending Flash policy response"); - } - return FALSE; + if (strncmp(bbuf, "RFB ", 4) == 0) { + rfbLog("Normal socket connection\n"); + return TRUE; } else if (strncmp(bbuf, "\x16", 1) == 0 || strncmp(bbuf, "\x80", 1) == 0) { rfbLog("Got TLS/SSL WebSockets connection\n"); if (-1 == rfbssl_init(cl)) { @@ -278,10 +194,13 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) if ((n < 0) && (errno == ETIMEDOUT)) { break; } - if (n == 0) + if (n == 0) { rfbLog("webSocketsHandshake: client gone\n"); - else + } + else { rfbLogPerror("webSocketsHandshake: read"); + } + free(response); free(buf); return FALSE; @@ -336,24 +255,33 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) /* rfbLog("Got key2: %s\n", key2); */ /* HyBI */ - } else if ((strncasecmp("sec-websocket-protocol: ", line, min(llen,24))) == 0) { + } else if ((strncasecmp("sec-websocket-protocol: ", line, min(llen,24))) == 0) { protocol = line+24; buf[len-2] = '\0'; rfbLog("Got protocol: %s\n", protocol); } else if ((strncasecmp("sec-websocket-origin: ", line, min(llen,22))) == 0) { - sec_ws_origin = line+22; + sec_ws_origin = line+22; buf[len-2] = '\0'; } else if ((strncasecmp("sec-websocket-key: ", line, min(llen,19))) == 0) { - sec_ws_key = line+19; + sec_ws_key = line+19; buf[len-2] = '\0'; } else if ((strncasecmp("sec-websocket-version: ", line, min(llen,23))) == 0) { - sec_ws_version = strtol(line+23, NULL, 10); + sec_ws_version = strtol(line+23, NULL, 10); buf[len-2] = '\0'; - } + } linestart = len; } } + + /* older hixie handshake, this could be removed if + * a final standard is established -- removed now */ + if (!sec_ws_version) { + rfbErr("Hixie no longer supported\n"); + free(response); + free(buf); + return FALSE; + } if (!(path && host && (origin || sec_ws_origin))) { rfbErr("webSocketsHandshake: incomplete client handshake\n"); @@ -363,12 +291,6 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) } if ((protocol) && (strstr(protocol, "binary"))) { - if (! sec_ws_version) { - rfbErr("webSocketsHandshake: 'binary' protocol not supported with Hixie\n"); - free(response); - free(buf); - return FALSE; - } rfbLog(" - webSocketsHandshake: using binary/raw encoding\n"); base64 = FALSE; protocol = "binary"; @@ -386,32 +308,16 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) * Generate the WebSockets server response based on the the headers sent * by the client. */ + char accept[B64LEN(SHA1_HASH_SIZE) + 1]; + rfbLog(" - WebSockets client version hybi-%02d\n", sec_ws_version); + webSocketsGenSha1Key(accept, sizeof(accept), sec_ws_key); - if (sec_ws_version) { - char accept[B64LEN(SHA1_HASH_SIZE) + 1]; - rfbLog(" - WebSockets client version hybi-%02d\n", sec_ws_version); - webSocketsGenSha1Key(accept, sizeof(accept), sec_ws_key); - if(strlen(protocol) > 0) - len = snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN, - SERVER_HANDSHAKE_HYBI, accept, protocol); - else - len = snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN, - SERVER_HANDSHAKE_HYBI_NO_PROTOCOL, accept); + if(strlen(protocol) > 0) { + len = snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN, + SERVER_HANDSHAKE_HYBI, accept, protocol); } else { - /* older hixie handshake, this could be removed if - * a final standard is established */ - if (!(key1 && key2 && key3)) { - rfbLog(" - WebSockets client version hixie-75\n"); - prefix[0] = '\0'; - trailer[0] = '\0'; - } else { - rfbLog(" - WebSockets client version hixie-76\n"); - snprintf(prefix, 5, "Sec-"); - webSocketsGenMd5(trailer, key1, key2, key3); - } - len = snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN, - SERVER_HANDSHAKE_HIXIE, prefix, origin, prefix, scheme, - host, path, prefix, protocol, trailer); + len = snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN, + SERVER_HANDSHAKE_HYBI_NO_PROTOCOL, accept); } if (rfbWriteExact(cl, response, len) < 0) { @@ -424,357 +330,30 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) free(response); free(buf); - wsctx = calloc(1, sizeof(ws_ctx_t)); - if (sec_ws_version) { - wsctx->version = WEBSOCKETS_VERSION_HYBI; - wsctx->encode = webSocketsEncodeHybi; - wsctx->decode = webSocketsDecodeHybi; - } else { - wsctx->version = WEBSOCKETS_VERSION_HIXIE; - wsctx->encode = webSocketsEncodeHixie; - wsctx->decode = webSocketsDecodeHixie; - } + wsctx->encode = webSocketsEncodeHybi; + wsctx->decode = webSocketsDecodeHybi; + wsctx->ctxInfo.readFunc = ws_read; wsctx->base64 = base64; + hybiDecodeCleanupComplete(wsctx); cl->wsctx = (wsCtx *)wsctx; return TRUE; } - -void -webSocketsGenMd5(char * target, char *key1, char *key2, char *key3) -{ - unsigned int i, spaces1 = 0, spaces2 = 0; - unsigned long num1 = 0, num2 = 0; - unsigned char buf[17]; - struct iovec iov[1]; - - for (i=0; i < strlen(key1); i++) { - if (key1[i] == ' ') { - spaces1 += 1; - } - if ((key1[i] >= 48) && (key1[i] <= 57)) { - num1 = num1 * 10 + (key1[i] - 48); - } - } - num1 = num1 / spaces1; - - for (i=0; i < strlen(key2); i++) { - if (key2[i] == ' ') { - spaces2 += 1; - } - if ((key2[i] >= 48) && (key2[i] <= 57)) { - num2 = num2 * 10 + (key2[i] - 48); - } - } - num2 = num2 / spaces2; - - /* Pack it big-endian */ - buf[0] = (num1 & 0xff000000) >> 24; - buf[1] = (num1 & 0xff0000) >> 16; - buf[2] = (num1 & 0xff00) >> 8; - buf[3] = num1 & 0xff; - - buf[4] = (num2 & 0xff000000) >> 24; - buf[5] = (num2 & 0xff0000) >> 16; - buf[6] = (num2 & 0xff00) >> 8; - buf[7] = num2 & 0xff; - - strncpy((char *)buf+8, key3, 8); - buf[16] = '\0'; - - iov[0].iov_base = buf; - iov[0].iov_len = 16; - digestmd5(iov, 1, target); - target[16] = '\0'; - - return; -} static int -webSocketsEncodeHixie(rfbClientPtr cl, const char *src, int len, char **dst) -{ - int sz = 0; - ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; - - wsctx->codeBufEncode[sz++] = '\x00'; - len = __b64_ntop((unsigned char *)src, len, wsctx->codeBufEncode+sz, sizeof(wsctx->codeBufEncode) - (sz + 1)); - if (len < 0) { - return len; - } - sz += len; - - wsctx->codeBufEncode[sz++] = '\xff'; - *dst = wsctx->codeBufEncode; - return sz; -} - -static int -ws_read(rfbClientPtr cl, char *buf, int len) +ws_read(void *ctxPtr, char *buf, size_t len) { int n; + rfbClientPtr cl = ctxPtr; if (cl->sslctx) { - n = rfbssl_read(cl, buf, len); + n = rfbssl_read(cl, buf, len); } else { - n = read(cl->sock, buf, len); + n = read(cl->sock, buf, len); } return n; } static int -ws_peek(rfbClientPtr cl, char *buf, int len) -{ - int n; - if (cl->sslctx) { - n = rfbssl_peek(cl, buf, len); - } else { - while (-1 == (n = recv(cl->sock, buf, len, MSG_PEEK))) { - if (errno != EAGAIN) - break; - } - } - return n; -} - -static int -webSocketsDecodeHixie(rfbClientPtr cl, char *dst, int len) -{ - int retlen = 0, n, i, avail, modlen, needlen; - char *buf, *end = NULL; - ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; - - buf = wsctx->codeBufDecode; - - n = ws_peek(cl, buf, len*2+2); - - if (n <= 0) { - /* save errno because rfbErr() will tamper it */ - int olderrno = errno; - rfbErr("%s: peek (%d) %m\n", __func__, errno); - errno = olderrno; - return n; - } - - - /* Base64 encoded WebSockets stream */ - - if (buf[0] == '\xff') { - i = ws_read(cl, buf, 1); /* Consume marker */ - buf++; - n--; - } - if (n == 0) { - errno = EAGAIN; - return -1; - } - if (buf[0] == '\x00') { - i = ws_read(cl, buf, 1); /* Consume marker */ - buf++; - n--; - } - if (n == 0) { - errno = EAGAIN; - return -1; - } - - /* end = memchr(buf, '\xff', len*2+2); */ - end = memchr(buf, '\xff', n); - if (!end) { - end = buf + n; - } - avail = end - buf; - - len -= wsctx->carrylen; - - /* Determine how much base64 data we need */ - modlen = len + (len+2)/3; - needlen = modlen; - if (needlen % 4) { - needlen += 4 - (needlen % 4); - } - - if (needlen > avail) { - /* rfbLog("Waiting for more base64 data\n"); */ - errno = EAGAIN; - return -1; - } - - /* Any carryover from previous decode */ - for (i=0; i < wsctx->carrylen; i++) { - /* rfbLog("Adding carryover %d\n", wsctx->carryBuf[i]); */ - dst[i] = wsctx->carryBuf[i]; - retlen += 1; - } - - /* Decode the rest of what we need */ - buf[needlen] = '\x00'; /* Replace end marker with end of string */ - /* rfbLog("buf: %s\n", buf); */ - n = __b64_pton(buf, (unsigned char *)dst+retlen, 2+len); - if (n < len) { - rfbErr("Base64 decode error\n"); - errno = EIO; - return -1; - } - retlen += n; - - /* Consume the data from socket */ - i = ws_read(cl, buf, needlen); - - wsctx->carrylen = n - len; - retlen -= wsctx->carrylen; - for (i=0; i < wsctx->carrylen; i++) { - /* rfbLog("Saving carryover %d\n", dst[retlen + i]); */ - wsctx->carryBuf[i] = dst[retlen + i]; - } - - /* rfbLog("<< webSocketsDecode, retlen: %d\n", retlen); */ - return retlen; -} - -static int -webSocketsDecodeHybi(rfbClientPtr cl, char *dst, int len) -{ - char *buf, *payload; - uint32_t *payload32; - int ret = -1, result = -1; - int total = 0; - ws_mask_t mask; - ws_header_t *header; - int i; - unsigned char opcode; - ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; - int flength, fhlen; - /* int fin; */ /* not used atm */ - - /* rfbLog(" <== %s[%d]: %d cl: %p, wsctx: %p-%p (%d)\n", __func__, gettid(), len, cl, wsctx, (char *)wsctx + sizeof(ws_ctx_t), sizeof(ws_ctx_t)); */ - - if (wsctx->readbuflen) { - /* simply return what we have */ - if (wsctx->readbuflen > len) { - memcpy(dst, wsctx->readbuf + wsctx->readbufstart, len); - result = len; - wsctx->readbuflen -= len; - wsctx->readbufstart += len; - } else { - memcpy(dst, wsctx->readbuf + wsctx->readbufstart, wsctx->readbuflen); - result = wsctx->readbuflen; - wsctx->readbuflen = 0; - wsctx->readbufstart = 0; - } - goto spor; - } - - buf = wsctx->codeBufDecode; - header = (ws_header_t *)wsctx->codeBufDecode; - - ret = ws_peek(cl, buf, B64LEN(len) + WSHLENMAX); - - if (ret < 2) { - /* save errno because rfbErr() will tamper it */ - if (-1 == ret) { - int olderrno = errno; - rfbErr("%s: peek; %m\n", __func__); - errno = olderrno; - } else if (0 == ret) { - result = 0; - } else { - errno = EAGAIN; - } - goto spor; - } - - opcode = header->b0 & 0x0f; - /* fin = (header->b0 & 0x80) >> 7; */ /* not used atm */ - flength = header->b1 & 0x7f; - - /* - * 4.3. Client-to-Server Masking - * - * The client MUST mask all frames sent to the server. A server MUST - * close the connection upon receiving a frame with the MASK bit set to 0. - **/ - if (!(header->b1 & 0x80)) { - rfbErr("%s: got frame without mask\n", __func__, ret); - errno = EIO; - goto spor; - } - - if (flength < 126) { - fhlen = 2; - mask = header->u.m; - } else if (flength == 126 && 4 <= ret) { - flength = WS_NTOH16(header->u.s16.l16); - fhlen = 4; - mask = header->u.s16.m16; - } else if (flength == 127 && 10 <= ret) { - flength = WS_NTOH64(header->u.s64.l64); - fhlen = 10; - mask = header->u.s64.m64; - } else { - /* Incomplete frame header */ - rfbErr("%s: incomplete frame header\n", __func__, ret); - errno = EIO; - goto spor; - } - - /* absolute length of frame */ - total = fhlen + flength + 4; - payload = buf + fhlen + 4; /* header length + mask */ - - if (-1 == (ret = ws_read(cl, buf, total))) { - int olderrno = errno; - rfbErr("%s: read; %m", __func__); - errno = olderrno; - return ret; - } else if (ret < total) { - /* GT TODO: hmm? */ - rfbLog("%s: read; got partial data\n", __func__); - } else { - buf[ret] = '\0'; - } - - /* process 1 frame (32 bit op) */ - payload32 = (uint32_t *)payload; - for (i = 0; i < flength / 4; i++) { - payload32[i] ^= mask.u; - } - /* process the remaining bytes (if any) */ - for (i*=4; i < flength; i++) { - payload[i] ^= mask.c[i % 4]; - } - - switch (opcode) { - case WS_OPCODE_CLOSE: - rfbLog("got closure, reason %d\n", WS_NTOH16(((uint16_t *)payload)[0])); - errno = ECONNRESET; - break; - case WS_OPCODE_TEXT_FRAME: - if (-1 == (flength = __b64_pton(payload, (unsigned char *)wsctx->codeBufDecode, sizeof(wsctx->codeBufDecode)))) { - rfbErr("%s: Base64 decode error; %m\n", __func__); - break; - } - payload = wsctx->codeBufDecode; - /* fall through */ - case WS_OPCODE_BINARY_FRAME: - if (flength > len) { - memcpy(wsctx->readbuf, payload + len, flength - len); - wsctx->readbufstart = 0; - wsctx->readbuflen = flength - len; - flength = len; - } - memcpy(dst, payload, flength); - result = flength; - break; - default: - rfbErr("%s: unhandled opcode %d, b0: %02x, b1: %02x\n", __func__, (int)opcode, header->b0, header->b1); - } - - /* single point of return, if someone has questions :-) */ -spor: - /* rfbLog("%s: ret: %d/%d\n", __func__, result, len); */ - return result; -} - -static int webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst) { int blen, ret = -1, sz = 0; @@ -799,12 +378,12 @@ webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst) header = (ws_header_t *)wsctx->codeBufEncode; if (wsctx->base64) { - opcode = WS_OPCODE_TEXT_FRAME; - /* calculate the resulting size */ - blen = B64LEN(len); + opcode = WS_OPCODE_TEXT_FRAME; + /* calculate the resulting size */ + blen = B64LEN(len); } else { - opcode = WS_OPCODE_BINARY_FRAME; - blen = len; + opcode = WS_OPCODE_BINARY_FRAME; + blen = len; } header->b0 = 0x80 | (opcode & 0x0f); @@ -822,16 +401,16 @@ webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst) } if (wsctx->base64) { - if (-1 == (ret = __b64_ntop((unsigned char *)src, len, wsctx->codeBufEncode + sz, sizeof(wsctx->codeBufEncode) - sz))) { - rfbErr("%s: Base 64 encode failed\n", __func__); - } else { - if (ret != blen) - rfbErr("%s: Base 64 encode; something weird happened\n", __func__); - ret += sz; - } + if (-1 == (ret = rfbBase64NtoP((unsigned char *)src, len, wsctx->codeBufEncode + sz, sizeof(wsctx->codeBufEncode) - sz))) { + rfbErr("%s: Base 64 encode failed\n", __func__); + } else { + if (ret != blen) + rfbErr("%s: Base 64 encode; something weird happened\n", __func__); + ret += sz; + } } else { - memcpy(wsctx->codeBufEncode + sz, src, len); - ret = sz + len; + memcpy(wsctx->codeBufEncode + sz, src, len); + ret = sz + len; } *dst = wsctx->codeBufEncode; @@ -842,76 +421,28 @@ webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst) int webSocketsEncode(rfbClientPtr cl, const char *src, int len, char **dst) { - return ((ws_ctx_t *)cl->wsctx)->encode(cl, src, len, dst); + return webSocketsEncodeHybi(cl, src, len, dst); } int webSocketsDecode(rfbClientPtr cl, char *dst, int len) { - return ((ws_ctx_t *)cl->wsctx)->decode(cl, dst, len); + ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; + wsctx->ctxInfo.ctxPtr = cl; + return webSocketsDecodeHybi(wsctx, dst, len); } - -/* returns TRUE if client sent a close frame or a single 'end of frame' - * marker was received, FALSE otherwise - * - * Note: This is a Hixie-only hack! - **/ +/** + * This is a stub function that was once used for Hixie-encoding. + * We keep it for API compatibility. + */ rfbBool webSocketCheckDisconnect(rfbClientPtr cl) { - ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; - /* With Base64 encoding we need at least 4 bytes */ - char peekbuf[4]; - int n; - - if (wsctx->version == WEBSOCKETS_VERSION_HYBI) - return FALSE; - - if (cl->sslctx) - n = rfbssl_peek(cl, peekbuf, 4); - else - n = recv(cl->sock, peekbuf, 4, MSG_PEEK); - - if (n <= 0) { - if (n != 0) - rfbErr("%s: peek; %m", __func__); - rfbCloseClient(cl); - return TRUE; - } - - if (peekbuf[0] == '\xff') { - int doclose = 0; - /* Make sure we don't miss a client disconnect on an end frame - * marker. Because we use a peek buffer in some cases it is not - * applicable to wait for more data per select(). */ - switch (n) { - case 3: - if (peekbuf[1] == '\xff' && peekbuf[2] == '\x00') - doclose = 1; - break; - case 2: - if (peekbuf[1] == '\x00') - doclose = 1; - break; - default: - return FALSE; - } - - if (cl->sslctx) - n = rfbssl_read(cl, peekbuf, n); - else - n = read(cl->sock, peekbuf, n); - - if (doclose) { - rfbErr("%s: websocket close frame received\n", __func__); - rfbCloseClient(cl); - } - return TRUE; - } return FALSE; } + /* returns TRUE if there is data waiting to be read in our internal buffer * or if is there any pending data in the buffer of the SSL implementation */ @@ -920,8 +451,8 @@ webSocketsHasDataInBuffer(rfbClientPtr cl) { ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; - if (wsctx && wsctx->readbuflen) - return TRUE; + if (wsctx && wsctx->readlen) + return TRUE; return (cl->sslctx && rfbssl_pending(cl) > 0); } diff --git a/libvncserver/ws_decode.c b/libvncserver/ws_decode.c new file mode 100644 index 0000000..441ebc7 --- /dev/null +++ b/libvncserver/ws_decode.c @@ -0,0 +1,563 @@ +#include "ws_decode.h" +#include "base64.h" + +#include <string.h> +#include <errno.h> + +#define WS_HYBI_MASK_LEN 4 +#define WS_HYBI_HEADER_LEN_SHORT 2 + WS_HYBI_MASK_LEN +#define WS_HYBI_HEADER_LEN_EXTENDED 4 + WS_HYBI_MASK_LEN +#define WS_HYBI_HEADER_LEN_LONG 10 + WS_HYBI_MASK_LEN + +#undef WS_DECODE_DEBUG +/* set to 1 to produce very fine debugging output */ +#define WS_DECODE_DEBUG 0 + +#if WS_DECODE_DEBUG == 1 +#define ws_dbg(fmt, ...) rfbLog((fmt), ##__VA_ARGS) +#else +#define ws_dbg(fmt, ...) +#endif + + +static inline int +isControlFrame(ws_ctx_t *wsctx) +{ + return 0 != (wsctx->header.opcode & 0x08); +} + +static uint64_t +hybiRemaining(ws_ctx_t *wsctx) +{ + return wsctx->header.payloadLen - wsctx->nReadPayload; +} + +static void +hybiDecodeCleanupBasics(ws_ctx_t *wsctx) +{ + /* keep opcode, cleanup rest */ + wsctx->header.opcode = WS_OPCODE_INVALID; + wsctx->header.payloadLen = 0; + wsctx->header.mask.u = 0; + wsctx->header.headerLen = 0; + wsctx->header.data = NULL; + wsctx->header.nRead = 0; + wsctx->nReadPayload = 0; + wsctx->carrylen = 0; + wsctx->readPos = (unsigned char *)wsctx->codeBufDecode; + wsctx->readlen = 0; + wsctx->hybiDecodeState = WS_HYBI_STATE_HEADER_PENDING; + wsctx->writePos = NULL; +} + +static void +hybiDecodeCleanupForContinuation(ws_ctx_t *wsctx) +{ + hybiDecodeCleanupBasics(wsctx); + ws_dbg("clean up frame, but expect continuation with opcode %d\n", wsctx->continuation_opcode); +} + +void +hybiDecodeCleanupComplete(ws_ctx_t *wsctx) +{ + hybiDecodeCleanupBasics(wsctx); + wsctx->continuation_opcode = WS_OPCODE_INVALID; + ws_dbg("cleaned up wsctx completely\n"); +} + + +/** + * Return payload data that has been decoded/unmasked from + * a websocket frame. + * + * @param[out] dst destination buffer + * @param[in] len bytes to copy to destination buffer + * @param[in,out] wsctx internal state of decoding procedure + * @param[out] number of bytes actually written to dst buffer + * @return next hybi decoding state + */ +static int +hybiReturnData(char *dst, int len, ws_ctx_t *wsctx, int *nWritten) +{ + int nextState = WS_HYBI_STATE_ERR; + + /* if we have something already decoded copy and return */ + if (wsctx->readlen > 0) { + /* simply return what we have */ + if (wsctx->readlen > len) { + ws_dbg("copy to %d bytes to dst buffer; readPos=%p, readLen=%d\n", len, wsctx->readPos, wsctx->readlen); + memcpy(dst, wsctx->readPos, len); + *nWritten = len; + wsctx->readlen -= len; + wsctx->readPos += len; + nextState = WS_HYBI_STATE_DATA_AVAILABLE; + } else { + ws_dbg("copy to %d bytes to dst buffer; readPos=%p, readLen=%d\n", wsctx->readlen, wsctx->readPos, wsctx->readlen); + memcpy(dst, wsctx->readPos, wsctx->readlen); + *nWritten = wsctx->readlen; + wsctx->readlen = 0; + wsctx->readPos = NULL; + if (hybiRemaining(wsctx) == 0) { + nextState = WS_HYBI_STATE_FRAME_COMPLETE; + } else { + nextState = WS_HYBI_STATE_DATA_NEEDED; + } + } + ws_dbg("after copy: readPos=%p, readLen=%d\n", wsctx->readPos, wsctx->readlen); + } else { + /* it may happen that we read some bytes but could not decode them, + * in that case, set errno to EAGAIN and return -1 */ + nextState = wsctx->hybiDecodeState; + errno = EAGAIN; + *nWritten = -1; + } + return nextState; +} + +/** + * Read an RFC 6455 websocket frame (IETF hybi working group). + * + * Internal state is updated according to bytes received and the + * decoding of header information. + * + * @param[in] cl client ptr with ptr to raw socket and ws_ctx_t ptr + * @param[out] sockRet emulated recv return value + * @param[out] nPayload number of payload bytes already read + * @return next hybi decoding state; WS_HYBI_STATE_HEADER_PENDING indicates + * that the header was not received completely. + */ +static int +hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) +{ + int ret; + char *headerDst = wsctx->codeBufDecode + wsctx->header.nRead; + int n = ((uint64_t)WSHLENMAX) - wsctx->header.nRead; + + + ws_dbg("header_read to %p with len=%d\n", headerDst, n); + ret = wsctx->ctxInfo.readFunc(wsctx->ctxInfo.ctxPtr, headerDst, n); + ws_dbg("read %d bytes from socket\n", ret); + if (ret <= 0) { + if (-1 == ret) { + /* save errno because rfbErr() will tamper it */ + int olderrno = errno; + rfbErr("%s: read; %s\n", __func__, strerror(errno)); + errno = olderrno; + goto err_cleanup_state; + } else { + *sockRet = 0; + goto err_cleanup_state_sock_closed; + } + } + + wsctx->header.nRead += ret; + if (wsctx->header.nRead < 2) { + /* cannot decode header with less than two bytes */ + goto ret_header_pending; + } + + /* first two header bytes received; interpret header data and get rest */ + wsctx->header.data = (ws_header_t *)wsctx->codeBufDecode; + + wsctx->header.opcode = wsctx->header.data->b0 & 0x0f; + wsctx->header.fin = (wsctx->header.data->b0 & 0x80) >> 7; + if (isControlFrame(wsctx)) { + ws_dbg("is control frame\n"); + /* is a control frame, leave remembered continuation opcode unchanged; + * just check if there is a wrong fragmentation */ + if (wsctx->header.fin == 0) { + + /* we only accept text/binary continuation frames; RFC6455: + * Control frames (see Section 5.5) MAY be injected in the middle of + * a fragmented message. Control frames themselves MUST NOT be + * fragmented. */ + rfbErr("control frame with FIN bit cleared received, aborting\n"); + errno = EPROTO; + goto err_cleanup_state; + } + } else { + ws_dbg("not a control frame\n"); + /* not a control frame, check for continuation opcode */ + if (wsctx->header.opcode == WS_OPCODE_CONTINUATION) { + ws_dbg("cont_frame\n"); + /* do we have state (i.e., opcode) for continuation frame? */ + if (wsctx->continuation_opcode == WS_OPCODE_INVALID) { + rfbErr("no continuation state\n"); + errno = EPROTO; + goto err_cleanup_state; + } + + /* otherwise, set opcode = continuation_opcode */ + wsctx->header.opcode = wsctx->continuation_opcode; + ws_dbg("set opcode to continuation_opcode: %d\n", wsctx->header.opcode); + } else { + if (wsctx->header.fin == 0) { + wsctx->continuation_opcode = wsctx->header.opcode; + } else { + wsctx->continuation_opcode = WS_OPCODE_INVALID; + } + ws_dbg("set continuation_opcode to %d\n", wsctx->continuation_opcode); + } + } + + wsctx->header.payloadLen = (uint64_t)(wsctx->header.data->b1 & 0x7f); + ws_dbg("first header bytes received; opcode=%d lenbyte=%d fin=%d\n", wsctx->header.opcode, wsctx->header.payloadLen, wsctx->header.fin); + + /* + * 4.3. Client-to-Server Masking + * + * The client MUST mask all frames sent to the server. A server MUST + * close the connection upon receiving a frame with the MASK bit set to 0. + **/ + if (!(wsctx->header.data->b1 & 0x80)) { + rfbErr("%s: got frame without mask; ret=%d\n", __func__, ret); + errno = EPROTO; + goto err_cleanup_state; + } + + + if (wsctx->header.payloadLen < 126 && wsctx->header.nRead >= 6) { + wsctx->header.headerLen = WS_HYBI_HEADER_LEN_SHORT; + wsctx->header.mask = wsctx->header.data->u.m; + } else if (wsctx->header.payloadLen == 126 && 8 <= wsctx->header.nRead) { + wsctx->header.headerLen = WS_HYBI_HEADER_LEN_EXTENDED; + wsctx->header.payloadLen = WS_NTOH16(wsctx->header.data->u.s16.l16); + wsctx->header.mask = wsctx->header.data->u.s16.m16; + } else if (wsctx->header.payloadLen == 127 && 14 <= wsctx->header.nRead) { + wsctx->header.headerLen = WS_HYBI_HEADER_LEN_LONG; + wsctx->header.payloadLen = WS_NTOH64(wsctx->header.data->u.s64.l64); + wsctx->header.mask = wsctx->header.data->u.s64.m64; + } else { + /* Incomplete frame header, try again */ + rfbErr("%s: incomplete frame header; ret=%d\n", __func__, ret); + goto ret_header_pending; + } + + char *h = wsctx->codeBufDecode; + int i; + ws_dbg("Header:\n"); + for (i=0; i <10; i++) { + ws_dbg("0x%02X\n", (unsigned char)h[i]); + } + ws_dbg("\n"); + + /* while RFC 6455 mandates that lengths MUST be encoded with the minimum + * number of bytes, it does not specify for the server how to react on + * 'wrongly' encoded frames --- this implementation rejects them*/ + if ((wsctx->header.headerLen > WS_HYBI_HEADER_LEN_SHORT + && wsctx->header.payloadLen < (uint64_t)126) + || (wsctx->header.headerLen > WS_HYBI_HEADER_LEN_EXTENDED + && wsctx->header.payloadLen < (uint64_t)65536)) { + rfbErr("%s: invalid length field; headerLen=%d payloadLen=%llu\n", __func__, wsctx->header.headerLen, wsctx->header.payloadLen); + errno = EPROTO; + goto err_cleanup_state; + } + + /* update write position for next bytes */ + wsctx->writePos = wsctx->codeBufDecode + wsctx->header.nRead; + + /* set payload pointer just after header */ + wsctx->readPos = (unsigned char *)(wsctx->codeBufDecode + wsctx->header.headerLen); + + *nPayload = wsctx->header.nRead - wsctx->header.headerLen; + wsctx->nReadPayload = *nPayload; + + ws_dbg("header complete: state=%d headerlen=%d payloadlen=%llu writeTo=%p nPayload=%d\n", wsctx->hybiDecodeState, wsctx->header.headerLen, wsctx->header.payloadLen, wsctx->writePos, *nPayload); + + return WS_HYBI_STATE_DATA_NEEDED; + +ret_header_pending: + errno = EAGAIN; + *sockRet = -1; + return WS_HYBI_STATE_HEADER_PENDING; + +err_cleanup_state: + *sockRet = -1; +err_cleanup_state_sock_closed: + hybiDecodeCleanupComplete(wsctx); + return WS_HYBI_STATE_ERR; +} + +static int +hybiWsFrameComplete(ws_ctx_t *wsctx) +{ + return wsctx != NULL && hybiRemaining(wsctx) == 0; +} + +static char * +hybiPayloadStart(ws_ctx_t *wsctx) +{ + return wsctx->codeBufDecode + wsctx->header.headerLen; +} + + +/** + * Read the remaining payload bytes from associated raw socket. + * + * - try to read remaining bytes from socket + * - unmask all multiples of 4 + * - if frame incomplete but some bytes are left, these are copied to + * the carry buffer + * - if opcode is TEXT: Base64-decode all unmasked received bytes + * - set state for reading decoded data + * - reset write position to begin of buffer (+ header) + * --> before we retrieve more data we let the caller clear all bytes + * from the reception buffer + * - execute return data routine + * + * Sets errno corresponding to what it gets from the underlying + * socket or EPROTO if some invalid data is in the received frame + * or ECONNRESET if a close reason + message is received. EIO is used if + * an internal sanity check fails. + * + * @param[in] cl client ptr with raw socket reference + * @param[out] dst destination buffer + * @param[in] len size of destination buffer + * @param[out] sockRet emulated recv return value + * @param[in] nInBuf number of undecoded bytes before writePos from header read + * @return next hybi decode state + */ +static int +hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet, int nInBuf) +{ + int n; + int i; + int toReturn; /* number of data bytes to return */ + int toDecode; /* number of bytes to decode starting at wsctx->writePos */ + int bufsize; + int nextRead; + unsigned char *data; + uint32_t *data32; + + /* if data was carried over, copy to start of buffer */ + memcpy(wsctx->writePos, wsctx->carryBuf, wsctx->carrylen); + wsctx->writePos += wsctx->carrylen; + + /* -1 accounts for potential '\0' terminator for base64 decoding */ + bufsize = wsctx->codeBufDecode + ARRAYSIZE(wsctx->codeBufDecode) - wsctx->writePos - 1; + ws_dbg("bufsize=%d\n", bufsize); + if (hybiRemaining(wsctx) > bufsize) { + nextRead = bufsize; + } else { + nextRead = hybiRemaining(wsctx); + } + + ws_dbg("calling read with buf=%p and len=%d (decodebuf=%p headerLen=%d)\n", wsctx->writePos, nextRead, wsctx->codeBufDecode, wsctx->header.headerLen); + + if (nextRead > 0) { + /* decode more data */ + if (-1 == (n = wsctx->ctxInfo.readFunc(wsctx->ctxInfo.ctxPtr, wsctx->writePos, nextRead))) { + int olderrno = errno; + rfbErr("%s: read; %s", __func__, strerror(errno)); + errno = olderrno; + *sockRet = -1; + return WS_HYBI_STATE_ERR; + } else if (n == 0) { + *sockRet = 0; + return WS_HYBI_STATE_ERR; + } else { + ws_dbg("read %d bytes from socket; nRead=%d\n", n, wsctx->nReadPayload); + } + } else { + n = 0; + } + + wsctx->nReadPayload += n; + wsctx->writePos += n; + + if (hybiRemaining(wsctx) == 0) { + wsctx->hybiDecodeState = WS_HYBI_STATE_FRAME_COMPLETE; + } + + /* number of not yet unmasked payload bytes: what we read here + what was + * carried over + what was read with the header */ + toDecode = n + wsctx->carrylen + nInBuf; + ws_dbg("toDecode=%d from n=%d carrylen=%d headerLen=%d\n", toDecode, n, wsctx->carrylen, wsctx->header.headerLen); + if (toDecode < 0) { + rfbErr("%s: internal error; negative number of bytes to decode: %d", __func__, toDecode); + errno=EIO; + *sockRet = -1; + return WS_HYBI_STATE_ERR; + } + + /* for a possible base64 decoding, we decode multiples of 4 bytes until + * the whole frame is received and carry over any remaining bytes in the carry buf*/ + data = (unsigned char *)(wsctx->writePos - toDecode); + data32= (uint32_t *)data; + + for (i = 0; i < (toDecode >> 2); i++) { + data32[i] ^= wsctx->header.mask.u; + } + ws_dbg("mask decoding; i=%d toDecode=%d\n", i, toDecode); + + if (wsctx->hybiDecodeState == WS_HYBI_STATE_FRAME_COMPLETE) { + /* process the remaining bytes (if any) */ + for (i*=4; i < toDecode; i++) { + data[i] ^= wsctx->header.mask.c[i % 4]; + } + + /* all data is here, no carrying */ + wsctx->carrylen = 0; + } else { + /* carry over remaining, non-multiple-of-four bytes */ + wsctx->carrylen = toDecode - (i * 4); + if (wsctx->carrylen < 0 || wsctx->carrylen > ARRAYSIZE(wsctx->carryBuf)) { + rfbErr("%s: internal error, invalid carry over size: carrylen=%d, toDecode=%d, i=%d", __func__, wsctx->carrylen, toDecode, i); + *sockRet = -1; + errno = EIO; + return WS_HYBI_STATE_ERR; + } + ws_dbg("carrying over %d bytes from %p to %p\n", wsctx->carrylen, wsctx->writePos + (i * 4), wsctx->carryBuf); + memcpy(wsctx->carryBuf, data + (i * 4), wsctx->carrylen); + wsctx->writePos -= wsctx->carrylen; + } + + toReturn = toDecode - wsctx->carrylen; + + switch (wsctx->header.opcode) { + case WS_OPCODE_CLOSE: + /* this data is not returned as payload data */ + if (hybiWsFrameComplete(wsctx)) { + *(wsctx->writePos) = '\0'; + ws_dbg("got close cmd %d, reason %d: %s\n", (int)(wsctx->writePos - hybiPayloadStart(wsctx)), WS_NTOH16(((uint16_t *)hybiPayloadStart(wsctx))[0]), &hybiPayloadStart(wsctx)[2]); + errno = ECONNRESET; + *sockRet = -1; + return WS_HYBI_STATE_FRAME_COMPLETE; + } else { + ws_dbg("got close cmd; waiting for %d more bytes to arrive\n", hybiRemaining(wsctx)); + *sockRet = -1; + errno = EAGAIN; + return WS_HYBI_STATE_CLOSE_REASON_PENDING; + } + break; + case WS_OPCODE_TEXT_FRAME: + data[toReturn] = '\0'; + ws_dbg("Initiate Base64 decoding in %p with max size %d and '\\0' at %p\n", data, bufsize, data + toReturn); + if (-1 == (wsctx->readlen = rfbBase64PtoN((char *)data, data, bufsize))) { + rfbErr("%s: Base64 decode error; %s\n", __func__, strerror(errno)); + } + wsctx->writePos = hybiPayloadStart(wsctx); + break; + case WS_OPCODE_BINARY_FRAME: + wsctx->readlen = toReturn; + wsctx->writePos = hybiPayloadStart(wsctx); + ws_dbg("set readlen=%d writePos=%p\n", wsctx->readlen, wsctx->writePos); + break; + default: + rfbErr("%s: unhandled opcode %d, b0: %02x, b1: %02x\n", __func__, (int)wsctx->header.opcode, wsctx->header.data->b0, wsctx->header.data->b1); + } + wsctx->readPos = data; + + return hybiReturnData(dst, len, wsctx, sockRet); +} + +/** + * Read function for websocket-socket emulation. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-------+-+-------------+-------------------------------+ + * |F|R|R|R| opcode|M| Payload len | Extended payload length | + * |I|S|S|S| (4) |A| (7) | (16/64) | + * |N|V|V|V| |S| | (if payload len==126/127) | + * | |1|2|3| |K| | | + * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + * | Extended payload length continued, if payload len == 127 | + * + - - - - - - - - - - - - - - - +-------------------------------+ + * | |Masking-key, if MASK set to 1 | + * +-------------------------------+-------------------------------+ + * | Masking-key (continued) | Payload Data | + * +-------------------------------- - - - - - - - - - - - - - - - + + * : Payload Data continued ... : + * + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + * | Payload Data continued ... | + * +---------------------------------------------------------------+ + * + * Using the decode buffer, this function: + * - reads the complete header from the underlying socket + * - reads any remaining data bytes + * - unmasks the payload data using the provided mask + * - decodes Base64 encoded text data + * - copies len bytes of decoded payload data into dst + * + * Emulates a read call on a socket. + */ +int +webSocketsDecodeHybi(ws_ctx_t *wsctx, char *dst, int len) +{ + int result = -1; + /* int fin; */ /* not used atm */ + + ws_dbg("%s_enter: len=%d; " + "CTX: readlen=%d readPos=%p " + "writeTo=%p " + "state=%d payloadtoRead=%d payloadRemaining=%llu " + " nReadPayload=%d carrylen=%d carryBuf=%p\n", + __func__, len, + wsctx->readlen, wsctx->readPos, + wsctx->writePos, + wsctx->hybiDecodeState, wsctx->header.payloadLen, hybiRemaining(wsctx), + wsctx->nReadPayload, wsctx->carrylen, wsctx->carryBuf); + + switch (wsctx->hybiDecodeState){ + int nInBuf; + case WS_HYBI_STATE_HEADER_PENDING: + wsctx->hybiDecodeState = hybiReadHeader(wsctx, &result, &nInBuf); + if (wsctx->hybiDecodeState == WS_HYBI_STATE_ERR) { + goto spor; + } + if (wsctx->hybiDecodeState != WS_HYBI_STATE_HEADER_PENDING) { + + /* when header is complete, try to read some more data */ + wsctx->hybiDecodeState = hybiReadAndDecode(wsctx, dst, len, &result, nInBuf); + } + break; + case WS_HYBI_STATE_DATA_AVAILABLE: + wsctx->hybiDecodeState = hybiReturnData(dst, len, wsctx, &result); + break; + case WS_HYBI_STATE_DATA_NEEDED: + wsctx->hybiDecodeState = hybiReadAndDecode(wsctx, dst, len, &result, 0); + break; + case WS_HYBI_STATE_CLOSE_REASON_PENDING: + wsctx->hybiDecodeState = hybiReadAndDecode(wsctx, dst, len, &result, 0); + break; + default: + /* invalid state */ + rfbErr("%s: called with invalid state %d\n", wsctx->hybiDecodeState); + result = -1; + errno = EIO; + wsctx->hybiDecodeState = WS_HYBI_STATE_ERR; + } + + /* single point of return, if someone has questions :-) */ +spor: + if (wsctx->hybiDecodeState == WS_HYBI_STATE_FRAME_COMPLETE) { + ws_dbg("frame received successfully, cleaning up: read=%d hlen=%d plen=%d\n", wsctx->header.nRead, wsctx->header.headerLen, wsctx->header.payloadLen); + if (wsctx->header.fin && !isControlFrame(wsctx)) { + /* frame finished, cleanup state */ + hybiDecodeCleanupComplete(wsctx); + } else { + /* always retain continuation opcode for unfinished data frames + * or control frames, which may interleave with data frames */ + hybiDecodeCleanupForContinuation(wsctx); + } + } else if (wsctx->hybiDecodeState == WS_HYBI_STATE_ERR) { + hybiDecodeCleanupComplete(wsctx); + } + + ws_dbg("%s_exit: len=%d; " + "CTX: readlen=%d readPos=%p " + "writePos=%p " + "state=%d payloadtoRead=%d payloadRemaining=%d " + "nRead=%d carrylen=%d carryBuf=%p " + "result=%d " + "errno=%d\n", + __func__, len, + wsctx->readlen, wsctx->readPos, + wsctx->writePos, + wsctx->hybiDecodeState, wsctx->header.payloadLen, hybiRemaining(wsctx), + wsctx->nReadPayload, wsctx->carrylen, wsctx->carryBuf, + result, + errno); + return result; +} diff --git a/libvncserver/ws_decode.h b/libvncserver/ws_decode.h new file mode 100644 index 0000000..eb774a4 --- /dev/null +++ b/libvncserver/ws_decode.h @@ -0,0 +1,145 @@ +#ifndef _WS_DECODE_H_ +#define _WS_DECODE_H_ + +#include <stdint.h> +#include <rfb/rfb.h> + +#if defined(__APPLE__) + +#include <libkern/OSByteOrder.h> +#define WS_NTOH64(n) OSSwapBigToHostInt64(n) +#define WS_NTOH32(n) OSSwapBigToHostInt32(n) +#define WS_NTOH16(n) OSSwapBigToHostInt16(n) +#define WS_HTON64(n) OSSwapHostToBigInt64(n) +#define WS_HTON16(n) OSSwapHostToBigInt16(n) + +#else + +#define WS_NTOH64(n) htobe64(n) +#define WS_NTOH32(n) htobe32(n) +#define WS_NTOH16(n) htobe16(n) +#define WS_HTON64(n) htobe64(n) +#define WS_HTON16(n) htobe16(n) + +#endif + +#define B64LEN(__x) (((__x + 2) / 3) * 12 / 3) +#define WSHLENMAX 14LL /* 2 + sizeof(uint64_t) + sizeof(uint32_t) */ +#define WS_HYBI_MASK_LEN 4 + +#define ARRAYSIZE(a) ((sizeof(a) / sizeof((a[0]))) / (size_t)(!(sizeof(a) % sizeof((a[0]))))) + +struct ws_ctx_s; +typedef struct ws_ctx_s ws_ctx_t; + +typedef int (*wsEncodeFunc)(rfbClientPtr cl, const char *src, int len, char **dst); +typedef int (*wsDecodeFunc)(ws_ctx_t *wsctx, char *dst, int len); + +typedef int (*wsReadFunc)(void *ctx, char *dst, size_t len); + +typedef struct ctxInfo_s{ + void *ctxPtr; + wsReadFunc readFunc; +} ctxInfo_t; + +enum { + /* header not yet received completely */ + WS_HYBI_STATE_HEADER_PENDING, + /* data available */ + WS_HYBI_STATE_DATA_AVAILABLE, + WS_HYBI_STATE_DATA_NEEDED, + /* received a complete frame */ + WS_HYBI_STATE_FRAME_COMPLETE, + /* received part of a 'close' frame */ + WS_HYBI_STATE_CLOSE_REASON_PENDING, + /* */ + WS_HYBI_STATE_ERR +}; + +typedef union ws_mask_s { + char c[4]; + uint32_t u; +} ws_mask_t; + +/* XXX: The union and the structs do not need to be named. + * We are working around a bug present in GCC < 4.6 which prevented + * it from recognizing anonymous structs and unions. + * See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=4784 + */ +typedef struct +#if __GNUC__ +__attribute__ ((__packed__)) +#endif +ws_header_s { + unsigned char b0; + unsigned char b1; + union { + struct +#if __GNUC__ + __attribute__ ((__packed__)) +#endif + { + uint16_t l16; + ws_mask_t m16; + } s16; + struct +#if __GNUC__ +__attribute__ ((__packed__)) +#endif + { + uint64_t l64; + ws_mask_t m64; + } s64; + ws_mask_t m; + } u; +} ws_header_t; + +typedef struct ws_header_data_s { + ws_header_t *data; + /** bytes read */ + int nRead; + /** mask value */ + ws_mask_t mask; + /** length of frame header including payload len, but without mask */ + int headerLen; + /** length of the payload data */ + uint64_t payloadLen; + /** opcode */ + unsigned char opcode; + /** fin bit */ + unsigned char fin; +} ws_header_data_t; + +struct ws_ctx_s { + char codeBufDecode[2048 + WSHLENMAX]; /* base64 + maximum frame header length */ + char codeBufEncode[B64LEN(UPDATE_BUF_SIZE) + WSHLENMAX]; /* base64 + maximum frame header length */ + char *writePos; + unsigned char *readPos; + int readlen; + int hybiDecodeState; + char carryBuf[3]; /* For base64 carry-over */ + int carrylen; + int base64; + ws_header_data_t header; + uint64_t nReadPayload; + unsigned char continuation_opcode; + wsEncodeFunc encode; + wsDecodeFunc decode; + ctxInfo_t ctxInfo; +}; + +enum +{ + WS_OPCODE_CONTINUATION = 0x00, + WS_OPCODE_TEXT_FRAME = 0x01, + WS_OPCODE_BINARY_FRAME = 0x02, + WS_OPCODE_CLOSE = 0x08, + WS_OPCODE_PING = 0x09, + WS_OPCODE_PONG = 0x0A, + WS_OPCODE_INVALID = 0xFF +}; + +int webSocketsDecodeHybi(ws_ctx_t *wsctx, char *dst, int len); + +void hybiDecodeCleanupComplete(ws_ctx_t *wsctx); +#endif diff --git a/libvncserver/zlib.c b/libvncserver/zlib.c index 45a1314..6fee4df 100644 --- a/libvncserver/zlib.c +++ b/libvncserver/zlib.c @@ -45,10 +45,11 @@ * tight. N.B. ZRLE does it the traditional way with per-client storage * (and so at least ZRLE will work threaded on older systems.) */ -#if LIBVNCSERVER_HAVE_LIBPTHREAD && LIBVNCSERVER_HAVE_TLS && !defined(TLS) && defined(__linux__) +#if defined(__GNUC__) #define TLS __thread -#endif -#ifndef TLS +#elif defined(_MSC_VER) +#define TLS __declspec(thread) +#else #define TLS #endif |