1 // compile with gcc -o proxyspace proxyspace.c -Wall -std=gnu99 -lbsd
3 // general note: Yeah, I don't always free stuff. Doesn't run in loops though,
4 // so it's not more than a KB or so that's wasted. Who cares.
10 #include <sys/socket.h>
12 #include <netinet/in.h>
13 #include <netinet/tcp.h>
25 #include <bsd/string.h>
26 #include <sys/mount.h>
28 #define eprintf(...) fprintf(stderr, __VA_ARGS__)
30 const struct addrinfo tcp_hints = {
31 .ai_flags = AI_ADDRCONFIG,
32 .ai_family = AF_UNSPEC,
33 .ai_socktype = SOCK_STREAM,
37 int netopen(const char *node, const char *service) {
38 struct addrinfo *addrs;
39 if (getaddrinfo(node, service, &tcp_hints, &addrs))
40 errx(1, "unable to resolve address");
42 int s = socket(addrs[0].ai_family, addrs[0].ai_socktype, addrs[0].ai_protocol);
44 err(1, "unable to create socket");
46 if (connect(s, addrs[0].ai_addr, addrs[0].ai_addrlen))
47 err(1, "unable to connect");
53 pid_t fork_nofail(void) {
56 err(1, "fork failed");
60 void write_file(char *path, char *data) {
61 int fd = open(path, O_WRONLY);
63 err(1, "unable to open \"%s\" for writing", path);
64 ssize_t r = write(fd, data, strlen(data));
66 err(1, "write to \"%s\" failed", path);
67 if (r != strlen(data))
68 errx(1, "write to \"%s\" failed", path);
70 err(1, "close failed");
73 char *xasprintf(char *fmt, ...) {
77 if (vasprintf(&ret, fmt, ap) == -1)
78 errx(1, "memory allocation failed");
84 char *ret = malloc(33);
86 errx(1, "unable to malloc");
87 int fd = open("/dev/urandom", O_RDONLY);
89 err(1, "unable to open urandom");
90 if (read(fd, ret, 32) != 32)
91 errx(1, "read from urandom failed");
93 for (int i=0; i<32; i++) {
94 ret[i] = (ret[i] & 0xf) + 'A';
100 int recvfd(int sock) {
101 int len = sizeof(struct cmsghdr) + sizeof(int);
102 struct cmsghdr *hdr = alloca(len);
108 struct msghdr msg = {
110 .msg_controllen = len,
114 if (recvmsg(sock, &msg, 0) < 0)
115 err(1, "unable to receive fd");
116 if (hdr->cmsg_len != len || hdr->cmsg_level != SOL_SOCKET || hdr->cmsg_type != SCM_RIGHTS)
117 errx(1, "got bad message");
118 return *(int*)CMSG_DATA(hdr);
121 void sendfd(int sock, int fd) {
122 int len = sizeof(struct cmsghdr) + sizeof(int);
123 struct cmsghdr *hdr = alloca(len);
124 *hdr = (struct cmsghdr) {
126 .cmsg_level = SOL_SOCKET,
127 .cmsg_type = SCM_RIGHTS
129 *(int*)CMSG_DATA(hdr) = fd;
135 struct msghdr msg = {
137 .msg_controllen = len,
141 if (sendmsg(sock, &msg, 0) < 0)
142 err(1, "unable to send fd");
145 int main(int argc, char **argv) {
146 if (!strcmp(argv[1], "--stage2")) {
147 eprintf("stage2 helper running...\n");
148 int usock = socket(AF_UNIX, SOCK_STREAM, 0);
150 err(1, "unable to create unix sock");
151 struct sockaddr_un unix_addr = {
152 .sun_family = AF_UNIX
154 char *socket_path = getenv("PROXYSPACE_SOCKET_PATH");
156 errx(1, "helper called without socket path in env");
157 if (strlcpy(unix_addr.sun_path, socket_path, UNIX_PATH_MAX) >= UNIX_PATH_MAX)
158 errx(1, "string is too long in helper");
159 if (connect(usock, (struct sockaddr *)&unix_addr, sizeof(unix_addr)))
160 err(1, "unable to connect to unix socket");
162 int proxyfd = recvfd(usock);
164 eprintf("sent proxy fd to ssh");
168 // invocation: ./proxyspace <sshhost-tcp> <sshport-tcp> <sshtarget-name>
169 char *sshhost = argv[1];
170 char *sshport = argv[2];
171 char *sshtarget = argv[3];
173 eprintf("connecting...\n");
174 int netfd = netopen(sshhost, sshport);
175 eprintf("TCP connection successful\n");
177 uid_t outer_uid = getuid();
178 gid_t outer_gid = getgid();
180 // This is the point of no return: After this, we don't have access to the normal
182 if (unshare(CLONE_NEWUSER|CLONE_NEWNET))
183 err(1, "unable to unshare (maybe unprivileged user namespaces are disabled)");
185 // Annoying. Fix up our mapped uid and gid to zero.
186 // On super-new kernels, we could use ambient capabilities here, that would avoid some trouble.
187 write_file("/proc/self/setgroups", "deny");
188 write_file("/proc/self/uid_map", xasprintf("0 %d 1\n", outer_uid));
189 write_file("/proc/self/gid_map", xasprintf("0 %d 1\n", outer_gid));
191 // we need to pass an FD to the helper through ssh, but ssh closes fds automatically.
192 // so we go for the ugly solution and pass the fd through unix domain sockets twice.
193 char *tempdir = getenv("XDG_RUNTIME_DIR");
195 errx(1, "no XDG_RUNTIME_DIR set in the environment - please set it to a safe tempdir");
196 char *socket_path = xasprintf("%s/proxyspace.socket-%s", tempdir, randhex());
197 int usock = socket(AF_UNIX, SOCK_STREAM, 0);
199 err(1, "unable to create unix sock");
200 struct sockaddr_un unix_addr = {
201 .sun_family = AF_UNIX
203 if (strlcpy(unix_addr.sun_path, socket_path, UNIX_PATH_MAX) >= UNIX_PATH_MAX)
204 errx(1, "stupid string is too long");
205 if (bind(usock, (struct sockaddr *)&unix_addr, sizeof(unix_addr)))
206 err(1, "unable to bind unix socket");
207 if (listen(usock, 5))
208 err(1, "unable to listen");
210 eprintf("launching ssh...\n");
211 pid_t ssh_pid = fork_nofail();
215 setenv("PROXYSPACE_SOCKET_PATH", socket_path, 1);
216 // ssh is too smart here and determines the homedir based on the UID it sees (0)
217 // instead of looking at $HOME like everyone else. therefore *sigh* also make a
218 // new mount namespace while we're at it and then bind-mount our $HOME to /root.
219 if (unshare(CLONE_NEWNS))
220 err(1, "unable to unshare mount ns");
221 if (mount(xasprintf("%s/", getenv("HOME")), "/root", NULL, MS_BIND|MS_REC, NULL))
222 err(1, "unable to do the homedir fixup step");
224 execlp("ssh", "ssh", "-v", "-f", "-n", "-w", "0:42", // HACK hardcoded remote tunnel interface number (local is okay)
225 "-o", xasprintf("ProxyCommand /proc/%d/exe --stage2 %d", (int)getppid(), netfd),
226 "-o", "ProxyUseFdpass yes",
229 /* really hacky, yes */
230 "ifconfig tun42 192.168.244.1 pointopoint 192.168.244.2 netmask 255.255.255.0 &&"
231 "while true; do sleep 60; done",
234 err(1, "execlp for ssh failed");
237 int usock_c = accept(usock, NULL, NULL);
239 err(1, "can't accept");
240 sendfd(usock_c, netfd);
243 eprintf("sent fd to helper.\n");
248 waitres = wait(&ssh_status);
249 } while (waitres != -1 && waitres != ssh_pid);
251 err(1, "unable to wait for ssh???");
252 if (!WIFEXITED(ssh_status))
253 errx(1, "uh... looks like ssh exited in a weird way?");
254 if (WEXITSTATUS(ssh_status) != 0)
255 errx(1, "ssh exited with exit code %d", (int)WEXITSTATUS(ssh_status));
256 eprintf("ssh seems to be connected!\n");
257 if (system("/sbin/ifconfig tun0 192.168.244.2 pointopoint 192.168.244.1 netmask 255.255.255.0"))
258 errx(1, "ifconfig failed");
259 if (system("ip route replace default via 192.168.244.1"))
260 errx(1, "route failed");
262 execlp("/bin/bash", "bash", NULL);
263 err(1, "unable to execute bash");