diff --git a/client/common/cmdline.c b/client/common/cmdline.c index f57279d01..22b48997e 100644 --- a/client/common/cmdline.c +++ b/client/common/cmdline.c @@ -1318,6 +1318,8 @@ int freerdp_client_settings_parse_command_line_arguments(rdpSettings* settings, allowUnknown); settings->ProxyHostname = NULL; + settings->ProxyUsername = NULL; + settings->ProxyPassword = NULL; if (compatibility) { @@ -1821,11 +1823,14 @@ int freerdp_client_settings_parse_command_line_arguments(rdpSettings* settings, } CommandLineSwitchCase(arg, "proxy") { - /* initial value */ - settings->ProxyType = PROXY_TYPE_HTTP; + /* initial value */ + settings->ProxyType = PROXY_TYPE_HTTP; if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) { + char *atPtr; + + /* value is [scheme://][user:password@]hostname:port */ p = strstr(arg->Value, "://"); if (p) @@ -1849,6 +1854,43 @@ int freerdp_client_settings_parse_command_line_arguments(rdpSettings* settings, arg->Value = p + 3; } + /* arg->Value is now [user:password@]hostname:port */ + atPtr = strrchr(arg->Value, '@'); + if (atPtr) + { + /* got a login / password, + * atPtr + * v + * [user:password@]hostname:port + * ^ + * colonPtr + */ + char *colonPtr = strchr(arg->Value, ':'); + if (!colonPtr || (colonPtr > atPtr)) + { + WLog_ERR(TAG, "invalid syntax for proxy, expected syntax is user:password@host:port"); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + *colonPtr = '\0'; + settings->ProxyUsername = _strdup(arg->Value); + if (!settings->ProxyUsername) + { + WLog_ERR(TAG, "unable to allocate proxy username"); + return COMMAND_LINE_ERROR_MEMORY; + } + + *atPtr = '\0'; + settings->ProxyPassword = _strdup(colonPtr + 1); + if (!settings->ProxyPassword) + { + WLog_ERR(TAG, "unable to allocate proxy password"); + return COMMAND_LINE_ERROR_MEMORY; + } + + arg->Value = atPtr + 1; + } + p = strchr(arg->Value, ':'); if (p) diff --git a/client/common/cmdline.h b/client/common/cmdline.h index f7951d18c..cb76152d4 100644 --- a/client/common/cmdline.h +++ b/client/common/cmdline.h @@ -137,7 +137,7 @@ static COMMAND_LINE_ARGUMENT_A args[] = { "port", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Server port" }, { "print-reconnect-cookie", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "Print base64 reconnect cookie after connecting" }, { "printer", COMMAND_LINE_VALUE_OPTIONAL, "[,]", NULL, NULL, -1, NULL, "Redirect printer device" }, - { "proxy", COMMAND_LINE_VALUE_REQUIRED, "[://]:", NULL, NULL, -1, NULL, "Proxy settings: override env.var (see also environment variable below).\n\tProtocol \"socks5\" should be given explicitly where \"http\" is default.\n\tNote: socks proxy is not supported by env. variable" }, + { "proxy", COMMAND_LINE_VALUE_REQUIRED, "[://][:@]:", NULL, NULL, -1, NULL, "Proxy settings: override env.var (see also environment variable below).\n\tProtocol \"socks5\" should be given explicitly where \"http\" is default.\n\tNote: socks proxy is not supported by env. variable" }, { "pth", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, "pass-the-hash", "Pass the hash (restricted admin mode)" }, { "pwidth", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Physical width of display (in millimeters)" }, { "reconnect-cookie", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Pass base64 reconnect cookie to the connection" }, diff --git a/include/freerdp/settings.h b/include/freerdp/settings.h index 0a6f25ec1..3f30bf589 100644 --- a/include/freerdp/settings.h +++ b/include/freerdp/settings.h @@ -1165,7 +1165,9 @@ struct rdp_settings ALIGN64 UINT32 ProxyType; /* 2015 */ ALIGN64 char* ProxyHostname; /* 2016 */ ALIGN64 UINT16 ProxyPort; /* 2017 */ - UINT64 padding2112[2112 - 2018]; /* 2018 */ + ALIGN64 char* ProxyUsername; /* 2018 */ + ALIGN64 char* ProxyPassword; /* 2019 */ + UINT64 padding2112[2112 - 2020]; /* 2020 */ /** * RemoteApp diff --git a/libfreerdp/core/gateway/rdg.c b/libfreerdp/core/gateway/rdg.c index 3dab0f4f1..905217de6 100755 --- a/libfreerdp/core/gateway/rdg.c +++ b/libfreerdp/core/gateway/rdg.c @@ -681,7 +681,8 @@ static BOOL rdg_tls_connect(rdpRdg* rdg, rdpTls* tls, const char* peerAddress, i rdpSettings* settings = rdg->settings; const char* peerHostname = settings->GatewayHostname; UINT16 peerPort = settings->GatewayPort; - BOOL isProxyConnection = proxy_prepare(settings, &peerHostname, &peerPort, TRUE); + const char *proxyUsername, *proxyPassword; + BOOL isProxyConnection = proxy_prepare(settings, &peerHostname, &peerPort, &proxyUsername, &proxyPassword); sockfd = freerdp_tcp_connect(rdg->context, settings, peerAddress ? peerAddress : peerHostname, @@ -714,7 +715,7 @@ static BOOL rdg_tls_connect(rdpRdg* rdg, rdpTls* tls, const char* peerAddress, i if (isProxyConnection) { - if (!proxy_connect(settings, bufferedBio, settings->GatewayHostname, settings->GatewayPort)) + if (!proxy_connect(settings, bufferedBio, proxyUsername, proxyPassword, settings->GatewayHostname, settings->GatewayPort)) return FALSE; } diff --git a/libfreerdp/core/gateway/rpc.c b/libfreerdp/core/gateway/rpc.c index 5c06d4235..24e7143ad 100644 --- a/libfreerdp/core/gateway/rpc.c +++ b/libfreerdp/core/gateway/rpc.c @@ -734,7 +734,8 @@ static int rpc_channel_tls_connect(RpcChannel* channel, int timeout) rdpSettings* settings = context->settings; const char* peerHostname = settings->GatewayHostname; UINT16 peerPort = settings->GatewayPort; - BOOL isProxyConnection = proxy_prepare(settings, &peerHostname, &peerPort, TRUE); + const char *proxyUsername = settings->ProxyUsername, *proxyPassword = settings->ProxyPassword; + BOOL isProxyConnection = proxy_prepare(settings, &peerHostname, &peerPort, &proxyUsername, &proxyPassword); sockfd = freerdp_tcp_connect(context, settings, peerHostname, peerPort, timeout); @@ -759,7 +760,7 @@ static int rpc_channel_tls_connect(RpcChannel* channel, int timeout) if (isProxyConnection) { - if (!proxy_connect(settings, bufferedBio, settings->GatewayHostname, settings->GatewayPort)) + if (!proxy_connect(settings, bufferedBio, proxyUsername, proxyPassword, settings->GatewayHostname, settings->GatewayPort)) return -1; } diff --git a/libfreerdp/core/proxy.c b/libfreerdp/core/proxy.c index c3aec5280..384a77b46 100644 --- a/libfreerdp/core/proxy.c +++ b/libfreerdp/core/proxy.c @@ -29,12 +29,50 @@ #define CRLF "\r\n" #define TAG FREERDP_TAG("core.proxy") +/* SOCKS Proxy auth methods by rfc1928 */ +enum +{ + AUTH_M_NO_AUTH = 0, + AUTH_M_GSSAPI = 1, + AUTH_M_USR_PASS = 2 +}; + +enum +{ + SOCKS_CMD_CONNECT = 1, + SOCKS_CMD_BIND = 2, + SOCKS_CMD_UDP_ASSOCIATE = 3 +}; + +enum +{ + SOCKS_ADDR_IPV4 = 1, + SOCKS_ADDR_FQDN = 3, + SOCKS_ADDR_IPV6 = 4, +}; + +/* CONN REQ replies in enum. order */ +static const char *rplstat[] = +{ + "succeeded", + "general SOCKS server failure", + "connection not allowed by ruleset", + "Network unreachable", + "Host unreachable", + "Connection refused", + "TTL expired", + "Command not supported", + "Address type not supported" +}; + + + static BOOL http_proxy_connect(BIO* bufferedBio, const char* hostname, UINT16 port); -static BOOL socks_proxy_connect(BIO* bufferedBio, const char* hostname, UINT16 port); +static BOOL socks_proxy_connect(BIO* bufferedBio, const char *proxyUsername, const char *proxyPassword, const char* hostname, UINT16 port); void proxy_read_environment(rdpSettings* settings, char* envname); BOOL proxy_prepare(rdpSettings* settings, const char** lpPeerHostname, UINT16* lpPeerPort, - BOOL isHTTPS) + const char** lpProxyUsername, const char** lpProxyPassword) { /* For TSGateway, find the system HTTPS proxy automatically */ if (!settings->ProxyType) @@ -47,6 +85,8 @@ BOOL proxy_prepare(rdpSettings* settings, const char** lpPeerHostname, UINT16* l { *lpPeerHostname = settings->ProxyHostname; *lpPeerPort = settings->ProxyPort; + *lpProxyUsername = settings->ProxyUsername; + *lpProxyPassword = settings->ProxyPassword; return TRUE; } @@ -151,7 +191,8 @@ BOOL proxy_parse_uri(rdpSettings* settings, const char* uri) return TRUE; } -BOOL proxy_connect(rdpSettings* settings, BIO* bufferedBio, const char* hostname, UINT16 port) +BOOL proxy_connect(rdpSettings* settings, BIO* bufferedBio, const char *proxyUsername, const char *proxyPassword, + const char* hostname, UINT16 port) { switch (settings->ProxyType) { @@ -162,7 +203,7 @@ BOOL proxy_connect(rdpSettings* settings, BIO* bufferedBio, const char* hostname return http_proxy_connect(bufferedBio, hostname, port); case PROXY_TYPE_SOCKS: - return socks_proxy_connect(bufferedBio, hostname, port); + return socks_proxy_connect(bufferedBio, proxyUsername, proxyPassword, hostname, port); default: WLog_ERR(TAG, "Invalid internal proxy configuration"); @@ -259,111 +300,160 @@ static BOOL http_proxy_connect(BIO* bufferedBio, const char* hostname, UINT16 po return TRUE; } -static int recv_socks_reply(BIO* bufferedBio, BYTE * buf, int len, char* reason) +static int recv_socks_reply(BIO* bufferedBio, BYTE* buf, int len, char* reason, BYTE checkVer) { int status; - ZeroMemory(buf, len); - for(;;) { - status = BIO_read(bufferedBio, buf, len); - if (status > 0) break; - else if (status < 0) - { - /* Error? */ - if (BIO_should_retry(bufferedBio)) - { - USleep(100); - continue; - } + for(;;) + { + status = BIO_read(bufferedBio, buf, len); + if (status > 0) + break; - WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (Status %d)", reason, status); - return -1; - } - else if (status == 0) - { - /* Error? */ - WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (BIO_read returned zero)", reason); - return -1; - } + if (status < 0) + { + /* Error? */ + if (BIO_should_retry(bufferedBio)) + { + USleep(100); + continue; + } + + WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (Status %d)", reason, status); + return -1; + } + + if (status == 0) + { + /* Error? */ + WLog_ERR(TAG, "Failed reading %s reply from SOCKS proxy (BIO_read returned zero)", reason); + return -1; + } } - if (buf[0] != 5) + if (status < 2) { - WLog_ERR(TAG, "SOCKS Proxy version is not 5 (%s)", reason); - return -1; + WLog_ERR(TAG, "SOCKS Proxy reply packet too short (%s)", reason); + return -1; + } + + if (buf[0] != checkVer) + { + WLog_ERR(TAG, "SOCKS Proxy version is not 5 (%s)", reason); + return -1; } return status; } -/* SOCKS Proxy auth methods by rfc1928 */ -#define AUTH_M_NO_AUTH 0 -#define AUTH_M_GSSAPI 1 -#define AUTH_M_USR_PASS 2 - -static BOOL socks_proxy_connect(BIO* bufferedBio, const char* hostname, UINT16 port) +static BOOL socks_proxy_connect(BIO* bufferedBio, const char *proxyUsername, const char *proxyPassword, + const char* hostname, UINT16 port) { int status; - BYTE buf[280], hostnlen = strlen(hostname) & 0xff; - /* CONN REQ replies in enum. order */ - static const char *rplstat[] = { - "succeeded", - "general SOCKS server failure", - "connection not allowed by ruleset", - "Network unreachable", - "Host unreachable", - "Connection refused", - "TTL expired", - "Command not supported", - "Address type not supported" - }; + int nauthMethods = 1, writeLen = 3; + BYTE buf[3 + 255 + 255]; /* biggest packet is user/pass auth */ + size_t hostnlen = strnlen(hostname, 255); + + if (proxyUsername && proxyPassword) + { + nauthMethods++; + writeLen++; + } /* select auth. method */ - memset(buf, '\0', sizeof(buf)); buf[0] = 5; /* SOCKS version */ - buf[1] = 1; /* #of methods offered */ + buf[1] = nauthMethods; /* #of methods offered */ buf[2] = AUTH_M_NO_AUTH; - status = BIO_write(bufferedBio, buf, 3); - if (status != 3) + if (nauthMethods > 1) + buf[3] = AUTH_M_USR_PASS; + + status = BIO_write(bufferedBio, buf, writeLen); + if (status != writeLen) { WLog_ERR(TAG, "SOCKS proxy: failed to write AUTH METHOD request"); return FALSE; } - status = recv_socks_reply(bufferedBio, buf, sizeof(buf), "AUTH REQ"); - if (status < 0) return FALSE; + status = recv_socks_reply(bufferedBio, buf, 2, "AUTH REQ", 5); + if (status <= 0) + return FALSE; - if (buf[1] != AUTH_M_NO_AUTH) + switch(buf[1]) { - WLog_ERR(TAG, "SOCKS Proxy: (NO AUTH) method was not selected by proxy"); - return FALSE; + case AUTH_M_NO_AUTH: + WLog_DBG(TAG, "SOCKS Proxy: (NO AUTH) method was selected"); + break; + case AUTH_M_USR_PASS: + { + int usernameLen = strnlen(proxyUsername, 255); + int userpassLen = strnlen(proxyPassword, 255); + BYTE *ptr; + + if (nauthMethods < 2) + { + WLog_ERR(TAG, "SOCKS Proxy: USER/PASS method was not proposed to server"); + return FALSE; + } + + /* user/password v1 method */ + ptr = buf + 2; + buf[0] = 1; + buf[1] = usernameLen; + memcpy(ptr, proxyUsername, usernameLen); + ptr += usernameLen; + *ptr = userpassLen; + ptr++; + memcpy(ptr, proxyPassword, userpassLen); + + status = BIO_write(bufferedBio, buf, 3 + usernameLen + userpassLen); + if (status != 3 + usernameLen + userpassLen) + { + WLog_ERR(TAG, "SOCKS Proxy: error writing user/password request"); + return FALSE; + } + + status = recv_socks_reply(bufferedBio, buf, 2, "AUTH REQ", 1); + if (status < 2) + return FALSE; + + if (buf[1] != 0x00) + { + WLog_ERR(TAG, "SOCKS Proxy: invalid user/password"); + return FALSE; + } + break; + } + default: + WLog_ERR(TAG, "SOCKS Proxy: unknown method 0x%x was selected by proxy", buf[1]); + return FALSE; } /* CONN request */ - memset(buf, '\0', sizeof(buf)); buf[0] = 5; /* SOCKS version */ - buf[1] = 1; /* command = connect */ - /* 3rd octet is reserved x00 */ - buf[3] = 3; /* addr.type = FQDN */ + buf[1] = SOCKS_CMD_CONNECT; /* command */ + buf[2] = 0; /* 3rd octet is reserved x00 */ + buf[3] = SOCKS_ADDR_FQDN; /* addr.type */ buf[4] = hostnlen; /* DST.ADDR */ - memcpy(buf +5, hostname, hostnlen); + memcpy(buf + 5, hostname, hostnlen); /* follows DST.PORT in netw. format */ - buf[hostnlen +5] = 0xff & (port >> 8); - buf[hostnlen +6] = 0xff & port; - status = BIO_write(bufferedBio, buf, hostnlen +7); - if (status != (hostnlen +7)) + buf[hostnlen + 5] = (port >> 8) & 0xff; + buf[hostnlen + 6] = port & 0xff; + + status = BIO_write(bufferedBio, buf, hostnlen + 7); + if (status != (hostnlen + 7)) { WLog_ERR(TAG, "SOCKS proxy: failed to write CONN REQ"); return FALSE; } - status = recv_socks_reply(bufferedBio, buf, sizeof(buf), "CONN REQ"); - if (status < 0) return FALSE; + status = recv_socks_reply(bufferedBio, buf, sizeof(buf), "CONN REQ", 5); + if (status < 4) + return FALSE; if (buf[1] == 0) { - WLog_INFO(TAG, "Successfully connected to %s:%d", hostname, port); - return TRUE; + WLog_INFO(TAG, "Successfully connected to %s:%d", hostname, port); + return TRUE; } if (buf[1] > 0 && buf[1] < 9) diff --git a/libfreerdp/core/proxy.h b/libfreerdp/core/proxy.h index 1b337a3f1..daf211387 100644 --- a/libfreerdp/core/proxy.h +++ b/libfreerdp/core/proxy.h @@ -24,8 +24,9 @@ #include BOOL proxy_prepare(rdpSettings* settings, const char** lpPeerHostname, UINT16* lpPeerPort, - BOOL isHTTPS); + const char **lpProxyUsername, const char **lpProxyPassword); BOOL proxy_parse_uri(rdpSettings* settings, const char* uri); -BOOL proxy_connect(rdpSettings* settings, BIO* bio, const char* hostname, UINT16 port); +BOOL proxy_connect(rdpSettings* settings, BIO* bio, const char *proxyUsername, const char *proxyPassword, + const char* hostname, UINT16 port); #endif /* FREERDP_LIB_CORE_HTTP_PROXY_H */ diff --git a/libfreerdp/core/transport.c b/libfreerdp/core/transport.c index 2ae98f727..e79fa00d4 100644 --- a/libfreerdp/core/transport.c +++ b/libfreerdp/core/transport.c @@ -406,11 +406,13 @@ BOOL transport_connect(rdpTransport* transport, const char* hostname, else { UINT16 peerPort; - const char *peerHostname; - BOOL isProxyConnection = proxy_prepare(settings, &peerHostname, &peerPort, TRUE); + const char *proxyHostname, *proxyUsername, *proxyPassword; + + BOOL isProxyConnection = proxy_prepare(settings, &proxyHostname, &peerPort, + &proxyUsername, &proxyPassword); if (isProxyConnection) - sockfd = freerdp_tcp_connect(context, settings, peerHostname, peerPort, timeout); + sockfd = freerdp_tcp_connect(context, settings, proxyHostname, peerPort, timeout); else sockfd = freerdp_tcp_connect(context, settings, hostname, port, timeout); @@ -421,8 +423,10 @@ BOOL transport_connect(rdpTransport* transport, const char* hostname, return FALSE; if (isProxyConnection) - if (!proxy_connect(settings, transport->frontBio, hostname, port)) - return FALSE; + { + if (!proxy_connect(settings, transport->frontBio, proxyUsername, proxyPassword, hostname, port)) + return FALSE; + } status = TRUE; }