Statistics
| Branch: | Revision:

root / ui / vnc-ws.c @ a8aec295

History | View | Annotate | Download (10.2 kB)

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

    
23
#ifdef CONFIG_VNC_TLS
24
#include "qemu/sockets.h"
25

    
26
static void vncws_tls_handshake_io(void *opaque);
27

    
28
static int vncws_start_tls_handshake(struct VncState *vs)
29
{
30
    int ret = gnutls_handshake(vs->ws_tls.session);
31

    
32
    if (ret < 0) {
33
        if (!gnutls_error_is_fatal(ret)) {
34
            VNC_DEBUG("Handshake interrupted (blocking)\n");
35
            if (!gnutls_record_get_direction(vs->ws_tls.session)) {
36
                qemu_set_fd_handler(vs->csock, vncws_tls_handshake_io,
37
                                    NULL, vs);
38
            } else {
39
                qemu_set_fd_handler(vs->csock, NULL, vncws_tls_handshake_io,
40
                                    vs);
41
            }
42
            return 0;
43
        }
44
        VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret));
45
        vnc_client_error(vs);
46
        return -1;
47
    }
48

    
49
    VNC_DEBUG("Handshake done, switching to TLS data mode\n");
50
    vs->ws_tls.wiremode = VNC_WIREMODE_TLS;
51
    qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read, NULL, vs);
52

    
53
    return 0;
54
}
55

    
56
static void vncws_tls_handshake_io(void *opaque)
57
{
58
    struct VncState *vs = (struct VncState *)opaque;
59

    
60
    VNC_DEBUG("Handshake IO continue\n");
61
    vncws_start_tls_handshake(vs);
62
}
63

    
64
void vncws_tls_handshake_peek(void *opaque)
65
{
66
    VncState *vs = opaque;
67
    long ret;
68

    
69
    if (!vs->ws_tls.session) {
70
        char peek[4];
71
        ret = qemu_recv(vs->csock, peek, sizeof(peek), MSG_PEEK);
72
        if (ret && (strncmp(peek, "\x16", 1) == 0
73
                    || strncmp(peek, "\x80", 1) == 0)) {
74
            VNC_DEBUG("TLS Websocket connection recognized");
75
            vnc_tls_client_setup(vs, 1);
76
            vncws_start_tls_handshake(vs);
77
        } else {
78
            vncws_handshake_read(vs);
79
        }
80
    } else {
81
        qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read, NULL, vs);
82
    }
83
}
84
#endif /* CONFIG_VNC_TLS */
85

    
86
void vncws_handshake_read(void *opaque)
87
{
88
    VncState *vs = opaque;
89
    uint8_t *handshake_end;
90
    long ret;
91
    buffer_reserve(&vs->ws_input, 4096);
92
    ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), 4096);
93

    
94
    if (!ret) {
95
        if (vs->csock == -1) {
96
            vnc_disconnect_finish(vs);
97
        }
98
        return;
99
    }
100
    vs->ws_input.offset += ret;
101

    
102
    handshake_end = (uint8_t *)g_strstr_len((char *)vs->ws_input.buffer,
103
            vs->ws_input.offset, WS_HANDSHAKE_END);
104
    if (handshake_end) {
105
        qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
106
        vncws_process_handshake(vs, vs->ws_input.buffer, vs->ws_input.offset);
107
        buffer_advance(&vs->ws_input, handshake_end - vs->ws_input.buffer +
108
                strlen(WS_HANDSHAKE_END));
109
    }
110
}
111

    
112

    
113
long vnc_client_read_ws(VncState *vs)
114
{
115
    int ret, err;
116
    uint8_t *payload;
117
    size_t payload_size, frame_size;
118
    VNC_DEBUG("Read websocket %p size %zd offset %zd\n", vs->ws_input.buffer,
119
            vs->ws_input.capacity, vs->ws_input.offset);
120
    buffer_reserve(&vs->ws_input, 4096);
121
    ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), 4096);
122
    if (!ret) {
123
        return 0;
124
    }
125
    vs->ws_input.offset += ret;
126

    
127
    /* make sure that nothing is left in the ws_input buffer */
