1 /*
2 * websockets.c - deal with WebSockets clients.
3 *
4 * This code should be independent of any changes in the RFB protocol. It is
5 * an additional handshake and framing of normal sockets:
6 * http://www.whatwg.org/specs/web-socket-protocol/
7 *
8 */
9
10 /*
11 * Copyright (C) 2010 Joel Martin
12 *
13 * This is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This software is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this software; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
26 * USA.
27 */
28
29 #ifdef __STRICT_ANSI__
30 #define _BSD_SOURCE
31 #endif
32
33 #include <rfb/rfb.h>
34 /* errno */
35 #include <errno.h>
36
37 #ifdef LIBVNCSERVER_HAVE_ENDIAN_H
38 #include <endian.h>
39 #elif LIBVNCSERVER_HAVE_SYS_ENDIAN_H
40 #include <sys/endian.h>
41 #endif
42
43 #ifdef LIBVNCSERVER_HAVE_SYS_TYPES_H
44 #include <sys/types.h>
45 #endif
46
47 #include <string.h>
48 #if LIBVNCSERVER_UNISTD_H
49 #include <unistd.h>
50 #endif
51 #include "rfb/rfbconfig.h"
52 #include "rfbssl.h"
53 #include "crypto.h"
54 #include "ws_decode.h"
55 #include "base64.h"
56
57 #if 0
58 #include <sys/syscall.h>
59 static int gettid() {
60 return (int)syscall(SYS_gettid);
61 }
62 #endif
63
64 /*
65 * draft-ietf-hybi-thewebsocketprotocol-10
66 * 5.2.2. Sending the Server's Opening Handshake
67 */
68 #define GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
69
70 #define SERVER_HANDSHAKE_HYBI "HTTP/1.1 101 Switching Protocols\r\n\
71 Upgrade: websocket\r\n\
72 Connection: Upgrade\r\n\
73 Sec-WebSocket-Accept: %s\r\n\
74 Sec-WebSocket-Protocol: %s\r\n\
75 \r\n"
76
77 #define SERVER_HANDSHAKE_HYBI_NO_PROTOCOL "HTTP/1.1 101 Switching Protocols\r\n\
78 Upgrade: websocket\r\n\
79 Connection: Upgrade\r\n\
80 Sec-WebSocket-Accept: %s\r\n\
81 \r\n"
82
83 #define WEBSOCKETS_CLIENT_CONNECT_WAIT_MS 100
84 #define WEBSOCKETS_CLIENT_SEND_WAIT_MS 100
85 #define WEBSOCKETS_MAX_HANDSHAKE_LEN 4096
86
87 #if defined(__linux__) && defined(NEED_TIMEVAL)
88 struct timeval
89 {
90 long int tv_sec,tv_usec;
91 }
92 ;
93 #endif
94
95 static rfbBool webSocketsHandshake(rfbClientPtr cl, char *scheme);
96
97 static int webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst);
98
99 static int ws_read(void *cl, char *buf, size_t len);
100
101
102 static int
min(int a,int b)103 min (int a, int b) {
104 return a < b ? a : b;
105 }
106
webSocketsGenSha1Key(char * target,int size,char * key)107 static void webSocketsGenSha1Key(char *target, int size, char *key)
108 {
109 unsigned char hash[SHA1_HASH_SIZE];
110 char tmp[strlen(key) + sizeof(GUID) - 1];
111 memcpy(tmp, key, strlen(key));
112 memcpy(tmp + strlen(key), GUID, sizeof(GUID) - 1);
113 hash_sha1(hash, tmp, sizeof(tmp));
114 if (-1 == rfbBase64NtoP(hash, sizeof(hash), target, size))
115 rfbErr("rfbBase64NtoP failed\n");
116 }
117
118 /*
119 * rfbWebSocketsHandshake is called to handle new WebSockets connections
120 */
121
122 rfbBool
webSocketsCheck(rfbClientPtr cl)123 webSocketsCheck (rfbClientPtr cl)
124 {
125 char bbuf[4], *scheme;
126 int ret;
127
128 ret = rfbPeekExactTimeout(cl, bbuf, 4,
129 WEBSOCKETS_CLIENT_CONNECT_WAIT_MS);
130 if ((ret < 0) && (errno == ETIMEDOUT)) {
131 rfbLog("Normal socket connection\n");
132 return TRUE;
133 } else if (ret <= 0) {
134 rfbErr("webSocketsHandshake: unknown connection error\n");
135 return FALSE;
136 }
137
138 if (strncmp(bbuf, "RFB ", 4) == 0) {
139 rfbLog("Normal socket connection\n");
140 return TRUE;
141 } else if (strncmp(bbuf, "\x16", 1) == 0 || strncmp(bbuf, "\x80", 1) == 0) {
142 rfbLog("Got TLS/SSL WebSockets connection\n");
143 if (-1 == rfbssl_init(cl)) {
144 rfbErr("webSocketsHandshake: rfbssl_init failed\n");
145 return FALSE;
146 }
147 ret = rfbPeekExactTimeout(cl, bbuf, 4, WEBSOCKETS_CLIENT_CONNECT_WAIT_MS);
148 scheme = "wss";
149 } else {
150 scheme = "ws";
151 }
152
153 if (strncmp(bbuf, "GET ", 4) != 0) {
154 rfbErr("webSocketsHandshake: invalid client header\n");
155 return FALSE;
156 }
157
158 rfbLog("Got '%s' WebSockets handshake\n", scheme);
159
160 if (!webSocketsHandshake(cl, scheme)) {
161 return FALSE;
162 }
163 /* Start WebSockets framing */
164 return TRUE;
165 }
166
167 static rfbBool
webSocketsHandshake(rfbClientPtr cl,char * scheme)168 webSocketsHandshake(rfbClientPtr cl, char *scheme)
169 {
170 char *buf, *response, *line;
171 int n, linestart = 0, len = 0, llen, base64 = FALSE;
172 char *path = NULL, *host = NULL, *origin = NULL, *protocol = NULL;
173 char *key1 = NULL, *key2 = NULL;
174 char *sec_ws_origin = NULL;
175 char *sec_ws_key = NULL;
176 char sec_ws_version = 0;
177 ws_ctx_t *wsctx = NULL;
178
179 buf = (char *) malloc(WEBSOCKETS_MAX_HANDSHAKE_LEN);
180 if (!buf) {
181 rfbLogPerror("webSocketsHandshake: malloc");
182 return FALSE;
183 }
184 response = (char *) malloc(WEBSOCKETS_MAX_HANDSHAKE_LEN);
185 if (!response) {
186 free(buf);
187 rfbLogPerror("webSocketsHandshake: malloc");
188 return FALSE;
189 }
190
191 while (len < WEBSOCKETS_MAX_HANDSHAKE_LEN-1) {
192 if ((n = rfbReadExactTimeout(cl, buf+len, 1,
193 WEBSOCKETS_CLIENT_SEND_WAIT_MS)) <= 0) {
194 if ((n < 0) && (errno == ETIMEDOUT)) {
195 break;
196 }
197 if (n == 0) {
198 rfbLog("webSocketsHandshake: client gone\n");
199 }
200 else {
201 rfbLogPerror("webSocketsHandshake: read");
202 }
203
204 free(response);
205 free(buf);
206 return FALSE;
207 }
208
209 len += 1;
210 llen = len - linestart;
211 if (((llen >= 2)) && (buf[len-1] == '\n')) {
212 line = buf+linestart;
213 if ((llen == 2) && (strncmp("\r\n", line, 2) == 0)) {
214 if (key1 && key2 && len+8 < WEBSOCKETS_MAX_HANDSHAKE_LEN) {
215 if ((n = rfbReadExact(cl, buf+len, 8)) <= 0) {
216 if ((n < 0) && (errno == ETIMEDOUT)) {
217 break;
218 }
219 if (n == 0)
220 rfbLog("webSocketsHandshake: client gone\n");
221 else
222 rfbLogPerror("webSocketsHandshake: read");
223 free(response);
224 free(buf);
225 return FALSE;
226 }
227 len += 8;
228 } else {
229 buf[len] = '\0';
230 }
231 break;
232 } else if ((llen >= 16) && ((strncmp("GET ", line, min(llen,4))) == 0)) {
233 /* 16 = 4 ("GET ") + 1 ("/.*") + 11 (" HTTP/1.1\r\n") */
234 path = line+4;
235 buf[len-11] = '\0'; /* Trim trailing " HTTP/1.1\r\n" */
236 cl->wspath = strdup(path);
237 /* rfbLog("Got path: %s\n", path); */
238 } else if ((strncasecmp("host: ", line, min(llen,6))) == 0) {
239 host = line+6;
240 buf[len-2] = '\0';
241 /* rfbLog("Got host: %s\n", host); */
242 } else if ((strncasecmp("origin: ", line, min(llen,8))) == 0) {
243 origin = line+8;
244 buf[len-2] = '\0';
245 /* rfbLog("Got origin: %s\n", origin); */
246 } else if ((strncasecmp("sec-websocket-key1: ", line, min(llen,20))) == 0) {
247 key1 = line+20;
248 buf[len-2] = '\0';
249 /* rfbLog("Got key1: %s\n", key1); */
250 } else if ((strncasecmp("sec-websocket-key2: ", line, min(llen,20))) == 0) {
251 key2 = line+20;
252 buf[len-2] = '\0';
253 /* rfbLog("Got key2: %s\n", key2); */
254 /* HyBI */
255
256 } else if ((strncasecmp("sec-websocket-protocol: ", line, min(llen,24))) == 0) {
257 protocol = line+24;
258 buf[len-2] = '\0';
259 rfbLog("Got protocol: %s\n", protocol);
260 } else if ((strncasecmp("sec-websocket-origin: ", line, min(llen,22))) == 0) {
261 sec_ws_origin = line+22;
262 buf[len-2] = '\0';
263 } else if ((strncasecmp("sec-websocket-key: ", line, min(llen,19))) == 0) {
264 sec_ws_key = line+19;
265 buf[len-2] = '\0';
266 } else if ((strncasecmp("sec-websocket-version: ", line, min(llen,23))) == 0) {
267 sec_ws_version = strtol(line+23, NULL, 10);
268 buf[len-2] = '\0';
269 }
270
271 linestart = len;
272 }
273 }
274
275 /* older hixie handshake, this could be removed if
276 * a final standard is established -- removed now */
277 if (!sec_ws_version) {
278 rfbErr("Hixie no longer supported\n");
279 free(response);
280 free(buf);
281 return FALSE;
282 }
283
284 if (!(path && host && (origin || sec_ws_origin))) {
285 rfbErr("webSocketsHandshake: incomplete client handshake\n");
286 free(response);
287 free(buf);
288 return FALSE;
289 }
290
291 if ((protocol) && (strstr(protocol, "base64"))) {
292 rfbLog(" - webSocketsHandshake: using base64 encoding\n");
293 base64 = TRUE;
294 protocol = "base64";
295 } else {
296 rfbLog(" - webSocketsHandshake: using binary/raw encoding\n");
297 if ((protocol) && (strstr(protocol, "binary"))) {
298 protocol = "binary";
299 } else {
300 protocol = "";
301 }
302 }
303
304 /*
305 * Generate the WebSockets server response based on the the headers sent
306 * by the client.
307 */
308 char accept[B64LEN(SHA1_HASH_SIZE) + 1];
309 rfbLog(" - WebSockets client version hybi-%02d\n", sec_ws_version);
310 webSocketsGenSha1Key(accept, sizeof(accept), sec_ws_key);
311
312 if(strlen(protocol) > 0) {
313 len = snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN,
314 SERVER_HANDSHAKE_HYBI, accept, protocol);
315 } else {
316 len = snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN,
317 SERVER_HANDSHAKE_HYBI_NO_PROTOCOL, accept);
318 }
319
320 if (rfbWriteExact(cl, response, len) < 0) {
321 rfbErr("webSocketsHandshake: failed sending WebSockets response\n");
322 free(response);
323 free(buf);
324 return FALSE;
325 }
326 /* rfbLog("webSocketsHandshake: %s\n", response); */
327 free(response);
328 free(buf);
329
330 wsctx = calloc(1, sizeof(ws_ctx_t));
331 if (!wsctx) {
332 rfbErr("webSocketsHandshake: could not allocate memory for context\n");
333 return FALSE;
334 }
335 wsctx->encode = webSocketsEncodeHybi;
336 wsctx->decode = webSocketsDecodeHybi;
337 wsctx->ctxInfo.readFunc = ws_read;
338 wsctx->base64 = base64;
339 hybiDecodeCleanupComplete(wsctx);
340 cl->wsctx = (wsCtx *)wsctx;
341 return TRUE;
342 }
343
344 static int
ws_read(void * ctxPtr,char * buf,size_t len)345 ws_read(void *ctxPtr, char *buf, size_t len)
346 {
347 int n;
348 rfbClientPtr cl = ctxPtr;
349 if (cl->sslctx) {
350 n = rfbssl_read(cl, buf, len);
351 } else {
352 n = read(cl->sock, buf, len);
353 }
354 return n;
355 }
356
357 static int
webSocketsEncodeHybi(rfbClientPtr cl,const char * src,int len,char ** dst)358 webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst)
359 {
360 int blen, ret = -1, sz = 0;
361 unsigned char opcode = '\0'; /* TODO: option! */
362 ws_header_t *header;
363 ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx;
364
365
366 /* Optional opcode:
367 * 0x0 - continuation
368 * 0x1 - text frame (base64 encode buf)
369 * 0x2 - binary frame (use raw buf)
370 * 0x8 - connection close
371 * 0x9 - ping
372 * 0xA - pong
373 **/
374 if (!len) {
375 /* nothing to encode */
376 return 0;
377 }
378
379 header = (ws_header_t *)wsctx->codeBufEncode;
380
381 if (wsctx->base64) {
382 opcode = WS_OPCODE_TEXT_FRAME;
383 /* calculate the resulting size */
384 blen = B64LEN(len);
385 } else {
386 opcode = WS_OPCODE_BINARY_FRAME;
387 blen = len;
388 }
389
390 header->b0 = 0x80 | (opcode & 0x0f);
391 if (blen <= 125) {
392 header->b1 = (uint8_t)blen;
393 sz = 2;
394 } else if (blen <= 65536) {
395 header->b1 = 0x7e;
396 header->u.s16.l16 = WS_HTON16((uint16_t)blen);
397 sz = 4;
398 } else {
399 header->b1 = 0x7f;
400 header->u.s64.l64 = WS_HTON64(blen);
401 sz = 10;
402 }
403
404 if (wsctx->base64) {
405 if (-1 == (ret = rfbBase64NtoP((unsigned char *)src, len, wsctx->codeBufEncode + sz, sizeof(wsctx->codeBufEncode) - sz))) {
406 rfbErr("%s: Base 64 encode failed\n", __func__);
407 } else {
408 if (ret != blen)
409 rfbErr("%s: Base 64 encode; something weird happened\n", __func__);
410 ret += sz;
411 }
412 } else {
413 memcpy(wsctx->codeBufEncode + sz, src, len);
414 ret = sz + len;
415 }
416
417 *dst = wsctx->codeBufEncode;
418
419 return ret;
420 }
421
422 int
webSocketsEncode(rfbClientPtr cl,const char * src,int len,char ** dst)423 webSocketsEncode(rfbClientPtr cl, const char *src, int len, char **dst)
424 {
425 return webSocketsEncodeHybi(cl, src, len, dst);
426 }
427
428 int
webSocketsDecode(rfbClientPtr cl,char * dst,int len)429 webSocketsDecode(rfbClientPtr cl, char *dst, int len)
430 {
431 ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx;
432 wsctx->ctxInfo.ctxPtr = cl;
433 return webSocketsDecodeHybi(wsctx, dst, len);
434 }
435
436 /**
437 * This is a stub function that was once used for Hixie-encoding.
438 * We keep it for API compatibility.
439 */
440 rfbBool
webSocketCheckDisconnect(rfbClientPtr cl)441 webSocketCheckDisconnect(rfbClientPtr cl)
442 {
443 return FALSE;
444 }
445
446
447 /* returns TRUE if there is data waiting to be read in our internal buffer
448 * or if is there any pending data in the buffer of the SSL implementation
449 */
450 rfbBool
webSocketsHasDataInBuffer(rfbClientPtr cl)451 webSocketsHasDataInBuffer(rfbClientPtr cl)
452 {
453 ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx;
454
455 if (wsctx && wsctx->readlen)
456 return TRUE;
457
458 return (cl->sslctx && rfbssl_pending(cl) > 0);
459 }
460