qemu/ui/vnc-ws.c
<<
>>
Prefs
   1/*
   2 * QEMU VNC display driver: Websockets support
   3 *
   4 * Copyright (C) 2010 Joel Martin
   5 * Copyright (C) 2012 Tim Hardeck
   6 *
   7 * This is free software; you can redistribute it and/or modify
   8 * it under the terms of the GNU General Public License as published by
   9 * the Free Software Foundation; either version 2 of the License, or
  10 * (at your option) any later version.
  11 *
  12 * This software is distributed in the hope that it will be useful,
  13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15 * GNU General Public License for more details.
  16 *
  17 * You should have received a copy of the GNU General Public License
  18 * along with this software; if not, see <http://www.gnu.org/licenses/>.
  19 */
  20
  21#include "vnc.h"
  22#include "qemu/main-loop.h"
  23
  24#ifdef CONFIG_VNC_TLS
  25#include "qemu/sockets.h"
  26
  27static void vncws_tls_handshake_io(void *opaque);
  28
  29static int vncws_start_tls_handshake(struct VncState *vs)
  30{
  31    int ret = gnutls_handshake(vs->ws_tls.session);
  32
  33    if (ret < 0) {
  34        if (!gnutls_error_is_fatal(ret)) {
  35            VNC_DEBUG("Handshake interrupted (blocking)\n");
  36            if (!gnutls_record_get_direction(vs->ws_tls.session)) {
  37                qemu_set_fd_handler(vs->csock, vncws_tls_handshake_io,
  38                                    NULL, vs);
  39            } else {
  40                qemu_set_fd_handler(vs->csock, NULL, vncws_tls_handshake_io,
  41                                    vs);
  42            }
  43            return 0;
  44        }
  45        VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret));
  46        vnc_client_error(vs);
  47        return -1;
  48    }
  49
  50    VNC_DEBUG("Handshake done, switching to TLS data mode\n");
  51    vs->ws_tls.wiremode = VNC_WIREMODE_TLS;
  52    qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read, NULL, vs);
  53
  54    return 0;
  55}
  56
  57static void vncws_tls_handshake_io(void *opaque)
  58{
  59    struct VncState *vs = (struct VncState *)opaque;
  60
  61    VNC_DEBUG("Handshake IO continue\n");
  62    vncws_start_tls_handshake(vs);
  63}
  64
  65void vncws_tls_handshake_peek(void *opaque)
  66{
  67    VncState *vs = opaque;
  68    long ret;
  69
  70    if (!vs->ws_tls.session) {
  71        char peek[4];
  72        ret = qemu_recv(vs->csock, peek, sizeof(peek), MSG_PEEK);
  73        if (ret && (strncmp(peek, "\x16", 1) == 0
  74                    || strncmp(peek, "\x80", 1) == 0)) {
  75            VNC_DEBUG("TLS Websocket connection recognized");
  76            vnc_tls_client_setup(vs, 1);
  77            vncws_start_tls_handshake(vs);
  78        } else {
  79            vncws_handshake_read(vs);
  80        }
  81    } else {
  82        qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read, NULL, vs);
  83    }
  84}
  85#endif /* CONFIG_VNC_TLS */
  86
  87void vncws_handshake_read(void *opaque)
  88{
  89    VncState *vs = opaque;
  90    uint8_t *handshake_end;
  91    long ret;
  92    buffer_reserve(&vs->ws_input, 4096);
  93    ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), 4096);
  94
  95    if (!ret) {
  96        if (vs->csock == -1) {
  97            vnc_disconnect_finish(vs);
  98        }
  99        return;
 100    }
 101    vs->ws_input.offset += ret;
 102
 103    handshake_end = (uint8_t *)g_strstr_len((char *)vs->ws_input.buffer,
 104            vs->ws_input.offset, WS_HANDSHAKE_END);
 105    if (handshake_end) {
 106        qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
 107        vncws_process_handshake(vs, vs->ws_input.buffer, vs->ws_input.offset);
 108        buffer_advance(&vs->ws_input, handshake_end - vs->ws_input.buffer +
 109                strlen(WS_HANDSHAKE_END));
 110    }
 111}
 112
 113
 114long vnc_client_read_ws(VncState *vs)
 115{
 116    int ret, err;
 117    uint8_t *payload;
 118    size_t payload_size, frame_size;
 119    VNC_DEBUG("Read websocket %p size %zd offset %zd\n", vs->ws_input.buffer,
 120            vs->ws_input.capacity, vs->ws_input.offset);
 121    buffer_reserve(&vs->ws_input, 4096);
 122    ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), 4096);
 123    if (!ret) {
 124        return 0;
 125    }
 126    vs->ws_input.offset += ret;
 127
 128    /* make sure that nothing is left in the ws_input buffer */
 129    do {
 130        err = vncws_decode_frame(&vs->ws_input, &payload,
 131                              &payload_size, &frame_size);
 132        if (err <= 0) {
 133            return err;
 134        }
 135
 136        buffer_reserve(&vs->input, payload_size);
 137        buffer_append(&vs->input, payload, payload_size);
 138
 139        buffer_advance(&vs->ws_input, frame_size);
 140    } while (vs->ws_input.offset > 0);
 141
 142    return ret;
 143}
 144
 145long vnc_client_write_ws(VncState *vs)
 146{
 147    long ret;
 148    VNC_DEBUG("Write WS: Pending output %p size %zd offset %zd\n",
 149              vs->output.buffer, vs->output.capacity, vs->output.offset);
 150    vncws_encode_frame(&vs->ws_output, vs->output.buffer, vs->output.offset);
 151    buffer_reset(&vs->output);
 152    ret = vnc_client_write_buf(vs, vs->ws_output.buffer, vs->ws_output.offset);
 153    if (!ret) {
 154        return 0;
 155    }
 156
 157    buffer_advance(&vs->ws_output, ret);
 158
 159    if (vs->ws_output.offset == 0) {
 160        qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
 161    }
 162
 163    return ret;
 164}
 165
 166static char *vncws_extract_handshake_entry(const char *handshake,
 167        size_t handshake_len, const char *name)
 168{
 169    char *begin, *end, *ret = NULL;
 170    char *line = g_strdup_printf("%s%s: ", WS_HANDSHAKE_DELIM, name);
 171    begin = g_strstr_len(handshake, handshake_len, line);
 172    if (begin != NULL) {
 173        begin += strlen(line);
 174        end = g_strstr_len(begin, handshake_len - (begin - handshake),
 175                WS_HANDSHAKE_DELIM);
 176        if (end != NULL) {
 177            ret = g_strndup(begin, end - begin);
 178        }
 179    }
 180    g_free(line);
 181    return ret;
 182}
 183
 184static void vncws_send_handshake_response(VncState *vs, const char* key)
 185{
 186    char combined_key[WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1];
 187    unsigned char hash[SHA1_DIGEST_LEN];
 188    size_t hash_size = sizeof(hash);
 189    char *accept = NULL, *response = NULL;
 190    gnutls_datum_t in;
 191    int ret;
 192
 193    g_strlcpy(combined_key, key, WS_CLIENT_KEY_LEN + 1);
 194    g_strlcat(combined_key, WS_GUID, WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1);
 195
 196    /* hash and encode it */
 197    in.data = (void *)combined_key;
 198    in.size = WS_CLIENT_KEY_LEN + WS_GUID_LEN;
 199    ret = gnutls_fingerprint(GNUTLS_DIG_SHA1, &in, hash, &hash_size);
 200    if (ret == GNUTLS_E_SUCCESS && hash_size <= SHA1_DIGEST_LEN) {
 201        accept = g_base64_encode(hash, hash_size);
 202    }
 203    if (accept == NULL) {
 204        VNC_DEBUG("Hashing Websocket combined key failed\n");
 205        vnc_client_error(vs);
 206        return;
 207    }
 208
 209    response = g_strdup_printf(WS_HANDSHAKE, accept);
 210    vnc_write(vs, response, strlen(response));
 211    vnc_flush(vs);
 212
 213    g_free(accept);
 214    g_free(response);
 215
 216    vs->encode_ws = 1;
 217    vnc_init_state(vs);
 218}
 219
 220void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size)
 221{
 222    char *protocols = vncws_extract_handshake_entry((const char *)line, size,
 223            "Sec-WebSocket-Protocol");
 224    char *version = vncws_extract_handshake_entry((const char *)line, size,
 225            "Sec-WebSocket-Version");
 226    char *key = vncws_extract_handshake_entry((const char *)line, size,
 227            "Sec-WebSocket-Key");
 228
 229    if (protocols && version && key
 230            && g_strrstr(protocols, "binary")
 231            && !strcmp(version, WS_SUPPORTED_VERSION)
 232            && strlen(key) == WS_CLIENT_KEY_LEN) {
 233        vncws_send_handshake_response(vs, key);
 234    } else {
 235        VNC_DEBUG("Defective Websockets header or unsupported protocol\n");
 236        vnc_client_error(vs);
 237    }
 238
 239    g_free(protocols);
 240    g_free(version);
 241    g_free(key);
 242}
 243
 244void vncws_encode_frame(Buffer *output, const void *payload,
 245        const size_t payload_size)
 246{
 247    size_t header_size = 0;
 248    unsigned char opcode = WS_OPCODE_BINARY_FRAME;
 249    union {
 250        char buf[WS_HEAD_MAX_LEN];
 251        WsHeader ws;
 252    } header;
 253
 254    if (!payload_size) {
 255        return;
 256    }
 257
 258    header.ws.b0 = 0x80 | (opcode & 0x0f);
 259    if (payload_size <= 125) {
 260        header.ws.b1 = (uint8_t)payload_size;
 261        header_size = 2;
 262    } else if (payload_size < 65536) {
 263        header.ws.b1 = 0x7e;
 264        header.ws.u.s16.l16 = cpu_to_be16((uint16_t)payload_size);
 265        header_size = 4;
 266    } else {
 267        header.ws.b1 = 0x7f;
 268        header.ws.u.s64.l64 = cpu_to_be64(payload_size);
 269        header_size = 10;
 270    }
 271
 272    buffer_reserve(output, header_size + payload_size);
 273    buffer_append(output, header.buf, header_size);
 274    buffer_append(output, payload, payload_size);
 275}
 276
 277int vncws_decode_frame(Buffer *input, uint8_t **payload,
 278                           size_t *payload_size, size_t *frame_size)
 279{
 280    unsigned char opcode = 0, fin = 0, has_mask = 0;
 281    size_t header_size = 0;
 282    uint32_t *payload32;
 283    WsHeader *header = (WsHeader *)input->buffer;
 284    WsMask mask;
 285    int i;
 286
 287    if (input->offset < WS_HEAD_MIN_LEN + 4) {
 288        /* header not complete */
 289        return 0;
 290    }
 291
 292    fin = (header->b0 & 0x80) >> 7;
 293    opcode = header->b0 & 0x0f;
 294    has_mask = (header->b1 & 0x80) >> 7;
 295    *payload_size = header->b1 & 0x7f;
 296
 297    if (opcode == WS_OPCODE_CLOSE) {
 298        /* disconnect */
 299        return -1;
 300    }
 301
 302    /* Websocket frame sanity check:
 303     * * Websocket fragmentation is not supported.
 304     * * All  websockets frames sent by a client have to be masked.
 305     * * Only binary encoding is supported.
 306     */
 307    if (!fin || !has_mask || opcode != WS_OPCODE_BINARY_FRAME) {
 308        VNC_DEBUG("Received faulty/unsupported Websocket frame\n");
 309        return -2;
 310    }
 311
 312    if (*payload_size < 126) {
 313        header_size = 6;
 314        mask = header->u.m;
 315    } else if (*payload_size == 126 && input->offset >= 8) {
 316        *payload_size = be16_to_cpu(header->u.s16.l16);
 317        header_size = 8;
 318        mask = header->u.s16.m16;
 319    } else if (*payload_size == 127 && input->offset >= 14) {
 320        *payload_size = be64_to_cpu(header->u.s64.l64);
 321        header_size = 14;
 322        mask = header->u.s64.m64;
 323    } else {
 324        /* header not complete */
 325        return 0;
 326    }
 327
 328    *frame_size = header_size + *payload_size;
 329
 330    if (input->offset < *frame_size) {
 331        /* frame not complete */
 332        return 0;
 333    }
 334
 335    *payload = input->buffer + header_size;
 336
 337    /* unmask frame */
 338    /* process 1 frame (32 bit op) */
 339    payload32 = (uint32_t *)(*payload);
 340    for (i = 0; i < *payload_size / 4; i++) {
 341        payload32[i] ^= mask.u;
 342    }
 343    /* process the remaining bytes (if any) */
 344    for (i *= 4; i < *payload_size; i++) {
 345        (*payload)[i] ^= mask.c[i % 4];
 346    }
 347
 348    return 1;
 349}
 350