128
    do {
129
        err = vncws_decode_frame(&vs->ws_input, &payload,
130
                              &payload_size, &frame_size);
131
        if (err <= 0) {
132
            return err;
133
        }
134

    
135
        buffer_reserve(&vs->input, payload_size);
136
        buffer_append(&vs->input, payload, payload_size);
137

    
138
        buffer_advance(&vs->ws_input, frame_size);
139
    } while (vs->ws_input.offset > 0);
140

    
141
    return ret;
142
}
143

    
144
long vnc_client_write_ws(VncState *vs)
145
{
146
    long ret;
147
    VNC_DEBUG("Write WS: Pending output %p size %zd offset %zd\n",
148
              vs->output.buffer, vs->output.capacity, vs->output.offset);
149
    vncws_encode_frame(&vs->ws_output, vs->output.buffer, vs->output.offset);
150
    buffer_reset(&vs->output);
151
    ret = vnc_client_write_buf(vs, vs->ws_output.buffer, vs->ws_output.offset);
152
    if (!ret) {
153
        return 0;
154
    }
155

    
156
    buffer_advance(&vs->ws_output, ret);
157

    
158
    if (vs->ws_output.offset == 0) {
159
        qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
160
    }
161

    
162
    return ret;
163
}
164

    
165
static char *vncws_extract_handshake_entry(const char *handshake,
166
        size_t handshake_len, const char *name)
167
{
168
    char *begin, *end, *ret = NULL;
169
    char *line = g_strdup_printf("%s%s: ", WS_HANDSHAKE_DELIM, name);
170
    begin = g_strstr_len(handshake, handshake_len, line);
171
    if (begin != NULL) {
172
        begin += strlen(line);
173
        end = g_strstr_len(begin, handshake_len - (begin - handshake),
174
                WS_HANDSHAKE_DELIM);
175
        if (end != NULL) {
176
            ret = g_strndup(begin, end - begin);
177
        }
178
    }
179
    g_free(line);
180
    return ret;
181
}
182

    
183
static void vncws_send_handshake_response(VncState *vs, const char* key)
184
{
185
    char combined_key[WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1];
186
    unsigned char hash[SHA1_DIGEST_LEN];
187
    size_t hash_size = sizeof(hash);
188
    char *accept = NULL, *response = NULL;
189
    gnutls_datum_t in;
190
    int ret;
191

    
192
    g_strlcpy(combined_key, key, WS_CLIENT_KEY_LEN + 1);
193
    g_strlcat(combined_key, WS_GUID, WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1);
194

    
195
    /* hash and encode it */
196
    in.data = (void *)combined_key;
197
    in.size = WS_CLIENT_KEY_LEN + WS_GUID_LEN;
198
    ret = gnutls_fingerprint(GNUTLS_DIG_SHA1, &in, hash, &hash_size);
199
    if (ret == GNUTLS_E_SUCCESS && hash_size <= SHA1_DIGEST_LEN) {
200
        accept = g_base64_encode(hash, hash_size);
201
    }
202
    if (accept == NULL) {
203
        VNC_DEBUG("Hashing Websocket combined key failed\n");
204
        vnc_client_error(vs);
205
        return;
206
    }
207

    
208
    response = g_strdup_printf(WS_HANDSHAKE, accept);
209
    vnc_write(vs, response, strlen(response));
210
    vnc_flush(vs);
211

    
212
    g_free(accept);
213
    g_free(response);
214

    
215
    vs->encode_ws = 1;
216
    vnc_init_state(vs);
217
}
218

    
219
void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size)
220
{
221
    char *protocols = vncws_extract_handshake_entry((const char *)line, size,
222
            "Sec-WebSocket-Protocol");
223
    char *version = vncws_extract_handshake_entry((const char *)line, size,
224
            "Sec-WebSocket-Version");
225
    char *key = vncws_extract_handshake_entry((const char *)line, size,
226
            "Sec-WebSocket-Key");
227

    
228
    if (protocols && version && key
229
            && g_strrstr(protocols, "binary")
230
            && !strcmp(version, WS_SUPPORTED_VERSION)
231
            && strlen(key) == WS_CLIENT_KEY_LEN) {
232
        vncws_send_handshake_response(vs, key);
233
    } else {
234
        VNC_DEBUG("Defective Websockets header or unsupported protocol\n");
235
        vnc_client_error(vs);
236
    }
237

    
238
    g_free(protocols);
239
    g_free(version);
240
    g_free(key);
241
}
242

    
243
void vncws_encode_frame(Buffer *output, const void *payload,
244
        const size_t payload_size)
