commit 19b552ecd452ea490aad26498d6ae33c9666d5df
parent 52872ba2cf357e4ee87723a63b0420aec4fd8754
Author: Jan Pobrislo <ccx@te2000.cz>
Date: Mon, 26 Aug 2024 10:57:16 +0000
UUIDv7 and SOCKS utilities
Diffstat:
4 files changed, 628 insertions(+), 1 deletion(-)
diff --git a/src/Makefile b/src/Makefile
@@ -1,5 +1,6 @@
-tools_simple:=argv0exec nosuid pidns_run safelink spawn-pty fdsend fdrecv fdrecvto socketpair
+tools_simple:=argv0exec nosuid pidns_run safelink spawn-pty fdsend fdrecv fdrecvto socketpair ptsname mtime_to_uuidv7 ucspi-socksserver ucspi-socksserver-connected
+
tools_libcap:=applyuidgid-caps
tools=$(tools_simple) $(tools_libcap)
diff --git a/src/mtime_to_uuidv7.c b/src/mtime_to_uuidv7.c
@@ -0,0 +1,63 @@
+#include <stdint.h>
+#include <sys/stat.h> /* for stat(), struct stat */
+#include <unistd.h> /* for getentropy() */
+#include <stdio.h> /* for printf() */
+#include <fcntl.h> /* Definition of AT_* constants */
+
+#include <skalibs/strerr.h>
+
+#define PROG "mtime_to_uuidv7"
+#define USAGE "mtime_to_uuidv7 [filename]"
+
+
+void uuidv7(struct timespec *ts, uint8_t* value) {
+ // timestamp in ms
+ uint64_t timestamp = (uint64_t)ts->tv_sec * 1000 + ts->tv_nsec / 1000000;
+
+ // random bytes
+ if(getentropy(value, 16)) {
+ strerr_dief1sys(111, "getentropy()");
+ }
+
+ // timestamp
+ value[0] = (timestamp >> 40) & 0xFF;
+ value[1] = (timestamp >> 32) & 0xFF;
+ value[2] = (timestamp >> 24) & 0xFF;
+ value[3] = (timestamp >> 16) & 0xFF;
+ value[4] = (timestamp >> 8) & 0xFF;
+ value[5] = timestamp & 0xFF;
+
+ // version and variant
+ value[6] = (value[6] & 0x0F) | 0x70;
+ value[8] = (value[8] & 0x3F) | 0x80;
+}
+
+
+int main (int argc, char const *const *argv)
+{
+ if (argc > 2) {
+ strerr_dieusage(100, USAGE);
+ }
+ struct stat statinfo;
+ if(argc == 2) {
+ if(stat(argv[1], &statinfo)) {
+ strerr_diefu2sys(111, "stat(): ", argv[1]);
+ }
+ } else {
+ if(fstatat(0, "", &statinfo, AT_EMPTY_PATH)) {
+ strerr_diefu1sys(111, "fstatat(stdin)");
+ }
+ }
+
+ uint8_t uuid_val[16];
+ uuidv7(&statinfo.st_mtim, uuid_val);
+
+ for (size_t i = 0; i < 16; i++) {
+ printf("%02x", uuid_val[i]);
+ }
+ printf("\n");
+
+ return 0;
+}
+/* vim: sw=4 sts=4 et
+*/
diff --git a/src/ucspi-socksserver-connected.c b/src/ucspi-socksserver-connected.c
@@ -0,0 +1,153 @@
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <skalibs/exec.h>
+#include <skalibs/strerr.h>
+#include <skalibs/types.h>
+
+#define PROG "ucspi-socketserver-connect"
+
+#define SOCKS_RESERVED 0x00
+#define SOCKS_VERSION_4 0x04
+#define SOCKS_VERSION_5 0x05
+
+typedef unsigned char socks5_addr_type_t;
+#define SOCKS5_ADDR_TYPE_IP4 0x01
+#define SOCKS5_ADDR_TYPE_DOMAIN 0x03
+#define SOCKS5_ADDR_TYPE_IP6 0x04
+
+typedef unsigned char socks5_response_t;
+#define SOCKS5_REPLY_OK 0x00 // succeeded
+#define SOCKS5_REPLY_ERR_GENERAL 0x01 // general SOCKS server failure
+#define SOCKS5_REPLY_ERR_FORBIDDEN 0x02 // connection not allowed by ruleset
+#define SOCKS5_REPLY_ERR_NET_UNREACH 0x03 // Network unreachable
+#define SOCKS5_REPLY_ERR_HOST_UNREACH 0x04 // Host unreachable
+#define SOCKS5_REPLY_ERR_CONN_REFUSED 0x05 // Connection refused
+#define SOCKS5_REPLY_ERR_TTL 0x06 // TTL expired
+#define SOCKS5_REPLY_ERR_CMD 0x07 // Command not supported
+#define SOCKS5_REPLY_ERR_ATYPE 0x08 // Address type not supported
+
+#define SOCKS4_STATUS_OK 0x5a
+#define SOCKS4_STATUS_FAILED 0x5b
+
+void fd_block(int fd)
+{
+ int flags = fcntl(fd, F_GETFL);
+ if(flags == -1) {
+ strerr_dief1sys(111, "fcntl() getfd");
+ }
+ if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+ strerr_dief1sys(111, "fcntl() setfd");
+ }
+}
+
+void write1(const void *buf, ssize_t n)
+{
+ ssize_t nwrite, left = n;
+ while (left > 0) {
+ if ((nwrite = write(STDOUT_FILENO, buf, left)) < 0) {
+ if (errno == EINTR || errno == EAGAIN) {
+ continue;
+ }
+ strerr_diefu1sys(111, "write(stdout)");
+ } else {
+ assert(left >= nwrite);
+ left -= nwrite;
+ buf += nwrite;
+ }
+ }
+}
+
+void socks5_response_ok(socks5_addr_type_t addr_type, void *addr, in_port_t port)
+{
+ size_t size;
+ switch(addr_type) {
+ case SOCKS5_ADDR_TYPE_IP4:
+ size = 4;
+ break;
+ case SOCKS5_ADDR_TYPE_IP6:
+ size = 16;
+ break;
+ case SOCKS5_ADDR_TYPE_DOMAIN:
+ size = strlen(addr);
+ if(size > 0xff) {
+ strerr_dieinvalid(110, "local hostname too long") ;
+ }
+ break;
+ }
+
+ char response[] = {
+ SOCKS_VERSION_5,
+ SOCKS5_REPLY_OK,
+ SOCKS_RESERVED,
+ addr_type,
+ };
+ write1(response, sizeof(response));
+
+ if (addr_type == SOCKS5_ADDR_TYPE_DOMAIN) {
+ /* prefix domain by length */
+ unsigned char domain_len = size;
+ write1(&domain_len, 1);
+ }
+ write1(addr, size);
+
+ /* should be already in network byte order (from sin_port) */
+ write1(&port, sizeof(port));
+}
+
+void socks4_send_response(int status)
+{
+ char resp[8] = {0x00, (char)status, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ write1(resp, sizeof(resp));
+}
+
+int main (int argc, char const *const *argv)
+{
+ char const *env_socks_version = getenv("SOCKS_VERSION");
+ if (!env_socks_version) {
+ strerr_dienotset(100, "SOCKS_VERSION");
+ }
+
+ /* fd_block(STDIN_FILENO); */
+ fd_block(STDOUT_FILENO);
+
+ if (strcmp(env_socks_version, "5") == 0) {
+
+ struct sockaddr local_addr;
+ socklen_t addrlen = sizeof(local_addr);
+ if(getsockname(6, &local_addr, &addrlen) != 0) {
+ strerr_diefu1sys(111, "getsockname(6, ...)");
+ }
+ assert(addrlen <= sizeof(local_addr));
+ switch(local_addr.sa_family) {
+ case AF_INET:
+ struct sockaddr_in *in_addr = (struct sockaddr_in *)&local_addr;
+ socks5_response_ok(SOCKS5_ADDR_TYPE_IP4, &in_addr->sin_addr, in_addr->sin_port);
+ break;
+ case AF_INET6:
+ struct sockaddr_in6 *in6_addr = (struct sockaddr_in6 *)&local_addr;
+ socks5_response_ok(SOCKS5_ADDR_TYPE_IP6, &in6_addr->sin6_addr, in6_addr->sin6_port);
+ break;
+ default:
+ socks5_response_ok(SOCKS5_ADDR_TYPE_DOMAIN, "localhost", 0);
+ break;
+ }
+
+ } else if (strcmp(env_socks_version, "4") == 0) {
+ socks4_send_response(SOCKS4_STATUS_OK);
+
+ } else {
+ strerr_dieinvalid(100, "SOCKS_VERSION") ;
+
+ }
+ xexec(argv+1);
+ return 110;
+}
+
+/* vim: sw=4 sts=4 et
+*/
diff --git a/src/ucspi-socksserver.c b/src/ucspi-socksserver.c
@@ -0,0 +1,410 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdio.h> /* for log_message */
+#include <stdarg.h> /* for log_message */
+#include <assert.h>
+#include <arpa/inet.h>
+
+#include <skalibs/exec.h>
+#include <skalibs/strerr.h>
+
+#define PROG "ucspi-socketserver"
+
+#define MAX_DOMAIN_LENGTH 256
+
+#define SOCKS_RESERVED 0x00
+#define SOCKS_VERSION_4 0x04
+#define SOCKS_VERSION_5 0x05
+
+typedef unsigned char socks5_auth_mode_t;
+#define SOCKS5_AUTH_METHOD_NOAUTH 0x00
+#define SOCKS5_AUTH_METHOD_USERPASS 0x02
+#define SOCKS5_AUTH_METHOD_NOMETHOD 0xff
+
+#define SOCKS5_AUTH_OK 0x00
+#define SOCKS5_AUTH_VERSION 0x01
+#define SOCKS5_AUTH_FAIL 0xff
+
+typedef unsigned char socks5_command_t;
+#define SOCKS5_CMD_CONNECT 0x01
+#define SOCKS5_BIND 0x02
+#define SOCKS5_UDP_ASSOCIATE 0x03
+
+typedef unsigned char socks5_addr_type_t;
+#define SOCKS5_ADDR_TYPE_IP4 0x01
+#define SOCKS5_ADDR_TYPE_DOMAIN 0x03
+#define SOCKS5_ADDR_TYPE_IP6 0x04
+
+typedef unsigned char socks5_response_t;
+#define SOCKS5_REPLY_OK 0x00 // succeeded
+#define SOCKS5_REPLY_ERR_GENERAL 0x01 // general SOCKS server failure
+#define SOCKS5_REPLY_ERR_FORBIDDEN 0x02 // connection not allowed by ruleset
+#define SOCKS5_REPLY_ERR_NET_UNREACH 0x03 // Network unreachable
+#define SOCKS5_REPLY_ERR_HOST_UNREACH 0x04 // Host unreachable
+#define SOCKS5_REPLY_ERR_CONN_REFUSED 0x05 // Connection refused
+#define SOCKS5_REPLY_ERR_TTL 0x06 // TTL expired
+#define SOCKS5_REPLY_ERR_CMD 0x07 // Command not supported
+#define SOCKS5_REPLY_ERR_ATYPE 0x08 // Address type not supported
+
+typedef struct socks_header_s {
+ unsigned char version;
+ unsigned char methods;
+} socks_header;
+
+
+typedef struct socks5_cmd_header_s {
+ unsigned char version;
+ socks5_command_t cmd;
+ unsigned char reserved;
+ socks5_addr_type_t atyp;
+} socks5_cmd_header;
+
+
+void fd_block(int fd);
+void read0(void *buf, ssize_t n);
+void write1(const void *buf, ssize_t n);
+void socks_read_header(socks_header *hdr);
+unsigned short socks_read_port(void);
+void interact(void);
+void handle_socks5(char methods);
+void socks5_auth(int methods_count, socks5_auth_mode_t supported_auth_method);
+void socks5_auth_userpass(void);
+void socks5_auth_noauth(void);
+void socks5_auth_notsupported(void);
+void socks5_command(socks5_cmd_header *chdr);
+void handle_socks4(void);
+void do_connect(unsigned char socks_version, socks5_addr_type_t addr_type, char *buf, unsigned short int port);
+
+int main (int argc, char const *const *argv)
+{
+ fd_block(STDIN_FILENO);
+ fd_block(STDOUT_FILENO);
+ interact();
+}
+
+// TODO: get rid of stdio dependency
+void log_message(const char *message, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, message);
+ ret = vdprintf(STDERR_FILENO, message, args);
+ va_end(args);
+ if (ret < 0) {
+ strerr_dief1sys(111, "log_message() failed");
+ }
+ write(2, "\n", 1);
+}
+
+void fd_block(int fd)
+{
+ int flags = fcntl(fd, F_GETFL);
+ if(flags == -1) {
+ strerr_dief1sys(111, "fcntl() getfd");
+ }
+ if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+ strerr_dief1sys(111, "fcntl() setfd");
+ }
+}
+
+void read0(void *buf, ssize_t n)
+{
+ ssize_t nread, left = n;
+ while (left > 0) {
+ if ((nread = read(STDIN_FILENO, buf, left)) < 0) {
+ if (errno == EINTR || errno == EAGAIN) {
+ continue;
+ }
+ strerr_diefu1sys(111, "read(stdin)");
+ } else {
+ if (nread == 0) {
+ strerr_diefu1x(111, "read(stdin): end of file");
+ } else {
+ assert(left >= nread);
+ left -= nread;
+ buf += nread;
+ }
+ }
+ }
+}
+
+void write1(const void *buf, ssize_t n)
+{
+ ssize_t nwrite, left = n;
+ while (left > 0) {
+ if ((nwrite = write(STDOUT_FILENO, buf, left)) < 0) {
+ if (errno == EINTR || errno == EAGAIN) {
+ continue;
+ }
+ strerr_diefu1sys(111, "write(stdout)");
+ } else {
+ assert(left >= nwrite);
+ left -= nwrite;
+ buf += nwrite;
+ }
+ }
+}
+
+void read0_cstring(char *buf, size_t n)
+{
+ size_t left = n;
+ while (left > 0) {
+ read0(buf, 1);
+ if(*buf == 0) {
+ return;
+ }
+ left--;
+ buf++;
+ }
+ strerr_diefu1x(0, "read0_cstring: exceeded maximum buffer size");
+}
+
+/* start here */
+void interact(void)
+{
+ socks_header hdr;
+ socks_read_header(&hdr);
+
+ switch (hdr.version) {
+ case SOCKS_VERSION_5:
+ handle_socks5(hdr.methods);
+ break;
+ case SOCKS_VERSION_4:
+ if (hdr.methods == 1) {
+ handle_socks4();
+ } else {
+ strerr_dief1x(0, "Unsupported SOCKS4 mode");
+ }
+ break;
+ }
+}
+
+void socks_read_header(socks_header *hdr)
+{
+ read0(hdr, sizeof(socks_header));
+ // log_message("Received header: 0x%hhX 0x%hhX", hdr->version, hdr->method);
+ if (hdr->version != SOCKS_VERSION_5 && hdr->version != SOCKS_VERSION_4) {
+ strerr_dief1x(0, "Incompatible version!");
+ }
+}
+
+unsigned short socks_read_port(void) {
+ unsigned short port;
+ read0(&port, sizeof(port));
+ return ntohs(port);
+}
+
+void handle_socks5(char methods)
+{
+ socks5_auth(methods, SOCKS5_AUTH_METHOD_NOAUTH); /* TODO: authentication support */
+ socks5_cmd_header chdr;
+ socks5_command(&chdr);
+ unsigned short port;
+
+ switch (chdr.atyp) {
+ case SOCKS5_ADDR_TYPE_IP4:
+ char ip4[4];
+ read0(ip4, sizeof(ip4));
+ port = socks_read_port();
+
+ do_connect(5, SOCKS5_ADDR_TYPE_IP4, ip4, port);
+ /* socks5_ip_send_response(ip, htons(port)); */
+ break;
+ case SOCKS5_ADDR_TYPE_IP6:
+ char ip6[16];
+ read0(ip6, sizeof(ip6));
+ port = socks_read_port();
+
+ do_connect(5, SOCKS5_ADDR_TYPE_IP6, ip6, port);
+ /* socks5_ip_send_response(ip, htons(port)); */
+ break;
+ case SOCKS5_ADDR_TYPE_DOMAIN:
+ unsigned char size;
+ char domain[MAX_DOMAIN_LENGTH];
+ read0(&size, sizeof(size));
+ assert(size < MAX_DOMAIN_LENGTH);
+ read0(domain, size);
+ domain[size] = 0;
+ port = socks_read_port();
+
+ do_connect(5, SOCKS5_ADDR_TYPE_DOMAIN, domain, port);
+ /* socks5_domain_send_response(address, size, htons(port)); */
+ break;
+ default:
+ strerr_dief1x(1, "Unsupported SOCKS5 command");
+ break;
+ }
+}
+
+void socks5_auth(int methods_count, socks5_auth_mode_t supported_auth_method)
+{
+ bool supported = false;
+ for (int i = 0; i < methods_count; i++) {
+ char type;
+ read0(&type, 1);
+ log_message("Method AUTH 0x%hhX", type);
+ if (type == supported_auth_method) {
+ supported = true;
+ }
+ }
+ if (!supported) {
+ socks5_auth_notsupported();
+ strerr_dief1x(1, "No supported authentication method");
+ }
+ switch (supported_auth_method) {
+ case SOCKS5_AUTH_METHOD_NOAUTH:
+ socks5_auth_noauth();
+ break;
+ case SOCKS5_AUTH_METHOD_USERPASS:
+ socks5_auth_userpass();
+ break;
+ default:
+ strerr_dief1x(110, "Internal error: unhandled authentication method");
+ break;
+ }
+}
+
+void socks5_auth_userpass(void)
+{
+ strerr_dief1x(110, "TODO: userpass auth");
+}
+
+void socks5_auth_noauth(void)
+{
+ const char answer[2] = { SOCKS_VERSION_5, SOCKS5_AUTH_METHOD_NOAUTH };
+ write1(answer, sizeof(answer));
+}
+
+void socks5_auth_notsupported(void)
+{
+ const char answer[2] = { SOCKS_VERSION_5, SOCKS5_AUTH_METHOD_NOMETHOD };
+ write1(answer, sizeof(answer));
+}
+
+void socks5_command(socks5_cmd_header *chdr)
+{
+ read0(chdr, sizeof(socks5_cmd_header));
+ log_message("Command %02hhX %02hhX %02hhX %02hhX",
+ chdr->version, chdr->cmd, chdr->reserved, chdr->atyp);
+ assert(chdr->version == SOCKS_VERSION_5);
+ assert(chdr->cmd == SOCKS5_CMD_CONNECT);
+}
+
+
+void handle_socks4(void)
+{
+ char ident[255];
+ unsigned short int port;
+ char ip[4];
+
+ port = socks_read_port();
+ read0(&ip, sizeof(ip));
+ read0_cstring(ident, sizeof(ident));
+
+ if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] != 0) {
+ char domain[MAX_DOMAIN_LENGTH];
+ read0_cstring(domain, sizeof(domain));
+ log_message("Socks4A: ident:%s; domain:%s;", ident, domain);
+ do_connect(4, SOCKS5_ADDR_TYPE_DOMAIN, domain, port);
+ } else {
+ log_message("Socks4: connect by ip & port");
+ do_connect(4, SOCKS5_ADDR_TYPE_IP4, ip, port);
+ }
+
+}
+
+void xenv(char const *s, char const *t) {
+ if (!env_mexec(s, t)) {
+ strerr_diefu1sys(111, "env_mexec");
+ }
+}
+
+void socks_env_ip4(const char *ip) {
+ char v4_address[16];
+ snprintf(v4_address, sizeof(v4_address), "%hhu.%hhu.%hhu.%hhu",
+ ip[0], ip[1], ip[2], ip[3]);
+ xenv("SOCKS_ADDR", v4_address);
+ xenv("SOCKS_ADDR_TYPE", "ip4");
+}
+
+void socks_env_ip6(const char *ip) {
+ char v6_address[41];
+ snprintf(v6_address, sizeof(v6_address),
+ "%02hhx%02hhx:%02hhx%02hhx:%02hhx%02hhx:%02hhx%02hhx:%02hhx%02hhx:%02hhx%02hhx:%02hhx%02hhx:%02hhx%02hhx",
+ ip[0x0], ip[0x1], ip[0x2], ip[0x3], ip[0x4], ip[0x5], ip[0x6], ip[0x7],
+ ip[0x8], ip[0x9], ip[0xa], ip[0xb], ip[0xc], ip[0xd], ip[0xe], ip[0xf]);
+ xenv("SOCKS_ADDR", v6_address);
+ xenv("SOCKS_ADDR_TYPE", "ip6");
+}
+
+void socks_env_dns(const char *domain) {
+ xenv("SOCKS_ADDR", domain);
+ xenv("SOCKS_ADDR_TYPE", "dns");
+}
+
+void do_connect(unsigned char socks_version, socks5_addr_type_t addr_type, char *buf, unsigned short int port) {
+ switch(socks_version) {
+ case 4:
+ xenv("SOCKS_VERSION", "4");
+ break;
+ case 5:
+ xenv("SOCKS_VERSION", "5");
+ break;
+ default:
+ strerr_dief1x(110, "Internal error: unhandled protocol version");
+ break;
+ }
+
+ char port_string[6];
+ snprintf(port_string, sizeof(port_string), "%d", port);
+ xenv("SOCKS_PORT", port_string);
+
+ const char *argv[] = {
+ "importas", "-i", "SOCKS_ADDR", "SOCKS_ADDR",
+ "importas", "-i", "SOCKS_PORT", "SOCKS_PORT",
+ "s6-tcpclient", "-v", "$SOCKS_ADDR", "$SOCKS_PORT",
+ "ucspi-socksserver-connected", "s6-ioconnect",
+ 0
+ };
+
+ switch (addr_type) {
+ case SOCKS5_ADDR_TYPE_IP4:
+ socks_env_ip4(buf);
+ /*
+ char v4_address[16];
+ snprintf(v4_address, sizeof(v4_address), "%hhu.%hhu.%hhu.%hhu",
+ buf[0], buf[1], buf[2], buf[3]);
+ const char *argv_ip4[] = {"s6-tcpclient", "-v4", v4_address, port_string, "ucspi-socksserver-connected", "s6-ioconnect", 0};
+ argv = argv_ip4;
+ */
+ break;
+ case SOCKS5_ADDR_TYPE_IP6:
+ socks_env_ip6(buf);
+ /*
+ char v6_address[41];
+ snprintf(v6_address, sizeof(v6_address),
+ "%02hhx%02hhx:%02hhx%02hhx:%02hhx%02hhx:%02hhx%02hhx:%02hhx%02hhx:%02hhx%02hhx:%02hhx%02hhx:%02hhx%02hhx",
+ buf[0x0], buf[0x1], buf[0x2], buf[0x3], buf[0x4], buf[0x5], buf[0x6], buf[0x7],
+ buf[0x8], buf[0x9], buf[0xa], buf[0xb], buf[0xc], buf[0xd], buf[0xe], buf[0xf]);
+ const char *argv_ip6[] = {"s6-tcpclient", "-v6", v6_address, port_string, "ucspi-socksserver-connected", "s6-ioconnect", 0};
+ argv = argv_ip4;
+ */
+ break;
+ case SOCKS5_ADDR_TYPE_DOMAIN:
+ socks_env_dns(buf);
+ /*
+ const char *argv_d[] = {"s6-tcpclient", "-v", buf, port_string, "ucspi-socksserver-connected", "s6-ioconnect", 0};
+ argv = argv_d;
+ */
+ break;
+ default:
+ strerr_dief1x(110, "Internal error: unhandled address type");
+ break;
+ }
+ xmexec(argv);
+}
+
+/* vim: sw=4 sts=4 et
+*/