this repo has no description
1/*
2This file is part of Darling.
3
4Copyright (C) 2016-2023 Lubos Dolezel
5
6Darling is free software: you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation, either version 3 of the License, or
9(at your option) any later version.
10
11Darling is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with Darling. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#include <stdio.h>
21#include <sys/types.h>
22#include <unistd.h>
23#include <stdlib.h>
24#include <string.h>
25#include <alloca.h>
26#include <errno.h>
27#include <fcntl.h>
28#include <sys/stat.h>
29#include <signal.h>
30#include <stdbool.h>
31#include <sched.h>
32#include <sys/poll.h>
33#include <sys/socket.h>
34#include <sys/un.h>
35#include <getopt.h>
36#include <termios.h>
37#include <pty.h>
38#include <pwd.h>
39#include "../shellspawn/shellspawn.h"
40#include "darling.h"
41#include "darling-config.h"
42
43// Between Linux 4.9 and 4.11, a strange bug has been introduced
44// which prevents connecting to Unix sockets if the socket was
45// created in a different mount namespace or under overlayfs
46// (dunno which one is really responsible for this).
47#define USE_LINUX_4_11_HACK 1
48
49char *prefix;
50uid_t g_originalUid, g_originalGid;
51bool g_fixPermissions = false;
52char g_workingDirectory[4096];
53
54int main(int argc, char ** argv)
55{
56 pid_t pidInit;
57
58 if (argc <= 1)
59 {
60 showHelp(argv[0]);
61 return 1;
62 }
63
64 if (geteuid() != 0)
65 {
66 missingSetuidRoot();
67 return 1;
68 }
69
70 g_originalUid = getuid();
71 g_originalGid = getgid();
72
73 setuid(0);
74 setgid(0);
75
76 prefix = getenv("DPREFIX");
77 if (!prefix)
78 prefix = defaultPrefixPath();
79 if (!prefix)
80 return 1;
81 if (strlen(prefix) > 255)
82 {
83 fprintf(stderr, "Prefix path too long\n");
84 return 1;
85 }
86 unsetenv("DPREFIX");
87 getcwd(g_workingDirectory, sizeof(g_workingDirectory));
88
89 if (!checkPrefixDir())
90 {
91 setupPrefix();
92 g_fixPermissions = true;
93 }
94 checkPrefixOwner();
95
96 int c;
97 while (1)
98 {
99 static struct option long_options[] =
100 {
101 {"help", no_argument, 0, 0},
102 {"version", no_argument, 0, 0},
103 {0, 0, 0, 0}
104 };
105 int option_index = 0;
106
107 c = getopt_long(argc, argv, "+", long_options, &option_index);
108
109 if (c == -1)
110 {
111 break;
112 }
113
114 switch (c)
115 {
116 case 0:
117 if (strcmp(long_options[option_index].name, "help") == 0)
118 {
119 showHelp(argv[0]);
120 exit(EXIT_SUCCESS);
121 }
122 else if (strcmp(long_options[option_index].name, "version") == 0)
123 {
124 showVersion(argv[0]);
125 exit(EXIT_SUCCESS);
126 }
127 break;
128 case '?':
129 break;
130 default:
131 abort();
132 }
133 }
134
135 pidInit = getInitProcess();
136
137 if (strcmp(argv[1], "shutdown") == 0)
138 {
139 if (pidInit == 0)
140 {
141 fprintf(stderr, "Darling container is not running\n");
142 return 1;
143 }
144
145 // TODO: when we have a working launchd,
146 // this is where we ask it to shut down nicely
147
148 char path_buf[128];
149 FILE* file;
150 pid_t launchd_pid;
151 snprintf(path_buf, sizeof(path_buf), "/proc/%d/task/%d/children", pidInit, pidInit);
152 file = fopen(path_buf, "r");
153 if (!file || fscanf(file, "%d", &launchd_pid) != 1) {
154 fprintf(stderr, "Failed to shutdown Darling container\n");
155 if (file) {
156 fclose(file);
157 }
158 return 1;
159 }
160 fclose(file);
161
162 kill(launchd_pid, SIGKILL);
163 kill(pidInit, SIGKILL);
164 return 0;
165 }
166
167 // If prefix's init is not running, start it up
168 if (pidInit == 0)
169 {
170 char socketPath[4096];
171
172 snprintf(socketPath, sizeof(socketPath), "%s" SHELLSPAWN_SOCKPATH, prefix);
173
174 unlink(socketPath);
175
176 setupWorkdir();
177 pidInit = spawnInitProcess();
178 putInitPid(pidInit);
179
180 // Wait until shellspawn starts
181 for (int i = 0; i < 15; i++)
182 {
183 if (access(socketPath, F_OK) == 0)
184 break;
185 sleep(1);
186 }
187 }
188
189#if USE_LINUX_4_11_HACK
190 joinNamespace(pidInit, CLONE_NEWNS, "mnt");
191#endif
192
193 seteuid(g_originalUid);
194
195 if (strcmp(argv[1], "shell") == 0)
196 {
197 // Spawn the shell
198 if (argc > 2)
199 spawnShell((const char**) &argv[2]);
200 else
201 spawnShell(NULL);
202 }
203 else
204 {
205 bool doExec = strcmp(argv[1], "exec") == 0;
206 int argvIndex = doExec ? 2 : 1;
207
208 if (doExec && argc <= 2)
209 {
210 printf("'exec' subcommand requires a binary to execute.\n");
211 return 1;
212 }
213
214 char *fullPath;
215 char *path = realpath(argv[argvIndex], NULL);
216
217 if (path == NULL)
218 {
219 printf("'%s' is not a supported command or a file.\n", argv[argvIndex]);
220 return 1;
221 }
222
223 fullPath = malloc(strlen(SYSTEM_ROOT) + strlen(path) + 1);
224 strcpy(fullPath, SYSTEM_ROOT);
225 strcat(fullPath, path);
226 free(path);
227
228 argv[argvIndex] = fullPath;
229
230 if (doExec)
231 spawnBinary(argv[argvIndex], (const char**) &argv[argvIndex]);
232 else
233 spawnShell((const char**) &argv[argvIndex]);
234 }
235
236 return 0;
237}
238
239void joinNamespace(pid_t pid, int type, const char* typeName)
240{
241 int fdNS;
242 char pathNS[4096];
243
244 snprintf(pathNS, sizeof(pathNS), "/proc/%d/ns/%s", pid, typeName);
245
246 fdNS = open(pathNS, O_RDONLY);
247
248 if (fdNS < 0)
249 {
250 fprintf(stderr, "Cannot open %s namespace file: %s\n", typeName, strerror(errno));
251 exit(1);
252 }
253
254 // Calling setns() with a PID namespace doesn't move our process into it,
255 // but our child process will be spawned inside the namespace
256 if (setns(fdNS, type) != 0)
257 {
258 fprintf(stderr, "Cannot join %s namespace: %s\n", typeName, strerror(errno));
259 exit(1);
260 }
261 close(fdNS);
262}
263
264static void pushShellspawnCommandData(int sockfd, shellspawn_cmd_type_t type, const void* data, size_t data_length)
265{
266 struct shellspawn_cmd* cmd;
267 size_t length;
268
269 length = sizeof(*cmd) + data_length;
270
271 cmd = (struct shellspawn_cmd*) malloc(length);
272 cmd->cmd = type;
273 cmd->data_length = data_length;
274
275 if (data != NULL)
276 memcpy(cmd->data, data, data_length);
277
278 if (write(sockfd, cmd, length) != length)
279 {
280 fprintf(stderr, "Error sending command to shellspawn: %s\n", strerror(errno));
281 exit(EXIT_FAILURE);
282 }
283
284 free(cmd);
285}
286
287static void pushShellspawnCommand(int sockfd, shellspawn_cmd_type_t type, const char* value)
288{
289 if (!value)
290 pushShellspawnCommandData(sockfd, type, NULL, 0);
291 else
292 pushShellspawnCommandData(sockfd, type, value, strlen(value) + 1);
293}
294
295static void pushShellspawnCommandFDs(int sockfd, shellspawn_cmd_type_t type, const int fds[3])
296{
297 struct shellspawn_cmd cmd;
298 char cmsgbuf[CMSG_SPACE(sizeof(int) * 3)];
299 struct msghdr msg;
300 struct iovec iov;
301 struct cmsghdr *cmptr;
302
303 cmd.cmd = type;
304 cmd.data_length = 0;
305
306 iov.iov_base = &cmd;
307
308 memset(&msg, 0, sizeof(msg));
309 msg.msg_control = cmsgbuf;
310 msg.msg_controllen = sizeof(cmsgbuf);
311
312 iov.iov_base = &cmd;
313 iov.iov_len = sizeof(cmd);
314 msg.msg_iov = &iov;
315 msg.msg_iovlen = 1;
316
317 cmptr = CMSG_FIRSTHDR(&msg);
318 cmptr->cmsg_len = CMSG_LEN(sizeof(int) * 3);
319 cmptr->cmsg_level = SOL_SOCKET;
320 cmptr->cmsg_type = SCM_RIGHTS;
321 memcpy(CMSG_DATA(cmptr), fds, sizeof(fds[0])*3);
322
323 if (sendmsg(sockfd, &msg, 0) < 0)
324 {
325 fprintf(stderr, "Error sending command to shellspawn: %s\n", strerror(errno));
326 exit(EXIT_FAILURE);
327 }
328}
329
330static int _shSockfd = -1;
331static struct termios orig_termios;
332static int pty_master;
333static void signalHandler(int signo)
334{
335 // printf("Received signal %d\n", signo);
336
337 // Forward window size changes
338 if (signo == SIGWINCH && pty_master != -1)
339 {
340 struct winsize win;
341
342 ioctl(0, TIOCGWINSZ, &win);
343 ioctl(pty_master, TIOCSWINSZ, &win);
344 }
345
346 // Foreground process loopkup in shellspawn doesn't work
347 // if we're not running in TTY mode, so shellspawn falls back
348 // to forwarding signals to the Bash subprocess.
349 //
350 // Hence we translate SIGINT to SIGTERM for user convenience,
351 // because Bash will not terminate on SIGINT.
352 if (pty_master == -1 && signo == SIGINT)
353 signo = SIGTERM;
354
355 pushShellspawnCommandData(_shSockfd, SHELLSPAWN_SIGNAL, &signo, sizeof(signo));
356}
357
358static void shellLoop(int sockfd, int master)
359{
360 struct sigaction sa;
361 struct pollfd pfds[3];
362 const int fdcount = (master != -1) ? 3 : 1; // do we do pty proxying?
363
364 // Vars for signal handler
365 _shSockfd = sockfd;
366 pty_master = master;
367
368 memset(&sa, 0, sizeof(sa));
369 sa.sa_handler = signalHandler;
370 sigfillset(&sa.sa_mask);
371
372 for (int i = 1; i < 32; i++)
373 sigaction(i, &sa, NULL);
374
375 pfds[2].fd = master;
376 pfds[2].events = POLLIN;
377 pfds[2].revents = 0;
378 pfds[1].fd = STDIN_FILENO;
379 pfds[1].events = POLLIN;
380 pfds[1].revents = 0;
381 pfds[0].fd = sockfd;
382 pfds[0].events = POLLIN;
383 pfds[0].revents = 0;
384
385 if (master != -1)
386 fcntl(master, F_SETFL, O_NONBLOCK);
387 //fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
388 fcntl(sockfd, F_SETFL, O_NONBLOCK);
389
390 while (1)
391 {
392 char buf[4096];
393
394 if (poll(pfds, fdcount, -1) < 0)
395 {
396 if (errno != EINTR)
397 {
398 perror("poll");
399 break;
400 }
401 }
402
403 if (pfds[2].revents & POLLIN)
404 {
405 int rd;
406 do
407 {
408 rd = read(master, buf, sizeof(buf));
409 if (rd > 0)
410 write(STDOUT_FILENO, buf, rd);
411 }
412 while (rd == sizeof(buf));
413 }
414
415 if (pfds[1].revents & POLLIN)
416 {
417 int rd = 0;
418 do
419 {
420 if (ioctl(STDIN_FILENO, FIONREAD, &rd) < 0) {
421 perror("ioctl");
422 exit(1);
423 }
424 if (rd > sizeof(buf)) {
425 rd = sizeof(buf);
426 }
427 rd = read(STDIN_FILENO, buf, rd);
428 if (rd > 0)
429 write(master, buf, rd);
430 else {
431 perror("read");
432 exit(1);
433 }
434 }
435 while (rd == sizeof(buf));
436 }
437
438 if (pfds[0].revents & (POLLHUP | POLLIN))
439 {
440 int exitStatus;
441
442 if (read(sockfd, &exitStatus, sizeof(int)) == sizeof(int))
443 exit(exitStatus);
444 else
445 exit(1);
446 }
447 }
448}
449
450static void restoreTermios(void)
451{
452 tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
453}
454
455// Glibc openpty() fails for me on Debian, because grantpty() fails to chown() the pty node with error code EPERM.
456// This is a more lenient version of openpty() that just works.
457static int openpty_darling(int* amaster, int* aslave, char* name_unused, const struct termios* tos, const struct winsize* wsz)
458{
459 const char* slave_name;
460
461 *amaster = posix_openpt(O_RDWR);
462 if (*amaster == -1)
463 return -1;
464
465 grantpt(*amaster);
466 if (unlockpt(*amaster) < 0)
467 return -1;
468
469 slave_name = ptsname(*amaster);
470 *aslave = open(slave_name, O_RDWR | O_NOCTTY);
471 if (*aslave == -1)
472 return -1;
473
474 if (tos != NULL)
475 tcsetattr(*amaster, TCSANOW, tos);
476 if (wsz != NULL)
477 ioctl(*amaster, TIOCSWINSZ, wsz);
478
479 return 0;
480}
481
482static void setupPtys(int fds[3], int* master)
483{
484 struct winsize win;
485 struct termios termios;
486 bool tty = true;
487
488 if (tcgetattr(STDIN_FILENO, &termios) < 0)
489 tty = false;
490
491 if (openpty_darling(master, &fds[0], NULL, &termios, NULL) < 0)
492 {
493 perror("openpty");
494 exit(1);
495 }
496 fds[2] = fds[1] = fds[0];
497
498 if (tty)
499 {
500 orig_termios = termios;
501
502 ioctl(0, TIOCGWINSZ, &win);
503
504 termios.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
505 termios.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR |
506 INPCK | ISTRIP | IXON | PARMRK);
507 termios.c_oflag &= ~OPOST;
508 termios.c_cc[VMIN] = 1;
509 termios.c_cc[VTIME] = 0;
510
511 if (tcsetattr(STDIN_FILENO, TCSANOW, &termios) < 0)
512 {
513 perror("tcsetattr");
514 exit(1);
515 }
516 ioctl(*master, TIOCSWINSZ, &win);
517
518 atexit(restoreTermios);
519 }
520}
521
522// Replace each quote character (') with the sequence '\''
523// (copying the result to dest, if non-null)
524// and return the length of the result.
525static size_t escapeQuotes(char *dest, const char *src)
526{
527 size_t len = 0;
528
529 for (; *src != 0; src++)
530 {
531 if (*src == '\'')
532 {
533 if (dest)
534 memcpy(&dest[len], "'\\''", 4);
535 len += 4;
536 }
537 else
538 {
539 if (dest)
540 dest[len] = *src;
541 len++;
542 }
543 }
544 if (dest)
545 dest[len] = 0;
546
547 return len;
548}
549
550int connectToShellspawn(void)
551{
552 struct sockaddr_un addr;
553 int sockfd;
554
555 // Connect to the shellspawn daemon in the container
556 addr.sun_family = AF_UNIX;
557#if USE_LINUX_4_11_HACK
558 addr.sun_path[0] = '\0';
559
560 strcpy(addr.sun_path, prefix);
561 strcat(addr.sun_path, SHELLSPAWN_SOCKPATH);
562#else
563 snprintf(addr.sun_path, sizeof(addr.sun_path), "%s" SHELLSPAWN_SOCKPATH, prefix);
564#endif
565
566 sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
567 if (sockfd == -1)
568 {
569 fprintf(stderr, "Error creating a unix domain socket: %s\n", strerror(errno));
570 exit(1);
571 }
572
573 if (connect(sockfd, (struct sockaddr*) &addr, sizeof(addr)) == -1)
574 {
575 fprintf(stderr, "Error connecting to shellspawn in the container (%s): %s\n", addr.sun_path, strerror(errno));
576 exit(1);
577 }
578
579 return sockfd;
580}
581
582void setupShellspawnEnv(int sockfd)
583{
584 static const char* skip_vars[] = {
585 "PATH",
586 "TMPDIR",
587 "HOME",
588 };
589
590 char buffer2[4096];
591
592 // Push environment variables
593 pushShellspawnCommand(sockfd, SHELLSPAWN_SETENV,
594 "PATH=/usr/bin:"
595 "/bin:"
596 "/usr/sbin:"
597 "/sbin:"
598 "/usr/local/bin");
599 pushShellspawnCommand(sockfd, SHELLSPAWN_SETENV, "TMPDIR=/private/tmp");
600
601 const char* login = NULL;
602 struct passwd* pw = getpwuid(geteuid());
603
604 if (pw != NULL)
605 login = pw->pw_name;
606
607 if (!login)
608 login = getlogin();
609 if (!login)
610 {
611 fprintf(stderr, "Cannot determine your user name\n");
612 exit(1);
613 }
614
615 snprintf(buffer2, sizeof(buffer2), "HOME=/Users/%s", login);
616 pushShellspawnCommand(sockfd, SHELLSPAWN_SETENV, buffer2);
617
618 for (char** var_ptr = environ; *var_ptr != NULL; ++var_ptr) {
619 const char* var = *var_ptr;
620 const char* equal = strchr(var, '=');
621 size_t name_len = (equal != NULL) ? (size_t)(equal - var) : strlen(var);
622 bool skip_it = false;
623
624 for (size_t i = 0; i < sizeof(skip_vars) / sizeof(*skip_vars); ++i) {
625 if (strlen(skip_vars[i]) == name_len && strncmp(var, skip_vars[i], name_len) == 0) {
626 skip_it = true;
627 break;
628 }
629 }
630
631 if (skip_it) {
632 continue;
633 }
634
635 pushShellspawnCommand(sockfd, SHELLSPAWN_SETENV, var);
636 }
637}
638
639void setupWorkingDir(int sockfd)
640{
641 char buffer2[4096];
642 snprintf(buffer2, sizeof(buffer2), SYSTEM_ROOT "%s", g_workingDirectory);
643 pushShellspawnCommand(sockfd, SHELLSPAWN_CHDIR, buffer2);
644}
645
646void setupIDs(int sockfd)
647{
648 int ids[2] = { g_originalUid, g_originalGid };
649 pushShellspawnCommandData(sockfd, SHELLSPAWN_SETUIDGID, ids, sizeof(ids));
650}
651
652void setupFDs(int fds[3], int* master)
653{
654 *master = -1;
655
656 if (isatty(STDIN_FILENO))
657 setupPtys(fds, master);
658 else
659 fds[0] = dup(STDIN_FILENO); // dup() because we close() after spawning
660
661 if (*master == -1 || !isatty(STDOUT_FILENO))
662 fds[1] = STDOUT_FILENO;
663 if (*master == -1 || !isatty(STDERR_FILENO))
664 fds[2] = STDERR_FILENO;
665}
666
667void spawnGo(int sockfd, int fds[3], int master)
668{
669 pushShellspawnCommandFDs(sockfd, SHELLSPAWN_GO, fds);
670 close(fds[0]);
671
672 shellLoop(sockfd, master);
673
674 if (master != -1)
675 close(master);
676 close(sockfd);
677}
678
679void spawnShell(const char** argv)
680{
681 size_t total_len = 0;
682 int count;
683 int sockfd;
684 char* buffer;
685 int fds[3], master;
686
687 if (argv != NULL)
688 {
689 for (count = 0; argv[count] != NULL; count++)
690 total_len += escapeQuotes(NULL, argv[count]);
691
692 buffer = malloc(total_len + count*3);
693
694 char *to = buffer;
695 for (int i = 0; argv[i] != NULL; i++)
696 {
697 if (to != buffer)
698 to = stpcpy(to, " ");
699 to = stpcpy(to, "'");
700 to += escapeQuotes(to, argv[i]);
701 to = stpcpy(to, "'");
702 }
703 }
704 else
705 buffer = NULL;
706
707 sockfd = connectToShellspawn();
708
709 setupShellspawnEnv(sockfd);
710
711 // Push shell arguments
712 if (buffer != NULL)
713 {
714 pushShellspawnCommand(sockfd, SHELLSPAWN_ADDARG, "-c");
715 pushShellspawnCommand(sockfd, SHELLSPAWN_ADDARG, buffer);
716
717 free(buffer);
718 }
719
720 setupWorkingDir(sockfd);
721 setupIDs(sockfd);
722 setupFDs(fds, &master);
723 spawnGo(sockfd, fds, master);
724}
725
726void spawnBinary(const char* binary, const char** argv)
727{
728 int fds[3], master;
729 int sockfd;
730
731 sockfd = connectToShellspawn();
732 setupShellspawnEnv(sockfd);
733
734 pushShellspawnCommand(sockfd, SHELLSPAWN_SETEXEC, binary);
735
736 for (; *argv != NULL; ++argv)
737 pushShellspawnCommand(sockfd, SHELLSPAWN_ADDARG, *argv);
738
739 setupWorkingDir(sockfd);
740 setupIDs(sockfd);
741 setupFDs(fds, &master);
742 spawnGo(sockfd, fds, master);
743}
744
745void showHelp(const char* argv0)
746{
747 fprintf(stderr, "This is Darling, translation layer for macOS software.\n\n");
748 fprintf(stderr, "Copyright (C) 2012-2023 Lubos Dolezel\n\n");
749
750 fprintf(stderr, "Usage:\n");
751 fprintf(stderr, "\t%s <program-path> [arguments...]\n", argv0);
752 fprintf(stderr, "\t%s shell [arguments...]\n", argv0);
753 fprintf(stderr, "\t%s exec <program-path> [arguments...]\n", argv0);
754 fprintf(stderr, "\t%s shutdown\n", argv0);
755 fprintf(stderr, "\n");
756 fprintf(stderr, "Environment variables:\n"
757 "DPREFIX - specifies the location of Darling prefix, defaults to ~/.darling\n");
758}
759
760void showVersion(const char* argv0) {
761 fprintf(stderr, "%s " GIT_BRANCH " @ " GIT_COMMIT_HASH "\n", argv0);
762 fprintf(stderr, "Copyright (C) 2012-2023 Lubos Dolezel\n");
763}
764
765void missingSetuidRoot(void)
766{
767 char path[4096];
768 int len;
769
770 len = readlink("/proc/self/exe", path, sizeof(path)-1);
771 if (len < 0)
772 strcpy(path, "darling");
773 else
774 path[len] = '\0';
775
776 fprintf(stderr, "Sorry, the `%s' binary is not setuid root, which is mandatory.\n", path);
777 fprintf(stderr, "Darling needs this in order to create mount and PID namespaces and to perform mounts.\n");
778}
779
780pid_t spawnInitProcess(void)
781{
782 pid_t pid;
783 int pipefd[2];
784 char buffer[1];
785
786 if (pipe(pipefd) == -1)
787 {
788 fprintf(stderr, "Cannot create a pipe for synchronization: %s\n", strerror(errno));
789 exit(1);
790 }
791
792 if (unshare(CLONE_NEWUTS | CLONE_NEWIPC) != 0)
793 {
794 fprintf(stderr, "Cannot unshare UTS and IPC namespaces to create darling-init: %s\n", strerror(errno));
795 exit(1);
796 }
797
798 pid = fork();
799
800 if (pid < 0)
801 {
802 fprintf(stderr, "Cannot fork() to create darling-init: %s\n", strerror(errno));
803 exit(1);
804 }
805
806 if (pid == 0)
807 {
808 // The child
809
810 char uid_str[21];
811 char gid_str[21];
812 char pipefd_str[21];
813
814 snprintf(uid_str, sizeof(uid_str), "%d", g_originalUid);
815 snprintf(gid_str, sizeof(gid_str), "%d", g_originalGid);
816 snprintf(pipefd_str, sizeof(pipefd_str), "%d", pipefd[1]);
817
818 close(pipefd[0]);
819
820 execl(INSTALL_PREFIX "/bin/darlingserver", "darlingserver", prefix, uid_str, gid_str, pipefd_str, g_fixPermissions ? "1" : "0", NULL);
821
822 fprintf(stderr, "Failed to start darlingserver\n");
823 exit(1);
824 }
825
826 // Wait for the child to drop UID/GIDs and unshare stuff
827 close(pipefd[1]);
828 read(pipefd[0], buffer, 1);
829 close(pipefd[0]);
830
831 /*
832 snprintf(idmap, sizeof(idmap), "/proc/%d/uid_map", pid);
833
834 file = fopen(idmap, "w");
835 if (file != NULL)
836 {
837 fprintf(file, "0 %d 1\n", g_originalUid); // all users map to our user on the outside
838 fclose(file);
839 }
840 else
841 {
842 fprintf(stderr, "Cannot set uid_map for the init process: %s\n", strerror(errno));
843 }
844
845 snprintf(idmap, sizeof(idmap), "/proc/%d/gid_map", pid);
846
847 file = fopen(idmap, "w");
848 if (file != NULL)
849 {
850 fprintf(file, "0 %d 1\n", g_originalGid); // all groups map to our group on the outside
851 fclose(file);
852 }
853 else
854 {
855 fprintf(stderr, "Cannot set gid_map for the init process: %s\n", strerror(errno));
856 }
857 */
858
859 // Here's where we resume the child
860 // if we enable user namespaces
861
862 return pid;
863}
864
865void putInitPid(pid_t pidInit)
866{
867 const char pidFile[] = "/.init.pid";
868 char* pidPath;
869 FILE *fp;
870
871 pidPath = (char*) alloca(strlen(prefix) + sizeof(pidFile));
872 strcpy(pidPath, prefix);
873 strcat(pidPath, pidFile);
874
875 seteuid(g_originalUid);
876 setegid(g_originalGid);
877
878 fp = fopen(pidPath, "w");
879
880 seteuid(0);
881 setegid(0);
882
883 if (fp == NULL)
884 {
885 fprintf(stderr, "Cannot write out PID of the init process: %s\n", strerror(errno));
886 return;
887 }
888 fprintf(fp, "%d", (int) pidInit);
889 fclose(fp);
890}
891
892char* defaultPrefixPath(void)
893{
894 const char defaultPath[] = "/.darling";
895 const char* home = getenv("HOME");
896 char* buf;
897
898 if (!home)
899 {
900 fprintf(stderr, "Cannot detect your home directory!\n");
901 return NULL;
902 }
903
904 buf = (char*) malloc(strlen(home) + sizeof(defaultPath));
905 strcpy(buf, home);
906 strcat(buf, defaultPath);
907
908 return buf;
909}
910
911void createDir(const char* path)
912{
913 struct stat st;
914
915 if (stat(path, &st) == 0)
916 {
917 if (!S_ISDIR(st.st_mode))
918 {
919 fprintf(stderr, "%s already exists and is a file. Remove the file.\n", path);
920 exit(1);
921 }
922 }
923 else
924 {
925 if (errno == ENOENT)
926 {
927 if (mkdir(path, 0755) != 0)
928 {
929 fprintf(stderr, "Cannot create %s: %s\n", path, strerror(errno));
930 exit(1);
931 }
932 }
933 else
934 {
935 fprintf(stderr, "Cannot access %s: %s\n", path, strerror(errno));
936 exit(1);
937 }
938 }
939}
940
941void setupWorkdir()
942{
943 char* workdir;
944 const char suffix[] = ".workdir";
945 size_t len;
946
947 len = strlen(prefix);
948 workdir = (char*) alloca(len + sizeof(suffix));
949 strcpy(workdir, prefix);
950
951 // Remove trailing /
952 while (workdir[len-1] == '/')
953 len--;
954 workdir[len] = '\0';
955
956 strcat(workdir, suffix);
957
958 createDir(workdir);
959}
960
961int checkPrefixDir()
962{
963 struct stat st;
964
965 if (stat(prefix, &st) == 0)
966 {
967 if (!S_ISDIR(st.st_mode))
968 {
969 fprintf(stderr, "%s is a file. Remove the file.\n", prefix);
970 exit(1);
971 }
972 return 1; // OK
973 }
974 if (errno == ENOENT)
975 return 0; // not found
976 fprintf(stderr, "Cannot access %s: %s\n", prefix, strerror(errno));
977 exit(1);
978}
979
980void setupPrefix()
981{
982 char path[4096];
983 size_t plen;
984 FILE* file;
985 struct passwd* passwd_entry;
986
987 const char* dirs[] = {
988 "/Volumes",
989 "/Applications",
990 "/usr",
991 "/usr/local",
992 "/usr/local/share",
993 "/private",
994 "/private/var",
995 "/private/var/log",
996 "/private/var/db",
997 "/private/etc",
998 "/var",
999 "/var/run",
1000 "/var/tmp",
1001 "/var/log"
1002 };
1003
1004 fprintf(stderr, "Setting up a new Darling prefix at %s\n", prefix);
1005
1006 seteuid(g_originalUid);
1007 setegid(g_originalGid);
1008
1009 createDir(prefix);
1010 strcpy(path, prefix);
1011 strcat(path, "/");
1012 plen = strlen(path);
1013
1014 for (size_t i = 0; i < sizeof(dirs)/sizeof(dirs[0]); i++)
1015 {
1016 path[plen] = '\0';
1017 strcat(path, dirs[i]);
1018 createDir(path);
1019 }
1020
1021 // create passwd, master.passwd, and group
1022
1023 passwd_entry = getpwuid(g_originalUid);
1024 if (!passwd_entry) {
1025 fprintf(stderr, "Failed to find Linux /etc/passwd entry for current user\n");
1026 exit(1);
1027 }
1028
1029 path[plen] = '\0';
1030 strcat(path, "/private/etc/passwd");
1031 file = fopen(path, "w");
1032 if (!file) {
1033 fprintf(stderr, "Failed to open /private/etc/passwd within the prefix\n");
1034 exit(1);
1035 }
1036
1037 fprintf(file,
1038 "root:*:0:0:System Administrator:/var/root:/bin/sh\n"
1039 "%s:*:%d:%d:Darling User:/Users/%s:/bin/bash\n",
1040 passwd_entry->pw_name,
1041 passwd_entry->pw_uid,
1042 passwd_entry->pw_gid,
1043 passwd_entry->pw_name
1044 );
1045 fclose(file);
1046
1047 path[plen] = '\0';
1048 strcat(path, "/private/etc/master.passwd");
1049 file = fopen(path, "w");
1050 if (!file) {
1051 fprintf(stderr, "Failed to open /private/etc/master.passwd within the prefix\n");
1052 exit(1);
1053 }
1054
1055 fprintf(file,
1056 "root:*:0:0::0:0:System Administrator:/var/root:/bin/sh\n"
1057 "%s:*:%d:%d::0:0:Darling User:/Users/%s:/bin/bash\n",
1058 passwd_entry->pw_name,
1059 passwd_entry->pw_uid,
1060 passwd_entry->pw_gid,
1061 passwd_entry->pw_name
1062 );
1063 fclose(file);
1064
1065 path[plen] = '\0';
1066 strcat(path, "/private/etc/group");
1067 file = fopen(path, "w");
1068 if (!file) {
1069 fprintf(stderr, "Failed to open /private/etc/group within the prefix\n");
1070 exit(1);
1071 }
1072
1073 fprintf(file,
1074 "wheel:*:0:root,%s\n"
1075 "%s:*:%d:%s\n",
1076 passwd_entry->pw_name,
1077 passwd_entry->pw_name,
1078 passwd_entry->pw_gid,
1079 passwd_entry->pw_name
1080 );
1081 fclose(file);
1082
1083 seteuid(0);
1084 setegid(0);
1085}
1086
1087pid_t getInitProcess()
1088{
1089 const char pidFile[] = "/.init.pid";
1090 char* pidPath;
1091 pid_t pid;
1092 int pid_i;
1093 FILE *fp;
1094 char procBuf[100];
1095 char *exeBuf, *statusBuf;
1096 int uidMatch = 0, gidMatch = 0;
1097
1098 pidPath = (char*) alloca(strlen(prefix) + sizeof(pidFile));
1099 strcpy(pidPath, prefix);
1100 strcat(pidPath, pidFile);
1101
1102 fp = fopen(pidPath, "r");
1103 if (fp == NULL)
1104 return 0;
1105
1106 if (fscanf(fp, "%d", &pid_i) != 1)
1107 {
1108 fclose(fp);
1109 unlink(pidPath);
1110 return 0;
1111 }
1112 fclose(fp);
1113 pid = (pid_t) pid_i;
1114
1115 // Does the process exist?
1116 if (kill(pid, 0) == -1)
1117 {
1118 unlink(pidPath);
1119 return 0;
1120 }
1121
1122 // Is it actually an init process?
1123 snprintf(procBuf, sizeof(procBuf), "/proc/%d/comm", pid);
1124 fp = fopen(procBuf, "r");
1125 if (fp == NULL)
1126 {
1127 unlink(pidPath);
1128 return 0;
1129 }
1130
1131 if (fscanf(fp, "%ms", &exeBuf) != 1)
1132 {
1133 fclose(fp);
1134 unlink(pidPath);
1135 return 0;
1136 }
1137 fclose(fp);
1138
1139 if (strcmp(exeBuf, "darlingserver") != 0)
1140 {
1141 unlink(pidPath);
1142 return 0;
1143 }
1144 free(exeBuf);
1145
1146 // Is it owned by the current user?
1147 if (g_originalUid != 0)
1148 {
1149 snprintf(procBuf, sizeof(procBuf), "/proc/%d/status", pid);
1150 fp = fopen(procBuf, "r");
1151 if (fp == NULL)
1152 {
1153 unlink(pidPath);
1154 return 0;
1155 }
1156
1157 while (1)
1158 {
1159 statusBuf = NULL;
1160 size_t len;
1161 if (getline(&statusBuf, &len, fp) == -1)
1162 break;
1163 int rid, eid, sid, fid;
1164 if (sscanf(statusBuf, "Uid: %d %d %d %d", &rid, &eid, &sid, &fid) == 4)
1165 {
1166 uidMatch = 1;
1167 uidMatch &= rid == g_originalUid;
1168 uidMatch &= eid == g_originalUid;
1169 uidMatch &= sid == g_originalUid;
1170 uidMatch &= fid == g_originalUid;
1171 }
1172 if (sscanf(statusBuf, "Gid: %d %d %d %d", &rid, &eid, &sid, &fid) == 4)
1173 {
1174 gidMatch = 1;
1175 gidMatch &= rid == g_originalGid;
1176 gidMatch &= eid == g_originalGid;
1177 gidMatch &= sid == g_originalGid;
1178 gidMatch &= fid == g_originalGid;
1179 }
1180 free(statusBuf);
1181 }
1182 fclose(fp);
1183
1184 if (!uidMatch || !gidMatch)
1185 {
1186 unlink(pidPath);
1187 return 0;
1188 }
1189 }
1190
1191 return pid;
1192}
1193
1194void checkPrefixOwner()
1195{
1196 struct stat st;
1197
1198 if (stat(prefix, &st) == 0)
1199 {
1200 if (g_originalUid != 0 && st.st_uid != g_originalUid)
1201 {
1202 fprintf(stderr, "You do not own the prefix directory.\n");
1203 exit(1);
1204 }
1205 }
1206 else if (errno == EACCES)
1207 {
1208 fprintf(stderr, "You do not own the prefix directory.\n");
1209 exit(1);
1210 }
1211}