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