From: Jann Horn Date: Mon, 30 Nov 2015 23:53:27 +0000 (+0100) Subject: IT WORKS!!! :D (and it's super-hacky) X-Git-Url: http://git.thejh.net/?a=commitdiff_plain;p=proxyspace.git IT WORKS!!! :D (and it's super-hacky) --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f9b04c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +proxyspace \ No newline at end of file diff --git a/proxyspace.c b/proxyspace.c index 2b0acc9..5b1dbd8 100644 --- a/proxyspace.c +++ b/proxyspace.c @@ -1,4 +1,7 @@ -// compile with gcc -o proxyspace proxyspace.c -Wall -std=gnu99 +// compile with gcc -o proxyspace proxyspace.c -Wall -std=gnu99 -lbsd + +// general note: Yeah, I don't always free stuff. Doesn't run in loops though, +// so it's not more than a KB or so that's wasted. Who cares. #define _GNU_SOURCE @@ -16,6 +19,11 @@ #include #include #include +#include +#include +#include +#include +#include #define eprintf(...) fprintf(stderr, __VA_ARGS__) @@ -72,12 +80,95 @@ char *xasprintf(char *fmt, ...) { return ret; } +char *randhex(void) { + char *ret = malloc(33); + if (!ret) + errx(1, "unable to malloc"); + int fd = open("/dev/urandom", O_RDONLY); + if (fd == -1) + err(1, "unable to open urandom"); + if (read(fd, ret, 32) != 32) + errx(1, "read from urandom failed"); + close(fd); + for (int i=0; i<32; i++) { + ret[i] = (ret[i] & 0xf) + 'A'; + } + ret[32] = 0; + return ret; +} + +int recvfd(int sock) { + int len = sizeof(struct cmsghdr) + sizeof(int); + struct cmsghdr *hdr = alloca(len); + char ch = '\0'; + struct iovec vec = { + .iov_base = &ch, + .iov_len = 1 + }; + struct msghdr msg = { + .msg_control = hdr, + .msg_controllen = len, + .msg_iov = &vec, + .msg_iovlen = 1 + }; + if (recvmsg(sock, &msg, 0) < 0) + err(1, "unable to receive fd"); + if (hdr->cmsg_len != len || hdr->cmsg_level != SOL_SOCKET || hdr->cmsg_type != SCM_RIGHTS) + errx(1, "got bad message"); + return *(int*)CMSG_DATA(hdr); +} + +void sendfd(int sock, int fd) { + int len = sizeof(struct cmsghdr) + sizeof(int); + struct cmsghdr *hdr = alloca(len); + *hdr = (struct cmsghdr) { + .cmsg_len = len, + .cmsg_level = SOL_SOCKET, + .cmsg_type = SCM_RIGHTS + }; + *(int*)CMSG_DATA(hdr) = fd; + char ch = '\0'; + struct iovec vec = { + .iov_base = &ch, + .iov_len = 1 + }; + struct msghdr msg = { + .msg_control = hdr, + .msg_controllen = len, + .msg_iov = &vec, + .msg_iovlen = 1 + }; + if (sendmsg(sock, &msg, 0) < 0) + err(1, "unable to send fd"); +} + int main(int argc, char **argv) { - // invocation: ./proxyspace [ ...] + if (!strcmp(argv[1], "--stage2")) { + eprintf("stage2 helper running...\n"); + int usock = socket(AF_UNIX, SOCK_STREAM, 0); + if (usock == -1) + err(1, "unable to create unix sock"); + struct sockaddr_un unix_addr = { + .sun_family = AF_UNIX + }; + char *socket_path = getenv("PROXYSPACE_SOCKET_PATH"); + if (!socket_path) + errx(1, "helper called without socket path in env"); + if (strlcpy(unix_addr.sun_path, socket_path, UNIX_PATH_MAX) >= UNIX_PATH_MAX) + errx(1, "string is too long in helper"); + if (connect(usock, (struct sockaddr *)&unix_addr, sizeof(unix_addr))) + err(1, "unable to connect to unix socket"); + + int proxyfd = recvfd(usock); + sendfd(1, proxyfd); + eprintf("sent proxy fd to ssh"); + return 0; + } + + // invocation: ./proxyspace char *sshhost = argv[1]; char *sshport = argv[2]; char *sshtarget = argv[3]; - char **next_argv = argv+4; eprintf("connecting...\n"); int netfd = netopen(sshhost, sshport); @@ -92,19 +183,65 @@ int main(int argc, char **argv) { err(1, "unable to unshare (maybe unprivileged user namespaces are disabled)"); // Annoying. Fix up our mapped uid and gid to zero. + // On super-new kernels, we could use ambient capabilities here, that would avoid some trouble. write_file("/proc/self/setgroups", "deny"); write_file("/proc/self/uid_map", xasprintf("0 %d 1\n", outer_uid)); write_file("/proc/self/gid_map", xasprintf("0 %d 1\n", outer_gid)); + // we need to pass an FD to the helper through ssh, but ssh closes fds automatically. + // so we go for the ugly solution and pass the fd through unix domain sockets twice. + char *tempdir = getenv("XDG_RUNTIME_DIR"); + if (!tempdir) + errx(1, "no XDG_RUNTIME_DIR set in the environment - please set it to a safe tempdir"); + char *socket_path = xasprintf("%s/proxyspace.socket-%s", tempdir, randhex()); + int usock = socket(AF_UNIX, SOCK_STREAM, 0); + if (usock == -1) + err(1, "unable to create unix sock"); + struct sockaddr_un unix_addr = { + .sun_family = AF_UNIX + }; + if (strlcpy(unix_addr.sun_path, socket_path, UNIX_PATH_MAX) >= UNIX_PATH_MAX) + errx(1, "stupid string is too long"); + if (bind(usock, (struct sockaddr *)&unix_addr, sizeof(unix_addr))) + err(1, "unable to bind unix socket"); + if (listen(usock, 5)) + err(1, "unable to listen"); + eprintf("launching ssh...\n"); pid_t ssh_pid = fork_nofail(); if (ssh_pid == 0) { - // -f -n doesn't seem to be working here... - execlp("ssh", "ssh", "-w", "0", + close(usock); + close(netfd); + setenv("PROXYSPACE_SOCKET_PATH", socket_path, 1); + // ssh is too smart here and determines the homedir based on the UID it sees (0) + // instead of looking at $HOME like everyone else. therefore *sigh* also make a + // new mount namespace while we're at it and then bind-mount our $HOME to /root. + if (unshare(CLONE_NEWNS)) + err(1, "unable to unshare mount ns"); + if (mount(xasprintf("%s/", getenv("HOME")), "/root", NULL, MS_BIND|MS_REC, NULL)) + err(1, "unable to do the homedir fixup step"); + + execlp("ssh", "ssh", "-v", "-f", "-n", "-w", "0:42", // HACK hardcoded remote tunnel interface number (local is okay) "-o", xasprintf("ProxyCommand /proc/%d/exe --stage2 %d", (int)getppid(), netfd), - sshtarget, NULL); + "-o", "ProxyUseFdpass yes", + sshtarget, + + /* really hacky, yes */ + "ifconfig tun42 192.168.244.1 pointopoint 192.168.244.2 netmask 255.255.255.0 &&" + "while true; do sleep 60; done", + + NULL); err(1, "execlp for ssh failed"); } + + int usock_c = accept(usock, NULL, NULL); + if (usock_c == -1) + err(1, "can't accept"); + sendfd(usock_c, netfd); + close(usock_c); + close(usock); + eprintf("sent fd to helper.\n"); + int ssh_status; pid_t waitres; do { @@ -117,7 +254,11 @@ int main(int argc, char **argv) { if (WEXITSTATUS(ssh_status) != 0) errx(1, "ssh exited with exit code %d", (int)WEXITSTATUS(ssh_status)); eprintf("ssh seems to be connected!\n"); + if (system("/sbin/ifconfig tun0 192.168.244.2 pointopoint 192.168.244.1 netmask 255.255.255.0")) + errx(1, "ifconfig failed"); + if (system("ip route replace default via 192.168.244.1")) + errx(1, "route failed"); - execvp(next_argv[0], next_argv); - err(1, "unable to execute specified next command \"%s\"", next_argv[0]); + execlp("/bin/bash", "bash", NULL); + err(1, "unable to execute bash"); } \ No newline at end of file