FreeRDP
Loading...
Searching...
No Matches
websocket.c
1
20#include "websocket.h"
21#include <freerdp/log.h>
22#include "../tcp.h"
23
24#define TAG FREERDP_TAG("core.gateway.websocket")
25
26#define RESPONSE_SIZE_LIMIT (64ULL * 1024ULL * 1024ULL)
27
28struct s_websocket_context
29{
30 size_t payloadLength;
31 uint32_t maskingKey;
32 BOOL masking;
33 BOOL closeSent;
34 BYTE opcode;
35 BYTE fragmentOriginalOpcode;
36 BYTE lengthAndMaskPosition;
37 WEBSOCKET_STATE state;
38 wStream* responseStreamBuffer;
39};
40
41WINPR_ATTR_NODISCARD
42static BOOL Stream_Reset(wStream* s)
43{
44 Stream_ResetPosition(s);
45 return Stream_SetLength(s, Stream_Capacity(s));
46}
47
48static int websocket_write_all(BIO* bio, const BYTE* data, size_t length);
49
50BOOL websocket_context_mask_and_send(BIO* bio, wStream* sPacket, wStream* sDataPacket,
51 UINT32 maskingKey)
52{
53 const size_t len = Stream_Length(sDataPacket);
54 Stream_ResetPosition(sDataPacket);
55
56 if (!Stream_EnsureRemainingCapacity(sPacket, len))
57 return FALSE;
58
59 /* mask as much as possible with 32bit access */
60 size_t streamPos = 0;
61 for (; streamPos + 4 <= len; streamPos += 4)
62 {
63 const uint32_t data = Stream_Get_UINT32(sDataPacket);
64 Stream_Write_UINT32(sPacket, data ^ maskingKey);
65 }
66
67 /* mask the rest byte by byte */
68 for (; streamPos < len; streamPos++)
69 {
70 BYTE data = 0;
71 BYTE* partialMask = ((BYTE*)&maskingKey) + (streamPos % 4);
72 Stream_Read_UINT8(sDataPacket, data);
73 Stream_Write_UINT8(sPacket, data ^ *partialMask);
74 }
75
76 Stream_SealLength(sPacket);
77
78 ERR_clear_error();
79 const size_t size = Stream_Length(sPacket);
80 const int status = websocket_write_all(bio, Stream_Buffer(sPacket), size);
81 Stream_Free(sPacket, TRUE);
82
83 return !((status < 0) || ((size_t)status != size));
84}
85
86wStream* websocket_context_packet_new(size_t len, WEBSOCKET_OPCODE opcode, UINT32* pMaskingKey)
87{
88 WINPR_ASSERT(pMaskingKey);
89 if (len > INT_MAX)
90 return nullptr;
91
92 size_t fullLen = 0;
93 if (len < 126)
94 fullLen = len + 6; /* 2 byte "mini header" + 4 byte masking key */
95 else if (len < 0x10000)
96 fullLen = len + 8; /* 2 byte "mini header" + 2 byte length + 4 byte masking key */
97 else
98 fullLen = len + 14; /* 2 byte "mini header" + 8 byte length + 4 byte masking key */
99
100 UINT32 maskingKey = 0;
101 if (winpr_RAND(&maskingKey, sizeof(maskingKey)) < 0)
102 return nullptr;
103
104 wStream* sWS = Stream_New(nullptr, fullLen);
105 if (!sWS)
106 return nullptr;
107
108 Stream_Write_UINT8(sWS, (UINT8)(WEBSOCKET_FIN_BIT | opcode));
109 if (len < 126)
110 Stream_Write_UINT8(sWS, (UINT8)len | WEBSOCKET_MASK_BIT);
111 else if (len < 0x10000)
112 {
113 Stream_Write_UINT8(sWS, 126 | WEBSOCKET_MASK_BIT);
114 Stream_Write_UINT16_BE(sWS, (UINT16)len);
115 }
116 else
117 {
118 Stream_Write_UINT8(sWS, 127 | WEBSOCKET_MASK_BIT);
119 Stream_Write_UINT32_BE(sWS, 0); /* payload is limited to INT_MAX */
120 Stream_Write_UINT32_BE(sWS, (UINT32)len);
121 }
122 Stream_Write_UINT32(sWS, maskingKey);
123 *pMaskingKey = maskingKey;
124 return sWS;
125}
126
127BOOL websocket_context_write_wstream(websocket_context* context, BIO* bio, wStream* sPacket,
128 WEBSOCKET_OPCODE opcode)
129{
130 WINPR_ASSERT(context);
131
132 if (context->closeSent)
133 return FALSE;
134
135 if (opcode == WebsocketCloseOpcode)
136 context->closeSent = TRUE;
137
138 WINPR_ASSERT(bio);
139 WINPR_ASSERT(sPacket);
140
141 const size_t len = Stream_Length(sPacket);
142 uint32_t maskingKey = 0;
143 wStream* sWS = websocket_context_packet_new(len, opcode, &maskingKey);
144 if (!sWS)
145 return FALSE;
146
147 return websocket_context_mask_and_send(bio, sWS, sPacket, maskingKey);
148}
149
150int websocket_write_all(BIO* bio, const BYTE* data, size_t length)
151{
152 WINPR_ASSERT(bio);
153 WINPR_ASSERT(data);
154 size_t offset = 0;
155
156 if (length > INT32_MAX)
157 return -1;
158
159 while (offset < length)
160 {
161 ERR_clear_error();
162 const size_t diff = length - offset;
163 int status = BIO_write(bio, &data[offset], (int)diff);
164
165 if (status > 0)
166 offset += (size_t)status;
167 else
168 {
169 if (!BIO_should_retry(bio))
170 return -1;
171
172 if (BIO_write_blocked(bio))
173 {
174 const long rstatus = BIO_wait_write(bio, 100);
175 if (rstatus < 0)
176 return -1;
177 }
178 else if (BIO_read_blocked(bio))
179 return -2; /* Abort write, there is data that must be read */
180 else
181 USleep(100);
182 }
183 }
184
185 return (int)length;
186}
187
188int websocket_context_write(websocket_context* context, BIO* bio, const BYTE* buf, int isize,
189 WEBSOCKET_OPCODE opcode)
190{
191 WINPR_ASSERT(bio);
192 WINPR_ASSERT(buf);
193
194 if (isize < 0)
195 return -1;
196
197 wStream sbuffer = WINPR_C_ARRAY_INIT;
198 wStream* s = Stream_StaticConstInit(&sbuffer, buf, (size_t)isize);
199 if (!Stream_SetLength(s, Stream_Capacity(s)))
200 return -3;
201 if (!websocket_context_write_wstream(context, bio, s, opcode))
202 return -2;
203 return isize;
204}
205
206static int websocket_read_data(BIO* bio, BYTE* pBuffer, size_t size,
207 websocket_context* encodingContext)
208{
209 int status = 0;
210
211 WINPR_ASSERT(bio);
212 WINPR_ASSERT(pBuffer);
213 WINPR_ASSERT(encodingContext);
214
215 if (encodingContext->payloadLength == 0)
216 {
217 encodingContext->state = WebsocketStateOpcodeAndFin;
218 return 0;
219 }
220
221 const size_t rlen =
222 (encodingContext->payloadLength < size ? encodingContext->payloadLength : size);
223 if (rlen > INT32_MAX)
224 return -1;
225
226 ERR_clear_error();
227 status = BIO_read(bio, pBuffer, (int)rlen);
228 if ((status <= 0) || ((size_t)status > encodingContext->payloadLength))
229 return status;
230
231 encodingContext->payloadLength -= (size_t)status;
232
233 if (encodingContext->payloadLength == 0)
234 encodingContext->state = WebsocketStateOpcodeAndFin;
235
236 return status;
237}
238
239static int websocket_read_wstream(BIO* bio, websocket_context* encodingContext)
240{
241 WINPR_ASSERT(bio);
242 WINPR_ASSERT(encodingContext);
243
244 wStream* s = encodingContext->responseStreamBuffer;
245 WINPR_ASSERT(s);
246
247 if (encodingContext->payloadLength == 0)
248 {
249 encodingContext->state = WebsocketStateOpcodeAndFin;
250 return 0;
251 }
252
253 if (!Stream_EnsureRemainingCapacity(s, encodingContext->payloadLength))
254 {
255 WLog_WARN(TAG,
256 "wStream::capacity [%" PRIuz "] != encodingContext::paylaodLangth [%" PRIuz "]",
257 Stream_GetRemainingCapacity(s), encodingContext->payloadLength);
258 return -1;
259 }
260
261 const int status = websocket_read_data(bio, Stream_Pointer(s), Stream_GetRemainingCapacity(s),
262 encodingContext);
263 if (status < 0)
264 return status;
265
266 if (!Stream_SafeSeek(s, (size_t)status))
267 return -1;
268
269 return status;
270}
271
272static BOOL websocket_reply_close(BIO* bio, websocket_context* context, wStream* s)
273{
274 WINPR_ASSERT(bio);
275
276 return websocket_context_write_wstream(context, bio, s, WebsocketCloseOpcode);
277}
278
279static BOOL websocket_reply_pong(BIO* bio, websocket_context* context, wStream* s)
280{
281 WINPR_ASSERT(bio);
282 WINPR_ASSERT(s);
283
284 if (Stream_GetPosition(s) != 0)
285 return websocket_context_write_wstream(context, bio, s, WebsocketPongOpcode);
286
287 return websocket_reply_close(bio, context, nullptr);
288}
289
290static int websocket_handle_payload(BIO* bio, BYTE* pBuffer, size_t size,
291 websocket_context* encodingContext)
292{
293 int status = 0;
294
295 WINPR_ASSERT(bio);
296 WINPR_ASSERT(pBuffer);
297 WINPR_ASSERT(encodingContext);
298
299 const BYTE effectiveOpcode = ((encodingContext->opcode & 0xf) == WebsocketContinuationOpcode
300 ? encodingContext->fragmentOriginalOpcode & 0xf
301 : encodingContext->opcode & 0xf);
302
303 switch (effectiveOpcode)
304 {
305 case WebsocketBinaryOpcode:
306 {
307 status = websocket_read_data(bio, pBuffer, size, encodingContext);
308 if (status < 0)
309 return status;
310
311 return status;
312 }
313 case WebsocketPingOpcode:
314 {
315 status = websocket_read_wstream(bio, encodingContext);
316 if (status < 0)
317 return status;
318
319 if (encodingContext->payloadLength == 0)
320 {
321 websocket_reply_pong(bio, encodingContext, encodingContext->responseStreamBuffer);
322 if (!Stream_Reset(encodingContext->responseStreamBuffer))
323 return -1;
324 }
325 }
326 break;
327 case WebsocketPongOpcode:
328 {
329 status = websocket_read_wstream(bio, encodingContext);
330 if (status < 0)
331 return status;
332 /* We don“t care about pong response data, discard. */
333 if (!Stream_Reset(encodingContext->responseStreamBuffer))
334 return -1;
335 }
336 break;
337 case WebsocketCloseOpcode:
338 {
339 status = websocket_read_wstream(bio, encodingContext);
340 if (status < 0)
341 return status;
342
343 if (encodingContext->payloadLength == 0)
344 {
345 websocket_reply_close(bio, encodingContext, encodingContext->responseStreamBuffer);
346 encodingContext->closeSent = TRUE;
347 if (!Stream_Reset(encodingContext->responseStreamBuffer))
348 return -1;
349 }
350 }
351 break;
352 default:
353 WLog_WARN(TAG, "Unimplemented websocket opcode %" PRIx8 ". Dropping", effectiveOpcode);
354
355 status = websocket_read_wstream(bio, encodingContext);
356 if (status < 0)
357 return status;
358 if (!Stream_Reset(encodingContext->responseStreamBuffer))
359 return -1;
360 break;
361 }
362 /* return how many bytes have been written to pBuffer.
363 * Only WebsocketBinaryOpcode writes into it and it returns directly */
364 return 0;
365}
366
367int websocket_context_read(websocket_context* encodingContext, BIO* bio, BYTE* pBuffer, size_t size)
368{
369 int status = 0;
370 size_t effectiveDataLen = 0;
371
372 WINPR_ASSERT(bio);
373 WINPR_ASSERT(pBuffer);
374 WINPR_ASSERT(encodingContext);
375
376 while (TRUE)
377 {
378 switch (encodingContext->state)
379 {
380 case WebsocketStateOpcodeAndFin:
381 {
382 BYTE buffer[1] = WINPR_C_ARRAY_INIT;
383
384 ERR_clear_error();
385 status = BIO_read(bio, (char*)buffer, sizeof(buffer));
386 if (status <= 0)
387 return (effectiveDataLen > 0 ? WINPR_ASSERTING_INT_CAST(int, effectiveDataLen)
388 : status);
389
390 encodingContext->opcode = buffer[0];
391 if (((encodingContext->opcode & 0xf) != WebsocketContinuationOpcode) &&
392 (encodingContext->opcode & 0xf) < 0x08)
393 encodingContext->fragmentOriginalOpcode = encodingContext->opcode;
394 encodingContext->state = WebsocketStateLengthAndMasking;
395 }
396 break;
397 case WebsocketStateLengthAndMasking:
398 {
399 BYTE buffer[1] = WINPR_C_ARRAY_INIT;
400
401 ERR_clear_error();
402 status = BIO_read(bio, (char*)buffer, sizeof(buffer));
403 if (status <= 0)
404 return (effectiveDataLen > 0 ? WINPR_ASSERTING_INT_CAST(int, effectiveDataLen)
405 : status);
406
407 encodingContext->masking = ((buffer[0] & WEBSOCKET_MASK_BIT) == WEBSOCKET_MASK_BIT);
408 encodingContext->lengthAndMaskPosition = 0;
409 encodingContext->payloadLength = 0;
410 const BYTE len = buffer[0] & 0x7f;
411 if (len < 126)
412 {
413 encodingContext->payloadLength = len;
414 encodingContext->state = (encodingContext->masking ? WebSocketStateMaskingKey
415 : WebSocketStatePayload);
416 }
417 else if (len == 126)
418 encodingContext->state = WebsocketStateShortLength;
419 else
420 encodingContext->state = WebsocketStateLongLength;
421 }
422 break;
423 case WebsocketStateShortLength:
424 case WebsocketStateLongLength:
425 {
426 BYTE buffer[1] = WINPR_C_ARRAY_INIT;
427 const BYTE lenLength =
428 (encodingContext->state == WebsocketStateShortLength ? 2 : 8);
429 while (encodingContext->lengthAndMaskPosition < lenLength)
430 {
431 ERR_clear_error();
432 status = BIO_read(bio, (char*)buffer, sizeof(buffer));
433 if (status <= 0)
434 return (effectiveDataLen > 0
435 ? WINPR_ASSERTING_INT_CAST(int, effectiveDataLen)
436 : status);
437 if (status > UINT8_MAX)
438 return -1;
439 encodingContext->payloadLength =
440 (encodingContext->payloadLength) << 8 | buffer[0];
441 encodingContext->lengthAndMaskPosition +=
442 WINPR_ASSERTING_INT_CAST(BYTE, status);
443 }
444 if (encodingContext->payloadLength > RESPONSE_SIZE_LIMIT)
445 {
446 WLog_ERR(TAG, "received excessive payload size %" PRIuz ", aborting",
447 encodingContext->payloadLength);
448 return -1;
449 }
450 encodingContext->state =
451 (encodingContext->masking ? WebSocketStateMaskingKey : WebSocketStatePayload);
452 }
453 break;
454 case WebSocketStateMaskingKey:
455 {
456 WLog_WARN(
457 TAG, "Websocket Server sends data with masking key. This is against RFC 6455.");
458 return -1;
459 }
460 case WebSocketStatePayload:
461 {
462 status = websocket_handle_payload(bio, pBuffer, size, encodingContext);
463 if (status < 0)
464 return (effectiveDataLen > 0 ? WINPR_ASSERTING_INT_CAST(int, effectiveDataLen)
465 : status);
466
467 effectiveDataLen += WINPR_ASSERTING_INT_CAST(size_t, status);
468
469 if (WINPR_ASSERTING_INT_CAST(size_t, status) >= size)
470 return WINPR_ASSERTING_INT_CAST(int, effectiveDataLen);
471 pBuffer += status;
472 size -= WINPR_ASSERTING_INT_CAST(size_t, status);
473 }
474 break;
475 default:
476 break;
477 }
478 }
479 /* should be unreachable */
480}
481
482websocket_context* websocket_context_new(void)
483{
484 websocket_context* context = calloc(1, sizeof(websocket_context));
485 if (!context)
486 goto fail;
487
488 context->responseStreamBuffer = Stream_New(nullptr, 1024);
489 if (!context->responseStreamBuffer)
490 goto fail;
491
492 if (!websocket_context_reset(context))
493 goto fail;
494
495 return context;
496fail:
497 websocket_context_free(context);
498 return nullptr;
499}
500
501void websocket_context_free(websocket_context* context)
502{
503 if (!context)
504 return;
505
506 Stream_Free(context->responseStreamBuffer, TRUE);
507 free(context);
508}
509
510BOOL websocket_context_reset(websocket_context* context)
511{
512 WINPR_ASSERT(context);
513
514 context->state = WebsocketStateOpcodeAndFin;
515 return Stream_Reset(context->responseStreamBuffer);
516}