
// CLIENT_SK5_V2

std::string hex_string(std::string hexstr)
{
	std::string str = "";
	str.resize((hexstr.size() + 1) / 2);
	for (size_t i = 0, j = 0; i < str.size(); i++, j++)
	{
		char at = '@';
		str[i] = (hexstr[j] & at ? hexstr[j] + 9 : hexstr[j]) << 4, j++;
		str[i] |= (hexstr[j] & at ? hexstr[j] + 9 : hexstr[j]) & 0xF;
	}
	return str;
}

std::string string_hex(std::string str, const bool capital = false)
{
	std::string hexstr = "";
	hexstr.resize(str.size() * 2);
	static const char a = capital ? 0x40 : 0x60;
	for (size_t i = 0; i < str.size(); i++)
	{
		char c = (str[i] >> 4) & 0xF;
		hexstr[i * 2] = c > 9 ? (c - 9) | a : c | '0';
		hexstr[i * 2 + 1] = (str[i] & 0xF) > 9 ? (str[i] - 9) & 0xF | a : str[i] & 0xF | '0';
	}
	return hexstr;
}

std::string char_to_string(char x[], int size_recv)
{
	int num_car = 0;
	int stop_while = size_recv;
	std::string output = "";

	while (stop_while > 0)
	{
		output += x[num_car];
		if (num_car < size_recv) { num_car++; }

		stop_while--;
	}

	return output;
}

long int hex_to_dec(std::string x)
{
	return strtol(x.data(), NULL, 16);
}

typedef struct
{
	SOCKET x;
	SOCKET y;
	BOOL VERBOSE_mode;
	int timeout_sec;
	int buf_size;
} threaddata;

void set_fds(int sock1, int sock2, fd_set* fds) {
	FD_ZERO(fds);
	FD_SET(sock1, fds);
	FD_SET(sock2, fds);
}

// MSG SOCKS5
char msg_auth_ok[] = { 0X05, 0X00 }; // VERSION SOCKS, AUTH MODE, OK
char msg_ipv6_nok[] = { 0X05, 0X08, 0X00, 0X01, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 }; // IPv6 not compt
char msg_request_co_ok[] = { 0X05, 0X00, 0X00, 0X01, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 }; // Request connect OK

