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