-// 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
#include <fcntl.h>
#include <string.h>
#include <stdarg.h>
+#include <alloca.h>
+#include <stdlib.h>
+#include <linux/un.h>
+#include <bsd/string.h>
+#include <sys/mount.h>
#define eprintf(...) fprintf(stderr, __VA_ARGS__)
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 <sshhost-tcp> <sshport-tcp> <sshtarget-name> <localcommand> [<arg1> ...]
+ 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 <sshhost-tcp> <sshport-tcp> <sshtarget-name>
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);
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 {
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