1
0
mirror of https://github.com/SoftEtherVPN/SoftEtherVPN.git synced 2024-10-06 18:40:39 +03:00
SoftEtherVPN/src/Mayaqua/Proxy.c
Davide Beatrici 0472f9c286 Rewrite DNS API from scratch into dedicated file(s)
From a functional point of view, the main improvement is that GetIP() now always prioritizes IPv6 over IPv4.
The previous implementation always returned an IPv4 address, unless not available: in such case it failed.
This means that now connections to hostnames should be established via IPv6 if available.

From a programmer point of view, getting rid of the insane wrappers is enough to justify a complete rewrite.

As an extra, several unrelated unused global variables are removed.
2021-04-18 01:46:59 +02:00

624 lines
15 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "Proxy.h"
// TODO: Mayaqua should not depend on Cedar.
#include "Cedar/WinUi.h"
#include "DNS.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 IPv4 address of the destination server (SOCKS4 does not support IPv6).
if (GetIP4(&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;
}