LibVNCServer/LibVNCClient
websockets.c
Go to the documentation of this file.
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
103 min (int a, int b) {
104  return a < b ? a : b;
105 }
106 
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
123 webSocketsCheck (rfbClientPtr cl)
124 {
125  char bbuf[4], *scheme;
126  int ret;
127 
128  ret = rfbPeekExactTimeout(cl, bbuf, 4,
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  }
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
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,
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,
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;
340  cl->wsctx = (wsCtx *)wsctx;
341  return TRUE;
342 }
343 
344 static int
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
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
423 webSocketsEncode(rfbClientPtr cl, const char *src, int len, char **dst)
424 {
425  return webSocketsEncodeHybi(cl, src, len, dst);
426 }
427 
428 int
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 
440 rfbBool
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
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 }
int rfbReadExactTimeout(rfbClientPtr cl, char *buf, int len, int timeout)
Definition: sockets.c:624
#define SERVER_HANDSHAKE_HYBI_NO_PROTOCOL
Definition: websockets.c:77
unsigned char b1
Definition: ws_decode.h:75
rfbBool webSocketsCheck(rfbClientPtr cl)
Definition: websockets.c:123
rfbLogProc rfbErr
Definition: main.c:264
int rfbssl_read(rfbClientPtr cl, char *buf, int bufsize)
int rfbPeekExactTimeout(rfbClientPtr cl, char *buf, int len, int timeout)
Definition: sockets.c:716
#define WS_HTON64(n)
Definition: ws_decode.h:21
#define TRUE
Definition: rfbproto.h:112
union ws_header_t::@8 u
#define WEBSOCKETS_MAX_HANDSHAKE_LEN
Definition: websockets.c:85
struct ws_header_t::@8::@9 s16
int8_t rfbBool
Definition: rfbproto.h:108
unsigned char b0
Definition: ws_decode.h:74
rfbBool webSocketCheckDisconnect(rfbClientPtr cl)
This is a stub function that was once used for Hixie-encoding.
Definition: websockets.c:441
#define GUID
Definition: websockets.c:68
#define WEBSOCKETS_CLIENT_SEND_WAIT_MS
Definition: websockets.c:84
int webSocketsEncode(rfbClientPtr cl, const char *src, int len, char **dst)
Definition: websockets.c:423
int rfbWriteExact(rfbClientPtr cl, const char *buf, int len)
Definition: sockets.c:792
struct _wsCtx wsCtx
Definition: rfb.h:412
int rfbReadExact(rfbClientPtr cl, char *buf, int len)
Definition: sockets.c:700
#define WS_HTON16(n)
Definition: ws_decode.h:22
#define B64LEN(__x)
Definition: ws_decode.h:26
int rfbssl_init(rfbClientPtr cl)
int webSocketsDecode(rfbClientPtr cl, char *dst, int len)
Definition: websockets.c:429
void rfbLogPerror(const char *str)
Definition: main.c:266
void hybiDecodeCleanupComplete(ws_ctx_t *wsctx)
Definition: ws_decode.c:61
#define WEBSOCKETS_CLIENT_CONNECT_WAIT_MS
Definition: websockets.c:83
rfbLogProc rfbLog
Definition: main.c:263
#define SERVER_HANDSHAKE_HYBI
Definition: websockets.c:70
struct ws_header_t::@8::@10 s64
#define FALSE
Definition: rfbproto.h:110
rfbBool webSocketsHasDataInBuffer(rfbClientPtr cl)
Definition: websockets.c:451
int webSocketsDecodeHybi(ws_ctx_t *wsctx, char *dst, int len)
Read function for websocket-socket emulation.
Definition: ws_decode.c:511
int rfbssl_pending(rfbClientPtr cl)