From b8f58a2f94c6508877ef9fab5bfca07722595672 Mon Sep 17 00:00:00 2001 From: Davide Beatrici Date: Wed, 30 Oct 2019 01:39:04 +0100 Subject: [PATCH 1/4] Move generic proxy stuff from Cedar to Mayaqua This commit moves the generic (not related to our protocol) proxy stuff from Cedar to Mayaqua, in dedicated files. The functions are refactored so that they all have the same arguments and follow the same logic. Dedicated error codes are added, in order to indicate clearly why the function(s) failed. --- src/Cedar/Cedar.h | 3 - src/Cedar/Connection.h | 4 +- src/Cedar/Protocol.c | 738 ------------------------------------- src/Cedar/Protocol.h | 14 - src/Mayaqua/HTTP.h | 12 +- src/Mayaqua/MayaType.h | 5 +- src/Mayaqua/Mayaqua.h | 3 + src/Mayaqua/Mayaqua.vcproj | 8 + src/Mayaqua/Proxy.c | 618 +++++++++++++++++++++++++++++++ src/Mayaqua/Proxy.h | 45 +++ 10 files changed, 687 insertions(+), 763 deletions(-) create mode 100644 src/Mayaqua/Proxy.c create mode 100644 src/Mayaqua/Proxy.h diff --git a/src/Cedar/Cedar.h b/src/Cedar/Cedar.h index 408bc44e..b5ad275e 100644 --- a/src/Cedar/Cedar.h +++ b/src/Cedar/Cedar.h @@ -116,8 +116,6 @@ #define MAX_ACCOUNT_NAME_LEN 255 // Maximum account name length #define MAX_USERNAME_LEN 255 // User name maximum length #define MAX_PASSWORD_LEN 255 // Password name maximum length -#define MAX_PROXY_USERNAME_LEN 255 // Proxy user name maximum length -#define MAX_PROXY_PASSWORD_LEN 255 // Proxy Password maximum length #define MAX_SERVER_STR_LEN 255 // Maximum length of server string #define MAX_CLIENT_STR_LEN 255 // Maximum length of client string #define MAX_HUBNAME_LEN 255 // Maximum length of HUB name @@ -190,7 +188,6 @@ #define TIMEOUT_MAX (60 * 1000) // Maximum timeout in seconds #define TIMEOUT_DEFAULT (30 * 1000) // Default number of seconds to timeout #define CONNECTING_TIMEOUT (15 * 1000) // Timeout in seconds of being connected -#define CONNECTING_TIMEOUT_PROXY (4 * 1000) // Timeout in seconds of being connected (Proxy) #define CONNECTING_POOLING_SPAN (3 * 1000) // Polling interval of connected #define MIN_RETRY_INTERVAL (5 * 1000) // Minimum retry interval #define MAX_RETRY_INTERVAL (300 * 1000) // Maximum retry interval diff --git a/src/Cedar/Connection.h b/src/Cedar/Connection.h index de6a50fd..a134b1ed 100644 --- a/src/Cedar/Connection.h +++ b/src/Cedar/Connection.h @@ -64,8 +64,8 @@ struct CLIENT_OPTION UINT ProxyType; // Type of proxy char ProxyName[MAX_HOST_NAME_LEN + 1]; // Proxy server name UINT ProxyPort; // Port number of the proxy server - char ProxyUsername[MAX_PROXY_USERNAME_LEN + 1]; // Maximum user name length - char ProxyPassword[MAX_PROXY_PASSWORD_LEN + 1]; // Maximum password length + char ProxyUsername[PROXY_MAX_USERNAME_LEN + 1]; // Maximum user name length + char ProxyPassword[PROXY_MAX_PASSWORD_LEN + 1]; // Maximum password length char CustomHttpHeader[HTTP_CUSTOM_HEADER_MAX_SIZE + 1]; // Custom HTTP proxy header UINT NumRetry; // Automatic retries UINT RetryInterval; // Retry interval diff --git a/src/Cedar/Protocol.c b/src/Cedar/Protocol.c index 3bdecd91..c458bbac 100644 --- a/src/Cedar/Protocol.c +++ b/src/Cedar/Protocol.c @@ -6185,744 +6185,6 @@ SOCK *ClientConnectGetSocket(CONNECTION *c, bool additional_connect) return s; } -// Connect via SOCKS4 -SOCK *SocksConnectEx2(CONNECTION *c, char *proxy_host_name, UINT proxy_port, - char *server_host_name, UINT server_port, - char *username, bool additional_connect, - bool *cancel_flag, void *hWnd, UINT timeout, IP *ret_ip) -{ - SOCK *s = NULL; - IP ip; - // Validate arguments - if (c == NULL || proxy_host_name == NULL || proxy_port == 0 || server_host_name == NULL - || server_port == 0) - { - if (c != NULL) - { - c->Err = ERR_PROXY_CONNECT_FAILED; - } - return NULL; - } - - // Get the IP address of the destination server - if (GetIP(&ip, server_host_name) == false) - { - // Failure - c->Err = ERR_CONNECT_FAILED; - return NULL; - } - - if (c->Halt) - { - // Stop - c->Err = ERR_USER_CANCEL; - return NULL; - } - - // Connection - s = TcpConnectEx3(proxy_host_name, proxy_port, timeout, cancel_flag, hWnd, true, NULL, false, ret_ip); - if (s == NULL) - { - // Failure - c->Err = ERR_PROXY_CONNECT_FAILED; - return NULL; - } - - // Timeout setting - SetTimeout(s, MIN(CONNECTING_TIMEOUT_PROXY, (timeout == 0 ? INFINITE : timeout))); - - if (additional_connect == false) - { - c->FirstSock = s; - } - - // Request packet transmission - if (SocksSendRequestPacket(c, s, server_port, &ip, username) == false) - { - // Failure - if (additional_connect == false) - { - c->FirstSock = NULL; - } - Disconnect(s); - ReleaseSock(s); - return NULL; - } - - // Receive a response packet - if (SocksRecvResponsePacket(c, s) == false) - { - // Failure - if (additional_connect == false) - { - c->FirstSock = NULL; - } - Disconnect(s); - ReleaseSock(s); - return NULL; - } - - SetTimeout(s, INFINITE); - - return s; -} - -// Receive a SOCKS4 response packet -bool SocksRecvResponsePacket(CONNECTION *c, SOCK *s) -{ - BUF *b; - UCHAR tmp[8]; - UCHAR vn, cd; - // Validate arguments - if (c == NULL || s == NULL) - { - return false; - } - - if (RecvAll(s, tmp, sizeof(tmp), false) == false) - { - c->Err = ERR_DISCONNECTED; - return false; - } - - b = NewBuf(); - WriteBuf(b, tmp, sizeof(tmp)); - SeekBuf(b, 0, 0); - - ReadBuf(b, &vn, 1); - ReadBuf(b, &cd, 1); - - FreeBuf(b); - - if (vn != 0) - { - c->Err = ERR_PROXY_ERROR; - return false; - } - - switch (cd) - { - case 90: - // Success - return true; - - case 93: - // Authentication failure - c->Err = ERR_PROXY_AUTH_FAILED; - return false; - - default: - // Connection to the server failure - c->Err = ERR_CONNECT_FAILED; - return false; - } -} - -// Send a SOCKS4 request packet -bool SocksSendRequestPacket(CONNECTION *c, SOCK *s, UINT dest_port, IP *dest_ip, char *userid) -{ - BUF *b; - UCHAR vn, cd; - USHORT port; - UINT ip; - bool ret; - // Validate arguments - if (s == NULL || dest_port == 0 || dest_ip == NULL || c == NULL) - { - return false; - } - if (userid == NULL) - { - userid = ""; - } - - b = NewBuf(); - vn = 4; - cd = 1; - WriteBuf(b, &vn, 1); - WriteBuf(b, &cd, 1); - port = Endian16((USHORT)dest_port); - ip = IPToUINT(dest_ip); - WriteBuf(b, &port, 2); - WriteBuf(b, &ip, 4); - WriteBuf(b, userid, StrLen(userid) + 1); - - ret = SendAll(s, b->Buf, b->Size, false); - if (ret == false) - { - c->Err = ERR_DISCONNECTED; - } - - FreeBuf(b); - - return ret; -} - -// Connect via SOCKS5 (RFC1928) -SOCK *Socks5Connect(CONNECTION *c, WPC_CONNECT *w, bool additional_connect, bool *cancel_flag, void *hWnd, UINT timeout, IP *ret_ip) -{ - UCHAR tmp, recv_buf[2], *recv_buf_final; - USHORT port; - bool ret; - SOCK *s; - BUF *b; - IP ip; - // Validate arguments - if (c == NULL || w == NULL || w->Port == 0 || w->ProxyPort == 0 || IsEmptyStr(w->HostName) || IsEmptyStr(w->ProxyHostName)) - { - if (c != NULL) - { - c->Err = ERR_PROXY_CONNECT_FAILED; - } - return NULL; - } - - if (c->Halt) - { - // Stop - c->Err = ERR_USER_CANCEL; - return NULL; - } - - // Open TCP connection to the proxy server - s = TcpConnectEx3(w->ProxyHostName, w->ProxyPort, timeout, cancel_flag, hWnd, true, NULL, false, ret_ip); - if (s == NULL) - { - // Failure - c->Err = ERR_PROXY_CONNECT_FAILED; - return NULL; - } - - // Set the timeout setting - SetTimeout(s, MIN(CONNECTING_TIMEOUT_PROXY, (timeout == 0 ? INFINITE : timeout))); - - if (additional_connect == false) - { - c->FirstSock = s; - } - - // +----+----------+----------+ - // |VER | NMETHODS | METHODS | - // +----+----------+----------+ - // | 1 | 1 | 1 to 255 | - // +----+----------+----------+ - // - // X'00' NO AUTHENTICATION REQUIRED - // X'01' GSSAPI - // X'02' USERNAME/PASSWORD - // X'03' to X'7F' IANA ASSIGNED - // X'80' to X'FE' RESERVED FOR PRIVATE METHODS - // X'FF' NO ACCEPTABLE METHOD - - b = NewBuf(); - tmp = 5; - WriteBuf(b, &tmp, sizeof(tmp)); // SOCKS version - tmp = 2; - WriteBuf(b, &tmp, sizeof(tmp)); // Number of supported methods - tmp = 0; - WriteBuf(b, &tmp, sizeof(tmp)); // No authentication - tmp = 2; - WriteBuf(b, &tmp, sizeof(tmp)); // Username/password - - ret = SendAll(s, b->Buf, b->Size, false); - FreeBuf(b); - - if (ret == false) - { - Debug("Socks5Connect(): [Phase 1] Failed to send initial data to the server.\n"); - c->Err = ERR_DISCONNECTED; - goto failure; - } - - // +----+--------+ - // |VER | METHOD | - // +----+--------+ - // | 1 | 1 | - // +----+--------+ - - if (RecvAll(s, recv_buf, sizeof(recv_buf), false) == false) - { - Debug("Socks5Connect(): [Phase 1] Failed to receive initial data response from the server.\n"); - c->Err = ERR_DISCONNECTED; - goto failure; - } - - if (recv_buf[0] != 5) - { - Debug("Socks5Connect(): [Phase 1] Unmatching version: %u.\n", recv_buf[0]); - c->Err = ERR_PROXY_ERROR; - goto failure; - } - - // Username/password authentication (RFC1929) - if (recv_buf[1] == 2) - { - // +----+------+----------+------+----------+ - // |VER | ULEN | UNAME | PLEN | PASSWD | - // +----+------+----------+------+----------+ - // | 1 | 1 | 1 to 255 | 1 | 1 to 255 | - // +----+------+----------+------+----------+ - - b = NewBuf(); - tmp = 1; - WriteBuf(b, &tmp, sizeof(tmp)); // Authentication protocol version - tmp = StrLen(w->ProxyUsername); - WriteBuf(b, &tmp, sizeof(tmp)); // Username length - WriteBuf(b, w->ProxyUsername, tmp); // Username - tmp = StrLen(w->ProxyPassword); - WriteBuf(b, &tmp, sizeof(tmp)); // Password length - WriteBuf(b, w->ProxyPassword, tmp); // Password - - ret = SendAll(s, b->Buf, b->Size, false); - FreeBuf(b); - - if (ret == false) - { - Debug("Socks5Connect(): [Phase 1] Failed to send authentication data to the server.\n"); - c->Err = ERR_DISCONNECTED; - goto failure; - } - - // +----+--------+ - // |VER | STATUS | - // +----+--------+ - // | 1 | 1 | - // +----+--------+ - - if (RecvAll(s, recv_buf, sizeof(recv_buf), false) == false) - { - Debug("Socks5Connect(): [Phase 1] Failed to receive authentication data response from the server.\n"); - c->Err = ERR_DISCONNECTED; - goto failure; - } - - if (recv_buf[1] != 0) - { - Debug("Socks5Connect(): [Phase 1] Authentication failure error code sent by the server: %u.\n", recv_buf[1]); - c->Err = ERR_PROXY_AUTH_FAILED; - goto failure; - } - } - - // +----+-----+-------+------+----------+----------+ - // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | - // +----+-----+-------+------+----------+----------+ - // | 1 | 1 | X'00' | 1 | Variable | 2 | - // +----+-----+-------+------+----------+----------+ - // - // VER protocol version: X'05' - // CMD - // CONNECT X'01' - // BIND X'02' - // UDP ASSOCIATE X'03' - // RSV RESERVED - // ATYP address type of following address - // IP V4 address X'01' - // DOMAINNAME X'03' - // IP V6 address X'04' - // DST.ADDR desired destination address - // DST.PORT desired destination port in network octet order - - // Prepare data to send - b = NewBuf(); - tmp = 5; - WriteBuf(b, &tmp, sizeof(tmp)); // SOCKS version - tmp = 1; - WriteBuf(b, &tmp, sizeof(tmp)); // Command - tmp = 0; - WriteBuf(b, &tmp, sizeof(tmp)); // Reserved byte - - // Convert the hostname to an IP structure (if it's an IP address) - StrToIP(&ip, w->HostName); - - // If the IP structure doesn't contain an IP address, it means that the string is an hostname - if (IsZeroIp(&ip)) - { - UCHAR dest_length = StrLen(w->HostName); - tmp = 3; - WriteBuf(b, &tmp, sizeof(tmp)); // Destination type (hostname) - WriteBuf(b, &dest_length, sizeof(dest_length)); // Destination hostname length - WriteBuf(b, w->HostName, dest_length); // Destination hostname - } - else - { - if (IsIP6(&ip)) - { - tmp = 4; - WriteBuf(b, &tmp, sizeof(tmp)); // Destination type (IPv6) - WriteBuf(b, ip.ipv6_addr, sizeof(ip.ipv6_addr)); // Destination IPv6 address - } - else - { - tmp = 1; - WriteBuf(b, &tmp, sizeof(tmp)); // Destination type (IPv4) - WriteBuf(b, ip.addr, sizeof(ip.addr)); // Destination IPv4 address - } - } - - // Convert the port in network octet order - port = Endian16((USHORT)w->Port); - WriteBuf(b, &port, sizeof(port)); // Destination port - - // Send data - ret = SendAll(s, b->Buf, b->Size, false); - FreeBuf(b); - - if (ret == false) - { - Debug("Socks5Connect(): [Phase 2] Failed to send data to the server.\n"); - c->Err = ERR_DISCONNECTED; - goto failure; - } - - // +----+-----+-------+------+----------+----------+ - // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | - // +----+-----+-------+------+----------+----------+ - // | 1 | 1 | X’00’ | 1 | Variable | 2 | - // +----+-----+-------+------+----------+----------+ - // - // VER protocol version: X’05’ - // REP Reply field: - // X’00’ succeeded - // X’01’ general SOCKS server failure - // X’02’ connection not allowed by ruleset - // X’03’ Network unreachable - // X’04’ Host unreachable - // X’05’ Connection refused - // X’06’ TTL expired - // X’07’ Command not supported - // X’08’ Address type not supported - // X’09’ to X’FF’ unassigned - - // The packet sent by the server should always have the same size as the one we sent to it. - // However, there are some implementations which send fixed values (aside from the first 2 bytes). - // In order to support such implementations, we read the first 4 bytes in order to know the address type before trying to read the rest of the packet. - recv_buf_final = Malloc(4); - - if (RecvAll(s, recv_buf_final, 4, false) == false) - { - Free(recv_buf_final); - Debug("Socks5Connect(): [Phase 2] Failed to receive response from the server.\n"); - c->Err = ERR_DISCONNECTED; - goto failure; - } - - // We only need the first two bytes (version and response code), but we have to read the entire packet from the socket - recv_buf[0] = recv_buf_final[0]; - recv_buf[1] = recv_buf_final[1]; - - // We receive the rest of the packet by knowing the size according to the address type - switch (recv_buf_final[3]) - { - case 1: - // IPv4 - recv_buf_final = ReAlloc(recv_buf_final, 6); // 4 bytes (IPv4) + 2 bytes (port) - ret = RecvAll(s, recv_buf_final, 6, false); - break; - case 4: - // IPv6 - recv_buf_final = ReAlloc(recv_buf_final, 18); // 4 bytes (IPv4) + 2 bytes (port) - ret = RecvAll(s, recv_buf_final, 18, false); - break; - case 3: - // Hostname - ret = RecvAll(s, &tmp, 1, false); - if (ret == true) - { - recv_buf_final = ReAlloc(recv_buf_final, tmp + 2); // Hostname length + 2 bytes (port) - ret = RecvAll(s, recv_buf_final, tmp + 2, false); - } - } - - Free(recv_buf_final); - - if (ret == false) - { - Debug("Socks5Connect(): [Phase 2] Malformed response received from the server.\n"); - c->Err = ERR_DISCONNECTED; - goto failure; - } - - if (recv_buf[0] != 5) - { - Debug("Socks5Connect(): [Phase 2] Unmatching version: %u.\n", recv_buf_final[0]); - c->Err = ERR_PROXY_ERROR; - goto failure; - } - - if (recv_buf[1] == 0) - { - // Success - SetTimeout(s, INFINITE); - return s; - } - else - { - Debug("Socks5Connect(): [Phase 2] Connection failed with error: %u\n", recv_buf[1]); - c->Err = ERR_PROXY_ERROR; - } - -failure: - if (additional_connect == false) - { - c->FirstSock = NULL; - } - - Disconnect(s); - ReleaseSock(s); - return NULL; -} - -// Connect through a proxy -SOCK *ProxyConnectEx2(CONNECTION *c, char *proxy_host_name, UINT proxy_port, - char *server_host_name, UINT server_port, - char *username, char *password, bool additional_connect, - bool *cancel_flag, void *hWnd, UINT timeout) -{ - WPC_CONNECT wpc_connect; - Zero(&wpc_connect, sizeof(wpc_connect)); - - StrCpy(wpc_connect.ProxyHostName, sizeof(wpc_connect.ProxyHostName), proxy_host_name); - wpc_connect.ProxyPort = proxy_port; - StrCpy(wpc_connect.HostName, sizeof(wpc_connect.HostName), server_host_name); - wpc_connect.Port = server_port; - StrCpy(wpc_connect.ProxyUsername, sizeof(wpc_connect.ProxyUsername), username); - StrCpy(wpc_connect.ProxyPassword, sizeof(wpc_connect.ProxyPassword), password); - - return ProxyConnectEx3(c, &wpc_connect, additional_connect, cancel_flag, hWnd, timeout); -} -SOCK *ProxyConnectEx3(CONNECTION *c, WPC_CONNECT *wpc_connect, - bool additional_connect, bool *cancel_flag, void *hWnd, - UINT timeout) -{ - SOCK *s = NULL; - bool use_auth = false; - char tmp[MAX_SIZE]; - char auth_tmp_str[MAX_SIZE], auth_b64_str[MAX_SIZE * 2]; - char basic_str[MAX_SIZE * 2]; - UINT http_error_code; - HTTP_HEADER *h; - char server_host_name_tmp[256]; - UINT i, len; - // Validate arguments - if (c == NULL || IsEmptyStr(wpc_connect->ProxyHostName) || wpc_connect->ProxyPort == 0 || IsEmptyStr(wpc_connect->HostName) || wpc_connect->Port == 0) - { - if (c != NULL) - { - c->Err = ERR_PROXY_CONNECT_FAILED; - } - return NULL; - } - - if ((IsEmptyStr(wpc_connect->ProxyUsername) || IsEmptyStr(wpc_connect->ProxyPassword)) == false) - { - use_auth = true; - } - - if (c->Halt) - { - // Stop - c->Err = ERR_USER_CANCEL; - return NULL; - } - - Zero(server_host_name_tmp, sizeof(server_host_name_tmp)); - StrCpy(server_host_name_tmp, sizeof(server_host_name_tmp), wpc_connect->HostName); - - len = StrLen(server_host_name_tmp); - - for (i = 0;i < len;i++) - { - if (server_host_name_tmp[i] == '/') - { - server_host_name_tmp[i] = 0; - } - } - - // Connection - s = TcpConnectEx3(wpc_connect->ProxyHostName, wpc_connect->ProxyPort, timeout, cancel_flag, hWnd, true, NULL, false, NULL); - if (s == NULL) - { - // Failure - c->Err = ERR_PROXY_CONNECT_FAILED; - return NULL; - } - - // Timeout setting - SetTimeout(s, MIN(CONNECTING_TIMEOUT_PROXY, (timeout == 0 ? INFINITE : timeout))); - - if (additional_connect == false) - { - c->FirstSock = s; - } - - // HTTP header generation - if (IsStrIPv6Address(server_host_name_tmp)) - { - IP ip; - char iptmp[MAX_PATH]; - - StrToIP(&ip, server_host_name_tmp); - IPToStr(iptmp, sizeof(iptmp), &ip); - - Format(tmp, sizeof(tmp), "[%s]:%u", iptmp, wpc_connect->Port); - } - else - { - Format(tmp, sizeof(tmp), "%s:%u", server_host_name_tmp, wpc_connect->Port); - } - - h = NewHttpHeader("CONNECT", tmp, "HTTP/1.0"); - - if (IsEmptyStr(wpc_connect->CustomHttpHeader) == false) - { - TOKEN_LIST *tokens = ParseToken(wpc_connect->CustomHttpHeader, "\r\n"); - if (tokens != NULL) - { - for (i = 0; i < tokens->NumTokens; i++) - { - AddHttpValueStr(h, tokens->Token[i]); - } - - FreeToken(tokens); - } - } - - if (GetHttpValue(h, "User-Agent") == NULL) - { - AddHttpValue(h, NewHttpValue("User-Agent", (c->Cedar == NULL ? DEFAULT_USER_AGENT : c->Cedar->HttpUserAgent))); - } - - if (GetHttpValue(h, "Host") == NULL) - { - AddHttpValue(h, NewHttpValue("Host", server_host_name_tmp)); - } - - if (GetHttpValue(h, "Content-Length") == NULL) - { - AddHttpValue(h, NewHttpValue("Content-Length", "0")); - } - - if (GetHttpValue(h, "Proxy-Connection") == NULL) - { - AddHttpValue(h, NewHttpValue("Proxy-Connection", "Keep-Alive")); - } - - if (GetHttpValue(h, "Pragma") == NULL) - { - AddHttpValue(h, NewHttpValue("Pragma", "no-cache")); - } - - if (use_auth && GetHttpValue(h, "Proxy-Authorization") == NULL) - { - wchar_t tmp[MAX_SIZE]; - UniFormat(tmp, sizeof(tmp), _UU("STATUS_3"), server_host_name_tmp); - // Generate the authentication string - Format(auth_tmp_str, sizeof(auth_tmp_str), "%s:%s", - wpc_connect->ProxyUsername, wpc_connect->ProxyPassword); - - // Base64 encode - Zero(auth_b64_str, sizeof(auth_b64_str)); - Encode64(auth_b64_str, auth_tmp_str); - Format(basic_str, sizeof(basic_str), "Basic %s", auth_b64_str); - - AddHttpValue(h, NewHttpValue("Proxy-Authorization", basic_str)); - } - - // Transmission - if (SendHttpHeader(s, h) == false) - { - // Failure - if (additional_connect == false) - { - c->FirstSock = NULL; - } - FreeHttpHeader(h); - Disconnect(s); - ReleaseSock(s); - c->Err = ERR_PROXY_ERROR; - return NULL; - } - - FreeHttpHeader(h); - - if (c->Halt) - { - // Stop - if (additional_connect == false) - { - c->FirstSock = NULL; - } - Disconnect(s); - ReleaseSock(s); - c->Err = ERR_USER_CANCEL; - return NULL; - } - - // Receive the results - h = RecvHttpHeader(s); - if (h == NULL) - { - // Failure - if (additional_connect == false) - { - c->FirstSock = NULL; - } - FreeHttpHeader(h); - Disconnect(s); - ReleaseSock(s); - c->Err = ERR_PROXY_ERROR; - return NULL; - } - - http_error_code = 0; - if (StrLen(h->Method) == 8) - { - if (Cmp(h->Method, "HTTP/1.", 7) == 0) - { - http_error_code = ToInt(h->Target); - } - } - FreeHttpHeader(h); - - // Check the code - switch (http_error_code) - { - case 401: - case 403: - case 407: - // Authentication failure - if (additional_connect == false) - { - c->FirstSock = NULL; - } - Disconnect(s); - ReleaseSock(s); - c->Err = ERR_PROXY_AUTH_FAILED; - return NULL; - - default: - if ((http_error_code / 100) == 2) - { - // Success - SetTimeout(s, INFINITE); - return s; - } - else - { - // Receive an unknown result - if (additional_connect == false) - { - c->FirstSock = NULL; - } - Disconnect(s); - ReleaseSock(s); - c->Err = ERR_PROXY_ERROR; - return NULL; - } - } -} - // TCP connection function SOCK *TcpConnectEx3(char *hostname, UINT port, UINT timeout, bool *cancel_flag, void *hWnd, bool no_nat_t, UINT *nat_t_error_code, bool try_start_ssl, IP *ret_ip) { diff --git a/src/Cedar/Protocol.h b/src/Cedar/Protocol.h index fabc0887..03f6ca17 100644 --- a/src/Cedar/Protocol.h +++ b/src/Cedar/Protocol.h @@ -151,20 +151,6 @@ SOCK *ClientAdditionalConnectToServer(CONNECTION *c); bool ClientUploadAuth2(CONNECTION *c, SOCK *s); bool GetSessionKeyFromPack(PACK *p, UCHAR *session_key, UINT *session_key_32); -SOCK *ProxyConnectEx2(CONNECTION *c, char *proxy_host_name, UINT proxy_port, - char *server_host_name, UINT server_port, - char *username, char *password, bool additional_connect, - bool *cancel_flag, void *hWnd, UINT timeout); -SOCK *ProxyConnectEx3(CONNECTION *c, WPC_CONNECT *wpc_connect, - bool additional_connect, bool *cancel_flag, void *hWnd, - UINT timeout); -SOCK *SocksConnectEx2(CONNECTION *c, char *proxy_host_name, UINT proxy_port, - char *server_host_name, UINT server_port, - char *username, bool additional_connect, - bool *cancel_flag, void *hWnd, UINT timeout, IP *ret_ip); -bool SocksSendRequestPacket(CONNECTION *c, SOCK *s, UINT dest_port, IP *dest_ip, char *userid); -bool SocksRecvResponsePacket(CONNECTION *c, SOCK *s); -SOCK *Socks5Connect(CONNECTION *c, WPC_CONNECT *w, bool additional_connect, bool *cancel_flag, void *hWnd, UINT timeout, IP *ret_ip); void CreateNodeInfo(NODE_INFO *info, CONNECTION *c); UINT SecureSign(SECURE_SIGN *sign, UINT device_id, char *pin); void ClientUploadNoop(CONNECTION *c); diff --git a/src/Mayaqua/HTTP.h b/src/Mayaqua/HTTP.h index 84814a8d..8d45cf22 100644 --- a/src/Mayaqua/HTTP.h +++ b/src/Mayaqua/HTTP.h @@ -16,15 +16,17 @@ #define HTTP_SAITAMA "/saitama.jpg" #define HTTP_PICTURES "/picture" // Maximum size of the custom HTTP header -#define HTTP_CUSTOM_HEADER_MAX_SIZE 1024 +#define HTTP_CUSTOM_HEADER_MAX_SIZE 1024 // Maximum size of a single line in the HTTP header -#define HTTP_HEADER_LINE_MAX_SIZE 4096 +#define HTTP_HEADER_LINE_MAX_SIZE 4096 // Maximum number of lines in the HTTP header -#define HTTP_HEADER_MAX_LINES 128 +#define HTTP_HEADER_MAX_LINES 128 +// Maximum size of the user agent string +#define HTTP_HEADER_USER_AGENT_MAX_SIZE 512 // Maximum size of the random number to be included in the PACK -#define HTTP_PACK_RAND_SIZE_MAX 1000 +#define HTTP_PACK_RAND_SIZE_MAX 1000 // Maximum PACK size in the HTTP -#define HTTP_PACK_MAX_SIZE 65536 +#define HTTP_PACK_MAX_SIZE 65536 // HTTP value struct HTTP_VALUE diff --git a/src/Mayaqua/MayaType.h b/src/Mayaqua/MayaType.h index 8f4cdece..b2ea200c 100644 --- a/src/Mayaqua/MayaType.h +++ b/src/Mayaqua/MayaType.h @@ -510,5 +510,8 @@ typedef struct IKE_HEADER IKE_HEADER; // HTTP.h typedef struct HTTP_MIME_TYPE HTTP_MIME_TYPE; -#endif // MAYATYPE_H +// Proxy.h +typedef struct PROXY_PARAM_IN PROXY_PARAM_IN; +typedef struct PROXY_PARAM_OUT PROXY_PARAM_OUT; +#endif // MAYATYPE_H diff --git a/src/Mayaqua/Mayaqua.h b/src/Mayaqua/Mayaqua.h index 2d88536f..236cf2d3 100644 --- a/src/Mayaqua/Mayaqua.h +++ b/src/Mayaqua/Mayaqua.h @@ -277,6 +277,9 @@ int iconv_close (iconv_t __cd); // HTTP #include +// Proxy +#include + // 64 bit real-time clock #include diff --git a/src/Mayaqua/Mayaqua.vcproj b/src/Mayaqua/Mayaqua.vcproj index efd2aa72..d3eda17b 100644 --- a/src/Mayaqua/Mayaqua.vcproj +++ b/src/Mayaqua/Mayaqua.vcproj @@ -366,6 +366,10 @@ RelativePath=".\Pack.c" > + + @@ -464,6 +468,10 @@ RelativePath=".\Pack.h" > + + diff --git a/src/Mayaqua/Proxy.c b/src/Mayaqua/Proxy.c new file mode 100644 index 00000000..9a5c4670 --- /dev/null +++ b/src/Mayaqua/Proxy.c @@ -0,0 +1,618 @@ +#include + +#include + +SOCK *Internal_ProxyTcpConnect(PROXY_PARAM_IN *param, volatile bool *cancel_flag, IP *resolved_ip) +{ +#ifdef OS_WIN32 + if (param->Hwnd != NULL) + { + return WinConnectEx3((HWND)param->Hwnd, param->Hostname, param->Port, param->Timeout, 0, NULL, NULL, NULL, NULL, false); + } +#endif + + return ConnectEx4(param->Hostname, param->Port, param->Timeout, (bool *)cancel_flag, NULL, NULL, false, true, resolved_ip); +} + +// Connect to an HTTP proxy +UINT ProxyHttpConnect(PROXY_PARAM_OUT *out, PROXY_PARAM_IN *in, volatile bool *cancel_flag) +{ + bool dummy_cancel_flag = false, use_auth = false; + char target_hostname[MAX_HOST_NAME_LEN + 1]; + char target_hostname_port[MAX_SIZE]; + HTTP_HEADER *h; + UINT i, ret; + SOCK *s; + // Validate arguments + if (out == NULL || in == NULL || in->Port == 0 || in->TargetPort == 0 || IsEmptyStr(in->Hostname) || IsEmptyStr(in->TargetHostname)) + { + return PROXY_ERROR_PARAMETER; + } + + if (cancel_flag == NULL) + { + cancel_flag = &dummy_cancel_flag; + } + else if (*cancel_flag) + { + return PROXY_ERROR_CANCELED; + } + + Zero(out, sizeof(PROXY_PARAM_OUT)); + + // Open TCP connection to the proxy server + s = Internal_ProxyTcpConnect(in, cancel_flag, &out->ResolvedIp); + if (s == NULL) + { + return PROXY_ERROR_CONNECTION; + } + + SetTimeout(s, MIN(PROXY_CONNECTION_TIMEOUT, (in->Timeout == 0 ? INFINITE : in->Timeout))); + + if ((IsEmptyStr(in->Username) || IsEmptyStr(in->Password)) == false) + { + use_auth = true; + } + + Zero(target_hostname, sizeof(target_hostname)); + StrCpy(target_hostname, sizeof(target_hostname), in->TargetHostname); + + for (i = 0; i < StrLen(target_hostname); ++i) + { + if (target_hostname[i] == '/') + { + target_hostname[i] = 0; + } + } + + // Generate HTTP header + if (IsStrIPv6Address(target_hostname)) + { + IP ip; + char iptmp[MAX_PATH]; + + StrToIP(&ip, target_hostname); + IPToStr(iptmp, sizeof(iptmp), &ip); + + Format(target_hostname_port, sizeof(target_hostname_port), "[%s]:%hu", iptmp, in->TargetPort); + } + else + { + Format(target_hostname_port, sizeof(target_hostname_port), "%s:%hu", target_hostname, in->TargetPort); + } + + h = NewHttpHeader("CONNECT", target_hostname_port, "HTTP/1.0"); + + if (IsEmptyStr(in->HttpCustomHeader) == false) + { + TOKEN_LIST *tokens = ParseToken(in->HttpCustomHeader, "\r\n"); + if (tokens != NULL) + { + for (i = 0; i < tokens->NumTokens; i++) + { + AddHttpValueStr(h, tokens->Token[i]); + } + + FreeToken(tokens); + } + } + + if (GetHttpValue(h, "User-Agent") == NULL) + { + AddHttpValue(h, NewHttpValue("User-Agent", IsEmptyStr(in->HttpUserAgent) ? DEFAULT_USER_AGENT : in->HttpUserAgent)); + } + + if (GetHttpValue(h, "Host") == NULL) + { + AddHttpValue(h, NewHttpValue("Host", target_hostname)); + } + + if (GetHttpValue(h, "Content-Length") == NULL) + { + AddHttpValue(h, NewHttpValue("Content-Length", "0")); + } + + if (GetHttpValue(h, "Proxy-Connection") == NULL) + { + AddHttpValue(h, NewHttpValue("Proxy-Connection", "Keep-Alive")); + } + + if (GetHttpValue(h, "Pragma") == NULL) + { + AddHttpValue(h, NewHttpValue("Pragma", "no-cache")); + } + + if (use_auth && GetHttpValue(h, "Proxy-Authorization") == NULL) + { + char auth_str[MAX_SIZE * 2], auth_b64_str[MAX_SIZE * 2]; + + // Generate the authentication string + Format(auth_str, sizeof(auth_str), "%s:%s", in->Username, in->Password); + + // Base64 encode + Zero(auth_b64_str, sizeof(auth_b64_str)); + Encode64(auth_b64_str, auth_str); + + // Generate final string + Format(auth_str, sizeof(auth_str), "Basic %s", auth_b64_str); + + AddHttpValue(h, NewHttpValue("Proxy-Authorization", auth_str)); + } + + // Transmission + ret = SendHttpHeader(s, h); + + FreeHttpHeader(h); + + if (ret == false) + { + ret = PROXY_ERROR_DISCONNECTED; + goto FAILURE; + } + + if (*cancel_flag) + { + ret = PROXY_ERROR_CANCELED; + goto FAILURE; + } + + // Receive the results + h = RecvHttpHeader(s); + if (h == NULL) + { + FreeHttpHeader(h); + ret = PROXY_ERROR_GENERIC; + goto FAILURE; + } + + ret = 0; + if (StrLen(h->Method) == 8) + { + if (Cmp(h->Method, "HTTP/1.", 7) == 0) + { + ret = ToInt(h->Target); + } + } + FreeHttpHeader(h); + + // Check the code + switch (ret) + { + case 401: + case 403: + case 407: + // Authentication failure + ret = PROXY_ERROR_AUTHENTICATION; + goto FAILURE; + + default: + if ((ret / 100) == 2) + { + // Success + SetTimeout(s, INFINITE); + out->Sock = s; + return PROXY_ERROR_SUCCESS; + } + else + { + // Unknown result + ret = PROXY_ERROR_GENERIC; + } + } + +FAILURE: + Disconnect(s); + ReleaseSock(s); + return ret; +} + +// Connect to a SOCKS5 proxy (RFC1928, RFC1929 defines username/password authentication) +UINT ProxySocks5Connect(PROXY_PARAM_OUT *out, PROXY_PARAM_IN *in, volatile bool *cancel_flag) +{ + bool dummy_cancel_flag = false; + UCHAR tmp, recv_buf[2], *recv_buf_final; + USHORT target_port; + IP target_ip; + UINT ret; + SOCK *s; + BUF *b; + // Validate arguments + if (out == NULL || in == NULL || in->Port == 0 || in->TargetPort == 0 || IsEmptyStr(in->Hostname) || IsEmptyStr(in->TargetHostname)) + { + return PROXY_ERROR_PARAMETER; + } + + if (cancel_flag == NULL) + { + cancel_flag = &dummy_cancel_flag; + } + else if (*cancel_flag) + { + return PROXY_ERROR_CANCELED; + } + + Zero(out, sizeof(PROXY_PARAM_OUT)); + + // Open TCP connection to the proxy server + s = Internal_ProxyTcpConnect(in, cancel_flag, &out->ResolvedIp); + if (s == NULL) + { + return PROXY_ERROR_CONNECTION; + } + + SetTimeout(s, MIN(PROXY_CONNECTION_TIMEOUT, (in->Timeout == 0 ? INFINITE : in->Timeout))); + + // +----+----------+----------+ + // |VER | NMETHODS | METHODS | + // +----+----------+----------+ + // | 1 | 1 | 1 to 255 | + // +----+----------+----------+ + // + // X'00' NO AUTHENTICATION REQUIRED + // X'01' GSSAPI + // X'02' USERNAME/PASSWORD + // X'03' to X'7F' IANA ASSIGNED + // X'80' to X'FE' RESERVED FOR PRIVATE METHODS + // X'FF' NO ACCEPTABLE METHOD + + b = NewBuf(); + tmp = 5; + WriteBuf(b, &tmp, sizeof(tmp)); // SOCKS version + tmp = 2; + WriteBuf(b, &tmp, sizeof(tmp)); // Number of supported methods + tmp = 0; + WriteBuf(b, &tmp, sizeof(tmp)); // No authentication + tmp = 2; + WriteBuf(b, &tmp, sizeof(tmp)); // Username/password + + ret = SendAll(s, b->Buf, b->Size, false); + + if (ret == false) + { + FreeBuf(b); + Debug("ProxySocks5Connect(): [Phase 1] Failed to send initial data to the server.\n"); + ret = PROXY_ERROR_DISCONNECTED; + goto FAILURE; + } + + // +----+--------+ + // |VER | METHOD | + // +----+--------+ + // | 1 | 1 | + // +----+--------+ + + if (RecvAll(s, recv_buf, sizeof(recv_buf), false) == false) + { + FreeBuf(b); + Debug("ProxySocks5Connect(): [Phase 1] Failed to receive initial data response from the server.\n"); + ret = PROXY_ERROR_DISCONNECTED; + goto FAILURE; + } + + if (recv_buf[0] != 5) + { + FreeBuf(b); + Debug("ProxySocks5Connect(): [Phase 1] Unmatching version: %u.\n", recv_buf[0]); + ret = PROXY_ERROR_VERSION; + goto FAILURE; + } + + ClearBuf(b); + + // Username/password authentication (RFC1929) + if (recv_buf[1] == 2) + { + // +----+------+----------+------+----------+ + // |VER | ULEN | UNAME | PLEN | PASSWD | + // +----+------+----------+------+----------+ + // | 1 | 1 | 1 to 255 | 1 | 1 to 255 | + // +----+------+----------+------+----------+ + + tmp = 1; + WriteBuf(b, &tmp, sizeof(tmp)); // Authentication protocol version + tmp = StrLen(in->Username); + WriteBuf(b, &tmp, sizeof(tmp)); // Username length + WriteBuf(b, in->Username, tmp); // Username + tmp = StrLen(in->Password); + WriteBuf(b, &tmp, sizeof(tmp)); // Password length + WriteBuf(b, in->Password, tmp); // Password + + ret = SendAll(s, b->Buf, b->Size, false); + + ClearBuf(b); + + if (ret == false) + { + Debug("ProxySocks5Connect(): [Phase 1] Failed to send authentication data to the server.\n"); + ret = PROXY_ERROR_DISCONNECTED; + goto FAILURE; + } + + // +----+--------+ + // |VER | STATUS | + // +----+--------+ + // | 1 | 1 | + // +----+--------+ + + if (RecvAll(s, recv_buf, sizeof(recv_buf), false) == false) + { + Debug("ProxySocks5Connect(): [Phase 1] Failed to receive authentication data response from the server.\n"); + ret = PROXY_ERROR_DISCONNECTED; + goto FAILURE; + } + + if (recv_buf[1] != 0) + { + Debug("ProxySocks5Connect(): [Phase 1] Authentication failure, error code sent by the server: %u.\n", recv_buf[1]); + ret = PROXY_ERROR_AUTHENTICATION; + goto FAILURE; + } + } + + // +----+-----+-------+------+----------+----------+ + // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | + // +----+-----+-------+------+----------+----------+ + // | 1 | 1 | X'00' | 1 | Variable | 2 | + // +----+-----+-------+------+----------+----------+ + // + // VER protocol version: X'05' + // CMD + // CONNECT X'01' + // BIND X'02' + // UDP ASSOCIATE X'03' + // RSV RESERVED + // ATYP address type of following address + // IP V4 address X'01' + // DOMAINNAME X'03' + // IP V6 address X'04' + // DST.ADDR desired destination address + // DST.PORT desired destination port in network octet order + + // Prepare data to send + tmp = 5; + WriteBuf(b, &tmp, sizeof(tmp)); // SOCKS version + tmp = 1; + WriteBuf(b, &tmp, sizeof(tmp)); // Command + tmp = 0; + WriteBuf(b, &tmp, sizeof(tmp)); // Reserved byte + + // Convert the hostname to an IP structure (if it's an IP address) + StrToIP(&target_ip, in->TargetHostname); + + // If the IP structure doesn't contain an IP address, the string should be an hostname + if (IsZeroIp(&target_ip)) + { + UCHAR dest_length = StrLen(in->TargetHostname); + tmp = 3; + WriteBuf(b, &tmp, sizeof(tmp)); // Destination type (hostname) + WriteBuf(b, &dest_length, sizeof(dest_length)); // Destination hostname length + WriteBuf(b, in->TargetHostname, dest_length); // Destination hostname + } + else + { + if (IsIP6(&target_ip)) + { + tmp = 4; + WriteBuf(b, &tmp, sizeof(tmp)); // Destination type (IPv6) + WriteBuf(b, target_ip.ipv6_addr, sizeof(target_ip.ipv6_addr)); // Destination IPv6 address + } + else + { + tmp = 1; + WriteBuf(b, &tmp, sizeof(tmp)); // Destination type (IPv4) + WriteBuf(b, target_ip.addr, sizeof(target_ip.addr)); // Destination IPv4 address + } + } + + // Convert the port in network octet order + target_port = Endian16(in->TargetPort); + WriteBuf(b, &target_port, sizeof(target_port)); // Destination port + + // Send data + ret = SendAll(s, b->Buf, b->Size, false); + FreeBuf(b); + + if (ret == false) + { + Debug("ProxySocks5Connect(): [Phase 2] Failed to send data to the server.\n"); + ret = PROXY_ERROR_DISCONNECTED; + goto FAILURE; + } + + // +----+-----+-------+------+----------+----------+ + // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | + // +----+-----+-------+------+----------+----------+ + // | 1 | 1 | X’00’ | 1 | Variable | 2 | + // +----+-----+-------+------+----------+----------+ + // + // VER protocol version: X’05’ + // REP Reply field: + // X’00’ succeeded + // X’01’ general SOCKS server failure + // X’02’ connection not allowed by ruleset + // X’03’ Network unreachable + // X’04’ Host unreachable + // X’05’ Connection refused + // X’06’ TTL expired + // X’07’ Command not supported + // X’08’ Address type not supported + // X’09’ to X’FF’ unassigned + + // The packet sent by the server should always have the same size as the one we sent to it. + // However, there are some implementations which send fixed values (aside from the first 2 bytes). + // In order to support such implementations, we read the first 4 bytes in order to know the address type before trying to read the rest of the packet. + recv_buf_final = Malloc(4); + + if (RecvAll(s, recv_buf_final, 4, false) == false) + { + Free(recv_buf_final); + Debug("ProxySocks5Connect(): [Phase 2] Failed to receive response from the server.\n"); + ret = PROXY_ERROR_DISCONNECTED; + goto FAILURE; + } + + // We only need the first two bytes (version and response code), but we have to read the entire packet from the socket + recv_buf[0] = recv_buf_final[0]; + recv_buf[1] = recv_buf_final[1]; + + // We receive the rest of the packet by knowing the size according to the address type + switch (recv_buf_final[3]) + { + case 1: + // IPv4 + recv_buf_final = ReAlloc(recv_buf_final, 6); // 4 bytes (IPv4) + 2 bytes (port) + ret = RecvAll(s, recv_buf_final, 6, false); + break; + case 4: + // IPv6 + recv_buf_final = ReAlloc(recv_buf_final, 18); // 16 bytes (IPv6) + 2 bytes (port) + ret = RecvAll(s, recv_buf_final, 18, false); + break; + case 3: + // Hostname + ret = RecvAll(s, &tmp, 1, false); + if (ret == true) + { + recv_buf_final = ReAlloc(recv_buf_final, tmp + 2); // Hostname length + 2 bytes (port) + ret = RecvAll(s, recv_buf_final, tmp + 2, false); + } + } + + Free(recv_buf_final); + + if (ret == false) + { + Debug("ProxySocks5Connect(): [Phase 2] Malformed response received from the server.\n"); + ret = PROXY_ERROR_DISCONNECTED; + goto FAILURE; + } + + if (recv_buf[0] != 5) + { + Debug("ProxySocks5Connect(): [Phase 2] Unmatching version: %u.\n", recv_buf_final[0]); + ret = PROXY_ERROR_VERSION; + goto FAILURE; + } + + switch (recv_buf[1]) + { + case 0: + // Success + SetTimeout(s, INFINITE); + out->Sock = s; + return PROXY_ERROR_SUCCESS; + case 3: + case 4: + case 5: + Debug("ProxySocks5Connect(): [Phase 2] Connection to target failed with error: %u\n", recv_buf[1]); + ret = PROXY_ERROR_TARGET; + goto FAILURE; + default: + Debug("ProxySocks5Connect(): [Phase 2] Connection failed with error: %u\n", recv_buf[1]); + ret = PROXY_ERROR_GENERIC; + goto FAILURE; + } + +FAILURE: + Disconnect(s); + ReleaseSock(s); + return ret; +} + +// Connect to a SOCKS4 proxy +UINT ProxySocks4Connect(PROXY_PARAM_OUT *out, PROXY_PARAM_IN *in, volatile bool *cancel_flag) +{ + bool dummy_cancel_flag = false; + UCHAR tmp, recv_buf[8]; + USHORT target_port; + IP target_ip; + UINT ret; + SOCK *s; + BUF *b; + // Validate arguments + if (out == NULL || in == NULL || in->Port == 0 || in->TargetPort == 0 || IsEmptyStr(in->Hostname) || IsEmptyStr(in->TargetHostname)) + { + return PROXY_ERROR_PARAMETER; + } + + if (cancel_flag == NULL) + { + cancel_flag = &dummy_cancel_flag; + } + else if (*cancel_flag) + { + return PROXY_ERROR_CANCELED; + } + + Zero(out, sizeof(PROXY_PARAM_OUT)); + + // Get the IP address of the destination server + if (GetIP(&target_ip, in->TargetHostname) == false) + { + return PROXY_ERROR_CONNECTION; + } + + // Open TCP connection to the proxy server + s = Internal_ProxyTcpConnect(in, cancel_flag, &out->ResolvedIp); + if (s == NULL) + { + return PROXY_ERROR_CONNECTION; + } + + SetTimeout(s, MIN(PROXY_CONNECTION_TIMEOUT, (in->Timeout == 0 ? INFINITE : in->Timeout))); + + // Send request packet + b = NewBuf(); + tmp = 4; + WriteBuf(b, &tmp, sizeof(tmp)); + tmp = 1; + WriteBuf(b, &tmp, sizeof(tmp)); + target_port = Endian16(in->TargetPort); + WriteBuf(b, &target_port, sizeof(target_port)); + WriteBuf(b, target_ip.addr, sizeof(target_ip.addr)); + WriteBuf(b, in->Username, StrLen(in->Username) + 1); + + ret = SendAll(s, b->Buf, b->Size, false); + + FreeBuf(b); + + if (ret == false) + { + ret = PROXY_ERROR_DISCONNECTED; + goto FAILURE; + } + + // Receive response packet + if (RecvAll(s, recv_buf, sizeof(recv_buf), false) == false) + { + ret = PROXY_ERROR_DISCONNECTED; + goto FAILURE; + } + + if (recv_buf[0] != 0) + { + ret = PROXY_ERROR_GENERIC; + goto FAILURE; + } + + switch (recv_buf[1]) + { + case 90: + // Success + SetTimeout(s, INFINITE); + out->Sock = s; + return PROXY_ERROR_SUCCESS; + case 93: + // Authentication failure + ret = PROXY_ERROR_AUTHENTICATION; + goto FAILURE; + default: + // Failed to connect to the target server + ret = PROXY_ERROR_TARGET; + } + +FAILURE: + Disconnect(s); + ReleaseSock(s); + return ret; +} diff --git a/src/Mayaqua/Proxy.h b/src/Mayaqua/Proxy.h new file mode 100644 index 00000000..27b67c36 --- /dev/null +++ b/src/Mayaqua/Proxy.h @@ -0,0 +1,45 @@ +#ifndef PROXY_H +#define PROXY_H + +#define PROXY_CONNECTION_TIMEOUT (4 * 1000) + +#define PROXY_MAX_USERNAME_LEN 255 +#define PROXY_MAX_PASSWORD_LEN 255 + +#define PROXY_ERROR_SUCCESS 0 +#define PROXY_ERROR_GENERIC 1 +#define PROXY_ERROR_PARAMETER 2 +#define PROXY_ERROR_CANCELED 3 +#define PROXY_ERROR_CONNECTION 4 +#define PROXY_ERROR_DISCONNECTED 5 +#define PROXY_ERROR_VERSION 6 +#define PROXY_ERROR_AUTHENTICATION 7 +#define PROXY_ERROR_TARGET 8 + +struct PROXY_PARAM_IN +{ + char Hostname[MAX_HOST_NAME_LEN + 1]; + USHORT Port; + char TargetHostname[MAX_HOST_NAME_LEN + 1]; + USHORT TargetPort; + char Username[PROXY_MAX_USERNAME_LEN + 1]; + char Password[PROXY_MAX_PASSWORD_LEN + 1]; + UINT Timeout; + char HttpCustomHeader[HTTP_CUSTOM_HEADER_MAX_SIZE + 1]; + char HttpUserAgent[HTTP_HEADER_USER_AGENT_MAX_SIZE + 1]; +#ifdef OS_WIN32 + void *Hwnd; +#endif +}; + +struct PROXY_PARAM_OUT +{ + SOCK *Sock; + IP ResolvedIp; +}; + +UINT ProxyHttpConnect(PROXY_PARAM_OUT *out, PROXY_PARAM_IN *in, volatile bool *cancel_flag); +UINT ProxySocks5Connect(PROXY_PARAM_OUT *out, PROXY_PARAM_IN *in, volatile bool *cancel_flag); +UINT ProxySocks4Connect(PROXY_PARAM_OUT *out, PROXY_PARAM_IN *in, volatile bool *cancel_flag); + +#endif From 59dc26aa21e1252b04b93529cc845d0327d5d86c Mon Sep 17 00:00:00 2001 From: Davide Beatrici Date: Tue, 29 Oct 2019 07:40:11 +0100 Subject: [PATCH 2/4] Protocol: add ProxyCodeToCedar() This new function translates a proxy error code to a Cedar error code. --- src/Cedar/Protocol.c | 24 ++++++++++++++++++++++++ src/Cedar/Protocol.h | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/Cedar/Protocol.c b/src/Cedar/Protocol.c index c458bbac..e89c06a7 100644 --- a/src/Cedar/Protocol.c +++ b/src/Cedar/Protocol.c @@ -6185,6 +6185,30 @@ SOCK *ClientConnectGetSocket(CONNECTION *c, bool additional_connect) return s; } +UINT ProxyCodeToCedar(UINT code) +{ + switch (code) + { + case PROXY_ERROR_SUCCESS: + return ERR_NO_ERROR; + case PROXY_ERROR_GENERIC: + case PROXY_ERROR_VERSION: + return ERR_PROXY_ERROR; + case PROXY_ERROR_CANCELED: + return ERR_USER_CANCEL; + case PROXY_ERROR_CONNECTION: + return ERR_PROXY_CONNECT_FAILED; + case PROXY_ERROR_TARGET: + return ERR_CONNECT_FAILED; + case PROXY_ERROR_DISCONNECTED: + return ERR_DISCONNECTED; + case PROXY_ERROR_AUTHENTICATION: + return ERR_PROXY_AUTH_FAILED; + default: + return ERR_INTERNAL_ERROR; + } +} + // TCP connection function SOCK *TcpConnectEx3(char *hostname, UINT port, UINT timeout, bool *cancel_flag, void *hWnd, bool no_nat_t, UINT *nat_t_error_code, bool try_start_ssl, IP *ret_ip) { diff --git a/src/Cedar/Protocol.h b/src/Cedar/Protocol.h index 03f6ca17..260a6b4e 100644 --- a/src/Cedar/Protocol.h +++ b/src/Cedar/Protocol.h @@ -120,11 +120,11 @@ bool ClientUploadAuth(CONNECTION *c); SOCK *ClientConnectGetSocket(CONNECTION *c, bool additional_connect); SOCK *TcpConnectEx3(char *hostname, UINT port, UINT timeout, bool *cancel_flag, void *hWnd, bool no_nat_t, UINT *nat_t_error_code, bool try_start_ssl, IP *ret_ip); +UINT ProxyCodeToCedar(UINT code); + void InitProtocol(); void FreeProtocol(); - - POLICY *PackGetPolicy(PACK *p); void PackAddPolicy(PACK *p, POLICY *y); PACK *PackWelcome(SESSION *s); From 3c21d982fc15146613c784808cb239e2fe3811db Mon Sep 17 00:00:00 2001 From: Davide Beatrici Date: Tue, 29 Oct 2019 04:47:43 +0100 Subject: [PATCH 3/4] Wpc.c: adapt WpcSockConnectEx() for new proxy functions --- src/Cedar/Wpc.c | 101 ++++++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 46 deletions(-) diff --git a/src/Cedar/Wpc.c b/src/Cedar/Wpc.c index 22faaf5e..7b608562 100644 --- a/src/Cedar/Wpc.c +++ b/src/Cedar/Wpc.c @@ -500,64 +500,73 @@ SOCK *WpcSockConnect(WPC_CONNECT *param, UINT *error_code, UINT timeout) } SOCK *WpcSockConnectEx(WPC_CONNECT *param, UINT *error_code, UINT timeout, bool *cancel) { - CONNECTION c; SOCK *sock; - UINT err = ERR_NO_ERROR; + UINT ret; // Validate arguments if (param == NULL) { return NULL; } - Zero(&c, sizeof(c)); - - sock = NULL; - err = ERR_INTERNAL_ERROR; - - switch (param->ProxyType) + if (error_code == NULL) + { + error_code = &ret; + } + + if (param->ProxyType == PROXY_DIRECT) { - case PROXY_DIRECT: sock = TcpConnectEx3(param->HostName, param->Port, timeout, cancel, NULL, true, NULL, false, NULL); - if (sock == NULL) - { - err = ERR_CONNECT_FAILED; - } - break; - - case PROXY_HTTP: - sock = ProxyConnectEx3(&c, param, false, cancel, NULL, timeout); - if (sock == NULL) - { - err = c.Err; - } - break; - - case PROXY_SOCKS: - // SOCKS4 connection - sock = SocksConnectEx2(&c, param->ProxyHostName, param->ProxyPort, - param->HostName, param->Port, - param->ProxyUsername, false, cancel, NULL, timeout, NULL); - if (sock == NULL) - { - err = c.Err; - } - break; - - case PROXY_SOCKS5: - // SOCKS5 connection - sock = Socks5Connect(&c, param, false, cancel, NULL, timeout, NULL); - if (sock == NULL) - { - err = c.Err; - } + *error_code = (sock != NULL ? ERR_NO_ERROR : ERR_CONNECT_FAILED); + return sock; } - - if (error_code != NULL) + else { - *error_code = err; - } + PROXY_PARAM_OUT out; + PROXY_PARAM_IN in; + UINT ret; - return sock; + Zero(&in, sizeof(in)); + + in.Timeout = timeout; + + StrCpy(in.TargetHostname, sizeof(in.TargetHostname), param->HostName); + in.TargetPort = param->Port; + + StrCpy(in.Hostname, sizeof(in.Hostname), param->ProxyHostName); + in.Port = param->ProxyPort; + + StrCpy(in.Username, sizeof(in.Username), param->ProxyUsername); + StrCpy(in.Password, sizeof(in.Password), param->ProxyPassword); + + StrCpy(in.HttpCustomHeader, sizeof(in.HttpCustomHeader), param->CustomHttpHeader); + + switch (param->ProxyType) + { + case PROXY_HTTP: + ret = ProxyHttpConnect(&out, &in, cancel); + break; + case PROXY_SOCKS: + ret = ProxySocks4Connect(&out, &in, cancel); + break; + case PROXY_SOCKS5: + ret = ProxySocks5Connect(&out, &in, cancel); + break; + default: + *error_code = ERR_INTERNAL_ERROR; + Debug("WpcSockConnect(): Unknown proxy type: %u!\n", param->ProxyType); + return NULL; + } + + *error_code = ProxyCodeToCedar(ret); + + if (*error_code != ERR_NO_ERROR) + { + Debug("ClientConnectGetSocket(): Connection via proxy server failed with error %u\n", ret); + return NULL; + } + + return out.Sock; + } } SOCK *WpcSockConnect2(char *hostname, UINT port, INTERNET_SETTING *t, UINT *error_code, UINT timeout) { From 63caa4b07f4771698ebd5021a9c674957a95d2d6 Mon Sep 17 00:00:00 2001 From: Davide Beatrici Date: Tue, 29 Oct 2019 04:50:32 +0100 Subject: [PATCH 4/4] Protocol.c: adapt ClientConnectGetSocket() for new proxy functions The function has been greatly improved, here are some of the changes: - The required SESSION (c->Session) parameter is checked correctly: the function returns immediately in case it's NULL. Previously, the function didn't return in case the parameter was NULL; multiple checks were in place, but not in all instances where the parameter was dereferenced. - The resolved IP address is cached with all proxy types. - The "RestoreServerNameAndPort" variable is documented. - The Debug() messages have been improved. --- src/Cedar/Protocol.c | 247 ++++++++++++++++++++----------------------- 1 file changed, 116 insertions(+), 131 deletions(-) diff --git a/src/Cedar/Protocol.c b/src/Cedar/Protocol.c index e89c06a7..b1498441 100644 --- a/src/Cedar/Protocol.c +++ b/src/Cedar/Protocol.c @@ -5996,104 +5996,89 @@ SOCK *ClientConnectToServer(CONNECTION *c) // Return a socket by connecting to the server SOCK *ClientConnectGetSocket(CONNECTION *c, bool additional_connect) { - SOCK *s = NULL; - CLIENT_OPTION *o; - WPC_CONNECT w; - wchar_t tmp[MAX_SIZE]; - SESSION *sess; volatile bool *cancel_flag = NULL; - void *hWnd; - UINT nat_t_err = 0; - bool is_additional_rudp_session = false; - UCHAR uc = 0; - IP ret_ip; + char hostname[MAX_HOST_NAME_LEN]; + bool save_resolved_ip = false; + CLIENT_OPTION *o; + SESSION *sess; + SOCK *sock = NULL; + IP resolved_ip; // Validate arguments - if (c == NULL) + if (c == NULL || c->Session == NULL || c->Session->ClientOption == NULL) { return NULL; } - Zero(&ret_ip, sizeof(IP)); - Zero(&w, sizeof(w)); - + cancel_flag = &c->Halt; sess = c->Session; - - if (sess != NULL) - { - cancel_flag = &sess->CancelConnect; - is_additional_rudp_session = sess->IsRUDPSession; - } - - hWnd = c->hWndForUI; - o = c->Session->ClientOption; - if (additional_connect) - { - if (sess != NULL) - { - Copy(&ret_ip, &sess->ServerIP_CacheForNextConnect, sizeof(IP)); - } - } + Zero(&resolved_ip, sizeof(resolved_ip)); - if (c->RestoreServerNameAndPort && additional_connect) + if (additional_connect == false && c->RestoreServerNameAndPort) { - // Restore to the original server name and port number + // Update server name and port number. + // At the time of writing this comment RestoreServerNameAndPort is never true. c->RestoreServerNameAndPort = false; if (StrCmpi(c->ServerName, o->Hostname) != 0) { StrCpy(c->ServerName, sizeof(c->ServerName), o->Hostname); - Zero(&ret_ip, sizeof(IP)); } c->ServerPort = o->Port; } - StrCpy(w.HostName, sizeof(w.HostName), c->ServerName); - w.Port = c->ServerPort; - StrCpy(w.ProxyHostName, sizeof(w.ProxyHostName), o->ProxyName); - w.ProxyPort = o->ProxyPort; - StrCpy(w.ProxyUsername, sizeof(w.ProxyUsername), o->ProxyUsername); - StrCpy(w.ProxyPassword, sizeof(w.ProxyPassword), o->ProxyPassword); - StrCpy(w.CustomHttpHeader, sizeof(w.CustomHttpHeader), o->CustomHttpHeader); - - switch (o->ProxyType) + if (IsZeroIP(&sess->ServerIP_CacheForNextConnect) == false) { - case PROXY_DIRECT: // TCP/IP - UniFormat(tmp, sizeof(tmp), _UU("STATUS_4"), w.HostName); + IPToStr(hostname, sizeof(hostname), &sess->ServerIP_CacheForNextConnect); + Debug("ClientConnectGetSocket(): Using cached IP address %s\n", hostname); + } + else + { + IP tmp; + + StrCpy(hostname, sizeof(hostname), o->ProxyType == PROXY_DIRECT ? c->ServerName : o->ProxyName); + + if (StrToIP(&tmp, hostname) == false) + { + // The hostname is not an IP address + save_resolved_ip = true; + } + } + + if (o->ProxyType == PROXY_DIRECT) + { + UINT nat_t_err = 0; + wchar_t tmp[MAX_SIZE]; + UniFormat(tmp, sizeof(tmp), _UU("STATUS_4"), hostname); PrintStatus(sess, tmp); - // Production job if (o->PortUDP == 0) { - { - // If additional_connect == false, enable trying to NAT-T connection - // If additional_connect == true, follow the IsRUDPSession setting in this session - s = TcpIpConnectEx(w.HostName, w.Port, - (bool *)cancel_flag, hWnd, &nat_t_err, (additional_connect ? (!is_additional_rudp_session) : false), - true, &ret_ip); - } + // If additional_connect == false, enable trying to NAT-T connection + // If additional_connect == true, follow the IsRUDPSession setting in this session + sock = TcpIpConnectEx(hostname, c->ServerPort, + (bool *)cancel_flag, c->hWndForUI, &nat_t_err, (additional_connect ? (!sess->IsRUDPSession) : false), + true, &resolved_ip); } else { // Mode to connect with R-UDP directly without using NAT-T server when using UDP IP ip; - - Zero(&ip, sizeof(ip)); - - StrToIP(&ip, o->Hostname); - - - s = NewRUDPClientDirect(VPN_RUDP_SVC_NAME, &ip, o->PortUDP, &nat_t_err, - TIMEOUT_TCP_PORT_CHECK, (bool *)cancel_flag, NULL, NULL, 0, false); - - if (s != NULL) + if (StrToIP(&ip, hostname)) { - StrCpy(s->UnderlayProtocol, sizeof(s->UnderlayProtocol), SOCK_UNDERLAY_NAT_T); + sock = NewRUDPClientDirect(VPN_RUDP_SVC_NAME, &ip, o->PortUDP, &nat_t_err, + TIMEOUT_TCP_PORT_CHECK, (bool *)cancel_flag, NULL, NULL, 0, false); + + if (sock != NULL) + { + StrCpy(sock->UnderlayProtocol, sizeof(sock->UnderlayProtocol), SOCK_UNDERLAY_NAT_T); + } } } - if (s == NULL) + + if (sock == NULL) { // Connection failure if (nat_t_err != RUDP_ERROR_NAT_T_TWO_OR_MORE) @@ -6104,85 +6089,85 @@ SOCK *ClientConnectGetSocket(CONNECTION *c, bool additional_connect) { c->Err = ERR_NAT_T_TWO_OR_MORE; } + return NULL; } - break; - - case PROXY_HTTP: // HTTP Proxy - UniFormat(tmp, sizeof(tmp), _UU("STATUS_2"), w.HostName, w.ProxyHostName); - PrintStatus(sess, tmp); - - // Proxy connection - s = ProxyConnectEx3(c, &w, additional_connect, (bool *)cancel_flag, hWnd, 0); - if (s == NULL) - { - // Connection failure - return NULL; - } - break; - - case PROXY_SOCKS: // SOCKS4 Proxy - UniFormat(tmp, sizeof(tmp), _UU("STATUS_2"), w.HostName, w.ProxyHostName); - PrintStatus(sess, tmp); - - // SOCKS4 connection - s = SocksConnectEx2(c, w.ProxyHostName, w.ProxyPort, - w.HostName, w.Port, w.ProxyUsername, additional_connect, (bool *)cancel_flag, - hWnd, 0, &ret_ip); - if (s == NULL) - { - // Connection failure - return NULL; - } - break; - - case PROXY_SOCKS5: // SOCKS5 Proxy - UniFormat(tmp, sizeof(tmp), _UU("STATUS_2"), w.HostName, w.ProxyHostName); - PrintStatus(sess, tmp); - - // SOCKS5 connection - s = Socks5Connect(c, &w, additional_connect, (bool *)cancel_flag, hWnd, 0, &ret_ip); - if (s == NULL) - { - // Connection failure - return NULL; - } - break; - } - - if (s == NULL) - { - // Connection failure - c->Err = ERR_CONNECT_FAILED; } else { - // Success to connect - // Keep a note of the IP address - if (additional_connect == false || IsZeroIP(&s->RemoteIP)) + wchar_t tmp[MAX_SIZE]; + PROXY_PARAM_OUT out; + PROXY_PARAM_IN in; + UINT ret; + + Zero(&in, sizeof(in)); + + in.Timeout = 0; + + StrCpy(in.TargetHostname, sizeof(in.TargetHostname), c->ServerName); + in.TargetPort = c->ServerPort; + + StrCpy(in.Hostname, sizeof(in.Hostname), IsEmptyStr(hostname) ? o->ProxyName : hostname); + in.Port = o->ProxyPort; + + StrCpy(in.Username, sizeof(in.Username), o->ProxyUsername); + StrCpy(in.Password, sizeof(in.Password), o->ProxyPassword); + + StrCpy(in.HttpCustomHeader, sizeof(in.HttpCustomHeader), o->CustomHttpHeader); + StrCpy(in.HttpUserAgent, sizeof(in.HttpUserAgent), c->Cedar->HttpUserAgent); + +#ifdef OS_WIN32 + in.Hwnd = c->hWndForUI; +#endif + + UniFormat(tmp, sizeof(tmp), _UU("STATUS_2"), in.TargetHostname, in.Hostname); + PrintStatus(sess, tmp); + + switch (o->ProxyType) { - char *hostname = o->ProxyType == PROXY_DIRECT ? w.HostName : w.ProxyHostName; - if (((s->IsRUDPSocket || s->IPv6) && IsZeroIP(&s->RemoteIP) == false && o->ProxyType == PROXY_DIRECT) || GetIP(&c->Session->ServerIP, hostname) == false) - { - Copy(&c->Session->ServerIP, &s->RemoteIP, sizeof(IP)); - } + case PROXY_HTTP: + ret = ProxyHttpConnect(&out, &in, cancel_flag); + break; + case PROXY_SOCKS: + ret = ProxySocks4Connect(&out, &in, cancel_flag); + break; + case PROXY_SOCKS5: + ret = ProxySocks5Connect(&out, &in, cancel_flag); + break; + default: + c->Err = ERR_INTERNAL_ERROR; + Debug("ClientConnectGetSocket(): Unknown proxy type: %u!\n", o->ProxyType); + return NULL; } - if (IsZeroIP(&ret_ip) == false) - { - if (c->Session != NULL) - { - if (additional_connect == false) - { - Copy(&c->Session->ServerIP_CacheForNextConnect, &ret_ip, sizeof(IP)); + c->Err = ProxyCodeToCedar(ret); - Debug("Saved ServerIP_CacheForNextConnect: %s = %r\n", c->ServerName, &ret_ip); - } - } + if (c->Err != ERR_NO_ERROR) + { + Debug("ClientConnectGetSocket(): Connection via proxy server failed with error %u\n", ret); + return NULL; + } + + sock = out.Sock; + + CopyIP(&resolved_ip, &out.ResolvedIp); + } + + if (additional_connect == false || IsZeroIP(&sock->RemoteIP)) + { + if (((sock->IsRUDPSocket || sock->IPv6) && IsZeroIP(&sock->RemoteIP) == false && o->ProxyType == PROXY_DIRECT) || GetIP(&c->Session->ServerIP, hostname) == false) + { + Copy(&c->Session->ServerIP, &sock->RemoteIP, sizeof(c->Session->ServerIP)); } } - return s; + if (save_resolved_ip && IsZeroIP(&resolved_ip) == false) + { + Copy(&c->Session->ServerIP_CacheForNextConnect, &resolved_ip, sizeof(c->Session->ServerIP_CacheForNextConnect)); + Debug("ClientConnectGetSocket(): Saved %s IP address %r for future connections.\n", hostname, &resolved_ip); + } + + return sock; } UINT ProxyCodeToCedar(UINT code)