FreeRDP
Loading...
Searching...
No Matches
wst.c
1
20#include <stdint.h>
21
22#include <freerdp/config.h>
23#include <freerdp/version.h>
24
25#include <winpr/assert.h>
26
27#include <winpr/crt.h>
28#include <winpr/synch.h>
29#include <winpr/print.h>
30#include <winpr/stream.h>
31#include <winpr/winsock.h>
32#include <winpr/cred.h>
33
34#include "../settings.h"
35
36#include <freerdp/log.h>
37#include <freerdp/error.h>
38#include <freerdp/utils/ringbuffer.h>
39#include <freerdp/utils/smartcardlogon.h>
40
41#include "wst.h"
42#include "websocket.h"
43#include "http.h"
44#include "../credssp_auth.h"
45#include "../proxy.h"
46#include "../rdp.h"
47#include "../../crypto/opensslcompat.h"
48#include "rpc_fault.h"
49#include "../utils.h"
50
51#define TAG FREERDP_TAG("core.gateway.wst")
52
53#define AUTH_PKG NEGO_SSP_NAME
54
55struct rdp_wst
56{
57 rdpContext* context;
58 BOOL attached;
59 BIO* frontBio;
60 rdpTls* tls;
61 rdpCredsspAuth* auth;
62 BOOL auth_required;
63 HttpContext* http;
64 CRITICAL_SECTION writeSection;
65 char* gwhostname;
66 uint16_t gwport;
67 char* gwpath;
68 websocket_context* wscontext;
69 wLog* log;
70};
71
72static const char arm_query_param[] = "%s%cClmTk=Bearer%%20%s";
73
74static BOOL wst_get_gateway_credentials(wLog* log, rdpContext* context, rdp_auth_reason reason)
75{
76 WINPR_ASSERT(context);
77 freerdp* instance = context->instance;
78
79 auth_status rc = utils_authenticate_gateway(instance, reason);
80 switch (rc)
81 {
82 case AUTH_SUCCESS:
83 case AUTH_SKIP:
84 return TRUE;
85 case AUTH_CANCELLED:
86 freerdp_set_last_error_log(instance->context, FREERDP_ERROR_CONNECT_CANCELLED);
87 return FALSE;
88 case AUTH_NO_CREDENTIALS:
89 WLog_Print(log, WLOG_INFO, "No credentials provided - using NULL identity");
90 return TRUE;
91 case AUTH_FAILED:
92 default:
93 return FALSE;
94 }
95}
96
97static BOOL wst_auth_init(rdpWst* wst, rdpTls* tls, TCHAR* authPkg)
98{
99 WINPR_ASSERT(wst);
100 WINPR_ASSERT(tls);
101 WINPR_ASSERT(authPkg);
102
103 rdpContext* context = wst->context;
104 rdpSettings* settings = context->settings;
105 SEC_WINNT_AUTH_IDENTITY identity = { 0 };
106 int rc = 0;
107
108 wst->auth_required = TRUE;
109 if (!credssp_auth_init(wst->auth, authPkg, tls->Bindings))
110 return FALSE;
111
112 if (!wst_get_gateway_credentials(wst->log, context, GW_AUTH_RDG))
113 return FALSE;
114
115 if (!identity_set_from_settings(&identity, settings, FreeRDP_GatewayUsername,
116 FreeRDP_GatewayDomain, FreeRDP_GatewayPassword))
117 return FALSE;
118
119 SEC_WINNT_AUTH_IDENTITY* identityArg = (settings->GatewayUsername ? &identity : NULL);
120 if (!credssp_auth_setup_client(wst->auth, "HTTP", wst->gwhostname, identityArg, NULL))
121 {
122 sspi_FreeAuthIdentity(&identity);
123 return FALSE;
124 }
125 sspi_FreeAuthIdentity(&identity);
126
127 credssp_auth_set_flags(wst->auth, ISC_REQ_CONFIDENTIALITY | ISC_REQ_MUTUAL_AUTH);
128
129 rc = credssp_auth_authenticate(wst->auth);
130 if (rc < 0)
131 return FALSE;
132
133 return TRUE;
134}
135
136static BOOL wst_set_auth_header(rdpCredsspAuth* auth, HttpRequest* request)
137{
138 WINPR_ASSERT(auth);
139 WINPR_ASSERT(request);
140
141 const SecBuffer* authToken = credssp_auth_get_output_buffer(auth);
142 char* base64AuthToken = NULL;
143
144 if (authToken)
145 {
146 if (authToken->cbBuffer > INT_MAX)
147 return FALSE;
148
149 base64AuthToken = crypto_base64_encode(authToken->pvBuffer, authToken->cbBuffer);
150 }
151
152 if (base64AuthToken)
153 {
154 BOOL rc = http_request_set_auth_scheme(request, credssp_auth_pkg_name(auth)) &&
155 http_request_set_auth_param(request, base64AuthToken);
156 free(base64AuthToken);
157
158 if (!rc)
159 return FALSE;
160 }
161
162 return TRUE;
163}
164
165static BOOL wst_recv_auth_token(rdpCredsspAuth* auth, HttpResponse* response)
166{
167 size_t len = 0;
168 size_t authTokenLength = 0;
169 BYTE* authTokenData = NULL;
170 SecBuffer authToken = { 0 };
171 int rc = 0;
172
173 if (!auth || !response)
174 return FALSE;
175
176 const UINT16 StatusCode = http_response_get_status_code(response);
177 switch (StatusCode)
178 {
179 case HTTP_STATUS_DENIED:
180 case HTTP_STATUS_OK:
181 break;
182 default:
183 http_response_log_error_status(WLog_Get(TAG), WLOG_WARN, response);
184 return FALSE;
185 }
186
187 const char* token64 = http_response_get_auth_token(response, credssp_auth_pkg_name(auth));
188
189 if (!token64)
190 return FALSE;
191
192 len = strlen(token64);
193
194 crypto_base64_decode(token64, len, &authTokenData, &authTokenLength);
195
196 if (authTokenLength && (authTokenLength <= UINT32_MAX) && authTokenData)
197 {
198 authToken.pvBuffer = authTokenData;
199 authToken.cbBuffer = (UINT32)authTokenLength;
200 credssp_auth_take_input_buffer(auth, &authToken);
201 }
202 else
203 free(authTokenData);
204
205 rc = credssp_auth_authenticate(auth);
206 if (rc < 0)
207 return FALSE;
208
209 return TRUE;
210}
211
212static BOOL wst_tls_connect(rdpWst* wst, rdpTls* tls, UINT32 timeout)
213{
214 WINPR_ASSERT(wst);
215 WINPR_ASSERT(tls);
216 int sockfd = 0;
217 long status = 0;
218 BIO* socketBio = NULL;
219 BIO* bufferedBio = NULL;
220 rdpSettings* settings = wst->context->settings;
221 const char* peerHostname = wst->gwhostname;
222 UINT16 peerPort = wst->gwport;
223 const char* proxyUsername = NULL;
224 const char* proxyPassword = NULL;
225 BOOL isProxyConnection =
226 proxy_prepare(settings, &peerHostname, &peerPort, &proxyUsername, &proxyPassword);
227
228 sockfd = freerdp_tcp_connect(wst->context, peerHostname, peerPort, timeout);
229
230 WLog_Print(wst->log, WLOG_DEBUG, "connecting to %s %d", peerHostname, peerPort);
231 if (sockfd < 0)
232 {
233 return FALSE;
234 }
235
236 socketBio = BIO_new(BIO_s_simple_socket());
237
238 if (!socketBio)
239 {
240 closesocket((SOCKET)sockfd);
241 return FALSE;
242 }
243
244 BIO_set_fd(socketBio, sockfd, BIO_CLOSE);
245 bufferedBio = BIO_new(BIO_s_buffered_socket());
246
247 if (!bufferedBio)
248 {
249 BIO_free_all(socketBio);
250 return FALSE;
251 }
252
253 bufferedBio = BIO_push(bufferedBio, socketBio);
254 status = BIO_set_nonblock(bufferedBio, TRUE);
255
256 if (isProxyConnection)
257 {
258 if (!proxy_connect(wst->context, bufferedBio, proxyUsername, proxyPassword, wst->gwhostname,
259 wst->gwport))
260 {
261 BIO_free_all(bufferedBio);
262 return FALSE;
263 }
264 }
265
266 if (!status)
267 {
268 BIO_free_all(bufferedBio);
269 return FALSE;
270 }
271
272 tls->hostname = wst->gwhostname;
273 tls->port = MIN(UINT16_MAX, wst->gwport);
274 tls->isGatewayTransport = TRUE;
275 status = freerdp_tls_connect(tls, bufferedBio);
276 if (status < 1)
277 {
278 rdpContext* context = wst->context;
279 if (status < 0)
280 {
281 freerdp_set_last_error_if_not(context, FREERDP_ERROR_TLS_CONNECT_FAILED);
282 }
283 else
284 {
285 freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_CANCELLED);
286 }
287
288 return FALSE;
289 }
290 return (status >= 1);
291}
292
293static wStream* wst_build_http_request(rdpWst* wst)
294{
295 wStream* s = NULL;
296 HttpRequest* request = NULL;
297 const char* uri = NULL;
298
299 if (!wst)
300 return NULL;
301
302 uri = http_context_get_uri(wst->http);
303 request = http_request_new();
304
305 if (!request)
306 return NULL;
307
308 if (!http_request_set_method(request, "GET") || !http_request_set_uri(request, uri))
309 goto out;
310
311 if (wst->auth_required)
312 {
313 if (!wst_set_auth_header(wst->auth, request))
314 goto out;
315 }
316 else if (freerdp_settings_get_string(wst->context->settings, FreeRDP_GatewayHttpExtAuthBearer))
317 {
318 http_request_set_auth_scheme(request, "Bearer");
319 http_request_set_auth_param(
320 request,
321 freerdp_settings_get_string(wst->context->settings, FreeRDP_GatewayHttpExtAuthBearer));
322 }
323
324 s = http_request_write(wst->http, request);
325out:
326 http_request_free(request);
327
328 if (s)
329 Stream_SealLength(s);
330
331 return s;
332}
333
334static BOOL wst_send_http_request(rdpWst* wst, rdpTls* tls)
335{
336 WINPR_ASSERT(wst);
337 WINPR_ASSERT(tls);
338
339 wStream* s = wst_build_http_request(wst);
340 if (!s)
341 return FALSE;
342
343 const size_t sz = Stream_Length(s);
344 WLog_Print(wst->log, WLOG_TRACE, "header [%" PRIuz "]: %s", sz, Stream_Buffer(s));
345
346 const int status = freerdp_tls_write_all(tls, Stream_Buffer(s), sz);
347 Stream_Free(s, TRUE);
348 return (status >= 0);
349}
350
351static BOOL wst_handle_ok_or_forbidden(rdpWst* wst, HttpResponse** ppresponse, DWORD timeout,
352 UINT16* pStatusCode)
353{
354 WINPR_ASSERT(wst);
355 WINPR_ASSERT(ppresponse);
356 WINPR_ASSERT(*ppresponse);
357 WINPR_ASSERT(pStatusCode);
358
359 /* AVD returns a 403 response with a ARRAffinity cookie set. retry with that cookie */
360 const char* affinity = http_response_get_setcookie(*ppresponse, "ARRAffinity");
361 const char* samesite = http_response_get_setcookie(*ppresponse, "ARRAffinitySameSite");
362 if ((affinity || samesite) &&
363 freerdp_settings_get_bool(wst->context->settings, FreeRDP_GatewayArmTransport))
364 {
365 WLog_Print(wst->log, WLOG_INFO, "Got ARRAffinity cookie %s", affinity);
366 WLog_Print(wst->log, WLOG_INFO, "Got ARRAffinitySameSite cookie %s", samesite);
367 if (affinity)
368 http_context_set_cookie(wst->http, "ARRAffinity", affinity);
369 if (samesite)
370 http_context_set_cookie(wst->http, "ARRAffinitySameSite", samesite);
371 http_response_free(*ppresponse);
372 *ppresponse = NULL;
373 /* Terminate this connection and make a new one with the Loadbalancing Cookie */
374 const long fd = BIO_get_fd(wst->tls->bio, NULL);
375 if ((fd >= 0) && (fd <= INT32_MAX))
376 closesocket((SOCKET)fd);
377 freerdp_tls_free(wst->tls);
378
379 wst->tls = freerdp_tls_new(wst->context);
380 if (!wst_tls_connect(wst, wst->tls, timeout))
381 return FALSE;
382
383 if (freerdp_settings_get_string(wst->context->settings, FreeRDP_GatewayHttpExtAuthBearer) &&
384 freerdp_settings_get_bool(wst->context->settings, FreeRDP_GatewayArmTransport))
385 {
386 char* urlWithAuth = NULL;
387 size_t urlLen = 0;
388 char firstParam = (strchr(wst->gwpath, '?') != NULL) ? '&' : '?';
389 const char* bearer = freerdp_settings_get_string(wst->context->settings,
390 FreeRDP_GatewayHttpExtAuthBearer);
391 const char* ua =
392 freerdp_settings_get_string(wst->context->settings, FreeRDP_GatewayHttpMsUserAgent);
393 winpr_asprintf(&urlWithAuth, &urlLen, arm_query_param, wst->gwpath, firstParam, bearer,
394 ua);
395 if (!urlWithAuth)
396 return FALSE;
397 free(wst->gwpath);
398 wst->gwpath = urlWithAuth;
399 if (!utils_str_is_empty(ua))
400 {
401 size_t ualen = 0;
402 char* uastr = NULL;
403 winpr_asprintf(&uastr, &ualen, "%s&X-MS-User-Agent=%s", wst->gwpath, ua);
404 if (!uastr)
405 return FALSE;
406 free(wst->gwpath);
407 wst->gwpath = uastr;
408 }
409 if (!http_context_set_uri(wst->http, wst->gwpath))
410 return FALSE;
411 if (!http_context_enable_websocket_upgrade(wst->http, TRUE))
412 return FALSE;
413 }
414
415 if (!wst_send_http_request(wst, wst->tls))
416 return FALSE;
417 *ppresponse = http_response_recv(wst->tls, TRUE);
418 if (!*ppresponse)
419 return FALSE;
420
421 (void)http_response_extract_cookies(*ppresponse, wst->http);
422 *pStatusCode = http_response_get_status_code(*ppresponse);
423 }
424
425 return TRUE;
426}
427
428static BOOL wst_handle_denied(rdpWst* wst, HttpResponse** ppresponse, UINT16* pStatusCode)
429{
430 WINPR_ASSERT(wst);
431 WINPR_ASSERT(ppresponse);
432 WINPR_ASSERT(*ppresponse);
433 WINPR_ASSERT(pStatusCode);
434
435 if (freerdp_settings_get_string(wst->context->settings, FreeRDP_GatewayHttpExtAuthBearer))
436 return FALSE;
437
438 if (!wst_auth_init(wst, wst->tls, AUTH_PKG))
439 return FALSE;
440 if (!wst_send_http_request(wst, wst->tls))
441 return FALSE;
442
443 http_response_free(*ppresponse);
444 *ppresponse = http_response_recv(wst->tls, TRUE);
445 if (!*ppresponse)
446 return FALSE;
447
448 (void)http_response_extract_cookies(*ppresponse, wst->http);
449
450 while (!credssp_auth_is_complete(wst->auth))
451 {
452 if (!wst_recv_auth_token(wst->auth, *ppresponse))
453 return FALSE;
454
455 if (credssp_auth_have_output_token(wst->auth))
456 {
457 if (!wst_send_http_request(wst, wst->tls))
458 return FALSE;
459
460 http_response_free(*ppresponse);
461 *ppresponse = http_response_recv(wst->tls, TRUE);
462 if (!*ppresponse)
463 return FALSE;
464 (void)http_response_extract_cookies(*ppresponse, wst->http);
465 }
466 }
467 *pStatusCode = http_response_get_status_code(*ppresponse);
468 return TRUE;
469}
470
471static BOOL wst_handle_http_code(rdpWst* wst, UINT16 StatusCode)
472{
473 switch (StatusCode)
474 {
475 case HTTP_STATUS_PAYMENT_REQ:
476 case HTTP_STATUS_FORBIDDEN:
477 case HTTP_STATUS_DENIED:
478 freerdp_set_last_error_if_not(wst->context, FREERDP_ERROR_CONNECT_ACCESS_DENIED);
479 break;
480 case HTTP_STATUS_MOVED:
481 case HTTP_STATUS_USE_PROXY:
482 case HTTP_STATUS_BAD_REQUEST:
483 case HTTP_STATUS_NOT_FOUND:
484 case HTTP_STATUS_GONE:
485 freerdp_set_last_error_if_not(wst->context, FREERDP_ERROR_CONNECT_TRANSPORT_FAILED);
486 break;
487 case HTTP_STATUS_SERVER_ERROR:
488 case HTTP_STATUS_NOT_SUPPORTED:
489 case HTTP_STATUS_BAD_GATEWAY:
490 case HTTP_STATUS_SERVICE_UNAVAIL:
491 case HTTP_STATUS_VERSION_NOT_SUP:
492 freerdp_set_last_error_if_not(wst->context, FREERDP_ERROR_CONNECT_TRANSPORT_FAILED);
493 break;
494 case HTTP_STATUS_GATEWAY_TIMEOUT:
495 freerdp_set_last_error_if_not(wst->context, FREERDP_ERROR_CONNECT_ACTIVATION_TIMEOUT);
496 break;
497 default:
498 break;
499 }
500
501 char buffer[64] = { 0 };
502 WLog_Print(wst->log, WLOG_ERROR, "Unexpected HTTP status: %s",
503 freerdp_http_status_string_format(StatusCode, buffer, ARRAYSIZE(buffer)));
504 freerdp_set_last_error_if_not(wst->context, FREERDP_ERROR_CONNECT_FAILED);
505 return FALSE;
506}
507
508BOOL wst_connect(rdpWst* wst, DWORD timeout)
509{
510 WINPR_ASSERT(wst);
511 WINPR_ASSERT(wst->context);
512
513 if (!wst_tls_connect(wst, wst->tls, timeout))
514 {
515 freerdp_set_last_error_if_not(wst->context, FREERDP_ERROR_CONNECT_FAILED);
516 return FALSE;
517 }
518
519 if (freerdp_settings_get_bool(wst->context->settings, FreeRDP_GatewayArmTransport))
520 {
521 /*
522 * If we are directed here from a ARM Gateway first
523 * we need to get a Loadbalancing Cookie (ARRAffinity)
524 * This is done by a plain GET request on the websocket URL
525 */
526 http_context_enable_websocket_upgrade(wst->http, FALSE);
527 }
528 if (!wst_send_http_request(wst, wst->tls))
529 {
530 freerdp_set_last_error_if_not(wst->context, FREERDP_ERROR_CONNECT_FAILED);
531 return FALSE;
532 }
533
534 HttpResponse* response = http_response_recv(wst->tls, TRUE);
535 if (!response)
536 {
537 freerdp_set_last_error_if_not(wst->context, FREERDP_ERROR_CONNECT_FAILED);
538 return FALSE;
539 }
540 (void)http_response_extract_cookies(response, wst->http);
541
542 UINT16 StatusCode = http_response_get_status_code(response);
543 BOOL success = TRUE;
544 switch (StatusCode)
545 {
546 case HTTP_STATUS_FORBIDDEN:
547 case HTTP_STATUS_OK:
548 success = wst_handle_ok_or_forbidden(wst, &response, timeout, &StatusCode);
549 break;
550
551 case HTTP_STATUS_DENIED:
552 success = wst_handle_denied(wst, &response, &StatusCode);
553 break;
554 default:
555 http_response_log_error_status(WLog_Get(TAG), WLOG_WARN, response);
556 break;
557 }
558
559 const BOOL isWebsocket = http_response_is_websocket(wst->http, response);
560 http_response_free(response);
561 if (!success)
562 return wst_handle_http_code(wst, StatusCode);
563
564 if (isWebsocket)
565 return websocket_context_reset(wst->wscontext);
566
567 return wst_handle_http_code(wst, StatusCode);
568}
569
570DWORD wst_get_event_handles(rdpWst* wst, HANDLE* events, DWORD count)
571{
572 DWORD nCount = 0;
573 WINPR_ASSERT(wst != NULL);
574
575 if (wst->tls)
576 {
577 if (events && (nCount < count))
578 {
579 BIO_get_event(wst->tls->bio, &events[nCount]);
580 nCount++;
581 }
582 else
583 return 0;
584 }
585
586 return nCount;
587}
588
589static int wst_bio_write(BIO* bio, const char* buf, int num)
590{
591 int status = 0;
592 WINPR_ASSERT(bio);
593 WINPR_ASSERT(buf);
594
595 rdpWst* wst = (rdpWst*)BIO_get_data(bio);
596 WINPR_ASSERT(wst);
597 BIO_clear_flags(bio, BIO_FLAGS_WRITE);
598 EnterCriticalSection(&wst->writeSection);
599 status = websocket_context_write(wst->wscontext, wst->tls->bio, (const BYTE*)buf, num,
600 WebsocketBinaryOpcode);
601 LeaveCriticalSection(&wst->writeSection);
602
603 if (status < 0)
604 {
605 BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
606 return -1;
607 }
608 else if (status < num)
609 {
610 BIO_set_flags(bio, BIO_FLAGS_WRITE);
611 WSASetLastError(WSAEWOULDBLOCK);
612 }
613 else
614 {
615 BIO_set_flags(bio, BIO_FLAGS_WRITE);
616 }
617
618 return status;
619}
620
621static int wst_bio_read(BIO* bio, char* buf, int size)
622{
623 int status = 0;
624 WINPR_ASSERT(bio);
625 WINPR_ASSERT(buf);
626 WINPR_ASSERT(size >= 0);
627
628 rdpWst* wst = (rdpWst*)BIO_get_data(bio);
629 WINPR_ASSERT(wst);
630
631 while (status <= 0)
632 {
633 status = websocket_context_read(wst->wscontext, wst->tls->bio, (BYTE*)buf, (size_t)size);
634 if (status <= 0)
635 {
636 if (!BIO_should_retry(wst->tls->bio))
637 return -1;
638 return 0;
639 }
640 }
641
642 if (status < 0)
643 {
644 BIO_clear_retry_flags(bio);
645 return -1;
646 }
647 else if (status == 0)
648 {
649 BIO_set_retry_read(bio);
650 WSASetLastError(WSAEWOULDBLOCK);
651 return -1;
652 }
653 else
654 {
655 BIO_set_flags(bio, BIO_FLAGS_READ);
656 }
657
658 return status;
659}
660
661static int wst_bio_puts(BIO* bio, const char* str)
662{
663 WINPR_UNUSED(bio);
664 WINPR_UNUSED(str);
665 return -2;
666}
667
668// NOLINTNEXTLINE(readability-non-const-parameter)
669static int wst_bio_gets(BIO* bio, char* str, int size)
670{
671 WINPR_UNUSED(bio);
672 WINPR_UNUSED(str);
673 WINPR_UNUSED(size);
674 return -2;
675}
676
677static long wst_bio_ctrl(BIO* bio, int cmd, long arg1, void* arg2)
678{
679 long status = -1;
680 WINPR_ASSERT(bio);
681
682 rdpWst* wst = (rdpWst*)BIO_get_data(bio);
683 WINPR_ASSERT(wst);
684 rdpTls* tls = wst->tls;
685
686 if (cmd == BIO_CTRL_FLUSH)
687 {
688 (void)BIO_flush(tls->bio);
689 status = 1;
690 }
691 else if (cmd == BIO_C_SET_NONBLOCK)
692 {
693 status = 1;
694 }
695 else if (cmd == BIO_C_READ_BLOCKED)
696 {
697 status = BIO_read_blocked(tls->bio);
698 }
699 else if (cmd == BIO_C_WRITE_BLOCKED)
700 {
701 status = BIO_write_blocked(tls->bio);
702 }
703 else if (cmd == BIO_C_WAIT_READ)
704 {
705 int timeout = (int)arg1;
706
707 if (BIO_read_blocked(tls->bio))
708 return BIO_wait_read(tls->bio, timeout);
709 status = 1;
710 }
711 else if (cmd == BIO_C_WAIT_WRITE)
712 {
713 int timeout = (int)arg1;
714
715 if (BIO_write_blocked(tls->bio))
716 status = BIO_wait_write(tls->bio, timeout);
717 else
718 status = 1;
719 }
720 else if (cmd == BIO_C_GET_EVENT || cmd == BIO_C_GET_FD)
721 {
722 status = BIO_ctrl(tls->bio, cmd, arg1, arg2);
723 }
724#if OPENSSL_VERSION_NUMBER >= 0x30000000L
725 else if (cmd == BIO_CTRL_GET_KTLS_SEND)
726 {
727 /* Even though BIO_get_ktls_send says that returning negative values is valid
728 * openssl internal sources are full of if(!BIO_get_ktls_send && ) stuff. This has some
729 * nasty sideeffects. return 0 as proper no KTLS offloading flag
730 */
731 status = 0;
732 }
733 else if (cmd == BIO_CTRL_GET_KTLS_RECV)
734 {
735 /* Even though BIO_get_ktls_recv says that returning negative values is valid
736 * there is no reason to trust trust negative values are implemented right everywhere
737 */
738 status = 0;
739 }
740#endif
741 return status;
742}
743
744static int wst_bio_new(BIO* bio)
745{
746 BIO_set_init(bio, 1);
747 BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
748 return 1;
749}
750
751static int wst_bio_free(BIO* bio)
752{
753 WINPR_UNUSED(bio);
754 return 1;
755}
756
757static BIO_METHOD* BIO_s_wst(void)
758{
759 static BIO_METHOD* bio_methods = NULL;
760
761 if (bio_methods == NULL)
762 {
763 if (!(bio_methods = BIO_meth_new(BIO_TYPE_TSG, "WSTransport")))
764 return NULL;
765
766 BIO_meth_set_write(bio_methods, wst_bio_write);
767 BIO_meth_set_read(bio_methods, wst_bio_read);
768 BIO_meth_set_puts(bio_methods, wst_bio_puts);
769 BIO_meth_set_gets(bio_methods, wst_bio_gets);
770 BIO_meth_set_ctrl(bio_methods, wst_bio_ctrl);
771 BIO_meth_set_create(bio_methods, wst_bio_new);
772 BIO_meth_set_destroy(bio_methods, wst_bio_free);
773 }
774
775 return bio_methods;
776}
777
778static BOOL wst_parse_url(rdpWst* wst, const char* url)
779{
780 const char* hostStart = NULL;
781 const char* pos = NULL;
782 WINPR_ASSERT(wst);
783 WINPR_ASSERT(url);
784
785 free(wst->gwhostname);
786 wst->gwhostname = NULL;
787 free(wst->gwpath);
788 wst->gwpath = NULL;
789
790 if (strncmp("wss://", url, 6) != 0)
791 {
792 if (strncmp("https://", url, 8) != 0)
793 {
794 WLog_Print(wst->log, WLOG_ERROR,
795 "Websocket URL is invalid. Only wss:// or https:// URLs are supported");
796 return FALSE;
797 }
798 else
799 hostStart = url + 8;
800 }
801 else
802 hostStart = url + 6;
803
804 pos = hostStart;
805 while (*pos != '\0' && *pos != ':' && *pos != '/')
806 pos++;
807 free(wst->gwhostname);
808 wst->gwhostname = NULL;
809 if (pos - hostStart == 0)
810 return FALSE;
811 wst->gwhostname = strndup(hostStart, WINPR_ASSERTING_INT_CAST(size_t, (pos - hostStart)));
812 if (!wst->gwhostname)
813 return FALSE;
814
815 if (*pos == ':')
816 {
817 char port[6] = { 0 };
818 char* portNumberEnd = NULL;
819 pos++;
820 const char* portStart = pos;
821 while (*pos != '\0' && *pos != '/')
822 pos++;
823 if (pos - portStart > 5 || pos - portStart == 0)
824 return FALSE;
825 strncpy(port, portStart, WINPR_ASSERTING_INT_CAST(size_t, (pos - portStart)));
826 port[pos - portStart] = '\0';
827 long _p = strtol(port, &portNumberEnd, 10);
828 if (portNumberEnd && (*portNumberEnd == '\0') && (_p > 0) && (_p <= UINT16_MAX))
829 wst->gwport = (uint16_t)_p;
830 else
831 return FALSE;
832 }
833 else
834 wst->gwport = 443;
835 wst->gwpath = _strdup(pos);
836 if (!wst->gwpath)
837 return FALSE;
838 return TRUE;
839}
840
841rdpWst* wst_new(rdpContext* context)
842{
843 if (!context)
844 return NULL;
845
846 rdpWst* wst = (rdpWst*)calloc(1, sizeof(rdpWst));
847 if (!wst)
848 return NULL;
849
850 wst->log = WLog_Get(TAG);
851 wst->context = context;
852
853 wst->gwhostname = NULL;
854 wst->gwport = 443;
855 wst->gwpath = NULL;
856
857 if (!wst_parse_url(wst, context->settings->GatewayUrl))
858 goto wst_alloc_error;
859
860 wst->tls = freerdp_tls_new(wst->context);
861 if (!wst->tls)
862 goto wst_alloc_error;
863
864 wst->http = http_context_new();
865
866 if (!wst->http)
867 goto wst_alloc_error;
868
869 const char* useragent =
870 freerdp_settings_get_string(context->settings, FreeRDP_GatewayHttpUserAgent);
871 const char* msuseragent =
872 freerdp_settings_get_string(context->settings, FreeRDP_GatewayHttpMsUserAgent);
873 if (!http_context_set_uri(wst->http, wst->gwpath) ||
874 !http_context_set_accept(wst->http, "*/*") ||
875 !http_context_set_cache_control(wst->http, "no-cache") ||
876 !http_context_set_pragma(wst->http, "no-cache") ||
877 !http_context_set_connection(wst->http, "Keep-Alive") ||
878 !http_context_set_user_agent(wst->http, useragent) ||
879 !http_context_set_x_ms_user_agent(wst->http, msuseragent) ||
880 !http_context_set_host(wst->http, wst->gwhostname) ||
881 !http_context_enable_websocket_upgrade(wst->http, TRUE))
882 {
883 goto wst_alloc_error;
884 }
885
886 wst->frontBio = BIO_new(BIO_s_wst());
887
888 if (!wst->frontBio)
889 goto wst_alloc_error;
890
891 BIO_set_data(wst->frontBio, wst);
892 InitializeCriticalSection(&wst->writeSection);
893 wst->auth = credssp_auth_new(context);
894 if (!wst->auth)
895 goto wst_alloc_error;
896
897 wst->wscontext = websocket_context_new();
898 if (!wst->wscontext)
899 goto wst_alloc_error;
900
901 return wst;
902wst_alloc_error:
903 WINPR_PRAGMA_DIAG_PUSH
904 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
905 wst_free(wst);
906 WINPR_PRAGMA_DIAG_POP
907 return NULL;
908}
909
910void wst_free(rdpWst* wst)
911{
912 if (!wst)
913 return;
914
915 freerdp_tls_free(wst->tls);
916 http_context_free(wst->http);
917 credssp_auth_free(wst->auth);
918 free(wst->gwhostname);
919 free(wst->gwpath);
920
921 if (!wst->attached)
922 BIO_free_all(wst->frontBio);
923
924 DeleteCriticalSection(&wst->writeSection);
925
926 websocket_context_free(wst->wscontext);
927
928 free(wst);
929}
930
931BIO* wst_get_front_bio_and_take_ownership(rdpWst* wst)
932{
933 if (!wst)
934 return NULL;
935
936 wst->attached = TRUE;
937 return wst->frontBio;
938}
FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.
FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.