1
0
mirror of https://github.com/SoftEtherVPN/SoftEtherVPN.git synced 2024-11-22 17:39:53 +03:00

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.
This commit is contained in:
Davide Beatrici 2019-10-30 01:39:04 +01:00
parent 1e835e7ec2
commit b8f58a2f94
10 changed files with 687 additions and 763 deletions

View File

@ -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

View File

@ -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

View File

@ -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 | X00 | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// VER protocol version: X05
// REP Reply field:
// X00 succeeded
// X01 general SOCKS server failure
// X02 connection not allowed by ruleset
// X03 Network unreachable
// X04 Host unreachable
// X05 Connection refused
// X06 TTL expired
// X07 Command not supported
// X08 Address type not supported
// X09 to XFF 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)
{

View File

@ -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);

View File

@ -21,6 +21,8 @@
#define HTTP_HEADER_LINE_MAX_SIZE 4096
// Maximum number of lines in the HTTP header
#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
// Maximum PACK size in the HTTP

View File

@ -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

View File

@ -277,6 +277,9 @@ int iconv_close (iconv_t __cd);
// HTTP
#include <Mayaqua/HTTP.h>
// Proxy
#include <Mayaqua/Proxy.h>
// 64 bit real-time clock
#include <Mayaqua/Tick64.h>

View File

@ -366,6 +366,10 @@
RelativePath=".\Pack.c"
>
</File>
<File
RelativePath=".\Proxy.c"
>
</File>
<File
RelativePath=".\Secure.c"
>
@ -464,6 +468,10 @@
RelativePath=".\Pack.h"
>
</File>
<File
RelativePath=".\Proxy.h"
>
</File>
<File
RelativePath=".\pkcs11.h"
>

618
src/Mayaqua/Proxy.c Normal file
View File

@ -0,0 +1,618 @@
#include <GlobalConst.h>
#include <Mayaqua/Mayaqua.h>
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 | X00 | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// VER protocol version: X05
// REP Reply field:
// X00 succeeded
// X01 general SOCKS server failure
// X02 connection not allowed by ruleset
// X03 Network unreachable
// X04 Host unreachable
// X05 Connection refused
// X06 TTL expired
// X07 Command not supported
// X08 Address type not supported
// X09 to XFF 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;
}

45
src/Mayaqua/Proxy.h Normal file
View File

@ -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