1
0
mirror of https://github.com/SoftEtherVPN/SoftEtherVPN.git synced 2024-10-06 10:30:40 +03:00
SoftEtherVPN/src/Mayaqua/Proxy.c

623 lines
15 KiB
C
Raw Normal View History

2021-04-05 05:48:25 +03:00
#include "Proxy.h"
2021-04-05 05:48:25 +03:00
// TODO: Mayaqua should not depend on Cedar.
#include "Cedar/WinUi.h"
#include "Memory.h"
#include "Str.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.address, sizeof(target_ip.address)); // Destination IPv6 address
}
else
{
tmp = 1;
WriteBuf(b, &tmp, sizeof(tmp)); // Destination type (IPv4)
WriteBuf(b, IPV4(target_ip.address), IPV4_SIZE); // 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, IPV4(target_ip.address), IPV4_SIZE);
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;
}