Files
openwrt-passwall-packages/microsocks/patches/100-Add-SOCKS5-forwarding-rules-support.patch
2025-12-29 20:14:45 +08:00

429 lines
12 KiB
Diff

--- a/sockssrv.c
+++ b/sockssrv.c
@@ -33,8 +33,10 @@
#include <arpa/inet.h>
#include <errno.h>
#include <limits.h>
+#include <sys/time.h>
#include "server.h"
#include "sblist.h"
+#define MICROSOCKS_VERSION "1.0.5-forward"
/* timeout in microseconds on resource exhaustion to prevent excessive
cpu usage. */
@@ -71,6 +73,7 @@
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};
+static sblist *fwd_rules;
enum socksstate {
SS_1_CONNECTED,
@@ -97,6 +100,17 @@
EC_ADDRESSTYPE_NOT_SUPPORTED = 8,
};
+struct fwd_rule {
+ char *match_name;
+ short match_port;
+ char *auth_buf; /* Username/Password request buffer (RFC-1929) */
+ size_t auth_len;
+ char *upstream_name;
+ short upstream_port;
+ char *req_buf; /* Client Connection Request buffer to send to upstream */
+ size_t req_len;
+};
+
struct thread {
pthread_t pt;
struct client client;
@@ -116,6 +130,109 @@
static void dolog(const char* fmt, ...) { }
#endif
+static int upstream_handshake(const struct fwd_rule* rule, unsigned char *client_buf, size_t client_buf_len,
+ int client_fd, int upstream_fd, unsigned short client_port) {
+ unsigned char sbuf[512];
+ ssize_t r;
+
+ if(rule->auth_buf) {
+ unsigned char handshake[4] = {5, 2, 0, 2};
+ if (write(upstream_fd, handshake, 4) != 4) {
+ close(upstream_fd);
+ return -1;
+ }
+ } else {
+ unsigned char handshake[3] = {5, 1, 0};
+ if (write(upstream_fd, handshake, 3) != 3) {
+ close(upstream_fd);
+ return -1;
+ }
+ }
+
+ if (read(upstream_fd, sbuf, 2) != 2 || sbuf[0] != 5) {
+ close(upstream_fd);
+ return -1;
+ }
+
+ if (sbuf[1] == 2) {
+ if (!rule->auth_buf) {
+ close(upstream_fd);
+ return -1;
+ }
+ if (write(upstream_fd, rule->auth_buf, rule->auth_len) != (ssize_t)rule->auth_len) {
+ close(upstream_fd);
+ return -1;
+ }
+ if (read(upstream_fd, sbuf, 2) != 2 || sbuf[0] != 1 || sbuf[1] != 0) {
+ close(upstream_fd);
+ return -1;
+ }
+ } else if (sbuf[1] != 0) {
+ close(upstream_fd);
+ return -1;
+ }
+
+ if (write(upstream_fd, client_buf, client_buf_len) != (ssize_t)client_buf_len) {
+ close(upstream_fd);
+ return -1;
+ }
+
+ size_t total = 0;
+ size_t need = 4;
+
+ while (total < need) {
+ r = read(upstream_fd, sbuf + total, need - total);
+ if (r <= 0) {
+ close(upstream_fd);
+ return -1;
+ }
+ total += r;
+ }
+
+ if (sbuf[1] != 0) {
+ close(upstream_fd);
+ return -sbuf[1];
+ }
+
+ size_t need_more = 0;
+ switch (sbuf[3]) {
+ case 1:
+ need_more = 4 + 2;
+ break;
+ case 4:
+ need_more = 16 + 2;
+ break;
+ case 3:
+ r = read(upstream_fd, sbuf + total, 1);
+ if (r != 1) {
+ close(upstream_fd);
+ return -1;
+ }
+ total += r;
+ need_more = sbuf[4] + 2;
+ break;
+ default:
+ close(upstream_fd);
+ return -EC_ADDRESSTYPE_NOT_SUPPORTED;
+ }
+
+ while (total < need + need_more) {
+ r = read(upstream_fd, sbuf + total, (need + need_more) - total);
+ if (r <= 0) {
+ close(upstream_fd);
+ return -1;
+ }
+ total += r;
+ }
+
+ if (write(client_fd, sbuf, total) != (ssize_t)total) {
+ close(upstream_fd);
+ return -1;
+ }
+
+ return upstream_fd;
+}
+
static struct addrinfo* addr_choose(struct addrinfo* list, union sockaddr_union* bindaddr) {
int af = SOCKADDR_UNION_AF(bindaddr);
if(af == AF_UNSPEC) return list;
@@ -125,7 +242,9 @@
return list;
}
-static int connect_socks_target(unsigned char *buf, size_t n, struct client *client) {
+static int connect_socks_target(unsigned char *buf, size_t n, struct client *client, int *used_rule) {
+ *used_rule = 0;
+
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 */
@@ -158,6 +277,29 @@
}
unsigned short port;
port = (buf[minlen-2] << 8) | buf[minlen-1];
+
+ size_t i;
+ struct fwd_rule *rule = NULL;
+ char original_name[256];
+ unsigned short original_port = port;
+ strncpy(original_name, namebuf, sizeof(original_name) - 1);
+ original_name[sizeof(original_name) - 1] = '\0';
+ if(fwd_rules) {
+ for(i=0;i<sblist_getsize(fwd_rules);++i) {
+ struct fwd_rule* r = (struct fwd_rule*)sblist_get(fwd_rules, i);
+ int name_match = (r->match_name[0]=='\0' || strcmp(r->match_name, namebuf) == 0);
+ int port_match = (r->match_port == 0 || r->match_port == port);
+ if(name_match && port_match) {
+ rule = r;
+ *used_rule = 1;
+ strncpy(namebuf, r->upstream_name, sizeof(namebuf)-1);
+ namebuf[sizeof(namebuf)-1] = '\0';
+ port = r->upstream_port;
+ break;
+ }
+ }
+ }
+
/* 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);
@@ -186,6 +328,11 @@
return -EC_GENERAL_FAILURE;
}
}
+
+ struct timeval tv = {5, 0};
+ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
+ setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
+
if(SOCKADDR_UNION_AF(&bind_addr) == raddr->ai_family &&
bindtoip(fd, &bind_addr) == -1)
goto eval_errno;
@@ -198,9 +345,22 @@
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);
+ if (rule) {
+ dolog("client[%d] %s: %s:%d -> via %s:%d\n", client->fd, clientname, original_name, original_port, rule->upstream_name, rule->upstream_port);
+ } else {
+ dolog("client[%d] %s: connected to %s:%d\n", client->fd, clientname, namebuf, port);
+ }
}
- return fd;
+
+ if (rule) {
+ int result = upstream_handshake(rule, buf, n, client->fd, fd, original_port);
+ if (result < 0) {
+ close(fd);
+ return result;
+ }
+ return result;
+ }
+ return fd;
}
static int is_authed(union sockaddr_union *client, union sockaddr_union *authedip) {
@@ -322,6 +482,7 @@
ssize_t n;
int ret;
enum authmethod am;
+ int used_rule = 0;
t->state = SS_1_CONNECTED;
while((n = recv(t->client.fd, buf, sizeof buf, 0)) > 0) {
switch(t->state) {
@@ -345,12 +506,14 @@
}
break;
case SS_3_AUTHED:
- ret = connect_socks_target(buf, n, &t->client);
+ ret = connect_socks_target(buf, n, &t->client, &used_rule);
if(ret < 0) {
send_error(t->client.fd, ret*-1);
return -1;
}
- send_error(t->client.fd, EC_SUCCESS);
+ if (!used_rule) {
+ send_error(t->client.fd, EC_SUCCESS);
+ }
return ret;
}
}
@@ -382,11 +545,131 @@
}
}
+static short host_get_port(char *name) {
+ int p,n;
+ char *c;
+ if((c = strrchr(name, ':')) && sscanf(c+1,"%d%n",&p, &n)==1 && n == (int)(strlen(c + 1)) && p >= 0 && p < USHRT_MAX)
+ return (*c='\0'),(short)p;
+ else
+ return -1;
+}
+
+static int fwd_rules_add(char *str) {
+ char *match = NULL, *upstream = NULL, *remote = NULL;
+ unsigned short match_port, upstream_port, remote_port;
+ int ncred;
+
+ if(sscanf(str, "%m[^,],%n%m[^,],%ms\n", &match, &ncred, &upstream, &remote) != 3)
+ return 1;
+
+ match_port = host_get_port(match);
+ upstream_port = host_get_port(upstream);
+ remote_port = host_get_port(remote);
+
+ if(match_port < 0 || upstream_port <= 0 || remote_port < 0) {
+ free(match);
+ free(upstream);
+ free(remote);
+ return 1;
+ }
+
+ char *match_copy = strdup(match);
+ char *upstream_copy = strdup(upstream);
+ char *remote_copy = strdup(remote);
+
+ struct fwd_rule *rule = (struct fwd_rule*)malloc(sizeof(struct fwd_rule));
+ if (!rule) {
+ free(match_copy);
+ free(upstream_copy);
+ free(remote_copy);
+ free(match);
+ free(upstream);
+ free(remote);
+ return 1;
+ }
+
+ if(strcmp(match_copy, "0.0.0.0") == 0 || strcmp(match_copy, "*") == 0) {
+ free(match_copy);
+ rule->match_name = strdup("");
+ } else {
+ rule->match_name = match_copy;
+ }
+ rule->match_port = match_port;
+ rule->auth_buf = NULL;
+ rule->auth_len = 0;
+
+ char *at_sign = strchr(upstream_copy, '@');
+ if (at_sign) {
+ *at_sign = '\0';
+ char *auth_part = upstream_copy;
+ char *host_part = at_sign + 1;
+ char *colon = strchr(auth_part, ':');
+ if (!colon) {
+ free(rule);
+ free(upstream_copy);
+ free(remote_copy);
+ free(match);
+ free(upstream);
+ free(remote);
+ return 1;
+ }
+ *colon++ = '\0';
+ char *username = auth_part;
+ char *password = colon;
+ size_t ulen = strlen(username);
+ size_t plen = strlen(password);
+ if (ulen > 255 || plen > 255) {
+ free(rule);
+ free(upstream_copy);
+ free(remote_copy);
+ free(match);
+ free(upstream);
+ free(remote);
+ return 1;
+ }
+ rule->auth_len = 1 + 1 + ulen + 1 + plen;
+ rule->auth_buf = malloc(rule->auth_len);
+ rule->auth_buf[0] = 1;
+ rule->auth_buf[1] = ulen;
+ memcpy(&rule->auth_buf[2], username, ulen);
+ rule->auth_buf[2 + ulen] = plen;
+ memcpy(&rule->auth_buf[3 + ulen], password, plen);
+ rule->upstream_name = strdup(host_part);
+ rule->upstream_port = upstream_port;
+ /* hide from ps */
+ memset(str+ncred, '*', ulen+1+plen);
+ } else {
+ rule->upstream_name = strdup(upstream_copy);
+ rule->upstream_port = upstream_port;
+ }
+
+ free(upstream_copy);
+ short rlen = strlen(remote_copy);
+ rule->req_len = 3 + 1 + 1 + rlen + 2;
+ rule->req_buf = (char*)malloc(rule->req_len);
+ rule->req_buf[0] = 5;
+ rule->req_buf[1] = 1;
+ rule->req_buf[2] = 0;
+ rule->req_buf[3] = 3;
+ rule->req_buf[4] = rlen;
+ memcpy(&rule->req_buf[5], remote_copy, rlen);
+ unsigned short rport = remote_port ? remote_port : 0;
+ rule->req_buf[5 + rlen] = (rport >> 8) & 0xFF;
+ rule->req_buf[5 + rlen + 1] = (rport & 0xFF);
+ free(remote_copy);
+ sblist_add(fwd_rules, rule);
+ free(match);
+ free(upstream);
+ free(remote);
+
+ return 0;
+}
+
static int usage(void) {
dprintf(2,
"MicroSocks SOCKS5 Server\n"
"------------------------\n"
- "usage: microsocks -1 -q -i listenip -p port -u user -P pass -b bindaddr -w ips\n"
+ "usage: microsocks -1 -q -i listenip -p port -u user -P pass -b bindaddr -w ips -f fwdrule\n"
"all arguments are optional.\n"
"by default listenip is 0.0.0.0 and port 1080.\n\n"
"option -q disables logging.\n"
@@ -401,6 +684,12 @@
" 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"
+ "option -f specifies a forwarding rule of the form\n"
+ " match_name:match_port,[user:password@]upstream_name:upstream_port,remote_name:remote_port\n"
+ " this will cause requests that /match/ to be renamed to /remote/\n"
+ " and sent to the /upstream/ SOCKS5 proxy server.\n"
+ " this option may be specified multiple times.\n"
+ "option -V prints version information and exits.\n"
);
return 1;
}
@@ -416,7 +705,7 @@
const char *listenip = "0.0.0.0";
char *p, *q;
unsigned port = 1080;
- while((ch = getopt(argc, argv, ":1qb:i:p:u:P:w:")) != -1) {
+ while((ch = getopt(argc, argv, ":1qb:i:p:u:P:w:f:V")) != -1) {
switch(ch) {
case 'w': /* fall-through */
case '1':
@@ -456,11 +745,20 @@
case 'p':
port = atoi(optarg);
break;
+ case 'f':
+ if(!fwd_rules)
+ fwd_rules = sblist_new(sizeof(struct fwd_rule), 16);
+ if(fwd_rules_add(optarg))
+ return dprintf(2, "error: could not parse forwarding rule %s\n", optarg), 1;
+ break;
case ':':
dprintf(2, "error: option -%c requires an operand\n", optopt);
/* fall through */
case '?':
return usage();
+ case 'V':
+ dprintf(1, "MicroSocks %s\n", MICROSOCKS_VERSION);
+ return 0;
}
}
if((auth_user && !auth_pass) || (!auth_user && auth_pass)) {