miniroon

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

commit f724fcf66ad2f1cf9d9d51a379aae2346d9caf56
parent 00a85994ac403d816340f4e1610ca3ded79a7eb2
Author: Jan Pobrislo <ccx@te2000.cz>
Date:   Tue, 27 Aug 2024 11:02:45 +0000

Extract common SOCKS constants and structures, add access control module.

Diffstat:
Asrc/socks.h | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ucspi-socksserver-access.c | 205+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/ucspi-socksserver-connected.c | 23+++++++++++------------
Msrc/ucspi-socksserver.c | 103+++++++++++++++++++++++++++++--------------------------------------------------
4 files changed, 307 insertions(+), 77 deletions(-)

diff --git a/src/socks.h b/src/socks.h @@ -0,0 +1,53 @@ + +#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_reply_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 + +typedef struct socks_header_s { + unsigned char version; + unsigned char methods; +} socks_header; + + +typedef struct socks5_request_header_s { + unsigned char version; + socks5_command_t cmd; + unsigned char reserved; + socks5_addr_type_t atyp; +} socks5_request_header; + +/* vim: sw=4 sts=4 et +*/ diff --git a/src/ucspi-socksserver-access.c b/src/ucspi-socksserver-access.c @@ -0,0 +1,205 @@ +#include <string.h> +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <assert.h> + +#include <skalibs/exec.h> +#include <skalibs/sgetopt.h> +#include <skalibs/strerr.h> +#include <skalibs/cdb.h> +#include <skalibs/ip46.h> + +#include <s6/accessrules.h> + +#include "socks.h" + +#define PROG "ucspi-socksserver-access" +#define USAGE "ucspi-socksserve-access [ -i rulesdir | -x rulesfile ] prog..." +#define dieusage() strerr_dieusage(100, USAGE) +#define X() strerr_dief1x(101, "internal error") + +typedef struct app_options_s { + unsigned int rulestype; + char const * rules; + char const * port; + char const * addr_type; + char const * addr; +} app_options; + +typedef struct check_result_s { + s6_accessrules_result_t accepted; + s6_accessrules_params_t params; +} check_result; + + +char * required_getenv(const char *name){ + char *x = getenv(name); + if (!x) { + strerr_dienotset(100, name); + } + return x; +} + +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_reply_fail(socks5_reply_t status) +{ + char reply[] = { + SOCKS_VERSION_5, + status, + SOCKS_RESERVED, + SOCKS5_ADDR_TYPE_DOMAIN, + 0, /* domain length */ + 0, /* port MSB */ + 0 /* port LSB */ + }; + write1(reply, sizeof(reply)); + close(1); +} + +void socks4_reply_fail() +{ + const char resp[8] = {0x00, SOCKS4_STATUS_FAILED, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + write1(resp, sizeof(resp)); + close(1); +} + + +void check_access_ip(app_options *opt, check_result *result) { + ip46 ip; + if (!ip46_scan(opt->addr_type, &ip)) { + strerr_dieinvalid(100, "SOCKS_ADDR") ; + } + switch (opt->rulestype) + { + case 1 : + result->accepted = s6_accessrules_ip46_fs(&ip, (void *)opt->rules, &result->params); + break; + case 2 : + { + cdb c = CDB_ZERO ; + if (!cdb_init(&c, opt->rules)) { + strerr_diefu2sys(111, "cdb_init ", opt->rules); + } + result->accepted = s6_accessrules_ip46_cdb(&ip, &c, &result->params); + if (result->accepted == S6_ACCESSRULES_ALLOW) { + cdb_free(&c); + } + } + break; + default : X(); + } +} + +s6_accessrules_result_t check_access_dns(app_options *opt, check_result *result) { + switch (opt->rulestype) + { + case 1 : + result->accepted = s6_accessrules_reversedns_fs(opt->addr, (void *)opt->rules, &result->params); + break; + case 2 : + { + cdb c = CDB_ZERO ; + if (!cdb_init(&c, opt->rules)) { + strerr_diefu2sys(111, "cdb_init ", opt->rules); + } + result->accepted = s6_accessrules_reversedns_cdb(opt->addr, &c, &result->params); + if (result->accepted == S6_ACCESSRULES_ALLOW) { + cdb_free(&c); + } + } + break; + default : X(); + } +} + + + +int main (int argc, char const *const *argv) +{ + char const *rulestypestr[3] = { "no", "fs", "cdb" } ; + app_options opt; + opt.rulestype = 0; + opt.rules = 0; + + subgetopt l = SUBGETOPT_ZERO ; + for (;;) + { + int o = subgetopt_r(argc, argv, "i:x:", &l) ; + if (o == -1) break ; + switch (o) + { + case 'i' : opt.rules = l.arg ; opt.rulestype = 1 ; break ; + case 'x' : opt.rules = l.arg ; opt.rulestype = 2 ; break ; + default : dieusage() ; + } + } + argc -= l.ind ; argv += l.ind ; + if (!argc) dieusage() ; + if (!*argv[0]) dieusage() ; + + opt.port = required_getenv("SOCKS_PORT"); + opt.addr_type = required_getenv("SOCKS_ADDR_TYPE"); + opt.addr = required_getenv("SOCKS_ADDR"); + + if(opt.rulestype == 0) { + xexec(argv); + } + + check_result result = { S6_ACCESSRULES_ALLOW, S6_ACCESSRULES_PARAMS_ZERO }; + + if(strcmp(opt.addr_type, "dns") == 0) { + check_access_dns(&opt, &result); + } else if ( strcmp(opt.addr_type, "ip4") == 0 || strcmp(opt.addr_type, "ip6") == 0) { + check_access_ip(&opt, &result); + } else { + strerr_dieinvalid(100, "SOCKS_ADDR_TYPE") ; + } + + switch (result.accepted) { + case S6_ACCESSRULES_ERROR: + strerr_diefu6sys(111, "check ", rulestypestr[opt.rulestype], " ruleset for ", opt.addr_type, " in ", opt.rules); + case S6_ACCESSRULES_ALLOW: + break ; + case S6_ACCESSRULES_DENY: + // if (verbosity >= 2) log_deny(getpid(), &remoteip) ; + return 1; + case S6_ACCESSRULES_NOTFOUND: + // if (flagdnslookup) { + // break; + // } + // if (verbosity >= 2) { + // log_deny(getpid(), opt.addr); + // } + return 1 ; + default: X() ; + } + + if (result.params.exec.len) + { + char *specialargv[4] = { "execlineb", "-c", result.params.exec.s, 0 } ; + xmexec_m((char const *const *)specialargv, result.params.env.s, result.params.env.len) ; + } + xexec(argv); + +} + +/* vim: sw=4 sts=4 et +*/ diff --git a/src/ucspi-socksserver-connected.c b/src/ucspi-socksserver-connected.c @@ -10,6 +10,8 @@ #include <skalibs/strerr.h> #include <skalibs/types.h> +#include "socks.h" + #define PROG "ucspi-socketserver-connect" #define SOCKS_RESERVED 0x00 @@ -21,7 +23,7 @@ typedef unsigned char socks5_addr_type_t; #define SOCKS5_ADDR_TYPE_DOMAIN 0x03 #define SOCKS5_ADDR_TYPE_IP6 0x04 -typedef unsigned char socks5_response_t; +typedef unsigned char socks5_reply_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 @@ -32,9 +34,6 @@ typedef unsigned char socks5_response_t; #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); @@ -63,7 +62,7 @@ void write1(const void *buf, ssize_t n) } } -void socks5_response_ok(socks5_addr_type_t addr_type, void *addr, in_port_t port) +void socks5_reply_ok(socks5_addr_type_t addr_type, void *addr, in_port_t port) { size_t size; switch(addr_type) { @@ -81,13 +80,13 @@ void socks5_response_ok(socks5_addr_type_t addr_type, void *addr, in_port_t port break; } - char response[] = { + char reply[] = { SOCKS_VERSION_5, SOCKS5_REPLY_OK, SOCKS_RESERVED, addr_type, }; - write1(response, sizeof(response)); + write1(reply, sizeof(reply)); if (addr_type == SOCKS5_ADDR_TYPE_DOMAIN) { /* prefix domain by length */ @@ -100,7 +99,7 @@ void socks5_response_ok(socks5_addr_type_t addr_type, void *addr, in_port_t port write1(&port, sizeof(port)); } -void socks4_send_response(int status) +void socks4_send_reply(int status) { char resp[8] = {0x00, (char)status, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; write1(resp, sizeof(resp)); @@ -127,19 +126,19 @@ int main (int argc, char const *const *argv) 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); + socks5_reply_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); + socks5_reply_ok(SOCKS5_ADDR_TYPE_IP6, &in6_addr->sin6_addr, in6_addr->sin6_port); break; default: - socks5_response_ok(SOCKS5_ADDR_TYPE_DOMAIN, "localhost", 0); + socks5_reply_ok(SOCKS5_ADDR_TYPE_DOMAIN, "localhost", 0); break; } } else if (strcmp(env_socks_version, "4") == 0) { - socks4_send_response(SOCKS4_STATUS_OK); + socks4_send_reply(SOCKS4_STATUS_OK); } else { strerr_dieinvalid(100, "SOCKS_VERSION") ; diff --git a/src/ucspi-socksserver.c b/src/ucspi-socksserver.c @@ -10,58 +10,12 @@ #include <skalibs/exec.h> #include <skalibs/strerr.h> +#include "socks.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); @@ -73,7 +27,7 @@ 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 socks5_command(socks5_request_header *rhdr); void handle_socks4(void); void do_connect(unsigned char socks_version, socks5_addr_type_t addr_type, char *buf, unsigned short int port); @@ -199,18 +153,17 @@ unsigned short socks_read_port(void) { void handle_socks5(char methods) { socks5_auth(methods, SOCKS5_AUTH_METHOD_NOAUTH); /* TODO: authentication support */ - socks5_cmd_header chdr; - socks5_command(&chdr); + socks5_request_header rhdr; + socks5_command(&rhdr); unsigned short port; - switch (chdr.atyp) { + switch (rhdr.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]; @@ -218,7 +171,6 @@ void handle_socks5(char methods) 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; @@ -230,7 +182,6 @@ void handle_socks5(char methods) 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"); @@ -244,7 +195,7 @@ void socks5_auth(int methods_count, socks5_auth_mode_t supported_auth_method) for (int i = 0; i < methods_count; i++) { char type; read0(&type, 1); - log_message("Method AUTH 0x%hhX", type); + // log_message("Method AUTH 0x%hhX", type); if (type == supported_auth_method) { supported = true; } @@ -283,13 +234,13 @@ void socks5_auth_notsupported(void) write1(answer, sizeof(answer)); } -void socks5_command(socks5_cmd_header *chdr) +void socks5_command(socks5_request_header *rhdr) { - 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); + read0(rhdr, sizeof(socks5_request_header)); + // log_message("Command %02hhX %02hhX %02hhX %02hhX", + // rhdr->version, rhdr->cmd, rhdr->reserved, rhdr->atyp); + assert(rhdr->version == SOCKS_VERSION_5); + assert(rhdr->cmd == SOCKS5_CMD_CONNECT); } @@ -306,10 +257,10 @@ void handle_socks4(void) 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); + // 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"); + // log_message("Socks4: connect by ip & port"); do_connect(4, SOCKS5_ADDR_TYPE_IP4, ip, port); } @@ -344,6 +295,27 @@ void socks_env_dns(const char *domain) { xenv("SOCKS_ADDR_TYPE", "dns"); } +void log_connect(unsigned char socks_version, socks5_addr_type_t addr_type, char *buf, unsigned short int port) { + char address[43]; + switch (addr_type) { + case SOCKS5_ADDR_TYPE_IP4: + snprintf(address, sizeof(address), "%hhu.%hhu.%hhu.%hhu", + buf[0], buf[1], buf[2], buf[3]); + break; + case SOCKS5_ADDR_TYPE_IP6: + snprintf(address, sizeof(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]); + break; + case SOCKS5_ADDR_TYPE_DOMAIN: + break; + default: + strerr_dief1x(110, "Internal error: unhandled address type"); + } + log_message("SOCKS%hhd %s:%hd", socks_version, addr_type == SOCKS5_ADDR_TYPE_DOMAIN ? buf : address, port); +} + void do_connect(unsigned char socks_version, socks5_addr_type_t addr_type, char *buf, unsigned short int port) { switch(socks_version) { case 4: @@ -381,8 +353,9 @@ void do_connect(unsigned char socks_version, socks5_addr_type_t addr_type, char break; default: strerr_dief1x(110, "Internal error: unhandled address type"); - break; } + + log_connect(socks_version, addr_type, buf, port); xmexec(argv); }