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