diff options
Diffstat (limited to 'libvncserver')
-rw-r--r-- | libvncserver/Makefile.am | 80 | ||||
-rw-r--r-- | libvncserver/font.c | 1 | ||||
-rw-r--r-- | libvncserver/main.c | 19 | ||||
-rw-r--r-- | libvncserver/rfbserver.c | 5 | ||||
-rw-r--r-- | libvncserver/scale.c | 13 | ||||
-rw-r--r-- | libvncserver/websockets.c | 612 | ||||
-rw-r--r-- | libvncserver/ws_decode.c | 562 | ||||
-rw-r--r-- | libvncserver/ws_decode.h | 148 |
8 files changed, 801 insertions, 639 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/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/main.c b/libvncserver/main.c index dbda77d..95c3da5 100644 --- a/libvncserver/main.c +++ b/libvncserver/main.c @@ -261,7 +261,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) @@ -1052,7 +1061,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 0; + } + WSAinitted=TRUE; + } #endif rfbInitSockets(screen); rfbHttpInitSockets(screen); diff --git a/libvncserver/rfbserver.c b/libvncserver/rfbserver.c index 040238d..116c488 100644 --- a/libvncserver/rfbserver.c +++ b/libvncserver/rfbserver.c @@ -1999,11 +1999,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"); diff --git a/libvncserver/scale.c b/libvncserver/scale.c index 3ca76dc..2325dc3 100644 --- a/libvncserver/scale.c +++ b/libvncserver/scale.c @@ -66,7 +66,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/websockets.c b/libvncserver/websockets.c index 0b2d46f..b9947c4 100644 --- a/libvncserver/websockets.c +++ b/libvncserver/websockets.c @@ -34,10 +34,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 @@ -55,33 +51,8 @@ #include "rfb/rfbconfig.h" #include "rfbssl.h" #include "rfbcrypto.h" +#include "ws_decode.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 14 /* 2 + sizeof(uint64_t) + sizeof(uint32_t) */ - -enum { - WEBSOCKETS_VERSION_HIXIE, - WEBSOCKETS_VERSION_HYBI -}; #if 0 #include <sys/syscall.h> @@ -90,72 +61,6 @@ 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 -#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; - -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 @@ -165,14 +70,6 @@ enum */ #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\ @@ -199,12 +96,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) { @@ -316,8 +212,8 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) rfbLog("webSocketsHandshake: client gone\n"); else rfbLogPerror("webSocketsHandshake: read"); - free(response); - free(buf); + free(response); + free(buf); return FALSE; } @@ -370,24 +266,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"); @@ -397,12 +302,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"; @@ -420,32 +319,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) { @@ -458,357 +341,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) -{ - int n; - if (cl->sslctx) { - n = rfbssl_read(cl, buf, len); - } else { - n = read(cl->sock, buf, len); - } - return n; -} static int -ws_peek(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_peek(cl, buf, len); + n = rfbssl_read(cl, buf, len); } else { - while (-1 == (n = recv(cl->sock, buf, len, MSG_PEEK))) { - if (errno != EAGAIN) - break; - } + n = read(cl->sock, buf, len); } 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; @@ -833,12 +389,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); @@ -857,15 +413,15 @@ 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; - } + 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; @@ -876,76 +432,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 */ @@ -954,8 +462,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..513fd4b --- /dev/null +++ b/libvncserver/ws_decode.c @@ -0,0 +1,562 @@ +#include "ws_decode.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 = b64_pton((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..2923e3d --- /dev/null +++ b/libvncserver/ws_decode.h @@ -0,0 +1,148 @@ +#ifndef _WS_DECODE_H_ +#define _WS_DECODE_H_ + +#include <stdint.h> +#include <rfb/rfb.h> +#ifndef _MSC_VER +#include <resolv.h> /* __b64_ntop */ +#endif + +#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; + +typedef 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; +} ws_ctx_t; + +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 |