miniroon

Simplistic macaroon-based authorization for Unix systems
git clone https://ccx.te2000.cz/git/miniroon
Log | Files | Refs

commit 19b552ecd452ea490aad26498d6ae33c9666d5df
parent 52872ba2cf357e4ee87723a63b0420aec4fd8754
Author: Jan Pobrislo <ccx@te2000.cz>
Date:   Mon, 26 Aug 2024 10:57:16 +0000

UUIDv7 and SOCKS utilities

Diffstat:
Msrc/Makefile | 3++-
Asrc/mtime_to_uuidv7.c | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ucspi-socksserver-connected.c | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ucspi-socksserver.c | 410+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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 +*/