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#include <rfb/rfb.h>
30#include <resolv.h> /* __b64_ntop */
31/* errno */
32#include <errno.h>
33
34#include <byteswap.h>
35#include <string.h>
36#include "rfbconfig.h"
37#include "rfbssl.h"
38#include "rfbcrypto.h"
39
40#if defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && __BYTE_ORDER == __BIG_ENDIAN
41#define WS_NTOH64(n) (n)
42#define WS_NTOH32(n) (n)
43#define WS_NTOH16(n) (n)
44#define WS_HTON64(n) (n)
45#define WS_HTON16(n) (n)
46#else
47#define WS_NTOH64(n) bswap_64(n)
48#define WS_NTOH32(n) bswap_32(n)
49#define WS_NTOH16(n) bswap_16(n)
50#define WS_HTON64(n) bswap_64(n)
51#define WS_HTON16(n) bswap_16(n)
52#endif
53
54#define B64LEN(__x) (((__x + 2) / 3) * 12 / 3)
55#define WSHLENMAX 14  /* 2 + sizeof(uint64_t) + sizeof(uint32_t) */
56
57enum {
58  WEBSOCKETS_VERSION_HIXIE,
59  WEBSOCKETS_VERSION_HYBI
60};
61
62#if 0
63#include <sys/syscall.h>
64static int gettid() {
65    return (int)syscall(SYS_gettid);
66}
67#endif
68
69typedef int (*wsEncodeFunc)(rfbClientPtr cl, const char *src, int len, char **dst);
70typedef int (*wsDecodeFunc)(rfbClientPtr cl, char *dst, int len);
71
72typedef struct ws_ctx_s {
73    char codeBuf[B64LEN(UPDATE_BUF_SIZE) + WSHLENMAX]; /* base64 + maximum frame header length */
74    char readbuf[8192];
75    int readbufstart;
76    int readbuflen;
77    int dblen;
78    char carryBuf[3];                      /* For base64 carry-over */
79    int carrylen;
80    int version;
81    int base64;
82    wsEncodeFunc encode;
83    wsDecodeFunc decode;
84} ws_ctx_t;
85
86typedef union ws_mask_s {
87  char c[4];
88  uint32_t u;
89} ws_mask_t;
90
91typedef struct __attribute__ ((__packed__)) ws_header_s {
92  unsigned char b0;
93  unsigned char b1;
94  union {
95    struct __attribute__ ((__packed__)) {
96      uint16_t l16;
97      ws_mask_t m16;
98    };
99    struct __attribute__ ((__packed__)) {
100      uint64_t l64;
101      ws_mask_t m64;
102    };
103    ws_mask_t m;
104  };
105} ws_header_t;
106
107enum
108{
109    WS_OPCODE_CONTINUATION = 0x0,
110    WS_OPCODE_TEXT_FRAME,
111    WS_OPCODE_BINARY_FRAME,
112    WS_OPCODE_CLOSE = 0x8,
113    WS_OPCODE_PING,
114    WS_OPCODE_PONG
115};
116
117#define FLASH_POLICY_RESPONSE "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>\n"
118#define SZ_FLASH_POLICY_RESPONSE 93
119
120/*
121 * draft-ietf-hybi-thewebsocketprotocol-10
122 * 5.2.2. Sending the Server's Opening Handshake
123 */
124#define GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
125
126#define SERVER_HANDSHAKE_HIXIE "HTTP/1.1 101 Web Socket Protocol Handshake\r\n\
127Upgrade: WebSocket\r\n\
128Connection: Upgrade\r\n\
129%sWebSocket-Origin: %s\r\n\
130%sWebSocket-Location: %s://%s%s\r\n\
131%sWebSocket-Protocol: %s\r\n\
132\r\n%s"
133
134#define SERVER_HANDSHAKE_HYBI "HTTP/1.1 101 Switching Protocols\r\n\
135Upgrade: websocket\r\n\
136Connection: Upgrade\r\n\
137Sec-WebSocket-Accept: %s\r\n\
138Sec-WebSocket-Protocol: %s\r\n\
139\r\n"
140
141
142#define WEBSOCKETS_CLIENT_CONNECT_WAIT_MS 100
143#define WEBSOCKETS_CLIENT_SEND_WAIT_MS 100
144#define WEBSOCKETS_MAX_HANDSHAKE_LEN 4096
145
146#if defined(__linux__) && defined(NEED_TIMEVAL)
147struct timeval
148{
149   long int tv_sec,tv_usec;
150}
151;
152#endif
153
154static rfbBool webSocketsHandshake(rfbClientPtr cl, char *scheme);
155void webSocketsGenMd5(char * target, char *key1, char *key2, char *key3);
156
157static int webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst);
158static int webSocketsEncodeHixie(rfbClientPtr cl, const char *src, int len, char **dst);
159static int webSocketsDecodeHybi(rfbClientPtr cl, char *dst, int len);
160static int webSocketsDecodeHixie(rfbClientPtr cl, char *dst, int len);
161
162static int
163min (int a, int b) {
164    return a < b ? a : b;
165}
166
167static void webSocketsGenSha1Key(char *target, int size, char *key)
168{
169    struct iovec iov[2];
170    unsigned char hash[20];
171
172    iov[0].iov_base = key;
173    iov[0].iov_len = strlen(key);
174    iov[1].iov_base = GUID;
175    iov[1].iov_len = sizeof(GUID) - 1;
176    digestsha1(iov, 2, hash);
177    if (-1 == __b64_ntop(hash, sizeof(hash), target, size))
178	rfbErr("b64_ntop failed\n");
179}
180
181/*
182 * rfbWebSocketsHandshake is called to handle new WebSockets connections
183 */
184
185rfbBool
186webSocketsCheck (rfbClientPtr cl)
187{
188    char bbuf[4], *scheme;
189    int ret;
190
191    ret = rfbPeekExactTimeout(cl, bbuf, 4,
192                                   WEBSOCKETS_CLIENT_CONNECT_WAIT_MS);
193    if ((ret < 0) && (errno == ETIMEDOUT)) {
194      rfbLog("Normal socket connection\n");
195      return TRUE;
196    } else if (ret <= 0) {
197      rfbErr("webSocketsHandshake: unknown connection error\n");
198      return FALSE;
199    }
200
201    if (strncmp(bbuf, "<", 1) == 0) {
202        rfbLog("Got Flash policy request, sending response\n");
203        if (rfbWriteExact(cl, FLASH_POLICY_RESPONSE,
204                          SZ_FLASH_POLICY_RESPONSE) < 0) {
205            rfbErr("webSocketsHandshake: failed sending Flash policy response");
206        }
207        return FALSE;
208    } else if (strncmp(bbuf, "\x16", 1) == 0 || strncmp(bbuf, "\x80", 1) == 0) {
209        rfbLog("Got TLS/SSL WebSockets connection\n");
210        if (-1 == rfbssl_init(cl)) {
211	  rfbErr("webSocketsHandshake: rfbssl_init failed\n");
212	  return FALSE;
213	}
214	ret = rfbPeekExactTimeout(cl, bbuf, 4, WEBSOCKETS_CLIENT_CONNECT_WAIT_MS);
215        scheme = "wss";
216    } else {
217        scheme = "ws";
218    }
219
220    if (strncmp(bbuf, "GET ", 4) != 0) {
221      rfbErr("webSocketsHandshake: invalid client header\n");
222      return FALSE;
223    }
224
225    rfbLog("Got '%s' WebSockets handshake\n", scheme);
226
227    if (!webSocketsHandshake(cl, scheme)) {
228        return FALSE;
229    }
230    /* Start WebSockets framing */
231    return TRUE;
232}
233
234static rfbBool
235webSocketsHandshake(rfbClientPtr cl, char *scheme)
236{
237    char *buf, *response, *line;
238    int n, linestart = 0, len = 0, llen, base64 = TRUE;
239    char prefix[5], trailer[17];
240    char *path = NULL, *host = NULL, *origin = NULL, *protocol = NULL;
241    char *key1 = NULL, *key2 = NULL, *key3 = NULL;
242    char *sec_ws_origin = NULL;
243    char *sec_ws_key = NULL;
244    char sec_ws_version = 0;
245    ws_ctx_t *wsctx = NULL;
246
247    buf = (char *) malloc(WEBSOCKETS_MAX_HANDSHAKE_LEN);
248    if (!buf) {
249        rfbLogPerror("webSocketsHandshake: malloc");
250        return FALSE;
251    }
252    response = (char *) malloc(WEBSOCKETS_MAX_HANDSHAKE_LEN);
253    if (!response) {
254        free(buf);
255        rfbLogPerror("webSocketsHandshake: malloc");
256        return FALSE;
257    }
258
259    while (len < WEBSOCKETS_MAX_HANDSHAKE_LEN-1) {
260        if ((n = rfbReadExactTimeout(cl, buf+len, 1,
261                                     WEBSOCKETS_CLIENT_SEND_WAIT_MS)) <= 0) {
262            if ((n < 0) && (errno == ETIMEDOUT)) {
263                break;
264            }
265            if (n == 0)
266                rfbLog("webSocketsHandshake: client gone\n");
267            else
268                rfbLogPerror("webSocketsHandshake: read");
269            free(response);
270            free(buf);
271            return FALSE;
272        }
273
274        len += 1;
275        llen = len - linestart;
276        if (((llen >= 2)) && (buf[len-1] == '\n')) {
277            line = buf+linestart;
278            if ((llen == 2) && (strncmp("\r\n", line, 2) == 0)) {
279                if (key1 && key2) {
280                    if ((n = rfbReadExact(cl, buf+len, 8)) <= 0) {
281                        if ((n < 0) && (errno == ETIMEDOUT)) {
282                            break;
283                        }
284                        if (n == 0)
285                            rfbLog("webSocketsHandshake: client gone\n");
286                        else
287                            rfbLogPerror("webSocketsHandshake: read");
288                        free(response);
289                        free(buf);
290                        return FALSE;
291                    }
292                    rfbLog("Got key3\n");
293                    key3 = buf+len;
294                    len += 8;
295                } else {
296                    buf[len] = '\0';
297                }
298                break;
299            } else if ((llen >= 16) && ((strncmp("GET ", line, min(llen,4))) == 0)) {
300                /* 16 = 4 ("GET ") + 1 ("/.*") + 11 (" HTTP/1.1\r\n") */
301                path = line+4;
302                buf[len-11] = '\0'; /* Trim trailing " HTTP/1.1\r\n" */
303                cl->wspath = strdup(path);
304                /* rfbLog("Got path: %s\n", path); */
305            } else if ((strncasecmp("host: ", line, min(llen,6))) == 0) {
306                host = line+6;
307                buf[len-2] = '\0';
308                /* rfbLog("Got host: %s\n", host); */
309            } else if ((strncasecmp("origin: ", line, min(llen,8))) == 0) {
310                origin = line+8;
311                buf[len-2] = '\0';
312                /* rfbLog("Got origin: %s\n", origin); */
313            } else if ((strncasecmp("sec-websocket-key1: ", line, min(llen,20))) == 0) {
314                key1 = line+20;
315                buf[len-2] = '\0';
316                /* rfbLog("Got key1: %s\n", key1); */
317            } else if ((strncasecmp("sec-websocket-key2: ", line, min(llen,20))) == 0) {
318                key2 = line+20;
319                buf[len-2] = '\0';
320                /* rfbLog("Got key2: %s\n", key2); */
321            /* HyBI */
322
323	    } else if ((strncasecmp("sec-websocket-protocol: ", line, min(llen,24))) == 0) {
324                protocol = line+24;
325                buf[len-2] = '\0';
326                rfbLog("Got protocol: %s\n", protocol);
327            } else if ((strncasecmp("sec-websocket-origin: ", line, min(llen,22))) == 0) {
328		sec_ws_origin = line+22;
329                buf[len-2] = '\0';
330            } else if ((strncasecmp("sec-websocket-key: ", line, min(llen,19))) == 0) {
331		sec_ws_key = line+19;
332                buf[len-2] = '\0';
333            } else if ((strncasecmp("sec-websocket-version: ", line, min(llen,23))) == 0) {
334		sec_ws_version = strtol(line+23, NULL, 10);
335                buf[len-2] = '\0';
336	    }
337
338            linestart = len;
339        }
340    }
341
342    if (!(path && host && (origin || sec_ws_origin))) {
343        rfbErr("webSocketsHandshake: incomplete client handshake\n");
344        free(response);
345        free(buf);
346        return FALSE;
347    }
348
349    if ((protocol) && (strstr(protocol, "binary"))) {
350        if (! sec_ws_version) {
351            rfbErr("webSocketsHandshake: 'binary' protocol not supported with Hixie\n");
352            free(response);
353            free(buf);
354            return FALSE;
355        }
356        rfbLog("  - webSocketsHandshake: using binary/raw encoding\n");
357        base64 = FALSE;
358        protocol = "binary";
359    } else {
360        rfbLog("  - webSocketsHandshake: using base64 encoding\n");
361        base64 = TRUE;
362        if ((protocol) && (strstr(protocol, "base64"))) {
363            protocol = "base64";
364        } else {
365            protocol = "";
366        }
367    }
368
369    /*
370     * Generate the WebSockets server response based on the the headers sent
371     * by the client.
372     */
373
374    if (sec_ws_version) {
375	char accept[B64LEN(SHA1_HASH_SIZE) + 1];
376	rfbLog("  - WebSockets client version hybi-%02d\n", sec_ws_version);
377	webSocketsGenSha1Key(accept, sizeof(accept), sec_ws_key);
378	len = snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN,
379		 SERVER_HANDSHAKE_HYBI, accept, protocol);
380    } else {
381	/* older hixie handshake, this could be removed if
382	 * a final standard is established */
383	if (!(key1 && key2 && key3)) {
384	    rfbLog("  - WebSockets client version hixie-75\n");
385	    prefix[0] = '\0';
386	    trailer[0] = '\0';
387	} else {
388	    rfbLog("  - WebSockets client version hixie-76\n");
389	    snprintf(prefix, 5, "Sec-");
390	    webSocketsGenMd5(trailer, key1, key2, key3);
391	}
392	len = snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN,
393		 SERVER_HANDSHAKE_HIXIE, prefix, origin, prefix, scheme,
394		 host, path, prefix, protocol, trailer);
395    }
396
397    if (rfbWriteExact(cl, response, len) < 0) {
398        rfbErr("webSocketsHandshake: failed sending WebSockets response\n");
399        free(response);
400        free(buf);
401        return FALSE;
402    }
403    /* rfbLog("webSocketsHandshake: %s\n", response); */
404    free(response);
405    free(buf);
406
407
408    wsctx = calloc(1, sizeof(ws_ctx_t));
409    if (sec_ws_version) {
410	wsctx->version = WEBSOCKETS_VERSION_HYBI;
411	wsctx->encode = webSocketsEncodeHybi;
412	wsctx->decode = webSocketsDecodeHybi;
413    } else {
414	wsctx->version = WEBSOCKETS_VERSION_HIXIE;
415	wsctx->encode = webSocketsEncodeHixie;
416	wsctx->decode = webSocketsDecodeHixie;
417    }
418    wsctx->base64 = base64;
419    cl->wsctx = (wsCtx *)wsctx;
420    return TRUE;
421}
422
423void
424webSocketsGenMd5(char * target, char *key1, char *key2, char *key3)
425{
426    unsigned int i, spaces1 = 0, spaces2 = 0;
427    unsigned long num1 = 0, num2 = 0;
428    unsigned char buf[17];
429    struct iovec iov[1];
430
431    for (i=0; i < strlen(key1); i++) {
432        if (key1[i] == ' ') {
433            spaces1 += 1;
434        }
435        if ((key1[i] >= 48) && (key1[i] <= 57)) {
436            num1 = num1 * 10 + (key1[i] - 48);
437        }
438    }
439    num1 = num1 / spaces1;
440
441    for (i=0; i < strlen(key2); i++) {
442        if (key2[i] == ' ') {
443            spaces2 += 1;
444        }
445        if ((key2[i] >= 48) && (key2[i] <= 57)) {
446            num2 = num2 * 10 + (key2[i] - 48);
447        }
448    }
449    num2 = num2 / spaces2;
450
451    /* Pack it big-endian */
452    buf[0] = (num1 & 0xff000000) >> 24;
453    buf[1] = (num1 & 0xff0000) >> 16;
454    buf[2] = (num1 & 0xff00) >> 8;
455    buf[3] =  num1 & 0xff;
456
457    buf[4] = (num2 & 0xff000000) >> 24;
458    buf[5] = (num2 & 0xff0000) >> 16;
459    buf[6] = (num2 & 0xff00) >> 8;
460    buf[7] =  num2 & 0xff;
461
462    strncpy((char *)buf+8, key3, 8);
463    buf[16] = '\0';
464
465    iov[0].iov_base = buf;
466    iov[0].iov_len = 16;
467    digestmd5(iov, 1, target);
468    target[16] = '\0';
469
470    return;
471}
472
473static int
474webSocketsEncodeHixie(rfbClientPtr cl, const char *src, int len, char **dst)
475{
476    int sz = 0;
477    ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx;
478
479    wsctx->codeBuf[sz++] = '\x00';
480    len = __b64_ntop((unsigned char *)src, len, wsctx->codeBuf+sz, sizeof(wsctx->codeBuf) - (sz + 1));
481    if (len < 0) {
482        return len;
483    }
484    sz += len;
485
486    wsctx->codeBuf[sz++] = '\xff';
487    *dst = wsctx->codeBuf;
488    return sz;
489}
490
491static int
492ws_read(rfbClientPtr cl, char *buf, int len)
493{
494    int n;
495    if (cl->sslctx) {
496	n = rfbssl_read(cl, buf, len);
497    } else {
498	n = read(cl->sock, buf, len);
499    }
500    return n;
501}
502
503static int
504ws_peek(rfbClientPtr cl, char *buf, int len)
505{
506    int n;
507    if (cl->sslctx) {
508	n = rfbssl_peek(cl, buf, len);
509    } else {
510	while (-1 == (n = recv(cl->sock, buf, len, MSG_PEEK))) {
511	    if (errno != EAGAIN)
512		break;
513	}
514    }
515    return n;
516}
517
518static int
519webSocketsDecodeHixie(rfbClientPtr cl, char *dst, int len)
520{
521    int retlen = 0, n, i, avail, modlen, needlen;
522    char *buf, *end = NULL;
523    ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx;
524
525    buf = wsctx->codeBuf;
526
527    n = ws_peek(cl, buf, len*2+2);
528
529    if (n <= 0) {
530        /* save errno because rfbErr() will tamper it */
531        int olderrno = errno;
532        rfbErr("%s: peek (%d) %m\n", __func__, errno);
533        errno = olderrno;
534        return n;
535    }
536
537
538    /* Base64 encoded WebSockets stream */
539
540    if (buf[0] == '\xff') {
541        i = ws_read(cl, buf, 1); /* Consume marker */
542        buf++;
543        n--;
544    }
545    if (n == 0) {
546        errno = EAGAIN;
547        return -1;
548    }
549    if (buf[0] == '\x00') {
550        i = ws_read(cl, buf, 1); /* Consume marker */
551        buf++;
552        n--;
553    }
554    if (n == 0) {
555        errno = EAGAIN;
556        return -1;
557    }
558
559    /* end = memchr(buf, '\xff', len*2+2); */
560    end = memchr(buf, '\xff', n);
561    if (!end) {
562        end = buf + n;
563    }
564    avail = end - buf;
565
566    len -= wsctx->carrylen;
567
568    /* Determine how much base64 data we need */
569    modlen = len + (len+2)/3;
570    needlen = modlen;
571    if (needlen % 4) {
572        needlen += 4 - (needlen % 4);
573    }
574
575    if (needlen > avail) {
576        /* rfbLog("Waiting for more base64 data\n"); */
577        errno = EAGAIN;
578        return -1;
579    }
580
581    /* Any carryover from previous decode */
582    for (i=0; i < wsctx->carrylen; i++) {
583        /* rfbLog("Adding carryover %d\n", wsctx->carryBuf[i]); */
584        dst[i] = wsctx->carryBuf[i];
585        retlen += 1;
586    }
587
588    /* Decode the rest of what we need */
589    buf[needlen] = '\x00';  /* Replace end marker with end of string */
590    /* rfbLog("buf: %s\n", buf); */
591    n = __b64_pton(buf, (unsigned char *)dst+retlen, 2+len);
592    if (n < len) {
593        rfbErr("Base64 decode error\n");
594        errno = EIO;
595        return -1;
596    }
597    retlen += n;
598
599    /* Consume the data from socket */
600    i = ws_read(cl, buf, needlen);
601
602    wsctx->carrylen = n - len;
603    retlen -= wsctx->carrylen;
604    for (i=0; i < wsctx->carrylen; i++) {
605        /* rfbLog("Saving carryover %d\n", dst[retlen + i]); */
606        wsctx->carryBuf[i] = dst[retlen + i];
607    }
608
609    /* rfbLog("<< webSocketsDecode, retlen: %d\n", retlen); */
610    return retlen;
611}
612
613static int
614webSocketsDecodeHybi(rfbClientPtr cl, char *dst, int len)
615{
616    char *buf, *payload;
617    uint32_t *payload32;
618    int ret = -1, result = -1;
619    int total = 0;
620    ws_mask_t mask;
621    ws_header_t *header;
622    int i;
623    unsigned char opcode;
624    ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx;
625    int flength, fhlen;
626    /* int fin; */ /* not used atm */
627
628    // 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));
629
630    if (wsctx->readbuflen) {
631      /* simply return what we have */
632      if (wsctx->readbuflen > len) {
633	memcpy(dst, wsctx->readbuf +  wsctx->readbufstart, len);
634	result = len;
635	wsctx->readbuflen -= len;
636	wsctx->readbufstart += len;
637      } else {
638	memcpy(dst, wsctx->readbuf +  wsctx->readbufstart, wsctx->readbuflen);
639	result = wsctx->readbuflen;
640	wsctx->readbuflen = 0;
641	wsctx->readbufstart = 0;
642      }
643      goto spor;
644    }
645
646    buf = wsctx->codeBuf;
647    header = (ws_header_t *)wsctx->codeBuf;
648
649    ret = ws_peek(cl, buf, B64LEN(len) + WSHLENMAX);
650
651    if (ret < 2) {
652        /* save errno because rfbErr() will tamper it */
653        if (-1 == ret) {
654            int olderrno = errno;
655            rfbErr("%s: peek; %m\n", __func__);
656            errno = olderrno;
657        } else if (0 == ret) {
658            result = 0;
659        } else {
660            errno = EAGAIN;
661        }
662        goto spor;
663    }
664
665    opcode = header->b0 & 0x0f;
666    /* fin = (header->b0 & 0x80) >> 7; */ /* not used atm */
667    flength = header->b1 & 0x7f;
668
669    /*
670     * 4.3. Client-to-Server Masking
671     *
672     * The client MUST mask all frames sent to the server.  A server MUST
673     * close the connection upon receiving a frame with the MASK bit set to 0.
674    **/
675    if (!(header->b1 & 0x80)) {
676	rfbErr("%s: got frame without mask\n", __func__, ret);
677	errno = EIO;
678	goto spor;
679    }
680
681    if (flength < 126) {
682	fhlen = 2;
683	mask = header->m;
684    } else if (flength == 126 && 4 <= ret) {
685	flength = WS_NTOH16(header->l16);
686	fhlen = 4;
687	mask = header->m16;
688    } else if (flength == 127 && 10 <= ret) {
689	flength = WS_NTOH64(header->l64);
690	fhlen = 10;
691	mask = header->m64;
692    } else {
693      /* Incomplete frame header */
694      rfbErr("%s: incomplete frame header\n", __func__, ret);
695      errno = EIO;
696      goto spor;
697    }
698
699    /* absolute length of frame */
700    total = fhlen + flength + 4;
701    payload = buf + fhlen + 4; /* header length + mask */
702
703    if (-1 == (ret = ws_read(cl, buf, total))) {
704      int olderrno = errno;
705      rfbErr("%s: read; %m", __func__);
706      errno = olderrno;
707      return ret;
708    } else if (ret < total) {
709      /* GT TODO: hmm? */
710      rfbLog("%s: read; got partial data\n", __func__);
711    } else {
712      buf[ret] = '\0';
713    }
714
715    /* process 1 frame (32 bit op) */
716    payload32 = (uint32_t *)payload;
717    for (i = 0; i < flength / 4; i++) {
718	payload32[i] ^= mask.u;
719    }
720    /* process the remaining bytes (if any) */
721    for (i*=4; i < flength; i++) {
722	payload[i] ^= mask.c[i % 4];
723    }
724
725    switch (opcode) {
726      case WS_OPCODE_CLOSE:
727	rfbLog("got closure, reason %d\n", WS_NTOH16(((uint16_t *)payload)[0]));
728	errno = ECONNRESET;
729	break;
730      case WS_OPCODE_TEXT_FRAME:
731	if (-1 == (flength = __b64_pton(payload, (unsigned char *)wsctx->codeBuf, sizeof(wsctx->codeBuf)))) {
732	  rfbErr("%s: Base64 decode error; %m\n", __func__);
733	  break;
734	}
735	payload = wsctx->codeBuf;
736	/* fall through */
737      case WS_OPCODE_BINARY_FRAME:
738	if (flength > len) {
739	  memcpy(wsctx->readbuf, payload + len, flength - len);
740	  wsctx->readbufstart = 0;
741	  wsctx->readbuflen = flength - len;
742	  flength = len;
743	}
744	memcpy(dst, payload, flength);
745	result = flength;
746	break;
747      default:
748	rfbErr("%s: unhandled opcode %d, b0: %02x, b1: %02x\n", __func__, (int)opcode, header->b0, header->b1);
749    }
750
751    /* single point of return, if someone has questions :-) */
752spor:
753    /* rfbLog("%s: ret: %d/%d\n", __func__, result, len); */
754    return result;
755}
756
757static int
758webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst)
759{
760    int blen, ret = -1, sz = 0;
761    unsigned char opcode = '\0'; /* TODO: option! */
762    ws_header_t *header;
763    ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx;
764
765
766    /* Optional opcode:
767     *   0x0 - continuation
768     *   0x1 - text frame (base64 encode buf)
769     *   0x2 - binary frame (use raw buf)
770     *   0x8 - connection close
771     *   0x9 - ping
772     *   0xA - pong
773    **/
774    if (!len) {
775	  /* nothing to encode */
776	  return 0;
777    }
778
779    header = (ws_header_t *)wsctx->codeBuf;
780
781    if (wsctx->base64) {
782	opcode = WS_OPCODE_TEXT_FRAME;
783	/* calculate the resulting size */
784	blen = B64LEN(len);
785    } else {
786	blen = len;
787    }
788
789    header->b0 = 0x80 | (opcode & 0x0f);
790    if (blen <= 125) {
791      header->b1 = (uint8_t)blen;
792      sz = 2;
793    } else if (blen <= 65536) {
794      header->b1 = 0x7e;
795      header->l16 = WS_HTON16((uint16_t)blen);
796      sz = 4;
797    } else {
798      header->b1 = 0x7f;
799      header->l64 = WS_HTON64(blen);
800      sz = 10;
801    }
802
803    if (wsctx->base64) {
804        if (-1 == (ret = __b64_ntop((unsigned char *)src, len, wsctx->codeBuf + sz, sizeof(wsctx->codeBuf) - sz))) {
805	  rfbErr("%s: Base 64 encode failed\n", __func__);
806	} else {
807	  if (ret != blen)
808	    rfbErr("%s: Base 64 encode; something weird happened\n", __func__);
809	  ret += sz;
810	}
811    } else {
812      memcpy(wsctx->codeBuf + sz, src, len);
813      ret =  sz + len;
814    }
815
816    *dst = wsctx->codeBuf;
817    return ret;
818}
819
820int
821webSocketsEncode(rfbClientPtr cl, const char *src, int len, char **dst)
822{
823    return ((ws_ctx_t *)cl->wsctx)->encode(cl, src, len, dst);
824}
825
826int
827webSocketsDecode(rfbClientPtr cl, char *dst, int len)
828{
829    return ((ws_ctx_t *)cl->wsctx)->decode(cl, dst, len);
830}
831
832
833/* returns TRUE if client sent a close frame or a single 'end of frame'
834 * marker was received, FALSE otherwise
835 *
836 * Note: This is a Hixie-only hack!
837 **/
838rfbBool
839webSocketCheckDisconnect(rfbClientPtr cl)
840{
841    ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx;
842    /* With Base64 encoding we need at least 4 bytes */
843    char peekbuf[4];
844    int n;
845
846    if (wsctx->version == WEBSOCKETS_VERSION_HYBI)
847	return FALSE;
848
849    if (cl->sslctx)
850	n = rfbssl_peek(cl, peekbuf, 4);
851    else
852	n = recv(cl->sock, peekbuf, 4, MSG_PEEK);
853
854    if (n <= 0) {
855	if (n != 0)
856	    rfbErr("%s: peek; %m", __func__);
857	rfbCloseClient(cl);
858	return TRUE;
859    }
860
861    if (peekbuf[0] == '\xff') {
862	int doclose = 0;
863	/* Make sure we don't miss a client disconnect on an end frame
864	 * marker. Because we use a peek buffer in some cases it is not
865	 * applicable to wait for more data per select(). */
866	switch (n) {
867	    case 3:
868		if (peekbuf[1] == '\xff' && peekbuf[2] == '\x00')
869		    doclose = 1;
870		break;
871	    case 2:
872		if (peekbuf[1] == '\x00')
873		    doclose = 1;
874		break;
875	    default:
876		return FALSE;
877	}
878
879	if (cl->sslctx)
880	    n = rfbssl_read(cl, peekbuf, n);
881	else
882	    n = read(cl->sock, peekbuf, n);
883
884	if (doclose) {
885	    rfbErr("%s: websocket close frame received\n", __func__);
886	    rfbCloseClient(cl);
887	}
888	return TRUE;
889    }
890    return FALSE;
891}
892
893