245
{
246
    size_t header_size = 0;
247
    unsigned char opcode = WS_OPCODE_BINARY_FRAME;
248
    union {
249
        char buf[WS_HEAD_MAX_LEN];
250
        WsHeader ws;
251
    } header;
252

    
253
    if (!payload_size) {
254
        return;
255
    }
256

    
257
    header.ws.b0 = 0x80 | (opcode & 0x0f);
258
    if (payload_size <= 125) {
259
        header.ws.b1 = (uint8_t)payload_size;
260
        header_size = 2;
261
    } else if (payload_size < 65536) {
262
        header.ws.b1 = 0x7e;
263
        header.ws.u.s16.l16 = cpu_to_be16((uint16_t)payload_size);
264
        header_size = 4;
265
    } else {
266
        header.ws.b1 = 0x7f;
267
        header.ws.u.s64.l64 = cpu_to_be64(payload_size);
268
        header_size = 10;
269
    }
270

    
271
    buffer_reserve(output, header_size + payload_size);
272
    buffer_append(output, header.buf, header_size);
273
    buffer_append(output, payload, payload_size);
274
}
275

    
276
int vncws_decode_frame(Buffer *input, uint8_t **payload,
277
                           size_t *payload_size, size_t *frame_size)
278
{
279
    unsigned char opcode = 0, fin = 0, has_mask = 0;
280
    size_t header_size = 0;
281
    uint32_t *payload32;
282
    WsHeader *header = (WsHeader *)input->buffer;
283
    WsMask mask;
284
    int i;
285

    
286
    if (input->offset < WS_HEAD_MIN_LEN + 4) {
287
        /* header not complete */
288
        return 0;
289
    }
290

    
291
    fin = (header->b0 & 0x80) >> 7;
292
    opcode = header->b0 & 0x0f;
293
    has_mask = (header->b1 & 0x80) >> 7;
294
    *payload_size = header->b1 & 0x7f;
295

    
296
    if (opcode == WS_OPCODE_CLOSE) {
297
        /* disconnect */
298
        return -1;
299
    }
300

    
301
    /* Websocket frame sanity check:
302
     * * Websocket fragmentation is not supported.
303
     * * All  websockets frames sent by a client have to be masked.
304
     * * Only binary encoding is supported.
305
     */
306
    if (!fin || !has_mask || opcode != WS_OPCODE_BINARY_FRAME) {
307
        VNC_DEBUG("Received faulty/unsupported Websocket frame\n");
308
        return -2;
309
    }
310

    
311
    if (*payload_size < 126) {
312
        header_size = 6;
313
        mask = header->u.m;
314
    } else if (*payload_size == 126 && input->offset >= 8) {
315
        *payload_size = be16_to_cpu(header->u.s16.l16);
316
        header_size = 8;
317
        mask = header->u.s16.m16;
318
    } else if (*payload_size == 127 && input->offset >= 14) {
319
        *payload_size = be64_to_cpu(header->u.s64.l64);
320
        header_size = 14;
321
        mask = header->u.s64.m64;
322
    } else {
323
        /* header not complete */
324
        return 0;
325
    }
326

    
327
    *frame_size = header_size + *payload_size;
328

    
329
    if (input->offset < *frame_size) {
330
        /* frame not complete */
331
        return 0;
332
    }
333

    
334
    *payload = input->buffer + header_size;
335

    
336
    /* unmask frame */
337
    /* process 1 frame (32 bit op) */
338
    payload32 = (uint32_t *)(*payload);
339
    for (i = 0; i < *payload_size / 4; i++) {
340
        payload32[i] ^= mask.u;
341
    }
342
    /* process the remaining bytes (if any) */
343
    for (i *= 4; i < *payload_size; i++) {
344
        (*payload)[i] ^= mask.c[i % 4];
345
    }
346

    
347
    return 1;
348
}