FreeRDP
Loading...
Searching...
No Matches
proxy.c
1
20#include <ctype.h>
21#include <errno.h>
22#include <limits.h>
23
24#include <openssl/err.h>
25
26#include "settings.h"
27#include "proxy.h"
28#include <freerdp/settings.h>
29#include <freerdp/utils/proxy_utils.h>
30#include <freerdp/crypto/crypto.h>
31#include "tcp.h"
32
33#include <winpr/assert.h>
34#include <winpr/sysinfo.h>
35#include <winpr/environment.h> /* For GetEnvironmentVariableA */
36
37#define CRLF "\r\n"
38
39#include <freerdp/log.h>
40#define TAG FREERDP_TAG("core.proxy")
41
42/* SOCKS Proxy auth methods by rfc1928 */
43enum
44{
45 AUTH_M_NO_AUTH = 0,
46 AUTH_M_GSSAPI = 1,
47 AUTH_M_USR_PASS = 2
48};
49
50enum
51{
52 SOCKS_CMD_CONNECT = 1,
53 SOCKS_CMD_BIND = 2,
54 SOCKS_CMD_UDP_ASSOCIATE = 3
55};
56
57enum
58{
59 SOCKS_ADDR_IPV4 = 1,
60 SOCKS_ADDR_FQDN = 3,
61 SOCKS_ADDR_IPV6 = 4,
62};
63
64static const char logprefix[] = "SOCKS Proxy:";
65
66/* CONN REQ replies in enum. order */
67static const char* rplstat[] = { "succeeded",
68 "general SOCKS server failure",
69 "connection not allowed by ruleset",
70 "Network unreachable",
71 "Host unreachable",
72 "Connection refused",
73 "TTL expired",
74 "Command not supported",
75 "Address type not supported" };
76
77WINPR_ATTR_NODISCARD
78static BOOL http_proxy_connect(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
79 const char* proxyPassword, const char* hostname, UINT16 port);
80
81WINPR_ATTR_NODISCARD
82static BOOL socks_proxy_connect(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
83 const char* proxyPassword, const char* hostname, UINT16 port);
84static void proxy_read_environment(rdpSettings* settings, char* envname);
85
86BOOL proxy_prepare(rdpSettings* settings, const char** lpPeerHostname, UINT16* lpPeerPort,
87 const char** lpProxyUsername, const char** lpProxyPassword)
88{
89 if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) == PROXY_TYPE_IGNORE)
90 return FALSE;
91
92 /* For TSGateway, find the system HTTPS proxy automatically */
93 if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) == PROXY_TYPE_NONE)
94 proxy_read_environment(settings, "https_proxy");
95
96 if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) == PROXY_TYPE_NONE)
97 proxy_read_environment(settings, "HTTPS_PROXY");
98
99 if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) != PROXY_TYPE_NONE)
100 proxy_read_environment(settings, "no_proxy");
101
102 if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) != PROXY_TYPE_NONE)
103 proxy_read_environment(settings, "NO_PROXY");
104
105 if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) != PROXY_TYPE_NONE)
106 {
107 *lpPeerHostname = freerdp_settings_get_string(settings, FreeRDP_ProxyHostname);
108 if (!*lpPeerHostname || !winpr_str_is_valid_hostname(*lpPeerHostname))
109 return FALSE;
110 *lpPeerPort = freerdp_settings_get_uint16(settings, FreeRDP_ProxyPort);
111 *lpProxyUsername = freerdp_settings_get_string(settings, FreeRDP_ProxyUsername);
112 *lpProxyPassword = freerdp_settings_get_string(settings, FreeRDP_ProxyPassword);
113 return TRUE;
114 }
115
116 return FALSE;
117}
118
119WINPR_ATTR_NODISCARD
120static BOOL value_to_int(const char* value, LONGLONG* result, LONGLONG min, LONGLONG max)
121{
122 long long rc = 0;
123
124 if (!value || !result)
125 return FALSE;
126
127 errno = 0;
128 rc = _strtoi64(value, nullptr, 0);
129
130 if (errno != 0)
131 return FALSE;
132
133 if ((rc < min) || (rc > max))
134 return FALSE;
135
136 *result = rc;
137 return TRUE;
138}
139
140WINPR_ATTR_NODISCARD
141static BOOL cidr4_match(const struct in_addr* addr, const struct in_addr* net, BYTE bits)
142{
143 if (bits == 0)
144 return TRUE;
145
146 const uint32_t mask = htonl(0xFFFFFFFFu << (32 - bits));
147 const uint32_t amask = addr->s_addr & mask;
148 const uint32_t nmask = net->s_addr & mask;
149 return amask == nmask;
150}
151
152WINPR_ATTR_NODISCARD
153static BOOL cidr6_match(const struct in6_addr* address, const struct in6_addr* network,
154 uint8_t bits)
155{
156 const uint32_t* a = (const uint32_t*)address;
157 const uint32_t* n = (const uint32_t*)network;
158 const size_t bits_whole = bits >> 5;
159 const size_t bits_incomplete = bits & 0x1F;
160
161 if (bits_whole)
162 {
163 if (memcmp(a, n, bits_whole << 2) != 0)
164 return FALSE;
165 }
166
167 if (bits_incomplete)
168 {
169 uint32_t mask = htonl((0xFFFFFFFFu) << (32 - bits_incomplete));
170
171 if ((a[bits_whole] ^ n[bits_whole]) & mask)
172 return FALSE;
173 }
174
175 return TRUE;
176}
177
178WINPR_ATTR_NODISCARD
179static BOOL option_ends_with(const char* str, const char* ext)
180{
181 WINPR_ASSERT(str);
182 WINPR_ASSERT(ext);
183 const size_t strLen = strlen(str);
184 const size_t extLen = strlen(ext);
185
186 if (strLen < extLen)
187 return FALSE;
188
189 return _strnicmp(&str[strLen - extLen], ext, extLen) == 0;
190}
191
192/* no_proxy has no proper definition, so use curl as reference:
193 * https://about.gitlab.com/blog/2021/01/27/we-need-to-talk-no-proxy/
194 */
195WINPR_ATTR_NODISCARD
196static BOOL no_proxy_match_host(const char* val, const char* hostname)
197{
198 WINPR_ASSERT(val);
199 WINPR_ASSERT(hostname);
200
201 /* match all */
202 if (_stricmp("*", val) == 0)
203 return TRUE;
204
205 /* Strip leading . */
206 if (val[0] == '.')
207 val++;
208
209 /* Match suffix */
210 return option_ends_with(hostname, val);
211}
212
213WINPR_ATTR_NODISCARD
214static BOOL starts_with(const char* val, const char* prefix)
215{
216 const size_t plen = strlen(prefix);
217 const size_t vlen = strlen(val);
218 if (vlen < plen)
219 return FALSE;
220 return _strnicmp(val, prefix, plen) == 0;
221}
222
223WINPR_ATTR_NODISCARD
224static BOOL no_proxy_match_ip(const char* val, const char* hostname)
225{
226 WINPR_ASSERT(val);
227 WINPR_ASSERT(hostname);
228
229 struct sockaddr_in sa4 = WINPR_C_ARRAY_INIT;
230 struct sockaddr_in6 sa6 = WINPR_C_ARRAY_INIT;
231
232 if (inet_pton(AF_INET, hostname, &sa4.sin_addr) == 1)
233 {
234 /* Prefix match */
235 if (starts_with(hostname, val))
236 return TRUE;
237
238 char* sub = strchr(val, '/');
239 if (sub)
240 *sub++ = '\0';
241
242 struct sockaddr_in mask = WINPR_C_ARRAY_INIT;
243 if (inet_pton(AF_INET, val, &mask.sin_addr) == 0)
244 return FALSE;
245
246 /* IP address match */
247 if (memcmp(&mask, &sa4, sizeof(mask)) == 0)
248 return TRUE;
249
250 if (sub)
251 {
252 const unsigned long usub = strtoul(sub, nullptr, 0);
253 if ((errno == 0) && (usub <= UINT8_MAX))
254 return cidr4_match(&sa4.sin_addr, &mask.sin_addr, (UINT8)usub);
255 }
256 }
257 else if (inet_pton(AF_INET6, hostname, &sa6.sin6_addr) == 1)
258 {
259 if (val[0] == '[')
260 val++;
261
262 char str[INET6_ADDRSTRLEN + 1] = WINPR_C_ARRAY_INIT;
263 strncpy(str, val, INET6_ADDRSTRLEN);
264
265 const size_t len = strnlen(str, INET6_ADDRSTRLEN);
266 if (len > 0)
267 {
268 if (str[len - 1] == ']')
269 str[len - 1] = '\0';
270 }
271
272 /* Prefix match */
273 if (starts_with(hostname, str))
274 return TRUE;
275
276 char* sub = strchr(str, '/');
277 if (sub)
278 *sub++ = '\0';
279
280 struct sockaddr_in6 mask = WINPR_C_ARRAY_INIT;
281 if (inet_pton(AF_INET6, str, &mask.sin6_addr) == 0)
282 return FALSE;
283
284 /* Address match */
285 if (memcmp(&mask, &sa6, sizeof(mask)) == 0)
286 return TRUE;
287
288 if (sub)
289 {
290 const unsigned long usub = strtoul(sub, nullptr, 0);
291 if ((errno == 0) && (usub <= UINT8_MAX))
292 return cidr6_match(&sa6.sin6_addr, &mask.sin6_addr, (UINT8)usub);
293 }
294 }
295
296 return FALSE;
297}
298
299WINPR_ATTR_NODISCARD
300static BOOL is_ipv6_addr(const char* hostname, size_t len)
301{
302 struct sockaddr_in6 sa6 = WINPR_C_ARRAY_INIT;
303 if (strnlen(hostname, len) > INET6_ADDRSTRLEN)
304 return FALSE;
305 return inet_pton(AF_INET6, hostname, &sa6.sin6_addr) == 1;
306}
307
308WINPR_ATTR_NODISCARD
309static BOOL check_no_proxy(rdpSettings* settings, const char* no_proxy)
310{
311 const char* delimiter = ", ";
312 BOOL result = FALSE;
313 char* context = nullptr;
314
315 if (!no_proxy || !settings)
316 return FALSE;
317
318 char* copy = _strdup(no_proxy);
319
320 if (!copy)
321 return FALSE;
322
323 char* current = strtok_s(copy, delimiter, &context);
324
325 while (current && !result)
326 {
327 const size_t currentlen = strlen(current);
328
329 if (currentlen > 0)
330 {
331 const char* ServerHostname =
332 freerdp_settings_get_string(settings, FreeRDP_ServerHostname);
333 WLog_DBG(TAG, "%s => %s (%" PRIuz ")", ServerHostname, current, currentlen);
334
335 if (no_proxy_match_host(current, ServerHostname))
336 result = TRUE;
337 else if (no_proxy_match_ip(current, ServerHostname))
338 result = TRUE;
339 }
340
341 current = strtok_s(nullptr, delimiter, &context);
342 }
343
344 free(copy);
345 return result;
346}
347
348void proxy_read_environment(rdpSettings* settings, char* envname)
349{
350 const DWORD envlen = GetEnvironmentVariableA(envname, nullptr, 0);
351
352 if (!envlen || (envlen <= 1))
353 return;
354
355 char* env = calloc(1, envlen);
356
357 if (!env)
358 {
359 WLog_ERR(TAG, "Not enough memory");
360 return;
361 }
362
363 if (GetEnvironmentVariableA(envname, env, envlen) == envlen - 1)
364 {
365 if (_strnicmp("NO_PROXY", envname, 9) == 0)
366 {
367 if (check_no_proxy(settings, env))
368 {
369 WLog_INFO(TAG, "deactivating proxy: %s [%s=%s]",
370 freerdp_settings_get_string(settings, FreeRDP_ServerHostname), envname,
371 env);
372 if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_NONE))
373 WLog_WARN(TAG, "failed to set FreeRDP_ProxyType=PROXY_TYPE_NONE");
374 }
375 }
376 else
377 {
378 if (!proxy_parse_uri(settings, env))
379 {
380 WLog_WARN(
381 TAG, "Error while parsing proxy URI from environment variable; ignoring proxy");
382 }
383 }
384 }
385
386 free(env);
387}
388
389BOOL proxy_parse_uri(rdpSettings* settings, const char* uri_in)
390{
391 BOOL rc = FALSE;
392 const char* protocol = "";
393 UINT16 port = 0;
394
395 if (!settings || !uri_in)
396 return FALSE;
397
398 char* uri_copy = _strdup(uri_in);
399 char* uri = uri_copy;
400 if (!uri)
401 goto fail;
402
403 {
404 char* p = strstr(uri, "://");
405 if (p)
406 {
407 *p = '\0';
408
409 if (_stricmp("no_proxy", uri) == 0)
410 {
411 if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_IGNORE))
412 goto fail;
413 }
414 if (_stricmp("http", uri) == 0)
415 {
416 if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_HTTP))
417 goto fail;
418 protocol = "http";
419 }
420 else if (_stricmp("socks5", uri) == 0)
421 {
422 if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_SOCKS))
423 goto fail;
424 protocol = "socks5";
425 }
426 else
427 {
428 WLog_ERR(TAG, "Only HTTP and SOCKS5 proxies supported by now");
429 goto fail;
430 }
431
432 uri = p + 3;
433 }
434 else
435 {
436 /* default proxy protocol is http */
437 if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_HTTP))
438 goto fail;
439 protocol = "http";
440 }
441 }
442
443 /* uri is now [user:password@]hostname:port */
444 {
445 char* atPtr = strrchr(uri, '@');
446
447 if (atPtr)
448 {
449 /* got a login / password,
450 * atPtr
451 * v
452 * [user:password@]hostname:port
453 * ^
454 * colonPtr
455 */
456 char* colonPtr = strchr(uri, ':');
457
458 if (!colonPtr || (colonPtr > atPtr))
459 {
460 WLog_ERR(TAG, "invalid syntax for proxy (contains no password)");
461 goto fail;
462 }
463
464 *colonPtr = '\0';
465 if (!freerdp_settings_set_string(settings, FreeRDP_ProxyUsername, uri))
466 {
467 WLog_ERR(TAG, "unable to allocate proxy username");
468 goto fail;
469 }
470
471 *atPtr = '\0';
472
473 if (!freerdp_settings_set_string(settings, FreeRDP_ProxyPassword, colonPtr + 1))
474 {
475 WLog_ERR(TAG, "unable to allocate proxy password");
476 goto fail;
477 }
478
479 uri = atPtr + 1;
480 }
481 }
482
483 {
484 char* p = strchr(uri, ':');
485
486 if (p)
487 {
488 LONGLONG val = 0;
489
490 if (!value_to_int(&p[1], &val, 0, UINT16_MAX))
491 {
492 WLog_ERR(TAG, "invalid syntax for proxy (invalid port)");
493 goto fail;
494 }
495
496 if (val == 0)
497 {
498 WLog_ERR(TAG, "invalid syntax for proxy (port missing)");
499 goto fail;
500 }
501
502 port = (UINT16)val;
503 *p = '\0';
504 }
505 else
506 {
507 if (_stricmp("http", protocol) == 0)
508 {
509 /* The default is 80. Also for Proxies. */
510 port = 80;
511 }
512 else
513 {
514 port = 1080;
515 }
516
517 WLog_DBG(TAG, "setting default proxy port: %" PRIu16, port);
518 }
519
520 if (!freerdp_settings_set_uint16(settings, FreeRDP_ProxyPort, port))
521 goto fail;
522 }
523 {
524 char* p = strchr(uri, '/');
525 if (p)
526 *p = '\0';
527 if (!freerdp_settings_set_string(settings, FreeRDP_ProxyHostname, uri))
528 goto fail;
529 }
530
531 if (_stricmp("", uri) == 0)
532 {
533 WLog_ERR(TAG, "invalid syntax for proxy (hostname missing)");
534 goto fail;
535 }
536
537 if (freerdp_settings_get_string(settings, FreeRDP_ProxyUsername))
538 {
539 WLog_INFO(TAG, "Parsed proxy configuration: %s://%s:%s@%s:%" PRIu16, protocol,
540 freerdp_settings_get_string(settings, FreeRDP_ProxyUsername), "******",
541 freerdp_settings_get_string(settings, FreeRDP_ProxyHostname),
542 freerdp_settings_get_uint16(settings, FreeRDP_ProxyPort));
543 }
544 else
545 {
546 WLog_INFO(TAG, "Parsed proxy configuration: %s://%s:%" PRIu16, protocol,
547 freerdp_settings_get_string(settings, FreeRDP_ProxyHostname),
548 freerdp_settings_get_uint16(settings, FreeRDP_ProxyPort));
549 }
550 rc = TRUE;
551
552fail:
553 if (!rc)
554 WLog_WARN(TAG, "Failed to parse proxy configuration: %s://%s:%" PRIu16, protocol, uri,
555 port);
556 free(uri_copy);
557 return rc;
558}
559
560BOOL proxy_connect(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
561 const char* proxyPassword, const char* hostname, UINT16 port)
562{
563 WINPR_ASSERT(context);
564 rdpSettings* settings = context->settings;
565
566 switch (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType))
567 {
568 case PROXY_TYPE_NONE:
569 case PROXY_TYPE_IGNORE:
570 return TRUE;
571
572 case PROXY_TYPE_HTTP:
573 return http_proxy_connect(context, bufferedBio, proxyUsername, proxyPassword, hostname,
574 port);
575
576 case PROXY_TYPE_SOCKS:
577 return socks_proxy_connect(context, bufferedBio, proxyUsername, proxyPassword, hostname,
578 port);
579
580 default:
581 WLog_ERR(TAG, "Invalid internal proxy configuration");
582 return FALSE;
583 }
584}
585
586WINPR_ATTR_NODISCARD
587static const char* get_response_header(char* response)
588{
589 char* current_pos = strchr(response, '\r');
590 if (!current_pos)
591 current_pos = strchr(response, '\n');
592
593 if (current_pos)
594 *current_pos = '\0';
595
596 return response;
597}
598
599static BOOL http_proxy_write_hostname(wStream* s, const char* hostname, size_t len)
600{
601 const BOOL isIPv6 = is_ipv6_addr(hostname, len);
602
603 if (isIPv6)
604 {
605 if (!Stream_EnsureRemainingCapacity(s, 1))
606 return FALSE;
607 Stream_Write_UINT8(s, '[');
608 }
609
610 if (!Stream_EnsureRemainingCapacity(s, len))
611 return FALSE;
612 Stream_Write(s, hostname, len);
613
614 if (isIPv6)
615 {
616 if (!Stream_EnsureRemainingCapacity(s, 1))
617 return FALSE;
618 Stream_Write_UINT8(s, ']');
619 }
620
621 return TRUE;
622}
623
624WINPR_ATTR_NODISCARD
625static BOOL http_proxy_connect(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
626 const char* proxyPassword, const char* hostname, UINT16 port)
627{
628 BOOL rc = FALSE;
629 int status = 0;
630 char port_str[10] = WINPR_C_ARRAY_INIT;
631 char recv_buf[256] = WINPR_C_ARRAY_INIT;
632 char* eol = nullptr;
633 size_t resultsize = 0;
634 const char connect[] = "CONNECT ";
635 const char httpheader[] = " HTTP/1.1" CRLF "Host: ";
636
637 WINPR_ASSERT(context);
638 WINPR_ASSERT(bufferedBio);
639 WINPR_ASSERT(hostname);
640 const UINT32 timeout =
641 freerdp_settings_get_uint32(context->settings, FreeRDP_TcpConnectTimeout);
642
643 // Check for IPv6 (max 45) or DNS (max 255) name. Bounded length to 256 so too long hostnames do
644 // not silently pass
645 const size_t hostLen = strnlen(hostname, 255 + 1);
646 if (!is_ipv6_addr(hostname, hostLen) && !winpr_str_is_valid_hostnameN(hostname, hostLen))
647 return FALSE;
648
649 if (_itoa_s(port, port_str, sizeof(port_str), 10) < 0)
650 {
651 WLog_ERR(TAG, "itoa %s failed", port_str);
652 return FALSE;
653 }
654
655 const size_t portLen = strnlen(port_str, sizeof(port_str));
656 wStream* s = Stream_New(nullptr, 1024);
657 if (!s)
658 goto fail;
659
660 const size_t clen = strnlen(connect, sizeof(connect));
661 if (!Stream_EnsureRemainingCapacity(s, clen))
662 goto fail;
663 Stream_Write(s, connect, clen);
664
665 if (!http_proxy_write_hostname(s, hostname, hostLen))
666 goto fail;
667
668 if (!Stream_EnsureRemainingCapacity(s, 1))
669 goto fail;
670 Stream_Write_UINT8(s, ':');
671
672 if (!Stream_EnsureRemainingCapacity(s, portLen))
673 goto fail;
674 Stream_Write(s, port_str, portLen);
675
676 const size_t httplen = strnlen(httpheader, sizeof(httpheader));
677 if (!Stream_EnsureRemainingCapacity(s, httplen))
678 goto fail;
679 Stream_Write(s, httpheader, httplen);
680
681 if (!http_proxy_write_hostname(s, hostname, hostLen))
682 goto fail;
683
684 if (!Stream_EnsureRemainingCapacity(s, 1))
685 goto fail;
686 Stream_Write_UINT8(s, ':');
687
688 if (!Stream_EnsureRemainingCapacity(s, portLen))
689 goto fail;
690 Stream_Write(s, port_str, portLen);
691
692 if (proxyUsername && proxyPassword && (strnlen(proxyUsername, 1) > 0) &&
693 (strnlen(proxyPassword, 1) > 0))
694 {
695 const char basic[] = CRLF "Proxy-Authorization: Basic ";
696 const size_t balen = strnlen(basic, sizeof(basic));
697 if (!Stream_EnsureRemainingCapacity(s, balen))
698 goto fail;
699 Stream_Write(s, basic, balen);
700
701 char* creds = nullptr;
702 size_t size = 0;
703 (void)winpr_asprintf(&creds, &size, "%s:%s", proxyUsername, proxyPassword);
704 if (!creds || (size < 1))
705 {
706 free(creds);
707 goto fail;
708 }
709 size_t b64len = 0;
710 char* base64 = crypto_base64_encode_len(creds, size, &b64len);
711 free(creds);
712 if (!base64 || !Stream_EnsureRemainingCapacity(s, b64len))
713 {
714 free(base64);
715 goto fail;
716 }
717
718 Stream_Write(s, base64, strlen(base64));
719 free(base64);
720 }
721
722 if (!Stream_EnsureRemainingCapacity(s, 4))
723 goto fail;
724
725 Stream_Write(s, CRLF CRLF, 4);
726 ERR_clear_error();
727
728 {
729 const size_t pos = Stream_GetPosition(s);
730 if (pos > INT32_MAX)
731 goto fail;
732
733 status = BIO_write(bufferedBio, Stream_Buffer(s), WINPR_ASSERTING_INT_CAST(int, pos));
734 }
735
736 if ((status < 0) || ((size_t)status != Stream_GetPosition(s)))
737 {
738 WLog_ERR(TAG, "HTTP proxy: failed to write CONNECT request");
739 goto fail;
740 }
741
742 /* Read result until CR-LF-CR-LF.
743 * Keep recv_buf a null-terminated string. */
744 {
745 const UINT64 start = GetTickCount64();
746 while (strstr(recv_buf, CRLF CRLF) == nullptr)
747 {
748 if (resultsize >= sizeof(recv_buf) - 1)
749 {
750 WLog_ERR(TAG, "HTTP Reply headers too long: %s", get_response_header(recv_buf));
751 goto fail;
752 }
753 const size_t rdsize = sizeof(recv_buf) - resultsize - 1ULL;
754
755 ERR_clear_error();
756
757 WINPR_ASSERT(rdsize <= INT32_MAX);
758 status = BIO_read(bufferedBio, (BYTE*)recv_buf + resultsize, (int)rdsize);
759
760 if (status < 0)
761 {
762 /* Error? */
763 if (!freerdp_shall_disconnect_context(context) && BIO_should_retry(bufferedBio))
764 {
765 USleep(100);
766 continue;
767 }
768
769 WLog_ERR(TAG, "Failed reading reply from HTTP proxy (Status %d)", status);
770 goto fail;
771 }
772 else if (status == 0)
773 {
774 const UINT64 now = GetTickCount64();
775 const UINT64 diff = now - start;
776 if (freerdp_shall_disconnect_context(context) || (now < start) || (diff > timeout))
777 {
778 /* Error? */
779 WLog_ERR(TAG, "Failed reading reply from HTTP proxy (BIO_read returned zero)");
780 goto fail;
781 }
782 Sleep(10);
783 }
784
785 resultsize += WINPR_ASSERTING_INT_CAST(size_t, status);
786 }
787 }
788
789 /* Extract HTTP status line */
790 eol = strchr(recv_buf, '\r');
791
792 if (!eol)
793 {
794 /* should never happen */
795 goto fail;
796 }
797
798 *eol = '\0';
799 WLog_INFO(TAG, "HTTP Proxy: %s", recv_buf);
800
801 if (strnlen(recv_buf, sizeof(recv_buf)) < 12)
802 goto fail;
803
804 recv_buf[7] = 'X';
805
806 if (strncmp(recv_buf, "HTTP/1.X 200", 12) != 0)
807 goto fail;
808
809 rc = TRUE;
810fail:
811 WLog_ERR(TAG, "Failed to connect to proxy");
812 Stream_Free(s, TRUE);
813 return rc;
814}
815
816WINPR_ATTR_NODISCARD
817static int recv_socks_reply(rdpContext* context, BIO* bufferedBio, BYTE* buf, int len, char* reason,
818 BYTE checkVer)
819{
820 int status = 0;
821
822 WINPR_ASSERT(context);
823
824 const UINT32 timeout =
825 freerdp_settings_get_uint32(context->settings, FreeRDP_TcpConnectTimeout);
826 const UINT64 start = GetTickCount64();
827 for (;;)
828 {
829 ERR_clear_error();
830 status = BIO_read(bufferedBio, buf, len);
831
832 if (status > 0)
833 {
834 break;
835 }
836 else if (status < 0)
837 {
838 /* Error? */
839 if (!freerdp_shall_disconnect_context(context) && BIO_should_retry(bufferedBio))
840 {
841 USleep(100);
842 continue;
843 }
844
845 WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (Status %d)", reason, status);
846 return -1;
847 }
848 else if (status == 0)
849 {
850 const UINT64 now = GetTickCount64();
851 const UINT64 diff = now - start;
852 if (freerdp_shall_disconnect_context(context) || (now < start) || (diff > timeout))
853 {
854 /* Error? */
855 WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (BIO_read returned zero)",
856 reason);
857 return status;
858 }
859 Sleep(10);
860 }
861 else // if (status == 0)
862 {
863 /* Error? */
864 WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (BIO_read returned zero)",
865 reason);
866 return -1;
867 }
868 }
869
870 if (status < 2)
871 {
872 WLog_ERR(TAG, "SOCKS Proxy reply packet too short (%s)", reason);
873 return -1;
874 }
875
876 if (buf[0] != checkVer)
877 {
878 WLog_ERR(TAG, "SOCKS Proxy version is not 5 (%s)", reason);
879 return -1;
880 }
881
882 return status;
883}
884
885WINPR_ATTR_NODISCARD
886static BOOL socks_proxy_userpass(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
887 const char* proxyPassword)
888{
889 WINPR_ASSERT(context);
890 WINPR_ASSERT(bufferedBio);
891
892 if (!proxyUsername || !proxyPassword)
893 {
894 WLog_ERR(TAG, "%s invalid username (%p) or password (%p)", logprefix,
895 WINPR_CXX_COMPAT_CAST(const void*, proxyUsername),
896 WINPR_CXX_COMPAT_CAST(const void*, proxyPassword));
897 return FALSE;
898 }
899
900 const size_t usernameLen = (BYTE)strnlen(proxyUsername, 256);
901 if (usernameLen > 255)
902 {
903 WLog_ERR(TAG, "%s username too long (%" PRIuz ", max=255)", logprefix, usernameLen);
904 return FALSE;
905 }
906
907 const size_t userpassLen = (BYTE)strnlen(proxyPassword, 256);
908 if (userpassLen > 255)
909 {
910 WLog_ERR(TAG, "%s password too long (%" PRIuz ", max=255)", logprefix, userpassLen);
911 return FALSE;
912 }
913
914 /* user/password v1 method */
915 {
916 BYTE buf[2 * 255 + 3] = WINPR_C_ARRAY_INIT;
917 size_t offset = 0;
918 buf[offset++] = 1;
919
920 buf[offset++] = WINPR_ASSERTING_INT_CAST(uint8_t, usernameLen);
921 memcpy(&buf[offset], proxyUsername, usernameLen);
922 offset += usernameLen;
923
924 buf[offset++] = WINPR_ASSERTING_INT_CAST(uint8_t, userpassLen);
925 memcpy(&buf[offset], proxyPassword, userpassLen);
926 offset += userpassLen;
927
928 ERR_clear_error();
929 const int ioffset = WINPR_ASSERTING_INT_CAST(int, offset);
930 const int status = BIO_write(bufferedBio, buf, ioffset);
931
932 if (status != ioffset)
933 {
934 WLog_ERR(TAG, "%s error writing user/password request", logprefix);
935 return FALSE;
936 }
937 }
938
939 BYTE buf[2] = WINPR_C_ARRAY_INIT;
940 const int status = recv_socks_reply(context, bufferedBio, buf, sizeof(buf), "AUTH REQ", 1);
941
942 if (status < 2)
943 return FALSE;
944
945 if (buf[1] != 0x00)
946 {
947 WLog_ERR(TAG, "%s invalid user/password", logprefix);
948 return FALSE;
949 }
950 return TRUE;
951}
952
953WINPR_ATTR_NODISCARD
954static BOOL socks_proxy_connect(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
955 const char* proxyPassword, const char* hostname, UINT16 port)
956{
957 WINPR_ASSERT(hostname);
958 // Check for IPv6 (max 45) or DNS (max 255) name. Bounded length to 256 so too long hostnames do
959 // not silently pass
960 const size_t hostnlen = strnlen(hostname, 255 + 1);
961 if (!is_ipv6_addr(hostname, hostnlen) && !winpr_str_is_valid_hostnameN(hostname, hostnlen))
962 return FALSE;
963
964 BYTE nauthMethods = 1;
965
966 if (proxyUsername || proxyPassword)
967 nauthMethods++;
968
969 /* select auth. method */
970 {
971 const BYTE buf[] = { 5, /* SOCKS version */
972 nauthMethods, /* #of methods offered */
973 AUTH_M_NO_AUTH, AUTH_M_USR_PASS };
974
975 size_t writeLen = sizeof(buf);
976 if (nauthMethods <= 1)
977 writeLen--;
978
979 ERR_clear_error();
980 const int iwriteLen = WINPR_ASSERTING_INT_CAST(int, writeLen);
981 const int status = BIO_write(bufferedBio, buf, iwriteLen);
982
983 if (status != iwriteLen)
984 {
985 WLog_ERR(TAG, "%s SOCKS proxy: failed to write AUTH METHOD request", logprefix);
986 return FALSE;
987 }
988 }
989
990 {
991 BYTE buf[2] = WINPR_C_ARRAY_INIT;
992 const int status = recv_socks_reply(context, bufferedBio, buf, sizeof(buf), "AUTH REQ", 5);
993
994 if (status <= 0)
995 return FALSE;
996
997 switch (buf[1])
998 {
999 case AUTH_M_NO_AUTH:
1000 WLog_DBG(TAG, "%s (NO AUTH) method was selected", logprefix);
1001 break;
1002
1003 case AUTH_M_USR_PASS:
1004 if (nauthMethods < 2)
1005 {
1006 WLog_ERR(TAG, "%s USER/PASS method was not proposed to server", logprefix);
1007 return FALSE;
1008 }
1009 if (!socks_proxy_userpass(context, bufferedBio, proxyUsername, proxyPassword))
1010 return FALSE;
1011 break;
1012
1013 default:
1014 WLog_ERR(TAG, "%s unknown method 0x%x was selected by proxy", logprefix, buf[1]);
1015 return FALSE;
1016 }
1017 }
1018 /* CONN request */
1019 {
1020 BYTE buf[262] = WINPR_C_ARRAY_INIT;
1021 size_t offset = 0;
1022 buf[offset++] = 5; /* SOCKS version */
1023 buf[offset++] = SOCKS_CMD_CONNECT; /* command */
1024 buf[offset++] = 0; /* 3rd octet is reserved x00 */
1025
1026 if (inet_pton(AF_INET6, hostname, &buf[offset + 1]) == 1)
1027 {
1028 buf[offset++] = SOCKS_ADDR_IPV6;
1029 offset += 16;
1030 }
1031 else if (inet_pton(AF_INET, hostname, &buf[offset + 1]) == 1)
1032 {
1033 buf[offset++] = SOCKS_ADDR_IPV4;
1034 offset += 4;
1035 }
1036 else
1037 {
1038 buf[offset++] = SOCKS_ADDR_FQDN;
1039 buf[offset++] = WINPR_ASSERTING_INT_CAST(uint8_t, hostnlen);
1040 memcpy(&buf[offset], hostname, hostnlen);
1041 offset += hostnlen;
1042 }
1043
1044 if (offset > sizeof(buf) - 2)
1045 {
1046 WLog_ERR(TAG, "Invalid offset %" PRIuz, offset);
1047 return FALSE;
1048 }
1049
1050 /* follows DST.PORT in netw. format */
1051 buf[offset++] = (port >> 8) & 0xff;
1052 buf[offset++] = port & 0xff;
1053
1054 ERR_clear_error();
1055 const int ioffset = WINPR_ASSERTING_INT_CAST(int, offset);
1056 const int status = BIO_write(bufferedBio, buf, ioffset);
1057
1058 if ((status < 0) || (status != ioffset))
1059 {
1060 WLog_ERR(TAG, "%s SOCKS proxy: failed to write CONN REQ", logprefix);
1061 return FALSE;
1062 }
1063 }
1064
1065 BYTE buf[255] = WINPR_C_ARRAY_INIT;
1066 const int status = recv_socks_reply(context, bufferedBio, buf, sizeof(buf), "CONN REQ", 5);
1067
1068 if (status < 4)
1069 return FALSE;
1070
1071 if (buf[1] == 0)
1072 {
1073 WLog_INFO(TAG, "Successfully connected to %s:%" PRIu16, hostname, port);
1074 return TRUE;
1075 }
1076
1077 if ((buf[1] > 0) && (buf[1] < 9))
1078 WLog_INFO(TAG, "SOCKS Proxy replied: %s", rplstat[buf[1]]);
1079 else
1080 WLog_INFO(TAG, "SOCKS Proxy replied: %" PRIu8 " status not listed in rfc1928", buf[1]);
1081
1082 return FALSE;
1083}
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 UINT16 freerdp_settings_get_uint16(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt16 id)
Returns a UINT16 settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_set_uint32(rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id, UINT32 val)
Sets a UINT32 settings value.
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_set_uint16(rdpSettings *settings, FreeRDP_Settings_Keys_UInt16 id, UINT16 val)
Sets a UINT16 settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_set_string(rdpSettings *settings, FreeRDP_Settings_Keys_String id, const char *val)
Sets a string settings value. The param is copied.