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