xref: /qemu/ui/vnc-ws.c (revision f9e90c798dc84be2b0b988517e86b49faf79d5dc)
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 void vncws_handshake_read(void *opaque)
24 {
25     VncState *vs = opaque;
26     uint8_t *handshake_end;
27     long ret;
28     buffer_reserve(&vs->ws_input, 4096);
29     ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), 4096);
30 
31     if (!ret) {
32         if (vs->csock == -1) {
33             vnc_disconnect_finish(vs);
34         }
35         return;
36     }
37     vs->ws_input.offset += ret;
38 
39     handshake_end = (uint8_t *)g_strstr_len((char *)vs->ws_input.buffer,
40             vs->ws_input.offset, WS_HANDSHAKE_END);
41     if (handshake_end) {
42         qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
43         vncws_process_handshake(vs, vs->ws_input.buffer, vs->ws_input.offset);
44         buffer_advance(&vs->ws_input, handshake_end - vs->ws_input.buffer +
45                 strlen(WS_HANDSHAKE_END));
46     }
47 }
48 
49 
50 long vnc_client_read_ws(VncState *vs)
51 {
52     int ret, err;
53     uint8_t *payload;
54     size_t payload_size, frame_size;
55     VNC_DEBUG("Read websocket %p size %zd offset %zd\n", vs->ws_input.buffer,
56             vs->ws_input.capacity, vs->ws_input.offset);
57     buffer_reserve(&vs->ws_input, 4096);
58     ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), 4096);
59     if (!ret) {
60         return 0;
61     }
62     vs->ws_input.offset += ret;
63 
64     /* make sure that nothing is left in the ws_input buffer */
65     do {
66         err = vncws_decode_frame(&vs->ws_input, &payload,
67                               &payload_size, &frame_size);
68         if (err <= 0) {
69             return err;
70         }
71 
72         buffer_reserve(&vs->input, payload_size);
73         buffer_append(&vs->input, payload, payload_size);
74 
75         buffer_advance(&vs->ws_input, frame_size);
76     } while (vs->ws_input.offset > 0);
77 
78     return ret;
79 }
80 
81 long vnc_client_write_ws(VncState *vs)
82 {
83     long ret;
84     VNC_DEBUG("Write WS: Pending output %p size %zd offset %zd\n",
85               vs->output.buffer, vs->output.capacity, vs->output.offset);
86     vncws_encode_frame(&vs->ws_output, vs->output.buffer, vs->output.offset);
87     buffer_reset(&vs->output);
88     ret = vnc_client_write_buf(vs, vs->ws_output.buffer, vs->ws_output.offset);
89     if (!ret) {
90         return 0;
91     }
92 
93     buffer_advance(&vs->ws_output, ret);
94 
95     if (vs->ws_output.offset == 0) {
96         qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
97     }
98 
99     return ret;
100 }
101 
102 static char *vncws_extract_handshake_entry(const char *handshake,
103         size_t handshake_len, const char *name)
104 {
105     char *begin, *end, *ret = NULL;
106     char *line = g_strdup_printf("%s%s: ", WS_HANDSHAKE_DELIM, name);
107     begin = g_strstr_len(handshake, handshake_len, line);
108     if (begin != NULL) {
109         begin += strlen(line);
110         end = g_strstr_len(begin, handshake_len - (begin - handshake),
111                 WS_HANDSHAKE_DELIM);
112         if (end != NULL) {
113             ret = g_strndup(begin, end - begin);
114         }
115     }
116     g_free(line);
117     return ret;
118 }
119 
120 static void vncws_send_handshake_response(VncState *vs, const char* key)
121 {
122     char combined_key[WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1];
123     char hash[SHA1_DIGEST_LEN];
124     size_t hash_size = SHA1_DIGEST_LEN;
125     char *accept = NULL, *response = NULL;
126     gnutls_datum_t in;
127 
128     g_strlcpy(combined_key, key, WS_CLIENT_KEY_LEN + 1);
129     g_strlcat(combined_key, WS_GUID, WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1);
130 
131     /* hash and encode it */
132     in.data = (void *)combined_key;
133     in.size = WS_CLIENT_KEY_LEN + WS_GUID_LEN;
134     if (gnutls_fingerprint(GNUTLS_DIG_SHA1, &in, hash, &hash_size)
135             == GNUTLS_E_SUCCESS) {
136         accept = g_base64_encode((guchar *)hash, SHA1_DIGEST_LEN);
137     }
138     if (accept == NULL) {
139         VNC_DEBUG("Hashing Websocket combined key failed\n");
140         vnc_client_error(vs);
141         return;
142     }
143 
144     response = g_strdup_printf(WS_HANDSHAKE, accept);
145     vnc_write(vs, response, strlen(response));
146     vnc_flush(vs);
147 
148     g_free(accept);
149     g_free(response);
150 
151     vs->encode_ws = 1;
152     vnc_init_state(vs);
153 }
154 
155 void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size)
156 {
157     char *protocols = vncws_extract_handshake_entry((const char *)line, size,
158             "Sec-WebSocket-Protocol");
159     char *version = vncws_extract_handshake_entry((const char *)line, size,
160             "Sec-WebSocket-Version");
161     char *key = vncws_extract_handshake_entry((const char *)line, size,
162             "Sec-WebSocket-Key");
163 
164     if (protocols && version && key
165             && g_strrstr(protocols, "binary")
166             && !strcmp(version, WS_SUPPORTED_VERSION)
167             && strlen(key) == WS_CLIENT_KEY_LEN) {
168         vncws_send_handshake_response(vs, key);
169     } else {
170         VNC_DEBUG("Defective Websockets header or unsupported protocol\n");
171         vnc_client_error(vs);
172     }
173 
174     g_free(protocols);
175     g_free(version);
176     g_free(key);
177 }
178 
179 void vncws_encode_frame(Buffer *output, const void *payload,
180         const size_t payload_size)
181 {
182     size_t header_size = 0;
183     unsigned char opcode = WS_OPCODE_BINARY_FRAME;
184     union {
185         char buf[WS_HEAD_MAX_LEN];
186         WsHeader ws;
187     } header;
188 
189     if (!payload_size) {
190         return;
191     }
192 
193     header.ws.b0 = 0x80 | (opcode & 0x0f);
194     if (payload_size <= 125) {
195         header.ws.b1 = (uint8_t)payload_size;
196         header_size = 2;
197     } else if (payload_size < 65536) {
198         header.ws.b1 = 0x7e;
199         header.ws.u.s16.l16 = cpu_to_be16((uint16_t)payload_size);
200         header_size = 4;
201     } else {
202         header.ws.b1 = 0x7f;
203         header.ws.u.s64.l64 = cpu_to_be64(payload_size);
204         header_size = 10;
205     }
206 
207     buffer_reserve(output, header_size + payload_size);
208     buffer_append(output, header.buf, header_size);
209     buffer_append(output, payload, payload_size);
210 }
211 
212 int vncws_decode_frame(Buffer *input, uint8_t **payload,
213                            size_t *payload_size, size_t *frame_size)
214 {
215     unsigned char opcode = 0, fin = 0, has_mask = 0;
216     size_t header_size = 0;
217     uint32_t *payload32;
218     WsHeader *header = (WsHeader *)input->buffer;
219     WsMask mask;
220     int i;
221 
222     if (input->offset < WS_HEAD_MIN_LEN + 4) {
223         /* header not complete */
224         return 0;
225     }
226 
227     fin = (header->b0 & 0x80) >> 7;
228     opcode = header->b0 & 0x0f;
229     has_mask = (header->b1 & 0x80) >> 7;
230     *payload_size = header->b1 & 0x7f;
231 
232     if (opcode == WS_OPCODE_CLOSE) {
233         /* disconnect */
234         return -1;
235     }
236 
237     /* Websocket frame sanity check:
238      * * Websocket fragmentation is not supported.
239      * * All  websockets frames sent by a client have to be masked.
240      * * Only binary encoding is supported.
241      */
242     if (!fin || !has_mask || opcode != WS_OPCODE_BINARY_FRAME) {
243         VNC_DEBUG("Received faulty/unsupported Websocket frame\n");
244         return -2;
245     }
246 
247     if (*payload_size < 126) {
248         header_size = 6;
249         mask = header->u.m;
250     } else if (*payload_size == 126 && input->offset >= 8) {
251         *payload_size = be16_to_cpu(header->u.s16.l16);
252         header_size = 8;
253         mask = header->u.s16.m16;
254     } else if (*payload_size == 127 && input->offset >= 14) {
255         *payload_size = be64_to_cpu(header->u.s64.l64);
256         header_size = 14;
257         mask = header->u.s64.m64;
258     } else {
259         /* header not complete */
260         return 0;
261     }
262 
263     *frame_size = header_size + *payload_size;
264 
265     if (input->offset < *frame_size) {
266         /* frame not complete */
267         return 0;
268     }
269 
270     *payload = input->buffer + header_size;
271 
272     /* unmask frame */
273     /* process 1 frame (32 bit op) */
274     payload32 = (uint32_t *)(*payload);
275     for (i = 0; i < *payload_size / 4; i++) {
276         payload32[i] ^= mask.u;
277     }
278     /* process the remaining bytes (if any) */
279     for (i *= 4; i < *payload_size; i++) {
280         (*payload)[i] ^= mask.c[i % 4];
281     }
282 
283     return 1;
284 }
285