ssh borked
[proxyspace.git] / proxyspace.c
1 // compile with gcc -o proxyspace proxyspace.c -Wall -std=gnu99
2
3 #define _GNU_SOURCE
4
5 #include <netdb.h>
6 #include <sys/types.h>
7 #include <sys/socket.h>
8 #include <sys/wait.h>
9 #include <netinet/in.h>
10 #include <netinet/tcp.h>
11 #include <unistd.h>
12 #include <err.h>
13 #include <stdio.h>
14 #include <sched.h>
15 #include <sys/stat.h>
16 #include <fcntl.h>
17 #include <string.h>
18 #include <stdarg.h>
19
20 #define eprintf(...) fprintf(stderr, __VA_ARGS__)
21
22 const struct addrinfo tcp_hints = {
23   .ai_flags = AI_ADDRCONFIG,
24   .ai_family = AF_UNSPEC,
25   .ai_socktype = SOCK_STREAM,
26   .ai_protocol = 0
27 };
28
29 int netopen(const char *node, const char *service) {
30   struct addrinfo *addrs;
31   if (getaddrinfo(node, service, &tcp_hints, &addrs))
32     errx(1, "unable to resolve address");
33
34   int s = socket(addrs[0].ai_family, addrs[0].ai_socktype, addrs[0].ai_protocol);
35   if (s == -1)
36     err(1, "unable to create socket");
37
38   if (connect(s, addrs[0].ai_addr, addrs[0].ai_addrlen))
39     err(1, "unable to connect");
40
41   freeaddrinfo(addrs);
42   return s;
43 }
44
45 pid_t fork_nofail(void) {
46   pid_t res = fork();
47   if (res == -1)
48     err(1, "fork failed");
49   return res;
50 }
51
52 void write_file(char *path, char *data) {
53   int fd = open(path, O_WRONLY);
54   if (fd == -1)
55     err(1, "unable to open \"%s\" for writing", path);
56   ssize_t r = write(fd, data, strlen(data));
57   if (r == -1)
58     err(1, "write to \"%s\" failed", path);
59   if (r != strlen(data))
60     errx(1, "write to \"%s\" failed", path);
61   if (close(fd))
62     err(1, "close failed");
63 }
64
65 char *xasprintf(char *fmt, ...) {
66   va_list ap;
67   va_start(ap, fmt);
68   char *ret;
69   if (vasprintf(&ret, fmt, ap) == -1)
70     errx(1, "memory allocation failed");
71   va_end(ap);
72   return ret;
73 }
74
75 int main(int argc, char **argv) {
76   // invocation: ./proxyspace <sshhost-tcp> <sshport-tcp> <sshtarget-name> <localcommand> [<arg1> ...]
77   char *sshhost = argv[1];
78   char *sshport = argv[2];
79   char *sshtarget = argv[3];
80   char **next_argv = argv+4;
81
82   eprintf("connecting...\n");
83   int netfd = netopen(sshhost, sshport);
84   eprintf("TCP connection successful\n");
85
86   uid_t outer_uid = getuid();
87   gid_t outer_gid = getgid();
88
89   // This is the point of no return: After this, we don't have access to the normal
90   // network anymore.
91   if (unshare(CLONE_NEWUSER|CLONE_NEWNET))
92     err(1, "unable to unshare (maybe unprivileged user namespaces are disabled)");
93
94   // Annoying. Fix up our mapped uid and gid to zero.
95   write_file("/proc/self/setgroups", "deny");
96   write_file("/proc/self/uid_map", xasprintf("0 %d 1\n", outer_uid));
97   write_file("/proc/self/gid_map", xasprintf("0 %d 1\n", outer_gid));
98
99   eprintf("launching ssh...\n");
100   pid_t ssh_pid = fork_nofail();
101   if (ssh_pid == 0) {
102     // -f -n doesn't seem to be working here...
103     execlp("ssh", "ssh", "-w", "0",
104       "-o", xasprintf("ProxyCommand /proc/%d/exe --stage2 %d", (int)getppid(), netfd),
105       sshtarget, NULL);
106     err(1, "execlp for ssh failed");
107   }
108   int ssh_status;
109   pid_t waitres;
110   do {
111     waitres = wait(&ssh_status);
112   } while (waitres != -1 && waitres != ssh_pid);
113   if (waitres == -1)
114     err(1, "unable to wait for ssh???");
115   if (!WIFEXITED(ssh_status))
116     errx(1, "uh... looks like ssh exited in a weird way?");
117   if (WEXITSTATUS(ssh_status) != 0)
118     errx(1, "ssh exited with exit code %d", (int)WEXITSTATUS(ssh_status));
119   eprintf("ssh seems to be connected!\n");
120
121   execvp(next_argv[0], next_argv);
122   err(1, "unable to execute specified next command \"%s\"", next_argv[0]);
123 }