fadd
This commit is contained in:
482
deployments/openvpn/third_party/microsocks/sockssrv.c
vendored
Normal file
482
deployments/openvpn/third_party/microsocks/sockssrv.c
vendored
Normal file
@@ -0,0 +1,482 @@
|
||||
/*
|
||||
MicroSocks - multithreaded, small, efficient SOCKS5 server.
|
||||
|
||||
Copyright (C) 2017 rofl0r.
|
||||
|
||||
This is the successor of "rocksocks5", and it was written with
|
||||
different goals in mind:
|
||||
|
||||
- prefer usage of standard libc functions over homegrown ones
|
||||
- no artificial limits
|
||||
- do not aim for minimal binary size, but for minimal source code size,
|
||||
and maximal readability, reusability, and extensibility.
|
||||
|
||||
as a result of that, ipv4, dns, and ipv6 is supported out of the box
|
||||
and can use the same code, while rocksocks5 has several compile time
|
||||
defines to bring down the size of the resulting binary to extreme values
|
||||
like 10 KB static linked when only ipv4 support is enabled.
|
||||
|
||||
still, if optimized for size, *this* program when static linked against musl
|
||||
libc is not even 50 KB. that's easily usable even on the cheapest routers.
|
||||
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <unistd.h>
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <poll.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include "server.h"
|
||||
#include "sblist.h"
|
||||
|
||||
/* timeout in microseconds on resource exhaustion to prevent excessive
|
||||
cpu usage. */
|
||||
#ifndef FAILURE_TIMEOUT
|
||||
#define FAILURE_TIMEOUT 64
|
||||
#endif
|
||||
|
||||
#ifndef MAX
|
||||
#define MAX(x, y) ((x) > (y) ? (x) : (y))
|
||||
#endif
|
||||
|
||||
#ifdef PTHREAD_STACK_MIN
|
||||
#define THREAD_STACK_SIZE MAX(8*1024, PTHREAD_STACK_MIN)
|
||||
#else
|
||||
#define THREAD_STACK_SIZE 64*1024
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#undef THREAD_STACK_SIZE
|
||||
#define THREAD_STACK_SIZE 64*1024
|
||||
#elif defined(__GLIBC__) || defined(__FreeBSD__)
|
||||
#undef THREAD_STACK_SIZE
|
||||
#define THREAD_STACK_SIZE 32*1024
|
||||
#endif
|
||||
|
||||
static const char* auth_user;
|
||||
static const char* auth_pass;
|
||||
static sblist* auth_ips;
|
||||
static pthread_rwlock_t auth_ips_lock = PTHREAD_RWLOCK_INITIALIZER;
|
||||
static const struct server* server;
|
||||
static union sockaddr_union bind_addr = {.v4.sin_family = AF_UNSPEC};
|
||||
|
||||
enum socksstate {
|
||||
SS_1_CONNECTED,
|
||||
SS_2_NEED_AUTH, /* skipped if NO_AUTH method supported */
|
||||
SS_3_AUTHED,
|
||||
};
|
||||
|
||||
enum authmethod {
|
||||
AM_NO_AUTH = 0,
|
||||
AM_GSSAPI = 1,
|
||||
AM_USERNAME = 2,
|
||||
AM_INVALID = 0xFF
|
||||
};
|
||||
|
||||
enum errorcode {
|
||||
EC_SUCCESS = 0,
|
||||
EC_GENERAL_FAILURE = 1,
|
||||
EC_NOT_ALLOWED = 2,
|
||||
EC_NET_UNREACHABLE = 3,
|
||||
EC_HOST_UNREACHABLE = 4,
|
||||
EC_CONN_REFUSED = 5,
|
||||
EC_TTL_EXPIRED = 6,
|
||||
EC_COMMAND_NOT_SUPPORTED = 7,
|
||||
EC_ADDRESSTYPE_NOT_SUPPORTED = 8,
|
||||
};
|
||||
|
||||
struct thread {
|
||||
pthread_t pt;
|
||||
struct client client;
|
||||
enum socksstate state;
|
||||
volatile int done;
|
||||
};
|
||||
|
||||
#ifndef CONFIG_LOG
|
||||
#define CONFIG_LOG 1
|
||||
#endif
|
||||
#if CONFIG_LOG
|
||||
/* we log to stderr because it's not using line buffering, i.e. malloc which would need
|
||||
locking when called from different threads. for the same reason we use dprintf,
|
||||
which writes directly to an fd. */
|
||||
#define dolog(...) dprintf(2, __VA_ARGS__)
|
||||
#else
|
||||
static void dolog(const char* fmt, ...) { }
|
||||
#endif
|
||||
|
||||
static struct addrinfo* addr_choose(struct addrinfo* list, union sockaddr_union* bind_addr) {
|
||||
int af = SOCKADDR_UNION_AF(bind_addr);
|
||||
if(af == AF_UNSPEC) return list;
|
||||
struct addrinfo* p;
|
||||
for(p=list; p; p=p->ai_next)
|
||||
if(p->ai_family == af) return p;
|
||||
return list;
|
||||
}
|
||||
|
||||
static int connect_socks_target(unsigned char *buf, size_t n, struct client *client) {
|
||||
if(n < 5) return -EC_GENERAL_FAILURE;
|
||||
if(buf[0] != 5) return -EC_GENERAL_FAILURE;
|
||||
if(buf[1] != 1) return -EC_COMMAND_NOT_SUPPORTED; /* we support only CONNECT method */
|
||||
if(buf[2] != 0) return -EC_GENERAL_FAILURE; /* malformed packet */
|
||||
|
||||
int af = AF_INET;
|
||||
size_t minlen = 4 + 4 + 2, l;
|
||||
char namebuf[256];
|
||||
struct addrinfo* remote;
|
||||
|
||||
switch(buf[3]) {
|
||||
case 4: /* ipv6 */
|
||||
af = AF_INET6;
|
||||
minlen = 4 + 2 + 16;
|
||||
/* fall through */
|
||||
case 1: /* ipv4 */
|
||||
if(n < minlen) return -EC_GENERAL_FAILURE;
|
||||
if(namebuf != inet_ntop(af, buf+4, namebuf, sizeof namebuf))
|
||||
return -EC_GENERAL_FAILURE; /* malformed or too long addr */
|
||||
break;
|
||||
case 3: /* dns name */
|
||||
l = buf[4];
|
||||
minlen = 4 + 2 + l + 1;
|
||||
if(n < 4 + 2 + l + 1) return -EC_GENERAL_FAILURE;
|
||||
memcpy(namebuf, buf+4+1, l);
|
||||
namebuf[l] = 0;
|
||||
break;
|
||||
default:
|
||||
return -EC_ADDRESSTYPE_NOT_SUPPORTED;
|
||||
}
|
||||
unsigned short port;
|
||||
port = (buf[minlen-2] << 8) | buf[minlen-1];
|
||||
/* there's no suitable errorcode in rfc1928 for dns lookup failure */
|
||||
if(resolve(namebuf, port, &remote)) return -EC_GENERAL_FAILURE;
|
||||
struct addrinfo* raddr = addr_choose(remote, &bind_addr);
|
||||
int fd = socket(raddr->ai_family, SOCK_STREAM, 0);
|
||||
if(fd == -1) {
|
||||
eval_errno:
|
||||
if(fd != -1) close(fd);
|
||||
freeaddrinfo(remote);
|
||||
switch(errno) {
|
||||
case ETIMEDOUT:
|
||||
return -EC_TTL_EXPIRED;
|
||||
case EPROTOTYPE:
|
||||
case EPROTONOSUPPORT:
|
||||
case EAFNOSUPPORT:
|
||||
return -EC_ADDRESSTYPE_NOT_SUPPORTED;
|
||||
case ECONNREFUSED:
|
||||
return -EC_CONN_REFUSED;
|
||||
case ENETDOWN:
|
||||
case ENETUNREACH:
|
||||
return -EC_NET_UNREACHABLE;
|
||||
case EHOSTUNREACH:
|
||||
return -EC_HOST_UNREACHABLE;
|
||||
case EBADF:
|
||||
default:
|
||||
perror("socket/connect");
|
||||
return -EC_GENERAL_FAILURE;
|
||||
}
|
||||
}
|
||||
if(SOCKADDR_UNION_AF(&bind_addr) == raddr->ai_family &&
|
||||
bindtoip(fd, &bind_addr) == -1)
|
||||
goto eval_errno;
|
||||
if(connect(fd, raddr->ai_addr, raddr->ai_addrlen) == -1)
|
||||
goto eval_errno;
|
||||
|
||||
freeaddrinfo(remote);
|
||||
if(CONFIG_LOG) {
|
||||
char clientname[256];
|
||||
af = SOCKADDR_UNION_AF(&client->addr);
|
||||
void *ipdata = SOCKADDR_UNION_ADDRESS(&client->addr);
|
||||
inet_ntop(af, ipdata, clientname, sizeof clientname);
|
||||
dolog("client[%d] %s: connected to %s:%d\n", client->fd, clientname, namebuf, port);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int is_authed(union sockaddr_union *client, union sockaddr_union *authedip) {
|
||||
int af = SOCKADDR_UNION_AF(authedip);
|
||||
if(af == SOCKADDR_UNION_AF(client)) {
|
||||
size_t cmpbytes = af == AF_INET ? 4 : 16;
|
||||
void *cmp1 = SOCKADDR_UNION_ADDRESS(client);
|
||||
void *cmp2 = SOCKADDR_UNION_ADDRESS(authedip);
|
||||
if(!memcmp(cmp1, cmp2, cmpbytes)) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is_in_authed_list(union sockaddr_union *caddr) {
|
||||
size_t i;
|
||||
for(i=0;i<sblist_getsize(auth_ips);i++)
|
||||
if(is_authed(caddr, sblist_get(auth_ips, i)))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void add_auth_ip(union sockaddr_union *caddr) {
|
||||
sblist_add(auth_ips, caddr);
|
||||
}
|
||||
|
||||
static enum authmethod check_auth_method(unsigned char *buf, size_t n, struct client*client) {
|
||||
if(buf[0] != 5) return AM_INVALID;
|
||||
size_t idx = 1;
|
||||
if(idx >= n ) return AM_INVALID;
|
||||
int n_methods = buf[idx];
|
||||
idx++;
|
||||
while(idx < n && n_methods > 0) {
|
||||
if(buf[idx] == AM_NO_AUTH) {
|
||||
if(!auth_user) return AM_NO_AUTH;
|
||||
else if(auth_ips) {
|
||||
int authed = 0;
|
||||
if(pthread_rwlock_rdlock(&auth_ips_lock) == 0) {
|
||||
authed = is_in_authed_list(&client->addr);
|
||||
pthread_rwlock_unlock(&auth_ips_lock);
|
||||
}
|
||||
if(authed) return AM_NO_AUTH;
|
||||
}
|
||||
} else if(buf[idx] == AM_USERNAME) {
|
||||
if(auth_user) return AM_USERNAME;
|
||||
}
|
||||
idx++;
|
||||
n_methods--;
|
||||
}
|
||||
return AM_INVALID;
|
||||
}
|
||||
|
||||
static void send_auth_response(int fd, int version, enum authmethod meth) {
|
||||
unsigned char buf[2];
|
||||
buf[0] = version;
|
||||
buf[1] = meth;
|
||||
write(fd, buf, 2);
|
||||
}
|
||||
|
||||
static void send_error(int fd, enum errorcode ec) {
|
||||
/* position 4 contains ATYP, the address type, which is the same as used in the connect
|
||||
request. we're lazy and return always IPV4 address type in errors. */
|
||||
char buf[10] = { 5, ec, 0, 1 /*AT_IPV4*/, 0,0,0,0, 0,0 };
|
||||
write(fd, buf, 10);
|
||||
}
|
||||
|
||||
static void copyloop(int fd1, int fd2) {
|
||||
struct pollfd fds[2] = {
|
||||
[0] = {.fd = fd1, .events = POLLIN},
|
||||
[1] = {.fd = fd2, .events = POLLIN},
|
||||
};
|
||||
|
||||
while(1) {
|
||||
/* inactive connections are reaped after 15 min to free resources.
|
||||
usually programs send keep-alive packets so this should only happen
|
||||
when a connection is really unused. */
|
||||
switch(poll(fds, 2, 60*15*1000)) {
|
||||
case 0:
|
||||
return;
|
||||
case -1:
|
||||
if(errno == EINTR || errno == EAGAIN) continue;
|
||||
else perror("poll");
|
||||
return;
|
||||
}
|
||||
int infd = (fds[0].revents & POLLIN) ? fd1 : fd2;
|
||||
int outfd = infd == fd2 ? fd1 : fd2;
|
||||
char buf[1024];
|
||||
ssize_t sent = 0, n = read(infd, buf, sizeof buf);
|
||||
if(n <= 0) return;
|
||||
while(sent < n) {
|
||||
ssize_t m = write(outfd, buf+sent, n-sent);
|
||||
if(m < 0) return;
|
||||
sent += m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static enum errorcode check_credentials(unsigned char* buf, size_t n) {
|
||||
if(n < 5) return EC_GENERAL_FAILURE;
|
||||
if(buf[0] != 1) return EC_GENERAL_FAILURE;
|
||||
unsigned ulen, plen;
|
||||
ulen=buf[1];
|
||||
if(n < 2 + ulen + 2) return EC_GENERAL_FAILURE;
|
||||
plen=buf[2+ulen];
|
||||
if(n < 2 + ulen + 1 + plen) return EC_GENERAL_FAILURE;
|
||||
char user[256], pass[256];
|
||||
memcpy(user, buf+2, ulen);
|
||||
memcpy(pass, buf+2+ulen+1, plen);
|
||||
user[ulen] = 0;
|
||||
pass[plen] = 0;
|
||||
if(!strcmp(user, auth_user) && !strcmp(pass, auth_pass)) return EC_SUCCESS;
|
||||
return EC_NOT_ALLOWED;
|
||||
}
|
||||
|
||||
static void* clientthread(void *data) {
|
||||
struct thread *t = data;
|
||||
t->state = SS_1_CONNECTED;
|
||||
unsigned char buf[1024];
|
||||
ssize_t n;
|
||||
int ret;
|
||||
int remotefd = -1;
|
||||
enum authmethod am;
|
||||
while((n = recv(t->client.fd, buf, sizeof buf, 0)) > 0) {
|
||||
switch(t->state) {
|
||||
case SS_1_CONNECTED:
|
||||
am = check_auth_method(buf, n, &t->client);
|
||||
if(am == AM_NO_AUTH) t->state = SS_3_AUTHED;
|
||||
else if (am == AM_USERNAME) t->state = SS_2_NEED_AUTH;
|
||||
send_auth_response(t->client.fd, 5, am);
|
||||
if(am == AM_INVALID) goto breakloop;
|
||||
break;
|
||||
case SS_2_NEED_AUTH:
|
||||
ret = check_credentials(buf, n);
|
||||
send_auth_response(t->client.fd, 1, ret);
|
||||
if(ret != EC_SUCCESS)
|
||||
goto breakloop;
|
||||
t->state = SS_3_AUTHED;
|
||||
if(auth_ips && !pthread_rwlock_wrlock(&auth_ips_lock)) {
|
||||
if(!is_in_authed_list(&t->client.addr))
|
||||
add_auth_ip(&t->client.addr);
|
||||
pthread_rwlock_unlock(&auth_ips_lock);
|
||||
}
|
||||
break;
|
||||
case SS_3_AUTHED:
|
||||
ret = connect_socks_target(buf, n, &t->client);
|
||||
if(ret < 0) {
|
||||
send_error(t->client.fd, ret*-1);
|
||||
goto breakloop;
|
||||
}
|
||||
remotefd = ret;
|
||||
send_error(t->client.fd, EC_SUCCESS);
|
||||
copyloop(t->client.fd, remotefd);
|
||||
goto breakloop;
|
||||
|
||||
}
|
||||
}
|
||||
breakloop:
|
||||
|
||||
if(remotefd != -1)
|
||||
close(remotefd);
|
||||
|
||||
close(t->client.fd);
|
||||
t->done = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void collect(sblist *threads) {
|
||||
size_t i;
|
||||
for(i=0;i<sblist_getsize(threads);) {
|
||||
struct thread* thread = *((struct thread**)sblist_get(threads, i));
|
||||
if(thread->done) {
|
||||
pthread_join(thread->pt, 0);
|
||||
sblist_delete(threads, i);
|
||||
free(thread);
|
||||
} else
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
static int usage(void) {
|
||||
dprintf(2,
|
||||
"MicroSocks SOCKS5 Server\n"
|
||||
"------------------------\n"
|
||||
"usage: microsocks -1 -i listenip -p port -u user -P password -b bindaddr\n"
|
||||
"all arguments are optional.\n"
|
||||
"by default listenip is 0.0.0.0 and port 1080.\n\n"
|
||||
"option -b specifies which ip outgoing connections are bound to\n"
|
||||
"option -1 activates auth_once mode: once a specific ip address\n"
|
||||
"authed successfully with user/pass, it is added to a whitelist\n"
|
||||
"and may use the proxy without auth.\n"
|
||||
"this is handy for programs like firefox that don't support\n"
|
||||
"user/pass auth. for it to work you'd basically make one connection\n"
|
||||
"with another program that supports it, and then you can use firefox too.\n"
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* prevent username and password from showing up in top. */
|
||||
static void zero_arg(char *s) {
|
||||
size_t i, l = strlen(s);
|
||||
for(i=0;i<l;i++) s[i] = 0;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int ch;
|
||||
const char *listenip = "0.0.0.0";
|
||||
unsigned port = 1080;
|
||||
while((ch = getopt(argc, argv, ":1b:i:p:u:P:")) != -1) {
|
||||
switch(ch) {
|
||||
case '1':
|
||||
auth_ips = sblist_new(sizeof(union sockaddr_union), 8);
|
||||
break;
|
||||
case 'b':
|
||||
resolve_sa(optarg, 0, &bind_addr);
|
||||
break;
|
||||
case 'u':
|
||||
auth_user = strdup(optarg);
|
||||
zero_arg(optarg);
|
||||
break;
|
||||
case 'P':
|
||||
auth_pass = strdup(optarg);
|
||||
zero_arg(optarg);
|
||||
break;
|
||||
case 'i':
|
||||
listenip = optarg;
|
||||
break;
|
||||
case 'p':
|
||||
port = atoi(optarg);
|
||||
break;
|
||||
case ':':
|
||||
dprintf(2, "error: option -%c requires an operand\n", optopt);
|
||||
/* fall through */
|
||||
case '?':
|
||||
return usage();
|
||||
}
|
||||
}
|
||||
if((auth_user && !auth_pass) || (!auth_user && auth_pass)) {
|
||||
dprintf(2, "error: user and pass must be used together\n");
|
||||
return 1;
|
||||
}
|
||||
if(auth_ips && !auth_pass) {
|
||||
dprintf(2, "error: auth-once option must be used together with user/pass\n");
|
||||
return 1;
|
||||
}
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
struct server s;
|
||||
sblist *threads = sblist_new(sizeof (struct thread*), 8);
|
||||
if(server_setup(&s, listenip, port)) {
|
||||
perror("server_setup");
|
||||
return 1;
|
||||
}
|
||||
server = &s;
|
||||
|
||||
while(1) {
|
||||
collect(threads);
|
||||
struct client c;
|
||||
struct thread *curr = malloc(sizeof (struct thread));
|
||||
if(!curr) goto oom;
|
||||
curr->done = 0;
|
||||
if(server_waitclient(&s, &c)) {
|
||||
dolog("failed to accept connection\n");
|
||||
free(curr);
|
||||
usleep(FAILURE_TIMEOUT);
|
||||
continue;
|
||||
}
|
||||
curr->client = c;
|
||||
if(!sblist_add(threads, &curr)) {
|
||||
close(curr->client.fd);
|
||||
free(curr);
|
||||
oom:
|
||||
dolog("rejecting connection due to OOM\n");
|
||||
usleep(FAILURE_TIMEOUT); /* prevent 100% CPU usage in OOM situation */
|
||||
continue;
|
||||
}
|
||||
pthread_attr_t *a = 0, attr;
|
||||
if(pthread_attr_init(&attr) == 0) {
|
||||
a = &attr;
|
||||
pthread_attr_setstacksize(a, THREAD_STACK_SIZE);
|
||||
}
|
||||
if(pthread_create(&curr->pt, a, clientthread, curr) != 0)
|
||||
dolog("pthread_create failed. OOM?\n");
|
||||
if(a) pthread_attr_destroy(&attr);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user