From 896ca2036c35b89a7f63e1adefe5e3724bf4d40d Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 19 Jul 2011 13:40:34 +0200 Subject: tightPng: Add initial tightPng encoding support. http://wiki.qemu.org/VNC_Tight_PNG Signed-off-by: Joel Martin Signed-off-by: Christian Beier --- rfb/rfb.h | 17 +++++++++++++++-- rfb/rfbconfig.h.cmake | 3 +++ rfb/rfbproto.h | 22 ++++++++++++++-------- 3 files changed, 32 insertions(+), 10 deletions(-) (limited to 'rfb') diff --git a/rfb/rfb.h b/rfb/rfb.h index c16336d..e79b68b 100644 --- a/rfb/rfb.h +++ b/rfb/rfb.h @@ -542,7 +542,9 @@ typedef struct _rfbClientRec { struct z_stream_s compStream; rfbBool compStreamInited; uint32_t zlibCompressLevel; - /** the quality level is also used by ZYWRLE */ +#endif +#if defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG) + /** the quality level is also used by ZYWRLE and TightPng */ int tightQualityLevel; #ifdef LIBVNCSERVER_HAVE_LIBJPEG @@ -550,6 +552,8 @@ typedef struct _rfbClientRec { z_stream zsStruct[4]; rfbBool zsActive[4]; int zsLevel[4]; +#endif +#if defined(LIBVNCSERVER_HAVE_LIBJPEG) || defined(LIBVNCSERVER_HAVE_LIBPNG) int tightCompressLevel; #endif #endif @@ -624,6 +628,9 @@ typedef struct _rfbClientRec { char *afterEncBuf; int afterEncBufSize; int afterEncBufLen; +#if defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG) + uint32_t tightEncoding; /* rfbEncodingTight or rfbEncodingTightPng */ +#endif } rfbClientRec, *rfbClientPtr; /** @@ -800,7 +807,7 @@ extern rfbBool rfbSendRectEncodingUltra(rfbClientPtr cl, int x,int y,int w,int h extern rfbBool rfbSendRectEncodingZlib(rfbClientPtr cl, int x, int y, int w, int h); -#ifdef LIBVNCSERVER_HAVE_LIBJPEG +#if defined(LIBVNCSERVER_HAVE_LIBJPEG) || defined(LIBVNCSERVER_HAVE_LIBPNG) /* tight.c */ #define TIGHT_DEFAULT_COMPRESSION 6 @@ -808,7 +815,13 @@ extern rfbBool rfbSendRectEncodingZlib(rfbClientPtr cl, int x, int y, int w, extern rfbBool rfbTightDisableGradient; extern int rfbNumCodedRectsTight(rfbClientPtr cl, int x,int y,int w,int h); + +#if defined(LIBVNCSERVER_HAVE_LIBJPEG) extern rfbBool rfbSendRectEncodingTight(rfbClientPtr cl, int x,int y,int w,int h); +#endif +#if defined(LIBVNCSERVER_HAVE_LIBPNG) +extern rfbBool rfbSendRectEncodingTightPng(rfbClientPtr cl, int x,int y,int w,int h); +#endif #endif #endif diff --git a/rfb/rfbconfig.h.cmake b/rfb/rfbconfig.h.cmake index de898fc..b7f225c 100644 --- a/rfb/rfbconfig.h.cmake +++ b/rfb/rfbconfig.h.cmake @@ -18,6 +18,9 @@ /* Define to 1 if you have the `jpeg' library (-ljpeg). */ #cmakedefine LIBVNCSERVER_HAVE_LIBJPEG 1 +/* Define if you have the `png' library (-lpng). */ +#cmakedefine LIBVNCSERVER_HAVE_LIBPNG 1 + /* Define to 1 if you have the `pthread' library (-lpthread). */ #cmakedefine LIBVNCSERVER_HAVE_LIBPTHREAD 1 diff --git a/rfb/rfbproto.h b/rfb/rfbproto.h index 73d200a..c6dfd2c 100644 --- a/rfb/rfbproto.h +++ b/rfb/rfbproto.h @@ -434,6 +434,7 @@ typedef struct { #define rfbEncodingHextile 5 #define rfbEncodingZlib 6 #define rfbEncodingTight 7 +#define rfbEncodingTightPng 0xFFFFFEFC /* -260 */ #define rfbEncodingZlibHex 8 #define rfbEncodingUltra 9 #define rfbEncodingZRLE 16 @@ -704,7 +705,10 @@ typedef struct { #ifdef LIBVNCSERVER_HAVE_LIBZ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * Tight Encoding. + * Tight and TightPng Encoding. + * + *-- TightPng is like Tight but basic compression is not used, instead PNG + * data is sent. * *-- The first byte of each Tight-encoded rectangle is a "compression control * byte". Its format is as follows (bit 0 is the least significant one): @@ -715,8 +719,9 @@ typedef struct { * bit 3: if 1, then compression stream 3 should be reset; * bits 7-4: if 1000 (0x08), then the compression type is "fill", * if 1001 (0x09), then the compression type is "jpeg", + * if 1001 (0x0A), then the compression type is "png", * if 0xxx, then the compression type is "basic", - * values greater than 1001 are not valid. + * values greater than 1010 are not valid. * * If the compression type is "basic", then bits 6..4 of the * compression control byte (those xxx in 0xxx) specify the following: @@ -726,17 +731,17 @@ typedef struct { * bit 6: if 1, then a "filter id" byte is following this byte. * *-- The data that follows after the compression control byte described - * above depends on the compression type ("fill", "jpeg" or "basic"). + * above depends on the compression type ("fill", "jpeg", "png" or "basic"). * *-- If the compression type is "fill", then the only pixel value follows, in * client pixel format (see NOTE 1). This value applies to all pixels of the * rectangle. * - *-- If the compression type is "jpeg", the following data stream looks like - * this: + *-- If the compression type is "jpeg" or "png", the following data stream + * looks like this: * * 1..3 bytes: data size (N) in compact representation; - * N bytes: JPEG image. + * N bytes: JPEG or PNG image. * * Data size is compactly represented in one, two or three bytes, according * to the following scheme: @@ -817,7 +822,7 @@ typedef struct { *-- NOTE 2. The decoder must reset compression streams' states before * decoding the rectangle, if some of bits 0,1,2,3 in the compression control * byte are set to 1. Note that the decoder must reset zlib streams even if - * the compression type is "fill" or "jpeg". + * the compression type is "fill", "jpeg" or "png". * *-- NOTE 3. The "gradient" filter and "jpeg" compression may be used only * when bits-per-pixel value is either 16 or 32, not 8. @@ -831,7 +836,8 @@ typedef struct { #define rfbTightExplicitFilter 0x04 #define rfbTightFill 0x08 #define rfbTightJpeg 0x09 -#define rfbTightMaxSubencoding 0x09 +#define rfbTightPng 0x0A +#define rfbTightMaxSubencoding 0x0A /* Filters to improve compression efficiency */ #define rfbTightFilterCopy 0x00 -- cgit v1.2.3 From 6fac22a74b5020387a6961e4cc197b5fa4743f96 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 16 Aug 2011 14:02:31 +0200 Subject: websockets: Initial WebSockets support. Has a bug: WebSocket client disconnects are not detected. rfbSendFramebufferUpdate is doing a MSG_PEEK recv to determine if enough data is available which prevents a disconnect from being detected. Otherwise it's working pretty well. [jes: moved added struct members to the end for binary compatibility with previous LibVNCServer versions, removed an unused variable] Signed-off-by: Johannes Schindelin --- configure.ac | 19 ++ libvncserver/Makefile.am | 6 +- libvncserver/md5.c | 448 ++++++++++++++++++++++++++++++++++++++++++++++ libvncserver/md5.h | 148 +++++++++++++++ libvncserver/rfbserver.c | 33 +++- libvncserver/sockets.c | 83 +++++++++ libvncserver/websockets.c | 448 ++++++++++++++++++++++++++++++++++++++++++++++ rfb/rfb.h | 22 +++ 8 files changed, 1205 insertions(+), 2 deletions(-) create mode 100644 libvncserver/md5.c create mode 100644 libvncserver/md5.h create mode 100755 libvncserver/websockets.c (limited to 'rfb') diff --git a/configure.ac b/configure.ac index 280ea58..029a600 100644 --- a/configure.ac +++ b/configure.ac @@ -25,6 +25,14 @@ AC_ARG_WITH(tightvnc-filetransfer, , [ with_tightvnc_filetransfer=yes ]) # AC_DEFINE moved to after libpthread check. +# WebSockets support +AC_CHECK_LIB(resolv, __b64_ntop, HAVE_B64="true", HAVE_B64="false") +AH_TEMPLATE(WITH_WEBSOCKETS, [Disable WebSockets support]) +AC_ARG_WITH(websockets, + [ --without-websockets disable WebSockets support], + , [ with_websockets=yes ]) +# AC_DEFINE moved to after libresolve check. + AH_TEMPLATE(ALLOW24BPP, [Enable 24 bit per pixel in native framebuffer]) AC_ARG_WITH(24bpp, [ --without-24bpp disable 24 bpp framebuffers], @@ -301,6 +309,7 @@ elif test "x$uname_s" = "xDarwin"; then fi + AH_TEMPLATE(HAVE_LIBCRYPT, [libcrypt library present]) AC_ARG_WITH(crypt, [ --without-crypt disable support for libcrypt],,) @@ -706,6 +715,16 @@ if test "x$with_tightvnc_filetransfer" = "xyes"; then fi AM_CONDITIONAL(WITH_TIGHTVNC_FILETRANSFER, test "$with_tightvnc_filetransfer" = "yes") +# websockets implemented using base64 from resolve +if test "x$HAVE_B64" != "xtrue"; then + with_websockets="" +fi +if test "x$with_websockets" = "xyes"; then + LIBS="$LIBS -lresolv" + AC_DEFINE(WITH_WEBSOCKETS) +fi +AM_CONDITIONAL(WITH_WEBSOCKETS, test "$with_websockets" = "yes") + AM_CONDITIONAL(HAVE_LIBZ, test ! -z "$HAVE_ZLIB_H") AM_CONDITIONAL(HAVE_LIBJPEG, test ! -z "$HAVE_JPEGLIB_H") AM_CONDITIONAL(HAVE_LIBPNG, test ! -z "$HAVE_PNGLIB_H") diff --git a/libvncserver/Makefile.am b/libvncserver/Makefile.am index a685ed1..0d64363 100644 --- a/libvncserver/Makefile.am +++ b/libvncserver/Makefile.am @@ -12,6 +12,10 @@ TIGHTVNCFILETRANSFERSRCS = tightvnc-filetransfer/rfbtightserver.c \ tightvnc-filetransfer/filelistinfo.c endif +if WITH_WEBSOCKETS +WEBSOCKETSSRCS = websockets.c md5.c +endif + includedir=$(prefix)/include/rfb #include_HEADERS=rfb.h rfbconfig.h rfbint.h rfbproto.h keysym.h rfbregion.h @@ -37,7 +41,7 @@ endif endif endif -LIB_SRCS = main.c rfbserver.c rfbregion.c auth.c sockets.c \ +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 \ diff --git a/libvncserver/md5.c b/libvncserver/md5.c new file mode 100644 index 0000000..a12c146 --- /dev/null +++ b/libvncserver/md5.c @@ -0,0 +1,448 @@ +/* Functions to compute MD5 message digest of files or memory blocks. + according to the definition of MD5 in RFC 1321 from April 1992. + Copyright (C) 1995,1996,1997,1999,2000,2001,2005 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +/* Written by Ulrich Drepper , 1995. */ + +#include + +# include +# include + +#include "md5.h" + +#ifdef _LIBC +# include +# if __BYTE_ORDER == __BIG_ENDIAN +# define WORDS_BIGENDIAN 1 +# endif +/* We need to keep the namespace clean so define the MD5 function + protected using leading __ . */ +# define md5_init_ctx __md5_init_ctx +# define md5_process_block __md5_process_block +# define md5_process_bytes __md5_process_bytes +# define md5_finish_ctx __md5_finish_ctx +# define md5_read_ctx __md5_read_ctx +# define md5_stream __md5_stream +# define md5_buffer __md5_buffer +#endif + +#ifdef WORDS_BIGENDIAN +# define SWAP(n) \ + (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) +#else +# define SWAP(n) (n) +#endif + + +/* This array contains the bytes used to pad the buffer to the next + 64-byte boundary. (RFC 1321, 3.1: Step 1) */ +static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ }; + + +/* Initialize structure containing state of computation. + (RFC 1321, 3.3: Step 3) */ +void +md5_init_ctx (ctx) + struct md5_ctx *ctx; +{ + ctx->A = 0x67452301; + ctx->B = 0xefcdab89; + ctx->C = 0x98badcfe; + ctx->D = 0x10325476; + + ctx->total[0] = ctx->total[1] = 0; + ctx->buflen = 0; +} + +/* Put result from CTX in first 16 bytes following RESBUF. The result + must be in little endian byte order. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ +void * +md5_read_ctx (ctx, resbuf) + const struct md5_ctx *ctx; + void *resbuf; +{ + ((md5_uint32 *) resbuf)[0] = SWAP (ctx->A); + ((md5_uint32 *) resbuf)[1] = SWAP (ctx->B); + ((md5_uint32 *) resbuf)[2] = SWAP (ctx->C); + ((md5_uint32 *) resbuf)[3] = SWAP (ctx->D); + + return resbuf; +} + +/* Process the remaining bytes in the internal buffer and the usual + prolog according to the standard and write the result to RESBUF. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ +void * +md5_finish_ctx (ctx, resbuf) + struct md5_ctx *ctx; + void *resbuf; +{ + /* Take yet unprocessed bytes into account. */ + md5_uint32 bytes = ctx->buflen; + size_t pad; + + /* Now count remaining bytes. */ + ctx->total[0] += bytes; + if (ctx->total[0] < bytes) + ++ctx->total[1]; + + pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes; + memcpy (&ctx->buffer[bytes], fillbuf, pad); + + /* Put the 64-bit file length in *bits* at the end of the buffer. */ + *(md5_uint32 *) &ctx->buffer[bytes + pad] = SWAP (ctx->total[0] << 3); + *(md5_uint32 *) &ctx->buffer[bytes + pad + 4] = SWAP ((ctx->total[1] << 3) | + (ctx->total[0] >> 29)); + + /* Process last bytes. */ + md5_process_block (ctx->buffer, bytes + pad + 8, ctx); + + return md5_read_ctx (ctx, resbuf); +} + +/* Compute MD5 message digest for bytes read from STREAM. The + resulting message digest number will be written into the 16 bytes + beginning at RESBLOCK. */ +int +md5_stream (stream, resblock) + FILE *stream; + void *resblock; +{ + /* Important: BLOCKSIZE must be a multiple of 64. */ +#define BLOCKSIZE 4096 + struct md5_ctx ctx; + char buffer[BLOCKSIZE + 72]; + size_t sum; + + /* Initialize the computation context. */ + md5_init_ctx (&ctx); + + /* Iterate over full file contents. */ + while (1) + { + /* We read the file in blocks of BLOCKSIZE bytes. One call of the + computation function processes the whole buffer so that with the + next round of the loop another block can be read. */ + size_t n; + sum = 0; + + /* Read block. Take care for partial reads. */ + do + { + n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream); + + sum += n; + } + while (sum < BLOCKSIZE && n != 0); + if (n == 0 && ferror (stream)) + return 1; + + /* If end of file is reached, end the loop. */ + if (n == 0) + break; + + /* Process buffer with BLOCKSIZE bytes. Note that + BLOCKSIZE % 64 == 0 + */ + md5_process_block (buffer, BLOCKSIZE, &ctx); + } + + /* Add the last bytes if necessary. */ + if (sum > 0) + md5_process_bytes (buffer, sum, &ctx); + + /* Construct result in desired memory. */ + md5_finish_ctx (&ctx, resblock); + return 0; +} + +/* Compute MD5 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +void * +md5_buffer (buffer, len, resblock) + const char *buffer; + size_t len; + void *resblock; +{ + struct md5_ctx ctx; + + /* Initialize the computation context. */ + md5_init_ctx (&ctx); + + /* Process whole buffer but last len % 64 bytes. */ + md5_process_bytes (buffer, len, &ctx); + + /* Put result in desired memory area. */ + return md5_finish_ctx (&ctx, resblock); +} + + +void +md5_process_bytes (buffer, len, ctx) + const void *buffer; + size_t len; + struct md5_ctx *ctx; +{ + /* When we already have some bits in our internal buffer concatenate + both inputs first. */ + if (ctx->buflen != 0) + { + size_t left_over = ctx->buflen; + size_t add = 128 - left_over > len ? len : 128 - left_over; + + memcpy (&ctx->buffer[left_over], buffer, add); + ctx->buflen += add; + + if (ctx->buflen > 64) + { + md5_process_block (ctx->buffer, ctx->buflen & ~63, ctx); + + ctx->buflen &= 63; + /* The regions in the following copy operation cannot overlap. */ + memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63], + ctx->buflen); + } + + buffer = (const char *) buffer + add; + len -= add; + } + + /* Process available complete blocks. */ + if (len >= 64) + { +#if !_STRING_ARCH_unaligned +/* To check alignment gcc has an appropriate operator. Other + compilers don't. */ +# if __GNUC__ >= 2 +# define UNALIGNED_P(p) (((md5_uintptr) p) % __alignof__ (md5_uint32) != 0) +# else +# define UNALIGNED_P(p) (((md5_uintptr) p) % sizeof (md5_uint32) != 0) +# endif + if (UNALIGNED_P (buffer)) + while (len > 64) + { + md5_process_block (memcpy (ctx->buffer, buffer, 64), 64, ctx); + buffer = (const char *) buffer + 64; + len -= 64; + } + else +#endif + { + md5_process_block (buffer, len & ~63, ctx); + buffer = (const char *) buffer + (len & ~63); + len &= 63; + } + } + + /* Move remaining bytes in internal buffer. */ + if (len > 0) + { + size_t left_over = ctx->buflen; + + memcpy (&ctx->buffer[left_over], buffer, len); + left_over += len; + if (left_over >= 64) + { + md5_process_block (ctx->buffer, 64, ctx); + left_over -= 64; + memcpy (ctx->buffer, &ctx->buffer[64], left_over); + } + ctx->buflen = left_over; + } +} + + +/* These are the four functions used in the four steps of the MD5 algorithm + and defined in the RFC 1321. The first function is a little bit optimized + (as found in Colin Plumbs public domain implementation). */ +/* #define FF(b, c, d) ((b & c) | (~b & d)) */ +#define FF(b, c, d) (d ^ (b & (c ^ d))) +#define FG(b, c, d) FF (d, b, c) +#define FH(b, c, d) (b ^ c ^ d) +#define FI(b, c, d) (c ^ (b | ~d)) + +/* Process LEN bytes of BUFFER, accumulating context into CTX. + It is assumed that LEN % 64 == 0. */ + +void +md5_process_block (buffer, len, ctx) + const void *buffer; + size_t len; + struct md5_ctx *ctx; +{ + md5_uint32 correct_words[16]; + const md5_uint32 *words = buffer; + size_t nwords = len / sizeof (md5_uint32); + const md5_uint32 *endp = words + nwords; + md5_uint32 A = ctx->A; + md5_uint32 B = ctx->B; + md5_uint32 C = ctx->C; + md5_uint32 D = ctx->D; + + /* First increment the byte count. RFC 1321 specifies the possible + length of the file up to 2^64 bits. Here we only compute the + number of bytes. Do a double word increment. */ + ctx->total[0] += len; + if (ctx->total[0] < len) + ++ctx->total[1]; + + /* Process all bytes in the buffer with 64 bytes in each round of + the loop. */ + while (words < endp) + { + md5_uint32 *cwp = correct_words; + md5_uint32 A_save = A; + md5_uint32 B_save = B; + md5_uint32 C_save = C; + md5_uint32 D_save = D; + + /* First round: using the given function, the context and a constant + the next context is computed. Because the algorithms processing + unit is a 32-bit word and it is determined to work on words in + little endian byte order we perhaps have to change the byte order + before the computation. To reduce the work for the next steps + we store the swapped words in the array CORRECT_WORDS. */ + +#define OP(a, b, c, d, s, T) \ + do \ + { \ + a += FF (b, c, d) + (*cwp++ = SWAP (*words)) + T; \ + ++words; \ + CYCLIC (a, s); \ + a += b; \ + } \ + while (0) + + /* It is unfortunate that C does not provide an operator for + cyclic rotation. Hope the C compiler is smart enough. */ +#define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s))) + + /* Before we start, one word to the strange constants. + They are defined in RFC 1321 as + + T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64 + */ + + /* Round 1. */ + OP (A, B, C, D, 7, 0xd76aa478); + OP (D, A, B, C, 12, 0xe8c7b756); + OP (C, D, A, B, 17, 0x242070db); + OP (B, C, D, A, 22, 0xc1bdceee); + OP (A, B, C, D, 7, 0xf57c0faf); + OP (D, A, B, C, 12, 0x4787c62a); + OP (C, D, A, B, 17, 0xa8304613); + OP (B, C, D, A, 22, 0xfd469501); + OP (A, B, C, D, 7, 0x698098d8); + OP (D, A, B, C, 12, 0x8b44f7af); + OP (C, D, A, B, 17, 0xffff5bb1); + OP (B, C, D, A, 22, 0x895cd7be); + OP (A, B, C, D, 7, 0x6b901122); + OP (D, A, B, C, 12, 0xfd987193); + OP (C, D, A, B, 17, 0xa679438e); + OP (B, C, D, A, 22, 0x49b40821); + + /* For the second to fourth round we have the possibly swapped words + in CORRECT_WORDS. Redefine the macro to take an additional first + argument specifying the function to use. */ +#undef OP +#define OP(f, a, b, c, d, k, s, T) \ + do \ + { \ + a += f (b, c, d) + correct_words[k] + T; \ + CYCLIC (a, s); \ + a += b; \ + } \ + while (0) + + /* Round 2. */ + OP (FG, A, B, C, D, 1, 5, 0xf61e2562); + OP (FG, D, A, B, C, 6, 9, 0xc040b340); + OP (FG, C, D, A, B, 11, 14, 0x265e5a51); + OP (FG, B, C, D, A, 0, 20, 0xe9b6c7aa); + OP (FG, A, B, C, D, 5, 5, 0xd62f105d); + OP (FG, D, A, B, C, 10, 9, 0x02441453); + OP (FG, C, D, A, B, 15, 14, 0xd8a1e681); + OP (FG, B, C, D, A, 4, 20, 0xe7d3fbc8); + OP (FG, A, B, C, D, 9, 5, 0x21e1cde6); + OP (FG, D, A, B, C, 14, 9, 0xc33707d6); + OP (FG, C, D, A, B, 3, 14, 0xf4d50d87); + OP (FG, B, C, D, A, 8, 20, 0x455a14ed); + OP (FG, A, B, C, D, 13, 5, 0xa9e3e905); + OP (FG, D, A, B, C, 2, 9, 0xfcefa3f8); + OP (FG, C, D, A, B, 7, 14, 0x676f02d9); + OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a); + + /* Round 3. */ + OP (FH, A, B, C, D, 5, 4, 0xfffa3942); + OP (FH, D, A, B, C, 8, 11, 0x8771f681); + OP (FH, C, D, A, B, 11, 16, 0x6d9d6122); + OP (FH, B, C, D, A, 14, 23, 0xfde5380c); + OP (FH, A, B, C, D, 1, 4, 0xa4beea44); + OP (FH, D, A, B, C, 4, 11, 0x4bdecfa9); + OP (FH, C, D, A, B, 7, 16, 0xf6bb4b60); + OP (FH, B, C, D, A, 10, 23, 0xbebfbc70); + OP (FH, A, B, C, D, 13, 4, 0x289b7ec6); + OP (FH, D, A, B, C, 0, 11, 0xeaa127fa); + OP (FH, C, D, A, B, 3, 16, 0xd4ef3085); + OP (FH, B, C, D, A, 6, 23, 0x04881d05); + OP (FH, A, B, C, D, 9, 4, 0xd9d4d039); + OP (FH, D, A, B, C, 12, 11, 0xe6db99e5); + OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8); + OP (FH, B, C, D, A, 2, 23, 0xc4ac5665); + + /* Round 4. */ + OP (FI, A, B, C, D, 0, 6, 0xf4292244); + OP (FI, D, A, B, C, 7, 10, 0x432aff97); + OP (FI, C, D, A, B, 14, 15, 0xab9423a7); + OP (FI, B, C, D, A, 5, 21, 0xfc93a039); + OP (FI, A, B, C, D, 12, 6, 0x655b59c3); + OP (FI, D, A, B, C, 3, 10, 0x8f0ccc92); + OP (FI, C, D, A, B, 10, 15, 0xffeff47d); + OP (FI, B, C, D, A, 1, 21, 0x85845dd1); + OP (FI, A, B, C, D, 8, 6, 0x6fa87e4f); + OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0); + OP (FI, C, D, A, B, 6, 15, 0xa3014314); + OP (FI, B, C, D, A, 13, 21, 0x4e0811a1); + OP (FI, A, B, C, D, 4, 6, 0xf7537e82); + OP (FI, D, A, B, C, 11, 10, 0xbd3af235); + OP (FI, C, D, A, B, 2, 15, 0x2ad7d2bb); + OP (FI, B, C, D, A, 9, 21, 0xeb86d391); + + /* Add the starting values of the context. */ + A += A_save; + B += B_save; + C += C_save; + D += D_save; + } + + /* Put checksum in context given as argument. */ + ctx->A = A; + ctx->B = B; + ctx->C = C; + ctx->D = D; +} diff --git a/libvncserver/md5.h b/libvncserver/md5.h new file mode 100644 index 0000000..b48545b --- /dev/null +++ b/libvncserver/md5.h @@ -0,0 +1,148 @@ +/* Declaration of functions and data types used for MD5 sum computing + library functions. + Copyright (C) 1995-1997,1999,2000,2001,2004,2005 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#ifndef _MD5_H +#define _MD5_H 1 + +#include + +#if defined HAVE_LIMITS_H || _LIBC +# include +#endif + +#define MD5_DIGEST_SIZE 16 +#define MD5_BLOCK_SIZE 64 + +/* The following contortions are an attempt to use the C preprocessor + to determine an unsigned integral type that is 32 bits wide. An + alternative approach is to use autoconf's AC_CHECK_SIZEOF macro, but + doing that would require that the configure script compile and *run* + the resulting executable. Locally running cross-compiled executables + is usually not possible. */ + +#ifdef _LIBC +# include +typedef uint32_t md5_uint32; +typedef uintptr_t md5_uintptr; +#else +# if defined __STDC__ && __STDC__ +# define UINT_MAX_32_BITS 4294967295U +# else +# define UINT_MAX_32_BITS 0xFFFFFFFF +# endif + +/* If UINT_MAX isn't defined, assume it's a 32-bit type. + This should be valid for all systems GNU cares about because + that doesn't include 16-bit systems, and only modern systems + (that certainly have ) have 64+-bit integral types. */ + +# ifndef UINT_MAX +# define UINT_MAX UINT_MAX_32_BITS +# endif + +# if UINT_MAX == UINT_MAX_32_BITS + typedef unsigned int md5_uint32; +# else +# if USHRT_MAX == UINT_MAX_32_BITS + typedef unsigned short md5_uint32; +# else +# if ULONG_MAX == UINT_MAX_32_BITS + typedef unsigned long md5_uint32; +# else + /* The following line is intended to evoke an error. + Using #error is not portable enough. */ + "Cannot determine unsigned 32-bit data type." +# endif +# endif +# endif +/* We have to make a guess about the integer type equivalent in size + to pointers which should always be correct. */ +typedef unsigned long int md5_uintptr; +#endif + +/* Structure to save state of computation between the single steps. */ +struct md5_ctx +{ + md5_uint32 A; + md5_uint32 B; + md5_uint32 C; + md5_uint32 D; + + md5_uint32 total[2]; + md5_uint32 buflen; + char buffer[128] __attribute__ ((__aligned__ (__alignof__ (md5_uint32)))); +}; + +/* + * The following three functions are build up the low level used in + * the functions `md5_stream' and `md5_buffer'. + */ + +/* Initialize structure containing state of computation. + (RFC 1321, 3.3: Step 3) */ +extern void __md5_init_ctx (struct md5_ctx *ctx) __THROW; + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is necessary that LEN is a multiple of 64!!! */ +extern void __md5_process_block (const void *buffer, size_t len, + struct md5_ctx *ctx) __THROW; + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is NOT required that LEN is a multiple of 64. */ +extern void __md5_process_bytes (const void *buffer, size_t len, + struct md5_ctx *ctx) __THROW; + +/* Process the remaining bytes in the buffer and put result from CTX + in first 16 bytes following RESBUF. The result is always in little + endian byte order, so that a byte-wise output yields to the wanted + ASCII representation of the message digest. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ +extern void *__md5_finish_ctx (struct md5_ctx *ctx, void *resbuf) __THROW; + + +/* Put result from CTX in first 16 bytes following RESBUF. The result is + always in little endian byte order, so that a byte-wise output yields + to the wanted ASCII representation of the message digest. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ +extern void *__md5_read_ctx (const struct md5_ctx *ctx, void *resbuf) __THROW; + + +/* Compute MD5 message digest for bytes read from STREAM. The + resulting message digest number will be written into the 16 bytes + beginning at RESBLOCK. */ +extern int __md5_stream (FILE *stream, void *resblock) __THROW; + +/* Compute MD5 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +extern void *__md5_buffer (const char *buffer, size_t len, + void *resblock) __THROW; + +#endif /* md5.h */ diff --git a/libvncserver/rfbserver.c b/libvncserver/rfbserver.c index 587a2f0..1df4fee 100644 --- a/libvncserver/rfbserver.c +++ b/libvncserver/rfbserver.c @@ -358,6 +358,14 @@ rfbNewTCPOrUDPClient(rfbScreenInfoPtr rfbScreen, rfbScreen->clientHead = cl; UNLOCK(rfbClientListMutex); +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS + cl->webSockets = FALSE; + cl->webSocketsSSL = FALSE; + cl->webSocketsBase64 = FALSE; + cl->dblen= 0; + cl->carrylen = 0; +#endif + #if defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG) cl->tightQualityLevel = -1; #if defined(LIBVNCSERVER_HAVE_LIBJPEG) || defined(LIBVNCSERVER_HAVE_LIBPNG) @@ -404,6 +412,20 @@ rfbNewTCPOrUDPClient(rfbScreenInfoPtr rfbScreen, cl->lastPtrX = -1; +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS + /* + * Wait a few ms for the client to send one of: + * - Flash policy request + * - WebSockets connection (TLS/SSL or plain) + */ + if (!webSocketsCheck(cl)) { + /* Error reporting handled in webSocketsHandshake */ + rfbCloseClient(cl); + rfbClientConnectionGone(cl); + return NULL; + } +#endif + sprintf(pv,rfbProtocolVersionFormat,rfbScreen->protocolMajorVersion, rfbScreen->protocolMinorVersion); @@ -1817,6 +1839,16 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) char encBuf[64]; char encBuf2[64]; +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS + if (cl->webSockets && cl->webSocketsBase64) { + /* With Base64 encoding we need at least 4 bytes */ + n = recv(cl->sock, encBuf, 4, MSG_PEEK); + if ((n > 0) && (n < 4)) { + return; + } + } +#endif + if ((n = rfbReadExact(cl, (char *)&msg, 1)) <= 0) { if (n != 0) rfbLogPerror("rfbProcessClientNormalMessage: read"); @@ -2904,7 +2936,6 @@ rfbSendFramebufferUpdate(rfbClientPtr cl, #endif #ifdef LIBVNCSERVER_HAVE_LIBPNG case rfbEncodingTightPng: - /* TODO */ if (!rfbSendRectEncodingTightPng(cl, x, y, w, h)) goto updateFailed; break; diff --git a/libvncserver/sockets.c b/libvncserver/sockets.c index 188a8fd..267287d 100644 --- a/libvncserver/sockets.c +++ b/libvncserver/sockets.c @@ -457,7 +457,15 @@ rfbReadExactTimeout(rfbClientPtr cl, char* buf, int len, int timeout) struct timeval tv; while (len > 0) { +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS + if (cl->webSockets) { + n = webSocketsDecode(cl, buf, len); + } else { + n = read(sock, buf, len); + } +#else n = read(sock, buf, len); +#endif if (n > 0) { @@ -517,6 +525,71 @@ int rfbReadExact(rfbClientPtr cl,char* buf,int len) return(rfbReadExactTimeout(cl,buf,len,rfbMaxClientWait)); } +/* + * PeekExact peeks at an exact number of bytes from a client. Returns 1 if + * those bytes have been read, 0 if the other end has closed, or -1 if an + * error occurred (errno is set to ETIMEDOUT if it timed out). + */ + +int +rfbPeekExactTimeout(rfbClientPtr cl, char* buf, int len, int timeout) +{ + int sock = cl->sock; + int n; + fd_set fds; + struct timeval tv; + + while (len > 0) { + n = recv(sock, buf, len, MSG_PEEK); + + if (n == len) { + + break; + + } else if (n == 0) { + + return 0; + + } else { +#ifdef WIN32 + errno = WSAGetLastError(); +#endif + if (errno == EINTR) + continue; + +#ifdef LIBVNCSERVER_ENOENT_WORKAROUND + if (errno != ENOENT) +#endif + if (errno != EWOULDBLOCK && errno != EAGAIN) { + return n; + } + + FD_ZERO(&fds); + FD_SET(sock, &fds); + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + n = select(sock+1, &fds, NULL, &fds, &tv); + if (n < 0) { + rfbLogPerror("ReadExact: select"); + return n; + } + if (n == 0) { + errno = ETIMEDOUT; + return -1; + } + } + } +#undef DEBUG_READ_EXACT +#ifdef DEBUG_READ_EXACT + rfbLog("ReadExact %d bytes\n",len); + for(n=0;nwebSockets) { + if ((len = webSocketsEncode(cl, buf, len)) < 0) { + rfbErr("WriteExact: WebSockets encode error\n"); + return -1; + } + buf = cl->encodeBuf; + } +#endif + LOCK(cl->outputMutex); while (len > 0) { n = write(sock, buf, len); diff --git a/libvncserver/websockets.c b/libvncserver/websockets.c new file mode 100755 index 0000000..63b0e87 --- /dev/null +++ b/libvncserver/websockets.c @@ -0,0 +1,448 @@ +/* + * websockets.c - deal with WebSockets clients. + * + * This code should be independent of any changes in the RFB protocol. It is + * an additional handshake and framing of normal sockets: + * http://www.whatwg.org/specs/web-socket-protocol/ + * + */ + +/* + * Copyright (C) 2010 Joel Martin + * + * 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 +#include /* __b64_ntop */ +/* errno */ +#include + +#include + +#define FLASH_POLICY_RESPONSE "\n" +#define SZ_FLASH_POLICY_RESPONSE 93 + +#define WEBSOCKETS_HANDSHAKE_RESPONSE "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: sample\r\n\ +\r\n%s" + +#define WEBSOCKETS_CLIENT_CONNECT_WAIT_MS 100 +#define WEBSOCKETS_CLIENT_SEND_WAIT_MS 20 +#define WEBSOCKETS_MAX_HANDSHAKE_LEN 4096 + +#if defined(__linux__) && defined(NEED_TIMEVAL) +struct timeval +{ + long int tv_sec,tv_usec; +} +; +#endif + +static rfbBool webSocketsHandshake(rfbClientPtr cl, char *scheme); +void webSocketsGenMd5(char * target, char *key1, char *key2, char *key3); + +static int +min (int a, int b) { + return a < b ? a : b; +} + +/* + * rfbWebSocketsHandshake is called to handle new WebSockets connections + */ + +rfbBool +webSocketsCheck (rfbClientPtr cl) +{ + char bbuf[4], *scheme; + int ret; + + ret = rfbPeekExactTimeout(cl, bbuf, 4, + WEBSOCKETS_CLIENT_CONNECT_WAIT_MS); + if ((ret < 0) && (errno == ETIMEDOUT)) { + rfbLog("Normal socket connection\n"); + return TRUE; + } else if (ret <= 0) { + rfbErr("webSocketsHandshake: unknown connection error\n"); + 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; + } else if (strncmp(bbuf, "\x16", 1) == 0) { + cl->webSocketsSSL = TRUE; + rfbLog("Got TLS/SSL WebSockets connection\n"); + scheme = "wss"; + /* TODO */ + /* bbuf = ... */ + return FALSE; + } else { + cl->webSocketsSSL = FALSE; + scheme = "ws"; + } + + if (strncmp(bbuf, "GET ", 4) != 0) { + rfbErr("webSocketsHandshake: invalid client header\n"); + return FALSE; + } + + rfbLog("Got '%s' WebSockets handshake\n", scheme); + + if (!webSocketsHandshake(cl, scheme)) { + return FALSE; + } + cl->webSockets = TRUE; /* Start WebSockets framing */ + return TRUE; +} + +static rfbBool +webSocketsHandshake(rfbClientPtr cl, char *scheme) +{ + char *buf, *response, *line; + int n, linestart = 0, len = 0, llen; + char prefix[5], trailer[17]; + char *path, *host, *origin; + char *key1 = NULL, *key2 = NULL, *key3 = NULL; + + buf = (char *) malloc(WEBSOCKETS_MAX_HANDSHAKE_LEN); + if (!buf) { + rfbLogPerror("webSocketsHandshake: malloc"); + return FALSE; + } + response = (char *) malloc(WEBSOCKETS_MAX_HANDSHAKE_LEN); + if (!response) { + free(buf); + rfbLogPerror("webSocketsHandshake: malloc"); + return FALSE; + } + + while (len < WEBSOCKETS_MAX_HANDSHAKE_LEN-1) { + if ((n = rfbReadExactTimeout(cl, buf+len, 1, + WEBSOCKETS_CLIENT_SEND_WAIT_MS)) <= 0) { + if ((n < 0) && (errno == ETIMEDOUT)) { + break; + } + if (n == 0) + rfbLog("webSocketsHandshake: client gone\n"); + else + rfbLogPerror("webSocketsHandshake: read"); + return FALSE; + } + + len += 1; + llen = len - linestart; + if (((llen >= 2)) && (buf[len-1] == '\n')) { + line = buf+linestart; + if ((llen == 2) && ((strncmp("\r\n\r\n", buf+len-4, 4)) == 0)) { + if (key1 && key2) { + if ((n = rfbReadExact(cl, buf+len, 8)) <= 0) { + if ((n < 0) && (errno == ETIMEDOUT)) { + break; + } + if (n == 0) + rfbLog("webSocketsHandshake: client gone\n"); + else + rfbLogPerror("webSocketsHandshake: read"); + return FALSE; + } + rfbLog("Got key3\n"); + key3 = buf+len; + len += 8; + } else { + buf[len] = '\0'; + } + break; + } else if ((llen >= 16) && ((strncmp("GET ", line, min(llen,4))) == 0)) { + /* 16 = 4 ("GET ") + 1 ("/.*") + 11 (" HTTP/1.1\r\n") */ + /* rfbLog("Got path\n"); */ + path = line+4; + buf[len-11] = '\0'; /* Trim trailing " HTTP/1.1\r\n" */ + if (strstr(path, "b64encode")) { + rfbLog(" - using base64 encoding\n"); + cl->webSocketsBase64 = TRUE; + } else { + rfbLog(" - using UTF-8 encoding\n"); + cl->webSocketsBase64 = FALSE; + } + } else if ((strncmp("Host: ", line, min(llen,6))) == 0) { + /* rfbLog("Got host\n"); */ + host = line+6; + buf[len-2] = '\0'; + } else if ((strncmp("Origin: ", line, min(llen,8))) == 0) { + /* rfbLog("Got origin\n"); */ + origin = line+8; + buf[len-2] = '\0'; + } else if ((strncmp("Sec-Websocket-Key1: ", line, min(llen,20))) == 0) { + /* rfbLog("Got key1\n"); */ + key1 = line+20; + buf[len-2] = '\0'; + } else if ((strncmp("Sec-Websocket-Key2: ", line, min(llen,20))) == 0) { + /* rfbLog("Got key2\n"); */ + key2 = line+20; + buf[len-2] = '\0'; + } + linestart = len; + } + } + + if (!(path && host && origin)) { + rfbErr("webSocketsHandshake: incomplete client handshake\n"); + free(response); + free(buf); + return FALSE; + } + + /* + * Generate the WebSockets server response based on the the headers sent + * by the client. + */ + + if (!(key1 && key2 && key3)) { + rfbLog(" - WebSockets client version 75\n"); + prefix[0] = '\0'; + trailer[0] = '\0'; + } else { + rfbLog(" - WebSockets client version 76\n"); + snprintf(prefix, 5, "Sec-"); + webSocketsGenMd5(trailer, key1, key2, key3); + } + + snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN, + WEBSOCKETS_HANDSHAKE_RESPONSE, prefix, origin, prefix, scheme, + host, path, prefix, trailer); + + if (rfbWriteExact(cl, response, strlen(response)) < 0) { + rfbErr("webSocketsHandshake: failed sending WebSockets response\n"); + free(response); + free(buf); + return FALSE; + } + /* rfbLog("webSocketsHandshake: handshake complete\n"); */ + 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]; + 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'; + + md5_buffer((char *)buf, 16, target); + target[16] = '\0'; + + return; +} + +int +webSocketsEncode(rfbClientPtr cl, const char *src, int len) +{ + int i, sz = 0; + unsigned char chr; + cl->encodeBuf[sz++] = '\x00'; + if (cl->webSocketsBase64) { + len = __b64_ntop((unsigned char *)src, len, cl->encodeBuf+sz, UPDATE_BUF_SIZE*2); + if (len < 0) { + return len; + } + sz += len; + } else { + for (i=0; i < len; i++) { + chr = src[i]; + if (chr < 128) { + if (chr == 0x00) { + cl->encodeBuf[sz++] = '\xc4'; + cl->encodeBuf[sz++] = '\x80'; + } else { + cl->encodeBuf[sz++] = chr; + } + } else { + if (chr < 192) { + cl->encodeBuf[sz++] = '\xc2'; + cl->encodeBuf[sz++] = chr; + } else { + cl->encodeBuf[sz++] = '\xc3'; + cl->encodeBuf[sz++] = chr - 64; + } + } + } + } + cl->encodeBuf[sz++] = '\xff'; + /* rfbLog("<< webSocketsEncode: %d\n", len); */ + return sz; +} + +int +webSocketsDecode(rfbClientPtr cl, char *dst, int len) +{ + int retlen = 0, n, i, avail, modlen, needlen; + char *buf, *end = NULL; + unsigned char chr; + + buf = cl->decodeBuf; + n = recv(cl->sock, buf, len*2+2, MSG_PEEK); + + if (n <= 0) { + rfbLog("recv of %d\n", n); + return n; + } + + if (buf[0] == '\xff') { + i = read(cl->sock, buf, 1); /* Consume marker */ + buf++; + n--; + } + if (buf[0] == '\x00') { + i = read(cl->sock, buf, 1); /* Consume marker */ + buf++; + n--; + } + /* rfbLog(">> webSocketsDecode, len: %d, n: %d\n", len, n); */ + end = memchr(buf, '\xff', len*2+2); + if (!end) { + end = buf + n; + } + avail = end - buf; + + if (cl->webSocketsBase64) { + /* Base64 encoded WebSockets stream */ + + len -= cl->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 < cl->carrylen; i++) { + /* rfbLog("Adding carryover %d\n", cl->carryBuf[i]); */ + dst[i] = cl->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 */ + /* rfbLog("here1, needlen: %d, n: %d, len: %d\n", needlen, n, len); */ + i = read(cl->sock, buf, needlen); + + cl->carrylen = n - len; + retlen -= cl->carrylen; + for (i=0; i < cl->carrylen; i++) { + /* rfbLog("Saving carryover %d\n", dst[retlen + i]); */ + cl->carryBuf[i] = dst[retlen + i]; + } + } else { + /* UTF-8 encoded WebSockets stream */ + while (retlen < len) { + chr = *buf; + buf += 1; + if (chr < 128) { + dst[retlen++] = chr; + } else { + if (buf >= end) { + rfbErr("Not enough UTF-8 data to decode\n"); + errno = EIO; + return -1; + } + chr = *buf; + buf += 1; + switch (chr) { + case (unsigned char) '\xc2': + dst[retlen++] = chr; + break; + case (unsigned char) '\xc3': + dst[retlen++] = chr + 64; + break; + case (unsigned char) '\xc4': + dst[retlen++] = 0; + break; + } + } + } + } + +#if 0 + sprintf(debug, "dst:"); + for (i = 0; i < retlen; i++) { + sprintf(debug+strlen(debug), "%d,", dst[i]); + } + rfbLog("%s\n", debug); + + rfbLog("<< webSocketsDecode, retlen: %d\n", retlen); +#endif + return retlen; +} diff --git a/rfb/rfb.h b/rfb/rfb.h index e79b68b..1a46e9a 100644 --- a/rfb/rfb.h +++ b/rfb/rfb.h @@ -631,6 +631,19 @@ typedef struct _rfbClientRec { #if defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG) uint32_t tightEncoding; /* rfbEncodingTight or rfbEncodingTightPng */ #endif + +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS + rfbBool webSockets; + rfbBool webSocketsSSL; + rfbBool webSocketsBase64; + + char encodeBuf[UPDATE_BUF_SIZE*2 + 2]; /* UTF-8 could double it + framing */ + + char decodeBuf[8192]; /* TODO: what makes sense? */ + int dblen; + char carryBuf[3]; /* For base64 carry-over */ + int carrylen; +#endif } rfbClientRec, *rfbClientPtr; /** @@ -683,6 +696,7 @@ extern void rfbDisconnectUDPSock(rfbScreenInfoPtr rfbScreen); extern void rfbCloseClient(rfbClientPtr cl); extern int rfbReadExact(rfbClientPtr cl, char *buf, int len); extern int rfbReadExactTimeout(rfbClientPtr cl, char *buf, int len,int timeout); +extern int rfbPeekExactTimeout(rfbClientPtr cl, char *buf, int len,int timeout); extern int rfbWriteExact(rfbClientPtr cl, const char *buf, int len); extern int rfbCheckFds(rfbScreenInfoPtr rfbScreen,long usec); extern int rfbConnect(rfbScreenInfoPtr rfbScreen, char* host, int port); @@ -692,6 +706,14 @@ extern int rfbListenOnUDPPort(int port, in_addr_t iface); extern int rfbStringToAddr(char* string,in_addr_t* addr); extern rfbBool rfbSetNonBlocking(int sock); +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS +/* websockets.c */ + +extern rfbBool webSocketsCheck(rfbClientPtr cl); +extern int webSocketsEncode(rfbClientPtr cl, const char *src, int len); +extern int webSocketsDecode(rfbClientPtr cl, char *dst, int len); +#endif + /* rfbserver.c */ /* Routines to iterate over the client list in a thread-safe way. -- cgit v1.2.3 From 4aa35863676335917d2a25a7952031f0fba66dfb Mon Sep 17 00:00:00 2001 From: Gernot Tenchio Date: Tue, 16 Aug 2011 14:02:35 +0200 Subject: websockets: Add encryption support [jes: moved out GnuTLS and OpenSSL support, added a dummy support, to separate changes better, and to keep things compiling] Signed-off-by: Johannes Schindelin --- libvncserver/Makefile.am | 2 +- libvncserver/rfbserver.c | 55 ++++++++++++++++++++++++++++++++++++------- libvncserver/rfbssl.h | 15 ++++++++++++ libvncserver/rfbssl_none.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++ libvncserver/sockets.c | 43 ++++++++++++++++++++++++++++++---- libvncserver/websockets.c | 49 +++++++++++++++++++++++++++++---------- rfb/rfb.h | 9 ++++++- 7 files changed, 204 insertions(+), 27 deletions(-) create mode 100644 libvncserver/rfbssl.h create mode 100644 libvncserver/rfbssl_none.c (limited to 'rfb') diff --git a/libvncserver/Makefile.am b/libvncserver/Makefile.am index 0d64363..bbc8feb 100644 --- a/libvncserver/Makefile.am +++ b/libvncserver/Makefile.am @@ -13,7 +13,7 @@ TIGHTVNCFILETRANSFERSRCS = tightvnc-filetransfer/rfbtightserver.c \ endif if WITH_WEBSOCKETS -WEBSOCKETSSRCS = websockets.c md5.c +WEBSOCKETSSRCS = websockets.c md5.c rfbssl_none.c endif includedir=$(prefix)/include/rfb diff --git a/libvncserver/rfbserver.c b/libvncserver/rfbserver.c index 25204cd..c623329 100644 --- a/libvncserver/rfbserver.c +++ b/libvncserver/rfbserver.c @@ -73,6 +73,10 @@ /* strftime() */ #include +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS +#include "rfbssl.h" +#endif + #ifdef __MINGW32__ static int compat_mkdir(const char *path, int mode) { @@ -360,7 +364,6 @@ rfbNewTCPOrUDPClient(rfbScreenInfoPtr rfbScreen, #ifdef LIBVNCSERVER_WITH_WEBSOCKETS cl->webSockets = FALSE; - cl->webSocketsSSL = FALSE; cl->webSocketsBase64 = FALSE; cl->dblen= 0; cl->carrylen = 0; @@ -1841,16 +1844,50 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) #ifdef LIBVNCSERVER_WITH_WEBSOCKETS if (cl->webSockets) { - n = recv(cl->sock, encBuf, 4, MSG_PEEK); - if (cl->webSocketsBase64) { + if (cl->sslctx) + n = rfbssl_peek(cl, encBuf, 4); + else + n = recv(cl->sock, encBuf, 4, MSG_PEEK); + + if (n <= 0) { + if (n != 0) + rfbLogPerror("rfbProcessClientNormalMessage: peek"); + rfbCloseClient(cl); + return; + } + + if (cl->webSocketsBase64) { /* With Base64 encoding we need at least 4 bytes */ if ((n > 0) && (n < 4)) { if (encBuf[0] == '\xff') { + int doclose = 0; /* Make sure we don't miss a client disconnect on an end frame - * marker */ - n = read(cl->sock, encBuf, 1); + * 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 (encBuf[1] == '\xff' && encBuf[2] == '\x00') + doclose = 1; + break; + case 2: + if (encBuf[1] == '\x00') + doclose = 1; + break; + default: + ; + } + + if (cl->sslctx) + n = rfbssl_read(cl, encBuf, n); + else + n = read(cl->sock, encBuf, n); + + if (doclose) { + rfbErr("rfbProcessClientNormalMessage: websocket close frame received\n"); + rfbCloseClient(cl); + } + return; } - return; } } else { /* With UTF-8 encoding we need at least 3 bytes (framing + 1) */ @@ -1858,9 +1895,11 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) if (encBuf[0] == '\xff') { /* Make sure we don't miss a client disconnect on an end frame * marker */ - n = read(cl->sock, encBuf, 1); + if (cl->sslctx) + n = rfbssl_read(cl, encBuf, 1); + else + n = read(cl->sock, encBuf, 1); } - return; } } } diff --git a/libvncserver/rfbssl.h b/libvncserver/rfbssl.h new file mode 100644 index 0000000..f1c4792 --- /dev/null +++ b/libvncserver/rfbssl.h @@ -0,0 +1,15 @@ +#ifndef _VNCSSL_H +#define _VNCSSL_H 1 + +#include "rfb/rfb.h" +#include "rfb/rfbconfig.h" + +int rfbssl_init(rfbClientPtr cl); +int rfbssl_pending(rfbClientPtr cl); +int rfbssl_peek(rfbClientPtr cl, char *buf, int bufsize); +int rfbssl_read(rfbClientPtr cl, char *buf, int bufsize); +int rfbssl_write(rfbClientPtr cl, const char *buf, int bufsize); +void rfbssl_destroy(rfbClientPtr cl); + + +#endif /* _VNCSSL_H */ diff --git a/libvncserver/rfbssl_none.c b/libvncserver/rfbssl_none.c new file mode 100644 index 0000000..488a6f4 --- /dev/null +++ b/libvncserver/rfbssl_none.c @@ -0,0 +1,58 @@ +/* + * rfbssl_none.c - Secure socket functions (fallback to failing) + */ + +/* + * Copyright (C) 2011 Johannes Schindelin + * + * 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 "rfbssl.h" + +struct rfbssl_ctx *rfbssl_init_global(char *key, char *cert) +{ + return NULL; +} + +int rfbssl_init(rfbClientPtr cl) +{ + return -1; +} + +int rfbssl_write(rfbClientPtr cl, const char *buf, int bufsize) +{ + return -1; +} + +int rfbssl_peek(rfbClientPtr cl, char *buf, int bufsize) +{ + return -1; +} + +int rfbssl_read(rfbClientPtr cl, char *buf, int bufsize) +{ + return -1; +} + +int rfbssl_pending(rfbClientPtr cl) +{ + return -1; +} + +void rfbssl_destroy(rfbClientPtr cl) +{ +} diff --git a/libvncserver/sockets.c b/libvncserver/sockets.c index 267287d..1886187 100644 --- a/libvncserver/sockets.c +++ b/libvncserver/sockets.c @@ -62,6 +62,10 @@ #include #endif +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS +#include "rfbssl.h" +#endif + #if defined(__linux__) && defined(NEED_TIMEVAL) struct timeval { @@ -392,6 +396,10 @@ rfbCloseClient(rfbClientPtr cl) while(cl->screen->maxFd>0 && !FD_ISSET(cl->screen->maxFd,&(cl->screen->allFds))) cl->screen->maxFd--; +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS + if (cl->sslctx) + rfbssl_destroy(cl); +#endif #ifndef __MINGW32__ shutdown(cl->sock,SHUT_RDWR); #endif @@ -460,7 +468,9 @@ rfbReadExactTimeout(rfbClientPtr cl, char* buf, int len, int timeout) #ifdef LIBVNCSERVER_WITH_WEBSOCKETS if (cl->webSockets) { n = webSocketsDecode(cl, buf, len); - } else { + } else if (cl->sslctx) { + n = rfbssl_read(cl, buf, len); + } else { n = read(sock, buf, len); } #else @@ -490,6 +500,12 @@ rfbReadExactTimeout(rfbClientPtr cl, char* buf, int len, int timeout) return n; } +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS + if (cl->sslctx) { + if (rfbssl_pending(cl)) + continue; + } +#endif FD_ZERO(&fds); FD_SET(sock, &fds); tv.tv_sec = timeout / 1000; @@ -500,6 +516,7 @@ rfbReadExactTimeout(rfbClientPtr cl, char* buf, int len, int timeout) return n; } if (n == 0) { + rfbErr("ReadExact: select timeout\n"); errno = ETIMEDOUT; return -1; } @@ -540,7 +557,12 @@ rfbPeekExactTimeout(rfbClientPtr cl, char* buf, int len, int timeout) struct timeval tv; while (len > 0) { - n = recv(sock, buf, len, MSG_PEEK); +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS + if (cl->sslctx) + n = rfbssl_peek(cl, buf, len); + else +#endif + n = recv(sock, buf, len, MSG_PEEK); if (n == len) { @@ -564,13 +586,19 @@ rfbPeekExactTimeout(rfbClientPtr cl, char* buf, int len, int timeout) return n; } +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS + if (cl->sslctx) { + if (rfbssl_pending(cl)) + continue; + } +#endif FD_ZERO(&fds); FD_SET(sock, &fds); tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000; n = select(sock+1, &fds, NULL, &fds, &tv); if (n < 0) { - rfbLogPerror("ReadExact: select"); + rfbLogPerror("PeekExact: select"); return n; } if (n == 0) { @@ -581,7 +609,7 @@ rfbPeekExactTimeout(rfbClientPtr cl, char* buf, int len, int timeout) } #undef DEBUG_READ_EXACT #ifdef DEBUG_READ_EXACT - rfbLog("ReadExact %d bytes\n",len); + rfbLog("PeekExact %d bytes\n",len); for(n=0;noutputMutex); while (len > 0) { - n = write(sock, buf, len); +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS + if (cl->sslctx) + n = rfbssl_write(cl, buf, len); + else +#endif + n = write(sock, buf, len); if (n > 0) { diff --git a/libvncserver/websockets.c b/libvncserver/websockets.c index e0d3cc9..63e2b53 100755 --- a/libvncserver/websockets.c +++ b/libvncserver/websockets.c @@ -32,6 +32,7 @@ #include #include +#include "rfbssl.h" #define FLASH_POLICY_RESPONSE "\n" #define SZ_FLASH_POLICY_RESPONSE 93 @@ -91,15 +92,15 @@ webSocketsCheck (rfbClientPtr cl) rfbErr("webSocketsHandshake: failed sending Flash policy response"); } return FALSE; - } else if (strncmp(bbuf, "\x16", 1) == 0) { - cl->webSocketsSSL = 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)) { + rfbErr("webSocketsHandshake: rfbssl_init failed\n"); + return FALSE; + } + ret = rfbPeekExactTimeout(cl, bbuf, 4, WEBSOCKETS_CLIENT_CONNECT_WAIT_MS); scheme = "wss"; - /* TODO */ - /* bbuf = ... */ - return FALSE; } else { - cl->webSocketsSSL = FALSE; scheme = "ws"; } @@ -334,6 +335,30 @@ webSocketsEncode(rfbClientPtr cl, const char *src, int len) 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) +{ + int n; + if (cl->sslctx) { + n = rfbssl_peek(cl, buf, len); + } else { + n = recv(cl->sock, buf, len, MSG_PEEK); + } + return n; +} + int webSocketsDecode(rfbClientPtr cl, char *dst, int len) { @@ -343,10 +368,10 @@ webSocketsDecode(rfbClientPtr cl, char *dst, int len) buf = cl->decodeBuf; - n = recv(cl->sock, buf, len*2+2, MSG_PEEK); + n = ws_peek(cl, buf, len*2+2); if (n <= 0) { - rfbLog("recv of %d\n", n); + rfbErr("%s: recv of %d\n", __func__, n); return n; } @@ -355,7 +380,7 @@ webSocketsDecode(rfbClientPtr cl, char *dst, int len) /* Base64 encoded WebSockets stream */ if (buf[0] == '\xff') { - i = read(cl->sock, buf, 1); /* Consume marker */ + i = ws_read(cl, buf, 1); /* Consume marker */ buf++; n--; } @@ -364,7 +389,7 @@ webSocketsDecode(rfbClientPtr cl, char *dst, int len) return -1; } if (buf[0] == '\x00') { - i = read(cl->sock, buf, 1); /* Consume marker */ + i = ws_read(cl, buf, 1); /* Consume marker */ buf++; n--; } @@ -414,7 +439,7 @@ webSocketsDecode(rfbClientPtr cl, char *dst, int len) retlen += n; /* Consume the data from socket */ - i = read(cl->sock, buf, needlen); + i = ws_read(cl, buf, needlen); cl->carrylen = n - len; retlen -= cl->carrylen; @@ -445,7 +470,7 @@ webSocketsDecode(rfbClientPtr cl, char *dst, int len) } /* Consume what we need */ - if ((n = read(cl->sock, buf, needlen)) < needlen) { + if ((n = ws_read(cl, buf, needlen)) < needlen) { return n; } diff --git a/rfb/rfb.h b/rfb/rfb.h index 1a46e9a..b6fb7c0 100644 --- a/rfb/rfb.h +++ b/rfb/rfb.h @@ -368,6 +368,10 @@ typedef struct _rfbScreenInfo rfbDisplayFinishedHookPtr displayFinishedHook; /** xvpHook is called to handle an xvp client message */ rfbXvpHookPtr xvpHook; +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS + char *sslkeyfile; + char *sslcertfile; +#endif } rfbScreenInfo, *rfbScreenInfoPtr; @@ -414,6 +418,8 @@ typedef struct _rfbStatList { struct _rfbStatList *Next; } rfbStatList; +typedef struct _rfbSslCtx rfbSslCtx; + typedef struct _rfbClientRec { /** back pointer to the screen */ @@ -637,8 +643,9 @@ typedef struct _rfbClientRec { rfbBool webSocketsSSL; rfbBool webSocketsBase64; - char encodeBuf[UPDATE_BUF_SIZE*2 + 2]; /* UTF-8 could double it + framing */ + rfbSslCtx *sslctx; + char encodeBuf[UPDATE_BUF_SIZE*2 + 2]; /* UTF-8 could double it + framing */ char decodeBuf[8192]; /* TODO: what makes sense? */ int dblen; char carryBuf[3]; /* For base64 carry-over */ -- cgit v1.2.3 From c2fb69f84d3243e7068754436f8b5edbacc837ad Mon Sep 17 00:00:00 2001 From: Gernot Tenchio Date: Tue, 16 Aug 2011 14:02:37 +0200 Subject: websockets: Add Websockets support to CMakeLists.txt Signed-off-by: Johannes Schindelin --- CMakeLists.txt | 30 ++++++++++++++++++++++++++++-- rfb/rfbconfig.h.cmake | 3 +++ 2 files changed, 31 insertions(+), 2 deletions(-) (limited to 'rfb') diff --git a/CMakeLists.txt b/CMakeLists.txt index bf17aa3..c823ff2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,11 +20,17 @@ find_package(SDL) find_package(GnuTLS) find_package(Threads) find_package(X11) +find_package(OpenSSL) find_library(LIBGCRYPT_LIBRARIES gcrypt) +set(CMAKE_REQUIRED_LIBRARIES resolv) +check_function_exists(__b64_ntop HAVE_B64) + if(Threads_FOUND) option(TIGHTVNC_FILETRANSFER "Enable filetransfer" ON) endif(Threads_FOUND) +if (HAVE_B64) +endif(HAVE_B64) if(ZLIB_FOUND) set(LIBVNCSERVER_HAVE_LIBZ 1) endif(ZLIB_FOUND) @@ -35,9 +41,18 @@ if(PNG_FOUND) set(LIBVNCSERVER_HAVE_LIBPNG 1) endif(PNG_FOUND) option(LIBVNCSERVER_ALLOW24BPP "Allow 24 bpp" ON) + if(GNUTLS_FOUND) set(LIBVNCSERVER_WITH_CLIENT_TLS 1) -endif(GNUTLS_FOUND) + option(LIBVNCSERVER_WITH_WEBSOCKETS "Build with websockets support (gnutls)" ON) + set(WEBSOCKET_LIBRARIES -lresolv ${GNUTLS_LIBRARIES}) + set(WSSRCS rfbssl_gnutls) +elseif(OPENSSL_FOUND) + option(LIBVNCSERVER_WITH_WEBSOCKETS "Build with websockets support (openssl)" ON) + set(WEBSOCKET_LIBRARIES -lresolv ${OPENSSL_LIBRARIES}) + set(WSSRCS rfbssl_openssl) +endif() + if(LIBGCRYPT_LIBRARIES) message(STATUS "Found libgcrypt: ${LIBGCRYPT_LIBRARIES}") set(LIBVNCSERVER_WITH_CLIENT_GCRYPT 1) @@ -155,7 +170,6 @@ if(PNG_FOUND) set(TIGHT_C ${LIBVNCSERVER_DIR}/tight.c) endif(PNG_FOUND) - set(LIBVNCSERVER_SOURCES ${LIBVNCSERVER_SOURCES} ${TIGHT_C} @@ -171,6 +185,17 @@ if(TIGHTVNC_FILETRANSFER) ) endif(TIGHTVNC_FILETRANSFER) +if(LIBVNCSERVER_WITH_WEBSOCKETS) + add_definitions(-DLIBVNCSERVER_WITH_WEBSOCKETS) + set(LIBVNCSERVER_SOURCES + ${LIBVNCSERVER_SOURCES} + ${LIBVNCSERVER_DIR}/websockets.c + ${LIBVNCSERVER_DIR}/${WSSRCS} + ${LIBVNCSERVER_DIR}/md5.c + ) +endif(LIBVNCSERVER_WITH_WEBSOCKETS) + + add_library(vncclient SHARED ${LIBVNCCLIENT_SOURCES}) add_library(vncserver SHARED ${LIBVNCSERVER_SOURCES}) if(WIN32) @@ -189,6 +214,7 @@ target_link_libraries(vncserver ${JPEG_LIBRARIES} ${PNG_LIBRARIES} ${SDL_LIBRARY} + ${WEBSOCKET_LIBRARIES} ) # tests diff --git a/rfb/rfbconfig.h.cmake b/rfb/rfbconfig.h.cmake index b7f225c..b095948 100644 --- a/rfb/rfbconfig.h.cmake +++ b/rfb/rfbconfig.h.cmake @@ -63,6 +63,9 @@ /* Define to 1 if GnuTLS is present */ #cmakedefine LIBVNCSERVER_WITH_CLIENT_TLS 1 +/* Define to 1 to build with websockets */ +#cmakedefine LIBVNCSERVER_WITH_WEBSOCKETS 1 + /* Define to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel and VAX). */ #cmakedefine LIBVNCSERVER_WORDS_BIGENDIAN 1 -- cgit v1.2.3 From 297072a691970fb7e2cd379b62f52f30d5988592 Mon Sep 17 00:00:00 2001 From: Gernot Tenchio Date: Tue, 16 Aug 2011 14:02:39 +0200 Subject: websockets: Add wspath member to rfbClientRec Added wspath member to rfbClientRec which holds the path component of the initial websocket request. Signed-off-by: Johannes Schindelin --- libvncserver/sockets.c | 1 + libvncserver/websockets.c | 1 + rfb/rfb.h | 1 + 3 files changed, 3 insertions(+) (limited to 'rfb') diff --git a/libvncserver/sockets.c b/libvncserver/sockets.c index 1886187..e18ce70 100644 --- a/libvncserver/sockets.c +++ b/libvncserver/sockets.c @@ -399,6 +399,7 @@ rfbCloseClient(rfbClientPtr cl) #ifdef LIBVNCSERVER_WITH_WEBSOCKETS if (cl->sslctx) rfbssl_destroy(cl); + free(cl->wspath); #endif #ifndef __MINGW32__ shutdown(cl->sock,SHUT_RDWR); diff --git a/libvncserver/websockets.c b/libvncserver/websockets.c index 63e2b53..7297339 100755 --- a/libvncserver/websockets.c +++ b/libvncserver/websockets.c @@ -180,6 +180,7 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) path = line+4; buf[len-11] = '\0'; /* Trim trailing " HTTP/1.1\r\n" */ cl->webSocketsBase64 = TRUE; + cl->wspath = strdup(path); /* rfbLog("Got path: %s\n", path); */ } else if ((strncasecmp("host: ", line, min(llen,6))) == 0) { host = line+6; diff --git a/rfb/rfb.h b/rfb/rfb.h index b6fb7c0..9239b35 100644 --- a/rfb/rfb.h +++ b/rfb/rfb.h @@ -645,6 +645,7 @@ typedef struct _rfbClientRec { rfbSslCtx *sslctx; + char *wspath; /* Requests path component */ char encodeBuf[UPDATE_BUF_SIZE*2 + 2]; /* UTF-8 could double it + framing */ char decodeBuf[8192]; /* TODO: what makes sense? */ int dblen; -- cgit v1.2.3 From 1408866c864cac3b1bbf37eb9fdc8d303f37957d Mon Sep 17 00:00:00 2001 From: Gernot Tenchio Date: Thu, 25 Aug 2011 10:58:19 +0200 Subject: websockets: Initial HyBi support --- libvncserver/rfbserver.c | 2 - libvncserver/sockets.c | 5 +- libvncserver/websockets.c | 430 +++++++++++++++++++++++++++++++++++++++++----- rfb/rfb.h | 12 +- 4 files changed, 395 insertions(+), 54 deletions(-) (limited to 'rfb') diff --git a/libvncserver/rfbserver.c b/libvncserver/rfbserver.c index c623329..d6a5da4 100644 --- a/libvncserver/rfbserver.c +++ b/libvncserver/rfbserver.c @@ -365,8 +365,6 @@ rfbNewTCPOrUDPClient(rfbScreenInfoPtr rfbScreen, #ifdef LIBVNCSERVER_WITH_WEBSOCKETS cl->webSockets = FALSE; cl->webSocketsBase64 = FALSE; - cl->dblen= 0; - cl->carrylen = 0; #endif #if defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG) diff --git a/libvncserver/sockets.c b/libvncserver/sockets.c index e18ce70..b3d5b59 100644 --- a/libvncserver/sockets.c +++ b/libvncserver/sockets.c @@ -647,11 +647,12 @@ rfbWriteExact(rfbClientPtr cl, #ifdef LIBVNCSERVER_WITH_WEBSOCKETS if (cl->webSockets) { - if ((len = webSocketsEncode(cl, buf, len)) < 0) { + char *tmp = NULL; + if ((len = webSocketsEncode(cl, buf, len, &tmp)) < 0) { rfbErr("WriteExact: WebSockets encode error\n"); return -1; } - buf = cl->encodeBuf; + buf = tmp; } #endif diff --git a/libvncserver/websockets.c b/libvncserver/websockets.c index 7297339..e3b47e3 100755 --- a/libvncserver/websockets.c +++ b/libvncserver/websockets.c @@ -32,12 +32,90 @@ #include #include +#include +#include "rfbconfig.h" #include "rfbssl.h" +#if defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && __BYTE_ORDER == __BIG_ENDIAN +#define WS_NTOH64(n) (n) +#define WS_NTOH32(n) (n) +#define WS_NTOH16(n) (n) +#define WS_HTON64(n) (n) +#define WS_HTON16(n) (n) +#else +#define WS_NTOH64(n) bswap_64(n) +#define WS_NTOH32(n) bswap_32(n) +#define WS_NTOH16(n) bswap_16(n) +#define WS_HTON64(n) bswap_64(n) +#define WS_HTON16(n) bswap_16(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 +}; + +#include +static int gettid() { + return (int)syscall(SYS_gettid); +} + +typedef struct ws_ctx_s { + char encodeBuf[B64LEN(UPDATE_BUF_SIZE) + WSHLENMAX]; /* base64 + maximum frame header length */ + char decodeBuf[8192]; /* TODO: what makes sense? */ + char readbuf[8192]; + int readbufstart; + int readbuflen; + int dblen; + char carryBuf[3]; /* For base64 carry-over */ + int carrylen; + int version; +} ws_ctx_t; + +typedef union ws_mask_s { + char c[4]; + uint32_t u; +} ws_mask_t; + +typedef struct __attribute__ ((__packed__)) ws_header_s { + unsigned char b0; + unsigned char b1; + union { + struct __attribute__ ((__packed__)) { + uint16_t l16; + ws_mask_t m16; + }; + struct __attribute__ ((__packed__)) { + uint64_t l64; + ws_mask_t m64; + }; + ws_mask_t m; + }; +} 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 "\n" #define SZ_FLASH_POLICY_RESPONSE 93 -#define WEBSOCKETS_HANDSHAKE_RESPONSE "HTTP/1.1 101 Web Socket Protocol Handshake\r\n\ +/* + * 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\ @@ -45,6 +123,14 @@ Connection: Upgrade\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\ +Sec-WebSocket-Accept: %s\r\n\ +Sec-WebSocket-Protocol: %s\r\n\ +\r\n" + + #define WEBSOCKETS_CLIENT_CONNECT_WAIT_MS 100 #define WEBSOCKETS_CLIENT_SEND_WAIT_MS 100 #define WEBSOCKETS_MAX_HANDSHAKE_LEN 4096 @@ -65,6 +151,24 @@ min (int a, int b) { return a < b ? a : b; } +#ifdef LIBVNCSERVER_WITH_CLIENT_GCRYPT +#else +#include + +static void webSocketsGenSha1Key(char *target, int size, char *key) +{ + SHA_CTX c; + unsigned char tmp[SHA_DIGEST_LENGTH]; + + SHA1_Init(&c); + SHA1_Update(&c, key, strlen(key)); + SHA1_Update(&c, GUID, sizeof(GUID) - 1); + SHA1_Final(tmp, &c); + if (-1 == __b64_ntop(tmp, SHA_DIGEST_LENGTH, target, size)) + rfbErr("b64_ntop failed\n"); +} +#endif + /* * rfbWebSocketsHandshake is called to handle new WebSockets connections */ @@ -126,6 +230,9 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) char prefix[5], trailer[17]; char *path = NULL, *host = NULL, *origin = NULL, *protocol = NULL; char *key1 = NULL, *key2 = NULL, *key3 = NULL; + char *sec_ws_origin = NULL; + char *sec_ws_key = NULL; + char sec_ws_version = 0; buf = (char *) malloc(WEBSOCKETS_MAX_HANDSHAKE_LEN); if (!buf) { @@ -198,16 +305,28 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) key2 = line+20; buf[len-2] = '\0'; /* rfbLog("Got key2: %s\n", key2); */ - } else if ((strncasecmp("sec-websocket-protocol: ", line, min(llen,24))) == 0) { + /* HyBI */ + + } else if ((strncasecmp("sec-websocket-protocol: ", line, min(llen,24))) == 0) { protocol = line+24; buf[len-2] = '\0'; - /* rfbLog("Got protocol: %s\n", protocol); */ - } + rfbLog("Got protocol: %s\n", protocol); + } else if ((strncasecmp("sec-websocket-origin: ", line, min(llen,22))) == 0) { + 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; + buf[len-2] = '\0'; + } else if ((strncasecmp("sec-websocket-version: ", line, min(llen,23))) == 0) { + sec_ws_version = strtol(line+23, NULL, 10); + buf[len-2] = '\0'; + } + linestart = len; } } - if (!(path && host && origin)) { + if (!(path && host && (origin || sec_ws_origin))) { rfbErr("webSocketsHandshake: incomplete client handshake\n"); free(response); free(buf); @@ -228,27 +347,40 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) * by the client. */ - if (!(key1 && key2 && key3)) { - rfbLog(" - WebSockets client version 75\n"); - prefix[0] = '\0'; - trailer[0] = '\0'; + if (sec_ws_version) { + char accept[SHA_DIGEST_LENGTH * 3]; + rfbLog(" - WebSockets client version hybi-%02d\n", sec_ws_version); + webSocketsGenSha1Key(accept, sizeof(accept), sec_ws_key); + len = snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN, + SERVER_HANDSHAKE_HYBI, accept, protocol); } else { - rfbLog(" - WebSockets client version 76\n"); - snprintf(prefix, 5, "Sec-"); - webSocketsGenMd5(trailer, key1, key2, key3); + /* 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); } - snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN, - WEBSOCKETS_HANDSHAKE_RESPONSE, prefix, origin, prefix, scheme, - host, path, prefix, protocol, trailer); - - if (rfbWriteExact(cl, response, strlen(response)) < 0) { + if (rfbWriteExact(cl, response, len) < 0) { rfbErr("webSocketsHandshake: failed sending WebSockets response\n"); free(response); free(buf); return FALSE; } - /* rfbLog("webSocketsHandshake: handshake complete\n"); */ + rfbLog("webSocketsHandshake: %s\n", response); + free(response); + free(buf); + cl->wsctx = (wsCtx *)calloc(1, sizeof(ws_ctx_t)); + ((ws_ctx_t *)cl->wsctx)->version = sec_ws_version ? WEBSOCKETS_VERSION_HYBI : WEBSOCKETS_VERSION_HIXIE; return TRUE; } @@ -299,13 +431,15 @@ webSocketsGenMd5(char * target, char *key1, char *key2, char *key3) } int -webSocketsEncode(rfbClientPtr cl, const char *src, int len) +webSocketsEncodeHixie(rfbClientPtr cl, const char *src, int len, char **dst) { int i, sz = 0; unsigned char chr; - cl->encodeBuf[sz++] = '\x00'; + ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; + + wsctx->encodeBuf[sz++] = '\x00'; if (cl->webSocketsBase64) { - len = __b64_ntop((unsigned char *)src, len, cl->encodeBuf+sz, UPDATE_BUF_SIZE*2); + len = __b64_ntop((unsigned char *)src, len, wsctx->encodeBuf+sz, sizeof(wsctx->encodeBuf) - (sz + 1)); if (len < 0) { return len; } @@ -315,24 +449,24 @@ webSocketsEncode(rfbClientPtr cl, const char *src, int len) chr = src[i]; if (chr < 128) { if (chr == 0x00) { - cl->encodeBuf[sz++] = '\xc4'; - cl->encodeBuf[sz++] = '\x80'; + wsctx->encodeBuf[sz++] = '\xc4'; + wsctx->encodeBuf[sz++] = '\x80'; } else { - cl->encodeBuf[sz++] = chr; + wsctx->encodeBuf[sz++] = chr; } } else { if (chr < 192) { - cl->encodeBuf[sz++] = '\xc2'; - cl->encodeBuf[sz++] = chr; + wsctx->encodeBuf[sz++] = '\xc2'; + wsctx->encodeBuf[sz++] = chr; } else { - cl->encodeBuf[sz++] = '\xc3'; - cl->encodeBuf[sz++] = chr - 64; + wsctx->encodeBuf[sz++] = '\xc3'; + wsctx->encodeBuf[sz++] = chr - 64; } } } } - cl->encodeBuf[sz++] = '\xff'; - /* rfbLog("<< webSocketsEncode: %d\n", len); */ + wsctx->encodeBuf[sz++] = '\xff'; + *dst = wsctx->encodeBuf; return sz; } @@ -361,18 +495,19 @@ ws_peek(rfbClientPtr cl, char *buf, int len) } int -webSocketsDecode(rfbClientPtr cl, char *dst, int len) +webSocketsDecodeHixie(rfbClientPtr cl, char *dst, int len) { int retlen = 0, n, i, avail, modlen, needlen, actual; char *buf, *end = NULL; unsigned char chr, chr2; + ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; - buf = cl->decodeBuf; + buf = wsctx->decodeBuf; n = ws_peek(cl, buf, len*2+2); if (n <= 0) { - rfbErr("%s: recv of %d\n", __func__, n); + rfbErr("%s: peek of %d\n", __func__, n); return n; } @@ -406,7 +541,7 @@ webSocketsDecode(rfbClientPtr cl, char *dst, int len) } avail = end - buf; - len -= cl->carrylen; + len -= wsctx->carrylen; /* Determine how much base64 data we need */ modlen = len + (len+2)/3; @@ -422,9 +557,9 @@ webSocketsDecode(rfbClientPtr cl, char *dst, int len) } /* Any carryover from previous decode */ - for (i=0; i < cl->carrylen; i++) { - /* rfbLog("Adding carryover %d\n", cl->carryBuf[i]); */ - dst[i] = cl->carryBuf[i]; + for (i=0; i < wsctx->carrylen; i++) { + /* rfbLog("Adding carryover %d\n", wsctx->carryBuf[i]); */ + dst[i] = wsctx->carryBuf[i]; retlen += 1; } @@ -442,11 +577,11 @@ webSocketsDecode(rfbClientPtr cl, char *dst, int len) /* Consume the data from socket */ i = ws_read(cl, buf, needlen); - cl->carrylen = n - len; - retlen -= cl->carrylen; - for (i=0; i < cl->carrylen; i++) { + wsctx->carrylen = n - len; + retlen -= wsctx->carrylen; + for (i=0; i < wsctx->carrylen; i++) { /* rfbLog("Saving carryover %d\n", dst[retlen + i]); */ - cl->carryBuf[i] = dst[retlen + i]; + wsctx->carryBuf[i] = dst[retlen + i]; } } else { /* UTF-8 encoded WebSockets stream */ @@ -509,3 +644,216 @@ webSocketsDecode(rfbClientPtr cl, char *dst, int len) /* rfbLog("<< webSocketsDecode, retlen: %d\n", retlen); */ return retlen; } + +int +webSocketsDecodeHybi(rfbClientPtr cl, char *dst, int len) +{ + char *buf, *payload, *rbuf; + int ret = -1, result = -1; + int total = 0; + ws_mask_t mask; + ws_header_t *header; + int i, j; + unsigned char opcode; + ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; + int flength, fin, fhlen, blen; + + // 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->decodeBuf; + header = (ws_header_t *)wsctx->decodeBuf; + + if (-1 == (ret = ws_peek(cl, buf, B64LEN(len) + WSHLENMAX))) { + rfbErr("%s: peek; %m\n", __func__); + goto spor; + } + + if (ret < 2) { + rfbErr("%s: peek; got %d bytes\n", __func__, ret); + goto spor; /* Incomplete frame header */ + } + + opcode = header->b0 & 0x0f; + fin = (header->b0 & 0x80) >> 7; + 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->m; + } else if (flength == 126 && 4 <= ret) { + flength = WS_NTOH16(header->l16); + fhlen = 4; + mask = header->m16; + } else if (flength == 127 && 10 <= ret) { + flength = WS_NTOH64(header->l64); + fhlen = 10; + mask = header->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))) { + rfbErr("%s: read; %m", __func__); + return ret; + } else if (ret < total) { + /* TODO: hmm? */ + rfbLog("%s: read; got partial data\n", __func__); + } else { + buf[ret] = '\0'; + } + + /* process 1 frame */ + for (i = 0; i < flength; i++) { + j = i % 4; + payload[i] ^= mask.c[j]; + } + + 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->decodeBuf, sizeof(wsctx->decodeBuf)))) { + rfbErr("%s: Base64 decode error; %m\n", __func__); + break; + } + payload = wsctx->decodeBuf; + /* 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("unhandled opcode %d, b0: %02x, b1: %02x\n", (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; +} + +int +webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst) +{ + int blen, ret = -1, sz = 0; + unsigned char opcode = '\0'; /* TODO: option! */ + ws_header_t *header; + ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; + + + /* Optional opcode: + * 0x0 - continuation + * 0x1 - text frame (base64 encode buf) + * 0x2 - binary frame (use raw buf) + * 0x8 - connection close + * 0x9 - ping + * 0xA - pong + **/ + if (!len) { + rfbLog("%s: nothing to encode\n", __func__); + return 0; + } + + header = (ws_header_t *)wsctx->encodeBuf; + + if (cl->webSocketsBase64) { + opcode = WS_OPCODE_TEXT_FRAME; + /* calculate the resulting size */ + blen = B64LEN(len); + } else { + blen = len; + } + + header->b0 = 0x80 | (opcode & 0x0f); + if (blen <= 125) { + header->b1 = (uint8_t)blen; + sz = 2; + } else if (blen <= 65536) { + header->b1 = 0x7e; + header->l16 = WS_HTON16((uint16_t)blen); + sz = 4; + } else { + header->b1 = 0x7f; + header->l64 = WS_HTON64(blen); + sz = 10; + } + + if (cl->webSocketsBase64) { + if (-1 == (ret = __b64_ntop((unsigned char *)src, len, wsctx->encodeBuf + sz, sizeof(wsctx->encodeBuf) - 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->encodeBuf + sz, src, len); + ret = sz + len; + } + + *dst = wsctx->encodeBuf; + return ret; +} + +int +webSocketsEncode(rfbClientPtr cl, const char *src, int len, char **dst) +{ + ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; + if (wsctx->version == WEBSOCKETS_VERSION_HIXIE) + return webSocketsEncodeHixie(cl, src, len, dst); + else + return webSocketsEncodeHybi(cl, src, len, dst); +} + +int +webSocketsDecode(rfbClientPtr cl, char *dst, int len) +{ + ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; + if (wsctx->version == WEBSOCKETS_VERSION_HIXIE) + return webSocketsDecodeHixie(cl, dst, len); + else + return webSocketsDecodeHybi(cl, dst, len); +} diff --git a/rfb/rfb.h b/rfb/rfb.h index 9239b35..1f29e63 100644 --- a/rfb/rfb.h +++ b/rfb/rfb.h @@ -419,6 +419,7 @@ typedef struct _rfbStatList { } rfbStatList; typedef struct _rfbSslCtx rfbSslCtx; +typedef struct _wsCtx wsCtx; typedef struct _rfbClientRec { @@ -640,17 +641,10 @@ typedef struct _rfbClientRec { #ifdef LIBVNCSERVER_WITH_WEBSOCKETS rfbBool webSockets; - rfbBool webSocketsSSL; rfbBool webSocketsBase64; - rfbSslCtx *sslctx; - + wsCtx *wsctx; char *wspath; /* Requests path component */ - char encodeBuf[UPDATE_BUF_SIZE*2 + 2]; /* UTF-8 could double it + framing */ - char decodeBuf[8192]; /* TODO: what makes sense? */ - int dblen; - char carryBuf[3]; /* For base64 carry-over */ - int carrylen; #endif } rfbClientRec, *rfbClientPtr; @@ -718,7 +712,7 @@ extern rfbBool rfbSetNonBlocking(int sock); /* websockets.c */ extern rfbBool webSocketsCheck(rfbClientPtr cl); -extern int webSocketsEncode(rfbClientPtr cl, const char *src, int len); +extern int webSocketsEncode(rfbClientPtr cl, const char *src, int len, char **dst); extern int webSocketsDecode(rfbClientPtr cl, char *dst, int len); #endif -- cgit v1.2.3 From 55234a37fd0f865261c09b602b94444d42f35daa Mon Sep 17 00:00:00 2001 From: Gernot Tenchio Date: Thu, 25 Aug 2011 12:12:17 +0200 Subject: websockets: Move Hixie disconnect hack to websockets.c Move the hixie disconnect hack to websockets.c. Removed the remaining websockets vars from rfbClientPtr, so all websockets stuff is hidden behind an opaque pointer. --- libvncserver/rfbserver.c | 67 ++----------------------------------- libvncserver/sockets.c | 4 +-- libvncserver/websockets.c | 84 ++++++++++++++++++++++++++++++++++++++++++----- rfb/rfb.h | 3 +- 4 files changed, 80 insertions(+), 78 deletions(-) (limited to 'rfb') diff --git a/libvncserver/rfbserver.c b/libvncserver/rfbserver.c index d6a5da4..63f21db 100644 --- a/libvncserver/rfbserver.c +++ b/libvncserver/rfbserver.c @@ -362,11 +362,6 @@ rfbNewTCPOrUDPClient(rfbScreenInfoPtr rfbScreen, rfbScreen->clientHead = cl; UNLOCK(rfbClientListMutex); -#ifdef LIBVNCSERVER_WITH_WEBSOCKETS - cl->webSockets = FALSE; - cl->webSocketsBase64 = FALSE; -#endif - #if defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG) cl->tightQualityLevel = -1; #if defined(LIBVNCSERVER_HAVE_LIBJPEG) || defined(LIBVNCSERVER_HAVE_LIBPNG) @@ -1841,66 +1836,8 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) char encBuf2[64]; #ifdef LIBVNCSERVER_WITH_WEBSOCKETS - if (cl->webSockets) { - if (cl->sslctx) - n = rfbssl_peek(cl, encBuf, 4); - else - n = recv(cl->sock, encBuf, 4, MSG_PEEK); - - if (n <= 0) { - if (n != 0) - rfbLogPerror("rfbProcessClientNormalMessage: peek"); - rfbCloseClient(cl); - return; - } - - if (cl->webSocketsBase64) { - /* With Base64 encoding we need at least 4 bytes */ - if ((n > 0) && (n < 4)) { - if (encBuf[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 (encBuf[1] == '\xff' && encBuf[2] == '\x00') - doclose = 1; - break; - case 2: - if (encBuf[1] == '\x00') - doclose = 1; - break; - default: - ; - } - - if (cl->sslctx) - n = rfbssl_read(cl, encBuf, n); - else - n = read(cl->sock, encBuf, n); - - if (doclose) { - rfbErr("rfbProcessClientNormalMessage: websocket close frame received\n"); - rfbCloseClient(cl); - } - return; - } - } - } else { - /* With UTF-8 encoding we need at least 3 bytes (framing + 1) */ - if ((n == 1) || (n == 2)) { - if (encBuf[0] == '\xff') { - /* Make sure we don't miss a client disconnect on an end frame - * marker */ - if (cl->sslctx) - n = rfbssl_read(cl, encBuf, 1); - else - n = read(cl->sock, encBuf, 1); - } - } - } - } + if (cl->wsctx && webSocketCheckDisconnect(cl)) + return; #endif if ((n = rfbReadExact(cl, (char *)&msg, 1)) <= 0) { diff --git a/libvncserver/sockets.c b/libvncserver/sockets.c index b3d5b59..415f712 100644 --- a/libvncserver/sockets.c +++ b/libvncserver/sockets.c @@ -467,7 +467,7 @@ rfbReadExactTimeout(rfbClientPtr cl, char* buf, int len, int timeout) while (len > 0) { #ifdef LIBVNCSERVER_WITH_WEBSOCKETS - if (cl->webSockets) { + if (cl->wsctx) { n = webSocketsDecode(cl, buf, len); } else if (cl->sslctx) { n = rfbssl_read(cl, buf, len); @@ -646,7 +646,7 @@ rfbWriteExact(rfbClientPtr cl, #endif #ifdef LIBVNCSERVER_WITH_WEBSOCKETS - if (cl->webSockets) { + if (cl->wsctx) { char *tmp = NULL; if ((len = webSocketsEncode(cl, buf, len, &tmp)) < 0) { rfbErr("WriteExact: WebSockets encode error\n"); diff --git a/libvncserver/websockets.c b/libvncserver/websockets.c index e3b47e3..88b76a5 100755 --- a/libvncserver/websockets.c +++ b/libvncserver/websockets.c @@ -73,6 +73,7 @@ typedef struct ws_ctx_s { char carryBuf[3]; /* For base64 carry-over */ int carrylen; int version; + int base64; } ws_ctx_t; typedef union ws_mask_s { @@ -218,7 +219,7 @@ webSocketsCheck (rfbClientPtr cl) if (!webSocketsHandshake(cl, scheme)) { return FALSE; } - cl->webSockets = TRUE; /* Start WebSockets framing */ + /* Start WebSockets framing */ return TRUE; } @@ -226,7 +227,7 @@ static rfbBool webSocketsHandshake(rfbClientPtr cl, char *scheme) { char *buf, *response, *line; - int n, linestart = 0, len = 0, llen; + int n, linestart = 0, len = 0, llen, base64 = 0; char prefix[5], trailer[17]; char *path = NULL, *host = NULL, *origin = NULL, *protocol = NULL; char *key1 = NULL, *key2 = NULL, *key3 = NULL; @@ -286,7 +287,7 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) /* 16 = 4 ("GET ") + 1 ("/.*") + 11 (" HTTP/1.1\r\n") */ path = line+4; buf[len-11] = '\0'; /* Trim trailing " HTTP/1.1\r\n" */ - cl->webSocketsBase64 = TRUE; + base64 = TRUE; cl->wspath = strdup(path); /* rfbLog("Got path: %s\n", path); */ } else if ((strncasecmp("host: ", line, min(llen,6))) == 0) { @@ -381,6 +382,7 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) free(buf); cl->wsctx = (wsCtx *)calloc(1, sizeof(ws_ctx_t)); ((ws_ctx_t *)cl->wsctx)->version = sec_ws_version ? WEBSOCKETS_VERSION_HYBI : WEBSOCKETS_VERSION_HIXIE; + ((ws_ctx_t *)cl->wsctx)->base64 = base64; return TRUE; } @@ -438,7 +440,7 @@ webSocketsEncodeHixie(rfbClientPtr cl, const char *src, int len, char **dst) ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; wsctx->encodeBuf[sz++] = '\x00'; - if (cl->webSocketsBase64) { + if (wsctx->base64) { len = __b64_ntop((unsigned char *)src, len, wsctx->encodeBuf+sz, sizeof(wsctx->encodeBuf) - (sz + 1)); if (len < 0) { return len; @@ -489,7 +491,10 @@ ws_peek(rfbClientPtr cl, char *buf, int len) if (cl->sslctx) { n = rfbssl_peek(cl, buf, len); } else { - n = recv(cl->sock, buf, len, MSG_PEEK); + while (-1 == (n = recv(cl->sock, buf, len, MSG_PEEK))) { + if (errno != EAGAIN) + break; + } } return n; } @@ -507,12 +512,12 @@ webSocketsDecodeHixie(rfbClientPtr cl, char *dst, int len) n = ws_peek(cl, buf, len*2+2); if (n <= 0) { - rfbErr("%s: peek of %d\n", __func__, n); + rfbErr("%s: peek (%d) %m\n", __func__, errno); return n; } - if (cl->webSocketsBase64) { + if (wsctx->base64) { /* Base64 encoded WebSockets stream */ if (buf[0] == '\xff') { @@ -799,7 +804,7 @@ webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst) header = (ws_header_t *)wsctx->encodeBuf; - if (cl->webSocketsBase64) { + if (wsctx->base64) { opcode = WS_OPCODE_TEXT_FRAME; /* calculate the resulting size */ blen = B64LEN(len); @@ -821,7 +826,7 @@ webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst) sz = 10; } - if (cl->webSocketsBase64) { + if (wsctx->base64) { if (-1 == (ret = __b64_ntop((unsigned char *)src, len, wsctx->encodeBuf + sz, sizeof(wsctx->encodeBuf) - sz))) { rfbErr("%s: Base 64 encode failed\n", __func__); } else { @@ -857,3 +862,64 @@ webSocketsDecode(rfbClientPtr cl, char *dst, int len) else return webSocketsDecodeHybi(cl, dst, len); } + +/* returns TRUE if client sent an close frame or a single end of marker + * was received, FALSE otherwise + * + * Note: This is a Hixie-only hack! + **/ +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: + ; + } + + 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; +} + diff --git a/rfb/rfb.h b/rfb/rfb.h index 1f29e63..11d1447 100644 --- a/rfb/rfb.h +++ b/rfb/rfb.h @@ -640,8 +640,6 @@ typedef struct _rfbClientRec { #endif #ifdef LIBVNCSERVER_WITH_WEBSOCKETS - rfbBool webSockets; - rfbBool webSocketsBase64; rfbSslCtx *sslctx; wsCtx *wsctx; char *wspath; /* Requests path component */ @@ -712,6 +710,7 @@ extern rfbBool rfbSetNonBlocking(int sock); /* websockets.c */ extern rfbBool webSocketsCheck(rfbClientPtr cl); +extern rfbBool webSocketCheckDisconnect(rfbClientPtr cl); extern int webSocketsEncode(rfbClientPtr cl, const char *src, int len, char **dst); extern int webSocketsDecode(rfbClientPtr cl, char *dst, int len); #endif -- cgit v1.2.3