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