commit 440f4151a20aeb9817f9cc726b6582d6fc998059 Author: E. S. Date: Wed Nov 27 02:19:12 2024 +0300 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aeaa3ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +rl_clearnet diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..26ab702 --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +CC := gcc + +CFLAGS = -O2 -std=c99 -Wall -W +LDFLAGS = + +INSTALL = /usr/bin/env install +PREFIX = /usr/local + +all: rl_clearnet.o + $(CC) $(CFLAGS) -o rl_clearnet $^ $(LDFLAGS) + +install: all + $(INSTALL) rl_clearnet $(PREFIX)/bin + chmod u+s $(PREFIX)/bin/rl_clearnet + +clean: + rm -f *.o rl_clearnet + +%.o: %.c + $(CC) $(CFLAGS) -c $^ -I. -o $@ + +.PHONY: all install clean diff --git a/config.def.h b/config.def.h new file mode 100644 index 0000000..9500c6e --- /dev/null +++ b/config.def.h @@ -0,0 +1,13 @@ +#define NETNS_PATH "/run/netns/clearnet" +#define DHCPCD_PATH "/usr/bin/dhcpcd" +#define EXEC_UID 1000 +#define EXEC_GUD 1000 + +static char *env_vars[] = {"DISPLAY", "HOME", "PWD", "EDITOR", "USER", "XAUTHORITY", "LANG", "DBUS_SESSION_BUS_ADDRESS"}; + +static char *firefox_cmd[] = {"firefox", "-P", "clearnet", NULL}; +static char *thunderbird_cmd[] = {"thunderbird", "-P", "clearnet", NULL}; +static char *ping_cmd[] = {"ping", "8.8.8.8", NULL}; + +static char **commands[] = {firefox_cmd, thunderbird_cmd, ping_cmd}; +static char *aliases[] = {"fx", "tb", "ping"}; diff --git a/rl_clearnet.c b/rl_clearnet.c new file mode 100644 index 0000000..080e65e --- /dev/null +++ b/rl_clearnet.c @@ -0,0 +1,350 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl_clearnet.h" +#include "config.def.h" + +static pid_t child_pid = -1; + +static void sigint_handler(int); +static void usage(const char *); +static int get_default_iface(char [IFNAMSIZ]); +static int get_domain_name_servers(const char *, char *[4]); + +int +main(int argc, char* argv[]) +{ + assert(ARRAY_SIZE(commands) == ARRAY_SIZE(aliases)); + + int rc = 1; + int tempfd = 0; + int nsfd = 0; + char cwd[PATH_MAX]; + char temp_name[PATH_MAX] = {0}; + char iface[IFNAMSIZ]; + int mounted = 0; + int cmdi = -1; + char *dns[4] = {0}; + size_t i; + + if (argc != 2) { + usage(argv[0]); + return 1; + } + + if (!strcmp(argv[1], "-h")) { + usage(argv[0]); + return 1; + } + + for (i = 0; i < (int)ARRAY_SIZE(aliases); i++) { + if (!strcmp(argv[1], aliases[i])) { + cmdi = (int)i; + break; + } + } + + if (cmdi == -1) { + fprintf(stderr, "error: alias %s not found\n", argv[1]); + return 1; + } + + if (geteuid() != 0) { + fprintf(stderr, "error: you must be root.\n"); + return 1; + } + + if (unshare(CLONE_NEWNS) == -1) { + perror("unshare"); + return 1; + } + + /* save cwd */ + if (!getcwd(cwd, PATH_MAX)) { + perror("getcwd"); + return 1; + } + + /* get default network interface */ + if (get_default_iface(iface) != 0) { + fprintf(stderr, "error: get_default_iface failed"); + return 1; + } + + /* get default domain name servers */ + if (get_domain_name_servers(iface, dns) != 0) { + fprintf(stderr, "error: get_domain_name_servers failed"); + return 1; + } + + /* create temp resolv.conf */ + strcpy(temp_name, "/tmp/rl_clearnet.XXXXXX"); + if ((tempfd = mkstemp(temp_name)) == -1) { + perror("mkstemp"); + goto end; + } + for (i = 0; i < ARRAY_SIZE(dns); i++) { + if (dns[i]) + dprintf(tempfd, "nameserver %s\n", dns[i]); + } + close(tempfd); + chmod(temp_name, 0644); + + /* bind-mount it as /etc/resolv.conf */ + if (mount(temp_name, "/etc/resolv.conf", NULL, MS_BIND, NULL) == -1) { + perror("mount"); + goto end; + } + mounted = 1; + + nsfd = open(NETNS_PATH, O_RDONLY); + if (nsfd == -1) { + perror("open"); + goto end; + } + + /* change to netns */ + if (setns(nsfd, CLONE_NEWNET) == -1) { + perror("setns"); + goto end; + } + + /* handle ctrl+c */ + struct sigaction sa; + sa.sa_handler = sigint_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGINT, &sa, NULL); + + child_pid = fork(); + if (!child_pid) { + /* change real and effective user and group */ + if (EXEC_GUD != getegid()) { + if (setregid(EXEC_GUD, EXEC_GUD) == -1) { + perror("setregid"); + goto end; + } + } + + if (EXEC_UID != geteuid()) { + if (setreuid(EXEC_UID, EXEC_UID) == -1) { + perror("setreuid"); + goto end; + } + } + + /* restore cwd */ + if (chdir(cwd) == -1) { + perror("chdir"); + goto end; + } + + /* allocate environment variables list */ + char **envp = malloc((ARRAY_SIZE(env_vars) + 1) * sizeof(char *)); + if (!envp) { + perror("malloc"); + goto end; + } + + size_t count = 0; + for (i = 0; i < ARRAY_SIZE(env_vars); i++) { + char *value = getenv(env_vars[i]); + if (value && *value) { + size_t len = strlen(env_vars[i]) + strlen(value) + 2; // key=value + '=' + '\0' + envp[count] = malloc(len); + if (!envp[count]) { + for (size_t j = 0; j < count; j++) + free(envp[j]); + free(envp); + perror("malloc"); + return 1; + } + snprintf(envp[count], len, "%s=%s", env_vars[i], value); + count++; + } + } + envp[count] = NULL; + + /* launch program */ + if (execvpe(*commands[cmdi], (char *const *)commands[cmdi], envp) == -1) + perror("execvpe"); + else + rc = 0; + for (i = 0; i < count; i++) + free(envp[i]); + free(envp); + return rc; + } + + rc = 0; + + int status = 0; + wait(&status); + if (WIFEXITED(status)) + rc = WEXITSTATUS(status); + else if (WIFSIGNALED(status)) + rc = 128 + WTERMSIG(status); + +end: + for (i = 0; i < ARRAY_SIZE(dns); i++) { + if (dns[i]) + free(dns[i]); + } + + if (mounted) + umount(temp_name); + + if (nsfd > 0) + close(nsfd); + + if (*temp_name) + unlink(temp_name); + + return rc; +} + +void +usage(const char *prog) +{ + printf("Usage: %s [-h] ALIAS\n\n", prog); + printf("Aliases:\n"); + for (size_t i = 0; i < ARRAY_SIZE(aliases); i++) + printf(" %s\n", aliases[i]); + printf("\n"); +} + +int +get_default_iface(char dst[IFNAMSIZ]) +{ + FILE *fp; + char line[256]; + char iface[IFNAMSIZ]; + char destination[64], gateway[64], flags[64], refcnt[64], use[64], metric[64], mask[64], mtu[64], window[64], irtt[64]; + struct ifreq ifr; + int found = 0; + + fp = fopen("/proc/net/route", "r"); + if (!fp) { + perror("fopen"); + return 1; + } + + fgets(line, sizeof(line), fp); /* skip header */ + while (fgets(line, sizeof(line), fp)) { + sscanf(line, "%s %s %s %s %s %s %s %s %s %s %s", + iface, destination, gateway, flags, refcnt, use, metric, mask, mtu, window, irtt); + + /* check if default route */ + if (strcmp(destination, "00000000") == 0) { + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + perror("socket"); + fclose(fp); + return 1; + } + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, iface, IFNAMSIZ-1); + + if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) == 0) { + if (ifr.ifr_hwaddr.sa_family == ARPHRD_ETHER) { + strlcpy(dst, iface, IFNAMSIZ-1); + found = 1; + close(sockfd); + break; + } + } else { + perror("ioctl"); + } + close(sockfd); + } + } + + fclose(fp); + + return found ? 0 : 1; +} + +int +get_domain_name_servers(const char *iface, char *servers[4]) +{ + char cmd[256]; + FILE *fp; + char line[1024]; + char domain_name_servers_line[1024]; + int found = 0; + int server_count = 0; + size_t len; + + if (snprintf(cmd, sizeof(cmd), DHCPCD_PATH " -U %s", iface) >= (int)sizeof(cmd)) { + fprintf(stderr, "error: interface name is too long\n"); + return 1; + } + + fp = popen(cmd, "r"); + if (!fp) { + perror("popen"); + return 1; + } + + while (fgets(line, sizeof(line), fp)) { + /* remove any trailing newline character */ + line[strcspn(line, "\n")] = '\0'; + + /* if line starts with "domain_name_servers=" */ + if (strncmp(line, "domain_name_servers=", 20) == 0) { + found = 1; + len = strlen(line + 20); + if (len >= sizeof(domain_name_servers_line)) + len = sizeof(domain_name_servers_line)-1; + memcpy(domain_name_servers_line, line + 20, len); + domain_name_servers_line[len] = '\0'; + break; + } + } + + if (pclose(fp) == -1) { + perror("pclose"); + return 1; + } + + if (!found) { + fprintf(stderr, "error: domain_name_servers not found\n"); + return 1; + } + + /* parse domain_name_servers_line */ + char *token = strtok(domain_name_servers_line, " "); + while (token && server_count < 4) { + servers[server_count] = strdup(token); + if (!servers[server_count]) { + fprintf(stderr, "error: memory allocation failed\n"); + return 1; + } + server_count++; + token = strtok(NULL, " "); + } + + return 0; +} + +void +sigint_handler(int sig) +{ + (void)sig; + if (!child_pid) + kill(child_pid, SIGTERM); +} \ No newline at end of file diff --git a/rl_clearnet.h b/rl_clearnet.h new file mode 100644 index 0000000..ca31914 --- /dev/null +++ b/rl_clearnet.h @@ -0,0 +1 @@ +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))