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