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