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