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