350 lines
8.3 KiB
C
350 lines
8.3 KiB
C
#define _GNU_SOURCE
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sched.h>
|
|
#include <fcntl.h>
|
|
#include <assert.h>
|
|
#include <signal.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <linux/limits.h>
|
|
#include <linux/if_arp.h>
|
|
|
|
#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);
|
|
} |