24#include <openssl/err.h>
28#include <freerdp/settings.h>
29#include <freerdp/utils/proxy_utils.h>
30#include <freerdp/crypto/crypto.h>
33#include <winpr/assert.h>
34#include <winpr/sysinfo.h>
35#include <winpr/environment.h>
39#include <freerdp/log.h>
40#define TAG FREERDP_TAG("core.proxy")
52 SOCKS_CMD_CONNECT = 1,
54 SOCKS_CMD_UDP_ASSOCIATE = 3
65static const char* rplstat[] = {
"succeeded",
66 "general SOCKS server failure",
67 "connection not allowed by ruleset",
68 "Network unreachable",
72 "Command not supported",
73 "Address type not supported" };
75static BOOL http_proxy_connect(rdpContext* context, BIO* bufferedBio,
const char* proxyUsername,
76 const char* proxyPassword,
const char* hostname, UINT16 port);
77static BOOL socks_proxy_connect(rdpContext* context, BIO* bufferedBio,
const char* proxyUsername,
78 const char* proxyPassword,
const char* hostname, UINT16 port);
79static void proxy_read_environment(rdpSettings* settings,
char* envname);
81BOOL proxy_prepare(rdpSettings* settings,
const char** lpPeerHostname, UINT16* lpPeerPort,
82 const char** lpProxyUsername,
const char** lpProxyPassword)
89 proxy_read_environment(settings,
"https_proxy");
92 proxy_read_environment(settings,
"HTTPS_PROXY");
95 proxy_read_environment(settings,
"no_proxy");
98 proxy_read_environment(settings,
"NO_PROXY");
112static BOOL value_to_int(
const char* value, LONGLONG* result, LONGLONG min, LONGLONG max)
116 if (!value || !result)
120 rc = _strtoi64(value, NULL, 0);
125 if ((rc < min) || (rc > max))
132static BOOL cidr4_match(
const struct in_addr* addr,
const struct in_addr* net, BYTE bits)
137 const uint32_t mask = htonl(0xFFFFFFFFu << (32 - bits));
138 const uint32_t amask = addr->s_addr & mask;
139 const uint32_t nmask = net->s_addr & mask;
140 return amask == nmask;
143static BOOL cidr6_match(
const struct in6_addr* address,
const struct in6_addr* network,
146 const uint32_t* a = (
const uint32_t*)address;
147 const uint32_t* n = (
const uint32_t*)network;
148 const size_t bits_whole = bits >> 5;
149 const size_t bits_incomplete = bits & 0x1F;
153 if (memcmp(a, n, bits_whole << 2) != 0)
159 uint32_t mask = htonl((0xFFFFFFFFu) << (32 - bits_incomplete));
161 if ((a[bits_whole] ^ n[bits_whole]) & mask)
168static BOOL option_ends_with(
const char* str,
const char* ext)
172 const size_t strLen = strlen(str);
173 const size_t extLen = strlen(ext);
178 return _strnicmp(&str[strLen - extLen], ext, extLen) == 0;
184static BOOL no_proxy_match_host(
const char* val,
const char* hostname)
187 WINPR_ASSERT(hostname);
190 if (_stricmp(
"*", val) == 0)
198 return option_ends_with(hostname, val);
201static BOOL starts_with(
const char* val,
const char* prefix)
203 const size_t plen = strlen(prefix);
204 const size_t vlen = strlen(val);
207 return _strnicmp(val, prefix, plen) == 0;
210static BOOL no_proxy_match_ip(
const char* val,
const char* hostname)
213 WINPR_ASSERT(hostname);
215 struct sockaddr_in sa4 = { 0 };
216 struct sockaddr_in6 sa6 = { 0 };
218 if (inet_pton(AF_INET, hostname, &sa4.sin_addr) == 1)
221 if (starts_with(hostname, val))
224 char* sub = strchr(val,
'/');
228 struct sockaddr_in mask = { 0 };
229 if (inet_pton(AF_INET, val, &mask.sin_addr) == 0)
233 if (memcmp(&mask, &sa4,
sizeof(mask)) == 0)
238 const unsigned long usub = strtoul(sub, NULL, 0);
239 if ((errno == 0) && (usub <= UINT8_MAX))
240 return cidr4_match(&sa4.sin_addr, &mask.sin_addr, (UINT8)usub);
243 else if (inet_pton(AF_INET6, hostname, &sa6.sin6_addr) == 1)
248 char str[INET6_ADDRSTRLEN + 1] = { 0 };
249 strncpy(str, val, INET6_ADDRSTRLEN);
251 const size_t len = strnlen(str, INET6_ADDRSTRLEN);
254 if (str[len - 1] ==
']')
259 if (starts_with(hostname, str))
262 char* sub = strchr(str,
'/');
266 struct sockaddr_in6 mask = { 0 };
267 if (inet_pton(AF_INET6, str, &mask.sin6_addr) == 0)
271 if (memcmp(&mask, &sa6,
sizeof(mask)) == 0)
276 const unsigned long usub = strtoul(sub, NULL, 0);
277 if ((errno == 0) && (usub <= UINT8_MAX))
278 return cidr6_match(&sa6.sin6_addr, &mask.sin6_addr, (UINT8)usub);
285static BOOL check_no_proxy(rdpSettings* settings,
const char* no_proxy)
287 const char* delimiter =
", ";
289 char* context = NULL;
291 if (!no_proxy || !settings)
294 char* copy = _strdup(no_proxy);
299 char* current = strtok_s(copy, delimiter, &context);
301 while (current && !result)
303 const size_t currentlen = strlen(current);
307 WLog_DBG(TAG,
"%s => %s (%" PRIdz
")", settings->ServerHostname, current, currentlen);
309 if (no_proxy_match_host(current, settings->ServerHostname))
311 else if (no_proxy_match_ip(current, settings->ServerHostname))
315 current = strtok_s(NULL, delimiter, &context);
322void proxy_read_environment(rdpSettings* settings,
char* envname)
324 const DWORD envlen = GetEnvironmentVariableA(envname, NULL, 0);
326 if (!envlen || (envlen <= 1))
329 char* env = calloc(1, envlen);
333 WLog_ERR(TAG,
"Not enough memory");
337 if (GetEnvironmentVariableA(envname, env, envlen) == envlen - 1)
339 if (_strnicmp(
"NO_PROXY", envname, 9) == 0)
341 if (check_no_proxy(settings, env))
343 WLog_INFO(TAG,
"deactivating proxy: %s [%s=%s]",
347 WLog_WARN(TAG,
"failed to set FreeRDP_ProxyType=PROXY_TYPE_NONE");
352 if (!proxy_parse_uri(settings, env))
355 TAG,
"Error while parsing proxy URI from environment variable; ignoring proxy");
363BOOL proxy_parse_uri(rdpSettings* settings,
const char* uri_in)
366 const char* protocol =
"";
369 if (!settings || !uri_in)
372 char* uri_copy = _strdup(uri_in);
373 char* uri = uri_copy;
377 char* p = strstr(uri,
"://");
383 if (_stricmp(
"no_proxy", uri) == 0)
388 if (_stricmp(
"http", uri) == 0)
394 else if (_stricmp(
"socks5", uri) == 0)
402 WLog_ERR(TAG,
"Only HTTP and SOCKS5 proxies supported by now");
417 char* atPtr = strrchr(uri,
'@');
428 char* colonPtr = strchr(uri,
':');
430 if (!colonPtr || (colonPtr > atPtr))
432 WLog_ERR(TAG,
"invalid syntax for proxy (contains no password)");
439 WLog_ERR(TAG,
"unable to allocate proxy username");
447 WLog_ERR(TAG,
"unable to allocate proxy password");
454 p = strchr(uri,
':');
460 if (!value_to_int(&p[1], &val, 0, UINT16_MAX))
462 WLog_ERR(TAG,
"invalid syntax for proxy (invalid port)");
468 WLog_ERR(TAG,
"invalid syntax for proxy (port missing)");
477 if (_stricmp(
"http", protocol) == 0)
487 WLog_DBG(TAG,
"setting default proxy port: %" PRIu16, port);
493 p = strchr(uri,
'/');
499 if (_stricmp(
"", uri) == 0)
501 WLog_ERR(TAG,
"invalid syntax for proxy (hostname missing)");
507 WLog_INFO(TAG,
"Parsed proxy configuration: %s://%s:%s@%s:%" PRIu16, protocol,
514 WLog_INFO(TAG,
"Parsed proxy configuration: %s://%s:%" PRIu16, protocol,
522 WLog_WARN(TAG,
"Failed to parse proxy configuration: %s://%s:%" PRIu16, protocol, uri,
528BOOL proxy_connect(rdpContext* context, BIO* bufferedBio,
const char* proxyUsername,
529 const char* proxyPassword,
const char* hostname, UINT16 port)
531 WINPR_ASSERT(context);
532 rdpSettings* settings = context->settings;
536 case PROXY_TYPE_NONE:
537 case PROXY_TYPE_IGNORE:
540 case PROXY_TYPE_HTTP:
541 return http_proxy_connect(context, bufferedBio, proxyUsername, proxyPassword, hostname,
544 case PROXY_TYPE_SOCKS:
545 return socks_proxy_connect(context, bufferedBio, proxyUsername, proxyPassword, hostname,
549 WLog_ERR(TAG,
"Invalid internal proxy configuration");
554static const char* get_response_header(
char* response)
556 char* current_pos = strchr(response,
'\r');
558 current_pos = strchr(response,
'\n');
566static BOOL http_proxy_connect(rdpContext* context, BIO* bufferedBio,
const char* proxyUsername,
567 const char* proxyPassword,
const char* hostname, UINT16 port)
572 char port_str[10] = { 0 };
573 char recv_buf[256] = { 0 };
575 size_t resultsize = 0;
576 size_t reserveSize = 0;
579 const char connect[] =
"CONNECT ";
580 const char httpheader[] =
" HTTP/1.1" CRLF
"Host: ";
582 WINPR_ASSERT(context);
583 WINPR_ASSERT(bufferedBio);
584 WINPR_ASSERT(hostname);
585 const UINT32 timeout =
588 _itoa_s(port, port_str,
sizeof(port_str), 10);
590 hostLen = strlen(hostname);
591 portLen = strnlen(port_str,
sizeof(port_str));
592 reserveSize = strlen(connect) + (hostLen + 1 + portLen) * 2 + strlen(httpheader);
593 s = Stream_New(NULL, reserveSize);
597 Stream_Write(s, connect, strlen(connect));
598 Stream_Write(s, hostname, hostLen);
599 Stream_Write_UINT8(s,
':');
600 Stream_Write(s, port_str, portLen);
601 Stream_Write(s, httpheader, strlen(httpheader));
602 Stream_Write(s, hostname, hostLen);
603 Stream_Write_UINT8(s,
':');
604 Stream_Write(s, port_str, portLen);
606 if (proxyUsername && proxyPassword)
608 const int length = _scprintf(
"%s:%s", proxyUsername, proxyPassword);
611 const size_t size = (size_t)length + 1ull;
612 char* creds = (
char*)malloc(size);
618 const char basic[] = CRLF
"Proxy-Authorization: Basic ";
621 (void)sprintf_s(creds, size,
"%s:%s", proxyUsername, proxyPassword);
622 base64 = crypto_base64_encode((
const BYTE*)creds, size - 1);
624 if (!base64 || !Stream_EnsureRemainingCapacity(s, strlen(basic) + strlen(base64)))
630 Stream_Write(s, basic, strlen(basic));
631 Stream_Write(s, base64, strlen(base64));
639 if (!Stream_EnsureRemainingCapacity(s, 4))
642 Stream_Write(s, CRLF CRLF, 4);
644 const size_t pos = Stream_GetPosition(s);
648 status = BIO_write(bufferedBio, Stream_Buffer(s), (
int)pos);
650 if ((status < 0) || ((
size_t)status != Stream_GetPosition(s)))
652 WLog_ERR(TAG,
"HTTP proxy: failed to write CONNECT request");
658 const UINT64 start = GetTickCount64();
659 while (strstr(recv_buf, CRLF CRLF) == NULL)
661 if (resultsize >=
sizeof(recv_buf) - 1)
663 WLog_ERR(TAG,
"HTTP Reply headers too long: %s", get_response_header(recv_buf));
666 const size_t rdsize =
sizeof(recv_buf) - resultsize - 1ULL;
670 WINPR_ASSERT(rdsize <= INT32_MAX);
671 status = BIO_read(bufferedBio, (BYTE*)recv_buf + resultsize, (
int)rdsize);
676 if (!freerdp_shall_disconnect_context(context) && BIO_should_retry(bufferedBio))
682 WLog_ERR(TAG,
"Failed reading reply from HTTP proxy (Status %d)", status);
685 else if (status == 0)
687 const UINT64 now = GetTickCount64();
688 const UINT64 diff = now - start;
689 if (freerdp_shall_disconnect_context(context) || (now < start) || (diff > timeout))
692 WLog_ERR(TAG,
"Failed reading reply from HTTP proxy (BIO_read returned zero)");
700 WLog_ERR(TAG,
"Failed reading reply from HTTP proxy (BIO_read returned zero)");
704 resultsize += WINPR_ASSERTING_INT_CAST(
size_t, status);
708 eol = strchr(recv_buf,
'\r');
717 WLog_INFO(TAG,
"HTTP Proxy: %s", recv_buf);
719 if (strnlen(recv_buf,
sizeof(recv_buf)) < 12)
724 if (strncmp(recv_buf,
"HTTP/1.X 200", 12) != 0)
729 Stream_Free(s, TRUE);
733static int recv_socks_reply(rdpContext* context, BIO* bufferedBio, BYTE* buf,
int len,
char* reason,
738 WINPR_ASSERT(context);
740 const UINT32 timeout =
742 const UINT64 start = GetTickCount64();
746 status = BIO_read(bufferedBio, buf, len);
755 if (!freerdp_shall_disconnect_context(context) && BIO_should_retry(bufferedBio))
761 WLog_ERR(TAG,
"Failed reading %s reply from SOCKS proxy (Status %d)", reason, status);
764 else if (status == 0)
766 const UINT64 now = GetTickCount64();
767 const UINT64 diff = now - start;
768 if (freerdp_shall_disconnect_context(context) || (now < start) || (diff > timeout))
771 WLog_ERR(TAG,
"Failed reading %s reply from SOCKS proxy (BIO_read returned zero)",
780 WLog_ERR(TAG,
"Failed reading %s reply from SOCKS proxy (BIO_read returned zero)",
788 WLog_ERR(TAG,
"SOCKS Proxy reply packet too short (%s)", reason);
792 if (buf[0] != checkVer)
794 WLog_ERR(TAG,
"SOCKS Proxy version is not 5 (%s)", reason);
801static BOOL socks_proxy_connect(rdpContext* context, BIO* bufferedBio,
const char* proxyUsername,
802 const char* proxyPassword,
const char* hostname, UINT16 port)
805 BYTE nauthMethods = 1;
807 BYTE buf[3 + 255 + 255] = { 0 };
808 const size_t hostnlen = strnlen(hostname, 255);
810 if (proxyUsername && proxyPassword)
818 buf[1] = nauthMethods;
819 buf[2] = AUTH_M_NO_AUTH;
821 if (nauthMethods > 1)
822 buf[3] = AUTH_M_USR_PASS;
825 status = BIO_write(bufferedBio, buf, writeLen);
827 if (status != writeLen)
829 WLog_ERR(TAG,
"SOCKS proxy: failed to write AUTH METHOD request");
833 status = recv_socks_reply(context, bufferedBio, buf, 2,
"AUTH REQ", 5);
841 WLog_DBG(TAG,
"SOCKS Proxy: (NO AUTH) method was selected");
844 case AUTH_M_USR_PASS:
845 if (!proxyUsername || !proxyPassword)
849 const BYTE usernameLen = (BYTE)strnlen(proxyUsername, 255);
850 const BYTE userpassLen = (BYTE)strnlen(proxyPassword, 255);
853 if (nauthMethods < 2)
855 WLog_ERR(TAG,
"SOCKS Proxy: USER/PASS method was not proposed to server");
862 buf[1] = usernameLen;
863 memcpy(ptr, proxyUsername, usernameLen);
867 memcpy(ptr, proxyPassword, userpassLen);
869 status = BIO_write(bufferedBio, buf, 3 + usernameLen + userpassLen);
871 if (status != 3 + usernameLen + userpassLen)
873 WLog_ERR(TAG,
"SOCKS Proxy: error writing user/password request");
877 status = recv_socks_reply(context, bufferedBio, buf, 2,
"AUTH REQ", 1);
884 WLog_ERR(TAG,
"SOCKS Proxy: invalid user/password");
892 WLog_ERR(TAG,
"SOCKS Proxy: unknown method 0x%x was selected by proxy", buf[1]);
898 buf[1] = SOCKS_CMD_CONNECT;
900 buf[3] = SOCKS_ADDR_FQDN;
901 buf[4] = (BYTE)hostnlen;
902 memcpy(buf + 5, hostname, hostnlen);
904 buf[hostnlen + 5] = (port >> 8) & 0xff;
905 buf[hostnlen + 6] = port & 0xff;
907 status = BIO_write(bufferedBio, buf, (
int)hostnlen + 7);
909 if ((status < 0) || ((
size_t)status != (hostnlen + 7U)))
911 WLog_ERR(TAG,
"SOCKS proxy: failed to write CONN REQ");
915 status = recv_socks_reply(context, bufferedBio, buf,
sizeof(buf),
"CONN REQ", 5);
922 WLog_INFO(TAG,
"Successfully connected to %s:%" PRIu16, hostname, port);
926 if (buf[1] > 0 && buf[1] < 9)
927 WLog_INFO(TAG,
"SOCKS Proxy replied: %s", rplstat[buf[1]]);
929 WLog_INFO(TAG,
"SOCKS Proxy replied: %" PRIu8
" status not listed in rfc1928", buf[1]);
FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
FREERDP_API BOOL freerdp_settings_set_string(rdpSettings *settings, FreeRDP_Settings_Keys_String id, const char *param)
Sets a string settings value. The param is copied.
FREERDP_API UINT16 freerdp_settings_get_uint16(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt16 id)
Returns a UINT16 settings value.
FREERDP_API BOOL freerdp_settings_set_uint32(rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id, UINT32 param)
Sets a UINT32 settings value.
FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.
FREERDP_API BOOL freerdp_settings_set_uint16(rdpSettings *settings, FreeRDP_Settings_Keys_UInt16 id, UINT16 param)
Sets a UINT16 settings value.