diff options
author | Christian Beier <dontmind@freeshell.org> | 2011-10-04 17:30:36 +0200 |
---|---|---|
committer | Christian Beier <dontmind@freeshell.org> | 2011-10-04 17:30:36 +0200 |
commit | bffd9ee33bd141f5c75304b1430d2ea2725239b3 (patch) | |
tree | 0fd8ce48bd28965b623bc03c389847d11a55f3e8 /libvncserver | |
parent | 629fe03d617b2a968d30d7ed85c8f8b1e5d6a410 (diff) | |
parent | abec0aa8c3e226682c684c9d8a0dab889815f511 (diff) | |
download | libtdevnc-bffd9ee33bd141f5c75304b1430d2ea2725239b3.tar.gz libtdevnc-bffd9ee33bd141f5c75304b1430d2ea2725239b3.zip |
Merge branch 'websockets'
Diffstat (limited to 'libvncserver')
-rw-r--r-- | libvncserver/Makefile.am | 28 | ||||
-rw-r--r-- | libvncserver/cargs.c | 14 | ||||
-rw-r--r-- | libvncserver/rfbcrypto.h | 12 | ||||
-rw-r--r-- | libvncserver/rfbcrypto_gnutls.c | 50 | ||||
-rw-r--r-- | libvncserver/rfbcrypto_included.c | 49 | ||||
-rw-r--r-- | libvncserver/rfbcrypto_openssl.c | 49 | ||||
-rw-r--r-- | libvncserver/rfbcrypto_polarssl.c | 26 | ||||
-rw-r--r-- | libvncserver/rfbserver.c | 71 | ||||
-rw-r--r-- | libvncserver/rfbssl.h | 15 | ||||
-rw-r--r-- | libvncserver/rfbssl_gnutls.c | 268 | ||||
-rw-r--r-- | libvncserver/rfbssl_none.c | 58 | ||||
-rw-r--r-- | libvncserver/rfbssl_openssl.c | 135 | ||||
-rw-r--r-- | libvncserver/sockets.c | 120 | ||||
-rw-r--r-- | libvncserver/stats.c | 1 | ||||
-rw-r--r-- | libvncserver/tight.c | 327 | ||||
-rw-r--r-- | libvncserver/websockets.c | 880 |
16 files changed, 2053 insertions, 50 deletions
diff --git a/libvncserver/Makefile.am b/libvncserver/Makefile.am index 538617f..287f1c9 100644 --- a/libvncserver/Makefile.am +++ b/libvncserver/Makefile.am @@ -12,6 +12,23 @@ TIGHTVNCFILETRANSFERSRCS = tightvnc-filetransfer/rfbtightserver.c \ tightvnc-filetransfer/filelistinfo.c endif +if WITH_WEBSOCKETS + +if HAVE_LIBSSL +WEBSOCKETSSSLSRCS = rfbssl_openssl.c rfbcrypto_openssl.c +WEBSOCKETSSSLLIBS = @SSL_LIBS@ @CRYPT_LIBS@ +else +if HAVE_GNUTLS +WEBSOCKETSSSLSRCS = rfbssl_gnutls.c rfbcrypto_gnutls.c +WEBSOCKETSSSLLIBS = @GNUTLS_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.h rfbconfig.h rfbint.h rfbproto.h keysym.h rfbregion.h @@ -29,17 +46,22 @@ EXTRA_DIST=tableinit24.c tableinittctemplate.c tabletranstemplate.c \ if HAVE_LIBZ ZLIBSRCS = zlib.c zrle.c zrleoutstream.c zrlepalettehelper.c ../common/zywrletemplate.c if HAVE_LIBJPEG -JPEGSRCS = tight.c +TIGHTSRCS = tight.c +else +if HAVE_LIBPNG +TIGHTSRCS = tight.c +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 \ - $(ZLIBSRCS) $(JPEGSRCS) $(TIGHTVNCFILETRANSFERSRCS) + $(ZLIBSRCS) $(TIGHTSRCS) $(TIGHTVNCFILETRANSFERSRCS) libvncserver_la_SOURCES=$(LIB_SRCS) +libvncserver_la_LIBADD=$(WEBSOCKETSSSLLIBS) lib_LTLIBRARIES=libvncserver.la diff --git a/libvncserver/cargs.c b/libvncserver/cargs.c index 332ffa1..2e973e8 100644 --- a/libvncserver/cargs.c +++ b/libvncserver/cargs.c @@ -163,6 +163,20 @@ rfbProcessArguments(rfbScreenInfoPtr rfbScreen,int* argc, char *argv[]) if (! rfbStringToAddr(argv[++i], &(rfbScreen->listenInterface))) { return FALSE; } +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS + } else if (strcmp(argv[i], "-sslkeyfile") == 0) { /* -sslkeyfile sslkeyfile */ + if (i + 1 >= *argc) { + rfbUsage(); + return FALSE; + } + rfbScreen->sslkeyfile = argv[++i]; + } else if (strcmp(argv[i], "-sslcertfile") == 0) { /* -sslcertfile sslcertfile */ + if (i + 1 >= *argc) { + rfbUsage(); + return FALSE; + } + rfbScreen->sslcertfile = argv[++i]; +#endif } else { rfbProtocolExtension* extension; int handled=0; diff --git a/libvncserver/rfbcrypto.h b/libvncserver/rfbcrypto.h new file mode 100644 index 0000000..9dc3e63 --- /dev/null +++ b/libvncserver/rfbcrypto.h @@ -0,0 +1,12 @@ +#ifndef _RFB_CRYPTO_H +#define _RFB_CRYPTO_H 1 + +#include <sys/uio.h> + +#define SHA1_HASH_SIZE 20 +#define MD5_HASH_SIZE 16 + +void digestmd5(const struct iovec *iov, int iovcnt, void *dest); +void digestsha1(const struct iovec *iov, int iovcnt, void *dest); + +#endif diff --git a/libvncserver/rfbcrypto_gnutls.c b/libvncserver/rfbcrypto_gnutls.c new file mode 100644 index 0000000..2ecb2da --- /dev/null +++ b/libvncserver/rfbcrypto_gnutls.c @@ -0,0 +1,50 @@ +/* + * rfbcrypto_gnutls.c - Crypto wrapper (gnutls version) + */ + +/* + * Copyright (C) 2011 Gernot Tenchio + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include <string.h> +#include <gcrypt.h> +#include "rfbcrypto.h" + +void digestmd5(const struct iovec *iov, int iovcnt, void *dest) +{ + gcry_md_hd_t c; + int i; + + gcry_md_open(&c, GCRY_MD_MD5, 0); + for (i = 0; i < iovcnt; i++) + gcry_md_write(c, iov[i].iov_base, iov[i].iov_len); + gcry_md_final(c); + memcpy(dest, gcry_md_read(c, 0), gcry_md_get_algo_dlen(GCRY_MD_MD5)); +} + +void digestsha1(const struct iovec *iov, int iovcnt, void *dest) +{ + gcry_md_hd_t c; + int i; + + gcry_md_open(&c, GCRY_MD_SHA1, 0); + for (i = 0; i < iovcnt; i++) + gcry_md_write(c, iov[i].iov_base, iov[i].iov_len); + gcry_md_final(c); + memcpy(dest, gcry_md_read(c, 0), gcry_md_get_algo_dlen(GCRY_MD_SHA1)); +} diff --git a/libvncserver/rfbcrypto_included.c b/libvncserver/rfbcrypto_included.c new file mode 100644 index 0000000..58c2e93 --- /dev/null +++ b/libvncserver/rfbcrypto_included.c @@ -0,0 +1,49 @@ +/* + * rfbcrypto_included.c - Crypto wrapper (included version) + */ + +/* + * Copyright (C) 2011 Gernot Tenchio + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include <string.h> +#include "md5.h" +#include "sha1.h" +#include "rfbcrypto.h" + +void digestmd5(const struct iovec *iov, int iovcnt, void *dest) +{ + struct md5_ctx c; + int i; + + __md5_init_ctx(&c); + for (i = 0; i < iovcnt; i++) + __md5_process_bytes(iov[i].iov_base, iov[i].iov_len, &c); + __md5_finish_ctx(&c, dest); +} + +void digestsha1(const struct iovec *iov, int iovcnt, void *dest) +{ + SHA1Context c; + int i; + + SHA1Reset(&c); + for (i = 0; i < iovcnt; i++) + SHA1Input(&c, iov[i].iov_base, iov[i].iov_len); + SHA1Result(&c, dest); +} diff --git a/libvncserver/rfbcrypto_openssl.c b/libvncserver/rfbcrypto_openssl.c new file mode 100644 index 0000000..29ec5c1 --- /dev/null +++ b/libvncserver/rfbcrypto_openssl.c @@ -0,0 +1,49 @@ +/* + * rfbcrypto_openssl.c - Crypto wrapper (openssl version) + */ + +/* + * Copyright (C) 2011 Gernot Tenchio + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include <string.h> +#include <openssl/sha.h> +#include <openssl/md5.h> +#include "rfbcrypto.h" + +void digestmd5(const struct iovec *iov, int iovcnt, void *dest) +{ + MD5_CTX c; + int i; + + MD5_Init(&c); + for (i = 0; i < iovcnt; i++) + MD5_Update(&c, iov[i].iov_base, iov[i].iov_len); + MD5_Final(dest, &c); +} + +void digestsha1(const struct iovec *iov, int iovcnt, void *dest) +{ + SHA_CTX c; + int i; + + SHA1_Init(&c); + for (i = 0; i < iovcnt; i++) + SHA1_Update(&c, iov[i].iov_base, iov[i].iov_len); + SHA1_Final(dest, &c); +} diff --git a/libvncserver/rfbcrypto_polarssl.c b/libvncserver/rfbcrypto_polarssl.c new file mode 100644 index 0000000..55e3a7b --- /dev/null +++ b/libvncserver/rfbcrypto_polarssl.c @@ -0,0 +1,26 @@ +#include <string.h> +#include <polarssl/md5.h> +#include <polarssl/sha1.h> +#include "rfbcrypto.h" + +void digestmd5(const struct iovec *iov, int iovcnt, void *dest) +{ + md5_context c; + int i; + + md5_starts(&c); + for (i = 0; i < iovcnt; i++) + md5_update(&c, iov[i].iov_base, iov[i].iov_len); + md5_finish(&c, dest); +} + +void digestsha1(const struct iovec *iov, int iovcnt, void *dest) +{ + sha1_context c; + int i; + + sha1_starts(&c); + for (i = 0; i < iovcnt; i++) + sha1_update(&c, iov[i].iov_base, iov[i].iov_len); + sha1_finish(&c, dest); +} diff --git a/libvncserver/rfbserver.c b/libvncserver/rfbserver.c index 8f0e390..63f21db 100644 --- a/libvncserver/rfbserver.c +++ b/libvncserver/rfbserver.c @@ -73,6 +73,10 @@ /* strftime() */ #include <time.h> +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS +#include "rfbssl.h" +#endif + #ifdef __MINGW32__ static int compat_mkdir(const char *path, int mode) { @@ -358,10 +362,12 @@ rfbNewTCPOrUDPClient(rfbScreenInfoPtr rfbScreen, rfbScreen->clientHead = cl; UNLOCK(rfbClientListMutex); -#ifdef LIBVNCSERVER_HAVE_LIBZ +#if defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG) cl->tightQualityLevel = -1; -#ifdef LIBVNCSERVER_HAVE_LIBJPEG +#if defined(LIBVNCSERVER_HAVE_LIBJPEG) || defined(LIBVNCSERVER_HAVE_LIBPNG) cl->tightCompressLevel = TIGHT_DEFAULT_COMPRESSION; +#endif +#ifdef LIBVNCSERVER_HAVE_LIBJPEG { int i; for (i = 0; i < 4; i++) @@ -402,6 +408,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); @@ -918,6 +938,9 @@ rfbSendSupportedEncodings(rfbClientPtr cl) #ifdef LIBVNCSERVER_HAVE_LIBJPEG rfbEncodingTight, #endif +#ifdef LIBVNCSERVER_HAVE_LIBPNG + rfbEncodingTightPng, +#endif rfbEncodingUltra, rfbEncodingUltraZip, rfbEncodingXCursor, @@ -1812,6 +1835,11 @@ 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"); @@ -1938,6 +1966,9 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) case rfbEncodingTight: #endif #endif +#ifdef LIBVNCSERVER_HAVE_LIBPNG + case rfbEncodingTightPng: +#endif /* The first supported encoding is the 'preferred' encoding */ if (cl->preferredEncoding == -1) cl->preferredEncoding = enc; @@ -2026,11 +2057,11 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) } break; default: -#ifdef LIBVNCSERVER_HAVE_LIBZ +#if defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG) if ( enc >= (uint32_t)rfbEncodingCompressLevel0 && enc <= (uint32_t)rfbEncodingCompressLevel9 ) { cl->zlibCompressLevel = enc & 0x0F; -#ifdef LIBVNCSERVER_HAVE_LIBJPEG +#if defined(LIBVNCSERVER_HAVE_LIBJPEG) || defined(LIBVNCSERVER_HAVE_LIBPNG) cl->tightCompressLevel = enc & 0x0F; rfbLog("Using compression level %d for client %s\n", cl->tightCompressLevel, cl->host); @@ -2755,6 +2786,28 @@ rfbSendFramebufferUpdate(rfbClientPtr cl, sraRgnReleaseIterator(i); i=NULL; #endif #endif +#ifdef LIBVNCSERVER_HAVE_LIBPNG + } else if (cl->preferredEncoding == rfbEncodingTightPng) { + nUpdateRegionRects = 0; + + for(i = sraRgnGetIterator(updateRegion); sraRgnIteratorNext(i,&rect);){ + int x = rect.x1; + int y = rect.y1; + int w = rect.x2 - x; + int h = rect.y2 - y; + int n; + /* We need to count the number of rects in the scaled screen */ + if (cl->screen!=cl->scaledScreen) + rfbScaledCorrection(cl->screen, cl->scaledScreen, &x, &y, &w, &h, "rfbSendFramebufferUpdate"); + n = rfbNumCodedRectsTight(cl, x, y, w, h); + if (n == 0) { + nUpdateRegionRects = 0xFFFF; + break; + } + nUpdateRegionRects += n; + } + sraRgnReleaseIterator(i); i=NULL; +#endif } else { nUpdateRegionRects = sraRgnCountRects(updateRegion); } @@ -2774,6 +2827,10 @@ rfbSendFramebufferUpdate(rfbClientPtr cl, && cl->preferredEncoding != rfbEncodingTight #endif #endif +#ifdef LIBVNCSERVER_HAVE_LIBPNG + /* Tight encoding counts the rectangles differently */ + && cl->preferredEncoding != rfbEncodingTightPng +#endif && nUpdateRegionRects>cl->screen->maxRectsPerUpdate) { sraRegion* newUpdateRegion = sraRgnBBox(updateRegion); sraRgnDestroy(updateRegion); @@ -2868,6 +2925,12 @@ rfbSendFramebufferUpdate(rfbClientPtr cl, break; #endif #endif +#ifdef LIBVNCSERVER_HAVE_LIBPNG + case rfbEncodingTightPng: + if (!rfbSendRectEncodingTightPng(cl, x, y, w, h)) + goto updateFailed; + break; +#endif #ifdef LIBVNCSERVER_HAVE_LIBZ case rfbEncodingZRLE: case rfbEncodingZYWRLE: 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_gnutls.c b/libvncserver/rfbssl_gnutls.c new file mode 100644 index 0000000..cf60cdc --- /dev/null +++ b/libvncserver/rfbssl_gnutls.c @@ -0,0 +1,268 @@ +/* + * rfbssl_gnutls.c - Secure socket funtions (gnutls version) + */ + +/* + * Copyright (C) 2011 Gernot Tenchio + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include "rfbssl.h" +#include <gnutls/gnutls.h> +#include <errno.h> + +struct rfbssl_ctx { + char peekbuf[2048]; + int peeklen; + int peekstart; + gnutls_session_t session; + gnutls_certificate_credentials_t x509_cred; + gnutls_dh_params_t dh_params; +#ifdef I_LIKE_RSA_PARAMS_THAT_MUCH + gnutls_rsa_params_t rsa_params; +#endif +}; + +void rfbssl_log_func(int level, const char *msg) +{ + rfbErr("SSL: %s", msg); +} + +static void rfbssl_error(const char *msg, int e) +{ + rfbErr("%s: %s (%ld)\n", msg, gnutls_strerror(e), e); +} + +static int rfbssl_init_session(struct rfbssl_ctx *ctx, int fd) +{ + gnutls_session_t session; + int ret; + + if (!GNUTLS_E_SUCCESS == (ret = gnutls_init(&session, GNUTLS_SERVER))) { + /* */ + } else if (!GNUTLS_E_SUCCESS == (ret = gnutls_priority_set_direct(session, "EXPORT", NULL))) { + /* */ + } else if (!GNUTLS_E_SUCCESS == (ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, ctx->x509_cred))) { + /* */ + } else { + gnutls_session_enable_compatibility_mode(session); + gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t)(uintptr_t)fd); + ctx->session = session; + } + return ret; +} + +static int generate_dh_params(struct rfbssl_ctx *ctx) +{ + int ret; + if (GNUTLS_E_SUCCESS == (ret = gnutls_dh_params_init(&ctx->dh_params))) + ret = gnutls_dh_params_generate2(ctx->dh_params, 1024); + return ret; +} + +#ifdef I_LIKE_RSA_PARAMS_THAT_MUCH +static int generate_rsa_params(struct rfbssl_ctx *ctx) +{ + int ret; + if (GNUTLS_E_SUCCESS == (ret = gnutls_rsa_params_init(&ctx->rsa_params))) + ret = gnutls_rsa_params_generate2(ctx->rsa_params, 512); + return ret; +} +#endif + +struct rfbssl_ctx *rfbssl_init_global(char *key, char *cert) +{ + int ret = GNUTLS_E_SUCCESS; + struct rfbssl_ctx *ctx = NULL; + + if (NULL == (ctx = malloc(sizeof(struct rfbssl_ctx)))) { + ret = GNUTLS_E_MEMORY_ERROR; + } else if (!GNUTLS_E_SUCCESS == (ret = gnutls_global_init())) { + /* */ + } else if (!GNUTLS_E_SUCCESS == (ret = gnutls_certificate_allocate_credentials(&ctx->x509_cred))) { + /* */ + } else if ((ret = gnutls_certificate_set_x509_trust_file(ctx->x509_cred, cert, GNUTLS_X509_FMT_PEM)) < 0) { + /* */ + } else if (!GNUTLS_E_SUCCESS == (ret = gnutls_certificate_set_x509_key_file(ctx->x509_cred, cert, key, GNUTLS_X509_FMT_PEM))) { + /* */ + } else if (!GNUTLS_E_SUCCESS == (ret = generate_dh_params(ctx))) { + /* */ +#ifdef I_LIKE_RSA_PARAMS_THAT_MUCH + } else if (!GNUTLS_E_SUCCESS == (ret = generate_rsa_params(ctx))) { + /* */ +#endif + } else { + gnutls_global_set_log_function(rfbssl_log_func); + gnutls_global_set_log_level(1); + gnutls_certificate_set_dh_params(ctx->x509_cred, ctx->dh_params); + return ctx; + } + + free(ctx); + return NULL; +} + +int rfbssl_init(rfbClientPtr cl) +{ + int ret = -1; + struct rfbssl_ctx *ctx; + char *keyfile; + if (!(keyfile = cl->screen->sslkeyfile)) + keyfile = cl->screen->sslcertfile; + + if (NULL == (ctx = rfbssl_init_global(keyfile, cl->screen->sslcertfile))) { + /* */ + } else if (GNUTLS_E_SUCCESS != (ret = rfbssl_init_session(ctx, cl->sock))) { + /* */ + } else { + while (GNUTLS_E_SUCCESS != (ret = gnutls_handshake(ctx->session))) { + if (ret == GNUTLS_E_AGAIN) + continue; + break; + } + } + + if (ret != GNUTLS_E_SUCCESS) { + rfbssl_error(__func__, ret); + } else { + cl->sslctx = (rfbSslCtx *)ctx; + rfbLog("%s protocol initialized\n", gnutls_protocol_get_name(gnutls_protocol_get_version(ctx->session))); + } + return ret; +} + +static int rfbssl_do_read(rfbClientPtr cl, char *buf, int bufsize) +{ + struct rfbssl_ctx *ctx = (struct rfbssl_ctx *)cl->sslctx; + int ret; + + while ((ret = gnutls_record_recv(ctx->session, buf, bufsize)) < 0) { + if (ret == GNUTLS_E_AGAIN) { + /* continue */ + } else if (ret == GNUTLS_E_INTERRUPTED) { + /* continue */ + } else { + break; + } + } + + if (ret < 0) { + rfbssl_error(__func__, ret); + errno = EIO; + ret = -1; + } + + return ret < 0 ? -1 : ret; +} + +int rfbssl_write(rfbClientPtr cl, const char *buf, int bufsize) +{ + struct rfbssl_ctx *ctx = (struct rfbssl_ctx *)cl->sslctx; + int ret; + + while ((ret = gnutls_record_send(ctx->session, buf, bufsize)) < 0) { + if (ret == GNUTLS_E_AGAIN) { + /* continue */ + } else if (ret == GNUTLS_E_INTERRUPTED) { + /* continue */ + } else { + break; + } + } + + if (ret < 0) + rfbssl_error(__func__, ret); + + return ret; +} + +static void rfbssl_gc_peekbuf(struct rfbssl_ctx *ctx, int bufsize) +{ + if (ctx->peekstart) { + int spaceleft = sizeof(ctx->peekbuf) - ctx->peeklen - ctx->peekstart; + if (spaceleft < bufsize) { + memmove(ctx->peekbuf, ctx->peekbuf + ctx->peekstart, ctx->peeklen); + ctx->peekstart = 0; + } + } +} + +static int __rfbssl_read(rfbClientPtr cl, char *buf, int bufsize, int peek) +{ + int ret = 0; + struct rfbssl_ctx *ctx = (struct rfbssl_ctx *)cl->sslctx; + + rfbssl_gc_peekbuf(ctx, bufsize); + + if (ctx->peeklen) { + /* If we have any peek data, simply return that. */ + ret = bufsize < ctx->peeklen ? bufsize : ctx->peeklen; + memcpy (buf, ctx->peekbuf + ctx->peekstart, ret); + if (!peek) { + ctx->peeklen -= ret; + if (ctx->peeklen != 0) + ctx->peekstart += ret; + else + ctx->peekstart = 0; + } + } + + if (ret < bufsize) { + int n; + /* read the remaining data */ + if ((n = rfbssl_do_read(cl, buf + ret, bufsize - ret)) <= 0) { + rfbErr("rfbssl_%s: %s error\n", __func__, peek ? "peek" : "read"); + return n; + } + if (peek) { + memcpy(ctx->peekbuf + ctx->peekstart + ctx->peeklen, buf + ret, n); + ctx->peeklen += n; + } + ret += n; + } + + return ret; +} + +int rfbssl_read(rfbClientPtr cl, char *buf, int bufsize) +{ + return __rfbssl_read(cl, buf, bufsize, 0); +} + +int rfbssl_peek(rfbClientPtr cl, char *buf, int bufsize) +{ + return __rfbssl_read(cl, buf, bufsize, 1); +} + +int rfbssl_pending(rfbClientPtr cl) +{ + struct rfbssl_ctx *ctx = (struct rfbssl_ctx *)cl->sslctx; + int ret = ctx->peeklen; + + if (ret <= 0) + ret = gnutls_record_check_pending(ctx->session); + + return ret; +} + +void rfbssl_destroy(rfbClientPtr cl) +{ + struct rfbssl_ctx *ctx = (struct rfbssl_ctx *)cl->sslctx; + gnutls_bye(ctx->session, GNUTLS_SHUT_WR); + gnutls_deinit(ctx->session); + gnutls_certificate_free_credentials(ctx->x509_cred); +} 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/rfbssl_openssl.c b/libvncserver/rfbssl_openssl.c new file mode 100644 index 0000000..cbd6865 --- /dev/null +++ b/libvncserver/rfbssl_openssl.c @@ -0,0 +1,135 @@ +/* + * rfbssl_openssl.c - Secure socket funtions (openssl version) + */ + +/* + * Copyright (C) 2011 Gernot Tenchio + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include "rfbssl.h" +#include <openssl/ssl.h> +#include <openssl/err.h> + +struct rfbssl_ctx { + SSL_CTX *ssl_ctx; + SSL *ssl; +}; + +static void rfbssl_error(void) +{ + char buf[1024]; + unsigned long e = ERR_get_error(); + rfbErr("%s (%ld)\n", ERR_error_string(e, buf), e); +} + +int rfbssl_init(rfbClientPtr cl) +{ + char *keyfile; + int r, ret = -1; + struct rfbssl_ctx *ctx; + + SSL_library_init(); + SSL_load_error_strings(); + + if (cl->screen->sslkeyfile && *cl->screen->sslkeyfile) { + keyfile = cl->screen->sslkeyfile; + } else { + keyfile = cl->screen->sslcertfile; + } + + if (NULL == (ctx = malloc(sizeof(struct rfbssl_ctx)))) { + rfbErr("OOM\n"); + } else if (!cl->screen->sslcertfile || !cl->screen->sslcertfile[0]) { + rfbErr("SSL connection but no cert specified\n"); + } else if (NULL == (ctx->ssl_ctx = SSL_CTX_new(TLSv1_server_method()))) { + rfbssl_error(); + } else if (SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, keyfile, SSL_FILETYPE_PEM) <= 0) { + rfbErr("Unable to load private key file %s\n", keyfile); + } else if (SSL_CTX_use_certificate_file(ctx->ssl_ctx, cl->screen->sslcertfile, SSL_FILETYPE_PEM) <= 0) { + rfbErr("Unable to load certificate file %s\n", cl->screen->sslcertfile); + } else if (NULL == (ctx->ssl = SSL_new(ctx->ssl_ctx))) { + rfbErr("SSL_new failed\n"); + rfbssl_error(); + } else if (!(SSL_set_fd(ctx->ssl, cl->sock))) { + rfbErr("SSL_set_fd failed\n"); + rfbssl_error(); + } else { + while ((r = SSL_accept(ctx->ssl)) < 0) { + if (SSL_get_error(ctx->ssl, r) != SSL_ERROR_WANT_READ) + break; + } + if (r < 0) { + rfbErr("SSL_accept failed %d\n", SSL_get_error(ctx->ssl, r)); + } else { + cl->sslctx = (rfbSslCtx *)ctx; + ret = 0; + } + } + return ret; +} + +int rfbssl_write(rfbClientPtr cl, const char *buf, int bufsize) +{ + int ret; + struct rfbssl_ctx *ctx = (struct rfbssl_ctx *)cl->sslctx; + + while ((ret = SSL_write(ctx->ssl, buf, bufsize)) <= 0) { + if (SSL_get_error(ctx->ssl, ret) != SSL_ERROR_WANT_WRITE) + break; + } + return ret; +} + +int rfbssl_peek(rfbClientPtr cl, char *buf, int bufsize) +{ + int ret; + struct rfbssl_ctx *ctx = (struct rfbssl_ctx *)cl->sslctx; + + while ((ret = SSL_peek(ctx->ssl, buf, bufsize)) <= 0) { + if (SSL_get_error(ctx->ssl, ret) != SSL_ERROR_WANT_READ) + break; + } + return ret; +} + +int rfbssl_read(rfbClientPtr cl, char *buf, int bufsize) +{ + int ret; + struct rfbssl_ctx *ctx = (struct rfbssl_ctx *)cl->sslctx; + + while ((ret = SSL_read(ctx->ssl, buf, bufsize)) <= 0) { + if (SSL_get_error(ctx->ssl, ret) != SSL_ERROR_WANT_READ) + break; + } + return ret; +} + +int rfbssl_pending(rfbClientPtr cl) +{ + struct rfbssl_ctx *ctx = (struct rfbssl_ctx *)cl->sslctx; + return SSL_pending(ctx->ssl); +} + +void rfbssl_destroy(rfbClientPtr cl) +{ + struct rfbssl_ctx *ctx = (struct rfbssl_ctx *)cl->sslctx; + if (ctx->ssl) + SSL_free(ctx->ssl); + if (ctx->ssl_ctx) + SSL_CTX_free(ctx->ssl_ctx); +} diff --git a/libvncserver/sockets.c b/libvncserver/sockets.c index 188a8fd..415f712 100644 --- a/libvncserver/sockets.c +++ b/libvncserver/sockets.c @@ -62,6 +62,10 @@ #include <unistd.h> #endif +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS +#include "rfbssl.h" +#endif + #if defined(__linux__) && defined(NEED_TIMEVAL) struct timeval { @@ -392,6 +396,11 @@ 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); + free(cl->wspath); +#endif #ifndef __MINGW32__ shutdown(cl->sock,SHUT_RDWR); #endif @@ -457,7 +466,17 @@ rfbReadExactTimeout(rfbClientPtr cl, char* buf, int len, int timeout) struct timeval tv; while (len > 0) { +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS + if (cl->wsctx) { + n = webSocketsDecode(cl, buf, len); + } else if (cl->sslctx) { + n = rfbssl_read(cl, buf, len); + } else { + n = read(sock, buf, len); + } +#else n = read(sock, buf, len); +#endif if (n > 0) { @@ -482,6 +501,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; @@ -492,6 +517,7 @@ rfbReadExactTimeout(rfbClientPtr cl, char* buf, int len, int timeout) return n; } if (n == 0) { + rfbErr("ReadExact: select timeout\n"); errno = ETIMEDOUT; return -1; } @@ -518,6 +544,82 @@ int rfbReadExact(rfbClientPtr cl,char* buf,int len) } /* + * 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) { +#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) { + + 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; + } + +#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("PeekExact: select"); + return n; + } + if (n == 0) { + errno = ETIMEDOUT; + return -1; + } + } + } +#undef DEBUG_READ_EXACT +#ifdef DEBUG_READ_EXACT + rfbLog("PeekExact %d bytes\n",len); + for(n=0;n<len;n++) + fprintf(stderr,"%02x ",(unsigned char)buf[n]); + fprintf(stderr,"\n"); +#endif + + return 1; +} + +/* * WriteExact writes an exact number of bytes to a client. Returns 1 if * those bytes have been written, or -1 if an error occurred (errno is set to * ETIMEDOUT if it timed out). @@ -543,9 +645,25 @@ rfbWriteExact(rfbClientPtr cl, fprintf(stderr,"\n"); #endif +#ifdef LIBVNCSERVER_WITH_WEBSOCKETS + if (cl->wsctx) { + char *tmp = NULL; + if ((len = webSocketsEncode(cl, buf, len, &tmp)) < 0) { + rfbErr("WriteExact: WebSockets encode error\n"); + return -1; + } + buf = tmp; + } +#endif + LOCK(cl->outputMutex); 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/stats.c b/libvncserver/stats.c index 6dab13b..39de1c6 100644 --- a/libvncserver/stats.c +++ b/libvncserver/stats.c @@ -100,6 +100,7 @@ char *encodingName(uint32_t type, char *buf, int len) { case rfbEncodingHextile: snprintf(buf, len, "hextile"); break; case rfbEncodingZlib: snprintf(buf, len, "zlib"); break; case rfbEncodingTight: snprintf(buf, len, "tight"); break; + case rfbEncodingTightPng: snprintf(buf, len, "tightPng"); break; case rfbEncodingZlibHex: snprintf(buf, len, "zlibhex"); break; case rfbEncodingUltra: snprintf(buf, len, "ultra"); break; case rfbEncodingZRLE: snprintf(buf, len, "ZRLE"); break; diff --git a/libvncserver/tight.c b/libvncserver/tight.c index bb033c3..474e9e3 100644 --- a/libvncserver/tight.c +++ b/libvncserver/tight.c @@ -37,6 +37,9 @@ #ifdef _RPCNDR_H /* This Windows header typedefs 'boolean', jpeglib has to know */ #define HAVE_BOOLEAN #endif +#ifdef LIBVNCSERVER_HAVE_LIBPNG +#include <png.h> +#endif #include <jpeglib.h> /* Note: The following constant should not be changed. */ @@ -91,6 +94,25 @@ static TIGHT_CONF tightConf[10] = { { 65536, 2048, 32, 8192, 9, 9, 9, 6, 200, 500, 96, 80, 200, 500 } }; +#ifdef LIBVNCSERVER_HAVE_LIBPNG +typedef struct TIGHT_PNG_CONF_s { + int png_zlib_level, png_filters; +} TIGHT_PNG_CONF; + +static TIGHT_PNG_CONF tightPngConf[10] = { + { 0, PNG_NO_FILTERS }, + { 1, PNG_NO_FILTERS }, + { 2, PNG_NO_FILTERS }, + { 3, PNG_NO_FILTERS }, + { 4, PNG_NO_FILTERS }, + { 5, PNG_ALL_FILTERS }, + { 6, PNG_ALL_FILTERS }, + { 7, PNG_ALL_FILTERS }, + { 8, PNG_ALL_FILTERS }, + { 9, PNG_ALL_FILTERS }, +}; +#endif + static TLS int compressLevel = 0; static TLS int qualityLevel = 0; @@ -146,6 +168,8 @@ void rfbTightCleanup(rfbScreenInfoPtr screen) /* Prototypes for static functions. */ +static rfbBool SendRectEncodingTight(rfbClientPtr cl, int x, int y, + int w, int h); static void FindBestSolidArea (rfbClientPtr cl, int x, int y, int w, int h, uint32_t colorValue, int *w_ptr, int *h_ptr); static void ExtendSolidArea (rfbClientPtr cl, int x, int y, int w, int h, @@ -165,10 +189,10 @@ static rfbBool SendSubrect (rfbClientPtr cl, int x, int y, int w, int h); static rfbBool SendTightHeader (rfbClientPtr cl, int x, int y, int w, int h); static rfbBool SendSolidRect (rfbClientPtr cl); -static rfbBool SendMonoRect (rfbClientPtr cl, int w, int h); -static rfbBool SendIndexedRect (rfbClientPtr cl, int w, int h); -static rfbBool SendFullColorRect (rfbClientPtr cl, int w, int h); -static rfbBool SendGradientRect (rfbClientPtr cl, int w, int h); +static rfbBool SendMonoRect (rfbClientPtr cl, int x, int y, int w, int h); +static rfbBool SendIndexedRect (rfbClientPtr cl, int x, int y, int w, int h); +static rfbBool SendFullColorRect (rfbClientPtr cl, int x, int y, int w, int h); +static rfbBool SendGradientRect (rfbClientPtr cl, int x, int y, int w, int h); static rfbBool CompressData(rfbClientPtr cl, int streamId, int dataLen, int zlibLevel, int zlibStrategy); @@ -201,16 +225,20 @@ static unsigned long DetectSmoothImage32(rfbClientPtr cl, rfbPixelFormat *fmt, i static rfbBool SendJpegRect(rfbClientPtr cl, int x, int y, int w, int h, int quality); -static void PrepareRowForJpeg(rfbClientPtr cl, uint8_t *dst, int x, int y, int count); -static void PrepareRowForJpeg24(rfbClientPtr cl, uint8_t *dst, int x, int y, int count); -static void PrepareRowForJpeg16(rfbClientPtr cl, uint8_t *dst, int x, int y, int count); -static void PrepareRowForJpeg32(rfbClientPtr cl, uint8_t *dst, int x, int y, int count); +static void PrepareRowForImg(rfbClientPtr cl, uint8_t *dst, int x, int y, int count); +static void PrepareRowForImg24(rfbClientPtr cl, uint8_t *dst, int x, int y, int count); +static void PrepareRowForImg16(rfbClientPtr cl, uint8_t *dst, int x, int y, int count); +static void PrepareRowForImg32(rfbClientPtr cl, uint8_t *dst, int x, int y, int count); static void JpegInitDestination(j_compress_ptr cinfo); static boolean JpegEmptyOutputBuffer(j_compress_ptr cinfo); static void JpegTermDestination(j_compress_ptr cinfo); static void JpegSetDstManager(j_compress_ptr cinfo); +#ifdef LIBVNCSERVER_HAVE_LIBPNG +static rfbBool SendPngRect(rfbClientPtr cl, int x, int y, int w, int h); +static rfbBool CanSendPngRect(rfbClientPtr cl, int w, int h); +#endif /* * Tight encoding implementation. @@ -251,6 +279,29 @@ rfbSendRectEncodingTight(rfbClientPtr cl, int w, int h) { + cl->tightEncoding = rfbEncodingTight; + return SendRectEncodingTight(cl, x, y, w, h); +} + +rfbBool +rfbSendRectEncodingTightPng(rfbClientPtr cl, + int x, + int y, + int w, + int h) +{ + cl->tightEncoding = rfbEncodingTightPng; + return SendRectEncodingTight(cl, x, y, w, h); +} + + +rfbBool +SendRectEncodingTight(rfbClientPtr cl, + int x, + int y, + int w, + int h) +{ int nMaxRows; uint32_t colorValue; int dx, dy, dw, dh; @@ -341,7 +392,7 @@ rfbSendRectEncodingTight(rfbClientPtr cl, !SendRectSimple(cl, x, y, w, y_best-y) ) return FALSE; if ( x_best != x && - !rfbSendRectEncodingTight(cl, x, y_best, + !SendRectEncodingTight(cl, x, y_best, x_best-x, h_best) ) return FALSE; @@ -364,11 +415,11 @@ rfbSendRectEncodingTight(rfbClientPtr cl, /* Send remaining rectangles (at right and bottom). */ if ( x_best + w_best != x + w && - !rfbSendRectEncodingTight(cl, x_best+w_best, y_best, + !SendRectEncodingTight(cl, x_best+w_best, y_best, w-(x_best-x)-w_best, h_best) ) return FALSE; if ( y_best + h_best != y + h && - !rfbSendRectEncodingTight(cl, x, y_best+h_best, + !SendRectEncodingTight(cl, x, y_best+h_best, w, h-(y_best-y)-h_best) ) return FALSE; @@ -629,10 +680,10 @@ SendSubrect(rfbClientPtr cl, success = SendJpegRect(cl, x, y, w, h, tightConf[qualityLevel].jpegQuality); } else { - success = SendGradientRect(cl, w, h); + success = SendGradientRect(cl, x, y, w, h); } } else { - success = SendFullColorRect(cl, w, h); + success = SendFullColorRect(cl, x, y, w, h); } break; case 1: @@ -641,7 +692,7 @@ SendSubrect(rfbClientPtr cl, break; case 2: /* Two-color rectangle */ - success = SendMonoRect(cl, w, h); + success = SendMonoRect(cl, x, y, w, h); break; default: /* Up to 256 different colors */ @@ -651,7 +702,7 @@ SendSubrect(rfbClientPtr cl, success = SendJpegRect(cl, x, y, w, h, tightConf[qualityLevel].jpegQuality); } else { - success = SendIndexedRect(cl, w, h); + success = SendIndexedRect(cl, x, y, w, h); } } return success; @@ -675,13 +726,13 @@ SendTightHeader(rfbClientPtr cl, rect.r.y = Swap16IfLE(y); rect.r.w = Swap16IfLE(w); rect.r.h = Swap16IfLE(h); - rect.encoding = Swap32IfLE(rfbEncodingTight); + rect.encoding = Swap32IfLE(cl->tightEncoding); memcpy(&cl->updateBuf[cl->ublen], (char *)&rect, sz_rfbFramebufferUpdateRectHeader); cl->ublen += sz_rfbFramebufferUpdateRectHeader; - rfbStatRecordEncodingSent(cl, rfbEncodingTight, sz_rfbFramebufferUpdateRectHeader, + rfbStatRecordEncodingSent(cl, cl->tightEncoding, sz_rfbFramebufferUpdateRectHeader, sz_rfbFramebufferUpdateRectHeader + w * (cl->format.bitsPerPixel / 8) * h); return TRUE; @@ -711,19 +762,29 @@ SendSolidRect(rfbClientPtr cl) memcpy (&cl->updateBuf[cl->ublen], tightBeforeBuf, len); cl->ublen += len; - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, len+1); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, len+1); return TRUE; } static rfbBool SendMonoRect(rfbClientPtr cl, + int x, + int y, int w, int h) { int streamId = 1; int paletteLen, dataLen; +#ifdef LIBVNCSERVER_HAVE_LIBPNG + if (CanSendPngRect(cl, w, h)) { + /* TODO: setup palette maybe */ + return SendPngRect(cl, x, y, w, h); + /* TODO: destroy palette maybe */ + } +#endif + if ( cl->ublen + TIGHT_MIN_TO_COMPRESS + 6 + 2 * cl->format.bitsPerPixel / 8 > UPDATE_BUF_SIZE ) { if (!rfbSendUpdateBuf(cl)) @@ -754,7 +815,7 @@ SendMonoRect(rfbClientPtr cl, memcpy(&cl->updateBuf[cl->ublen], tightAfterBuf, paletteLen); cl->ublen += paletteLen; - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 3 + paletteLen); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 3 + paletteLen); break; case 16: @@ -765,7 +826,7 @@ SendMonoRect(rfbClientPtr cl, memcpy(&cl->updateBuf[cl->ublen], tightAfterBuf, 4); cl->ublen += 4; - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 7); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 7); break; default: @@ -773,7 +834,7 @@ SendMonoRect(rfbClientPtr cl, cl->updateBuf[cl->ublen++] = (char)monoBackground; cl->updateBuf[cl->ublen++] = (char)monoForeground; - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 5); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 5); } return CompressData(cl, streamId, dataLen, @@ -783,12 +844,20 @@ SendMonoRect(rfbClientPtr cl, static rfbBool SendIndexedRect(rfbClientPtr cl, + int x, + int y, int w, int h) { int streamId = 2; int i, entryLen; +#ifdef LIBVNCSERVER_HAVE_LIBPNG + if (CanSendPngRect(cl, w, h)) { + return SendPngRect(cl, x, y, w, h); + } +#endif + if ( cl->ublen + TIGHT_MIN_TO_COMPRESS + 6 + paletteNumColors * cl->format.bitsPerPixel / 8 > UPDATE_BUF_SIZE ) { @@ -819,7 +888,7 @@ SendIndexedRect(rfbClientPtr cl, memcpy(&cl->updateBuf[cl->ublen], tightAfterBuf, paletteNumColors * entryLen); cl->ublen += paletteNumColors * entryLen; - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 3 + paletteNumColors * entryLen); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 3 + paletteNumColors * entryLen); break; case 16: @@ -832,7 +901,7 @@ SendIndexedRect(rfbClientPtr cl, memcpy(&cl->updateBuf[cl->ublen], tightAfterBuf, paletteNumColors * 2); cl->ublen += paletteNumColors * 2; - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 3 + paletteNumColors * 2); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 3 + paletteNumColors * 2); break; default: @@ -846,19 +915,27 @@ SendIndexedRect(rfbClientPtr cl, static rfbBool SendFullColorRect(rfbClientPtr cl, + int x, + int y, int w, int h) { int streamId = 0; int len; +#ifdef LIBVNCSERVER_HAVE_LIBPNG + if (CanSendPngRect(cl, w, h)) { + return SendPngRect(cl, x, y, w, h); + } +#endif + if (cl->ublen + TIGHT_MIN_TO_COMPRESS + 1 > UPDATE_BUF_SIZE) { if (!rfbSendUpdateBuf(cl)) return FALSE; } cl->updateBuf[cl->ublen++] = 0x00; /* stream id = 0, no flushing, no filter */ - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 1); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1); if (usePixelFormat24) { Pack24(cl, tightBeforeBuf, &cl->format, w * h); @@ -873,14 +950,22 @@ SendFullColorRect(rfbClientPtr cl, static rfbBool SendGradientRect(rfbClientPtr cl, + int x, + int y, int w, int h) { int streamId = 3; int len; +#ifdef LIBVNCSERVER_HAVE_LIBPNG + if (CanSendPngRect(cl, w, h)) { + return SendPngRect(cl, x, y, w, h); + } +#endif + if (cl->format.bitsPerPixel == 8) - return SendFullColorRect(cl, w, h); + return SendFullColorRect(cl, x, y, w, h); if (cl->ublen + TIGHT_MIN_TO_COMPRESS + 2 > UPDATE_BUF_SIZE) { if (!rfbSendUpdateBuf(cl)) @@ -892,7 +977,7 @@ SendGradientRect(rfbClientPtr cl, cl->updateBuf[cl->ublen++] = (streamId | rfbTightExplicitFilter) << 4; cl->updateBuf[cl->ublen++] = rfbTightFilterGradient; - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 2); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 2); if (usePixelFormat24) { FilterGradient24(cl, tightBeforeBuf, &cl->format, w, h); @@ -923,7 +1008,7 @@ CompressData(rfbClientPtr cl, if (dataLen < TIGHT_MIN_TO_COMPRESS) { memcpy(&cl->updateBuf[cl->ublen], tightBeforeBuf, dataLen); cl->ublen += dataLen; - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, dataLen); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, dataLen); return TRUE; } @@ -973,15 +1058,15 @@ static rfbBool SendCompressedData(rfbClientPtr cl, int i, portionLen; cl->updateBuf[cl->ublen++] = compressedLen & 0x7F; - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 1); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1); if (compressedLen > 0x7F) { cl->updateBuf[cl->ublen-1] |= 0x80; cl->updateBuf[cl->ublen++] = compressedLen >> 7 & 0x7F; - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 1); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1); if (compressedLen > 0x3FFF) { cl->updateBuf[cl->ublen-1] |= 0x80; cl->updateBuf[cl->ublen++] = compressedLen >> 14 & 0xFF; - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 1); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1); } } @@ -997,7 +1082,7 @@ static rfbBool SendCompressedData(rfbClientPtr cl, memcpy(&cl->updateBuf[cl->ublen], &tightAfterBuf[i], portionLen); cl->ublen += portionLen; } - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, compressedLen); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, compressedLen); return TRUE; } @@ -1659,11 +1744,11 @@ SendJpegRect(rfbClientPtr cl, int x, int y, int w, int h, int quality) int dy; if (cl->screen->serverFormat.bitsPerPixel == 8) - return SendFullColorRect(cl, w, h); + return SendFullColorRect(cl, x, y, w, h); srcBuf = (uint8_t *)malloc(w * 3); if (srcBuf == NULL) { - return SendFullColorRect(cl, w, h); + return SendFullColorRect(cl, x, y, w, h); } rowPointer[0] = srcBuf; @@ -1683,7 +1768,7 @@ SendJpegRect(rfbClientPtr cl, int x, int y, int w, int h, int quality) jpeg_start_compress(&cinfo, TRUE); for (dy = 0; dy < h; dy++) { - PrepareRowForJpeg(cl, srcBuf, x, y + dy, w); + PrepareRowForImg(cl, srcBuf, x, y + dy, w); jpeg_write_scanlines(&cinfo, rowPointer, 1); if (jpegError) break; @@ -1696,7 +1781,7 @@ SendJpegRect(rfbClientPtr cl, int x, int y, int w, int h, int quality) free(srcBuf); if (jpegError) - return SendFullColorRect(cl, w, h); + return SendFullColorRect(cl, x, y, w, h); if (cl->ublen + TIGHT_MIN_TO_COMPRESS + 1 > UPDATE_BUF_SIZE) { if (!rfbSendUpdateBuf(cl)) @@ -1704,13 +1789,13 @@ SendJpegRect(rfbClientPtr cl, int x, int y, int w, int h, int quality) } cl->updateBuf[cl->ublen++] = (char)(rfbTightJpeg << 4); - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 1); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1); return SendCompressedData(cl, jpegDstDataLen); } static void -PrepareRowForJpeg(rfbClientPtr cl, +PrepareRowForImg(rfbClientPtr cl, uint8_t *dst, int x, int y, @@ -1720,18 +1805,18 @@ PrepareRowForJpeg(rfbClientPtr cl, if ( cl->screen->serverFormat.redMax == 0xFF && cl->screen->serverFormat.greenMax == 0xFF && cl->screen->serverFormat.blueMax == 0xFF ) { - PrepareRowForJpeg24(cl, dst, x, y, count); + PrepareRowForImg24(cl, dst, x, y, count); } else { - PrepareRowForJpeg32(cl, dst, x, y, count); + PrepareRowForImg32(cl, dst, x, y, count); } } else { /* 16 bpp assumed. */ - PrepareRowForJpeg16(cl, dst, x, y, count); + PrepareRowForImg16(cl, dst, x, y, count); } } static void -PrepareRowForJpeg24(rfbClientPtr cl, +PrepareRowForImg24(rfbClientPtr cl, uint8_t *dst, int x, int y, @@ -1754,7 +1839,7 @@ PrepareRowForJpeg24(rfbClientPtr cl, #define DEFINE_JPEG_GET_ROW_FUNCTION(bpp) \ \ static void \ -PrepareRowForJpeg##bpp(rfbClientPtr cl, uint8_t *dst, int x, int y, int count) { \ +PrepareRowForImg##bpp(rfbClientPtr cl, uint8_t *dst, int x, int y, int count) { \ uint##bpp##_t *fbptr; \ uint##bpp##_t pix; \ int inRed, inGreen, inBlue; \ @@ -1822,3 +1907,161 @@ JpegSetDstManager(j_compress_ptr cinfo) cinfo->dest = &jpegDstManager; } +/* + * PNG compression stuff. + */ + +#ifdef LIBVNCSERVER_HAVE_LIBPNG + +static TLS int pngDstDataLen = 0; + +static rfbBool CanSendPngRect(rfbClientPtr cl, int w, int h) { + if (cl->tightEncoding != rfbEncodingTightPng) { + return FALSE; + } + + if ( cl->screen->serverFormat.bitsPerPixel == 8 || + cl->format.bitsPerPixel == 8) { + return FALSE; + } + + return TRUE; +} + +static void pngWriteData(png_structp png_ptr, png_bytep data, + png_size_t length) +{ +#if 0 + rfbClientPtr cl = png_get_io_ptr(png_ptr); + + buffer_reserve(&vs->tight.png, vs->tight.png.offset + length); + memcpy(vs->tight.png.buffer + vs->tight.png.offset, data, length); +#endif + memcpy(tightAfterBuf + pngDstDataLen, data, length); + + pngDstDataLen += length; +} + +static void pngFlushData(png_structp png_ptr) +{ +} + + +static void *pngMalloc(png_structp png_ptr, png_size_t size) +{ + return malloc(size); +} + +static void pngFree(png_structp png_ptr, png_voidp ptr) +{ + free(ptr); +} + +static rfbBool SendPngRect(rfbClientPtr cl, int x, int y, int w, int h) { + /* rfbLog(">> SendPngRect x:%d, y:%d, w:%d, h:%d\n", x, y, w, h); */ + + png_byte color_type; + png_structp png_ptr; + png_infop info_ptr; + png_colorp png_palette = NULL; + int level = tightPngConf[cl->tightCompressLevel].png_zlib_level; + int filters = tightPngConf[cl->tightCompressLevel].png_filters; + uint8_t *buf; + int dy; + + pngDstDataLen = 0; + + png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL, + NULL, pngMalloc, pngFree); + + if (png_ptr == NULL) + return FALSE; + + info_ptr = png_create_info_struct(png_ptr); + + if (info_ptr == NULL) { + png_destroy_write_struct(&png_ptr, NULL); + return FALSE; + } + + png_set_write_fn(png_ptr, (void *) cl, pngWriteData, pngFlushData); + png_set_compression_level(png_ptr, level); + png_set_filter(png_ptr, PNG_FILTER_TYPE_DEFAULT, filters); + +#if 0 + /* TODO: */ + if (palette) { + color_type = PNG_COLOR_TYPE_PALETTE; + } else { + color_type = PNG_COLOR_TYPE_RGB; + } +#else + color_type = PNG_COLOR_TYPE_RGB; +#endif + png_set_IHDR(png_ptr, info_ptr, w, h, + 8, color_type, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + +#if 0 + if (color_type == PNG_COLOR_TYPE_PALETTE) { + struct palette_cb_priv priv; + + png_palette = pngMalloc(png_ptr, sizeof(*png_palette) * + palette_size(palette)); + + priv.vs = vs; + priv.png_palette = png_palette; + palette_iter(palette, write_png_palette, &priv); + + png_set_PLTE(png_ptr, info_ptr, png_palette, palette_size(palette)); + + offset = vs->tight.tight.offset; + if (vs->clientds.pf.bytes_per_pixel == 4) { + tight_encode_indexed_rect32(vs->tight.tight.buffer, w * h, palette); + } else { + tight_encode_indexed_rect16(vs->tight.tight.buffer, w * h, palette); + } + } + + buffer_reserve(&vs->tight.png, 2048); +#endif + + png_write_info(png_ptr, info_ptr); + buf = malloc(w * 3); + for (dy = 0; dy < h; dy++) + { +#if 0 + if (color_type == PNG_COLOR_TYPE_PALETTE) { + memcpy(buf, vs->tight.tight.buffer + (dy * w), w); + } else { + PrepareRowForImg(cl, buf, x, y + dy, w); + } +#else + PrepareRowForImg(cl, buf, x, y + dy, w); +#endif + png_write_row(png_ptr, buf); + } + free(buf); + + png_write_end(png_ptr, NULL); + + if (color_type == PNG_COLOR_TYPE_PALETTE) { + pngFree(png_ptr, png_palette); + } + + png_destroy_write_struct(&png_ptr, &info_ptr); + + /* done v */ + + if (cl->ublen + TIGHT_MIN_TO_COMPRESS + 1 > UPDATE_BUF_SIZE) { + if (!rfbSendUpdateBuf(cl)) + return FALSE; + } + + cl->updateBuf[cl->ublen++] = (char)(rfbTightPng << 4); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1); + + /* rfbLog("<< SendPngRect\n"); */ + return SendCompressedData(cl, pngDstDataLen); +} +#endif diff --git a/libvncserver/websockets.c b/libvncserver/websockets.c new file mode 100644 index 0000000..7532e33 --- /dev/null +++ b/libvncserver/websockets.c @@ -0,0 +1,880 @@ +/* + * 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 <rfb/rfb.h> +#include <resolv.h> /* __b64_ntop */ +/* errno */ +#include <errno.h> + +#include <byteswap.h> +#include <string.h> +#include "rfbconfig.h" +#include "rfbssl.h" +#include "rfbcrypto.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 +}; + +#if 0 +#include <sys/syscall.h> +static int gettid() { + return (int)syscall(SYS_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 codeBuf[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; + +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 "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>\n" +#define SZ_FLASH_POLICY_RESPONSE 93 + +/* + * draft-ietf-hybi-thewebsocketprotocol-10 + * 5.2.2. Sending the Server's Opening Handshake + */ +#define GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + +#define SERVER_HANDSHAKE_HIXIE "HTTP/1.1 101 Web Socket Protocol Handshake\r\n\ +Upgrade: WebSocket\r\n\ +Connection: Upgrade\r\n\ +%sWebSocket-Origin: %s\r\n\ +%sWebSocket-Location: %s://%s%s\r\n\ +%sWebSocket-Protocol: %s\r\n\ +\r\n%s" + +#define SERVER_HANDSHAKE_HYBI "HTTP/1.1 101 Switching Protocols\r\n\ +Upgrade: websocket\r\n\ +Connection: Upgrade\r\n\ +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 + +#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 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 +min (int a, int b) { + return a < b ? a : b; +} + +static void webSocketsGenSha1Key(char *target, int size, char *key) +{ + struct iovec iov[2]; + unsigned char hash[20]; + + iov[0].iov_base = key; + iov[0].iov_len = strlen(key); + iov[1].iov_base = GUID; + iov[1].iov_len = sizeof(GUID) - 1; + digestsha1(iov, 2, hash); + if (-1 == __b64_ntop(hash, sizeof(hash), target, size)) + rfbErr("b64_ntop failed\n"); +} + +/* + * 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 || 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"; + } else { + 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; + } + /* Start WebSockets framing */ + return TRUE; +} + +static rfbBool +webSocketsHandshake(rfbClientPtr cl, char *scheme) +{ + char *buf, *response, *line; + int n, linestart = 0, len = 0, llen, base64 = TRUE; + 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; + ws_ctx_t *wsctx = 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"); + free(response); + free(buf); + return FALSE; + } + + len += 1; + llen = len - linestart; + if (((llen >= 2)) && (buf[len-1] == '\n')) { + line = buf+linestart; + if ((llen == 2) && (strncmp("\r\n", line, 2) == 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"); + free(response); + free(buf); + 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") */ + path = line+4; + buf[len-11] = '\0'; /* Trim trailing " HTTP/1.1\r\n" */ + cl->wspath = strdup(path); + /* rfbLog("Got path: %s\n", path); */ + } else if ((strncasecmp("host: ", line, min(llen,6))) == 0) { + host = line+6; + buf[len-2] = '\0'; + /* rfbLog("Got host: %s\n", host); */ + } else if ((strncasecmp("origin: ", line, min(llen,8))) == 0) { + origin = line+8; + buf[len-2] = '\0'; + /* rfbLog("Got origin: %s\n", origin); */ + } else if ((strncasecmp("sec-websocket-key1: ", line, min(llen,20))) == 0) { + key1 = line+20; + buf[len-2] = '\0'; + /* rfbLog("Got key1: %s\n", key1); */ + } else if ((strncasecmp("sec-websocket-key2: ", line, min(llen,20))) == 0) { + key2 = line+20; + buf[len-2] = '\0'; + /* rfbLog("Got key2: %s\n", key2); */ + /* 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); + } 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 || sec_ws_origin))) { + rfbErr("webSocketsHandshake: incomplete client handshake\n"); + free(response); + free(buf); + return FALSE; + } + + 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"; + } else { + rfbLog(" - webSocketsHandshake: using base64 encoding\n"); + base64 = TRUE; + if ((protocol) && (strstr(protocol, "base64"))) { + protocol = "base64"; + } else { + protocol = ""; + } + } + + /* + * Generate the WebSockets server response based on the the headers sent + * by the client. + */ + + 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); + 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); + } + + if (rfbWriteExact(cl, response, len) < 0) { + rfbErr("webSocketsHandshake: failed sending WebSockets response\n"); + free(response); + free(buf); + return FALSE; + } + /* rfbLog("webSocketsHandshake: %s\n", response); */ + 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->base64 = base64; + 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->codeBuf[sz++] = '\x00'; + len = __b64_ntop((unsigned char *)src, len, wsctx->codeBuf+sz, sizeof(wsctx->codeBuf) - (sz + 1)); + if (len < 0) { + return len; + } + sz += len; + + wsctx->codeBuf[sz++] = '\xff'; + *dst = wsctx->codeBuf; + 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 { + while (-1 == (n = recv(cl->sock, buf, len, MSG_PEEK))) { + if (errno != EAGAIN) + break; + } + } + return n; +} + +static int +webSocketsDecodeHixie(rfbClientPtr cl, char *dst, int len) +{ + int retlen = 0, n, i, avail, modlen, needlen; + char *buf, *end = NULL; + ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; + + buf = wsctx->codeBuf; + + n = ws_peek(cl, buf, len*2+2); + + if (n <= 0) { + rfbErr("%s: peek (%d) %m\n", __func__, errno); + 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, fin, fhlen; + + // 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->codeBuf; + header = (ws_header_t *)wsctx->codeBuf; + + 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) { + /* 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->codeBuf, sizeof(wsctx->codeBuf)))) { + rfbErr("%s: Base64 decode error; %m\n", __func__); + break; + } + payload = wsctx->codeBuf; + /* 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; + 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->codeBuf; + + if (wsctx->base64) { + 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 (wsctx->base64) { + if (-1 == (ret = __b64_ntop((unsigned char *)src, len, wsctx->codeBuf + sz, sizeof(wsctx->codeBuf) - 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->codeBuf + sz, src, len); + ret = sz + len; + } + + *dst = wsctx->codeBuf; + return ret; +} + +int +webSocketsEncode(rfbClientPtr cl, const char *src, int len, char **dst) +{ + return ((ws_ctx_t *)cl->wsctx)->encode(cl, src, len, dst); +} + +int +webSocketsDecode(rfbClientPtr cl, char *dst, int len) +{ + return ((ws_ctx_t *)cl->wsctx)->decode(cl, 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! + **/ +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; +} + |