diff options
author | Gernot Tenchio <gernot.tenchio@securepoint.de> | 2011-08-25 10:58:19 +0200 |
---|---|---|
committer | Gernot Tenchio <gernot.tenchio@securepoint.de> | 2011-08-25 11:00:19 +0200 |
commit | 1408866c864cac3b1bbf37eb9fdc8d303f37957d (patch) | |
tree | 7602c620ed56aadd74639d3da0cb8b57e2aebdf7 | |
parent | 02651bacca81c5a63b80d782123d20b26a65a4b0 (diff) | |
download | libvncserver-1408866c864cac3b1bbf37eb9fdc8d303f37957d.zip libvncserver-1408866c864cac3b1bbf37eb9fdc8d303f37957d.tar.gz |
websockets: Initial HyBi support
-rw-r--r-- | libvncserver/rfbserver.c | 2 | ||||
-rw-r--r-- | libvncserver/sockets.c | 5 | ||||
-rwxr-xr-x | libvncserver/websockets.c | 430 | ||||
-rw-r--r-- | rfb/rfb.h | 12 |
4 files changed, 395 insertions, 54 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); +} @@ -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 |