/*
* Implements a minimal subset of RFC 1928. Will listen to standard or
* given port or 1080 and handles requests. Clients are handled in
* separate threads. No authentication (only method 0), only TCP 4,
* only connect requests.
*/
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <iomanip>
#include <string>
#include <memory>
#include <algorithm>
#include <thread>
#include <utility>
#include <cleansocks.h>
#include <cleanip.h>
using namespace cleansocks;
/*
* We need to use failure codes from socket to determine the response
* code to the client. Unfortunately, these are system-dependent.
* So we have two versions to map the codes to socks error responses.
*/
#ifdef WIN32
unsigned char errmap(int err)
{
switch(err)
{
case WSAECONNREFUSED:
return 5;
case WSAEHOSTUNREACH:
case WSAEHOSTDOWN:
return 4;
case WSAENETDOWN:
case WSAENETUNREACH:
return 3;
default:
return 1;
}
}
#else
unsigned char errmap(int err)
{
switch(err)
{
case ECONNREFUSED:
return 5;
case ENONET:
case EHOSTDOWN:
case EHOSTUNREACH:
return 4;
case ENETUNREACH:
return 3;
default:
return 1;
}
}
#endif
/*
* Holds a variable field result. I should probably just use a vector, but
* that seems like such overkill. Can't use a template array since the
* size varies, and don't want use a native dynamic since it doesn't know
* its size.
*/
struct vf {
// Apparently, the mingw that comes with cleansocks on Windows is too
// old to have std::make_unique.
//vf(int s): size(s), data(std::make_unique<unsigned char[]>(s)) { }
vf(int s): size(s), data
(std::unique_ptr<unsigned char[]>(new unsigned char[s])) { }
int size;
std::unique_ptr<unsigned char[]> data;
unsigned char * begin() { return data.get(); }
unsigned char * end() { return begin() + size; }
};
/*
* Read a variable-length field. Read the length byte, then the specified
* amount of data. Returns the result wrapped in a struct vf (see just above).
*/
vf readvar(TCPsocket from)
{
unsigned char size = 0;
recv(from, &size, 1);
vf ret(size);
recv(from, ret.begin(), ret.size, cleansocks::CSMSG_WAITALL);
return ret;
}
/*
* Thread that forwards between sockets from from to to, until from
* returns eof. Then closes to and returns.
*/
void fwd(TCPsocket from, TCPsocket to)
{
unsigned char buf[1024]; // Any size.
int nread;
try {
while((nread = recv(from, buf, sizeof buf)) > 0) {
send(to, buf, nread);
}
shutdownsend(to);
} catch(socket_sys_error &e) {
// Seems to have a lot of connection crashes. Maybe
// hosts don't bother much with nice shutdown?
//std::cout << "Good heavens! " << e.what() << std::endl;
}
}
/*
* Runs in a thread to serve a client. Peforms the interaction with the
* client: Reads and responds to the selection, and reads the command
* (which can only be connect) and attempts to perform it. Generates
* a response (success or failure), then, if successful, forwards the
* stream between the client and the server.
*/
void serve_client(TCPsocket reader)
{
// Read the selection method. Two parts
unsigned char ver;
int cnt = recv(reader, &ver, sizeof ver);
auto meths = readvar(reader);
// We only accept version 5 and method 0.
if(ver != 5 ||
std::find(meths.begin(), meths.end(), 0) == meths.end()) {
// This appears to be the only option for an error response.
unsigned char bad[] = { 5, 0xff };
send(reader, bad, sizeof bad);
close(reader);
return;
}
// Approve, and select no authentication.
{
unsigned char ok[] = { 5, 0 };
send(reader, ok, sizeof ok);
}
// Read the first four bytes of the request.
unsigned char prefix[4];
recv(reader, prefix, sizeof prefix, CSMSG_WAITALL);
// Reply code for the response.
// Change from 255 when something happens.
unsigned char reason = 0; // Well, okay so far.
// Read and interpred the address based on the type.
IPaddress addr;
if(prefix[3] == 1) {
// IP4 address. Will be sent in network byte order.
uint32_t proxy_this;
recv(reader, &proxy_this, sizeof proxy_this,
CSMSG_WAITALL);
addr.net(proxy_this);
// Note the request.
std::cout << "Request to proxy IP4 address "
<< addr << std::endl;
} else if(prefix[3] == 4) {
// IP6 address. We'll discard it, since we don't
// support IP6.
unsigned char addrbytes[16];
recv(reader, addrbytes, 16, CSMSG_WAITALL);
// Note the request.
std::cout << "Request to proxy IP6 address:";
char sep = ' ';
for(auto b: addrbytes) {
char buf[3];
snprintf(buf, sizeof buf, "%02x", b);
std::cout << sep << buf;
sep = ':';
}
std::cout << std::endl;
reason = 8; // Unsupp. address type.
} else if(prefix[3] == 3) {
// Host name. Not implemented.
auto hostname = readvar(reader);
// Note the request.
std::cout << "Request to proxy by domain name "
<< std::string(reinterpret_cast<char *>
(hostname.begin()), hostname.size)
<< std::endl;
reason = 8; // Too lazy.
} else {
std::cout << "Unrecognized address type " << prefix[3]
<< std::endl;
reason = 8;
}
// Read the port. Again, network order.
uint16_t portno;
recv(reader, &portno, sizeof portno, CSMSG_WAITALL);
IPport port;
port.net(portno);
std::cout << "Request port number: " << port << std::endl;
// Try to connect, if that was the command. Otherwise, we'll
// just give an error response. The connect case also fetches the
// local endpoint data.
if(prefix[1] != 1) reason = 7; // Only doing connect.
TCPsocket connected;
IPendpoint localend;
if(reason == 0) {
// Okay, now we're going to try to connect.
try {
connect(connected, IPendpoint(addr, port));
getsockname(connected,localend);
std::cout << "Connected to " << addr << ":" << port
<< " from " << localend << std::endl;
} catch(socket_sys_error &e) {
reason = errmap(e.code());
std::cout << "Connect to " << addr << ":" << port
<< " failed: " << e.what() << std::endl;
}
}
// Fail if we're gonna
unsigned char reply[] = { 5, reason, 0, 1, 0, 0, 0, 0, 0, 0 };
if(reason != 0) {
send(reader, reply, sizeof reply);
close(reader);
return;
}
// Add the local endpoint info to the message, and send it
// as a successful response.
uint32_t locip = localend.addr().net();
uint16_t locport = localend.port().net();
std::memcpy(reply+4, &locip, sizeof locip);
std::memcpy(reply+8, &locport, sizeof locport);
send(reader, reply, sizeof reply);
// Forward the stream, each way. Use threads so that a read for
// one direction doesn't block while there is data available on
// the read for the other.
std::thread otherway(fwd, reader, connected);
fwd(connected, reader);
otherway.join();
close(reader);
close(connected);
}
/*
* This just catches networking errors in a call to serve_client, so that an
* exception on the client service thread will not crash the whole server.
*/
void server_catcher(TCPsocket from)
{
try {
serve_client(from);
} catch(cleansocks::socket_error & e) {
std::cerr << "Client crashes " << e.what() << std::endl;
}
}
int main(int argc, char **argv)
{
// Get the listening port
IPport p;
if(argc > 1) p = atoi(argv[1]);
else {
std::cout << "Enter listen port number: ";
uint16_t pno;
if(std::cin >> pno)
p = pno;
else
p = lookup_service("socks");
}
try {
// Listen.
TCPsocket listener;
//bind(listener, IPendpoint(IPaddress::any(),p));
bind(listener, IPendpoint("127.0.0.1",p));
listen(listener);
// Accept connections and let a detached thread handle them.
while(true) {
IPendpoint client;
TCPsocket reader = accept(listener, client);
std::cout << "Accepted connection from " << client
<< std::endl;
std::thread(server_catcher, reader).detach();
}
} catch(cleansocks::socket_error & e) {
std::cerr << "Networking error: " << e.what() << std::endl;
} catch(std::runtime_error & e) {
std::cerr << "General error: " << e.what() << std::endl;
} catch(...) {
std::cerr << "Unknown exception" << std::endl;
}
}