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_url(*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_urlN(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)
693 {
694 const int length = _scprintf("%s:%s", proxyUsername, proxyPassword);
695 if (length > 0)
696 {
697 const char basic[] = CRLF "Proxy-Authorization: Basic ";
698 const size_t balen = strnlen(basic, sizeof(basic));
699 if (!Stream_EnsureRemainingCapacity(s, balen))
700 goto fail;
701 Stream_Write(s, basic, balen);
702
703 char* creds = nullptr;
704 size_t size = 0;
705 (void)winpr_asprintf(&creds, &size, "%s:%s", proxyUsername, proxyPassword);
706 if (!creds || (size < 1))
707 {
708 free(creds);
709 goto fail;
710 }
711 size_t b64len = 0;
712 char* base64 = crypto_base64_encode_len(creds, size - 1, &b64len);
713 free(creds);
714 if (!base64 || !Stream_EnsureRemainingCapacity(s, b64len))
715 {
716 free(base64);
717 goto fail;
718 }
719
720 Stream_Write(s, base64, strlen(base64));
721 free(base64);
722 }
723 }
724
725 if (!Stream_EnsureRemainingCapacity(s, 4))
726 goto fail;
727
728 Stream_Write(s, CRLF CRLF, 4);
729 ERR_clear_error();
730
731 {
732 const size_t pos = Stream_GetPosition(s);
733 if (pos > INT32_MAX)
734 goto fail;
735
736 status = BIO_write(bufferedBio, Stream_Buffer(s), WINPR_ASSERTING_INT_CAST(int, pos));
737 }
738
739 if ((status < 0) || ((size_t)status != Stream_GetPosition(s)))
740 {
741 WLog_ERR(TAG, "HTTP proxy: failed to write CONNECT request");
742 goto fail;
743 }
744
745 /* Read result until CR-LF-CR-LF.
746 * Keep recv_buf a null-terminated string. */
747 {
748 const UINT64 start = GetTickCount64();
749 while (strstr(recv_buf, CRLF CRLF) == nullptr)
750 {
751 if (resultsize >= sizeof(recv_buf) - 1)
752 {
753 WLog_ERR(TAG, "HTTP Reply headers too long: %s", get_response_header(recv_buf));
754 goto fail;
755 }
756 const size_t rdsize = sizeof(recv_buf) - resultsize - 1ULL;
757
758 ERR_clear_error();
759
760 WINPR_ASSERT(rdsize <= INT32_MAX);
761 status = BIO_read(bufferedBio, (BYTE*)recv_buf + resultsize, (int)rdsize);
762
763 if (status < 0)
764 {
765 /* Error? */
766 if (!freerdp_shall_disconnect_context(context) && BIO_should_retry(bufferedBio))
767 {
768 USleep(100);
769 continue;
770 }
771
772 WLog_ERR(TAG, "Failed reading reply from HTTP proxy (Status %d)", status);
773 goto fail;
774 }
775 else if (status == 0)
776 {
777 const UINT64 now = GetTickCount64();
778 const UINT64 diff = now - start;
779 if (freerdp_shall_disconnect_context(context) || (now < start) || (diff > timeout))
780 {
781 /* Error? */
782 WLog_ERR(TAG, "Failed reading reply from HTTP proxy (BIO_read returned zero)");
783 goto fail;
784 }
785 Sleep(10);
786 }
787
788 resultsize += WINPR_ASSERTING_INT_CAST(size_t, status);
789 }
790 }
791
792 /* Extract HTTP status line */
793 eol = strchr(recv_buf, '\r');
794
795 if (!eol)
796 {
797 /* should never happen */
798 goto fail;
799 }
800
801 *eol = '\0';
802 WLog_INFO(TAG, "HTTP Proxy: %s", recv_buf);
803
804 if (strnlen(recv_buf, sizeof(recv_buf)) < 12)
805 goto fail;
806
807 recv_buf[7] = 'X';
808
809 if (strncmp(recv_buf, "HTTP/1.X 200", 12) != 0)
810 goto fail;
811
812 rc = TRUE;
813fail:
814 WLog_ERR(TAG, "Failed to connect to proxy");
815 Stream_Free(s, TRUE);
816 return rc;
817}
818
819WINPR_ATTR_NODISCARD
820static int recv_socks_reply(rdpContext* context, BIO* bufferedBio, BYTE* buf, int len, char* reason,
821 BYTE checkVer)
822{
823 int status = 0;
824
825 WINPR_ASSERT(context);
826
827 const UINT32 timeout =
828 freerdp_settings_get_uint32(context->settings, FreeRDP_TcpConnectTimeout);
829 const UINT64 start = GetTickCount64();
830 for (;;)
831 {
832 ERR_clear_error();
833 status = BIO_read(bufferedBio, buf, len);
834
835 if (status > 0)
836 {
837 break;
838 }
839 else if (status < 0)
840 {
841 /* Error? */
842 if (!freerdp_shall_disconnect_context(context) && BIO_should_retry(bufferedBio))
843 {
844 USleep(100);
845 continue;
846 }
847
848 WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (Status %d)", reason, status);
849 return -1;
850 }
851 else if (status == 0)
852 {
853 const UINT64 now = GetTickCount64();
854 const UINT64 diff = now - start;
855 if (freerdp_shall_disconnect_context(context) || (now < start) || (diff > timeout))
856 {
857 /* Error? */
858 WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (BIO_read returned zero)",
859 reason);
860 return status;
861 }
862 Sleep(10);
863 }
864 else // if (status == 0)
865 {
866 /* Error? */
867 WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (BIO_read returned zero)",
868 reason);
869 return -1;
870 }
871 }
872
873 if (status < 2)
874 {
875 WLog_ERR(TAG, "SOCKS Proxy reply packet too short (%s)", reason);
876 return -1;
877 }
878
879 if (buf[0] != checkVer)
880 {
881 WLog_ERR(TAG, "SOCKS Proxy version is not 5 (%s)", reason);
882 return -1;
883 }
884
885 return status;
886}
887
888WINPR_ATTR_NODISCARD
889static BOOL socks_proxy_userpass(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
890 const char* proxyPassword)
891{
892 WINPR_ASSERT(context);
893 WINPR_ASSERT(bufferedBio);
894
895 if (!proxyUsername || !proxyPassword)
896 {
897 WLog_ERR(TAG, "%s invalid username (%p) or password (%p)", logprefix,
898 WINPR_CXX_COMPAT_CAST(const void*, proxyUsername),
899 WINPR_CXX_COMPAT_CAST(const void*, proxyPassword));
900 return FALSE;
901 }
902
903 const size_t usernameLen = (BYTE)strnlen(proxyUsername, 256);
904 if (usernameLen > 255)
905 {
906 WLog_ERR(TAG, "%s username too long (%" PRIuz ", max=255)", logprefix, usernameLen);
907 return FALSE;
908 }
909
910 const size_t userpassLen = (BYTE)strnlen(proxyPassword, 256);
911 if (userpassLen > 255)
912 {
913 WLog_ERR(TAG, "%s password too long (%" PRIuz ", max=255)", logprefix, userpassLen);
914 return FALSE;
915 }
916
917 /* user/password v1 method */
918 {
919 BYTE buf[2 * 255 + 3] = WINPR_C_ARRAY_INIT;
920 size_t offset = 0;
921 buf[offset++] = 1;
922
923 buf[offset++] = WINPR_ASSERTING_INT_CAST(uint8_t, usernameLen);
924 memcpy(&buf[offset], proxyUsername, usernameLen);
925 offset += usernameLen;
926
927 buf[offset++] = WINPR_ASSERTING_INT_CAST(uint8_t, userpassLen);
928 memcpy(&buf[offset], proxyPassword, userpassLen);
929 offset += userpassLen;
930
931 ERR_clear_error();
932 const int ioffset = WINPR_ASSERTING_INT_CAST(int, offset);
933 const int status = BIO_write(bufferedBio, buf, ioffset);
934
935 if (status != ioffset)
936 {
937 WLog_ERR(TAG, "%s error writing user/password request", logprefix);
938 return FALSE;
939 }
940 }
941
942 BYTE buf[2] = WINPR_C_ARRAY_INIT;
943 const int status = recv_socks_reply(context, bufferedBio, buf, sizeof(buf), "AUTH REQ", 1);
944
945 if (status < 2)
946 return FALSE;
947
948 if (buf[1] != 0x00)
949 {
950 WLog_ERR(TAG, "%s invalid user/password", logprefix);
951 return FALSE;
952 }
953 return TRUE;
954}
955
956WINPR_ATTR_NODISCARD
957static BOOL socks_proxy_connect(rdpContext* context, BIO* bufferedBio, const char* proxyUsername,
958 const char* proxyPassword, const char* hostname, UINT16 port)
959{
960 WINPR_ASSERT(hostname);
961 // Check for IPv6 (max 45) or DNS (max 255) name. Bounded length to 256 so too long hostnames do
962 // not silently pass
963 const size_t hostnlen = strnlen(hostname, 255 + 1);
964 if (!is_ipv6_addr(hostname, hostnlen) && !winpr_str_is_valid_urlN(hostname, hostnlen))
965 return FALSE;
966
967 BYTE nauthMethods = 1;
968
969 if (proxyUsername || proxyPassword)
970 nauthMethods++;
971
972 /* select auth. method */
973 {
974 const BYTE buf[] = { 5, /* SOCKS version */
975 nauthMethods, /* #of methods offered */
976 AUTH_M_NO_AUTH, AUTH_M_USR_PASS };
977
978 size_t writeLen = sizeof(buf);
979 if (nauthMethods <= 1)
980 writeLen--;
981
982 ERR_clear_error();
983 const int iwriteLen = WINPR_ASSERTING_INT_CAST(int, writeLen);
984 const int status = BIO_write(bufferedBio, buf, iwriteLen);
985
986 if (status != iwriteLen)
987 {
988 WLog_ERR(TAG, "%s SOCKS proxy: failed to write AUTH METHOD request", logprefix);
989 return FALSE;
990 }
991 }
992
993 {
994 BYTE buf[2] = WINPR_C_ARRAY_INIT;
995 const int status = recv_socks_reply(context, bufferedBio, buf, sizeof(buf), "AUTH REQ", 5);
996
997 if (status <= 0)
998 return FALSE;
999
1000 switch (buf[1])
1001 {
1002 case AUTH_M_NO_AUTH:
1003 WLog_DBG(TAG, "%s (NO AUTH) method was selected", logprefix);
1004 break;
1005
1006 case AUTH_M_USR_PASS:
1007 if (nauthMethods < 2)
1008 {
1009 WLog_ERR(TAG, "%s USER/PASS method was not proposed to server", logprefix);
1010 return FALSE;
1011 }
1012 if (!socks_proxy_userpass(context, bufferedBio, proxyUsername, proxyPassword))
1013 return FALSE;
1014 break;
1015
1016 default:
1017 WLog_ERR(TAG, "%s unknown method 0x%x was selected by proxy", logprefix, buf[1]);
1018 return FALSE;
1019 }
1020 }
1021 /* CONN request */
1022 {
1023 BYTE buf[262] = WINPR_C_ARRAY_INIT;
1024 size_t offset = 0;
1025 buf[offset++] = 5; /* SOCKS version */
1026 buf[offset++] = SOCKS_CMD_CONNECT; /* command */
1027 buf[offset++] = 0; /* 3rd octet is reserved x00 */
1028
1029 if (inet_pton(AF_INET6, hostname, &buf[offset + 1]) == 1)
1030 {
1031 buf[offset++] = SOCKS_ADDR_IPV6;
1032 offset += 16;
1033 }
1034 else if (inet_pton(AF_INET, hostname, &buf[offset + 1]) == 1)
1035 {
1036 buf[offset++] = SOCKS_ADDR_IPV4;
1037 offset += 4;
1038 }
1039 else
1040 {
1041 buf[offset++] = SOCKS_ADDR_FQDN;
1042 buf[offset++] = WINPR_ASSERTING_INT_CAST(uint8_t, hostnlen);
1043 memcpy(&buf[offset], hostname, hostnlen);
1044 offset += hostnlen;
1045 }
1046
1047 if (offset > sizeof(buf) - 2)
1048 {
1049 WLog_ERR(TAG, "Invalid offset %" PRIuz, offset);
1050 return FALSE;
1051 }
1052
1053 /* follows DST.PORT in netw. format */
1054 buf[offset++] = (port >> 8) & 0xff;
1055 buf[offset++] = port & 0xff;
1056
1057 ERR_clear_error();
1058 const int ioffset = WINPR_ASSERTING_INT_CAST(int, offset);
1059 const int status = BIO_write(bufferedBio, buf, ioffset);
1060
1061 if ((status < 0) || (status != ioffset))
1062 {
1063 WLog_ERR(TAG, "%s SOCKS proxy: failed to write CONN REQ", logprefix);
1064 return FALSE;
1065 }
1066 }
1067
1068 BYTE buf[255] = WINPR_C_ARRAY_INIT;
1069 const int status = recv_socks_reply(context, bufferedBio, buf, sizeof(buf), "CONN REQ", 5);
1070
1071 if (status < 4)
1072 return FALSE;
1073
1074 if (buf[1] == 0)
1075 {
1076 WLog_INFO(TAG, "Successfully connected to %s:%" PRIu16, hostname, port);
1077 return TRUE;
1078 }
1079
1080 if ((buf[1] > 0) && (buf[1] < 9))
1081 WLog_INFO(TAG, "SOCKS Proxy replied: %s", rplstat[buf[1]]);
1082 else
1083 WLog_INFO(TAG, "SOCKS Proxy replied: %" PRIu8 " status not listed in rfc1928", buf[1]);
1084
1085 return FALSE;
1086}
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.