summaryrefslogtreecommitdiffstats
path: root/libvncserver
diff options
context:
space:
mode:
authorGernot Tenchio <gernot.tenchio@securepoint.de>2011-08-25 10:58:19 +0200
committerGernot Tenchio <gernot.tenchio@securepoint.de>2011-08-25 11:00:19 +0200
commit1408866c864cac3b1bbf37eb9fdc8d303f37957d (patch)
tree7602c620ed56aadd74639d3da0cb8b57e2aebdf7 /libvncserver
parent02651bacca81c5a63b80d782123d20b26a65a4b0 (diff)
downloadlibtdevnc-1408866c864cac3b1bbf37eb9fdc8d303f37957d.tar.gz
libtdevnc-1408866c864cac3b1bbf37eb9fdc8d303f37957d.zip
websockets: Initial HyBi support
Diffstat (limited to 'libvncserver')
-rw-r--r--libvncserver/rfbserver.c2
-rw-r--r--libvncserver/sockets.c5
-rwxr-xr-xlibvncserver/websockets.c430
3 files changed, 392 insertions, 45 deletions
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 <errno.h>
#include <md5.h>
+#include <byteswap.h>
+#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 <sys/syscall.h>
+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 "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>\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 <openssl/sha.h>
+
+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);
+}