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 |
} |