root / ui / vnc-ws.c @ e27bd65a
History | View | Annotate | Download (8.4 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 |
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 |
unsigned char hash[SHA1_DIGEST_LEN]; |
124 |
size_t hash_size = sizeof(hash);
|
125 |
char *accept = NULL, *response = NULL; |
126 |
gnutls_datum_t in; |
127 |
int ret;
|
128 |
|
129 |
g_strlcpy(combined_key, key, WS_CLIENT_KEY_LEN + 1);
|
130 |
g_strlcat(combined_key, WS_GUID, WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1);
|
131 |
|
132 |
/* hash and encode it */
|
133 |
in.data = (void *)combined_key;
|
134 |
in.size = WS_CLIENT_KEY_LEN + WS_GUID_LEN; |
135 |
ret = gnutls_fingerprint(GNUTLS_DIG_SHA1, &in, hash, &hash_size); |
136 |
if (ret == GNUTLS_E_SUCCESS && hash_size <= SHA1_DIGEST_LEN) {
|
137 |
accept = g_base64_encode(hash, hash_size); |
138 |
} |
139 |
if (accept == NULL) { |
140 |
VNC_DEBUG("Hashing Websocket combined key failed\n");
|
141 |
vnc_client_error(vs); |
142 |
return;
|
143 |
} |
144 |
|
145 |
response = g_strdup_printf(WS_HANDSHAKE, accept); |
146 |
vnc_write(vs, response, strlen(response)); |
147 |
vnc_flush(vs); |
148 |
|
149 |
g_free(accept); |
150 |
g_free(response); |
151 |
|
152 |
vs->encode_ws = 1;
|
153 |
vnc_init_state(vs); |
154 |
} |
155 |
|
156 |
void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size)
|
157 |
{ |
158 |
char *protocols = vncws_extract_handshake_entry((const char *)line, size, |
159 |
"Sec-WebSocket-Protocol");
|
160 |
char *version = vncws_extract_handshake_entry((const char *)line, size, |
161 |
"Sec-WebSocket-Version");
|
162 |
char *key = vncws_extract_handshake_entry((const char *)line, size, |
163 |
"Sec-WebSocket-Key");
|
164 |
|
165 |
if (protocols && version && key
|
166 |
&& g_strrstr(protocols, "binary")
|
167 |
&& !strcmp(version, WS_SUPPORTED_VERSION) |
168 |
&& strlen(key) == WS_CLIENT_KEY_LEN) { |
169 |
vncws_send_handshake_response(vs, key); |
170 |
} else {
|
171 |
VNC_DEBUG("Defective Websockets header or unsupported protocol\n");
|
172 |
vnc_client_error(vs); |
173 |
} |
174 |
|
175 |
g_free(protocols); |
176 |
g_free(version); |
177 |
g_free(key); |
178 |
} |
179 |
|
180 |
void vncws_encode_frame(Buffer *output, const void *payload, |
181 |
const size_t payload_size)
|
182 |
{ |
183 |
size_t header_size = 0;
|
184 |
unsigned char opcode = WS_OPCODE_BINARY_FRAME; |
185 |
union {
|
186 |
char buf[WS_HEAD_MAX_LEN];
|
187 |
WsHeader ws; |
188 |
} header; |
189 |
|
190 |
if (!payload_size) {
|
191 |
return;
|
192 |
} |
193 |
|
194 |
header.ws.b0 = 0x80 | (opcode & 0x0f); |
195 |
if (payload_size <= 125) { |
196 |
header.ws.b1 = (uint8_t)payload_size; |
197 |
header_size = 2;
|
198 |
} else if (payload_size < 65536) { |
199 |
header.ws.b1 = 0x7e;
|
200 |
header.ws.u.s16.l16 = cpu_to_be16((uint16_t)payload_size); |
201 |
header_size = 4;
|
202 |
} else {
|
203 |
header.ws.b1 = 0x7f;
|
204 |
header.ws.u.s64.l64 = cpu_to_be64(payload_size); |
205 |
header_size = 10;
|
206 |
} |
207 |
|
208 |
buffer_reserve(output, header_size + payload_size); |
209 |
buffer_append(output, header.buf, header_size); |
210 |
buffer_append(output, payload, payload_size); |
211 |
} |
212 |
|
213 |
int vncws_decode_frame(Buffer *input, uint8_t **payload,
|
214 |
size_t *payload_size, size_t *frame_size) |
215 |
{ |
216 |
unsigned char opcode = 0, fin = 0, has_mask = 0; |
217 |
size_t header_size = 0;
|
218 |
uint32_t *payload32; |
219 |
WsHeader *header = (WsHeader *)input->buffer; |
220 |
WsMask mask; |
221 |
int i;
|
222 |
|
223 |
if (input->offset < WS_HEAD_MIN_LEN + 4) { |
224 |
/* header not complete */
|
225 |
return 0; |
226 |
} |
227 |
|
228 |
fin = (header->b0 & 0x80) >> 7; |
229 |
opcode = header->b0 & 0x0f;
|
230 |
has_mask = (header->b1 & 0x80) >> 7; |
231 |
*payload_size = header->b1 & 0x7f;
|
232 |
|
233 |
if (opcode == WS_OPCODE_CLOSE) {
|
234 |
/* disconnect */
|
235 |
return -1; |
236 |
} |
237 |
|
238 |
/* Websocket frame sanity check:
|
239 |
* * Websocket fragmentation is not supported.
|
240 |
* * All websockets frames sent by a client have to be masked.
|
241 |
* * Only binary encoding is supported.
|
242 |
*/
|
243 |
if (!fin || !has_mask || opcode != WS_OPCODE_BINARY_FRAME) {
|
244 |
VNC_DEBUG("Received faulty/unsupported Websocket frame\n");
|
245 |
return -2; |
246 |
} |
247 |
|
248 |
if (*payload_size < 126) { |
249 |
header_size = 6;
|
250 |
mask = header->u.m; |
251 |
} else if (*payload_size == 126 && input->offset >= 8) { |
252 |
*payload_size = be16_to_cpu(header->u.s16.l16); |
253 |
header_size = 8;
|
254 |
mask = header->u.s16.m16; |
255 |
} else if (*payload_size == 127 && input->offset >= 14) { |
256 |
*payload_size = be64_to_cpu(header->u.s64.l64); |
257 |
header_size = 14;
|
258 |
mask = header->u.s64.m64; |
259 |
} else {
|
260 |
/* header not complete */
|
261 |
return 0; |
262 |
} |
263 |
|
264 |
*frame_size = header_size + *payload_size; |
265 |
|
266 |
if (input->offset < *frame_size) {
|
267 |
/* frame not complete */
|
268 |
return 0; |
269 |
} |
270 |
|
271 |
*payload = input->buffer + header_size; |
272 |
|
273 |
/* unmask frame */
|
274 |
/* process 1 frame (32 bit op) */
|
275 |
payload32 = (uint32_t *)(*payload); |
276 |
for (i = 0; i < *payload_size / 4; i++) { |
277 |
payload32[i] ^= mask.u; |
278 |
} |
279 |
/* process the remaining bytes (if any) */
|
280 |
for (i *= 4; i < *payload_size; i++) { |
281 |
(*payload)[i] ^= mask.c[i % 4];
|
282 |
} |
283 |
|
284 |
return 1; |
285 |
} |