DWORD WINAPI sock5_gen(void* param)
{
	threaddata* sub = (threaddata*)param;

	SOCKET sock_SK5 = sub->x;
	BOOL VERBOSE_mode = sub->VERBOSE_mode;
	int timeout_sec = sub->timeout_sec;
	int buf_size = sub->buf_size;

	char buf[5016]; // If changed, line 25 in main.cpp is a change too
	int version_sock = 0; // 5, 4, 0, ??

	// Phase A = Version Socks, Send Msg Auth
	memset(buf, 0, sizeof(buf));

	// Get Version Socks
	int size_recv_PA = recv(sock_SK5, buf, sizeof(buf), 0);

	if (size_recv_PA <= 0)
	{
		printf(EC("[!] size_recv_PA <= 0\n"));
		closesocket(sock_SK5);
		return 1;
	}

	version_sock = buf[0];
	if (version_sock != 5)
	{
		printf(EC("[!] version_sock != 5\n"));
		closesocket(sock_SK5);
		return 1;
	}

	// Send msg_auth_ok

	int size_send_PA = send(sock_SK5, msg_auth_ok, sizeof(msg_auth_ok), 0);

	if (size_send_PA <= 0)
	{
		printf(EC("[!] size_send_PA <= 0\n"));
		closesocket(sock_SK5);
		return 1;
	}

	// Phase B = Mode Connect & Type Addr & Addr/Port
	memset(buf, 0, sizeof(buf));

	int size_recv_PB = recv(sock_SK5, buf, sizeof(buf), 0);

	if (size_recv_PB <= 0)
	{
		printf(EC("[!] size_recv_PB <= 0\n"));
		closesocket(sock_SK5);
		return 1;
	}

	int mode_cmd = buf[1]; // Connect = 1, Bind = 2, UDP = 3
	int type_addr = buf[3]; // IPv4 = 01, Domain Name = 03, IPv6 = 04

	if (mode_cmd != 1)
	{
		printf(EC("[!] Bind/UDP not supported\n"));
		closesocket(sock_SK5);
		return 1;
	}

	std::string dest_ip = ""; // IP dest
	int dest_port = 0; // Port dest

	if (type_addr == 1) // IPv4
	{
		std::string raw_demand = char_to_string(buf, size_recv_PB); // convert to string
		std::string str_ip_dest = raw_demand.substr(4, 11); // get IPv4
		std::string str_port_dest = raw_demand.substr(8, 10); // get Port

		sockaddr_in svr = { 0 };
		svr.sin_family = AF_INET;
		svr.sin_addr.s_addr = MAKELONG(MAKEWORD((buf[4] & 0xff), (buf[5] & 0xff)), MAKEWORD((buf[6] & 0xff), (buf[7] & 0xff)));

		dest_ip = inet_ntoa(svr.sin_addr); // IPv4 dest in string
		dest_port = hex_to_dec(string_hex(str_port_dest)); // port dest in long
	}

	if (type_addr == 3) // Domain Name
	{
		std::string raw_demand = char_to_string(buf, size_recv_PB); // convert to string
		int size_domain_name = buf[4]; // Get size Domain Name
		std::string str_domain_name;
		std::string str_port_dest;

		try
		{
			str_domain_name = raw_demand.substr(5, size_domain_name); // get domain_name
			str_port_dest = raw_demand.substr(5 + size_domain_name, 5 + size_domain_name + 2); // get Port
			dest_port = hex_to_dec(string_hex(str_port_dest)); // port dest in long
		}
		catch (const std::out_of_range& e)
		{
			printf(EC("[!] Out of Range\n"));
		}

		struct hostent* he_a;
		he_a = gethostbyname(str_domain_name.c_str());
		if (he_a == NULL) // Error gethostbyname
		{
			printf(EC("[!] gethostbyname\n"));
			closesocket(sock_SK5);
			return 12;
		}
		else // Ok gethostbyname
		{
			dest_ip = inet_ntoa(*((struct in_addr*)he_a->h_addr_list[0]));
		}
	}

	if (type_addr == 4) // IPv6
	{
		printf(EC("[!] IPv6 not supported\n"));

		int size_send_IPNOK = send(sock_SK5, msg_ipv6_nok, sizeof(msg_ipv6_nok), 0);

		if (size_send_IPNOK <= 0) // Send msg_ipv6_nok
		{
			closesocket(sock_SK5);
			return 1;
		}
		closesocket(sock_SK5);
		return 1;
	}

	// Phase C = Send Request Addr OK & Recv Request & Connect to TARGET SERVER
	int size_send_PC = send(sock_SK5, msg_request_co_ok, sizeof(msg_request_co_ok), 0);

	if (size_send_PC <= 0) // Send msg_request_co_ok
	{
		printf(EC("[!] size_send_PC <= 0\n"));
		closesocket(sock_SK5);
		return 1;
	}

	memset(buf, 0, sizeof(buf));

	int size_recv_PC = recv(sock_SK5, buf, sizeof(buf), 0);

	if (size_recv_PC <= 0)
	{
		printf(EC("[!] size_recv_PC <= 0\n"));
		closesocket(sock_SK5);
		return 1;
	}

	SOCKET client_TARGET; // Socket Connection for TARGET
	struct sockaddr_in serveraddra_a;

	if ((client_TARGET = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) //open socket
		printf(EC("[!] socket() failed\n"));

	//connect
	memset(&serveraddra_a, 0, sizeof(serveraddra_a));
	serveraddra_a.sin_family = AF_INET;
	serveraddra_a.sin_addr.s_addr = inet_addr(dest_ip.c_str());
	serveraddra_a.sin_port = htons((unsigned short)dest_port);

	if (connect(client_TARGET, (struct sockaddr*)&serveraddra_a, sizeof(serveraddra_a)) == SOCKET_ERROR)
	{
		printf(EC("[!] connect() failed\n"));
		closesocket(sock_SK5);
		return 1;
	}

	if (send(client_TARGET, buf, size_recv_PC, 0) != size_recv_PC)
		printf(EC("[!] send() sent a different number of bytes than expected\n"));

	fd_set readfds;
	int result, nfds = max(sock_SK5, client_TARGET) + 1;
	set_fds(sock_SK5, client_TARGET, &readfds);

	// Set timeout for select
	timeval tv;
	tv.tv_sec = timeout_sec;
	tv.tv_usec = 0;

	memset(buf, 0, sizeof(buf));

	while (TRUE)  // SK5_BASE <-> SERVER_TARGET
	{
		if ((result = select(nfds, &readfds, 0, 0, &tv)) > 0)
		{
			if (FD_ISSET(sock_SK5, &readfds)) // SK5_BASE -> SERVER_TARGET
			{
				int recvd_A = 0;

				recvd_A = recv(sock_SK5, buf, buf_size, 0);

				if (recvd_A <= 0)
				{
					printf(EC("[!] recvd_A (%i) <= 0\n"), recvd_A);
					closesocket(sock_SK5);
					return 1;
				}

				int sendd_A = 0;
				send(client_TARGET, buf, recvd_A, 0); // SERVER_TARGET

				if (VERBOSE_mode) printf(EC("[+] A DATA RECV = %i | DATA SEND = %i\n"), recvd_A, sendd_A);
			}

			if (FD_ISSET(client_TARGET, &readfds))  // SERVER_TARGET -> SK5_BASE
			{
				int recvd_B = recv(client_TARGET, buf, buf_size, 0);
				if (recvd_B <= 0)
				{
					printf(EC("[!] recvd_B (%i) <= 0\n"), recvd_B);
					closesocket(sock_SK5);
					return 1;
				}

				int sendd_B = 0;

				sendd_B = send(sock_SK5, buf, recvd_B, 0);

				if (VERBOSE_mode) printf(EC("[+] B DATA RECV = %i | DATA SEND = %i\n"), recvd_B, sendd_B);
			}
			set_fds(sock_SK5, client_TARGET, &readfds);
		}
		else
		{
			printf(EC("[!] select > 0\n"));
			shutdown(client_TARGET, 2);
			closesocket(client_TARGET);
			closesocket(sock_SK5);

			return 12;
		}

	}

	shutdown(client_TARGET, 2);
	closesocket(client_TARGET);
	closesocket(sock_SK5);

	return 0;
}

static int create_sock_thread(void* s)
{
	HANDLE handle;

	if (!(handle = CreateThread(NULL, 0, sock5_gen, s, 0, 0)))
		return 0;

	CloseHandle(handle);
	return 1;
}