Home | History | Annotate | Download | only in libvncserver
      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 
     57 enum {
     58   WEBSOCKETS_VERSION_HIXIE,
     59   WEBSOCKETS_VERSION_HYBI
     60 };
     61 
     62 #if 0
     63 #include <sys/syscall.h>
     64 static int gettid() {
     65     return (int)syscall(SYS_gettid);
     66 }
     67 #endif
     68 
     69 typedef int (*wsEncodeFunc)(rfbClientPtr cl, const char *src, int len, char **dst);
     70 typedef int (*wsDecodeFunc)(rfbClientPtr cl, char *dst, int len);
     71 
     72 typedef 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 
     86 typedef union ws_mask_s {
     87   char c[4];
     88   uint32_t u;
     89 } ws_mask_t;
     90 
     91 typedef 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 
    107 enum
    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\
    127 Upgrade: WebSocket\r\n\
    128 Connection: 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\
    135 Upgrade: websocket\r\n\
    136 Connection: Upgrade\r\n\
    137 Sec-WebSocket-Accept: %s\r\n\
    138 Sec-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)
    147 struct timeval
    148 {
    149    long int tv_sec,tv_usec;
    150 }
    151 ;
    152 #endif
    153 
    154 static rfbBool webSocketsHandshake(rfbClientPtr cl, char *scheme);
    155 void webSocketsGenMd5(char * target, char *key1, char *key2, char *key3);
    156 
    157 static int webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst);
    158 static int webSocketsEncodeHixie(rfbClientPtr cl, const char *src, int len, char **dst);
    159 static int webSocketsDecodeHybi(rfbClientPtr cl, char *dst, int len);
    160 static int webSocketsDecodeHixie(rfbClientPtr cl, char *dst, int len);
    161 
    162 static int
    163 min (int a, int b) {
    164     return a < b ? a : b;
    165 }
    166 
    167 static 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 
    185 rfbBool
    186 webSocketsCheck (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 
    234 static rfbBool
    235 webSocketsHandshake(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 
    423 void
    424 webSocketsGenMd5(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 
    473 static int
    474 webSocketsEncodeHixie(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 
    491 static int
    492 ws_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 
    503 static int
    504 ws_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 
    518 static int
    519 webSocketsDecodeHixie(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 
    613 static int
    614 webSocketsDecodeHybi(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 :-) */
    752 spor:
    753     /* rfbLog("%s: ret: %d/%d\n", __func__, result, len); */
    754     return result;
    755 }
    756 
    757 static int
    758 webSocketsEncodeHybi(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 
    820 int
    821 webSocketsEncode(rfbClientPtr cl, const char *src, int len, char **dst)
    822 {
    823     return ((ws_ctx_t *)cl->wsctx)->encode(cl, src, len, dst);
    824 }
    825 
    826 int
    827 webSocketsDecode(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  **/
    838 rfbBool
    839 webSocketCheckDisconnect(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