IT WORKS!!! :D (and it's super-hacky) master
authorJann Horn <jann@thejh.net>
Mon, 30 Nov 2015 23:53:27 +0000 (00:53 +0100)
committerJann Horn <jann@thejh.net>
Mon, 30 Nov 2015 23:53:27 +0000 (00:53 +0100)
.gitignore [new file with mode: 0644]
proxyspace.c

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..f9b04c1
--- /dev/null
@@ -0,0 +1 @@
+proxyspace
\ No newline at end of file
index 2b0acc9..5b1dbd8 100644 (file)
@@ -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
 
 #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__)
 
@@ -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 <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);
@@ -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