summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2021-07-28 18:24:23 +0300
committerEvgeny Zinoviev <me@ch1p.io>2021-07-28 18:24:23 +0300
commit42df77cfe8cfc87c98f584875202c9ae95d6e019 (patch)
tree410b55b54c2eb6816d821f3bb132287b823513b4
initial
-rw-r--r--.gitignore3
-rw-r--r--CMakeLists.txt12
-rw-r--r--README.md2
-rw-r--r--main.c185
4 files changed, 202 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..207dd8a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/cmake-build-debug
+/cmake-build-release
+.idea
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..18493d0
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,12 @@
+cmake_minimum_required(VERSION 3.19)
+project(captive-netns-helper C)
+
+set(CMAKE_C_STANDARD 99)
+
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+ set(CMAKE_INSTALL_PREFIX /usr/local/bin)
+endif(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+
+add_executable(captive-netns-helper main.c)
+install(TARGETS captive-netns-helper
+ RUNTIME DESTINATION bin) \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c1b7914
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+# captive-netns-helper
+
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..34f6df2
--- /dev/null
+++ b/main.c
@@ -0,0 +1,185 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sched.h>
+#include <getopt.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#define myerror(f_, ...) { \
+ fprintf(stderr, (f_), ##__VA_ARGS__); \
+ rc = 1; \
+ goto end; \
+ }
+
+#define stderror(prefix_) myerror(prefix_ ": %s.\n", strerror(errno)); \
+
+
+static void usage(char* progname)
+{
+ printf("Usage: %s OPTIONS COMMAND [ARGS...]\n\n", progname);
+ printf("Options:\n"
+ " -h, --help: show this help\n"
+ " -n, --nameserver NAMESERVER\n"
+ " -f, --ns-file FILE\n"
+ " -u, --uid UID\n"
+ " -g, --gid GID\n"
+ " -e, --env VAR=VALUE\n"
+ );
+}
+
+int main(int argc, char* argv[])
+{
+ int rc = 0;
+ int temp_fd = 0;
+ int ns_fd = 0;
+ uid_t uid = 0;
+ uid_t gid = 0;
+ char cwd[PATH_MAX];
+ char temp_name[PATH_MAX] = {0};
+ char *ns_file = NULL;
+ char *nameserver = NULL;
+ bool ismounted;
+
+ int envi = 0;
+ size_t envn = sizeof(char*)*(argc/2);
+ char **envp = malloc(envn);
+ memset(envp, 0, envn);
+
+ if (argc < 2) {
+ usage(argv[0]);
+ return 1;
+ }
+
+ int opt;
+ struct option long_options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"nameserver", required_argument, NULL, 'n'},
+ {"ns-file", required_argument, NULL, 'f'},
+ {"uid", required_argument, NULL, 'u'},
+ {"gid", required_argument, NULL, 'g'},
+ {"env", required_argument, NULL, 'e'},
+ {0, 0, 0, 0}
+ };
+
+ while ((opt = getopt_long(argc, argv, "+hn:f:u:g:", long_options, NULL)) != EOF) {
+ switch (opt) {
+ case 'h':
+ usage(argv[0]);
+ return 0;
+
+ case 'n':
+ nameserver = optarg;
+ break;
+
+ case 'f':
+ ns_file = optarg;
+ break;
+
+ case 'u':
+ uid = (uid_t)atoi(optarg);
+ break;
+
+ case 'g':
+ gid = (uid_t)atoi(optarg);
+ break;
+
+ case 'e':
+ if (envi == envn-1)
+ fprintf(stderr, "warn: skipping --env %s\n", optarg);
+ else
+ envp[envi++] = optarg;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (nameserver == NULL)
+ myerror("error: --nameserver is required\n");
+
+ if (ns_file == NULL)
+ myerror("error: --ns-file is required\n");
+
+ if (geteuid() != 0)
+ myerror("error: you must be root.\n");
+
+ if (unshare(CLONE_NEWNS) == -1)
+ stderror("unshare");
+
+ /* save cwd */
+ getcwd(cwd, PATH_MAX);
+
+ /* create temp file */
+ strcpy(temp_name, "/tmp/capresolv.XXXXXX");
+ temp_fd = mkstemp(temp_name);
+ dprintf(temp_fd, "nameserver %s\n", nameserver);
+ close(temp_fd);
+ temp_fd = 0;
+
+ chmod(temp_name, 0644);
+
+ /* mount /etc/resolv.conf */
+ if (mount(temp_name, "/etc/resolv.conf", NULL, MS_BIND, NULL) == -1)
+ stderror("mount");
+ ismounted = true;
+
+ ns_fd = open(ns_file, O_RDONLY);
+ if (ns_fd == -1)
+ stderror("open");
+
+ /* change to netns */
+ if (setns(ns_fd, CLONE_NEWNET) == -1)
+ stderror("setns");
+
+ pid_t pid = fork();
+ if (!pid) {
+ /* change real and effective user and group (group first, then user) */
+ if (gid != getegid()) {
+ if (setregid(gid, gid) == -1)
+ stderror("setregid");
+ }
+
+ if (uid != geteuid()) {
+ if (setreuid(uid, uid) == -1)
+ myerror("setreuid");
+ }
+
+ /* restore cwd */
+ if (chdir(cwd) == -1)
+ stderror("chdir");
+
+ /* launch program */
+ if (execvpe(argv[optind], (char *const *)argv+optind, envp) == -1)
+ stderror("execvpe");
+
+ return 0;
+ }
+
+ waitpid(pid, NULL, 0);
+
+end:
+ if (ismounted)
+ umount(temp_name);
+
+ if (temp_fd > 0)
+ close(temp_fd);
+
+ if (ns_fd > 0)
+ close(ns_fd);
+
+ if (temp_name[0] != 0) {
+ unlink(temp_name);
+ }
+
+ return rc;
+}