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=FreeRDP%%2F3.0";
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 if (affinity && freerdp_settings_get_bool(wst->context->settings, FreeRDP_GatewayArmTransport))
360 {
361 WLog_DBG(TAG, "Got Affinity cookie %s", affinity);
362 http_context_set_cookie(wst->http, "ARRAffinity", affinity);
363 http_response_free(*ppresponse);
364 *ppresponse = NULL;
365 /* Terminate this connection and make a new one with the Loadbalancing Cookie */
366 const long fd = BIO_get_fd(wst->tls->bio, NULL);
367 if ((fd >= 0) && (fd <= INT32_MAX))
368 closesocket((SOCKET)fd);
369 freerdp_tls_free(wst->tls);
370
371 wst->tls = freerdp_tls_new(wst->context);
372 if (!wst_tls_connect(wst, wst->tls, timeout))
373 return FALSE;
374
375 if (freerdp_settings_get_string(wst->context->settings, FreeRDP_GatewayHttpExtAuthBearer) &&
376 freerdp_settings_get_bool(wst->context->settings, FreeRDP_GatewayArmTransport))
377 {
378 char* urlWithAuth = NULL;
379 size_t urlLen = 0;
380 char firstParam = (strchr(wst->gwpath, '?') != NULL) ? '&' : '?';
381 winpr_asprintf(&urlWithAuth, &urlLen, arm_query_param, wst->gwpath, firstParam,
382 freerdp_settings_get_string(wst->context->settings,
383 FreeRDP_GatewayHttpExtAuthBearer));
384 if (!urlWithAuth)
385 return FALSE;
386 free(wst->gwpath);
387 wst->gwpath = urlWithAuth;
388 if (!http_context_set_uri(wst->http, wst->gwpath))
389 return FALSE;
390 if (!http_context_enable_websocket_upgrade(wst->http, TRUE))
391 return FALSE;
392 }
393
394 if (!wst_send_http_request(wst, wst->tls))
395 return FALSE;
396 *ppresponse = http_response_recv(wst->tls, TRUE);
397 if (!*ppresponse)
398 return FALSE;
399
400 *pStatusCode = http_response_get_status_code(*ppresponse);
401 }
402
403 return TRUE;
404}
405
406static BOOL wst_handle_denied(rdpWst* wst, HttpResponse** ppresponse, UINT16* pStatusCode)
407{
408 WINPR_ASSERT(wst);
409 WINPR_ASSERT(ppresponse);
410 WINPR_ASSERT(*ppresponse);
411 WINPR_ASSERT(pStatusCode);
412
413 if (freerdp_settings_get_string(wst->context->settings, FreeRDP_GatewayHttpExtAuthBearer))
414 return FALSE;
415
416 if (!wst_auth_init(wst, wst->tls, AUTH_PKG))
417 return FALSE;
418 if (!wst_send_http_request(wst, wst->tls))
419 return FALSE;
420
421 http_response_free(*ppresponse);
422 *ppresponse = http_response_recv(wst->tls, TRUE);
423 if (!*ppresponse)
424 return FALSE;
425
426 while (!credssp_auth_is_complete(wst->auth))
427 {
428 if (!wst_recv_auth_token(wst->auth, *ppresponse))
429 return FALSE;
430
431 if (credssp_auth_have_output_token(wst->auth))
432 {
433 if (!wst_send_http_request(wst, wst->tls))
434 return FALSE;
435
436 http_response_free(*ppresponse);
437 *ppresponse = http_response_recv(wst->tls, TRUE);
438 if (!*ppresponse)
439 return FALSE;
440 }
441 }
442 *pStatusCode = http_response_get_status_code(*ppresponse);
443 return TRUE;
444}
445
446static BOOL wst_handle_http_code(rdpWst* wst, UINT16 StatusCode)
447{
448 switch (StatusCode)
449 {
450 case HTTP_STATUS_PAYMENT_REQ:
451 case HTTP_STATUS_FORBIDDEN:
452 case HTTP_STATUS_DENIED:
453 freerdp_set_last_error_if_not(wst->context, FREERDP_ERROR_CONNECT_ACCESS_DENIED);
454 break;
455 case HTTP_STATUS_MOVED:
456 case HTTP_STATUS_USE_PROXY:
457 case HTTP_STATUS_BAD_REQUEST:
458 case HTTP_STATUS_NOT_FOUND:
459 case HTTP_STATUS_GONE:
460 freerdp_set_last_error_if_not(wst->context, FREERDP_ERROR_CONNECT_TRANSPORT_FAILED);
461 break;
462 case HTTP_STATUS_SERVER_ERROR:
463 case HTTP_STATUS_NOT_SUPPORTED:
464 case HTTP_STATUS_BAD_GATEWAY:
465 case HTTP_STATUS_SERVICE_UNAVAIL:
466 case HTTP_STATUS_VERSION_NOT_SUP:
467 freerdp_set_last_error_if_not(wst->context, FREERDP_ERROR_CONNECT_TRANSPORT_FAILED);
468 break;
469 case HTTP_STATUS_GATEWAY_TIMEOUT:
470 freerdp_set_last_error_if_not(wst->context, FREERDP_ERROR_CONNECT_ACTIVATION_TIMEOUT);
471 break;
472 default:
473 break;
474 }
475
476 char buffer[64] = { 0 };
477 WLog_ERR(TAG, "Unexpected HTTP status: %s",
478 freerdp_http_status_string_format(StatusCode, buffer, ARRAYSIZE(buffer)));
479 freerdp_set_last_error_if_not(wst->context, FREERDP_ERROR_CONNECT_FAILED);
480 return FALSE;
481}
482
483BOOL wst_connect(rdpWst* wst, DWORD timeout)
484{
485 WINPR_ASSERT(wst);
486 WINPR_ASSERT(wst->context);
487
488 if (!wst_tls_connect(wst, wst->tls, timeout))
489 {
490 freerdp_set_last_error_if_not(wst->context, FREERDP_ERROR_CONNECT_FAILED);
491 return FALSE;
492 }
493
494 if (freerdp_settings_get_bool(wst->context->settings, FreeRDP_GatewayArmTransport))
495 {
496 /*
497 * If we are directed here from a ARM Gateway first
498 * we need to get a Loadbalancing Cookie (ARRAffinity)
499 * This is done by a plain GET request on the websocket URL
500 */
501 http_context_enable_websocket_upgrade(wst->http, FALSE);
502 }
503 if (!wst_send_http_request(wst, wst->tls))
504 {
505 freerdp_set_last_error_if_not(wst->context, FREERDP_ERROR_CONNECT_FAILED);
506 return FALSE;
507 }
508
509 HttpResponse* response = http_response_recv(wst->tls, TRUE);
510 if (!response)
511 {
512 freerdp_set_last_error_if_not(wst->context, FREERDP_ERROR_CONNECT_FAILED);
513 return FALSE;
514 }
515
516 UINT16 StatusCode = http_response_get_status_code(response);
517 BOOL success = TRUE;
518 switch (StatusCode)
519 {
520 case HTTP_STATUS_FORBIDDEN:
521 case HTTP_STATUS_OK:
522 success = wst_handle_ok_or_forbidden(wst, &response, timeout, &StatusCode);
523 break;
524
525 case HTTP_STATUS_DENIED:
526 success = wst_handle_denied(wst, &response, &StatusCode);
527 break;
528 default:
529 http_response_log_error_status(WLog_Get(TAG), WLOG_WARN, response);
530 break;
531 }
532
533 const BOOL isWebsocket = http_response_is_websocket(wst->http, response);
534 http_response_free(response);
535 if (!success)
536 return wst_handle_http_code(wst, StatusCode);
537
538 if (isWebsocket)
539 return websocket_context_reset(wst->wscontext);
540
541 return wst_handle_http_code(wst, StatusCode);
542}
543
544DWORD wst_get_event_handles(rdpWst* wst, HANDLE* events, DWORD count)
545{
546 DWORD nCount = 0;
547 WINPR_ASSERT(wst != NULL);
548
549 if (wst->tls)
550 {
551 if (events && (nCount < count))
552 {
553 BIO_get_event(wst->tls->bio, &events[nCount]);
554 nCount++;
555 }
556 else
557 return 0;
558 }
559
560 return nCount;
561}
562
563static int wst_bio_write(BIO* bio, const char* buf, int num)
564{
565 int status = 0;
566 WINPR_ASSERT(bio);
567 WINPR_ASSERT(buf);
568
569 rdpWst* wst = (rdpWst*)BIO_get_data(bio);
570 WINPR_ASSERT(wst);
571 BIO_clear_flags(bio, BIO_FLAGS_WRITE);
572 EnterCriticalSection(&wst->writeSection);
573 status = websocket_context_write(wst->wscontext, wst->tls->bio, (const BYTE*)buf, num,
574 WebsocketBinaryOpcode);
575 LeaveCriticalSection(&wst->writeSection);
576
577 if (status < 0)
578 {
579 BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
580 return -1;
581 }
582 else if (status < num)
583 {
584 BIO_set_flags(bio, BIO_FLAGS_WRITE);
585 WSASetLastError(WSAEWOULDBLOCK);
586 }
587 else
588 {
589 BIO_set_flags(bio, BIO_FLAGS_WRITE);
590 }
591
592 return status;
593}
594
595static int wst_bio_read(BIO* bio, char* buf, int size)
596{
597 int status = 0;
598 WINPR_ASSERT(bio);
599 WINPR_ASSERT(buf);
600 WINPR_ASSERT(size >= 0);
601
602 rdpWst* wst = (rdpWst*)BIO_get_data(bio);
603 WINPR_ASSERT(wst);
604
605 while (status <= 0)
606 {
607 status = websocket_context_read(wst->wscontext, wst->tls->bio, (BYTE*)buf, (size_t)size);
608 if (status <= 0)
609 {
610 if (!BIO_should_retry(wst->tls->bio))
611 return -1;
612 return 0;
613 }
614 }
615
616 if (status < 0)
617 {
618 BIO_clear_retry_flags(bio);
619 return -1;
620 }
621 else if (status == 0)
622 {
623 BIO_set_retry_read(bio);
624 WSASetLastError(WSAEWOULDBLOCK);
625 return -1;
626 }
627 else
628 {
629 BIO_set_flags(bio, BIO_FLAGS_READ);
630 }
631
632 return status;
633}
634
635static int wst_bio_puts(BIO* bio, const char* str)
636{
637 WINPR_UNUSED(bio);
638 WINPR_UNUSED(str);
639 return -2;
640}
641
642// NOLINTNEXTLINE(readability-non-const-parameter)
643static int wst_bio_gets(BIO* bio, char* str, int size)
644{
645 WINPR_UNUSED(bio);
646 WINPR_UNUSED(str);
647 WINPR_UNUSED(size);
648 return -2;
649}
650
651static long wst_bio_ctrl(BIO* bio, int cmd, long arg1, void* arg2)
652{
653 long status = -1;
654 WINPR_ASSERT(bio);
655
656 rdpWst* wst = (rdpWst*)BIO_get_data(bio);
657 WINPR_ASSERT(wst);
658 rdpTls* tls = wst->tls;
659
660 if (cmd == BIO_CTRL_FLUSH)
661 {
662 (void)BIO_flush(tls->bio);
663 status = 1;
664 }
665 else if (cmd == BIO_C_SET_NONBLOCK)
666 {
667 status = 1;
668 }
669 else if (cmd == BIO_C_READ_BLOCKED)
670 {
671 status = BIO_read_blocked(tls->bio);
672 }
673 else if (cmd == BIO_C_WRITE_BLOCKED)
674 {
675 status = BIO_write_blocked(tls->bio);
676 }
677 else if (cmd == BIO_C_WAIT_READ)
678 {
679 int timeout = (int)arg1;
680
681 if (BIO_read_blocked(tls->bio))
682 return BIO_wait_read(tls->bio, timeout);
683 status = 1;
684 }
685 else if (cmd == BIO_C_WAIT_WRITE)
686 {
687 int timeout = (int)arg1;
688
689 if (BIO_write_blocked(tls->bio))
690 status = BIO_wait_write(tls->bio, timeout);
691 else
692 status = 1;
693 }
694 else if (cmd == BIO_C_GET_EVENT || cmd == BIO_C_GET_FD)
695 {
696 status = BIO_ctrl(tls->bio, cmd, arg1, arg2);
697 }
698#if OPENSSL_VERSION_NUMBER >= 0x30000000L
699 else if (cmd == BIO_CTRL_GET_KTLS_SEND)
700 {
701 /* Even though BIO_get_ktls_send says that returning negative values is valid
702 * openssl internal sources are full of if(!BIO_get_ktls_send && ) stuff. This has some
703 * nasty sideeffects. return 0 as proper no KTLS offloading flag
704 */
705 status = 0;
706 }
707 else if (cmd == BIO_CTRL_GET_KTLS_RECV)
708 {
709 /* Even though BIO_get_ktls_recv says that returning negative values is valid
710 * there is no reason to trust trust negative values are implemented right everywhere
711 */
712 status = 0;
713 }
714#endif
715 return status;
716}
717
718static int wst_bio_new(BIO* bio)
719{
720 BIO_set_init(bio, 1);
721 BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
722 return 1;
723}
724
725static int wst_bio_free(BIO* bio)
726{
727 WINPR_UNUSED(bio);
728 return 1;
729}
730
731static BIO_METHOD* BIO_s_wst(void)
732{
733 static BIO_METHOD* bio_methods = NULL;
734
735 if (bio_methods == NULL)
736 {
737 if (!(bio_methods = BIO_meth_new(BIO_TYPE_TSG, "WSTransport")))
738 return NULL;
739
740 BIO_meth_set_write(bio_methods, wst_bio_write);
741 BIO_meth_set_read(bio_methods, wst_bio_read);
742 BIO_meth_set_puts(bio_methods, wst_bio_puts);
743 BIO_meth_set_gets(bio_methods, wst_bio_gets);
744 BIO_meth_set_ctrl(bio_methods, wst_bio_ctrl);
745 BIO_meth_set_create(bio_methods, wst_bio_new);
746 BIO_meth_set_destroy(bio_methods, wst_bio_free);
747 }
748
749 return bio_methods;
750}
751
752static BOOL wst_parse_url(rdpWst* wst, const char* url)
753{
754 const char* hostStart = NULL;
755 const char* pos = NULL;
756 WINPR_ASSERT(wst);
757 WINPR_ASSERT(url);
758
759 free(wst->gwhostname);
760 wst->gwhostname = NULL;
761 free(wst->gwpath);
762 wst->gwpath = NULL;
763
764 if (strncmp("wss://", url, 6) != 0)
765 {
766 if (strncmp("https://", url, 8) != 0)
767 {
768 WLog_ERR(TAG, "Websocket URL is invalid. Only wss:// or https:// URLs are supported");
769 return FALSE;
770 }
771 else
772 hostStart = url + 8;
773 }
774 else
775 hostStart = url + 6;
776
777 pos = hostStart;
778 while (*pos != '\0' && *pos != ':' && *pos != '/')
779 pos++;
780 free(wst->gwhostname);
781 wst->gwhostname = NULL;
782 if (pos - hostStart == 0)
783 return FALSE;
784 wst->gwhostname = strndup(hostStart, WINPR_ASSERTING_INT_CAST(size_t, (pos - hostStart)));
785 if (!wst->gwhostname)
786 return FALSE;
787
788 if (*pos == ':')
789 {
790 char port[6] = { 0 };
791 char* portNumberEnd = NULL;
792 pos++;
793 const char* portStart = pos;
794 while (*pos != '\0' && *pos != '/')
795 pos++;
796 if (pos - portStart > 5 || pos - portStart == 0)
797 return FALSE;
798 strncpy(port, portStart, WINPR_ASSERTING_INT_CAST(size_t, (pos - portStart)));
799 port[pos - portStart] = '\0';
800 long _p = strtol(port, &portNumberEnd, 10);
801 if (portNumberEnd && (*portNumberEnd == '\0') && (_p > 0) && (_p <= UINT16_MAX))
802 wst->gwport = (uint16_t)_p;
803 else
804 return FALSE;
805 }
806 else
807 wst->gwport = 443;
808 wst->gwpath = _strdup(pos);
809 if (!wst->gwpath)
810 return FALSE;
811 return TRUE;
812}
813
814rdpWst* wst_new(rdpContext* context)
815{
816 if (!context)
817 return NULL;
818
819 rdpWst* wst = (rdpWst*)calloc(1, sizeof(rdpWst));
820 if (!wst)
821 return NULL;
822
823 wst->context = context;
824
825 wst->gwhostname = NULL;
826 wst->gwport = 443;
827 wst->gwpath = NULL;
828
829 if (!wst_parse_url(wst, context->settings->GatewayUrl))
830 goto wst_alloc_error;
831
832 wst->tls = freerdp_tls_new(wst->context);
833 if (!wst->tls)
834 goto wst_alloc_error;
835
836 wst->http = http_context_new();
837
838 if (!wst->http)
839 goto wst_alloc_error;
840
841 if (!http_context_set_uri(wst->http, wst->gwpath) ||
842 !http_context_set_accept(wst->http, "*/*") ||
843 !http_context_set_cache_control(wst->http, "no-cache") ||
844 !http_context_set_pragma(wst->http, "no-cache") ||
845 !http_context_set_connection(wst->http, "Keep-Alive") ||
846 !http_context_set_user_agent(wst->http, FREERDP_USER_AGENT) ||
847 !http_context_set_x_ms_user_agent(wst->http, FREERDP_USER_AGENT) ||
848 !http_context_set_host(wst->http, wst->gwhostname) ||
849 !http_context_enable_websocket_upgrade(wst->http, TRUE))
850 {
851 goto wst_alloc_error;
852 }
853
854 wst->frontBio = BIO_new(BIO_s_wst());
855
856 if (!wst->frontBio)
857 goto wst_alloc_error;
858
859 BIO_set_data(wst->frontBio, wst);
860 InitializeCriticalSection(&wst->writeSection);
861 wst->auth = credssp_auth_new(context);
862 if (!wst->auth)
863 goto wst_alloc_error;
864
865 wst->wscontext = websocket_context_new();
866 if (!wst->wscontext)
867 goto wst_alloc_error;
868
869 return wst;
870wst_alloc_error:
871 WINPR_PRAGMA_DIAG_PUSH
872 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
873 wst_free(wst);
874 WINPR_PRAGMA_DIAG_POP
875 return NULL;
876}
877
878void wst_free(rdpWst* wst)
879{
880 if (!wst)
881 return;
882
883 freerdp_tls_free(wst->tls);
884 http_context_free(wst->http);
885 credssp_auth_free(wst->auth);
886 free(wst->gwhostname);
887 free(wst->gwpath);
888
889 if (!wst->attached)
890 BIO_free_all(wst->frontBio);
891
892 DeleteCriticalSection(&wst->writeSection);
893
894 websocket_context_free(wst->wscontext);
895
896 free(wst);
897}
898
899BIO* wst_get_front_bio_and_take_ownership(rdpWst* wst)
900{
901 if (!wst)
902 return NULL;
903
904 wst->attached = TRUE;
905 return wst->frontBio;
906}
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.