Line | Count | Source |
1 | | #include "event2/event-config.h" |
2 | | #include "evconfig-private.h" |
3 | | |
4 | | #include "event2/buffer.h" |
5 | | #include "event2/bufferevent.h" |
6 | | #include "event2/event.h" |
7 | | #include "event2/http.h" |
8 | | #include "event2/ws.h" |
9 | | #include "util-internal.h" |
10 | | #include "mm-internal.h" |
11 | | #include "sha1.h" |
12 | | #include "event2/bufferevent.h" |
13 | | #include "sys/queue.h" |
14 | | #include "http-internal.h" |
15 | | #include "bufferevent-internal.h" |
16 | | |
17 | | #include <assert.h> |
18 | | #include <inttypes.h> |
19 | | #include <string.h> |
20 | | #include <stdbool.h> |
21 | | |
22 | | #ifndef _WIN32 |
23 | | #include <sys/socket.h> |
24 | | #include <sys/stat.h> |
25 | | #else /* _WIN32 */ |
26 | | #include <winsock2.h> |
27 | | #include <ws2tcpip.h> |
28 | | #endif /* _WIN32 */ |
29 | | |
30 | | #ifdef EVENT__HAVE_ARPA_INET_H |
31 | | #include <arpa/inet.h> |
32 | | #endif |
33 | | #ifdef EVENT__HAVE_NETINET_IN_H |
34 | | #include <netinet/in.h> |
35 | | #endif |
36 | | #ifdef EVENT__HAVE_NETINET_IN6_H |
37 | | #include <netinet/in6.h> |
38 | | #endif |
39 | | |
40 | 0 | #define WS_UUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" |
41 | | /* |
42 | | * We limit the size of received WS frames to 10 MiB, |
43 | | * as a DoS prevention measure. |
44 | | */ |
45 | | static const size_t WS_MAX_RECV_FRAME_SZ = 10485760; |
46 | | |
47 | | struct evws_connection { |
48 | | TAILQ_ENTRY(evws_connection) next; |
49 | | |
50 | | struct bufferevent *bufev; |
51 | | |
52 | | ws_on_msg_cb cb; |
53 | | void *cb_arg; |
54 | | |
55 | | ws_on_close_cb cbclose; |
56 | | void *cbclose_arg; |
57 | | |
58 | | /* for server connections, the http server they are connected with */ |
59 | | struct evhttp *http_server; |
60 | | |
61 | | struct evbuffer *incomplete_frames; |
62 | | bool closed; |
63 | | }; |
64 | | |
65 | | enum WebSocketFrameType { |
66 | | ERROR_FRAME = 0xFF, |
67 | | INCOMPLETE_DATA = 0xFE, |
68 | | |
69 | | CLOSING_FRAME = 0x8, |
70 | | |
71 | | INCOMPLETE_FRAME = 0x81, |
72 | | |
73 | | TEXT_FRAME = 0x1, |
74 | | BINARY_FRAME = 0x2, |
75 | | |
76 | | PING_FRAME = 0x9, |
77 | | PONG_FRAME = 0xA |
78 | | }; |
79 | | |
80 | | |
81 | | static void evws_send(struct evws_connection *evws, |
82 | | enum WebSocketFrameType frame_type, const char *packet_str, size_t str_len); |
83 | | |
84 | | /* |
85 | | * Clean up a WebSockets connection object |
86 | | */ |
87 | | |
88 | | void |
89 | | evws_connection_free(struct evws_connection *evws) |
90 | 0 | { |
91 | | /* notify interested parties that this connection is going down */ |
92 | 0 | if (evws->cbclose != NULL) |
93 | 0 | (*evws->cbclose)(evws, evws->cbclose_arg); |
94 | |
|
95 | 0 | if (evws->http_server != NULL) { |
96 | 0 | struct evhttp *http = evws->http_server; |
97 | 0 | TAILQ_REMOVE(&http->ws_sessions, evws, next); |
98 | 0 | http->connection_cnt--; |
99 | 0 | } |
100 | |
|
101 | 0 | if (evws->bufev != NULL) { |
102 | 0 | bufferevent_free(evws->bufev); |
103 | 0 | } |
104 | 0 | if (evws->incomplete_frames != NULL) { |
105 | 0 | evbuffer_free(evws->incomplete_frames); |
106 | 0 | } |
107 | |
|
108 | 0 | mm_free(evws); |
109 | 0 | } |
110 | | |
111 | | static const char basis_64[] = |
112 | | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
113 | | |
114 | | static int |
115 | | Base64encode(char *encoded, const char *string, int len) |
116 | 0 | { |
117 | 0 | int i; |
118 | 0 | char *p; |
119 | |
|
120 | 0 | p = encoded; |
121 | 0 | for (i = 0; i < len - 2; i += 3) { |
122 | 0 | *p++ = basis_64[(string[i] >> 2) & 0x3F]; |
123 | 0 | *p++ = basis_64[((string[i] & 0x3) << 4) | |
124 | 0 | ((int)(string[i + 1] & 0xF0) >> 4)]; |
125 | 0 | *p++ = basis_64[((string[i + 1] & 0xF) << 2) | |
126 | 0 | ((int)(string[i + 2] & 0xC0) >> 6)]; |
127 | 0 | *p++ = basis_64[string[i + 2] & 0x3F]; |
128 | 0 | } |
129 | 0 | if (i < len) { |
130 | 0 | *p++ = basis_64[(string[i] >> 2) & 0x3F]; |
131 | 0 | if (i == (len - 1)) { |
132 | 0 | *p++ = basis_64[((string[i] & 0x3) << 4)]; |
133 | 0 | *p++ = '='; |
134 | 0 | } else { |
135 | 0 | *p++ = basis_64[((string[i] & 0x3) << 4) | |
136 | 0 | ((int)(string[i + 1] & 0xF0) >> 4)]; |
137 | 0 | *p++ = basis_64[((string[i + 1] & 0xF) << 2)]; |
138 | 0 | } |
139 | 0 | *p++ = '='; |
140 | 0 | } |
141 | |
|
142 | 0 | *p++ = '\0'; |
143 | 0 | return p - encoded; |
144 | 0 | } |
145 | | |
146 | | static char * |
147 | | ws_gen_accept_key(const char *ws_key, char out[32]) |
148 | 0 | { |
149 | 0 | char buf[1024]; |
150 | 0 | char digest[20]; |
151 | |
|
152 | 0 | snprintf(buf, sizeof(buf), "%s" WS_UUID, ws_key); |
153 | |
|
154 | 0 | builtin_SHA1(digest, buf, strlen(buf)); |
155 | 0 | Base64encode(out, digest, sizeof(digest)); |
156 | 0 | return out; |
157 | 0 | } |
158 | | |
159 | | static void |
160 | | close_after_write_cb(struct bufferevent *bev, void *ctx) |
161 | 0 | { |
162 | 0 | if (evbuffer_get_length(bufferevent_get_output(bev)) == 0) { |
163 | 0 | evws_connection_free(ctx); |
164 | 0 | } |
165 | 0 | } |
166 | | |
167 | | static void |
168 | | close_event_cb(struct bufferevent *bev, short what, void *ctx) |
169 | 0 | { |
170 | 0 | evws_connection_free(ctx); |
171 | 0 | } |
172 | | |
173 | | void |
174 | | evws_close(struct evws_connection *evws, uint16_t reason) |
175 | 0 | { |
176 | 0 | uint8_t fr[4] = {0x8 | 0x80, 2, 0}; |
177 | 0 | struct evbuffer *output; |
178 | 0 | uint16_t *u16; |
179 | |
|
180 | 0 | if (evws->closed) |
181 | 0 | return; |
182 | 0 | evws->closed = true; |
183 | |
|
184 | 0 | u16 = (uint16_t *)&fr[2]; |
185 | 0 | *u16 = htons((int16_t)reason); |
186 | 0 | output = bufferevent_get_output(evws->bufev); |
187 | 0 | evbuffer_add(output, fr, 4); |
188 | | |
189 | | /* wait for close frame writing complete and close connection */ |
190 | 0 | bufferevent_setcb( |
191 | 0 | evws->bufev, NULL, close_after_write_cb, close_event_cb, evws); |
192 | 0 | } |
193 | | |
194 | | static void |
195 | | evws_force_disconnect_(struct evws_connection *evws) |
196 | 0 | { |
197 | 0 | evws_close(evws, WS_CR_NONE); |
198 | 0 | } |
199 | | |
200 | | /* parse base frame according to |
201 | | * https://www.rfc-editor.org/rfc/rfc6455#section-5.2 |
202 | | */ |
203 | | static enum WebSocketFrameType |
204 | | get_ws_frame(unsigned char *in_buffer, size_t buf_len, |
205 | | unsigned char **payload_ptr, size_t *out_len) |
206 | 0 | { |
207 | 0 | unsigned char opcode; |
208 | 0 | unsigned char fin; |
209 | 0 | unsigned char masked; |
210 | 0 | size_t payload_len; |
211 | 0 | size_t pos; |
212 | 0 | int length_field; |
213 | |
|
214 | 0 | if (buf_len < 2) { |
215 | 0 | return INCOMPLETE_DATA; |
216 | 0 | } |
217 | | |
218 | 0 | opcode = in_buffer[0] & 0x0F; |
219 | 0 | fin = (in_buffer[0] >> 7) & 0x01; |
220 | 0 | masked = (in_buffer[1] >> 7) & 0x01; |
221 | |
|
222 | 0 | payload_len = 0; |
223 | 0 | pos = 2; |
224 | 0 | length_field = in_buffer[1] & (~0x80); |
225 | |
|
226 | 0 | if (length_field <= 125) { |
227 | 0 | payload_len = length_field; |
228 | 0 | } else if (length_field == 126) { /* msglen is 16bit */ |
229 | 0 | uint16_t tmp16; |
230 | 0 | if (buf_len < 4) |
231 | 0 | return INCOMPLETE_DATA; |
232 | 0 | memcpy(&tmp16, in_buffer + pos, 2); |
233 | 0 | payload_len = ntohs(tmp16); |
234 | 0 | pos += 2; |
235 | 0 | } else if (length_field == 127) { /* msglen is 64bit */ |
236 | 0 | int i; |
237 | 0 | uint64_t tmp64 = 0; |
238 | 0 | if (buf_len < 10) |
239 | 0 | return INCOMPLETE_DATA; |
240 | | /* swap bytes from big endian to host byte order */ |
241 | 0 | for (i = 56; i >= 0; i -= 8) { |
242 | 0 | tmp64 |= (uint64_t)in_buffer[pos++] << i; |
243 | 0 | } |
244 | 0 | if (tmp64 > WS_MAX_RECV_FRAME_SZ) { |
245 | | /* Implementation limitation, we support up to 10 MiB |
246 | | * length, as a DoS prevention measure. |
247 | | */ |
248 | 0 | event_warn("%s: frame length %" PRIu64 " exceeds %" PRIu64 "\n", |
249 | 0 | __func__, tmp64, (uint64_t)WS_MAX_RECV_FRAME_SZ); |
250 | | /* Calling code needs these values; do the best we can here. |
251 | | * Caller will close the connection anyway. |
252 | | */ |
253 | 0 | *payload_ptr = in_buffer + pos; |
254 | 0 | *out_len = 0; |
255 | 0 | return ERROR_FRAME; |
256 | 0 | } |
257 | 0 | payload_len = (size_t)tmp64; |
258 | 0 | } |
259 | 0 | if (buf_len < payload_len + pos + (masked ? 4u : 0u)) { |
260 | 0 | return INCOMPLETE_DATA; |
261 | 0 | } |
262 | | |
263 | | /* According to RFC it seems that unmasked data should be prohibited |
264 | | * but we support it for nonconformant clients |
265 | | */ |
266 | 0 | if (masked) { |
267 | 0 | unsigned char *c, *mask; |
268 | 0 | size_t i; |
269 | |
|
270 | 0 | mask = in_buffer + pos; /* first 4 bytes are mask bytes */ |
271 | 0 | pos += 4; |
272 | | |
273 | | /* unmask data */ |
274 | 0 | c = in_buffer + pos; |
275 | 0 | for (i = 0; i < payload_len; i++) { |
276 | 0 | c[i] = c[i] ^ mask[i % 4u]; |
277 | 0 | } |
278 | 0 | } |
279 | |
|
280 | 0 | *payload_ptr = in_buffer + pos; |
281 | 0 | *out_len = payload_len; |
282 | | |
283 | | /* are reserved for further frames */ |
284 | 0 | if ((opcode >= 3 && opcode <= 7) || (opcode >= 0xb)) |
285 | 0 | return ERROR_FRAME; |
286 | | |
287 | 0 | if (opcode <= 0x3 && !fin) { |
288 | 0 | return INCOMPLETE_FRAME; |
289 | 0 | } |
290 | 0 | return opcode; |
291 | 0 | } |
292 | | |
293 | | |
294 | | static void |
295 | | ws_evhttp_read_cb(struct bufferevent *bufev, void *arg) |
296 | 0 | { |
297 | 0 | struct evws_connection *evws = arg; |
298 | 0 | unsigned char *payload; |
299 | 0 | enum WebSocketFrameType type; |
300 | 0 | size_t msg_len, in_len, header_sz; |
301 | 0 | struct evbuffer *input = bufferevent_get_input(evws->bufev); |
302 | |
|
303 | 0 | bufferevent_incref_and_lock_(evws->bufev); |
304 | 0 | while ((in_len = evbuffer_get_length(input))) { |
305 | 0 | unsigned char *data = evbuffer_pullup(input, in_len); |
306 | 0 | if (data == NULL) { |
307 | 0 | goto bailout; |
308 | 0 | } |
309 | | |
310 | 0 | type = get_ws_frame(data, in_len, &payload, &msg_len); |
311 | 0 | if (type == INCOMPLETE_DATA) { |
312 | | /* incomplete data received, wait for next chunk */ |
313 | 0 | goto bailout; |
314 | 0 | } |
315 | 0 | header_sz = payload - data; |
316 | 0 | evbuffer_drain(input, header_sz); |
317 | 0 | data = evbuffer_pullup(input, -1); |
318 | |
|
319 | 0 | switch (type) { |
320 | 0 | case TEXT_FRAME: |
321 | 0 | case BINARY_FRAME: |
322 | 0 | if (evws->incomplete_frames != NULL) { |
323 | | /* we already have incomplete frames in internal buffer |
324 | | * and need to concatenate them with final one */ |
325 | 0 | evbuffer_add(evws->incomplete_frames, data, msg_len); |
326 | |
|
327 | 0 | data = evbuffer_pullup(evws->incomplete_frames, -1); |
328 | |
|
329 | 0 | evws->cb(evws, type, data, |
330 | 0 | evbuffer_get_length(evws->incomplete_frames), evws->cb_arg); |
331 | 0 | evbuffer_free(evws->incomplete_frames); |
332 | 0 | evws->incomplete_frames = NULL; |
333 | 0 | } else { |
334 | 0 | evws->cb(evws, type, data, msg_len, evws->cb_arg); |
335 | 0 | } |
336 | 0 | break; |
337 | 0 | case INCOMPLETE_FRAME: |
338 | | /* we received full frame until get fin and need to |
339 | | * postpone callback until all data arrives */ |
340 | 0 | if (evws->incomplete_frames == NULL) { |
341 | 0 | evws->incomplete_frames = evbuffer_new(); |
342 | 0 | } |
343 | 0 | evbuffer_remove_buffer(input, evws->incomplete_frames, msg_len); |
344 | 0 | continue; |
345 | 0 | case CLOSING_FRAME: |
346 | 0 | case ERROR_FRAME: |
347 | 0 | evws_force_disconnect_(evws); |
348 | 0 | break; |
349 | 0 | case PING_FRAME: |
350 | 0 | case PONG_FRAME: |
351 | | /* ping or pong frame */ |
352 | 0 | break; |
353 | 0 | default: |
354 | 0 | event_warn("%s: unexpected frame type %d\n", __func__, type); |
355 | 0 | evws_force_disconnect_(evws); |
356 | 0 | } |
357 | 0 | evbuffer_drain(input, msg_len); |
358 | 0 | } |
359 | | |
360 | 0 | bailout: |
361 | 0 | bufferevent_decref_and_unlock_(evws->bufev); |
362 | 0 | } |
363 | | |
364 | | static void |
365 | | ws_evhttp_error_cb(struct bufferevent *bufev, short what, void *arg) |
366 | 0 | { |
367 | | /* when client just disappears after connection (wscat closed by Cmd+Q) */ |
368 | 0 | if (what & BEV_EVENT_EOF) { |
369 | 0 | close_after_write_cb(bufev, arg); |
370 | 0 | } |
371 | 0 | } |
372 | | |
373 | | struct evws_connection * |
374 | | evws_new_session( |
375 | | struct evhttp_request *req, ws_on_msg_cb cb, void *arg, int options) |
376 | 0 | { |
377 | 0 | struct evws_connection *evws = NULL; |
378 | 0 | struct evkeyvalq *in_hdrs; |
379 | 0 | const char *upgrade, *connection, *ws_key, *ws_protocol; |
380 | 0 | struct evkeyvalq *out_hdrs; |
381 | 0 | struct evhttp_connection *evcon; |
382 | |
|
383 | 0 | in_hdrs = evhttp_request_get_input_headers(req); |
384 | 0 | upgrade = evhttp_find_header(in_hdrs, "Upgrade"); |
385 | 0 | if (upgrade == NULL || evutil_ascii_strcasecmp(upgrade, "websocket")) |
386 | 0 | goto error; |
387 | | |
388 | 0 | connection = evhttp_find_header(in_hdrs, "Connection"); |
389 | 0 | if (connection == NULL || evutil_ascii_strcasestr(connection, "Upgrade") == NULL) |
390 | 0 | goto error; |
391 | | |
392 | 0 | ws_key = evhttp_find_header(in_hdrs, "Sec-WebSocket-Key"); |
393 | 0 | if (ws_key == NULL) |
394 | 0 | goto error; |
395 | | |
396 | 0 | out_hdrs = evhttp_request_get_output_headers(req); |
397 | 0 | evhttp_add_header(out_hdrs, "Upgrade", "websocket"); |
398 | 0 | evhttp_add_header(out_hdrs, "Connection", "Upgrade"); |
399 | |
|
400 | 0 | evhttp_add_header(out_hdrs, "Sec-WebSocket-Accept", |
401 | 0 | ws_gen_accept_key(ws_key, (char[32]){0})); |
402 | |
|
403 | 0 | ws_protocol = evhttp_find_header(in_hdrs, "Sec-WebSocket-Protocol"); |
404 | 0 | if (ws_protocol != NULL) |
405 | 0 | evhttp_add_header(out_hdrs, "Sec-WebSocket-Protocol", ws_protocol); |
406 | |
|
407 | 0 | if ((evws = mm_calloc(1, sizeof(struct evws_connection))) == NULL) { |
408 | 0 | event_warn("%s: calloc failed", __func__); |
409 | 0 | goto error; |
410 | 0 | } |
411 | | |
412 | 0 | evws->cb = cb; |
413 | 0 | evws->cb_arg = arg; |
414 | |
|
415 | 0 | evcon = evhttp_request_get_connection(req); |
416 | 0 | evws->http_server = evcon->http_server; |
417 | |
|
418 | 0 | evws->bufev = evhttp_start_ws_(req); |
419 | 0 | if (evws->bufev == NULL) { |
420 | 0 | goto error; |
421 | 0 | } |
422 | | |
423 | 0 | if (options & BEV_OPT_THREADSAFE) { |
424 | 0 | if (bufferevent_enable_locking_(evws->bufev, NULL) < 0) |
425 | 0 | goto error; |
426 | 0 | } |
427 | | |
428 | 0 | bufferevent_setcb( |
429 | 0 | evws->bufev, ws_evhttp_read_cb, NULL, ws_evhttp_error_cb, evws); |
430 | |
|
431 | 0 | TAILQ_INSERT_TAIL(&evws->http_server->ws_sessions, evws, next); |
432 | 0 | evws->http_server->connection_cnt++; |
433 | |
|
434 | 0 | return evws; |
435 | | |
436 | 0 | error: |
437 | 0 | if (evws) |
438 | 0 | evws_connection_free(evws); |
439 | |
|
440 | 0 | evhttp_send_reply(req, HTTP_BADREQUEST, NULL, NULL); |
441 | 0 | return NULL; |
442 | 0 | } |
443 | | |
444 | | static void |
445 | | make_ws_frame(struct evbuffer *output, enum WebSocketFrameType frame_type, |
446 | | unsigned char *msg, size_t len) |
447 | 0 | { |
448 | 0 | size_t pos = 0; |
449 | 0 | unsigned char header[16] = {0}; |
450 | |
|
451 | 0 | header[pos++] = (unsigned char)frame_type | 0x80; /* fin */ |
452 | 0 | if (len <= 125) { |
453 | 0 | header[pos++] = len; |
454 | 0 | } else if (len <= 65535) { |
455 | 0 | header[pos++] = 126; /* 16 bit length */ |
456 | 0 | header[pos++] = (len >> 8) & 0xFF; /* rightmost first */ |
457 | 0 | header[pos++] = len & 0xFF; |
458 | 0 | } else { /* >2^16-1 */ |
459 | 0 | int i; |
460 | 0 | const uint64_t tmp64 = len; |
461 | 0 | header[pos++] = 127; /* 64 bit length */ |
462 | | /* swap bytes from host byte order to big endian */ |
463 | 0 | for (i = 56; i >= 0; i -= 8) { |
464 | 0 | header[pos++] = tmp64 >> i & 0xFFu; |
465 | 0 | } |
466 | 0 | } |
467 | 0 | evbuffer_add(output, header, pos); |
468 | 0 | evbuffer_add(output, msg, len); |
469 | 0 | } |
470 | | |
471 | | static void |
472 | | evws_send(struct evws_connection *evws, enum WebSocketFrameType frame_type, |
473 | | const char *packet_str, size_t str_len) |
474 | 0 | { |
475 | 0 | struct evbuffer *output; |
476 | |
|
477 | 0 | bufferevent_lock(evws->bufev); |
478 | 0 | output = bufferevent_get_output(evws->bufev); |
479 | 0 | make_ws_frame(output, frame_type, (unsigned char *)packet_str, str_len); |
480 | 0 | bufferevent_unlock(evws->bufev); |
481 | 0 | } |
482 | | |
483 | | void |
484 | | evws_send_text(struct evws_connection *evws, const char *packet_str) |
485 | 0 | { |
486 | 0 | evws_send(evws, TEXT_FRAME, packet_str, strlen(packet_str)); |
487 | 0 | } |
488 | | |
489 | | void |
490 | | evws_send_binary( |
491 | | struct evws_connection *evws, const char *packet_data, size_t packet_len) |
492 | 0 | { |
493 | 0 | evws_send(evws, BINARY_FRAME, packet_data, packet_len); |
494 | 0 | } |
495 | | |
496 | | void |
497 | | evws_connection_set_closecb( |
498 | | struct evws_connection *evws, ws_on_close_cb cb, void *cbarg) |
499 | 0 | { |
500 | 0 | evws->cbclose = cb; |
501 | 0 | evws->cbclose_arg = cbarg; |
502 | 0 | } |
503 | | |
504 | | struct bufferevent * |
505 | | evws_connection_get_bufferevent(struct evws_connection *evws) |
506 | 0 | { |
507 | 0 | return evws->bufev; |
508 | 0 | } |