diff --git a/src/shellspawn/CMakeLists.txt b/src/shellspawn/CMakeLists.txt index d441f49ca..fa1c195c9 100644 --- a/src/shellspawn/CMakeLists.txt +++ b/src/shellspawn/CMakeLists.txt @@ -3,9 +3,7 @@ project(shellspawn) add_definitions(-nostdinc) add_darling_executable(shellspawn shellspawn.c duct_signals.c) -add_darling_executable(shellsession shellsession.c) -install(TARGETS shellspawn shellsession DESTINATION libexec/darling/usr/libexec) -install(FILES org.darlinghq.shellsession.plist DESTINATION libexec/darling/System/Library/LaunchDaemons) -install(FILES org.darlinghq.shellspawn.plist DESTINATION libexec/darling/System/Library/LaunchAgents) +install(TARGETS shellspawn DESTINATION libexec/darling/usr/libexec) +install(FILES org.darlinghq.shellspawn.plist DESTINATION libexec/darling/System/Library/LaunchDaemons) diff --git a/src/shellspawn/org.darlinghq.shellsession.plist b/src/shellspawn/org.darlinghq.shellsession.plist deleted file mode 100644 index c8b519d37..000000000 --- a/src/shellspawn/org.darlinghq.shellsession.plist +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Label - org.darlinghq.shellsession - ProgramArguments - - /usr/libexec/shellsession - - RunAtLoad - - KeepAlive - - - diff --git a/src/shellspawn/shellsession.c b/src/shellspawn/shellsession.c deleted file mode 100644 index dbc1101c3..000000000 --- a/src/shellspawn/shellsession.c +++ /dev/null @@ -1,373 +0,0 @@ -/* - * This file is part of Darling. - * - * Copyright (C) 2023 Darling Developers - * - * Darling is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Darling is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Darling. If not, see . - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "shellsession.h" -#include "shellspawn.h" - -#define MAX_ACCESS_TRIES 10 - -static void setup_socket_addr(struct sockaddr_un* addr); -static void setup_session_socket_addr(unsigned int uid, struct sockaddr_un* addr); -static int setup_socket(const struct sockaddr_un* addr); -static void listen_for_connections(int server_socket); -static void spawn_session(int sockfd); -static void send_client_socket(int sockfd, int socket_to_send); -static int try_connect_session(const struct sockaddr_un* addr); -static void run_session_manager(unsigned int uid, unsigned int gid); - -int main(int argc, const char* const* argv) { - struct sockaddr_un server_addr; - int server_socket = -1; - - setup_socket_addr(&server_addr); - - server_socket = setup_socket(&server_addr); - if (server_socket < 0) { - exit(EXIT_FAILURE); - } - - listen_for_connections(server_socket); - - if (server_socket >= 0) { - close(server_socket); - } - - return 0; -}; - -static void setup_socket_addr(struct sockaddr_un* addr) { - memset(addr, 0, sizeof(*addr)); - addr->sun_family = AF_UNIX; - snprintf(addr->sun_path, sizeof(addr->sun_path), "%s", SHELLSESSION_SOCKPATH); -}; - -static void setup_session_socket_addr(unsigned int uid, struct sockaddr_un* addr) { - memset(addr, 0, sizeof(*addr)); - addr->sun_family = AF_UNIX; - snprintf(addr->sun_path, sizeof(addr->sun_path), "%s.%d", SHELLSPAWN_SOCKPATH, uid); -}; - -static int setup_socket(const struct sockaddr_un* addr) { - int server_socket = -1; - - server_socket = socket(AF_UNIX, SOCK_STREAM, 0); - if (server_socket == -1) { - perror("Creating unix socket"); - goto err; - } - - fcntl(server_socket, F_SETFD, FD_CLOEXEC); - unlink(addr->sun_path); - - if (bind(server_socket, (struct sockaddr*)addr, sizeof(*addr)) == -1) { - perror("Binding the unix socket"); - goto err; - } - - chmod(addr->sun_path, S_IRUSR | S_IWUSR); - - if (listen(server_socket, 1) == -1) { - perror("Listening on unix socket"); - goto err; - } - - return server_socket; - -err: - if (server_socket >= 0) { - close(server_socket); - } - return -1; -}; - -static void listen_for_connections(int server_socket) { - int sock; - struct sockaddr_un client_addr; - socklen_t len = sizeof(client_addr); - - while (true) { - sock = accept(server_socket, (struct sockaddr*)&client_addr, &len); - if (sock == -1) { - break; - } - - if (fork() == 0) { - // we don't need the server socket - close(server_socket); - server_socket = -1; - - fcntl(sock, F_SETFD, FD_CLOEXEC); - spawn_session(sock); - - exit(EXIT_SUCCESS); - } else { - close(sock); - } - } -}; - -void spawn_session(int sockfd) { - shellsession_cmd_t cmd; - struct sockaddr_un session_addr; - int client_socket = -1; - char session_started_path[PATH_MAX]; - int started_fd = -1; - - if (read(sockfd, &cmd, sizeof(cmd)) != sizeof(cmd)) { - goto err; - } - - setup_session_socket_addr(cmd.uid, &session_addr); - snprintf(session_started_path, sizeof(session_started_path), "%s.%d", SHELLSESSION_STARTED_PATH, cmd.uid); - - started_fd = open(session_started_path, O_RDONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - - if (started_fd < 0 && errno == EEXIST) { - // we already had a session set up; avoid setting up a new one. - - // wait until shellspawn starts - for (int i = 0; i < MAX_ACCESS_TRIES; i++) { - if (access(session_addr.sun_path, F_OK) == 0) { - break; - } - - sleep(1); - } - - client_socket = try_connect_session(&session_addr); - if (client_socket < 0) { - goto err; - } - - // now reply to the client with the session client socket - send_client_socket(sockfd, client_socket); - close(sockfd); - return; - } else if (started_fd < 0) { - perror("Failed to create session indicator file"); - goto err; - } - - // we didn't have a session set up already, so let's create one. - - // we only need to create the session-running file; we don't need to write anything to it - close(started_fd); - - if (fork() == 0) { - // this is the new session manager process - close(sockfd); - run_session_manager(cmd.uid, cmd.gid); - } else { - // wait until shellspawn starts - for (int i = 0; i < MAX_ACCESS_TRIES; i++) { - if (access(session_addr.sun_path, F_OK) == 0) { - break; - } - - sleep(1); - } - - client_socket = try_connect_session(&session_addr); - if (client_socket < 0) { - fprintf(stderr, "Error connecting to shellspawn session\n"); - exit(EXIT_FAILURE); - } - - // now reply to the client with the session client socket - send_client_socket(sockfd, client_socket); - close(sockfd); - } - - return; - -err: - close(sockfd); -}; - -static void send_client_socket(int sockfd, int socket_to_send) { - char dummy = '\0'; - struct msghdr msg; - char cmsgbuf[CMSG_SPACE(sizeof(int))]; - struct cmsghdr *cmptr; - struct iovec iov; - - memset(&msg, 0, sizeof(msg)); - memset(&iov, 0, sizeof(iov)); - - iov.iov_base = &dummy; - iov.iov_len = sizeof(dummy); - - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = cmsgbuf; - msg.msg_controllen = sizeof(cmsgbuf); - - cmptr = CMSG_FIRSTHDR(&msg); - cmptr->cmsg_len = CMSG_LEN(sizeof(int)); - cmptr->cmsg_level = SOL_SOCKET; - cmptr->cmsg_type = SCM_RIGHTS; - memcpy(CMSG_DATA(cmptr), &socket_to_send, sizeof(socket_to_send)); - - if (sendmsg(sockfd, &msg, 0) != sizeof(dummy)) { - fprintf(stderr, "Error sending reply: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } -}; - -static int try_connect_session(const struct sockaddr_un* addr) { - int client_socket = -1; - - client_socket = socket(AF_UNIX, SOCK_STREAM, 0); - if (client_socket < 0) { - goto err; - } - - if (connect(client_socket, (struct sockaddr*)addr, sizeof(*addr)) != 0) { - goto err; - } - - return client_socket; - -err: - if (client_socket >= 0) { - close(client_socket); - } - return -1; -}; - -static void run_session_manager(unsigned int uid, unsigned int gid) { - const char* login = NULL; - struct passwd* pw = NULL; - char* homedirTmp = NULL; - mach_port_t subset = MACH_PORT_NULL; - mach_port_t dummyService = MACH_PORT_NULL; - kern_return_t kr = KERN_SUCCESS; - - // we are the shellspawn instance for the user's session. this means we're in charge - // of the user's session and are responsible for setting it up as macOS would. - // we are essentially going to be doing the same kind of setup that LoginWindow - // would do on macOS. - - // switch ourselves to run under the user's UID and GID (but under Darling, this is all fake anyways) - setgid(gid); - setuid(uid); - - // fix up env vars to what they should be - pw = getpwuid(uid); - if (pw != NULL) - login = pw->pw_name; - - if (!login) - login = getlogin(); - - setenv("PATH", "/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin", 1); - setenv("TMPDIR", "/private/tmp", 1); - - asprintf(&homedirTmp, "HOME=/Users/%s", login); - putenv(homedirTmp); - homedirTmp = NULL; // `putenv()` assumes ownership of the string - - // - // IMPORTANT NOTE - // - // okay, so i have no clue how this whole session switching is supposed to work. - // or rather, i do have *some* idea: we need to call `create_and_switch_to_per_session_launchd` - // to switch to a per-user launchd (which will also trigger LaunchAgents for the user). - // the problem is that the way this function works internally is that it calls `_vprocmgr_move_subset_to_user`. - // this function, in turn, moves the current subset into the new per-user launchd session. - // obviously, this means we must already have a subset prior to calling `create_and_switch_to_per_session_launchd`. - // - // the thing is, i can't see how (the modern version of) LoginWindow does this. maybe the API has changed since then - // and it no longer moves a subset into the new session, but i can't see it creating a new subset at any point during - // the session setup. but clearly, *we* need to do this, so let's go ahead and do so. - // - - if ((kr = bootstrap_subset(bootstrap_port, mach_task_self(), &subset)) != KERN_SUCCESS) { - fprintf(stderr, "Failed to create bootstrap subset: %d\n", kr); - exit(EXIT_FAILURE); - } - - // replace the bootstrap port with our subset port - if ((kr = task_set_bootstrap_port(mach_task_self(), subset)) != KERN_SUCCESS) { - fprintf(stderr, "Failed to replace bootstrap port: %d\n", kr); - exit(EXIT_FAILURE); - } - - // we no longer need the old bootstrap port - mach_port_deallocate(mach_task_self(), bootstrap_port); - - bootstrap_port = subset; - - // - // ANOTHER IMPORTANT NOTE - // - // launchd requires the new subset to have at least one Mach service registered before switching to the per-user - // session. to that end, we simply create a bogus service here. - // - if ((kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &dummyService)) != KERN_SUCCESS) { - fprintf(stderr, "Failed to allocate dummy service port: %d\n", kr); - exit(EXIT_FAILURE); - } - - if ((kr = mach_port_insert_right(mach_task_self(), dummyService, dummyService, MACH_MSG_TYPE_MAKE_SEND)) != KERN_SUCCESS) { - fprintf(stderr, "Failed to insert send right into dummy service port: %d\n", kr); - exit(EXIT_FAILURE); - } - - if ((kr = bootstrap_register(bootstrap_port, "org.darlinghq.shellsession.dummy-service", dummyService)) != KERN_SUCCESS) { - fprintf(stderr, "Failed to register dummy service: %d\n", kr); - exit(EXIT_FAILURE); - } - - // set up a launchd session - if (create_and_switch_to_per_session_launchd("shellspawn", LAUNCH_GLOBAL_ON_DEMAND) < 0) { - fprintf(stderr, "Failed to set up launchd user session.\n"); - exit(EXIT_FAILURE); - } - - // unset the global-on-demand flag to kickstart jobs that need to run at load - if (!_vproc_set_global_on_demand(false)) { - fprintf(stderr, "Failed to unset global-on-demand flag.\n"); - exit(EXIT_FAILURE); - } - - // now we just need to stay alive for launchd to keep our session alive - // (launchd will start shellspawn as a LaunchAgent, and that's what the `darling` command will actually send commands to) - while (true) { - pause(); - } -}; diff --git a/src/shellspawn/shellsession.h b/src/shellspawn/shellsession.h deleted file mode 100644 index a2d4c7421..000000000 --- a/src/shellspawn/shellsession.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * This file is part of Darling. - * - * Copyright (C) 2023 Darling Developers - * - * Darling is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Darling is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Darling. If not, see . - */ - -#ifndef _SHELLSESSION_H_ -#define _SHELLSESSION_H_ - -#ifdef TESTING -#define SHELLSESSION_SOCKPATH "/tmp/shellsession.sock" -#define SHELLSESSION_STARTED_PATH "/tmp/shellsession.running" -#else -#define SHELLSESSION_SOCKPATH "/var/run/shellsession.sock" -#define SHELLSESSION_STARTED_PATH "/var/run/shellsession.running" -#endif - -typedef struct shellsession_cmd { - unsigned int uid; - unsigned int gid; -} shellsession_cmd_t; - -#endif // _SHELLSESSION_H_ - diff --git a/src/shellspawn/shellspawn.c b/src/shellspawn/shellspawn.c index a32603498..2f99825bc 100644 --- a/src/shellspawn/shellspawn.c +++ b/src/shellspawn/shellspawn.c @@ -64,10 +64,9 @@ void setupSocket(void) { struct sockaddr_un addr = { .sun_family = AF_UNIX, + .sun_path = SHELLSPAWN_SOCKPATH }; - snprintf(addr.sun_path, sizeof(addr.sun_path), "%s.%d", SHELLSPAWN_SOCKPATH, getuid()); - g_serverSocket = socket(AF_UNIX, SOCK_STREAM, 0); if (g_serverSocket == -1) { @@ -76,7 +75,7 @@ void setupSocket(void) } fcntl(g_serverSocket, F_SETFD, FD_CLOEXEC); - unlink(addr.sun_path); + unlink(SHELLSPAWN_SOCKPATH); if (bind(g_serverSocket, (struct sockaddr*) &addr, sizeof(addr)) == -1) { diff --git a/src/startup/darling.c b/src/startup/darling.c index 7912e9782..6014f46e8 100644 --- a/src/startup/darling.c +++ b/src/startup/darling.c @@ -37,7 +37,6 @@ along with Darling. If not, see . #include #include #include "../shellspawn/shellspawn.h" -#include "../shellspawn/shellsession.h" #include "darling.h" #include "darling-config.h" @@ -170,7 +169,7 @@ int main(int argc, char ** argv) { char socketPath[4096]; - snprintf(socketPath, sizeof(socketPath), "%s" SHELLSESSION_SOCKPATH, prefix); + snprintf(socketPath, sizeof(socketPath), "%s" SHELLSPAWN_SOCKPATH, prefix); unlink(socketPath); @@ -546,20 +545,20 @@ static size_t escapeQuotes(char *dest, const char *src) return len; } -int connectToShellsession(void) +int connectToShellspawn(void) { struct sockaddr_un addr; int sockfd; - // Connect to the shellsession daemon in the container + // Connect to the shellspawn daemon in the container addr.sun_family = AF_UNIX; #if USE_LINUX_4_11_HACK addr.sun_path[0] = '\0'; strcpy(addr.sun_path, prefix); - strcat(addr.sun_path, SHELLSESSION_SOCKPATH); + strcat(addr.sun_path, SHELLSPAWN_SOCKPATH); #else - snprintf(addr.sun_path, sizeof(addr.sun_path), "%s" SHELLSESSION_SOCKPATH, prefix); + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s" SHELLSPAWN_SOCKPATH, prefix); #endif sockfd = socket(AF_UNIX, SOCK_STREAM, 0); @@ -578,66 +577,6 @@ int connectToShellsession(void) return sockfd; } -static int startShellspawnSession(int sockfd) -{ - shellsession_cmd_t cmd; - - cmd.uid = g_originalGid; - cmd.gid = g_originalGid; - - if (write(sockfd, &cmd, sizeof(cmd)) != sizeof(cmd)) - { - fprintf(stderr, "Error sending command to shellsession: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } - - char dummy = '\0'; - char cmsgbuf[CMSG_SPACE(sizeof(int))]; - struct msghdr msg; - struct iovec iov; - struct cmsghdr *cmptr; - int sessionFD; - - memset(&msg, 0, sizeof(msg)); - msg.msg_control = cmsgbuf; - msg.msg_controllen = sizeof(cmsgbuf); - - iov.iov_base = &dummy; - iov.iov_len = sizeof(dummy); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - if (recvmsg(sockfd, &msg, 0) != sizeof(dummy)) - { - fprintf(stderr, "Error receiving reply from shellsession: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } - - cmptr = CMSG_FIRSTHDR(&msg); - - if (cmptr == NULL || cmptr->cmsg_level != SOL_SOCKET || cmptr->cmsg_type != SCM_RIGHTS) - { - fprintf(stderr, "Invalid reply from shellsession: no attached FD\n"); - exit(EXIT_FAILURE); - } - - if (cmptr->cmsg_len != CMSG_LEN(sizeof(int))) - { - fprintf(stderr, "Invalid reply from shellsession: invalid CMSG length %lu (expected %lu)\n", cmptr->cmsg_len, sizeof(int)); - exit(EXIT_FAILURE); - } - - memcpy(&sessionFD, CMSG_DATA(cmptr), sizeof(int)); - - if (sessionFD < 0) - { - fprintf(stderr, "Invalid reply from shellsession: invalid FD %d\n", sessionFD); - exit(EXIT_FAILURE); - } - - return sessionFD; -} - void setupShellspawnEnv(int sockfd) { static const char* skip_vars[] = { @@ -763,8 +702,7 @@ void spawnShell(const char** argv) else buffer = NULL; - sockfd = connectToShellsession(); - sockfd = startShellspawnSession(sockfd); + sockfd = connectToShellspawn(); setupShellspawnEnv(sockfd); @@ -788,9 +726,7 @@ void spawnBinary(const char* binary, const char** argv) int fds[3], master; int sockfd; - sockfd = connectToShellsession(); - sockfd = startShellspawnSession(sockfd); - + sockfd = connectToShellspawn(); setupShellspawnEnv(sockfd); pushShellspawnCommand(sockfd, SHELLSPAWN_SETEXEC, binary);