fadd
This commit is contained in:
3
deployments/openvpn/third_party/microsocks/.gitignore
vendored
Normal file
3
deployments/openvpn/third_party/microsocks/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*.o
|
||||
*.out
|
||||
|
||||
24
deployments/openvpn/third_party/microsocks/COPYING
vendored
Normal file
24
deployments/openvpn/third_party/microsocks/COPYING
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
microSocks is licensed under the following standard MIT license:
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Copyright © 2017 rofl0r.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
----------------------------------------------------------------------
|
||||
35
deployments/openvpn/third_party/microsocks/Makefile
vendored
Normal file
35
deployments/openvpn/third_party/microsocks/Makefile
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# if you want to change/override some variables, do so in a file called
|
||||
# config.mak, which is gets included automatically if it exists.
|
||||
|
||||
prefix = /usr/local
|
||||
bindir = $(prefix)/bin
|
||||
|
||||
PROG = microsocks
|
||||
SRCS = sockssrv.c server.c sblist.c sblist_delete.c
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
|
||||
LIBS = -lpthread
|
||||
|
||||
CFLAGS += -Wall -std=c99
|
||||
|
||||
INSTALL = ./install.sh
|
||||
|
||||
-include config.mak
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
install: $(PROG)
|
||||
$(INSTALL) -D -m 755 $(PROG) $(DESTDIR)$(bindir)/$(PROG)
|
||||
|
||||
clean:
|
||||
rm -f $(PROG)
|
||||
rm -f $(OBJS)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) $(INC) $(PIC) -c -o $@ $<
|
||||
|
||||
$(PROG): $(OBJS)
|
||||
$(CC) $(LDFLAGS) $(OBJS) $(LIBS) -o $@
|
||||
|
||||
.PHONY: all clean install
|
||||
|
||||
65
deployments/openvpn/third_party/microsocks/README.md
vendored
Normal file
65
deployments/openvpn/third_party/microsocks/README.md
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
MicroSocks - multithreaded, small, efficient SOCKS5 server.
|
||||
===========================================================
|
||||
|
||||
a SOCKS5 service that you can run on your remote boxes to tunnel connections
|
||||
through them, if for some reason SSH doesn't cut it for you.
|
||||
|
||||
It's very lightweight, and very light on resources too:
|
||||
|
||||
for every client, a thread with a stack size of 8KB is spawned.
|
||||
the main process basically doesn't consume any resources at all.
|
||||
|
||||
the only limits are the amount of file descriptors and the RAM.
|
||||
|
||||
It's also designed to be robust: it handles resource exhaustion
|
||||
gracefully by simply denying new connections, instead of calling abort()
|
||||
as most other programs do these days.
|
||||
|
||||
another plus is ease-of-use: no config file necessary, everything can be
|
||||
done from the command line and doesn't even need any parameters for quick
|
||||
setup.
|
||||
|
||||
History
|
||||
-------
|
||||
|
||||
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.
|
||||
|
||||
command line options
|
||||
--------------------
|
||||
|
||||
microsocks -1 -i listenip -p port -u user -P password -b bindaddr
|
||||
|
||||
all arguments are optional.
|
||||
by default listenip is 0.0.0.0 and port 1080.
|
||||
|
||||
option -1 activates auth_once mode: once a specific ip address
|
||||
authed successfully with user/pass, it is added to a whitelist
|
||||
and may use the proxy without auth.
|
||||
this is handy for programs like firefox that don't support
|
||||
user/pass auth. for it to work you'd basically make one connection
|
||||
with another program that supports it, and then you can use firefox too.
|
||||
for example, authenticate once using curl:
|
||||
|
||||
curl --socks5 user:password@listenip:port anyurl
|
||||
|
||||
|
||||
Supported SOCKS5 Features
|
||||
-------------------------
|
||||
- authentication: none, password, one-time
|
||||
- IPv4, IPv6, DNS
|
||||
- TCP (no UDP at this time)
|
||||
|
||||
28
deployments/openvpn/third_party/microsocks/create-dist.sh
vendored
Normal file
28
deployments/openvpn/third_party/microsocks/create-dist.sh
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
if [ -z "$VER" ] ; then
|
||||
echo set VER!
|
||||
exit
|
||||
fi
|
||||
me=`pwd`
|
||||
|
||||
proj=microsocks
|
||||
projver=${proj}-${VER}
|
||||
|
||||
tempdir=/tmp/${proj}-0000
|
||||
rm -rf "$tempdir"
|
||||
mkdir -p "$tempdir"
|
||||
|
||||
cd $tempdir
|
||||
GITDIR=https://github.com/rofl0r/$proj
|
||||
GITDIR=$me
|
||||
git clone "$GITDIR" $projver
|
||||
|
||||
rm -rf $projver/.git
|
||||
rm -rf $projver/docs
|
||||
rm -f $projver/.gitignore
|
||||
rm -f $projver/create-dist.sh
|
||||
|
||||
tar cf $proj.tar $projver/
|
||||
xz -z -9 -e $proj.tar
|
||||
mv $proj.tar.xz $me/$projver.tar.xz
|
||||
rm -rf "$tempdir"
|
||||
67
deployments/openvpn/third_party/microsocks/install.sh
vendored
Normal file
67
deployments/openvpn/third_party/microsocks/install.sh
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Written by Rich Felker, originally as part of musl libc.
|
||||
# Multi-licensed under MIT, 0BSD, and CC0.
|
||||
#
|
||||
# This is an actually-safe install command which installs the new
|
||||
# file atomically in the new location, rather than overwriting
|
||||
# existing files.
|
||||
#
|
||||
|
||||
usage() {
|
||||
printf "usage: %s [-D] [-l] [-m mode] src dest\n" "$0" 1>&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
mkdirp=
|
||||
symlink=
|
||||
mode=755
|
||||
|
||||
while getopts Dlm: name ; do
|
||||
case "$name" in
|
||||
D) mkdirp=yes ;;
|
||||
l) symlink=yes ;;
|
||||
m) mode=$OPTARG ;;
|
||||
?) usage ;;
|
||||
esac
|
||||
done
|
||||
shift $(($OPTIND - 1))
|
||||
|
||||
test "$#" -eq 2 || usage
|
||||
src=$1
|
||||
dst=$2
|
||||
tmp="$dst.tmp.$$"
|
||||
|
||||
case "$dst" in
|
||||
*/) printf "%s: %s ends in /\n", "$0" "$dst" 1>&2 ; exit 1 ;;
|
||||
esac
|
||||
|
||||
set -C
|
||||
set -e
|
||||
|
||||
if test "$mkdirp" ; then
|
||||
umask 022
|
||||
case "$2" in
|
||||
*/*) mkdir -p "${dst%/*}" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
trap 'rm -f "$tmp"' EXIT INT QUIT TERM HUP
|
||||
|
||||
umask 077
|
||||
|
||||
if test "$symlink" ; then
|
||||
ln -s "$1" "$tmp"
|
||||
else
|
||||
cat < "$1" > "$tmp"
|
||||
chmod "$mode" "$tmp"
|
||||
fi
|
||||
|
||||
mv -f "$tmp" "$2"
|
||||
test -d "$2" && {
|
||||
rm -f "$2/$tmp"
|
||||
printf "%s: %s is a directory\n" "$0" "$dst" 1>&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
exit 0
|
||||
73
deployments/openvpn/third_party/microsocks/sblist.c
vendored
Normal file
73
deployments/openvpn/third_party/microsocks/sblist.c
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
#undef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "sblist.h"
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#define MY_PAGE_SIZE 4096
|
||||
|
||||
sblist* sblist_new(size_t itemsize, size_t blockitems) {
|
||||
sblist* ret = (sblist*) malloc(sizeof(sblist));
|
||||
sblist_init(ret, itemsize, blockitems);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sblist_clear(sblist* l) {
|
||||
l->items = NULL;
|
||||
l->capa = 0;
|
||||
l->count = 0;
|
||||
}
|
||||
|
||||
void sblist_init(sblist* l, size_t itemsize, size_t blockitems) {
|
||||
if(l) {
|
||||
l->blockitems = blockitems ? blockitems : MY_PAGE_SIZE / itemsize;
|
||||
l->itemsize = itemsize;
|
||||
sblist_clear(l);
|
||||
}
|
||||
}
|
||||
|
||||
void sblist_free_items(sblist* l) {
|
||||
if(l) {
|
||||
if(l->items) free(l->items);
|
||||
sblist_clear(l);
|
||||
}
|
||||
}
|
||||
|
||||
void sblist_free(sblist* l) {
|
||||
if(l) {
|
||||
sblist_free_items(l);
|
||||
free(l);
|
||||
}
|
||||
}
|
||||
|
||||
char* sblist_item_from_index(sblist* l, size_t idx) {
|
||||
return l->items + (idx * l->itemsize);
|
||||
}
|
||||
|
||||
void* sblist_get(sblist* l, size_t item) {
|
||||
if(item < l->count) return (void*) sblist_item_from_index(l, item);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int sblist_set(sblist* l, void* item, size_t pos) {
|
||||
if(pos >= l->count) return 0;
|
||||
memcpy(sblist_item_from_index(l, pos), item, l->itemsize);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int sblist_grow_if_needed(sblist* l) {
|
||||
char* temp;
|
||||
if(l->count == l->capa) {
|
||||
temp = realloc(l->items, (l->capa + l->blockitems) * l->itemsize);
|
||||
if(!temp) return 0;
|
||||
l->capa += l->blockitems;
|
||||
l->items = temp;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int sblist_add(sblist* l, void* item) {
|
||||
if(!sblist_grow_if_needed(l)) return 0;
|
||||
l->count++;
|
||||
return sblist_set(l, item, l->count - 1);
|
||||
}
|
||||
92
deployments/openvpn/third_party/microsocks/sblist.h
vendored
Normal file
92
deployments/openvpn/third_party/microsocks/sblist.h
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
#ifndef SBLIST_H
|
||||
#define SBLIST_H
|
||||
|
||||
/* this file is part of libulz, as of commit 8ab361a27743aaf025323ee43b8b8876dc054fdd
|
||||
modified for direct inclusion in microsocks. */
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
/*
|
||||
* simple buffer list.
|
||||
*
|
||||
* this thing here is basically a generic dynamic array
|
||||
* will realloc after every blockitems inserts
|
||||
* can store items of any size.
|
||||
*
|
||||
* so think of it as a by-value list, as opposed to a typical by-ref list.
|
||||
* you typically use it by having some struct on the stack, and pass a pointer
|
||||
* to sblist_add, which will copy the contents into its internal memory.
|
||||
*
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
size_t itemsize;
|
||||
size_t blockitems;
|
||||
size_t count;
|
||||
size_t capa;
|
||||
char* items;
|
||||
} sblist;
|
||||
|
||||
#define sblist_getsize(X) ((X)->count)
|
||||
#define sblist_get_count(X) ((X)->count)
|
||||
#define sblist_empty(X) ((X)->count == 0)
|
||||
|
||||
// for dynamic style
|
||||
sblist* sblist_new(size_t itemsize, size_t blockitems);
|
||||
void sblist_free(sblist* l);
|
||||
|
||||
//for static style
|
||||
void sblist_init(sblist* l, size_t itemsize, size_t blockitems);
|
||||
void sblist_free_items(sblist* l);
|
||||
|
||||
// accessors
|
||||
void* sblist_get(sblist* l, size_t item);
|
||||
// returns 1 on success, 0 on OOM
|
||||
int sblist_add(sblist* l, void* item);
|
||||
int sblist_set(sblist* l, void* item, size_t pos);
|
||||
void sblist_delete(sblist* l, size_t item);
|
||||
char* sblist_item_from_index(sblist* l, size_t idx);
|
||||
int sblist_grow_if_needed(sblist* l);
|
||||
int sblist_insert(sblist* l, void* item, size_t pos);
|
||||
/* same as sblist_add, but returns list index of new item, or -1 */
|
||||
size_t sblist_addi(sblist* l, void* item);
|
||||
void sblist_sort(sblist *l, int (*compar)(const void *, const void *));
|
||||
/* insert element into presorted list, returns listindex of new entry or -1*/
|
||||
size_t sblist_insert_sorted(sblist* l, void* o, int (*compar)(const void *, const void *));
|
||||
|
||||
#ifndef __COUNTER__
|
||||
#define __COUNTER__ __LINE__
|
||||
#endif
|
||||
|
||||
#define __sblist_concat_impl( x, y ) x##y
|
||||
#define __sblist_macro_concat( x, y ) __sblist_concat_impl( x, y )
|
||||
#define __sblist_iterator_name __sblist_macro_concat(sblist_iterator, __COUNTER__)
|
||||
|
||||
// use with custom iterator variable
|
||||
#define sblist_iter_counter(LIST, ITER, PTR) \
|
||||
for(size_t ITER = 0; (PTR = sblist_get(LIST, ITER)), ITER < sblist_getsize(LIST); ITER++)
|
||||
|
||||
// use with custom iterator variable, which is predeclared
|
||||
#define sblist_iter_counter2(LIST, ITER, PTR) \
|
||||
for(ITER = 0; (PTR = sblist_get(LIST, ITER)), ITER < sblist_getsize(LIST); ITER++)
|
||||
|
||||
// use with custom iterator variable, which is predeclared and signed
|
||||
// useful for a loop which can delete items from the list, and then decrease the iterator var.
|
||||
#define sblist_iter_counter2s(LIST, ITER, PTR) \
|
||||
for(ITER = 0; (PTR = sblist_get(LIST, ITER)), ITER < (ssize_t) sblist_getsize(LIST); ITER++)
|
||||
|
||||
|
||||
// uses "magic" iterator variable
|
||||
#define sblist_iter(LIST, PTR) sblist_iter_counter(LIST, __sblist_iterator_name, PTR)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma RcB2 DEP "sblist.c" "sblist_delete.c"
|
||||
|
||||
#endif
|
||||
9
deployments/openvpn/third_party/microsocks/sblist_delete.c
vendored
Normal file
9
deployments/openvpn/third_party/microsocks/sblist_delete.c
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "sblist.h"
|
||||
#include <string.h>
|
||||
|
||||
void sblist_delete(sblist* l, size_t item) {
|
||||
if (l->count && item < l->count) {
|
||||
memmove(sblist_item_from_index(l, item), sblist_item_from_index(l, item + 1), (sblist_getsize(l) - (item + 1)) * l->itemsize);
|
||||
l->count--;
|
||||
}
|
||||
}
|
||||
64
deployments/openvpn/third_party/microsocks/server.c
vendored
Normal file
64
deployments/openvpn/third_party/microsocks/server.c
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "server.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int resolve(const char *host, unsigned short port, struct addrinfo** addr) {
|
||||
struct addrinfo hints = {
|
||||
.ai_family = AF_UNSPEC,
|
||||
.ai_socktype = SOCK_STREAM,
|
||||
.ai_flags = AI_PASSIVE,
|
||||
};
|
||||
char port_buf[8];
|
||||
snprintf(port_buf, sizeof port_buf, "%u", port);
|
||||
return getaddrinfo(host, port_buf, &hints, addr);
|
||||
}
|
||||
|
||||
int resolve_sa(const char *host, unsigned short port, union sockaddr_union *res) {
|
||||
struct addrinfo *ainfo = 0;
|
||||
int ret;
|
||||
SOCKADDR_UNION_AF(res) = AF_UNSPEC;
|
||||
if((ret = resolve(host, port, &ainfo))) return ret;
|
||||
memcpy(res, ainfo->ai_addr, ainfo->ai_addrlen);
|
||||
freeaddrinfo(ainfo);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bindtoip(int fd, union sockaddr_union *bindaddr) {
|
||||
socklen_t sz = SOCKADDR_UNION_LENGTH(bindaddr);
|
||||
if(sz)
|
||||
return bind(fd, (struct sockaddr*) bindaddr, sz);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int server_waitclient(struct server *server, struct client* client) {
|
||||
socklen_t clen = sizeof client->addr;
|
||||
return ((client->fd = accept(server->fd, (void*)&client->addr, &clen)) == -1)*-1;
|
||||
}
|
||||
|
||||
int server_setup(struct server *server, const char* listenip, unsigned short port) {
|
||||
struct addrinfo *ainfo = 0;
|
||||
if(resolve(listenip, port, &ainfo)) return -1;
|
||||
struct addrinfo* p;
|
||||
int listenfd = -1;
|
||||
for(p = ainfo; p; p = p->ai_next) {
|
||||
if((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
|
||||
continue;
|
||||
int yes = 1;
|
||||
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
|
||||
if(bind(listenfd, p->ai_addr, p->ai_addrlen) < 0) {
|
||||
close(listenfd);
|
||||
listenfd = -1;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
freeaddrinfo(ainfo);
|
||||
if(listenfd < 0) return -2;
|
||||
if(listen(listenfd, SOMAXCONN) < 0) {
|
||||
close(listenfd);
|
||||
return -3;
|
||||
}
|
||||
server->fd = listenfd;
|
||||
return 0;
|
||||
}
|
||||
49
deployments/openvpn/third_party/microsocks/server.h
vendored
Normal file
49
deployments/openvpn/third_party/microsocks/server.h
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef SERVER_H
|
||||
#define SERVER_H
|
||||
|
||||
#undef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#pragma RcB2 DEP "server.c"
|
||||
|
||||
union sockaddr_union {
|
||||
struct sockaddr_in v4;
|
||||
struct sockaddr_in6 v6;
|
||||
};
|
||||
|
||||
#define SOCKADDR_UNION_AF(PTR) (PTR)->v4.sin_family
|
||||
|
||||
#define SOCKADDR_UNION_LENGTH(PTR) ( \
|
||||
( SOCKADDR_UNION_AF(PTR) == AF_INET ) ? sizeof((PTR)->v4) : ( \
|
||||
( SOCKADDR_UNION_AF(PTR) == AF_INET6 ) ? sizeof((PTR)->v6) : 0 ) )
|
||||
|
||||
#define SOCKADDR_UNION_ADDRESS(PTR) ( \
|
||||
( SOCKADDR_UNION_AF(PTR) == AF_INET ) ? (void*) &(PTR)->v4.sin_addr : ( \
|
||||
( SOCKADDR_UNION_AF(PTR) == AF_INET6 ) ? (void*) &(PTR)->v6.sin6_addr : (void*) 0 ) )
|
||||
|
||||
#define SOCKADDR_UNION_PORT(PTR) ( \
|
||||
( SOCKADDR_UNION_AF(PTR) == AF_INET ) ? (PTR)->v4.sin_port : ( \
|
||||
( SOCKADDR_UNION_AF(PTR) == AF_INET6 ) ? (PTR)->v6.sin6_port : 0 ) )
|
||||
|
||||
struct client {
|
||||
union sockaddr_union addr;
|
||||
int fd;
|
||||
};
|
||||
|
||||
struct server {
|
||||
int fd;
|
||||
};
|
||||
|
||||
int resolve(const char *host, unsigned short port, struct addrinfo** addr);
|
||||
int resolve_sa(const char *host, unsigned short port, union sockaddr_union *res);
|
||||
int bindtoip(int fd, union sockaddr_union *bindaddr);
|
||||
|
||||
int server_waitclient(struct server *server, struct client* client);
|
||||
int server_setup(struct server *server, const char* listenip, unsigned short port);
|
||||
|
||||
#endif
|
||||
|
||||
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