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