| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <assert.h> |
| #include <fcntl.h> |
| #include <pwd.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <zircon/device/vfs.h> |
| |
| #ifndef _ALL_SOURCE |
| #define _ALL_SOURCE // Enables thrd_create_with_name in <threads.h>. |
| #endif |
| #include <fuchsia/hardware/pty/c/fidl.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async-loop/loop.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/io.h> |
| #include <lib/fdio/spawn.h> |
| #include <lib/fdio/unsafe.h> |
| #include <lib/zircon-internal/paths.h> |
| #include <threads.h> |
| #include <zircon/processargs.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/port.h> |
| |
| #include "fuchsia/loader-wrapper.h" |
| #include "misc.h" |
| #include "openbsd-compat/bsd-misc.h" |
| #include "xmalloc.h" |
| |
| static async_loop_t* loop; |
| static thrd_t loop_thread; |
| static loader_service_t* ldsvc; |
| |
| async_dispatcher_t* get_async() { return async_loop_get_dispatcher(loop); } |
| |
| void fuchsia_init_async(void) { |
| zx_status_t status; |
| status = async_loop_create(&kAsyncLoopConfigNoAttachToCurrentThread, &loop); |
| if (status != ZX_OK) { |
| fprintf(stderr, "fatal: failed to create async loop\n"); |
| exit(1); |
| } |
| |
| status = async_loop_start_thread(loop, "sshd-async-loop", &loop_thread); |
| if (status != ZX_OK) { |
| fprintf(stderr, "fatal: failed to create async loop\n"); |
| exit(1); |
| } |
| |
| int boot_lib_dir; |
| status = fdio_open_fd("/boot/lib", |
| ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_EXECUTABLE | ZX_FS_FLAG_DIRECTORY, |
| &boot_lib_dir); |
| if (status != ZX_OK) { |
| fprintf(stderr, "fatal: failed to open /boot/lib: %d\n", status); |
| exit(1); |
| } |
| |
| if ((status = loader_service_create(get_async(), boot_lib_dir, "openssh", &ldsvc)) != ZX_OK) { |
| fprintf(stderr, "failed to create loader service: %s\n", zx_status_get_string(status)); |
| exit(1); |
| } |
| } |
| |
| int chroot(const char* path) { return -1; } |
| |
| typedef struct Authctxt Authctxt; |
| |
| int sys_auth_passwd(Authctxt* authctxt, const char* password) { |
| // Password authentication always fails. |
| return 0; |
| } |
| |
| #define USERNAME_MAX 32 |
| static char username[USERNAME_MAX + 1] = {'f', 'u', 'c', 'h', 's', 'i', 'a', 0}; |
| static struct passwd static_passwd = { |
| .pw_name = username, |
| .pw_passwd = "", |
| .pw_uid = 23, // matches ZX_UID |
| .pw_gid = 23, |
| .pw_gecos = "Fuchsia", |
| .pw_dir = "/", |
| .pw_shell = ZX_SHELL_DEFAULT, |
| }; |
| |
| struct passwd* getpwnam(const char* name) { |
| size_t len = strlen(name); |
| if (len > USERNAME_MAX) { |
| errno = EINVAL; |
| return NULL; |
| } |
| strncpy(username, name, len); |
| username[len] = 0; |
| return &static_passwd; |
| } |
| |
| struct passwd* getpwuid(uid_t uid) { |
| return &static_passwd; |
| } |
| |
| #define ARGV_MAX 256 |
| |
| typedef struct { |
| enum { UNUSED, RUNNING, STOPPED } state; |
| int exit_code; |
| } Child; |
| |
| #define BASE_PID 2 |
| #define NUM_CHILDREN 256 |
| static Child children[NUM_CHILDREN]; |
| |
| static Child* get_child(pid_t pid) { |
| assert(pid - BASE_PID < NUM_CHILDREN); |
| assert(pid >= BASE_PID); |
| return &children[pid - BASE_PID]; |
| } |
| |
| static pid_t get_unused_pid() { |
| for (int i = 0; i < NUM_CHILDREN; i++) { |
| if (children[i].state == UNUSED) { |
| return i + BASE_PID; |
| } |
| } |
| fprintf(stderr, "Can't allocate new pid.\n"); |
| exit(1); |
| } |
| |
| static volatile sshsig_t sigchld_handler = SIG_IGN; |
| |
| typedef struct { |
| pid_t pid; |
| zx_handle_t in, out, err; |
| char* command; |
| char** env; |
| } LaunchRequest; |
| |
| // make_launch_request copies command, but takes ownership over env. |
| LaunchRequest* make_launch_request(const char* command, char** env, int in, int out, int err) { |
| LaunchRequest* lr = xmalloc(sizeof(LaunchRequest)); |
| lr->pid = get_unused_pid(); |
| if (command == NULL) { |
| lr->command = NULL; |
| } else { |
| lr->command = xmalloc(strlen(command) + 1); |
| strcpy(lr->command, command); |
| } |
| |
| lr->env = env; |
| |
| /* |
| * ssh's session.c will close out our file descriptors, as it is expecting a fork() to occur, so |
| * we can't use these fd's "some time later" in the child thread. Clone handles from the fd's now |
| * and use those on the thread. fdio_fd_transfer cannot be used because that immediately consumes |
| * and closes the fd, which results in racy behavior since session.c will still also close these |
| * fd numbers, but they may have been reused by other threads by then. |
| */ |
| zx_status_t status; |
| if ((status = fdio_fd_clone(in, &lr->in)) != ZX_OK) { |
| fprintf(stderr, "failed to clone in fd %d\n", in); |
| } |
| if ((status = fdio_fd_clone(out, &lr->out)) != ZX_OK) { |
| fprintf(stderr, "failed to clone out fd %d\n", out); |
| } |
| if ((status = fdio_fd_clone(err, &lr->err)) != ZX_OK) { |
| fprintf(stderr, "failed to clone err fd %d\n", err); |
| } |
| return lr; |
| } |
| |
| // free_env free's a **env as produced by session.c |
| void free_env(char** env) { |
| char** envf = env; |
| while (*envf != NULL) { |
| free(*envf++); |
| } |
| free(env); |
| } |
| |
| // free_launch_request frees a LaunchRequest. It frees command and env. It does |
| // not modify child, and does not close any of the handles. |
| void free_launch_request(LaunchRequest* lr) { |
| free(lr->command); |
| free_env(lr->env); |
| free(lr); |
| } |
| |
| static zx_status_t spawn_launch_request(LaunchRequest* lr, zx_handle_t* proc, char* err_msg) { |
| const char* command = lr->command; |
| char** env = lr->env; |
| |
| // TODO(CF-578): replace most of this with a "shell runner" instead |
| const char* argv[ARGV_MAX]; |
| int argc = 1; |
| argv[0] = ZX_SHELL_DEFAULT; |
| if (command) { |
| argv[argc++] = "-c"; |
| argv[argc++] = command; |
| } else { |
| command = argv[0]; |
| } |
| argv[argc] = NULL; |
| |
| zx_status_t status; |
| zx_handle_t ldsvc_hnd; |
| if ((status = loader_service_connect(ldsvc, &ldsvc_hnd)) != ZX_OK) { |
| fprintf(stderr, "failed to connect to loader service: %s\n", zx_status_get_string(status)); |
| exit(1); |
| } |
| |
| fdio_spawn_action_t actions[4] = { |
| { |
| .action = FDIO_SPAWN_ACTION_ADD_HANDLE, |
| .h = {.id = PA_LDSVC_LOADER, .handle = ldsvc_hnd}, |
| }, |
| { |
| .action = FDIO_SPAWN_ACTION_ADD_HANDLE, |
| .h = {.id = PA_HND(PA_FD, STDIN_FILENO), .handle = lr->in}, |
| }, |
| { |
| .action = FDIO_SPAWN_ACTION_ADD_HANDLE, |
| .h = {.id = PA_HND(PA_FD, STDOUT_FILENO), .handle = lr->out}, |
| }, |
| { |
| .action = FDIO_SPAWN_ACTION_ADD_HANDLE, |
| .h = {.id = PA_HND(PA_FD, STDERR_FILENO), .handle = lr->err}, |
| }, |
| }; |
| |
| uint32_t flags = FDIO_SPAWN_CLONE_JOB | FDIO_SPAWN_CLONE_NAMESPACE; |
| return fdio_spawn_etc(ZX_HANDLE_INVALID, flags, argv[0], argv, (const char* const*)env, 4, |
| actions, proc, err_msg); |
| } |
| |
| static int child_thread_func(void* voidp) { |
| thrd_detach(thrd_current()); |
| |
| Child* child = get_child(((LaunchRequest*)voidp)->pid); |
| zx_handle_t proc; |
| |
| char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; |
| |
| zx_status_t status = spawn_launch_request(voidp, &proc, err_msg); |
| free_launch_request(voidp); |
| |
| if (status < 0) { |
| fprintf(stderr, "error from fdio_spawn_etc: %s\n", err_msg); |
| fprintf(stderr, " status=%d (%s)\n", status, zx_status_get_string(status)); |
| exit(1); |
| } |
| |
| zx_signals_t observed; |
| zx_object_wait_one(proc, ZX_PROCESS_TERMINATED, ZX_TIME_INFINITE, &observed); |
| |
| zx_info_process_t info; |
| size_t actual; |
| zx_object_get_info(proc, ZX_INFO_PROCESS, &info, sizeof(info), &actual, NULL); |
| |
| child->state = STOPPED; |
| child->exit_code = info.return_code; |
| |
| sshsig_t handler = sigchld_handler; |
| if (handler == SIG_IGN || handler == SIG_DFL) { |
| // Don't call a handler |
| } else { |
| handler(SIGCHLD); |
| } |
| |
| zx_handle_close(proc); |
| return 0; |
| } |
| |
| // Note: **env is consumed by this function and will be freed. |
| pid_t fuchsia_launch_child(const char* command, char** env, int in, int out, int err) { |
| LaunchRequest* lr = make_launch_request(command, env, in, out, err); |
| pid_t pid = lr->pid; |
| get_child(pid)->state = RUNNING; |
| |
| thrd_t child_thread; |
| if (thrd_create_with_name(&child_thread, child_thread_func, (void*)lr, "child-waiter") != 0) { |
| fprintf(stderr, "Failed to create process spawn thread: %s\n", strerror(errno)); |
| exit(1); |
| } |
| |
| return pid; |
| } |
| |
| sshsig_t ssh_signal(int signum, sshsig_t handler) { |
| if (signum == SIGCHLD) { |
| sigchld_handler = handler; |
| } |
| // Ignore all non-SIGCHLD requests |
| return handler; |
| } |
| |
| pid_t waitpid(pid_t pid, int* status, int options) { |
| if (pid == -1 || pid == 0) { |
| // Find an exited process. |
| for (pid = BASE_PID; pid < BASE_PID + NUM_CHILDREN; pid++) { |
| if (get_child(pid)->state == STOPPED) { |
| return waitpid(pid, status, options); |
| } |
| } |
| if (options & WNOHANG) { |
| return 0; |
| } else { |
| fprintf(stderr, "No child pids waiting for wait.\n"); |
| exit(1); |
| } |
| } |
| |
| Child* child = get_child(pid); |
| if (child->state != STOPPED) { |
| fprintf(stderr, "Child with pid %d isn't stopped.\n", pid); |
| exit(1); |
| } |
| |
| if (status) { |
| // Make a status that can be parsed by WIFEXITED/WEXITSTATUS/etc. |
| *status = (0xFF & child->exit_code) << 8; |
| } |
| child->state = UNUSED; |
| |
| return pid; |
| } |
| |
| // This is a very inefficient way to emulate select() by creating a port, adding all of the fds |
| // of interest as async waits, and blocking until we get a port packet back. |
| // The callers of this (like serverloop) generally have a static set of fds they care about, so |
| // it'd be much more efficient for them to register async waits on a port object that persists |
| // across blocking calls. |
| int fuchsia_select(int nfds, void* readfds, void* writefds, void* exceptfds, |
| struct timeval* timeout) { |
| if (exceptfds) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| fd_set* readfds_fd_set = (fd_set*)readfds; |
| fd_set* writefds_fd_set = (fd_set*)writefds; |
| |
| int ret = 0; |
| zx_handle_t port = ZX_HANDLE_INVALID; |
| fdio_t* ios[FD_SETSIZE] = {0}; |
| |
| // Create a fresh port for this wait. |
| zx_status_t st = zx_port_create(0, &port); |
| |
| if (st != ZX_OK) { |
| fprintf(stderr, "Can't allocate new port.\n"); |
| errno = EINVAL; |
| ret = -1; |
| goto cleanup; |
| } |
| |
| // Register port waits for file descriptors in the read and write sets. |
| for (int fd = 0; fd < nfds; ++fd) { |
| uint32_t events = 0; |
| if (readfds_fd_set && FD_ISSET(fd, readfds_fd_set)) { |
| events |= POLLIN; |
| } |
| if (writefds_fd_set && FD_ISSET(fd, writefds_fd_set)) { |
| events |= POLLOUT; |
| } |
| if (!events) |
| continue; |
| |
| fdio_t* io; |
| // This acquires a reference to the fdio which is released in the cleanup path below. |
| if ((io = fdio_unsafe_fd_to_io(fd)) == NULL) { |
| errno = EBADF; |
| ret = -1; |
| goto cleanup; |
| } |
| ios[fd] = io; |
| zx_handle_t h; |
| zx_signals_t sigs; |
| // Translate the poll-style events to fdio-specific signal bits to wait on. |
| fdio_unsafe_wait_begin(io, events, &h, &sigs); |
| if (h == ZX_HANDLE_INVALID) { |
| errno = EBADF; |
| ret = -1; |
| goto cleanup; |
| } |
| uint64_t key = fd; |
| st = zx_object_wait_async(h, port, key, sigs, ZX_WAIT_ASYNC_ONCE); |
| if (st != ZX_OK) { |
| fprintf(stderr, "Can't wait on object %d.\n", st); |
| errno = EINVAL; |
| ret = -1; |
| goto cleanup; |
| } |
| } |
| |
| zx_time_t deadline = (timeout == NULL) |
| ? ZX_TIME_INFINITE |
| : zx_deadline_after(ZX_SEC(timeout->tv_sec) + ZX_USEC(timeout->tv_usec)); |
| |
| for (;;) { |
| zx_port_packet_t packet; |
| st = zx_port_wait(port, deadline, &packet); |
| |
| // We expect zx_port_wait to return either ZX_ERR_TIMED_OUT if nothing happened, or ZX_OK |
| // if at least one thing happened. |
| if (st == ZX_OK) { |
| if (packet.type != ZX_PKT_TYPE_SIGNAL_ONE) { |
| fprintf(stderr, "Unexpected port packet type %u\n", packet.type); |
| errno = EINVAL; |
| ret = -1; |
| goto cleanup; |
| } |
| // We've heard about an fd in the set we care about. Update the read/write |
| // sets to reflect this information, then remove them from the set we are |
| // listening to. |
| int fd = (int)packet.key; |
| uint32_t events = 0; |
| fdio_t* io = ios[fd]; |
| if (!io) { |
| fprintf(stderr, "Can't find fd for packet key %d.\n", fd); |
| errno = EINVAL; |
| ret = -1; |
| goto cleanup; |
| } |
| // fdio_unsafe_wait_end translates the signals back to poll-style flags. |
| fdio_unsafe_wait_end(io, packet.signal.observed, &events); |
| if (readfds_fd_set && FD_ISSET(fd, readfds_fd_set)) { |
| if (events & POLLIN) |
| ret++; |
| else |
| FD_CLR(fd, readfds_fd_set); |
| } |
| if (writefds_fd_set && FD_ISSET(fd, writefds_fd_set)) { |
| if (events & POLLOUT) |
| ret++; |
| else |
| FD_CLR(fd, writefds_fd_set); |
| } |
| // The read and write sets for this fd are now updated, and our wait has expired, so |
| // remove this fd from the set of things we care about. |
| ios[fd] = NULL; |
| fdio_unsafe_release(io); |
| } else if (st == ZX_ERR_TIMED_OUT) { |
| break; |
| } else { |
| fprintf(stderr, "Port wait return unexpected error %d.\n", st); |
| errno = EINVAL; |
| ret = -1; |
| goto cleanup; |
| } |
| |
| // After pulling the first packet out, poll without blocking by doing another wait with a |
| // deadline in the past. This will populate any other members of the read/write set that |
| // are ready to go now. |
| deadline = 0; |
| } |
| |
| // If there are any entries left in ios at this point, we have not received a port packet |
| // indicating that those fds are readable or writable and so we should clear those from the |
| // read/write sets. |
| for (int fd = 0; fd < nfds; ++fd) { |
| if (ios[fd]) { |
| if (readfds_fd_set && FD_ISSET(fd, readfds_fd_set)) { |
| FD_CLR(fd, readfds_fd_set); |
| } |
| if (writefds_fd_set && FD_ISSET(fd, writefds_fd_set)) { |
| FD_CLR(fd, writefds_fd_set); |
| } |
| } |
| } |
| |
| cleanup: |
| // Release reference to any fdio objects we acquired with fdio_unsafe_fd_to_io(). |
| for (int fd = 0; fd < nfds; ++fd) { |
| if (ios[fd]) { |
| fdio_unsafe_release(ios[fd]); |
| } |
| } |
| |
| if (port != ZX_HANDLE_INVALID) { |
| zx_handle_close(port); |
| } |
| return ret; |
| } |
| |
| int fuchsia_open_pty_client(int fd, uint32_t id) { |
| fdio_t* io = fdio_unsafe_fd_to_io(fd); |
| if (io == NULL) { |
| fprintf(stderr, "Failed to open PTY client: couldn't create fdio\n"); |
| return -1; |
| } |
| |
| zx_handle_t device_channel, client_channel; |
| zx_status_t status = zx_channel_create(0 /* flags */, &device_channel, &client_channel); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Failed to open PTY client: %s\n", zx_status_get_string(status)); |
| return -1; |
| } |
| |
| zx_status_t fidl_status = fuchsia_hardware_pty_DeviceOpenClient(fdio_unsafe_borrow_channel(io), |
| id, device_channel, &status); |
| fdio_unsafe_release(io); |
| if (fidl_status != ZX_OK) { |
| fprintf(stderr, "Failed to open PTY client: %s\n", zx_status_get_string(fidl_status)); |
| zx_handle_close(device_channel); |
| zx_handle_close(client_channel); |
| return -1; |
| } |
| if (status != ZX_OK) { |
| fprintf(stderr, "Failed to open PTY client: %s\n", zx_status_get_string(status)); |
| zx_handle_close(device_channel); |
| zx_handle_close(client_channel); |
| return -1; |
| } |
| |
| int client_fd; |
| status = fdio_fd_create(client_channel, &client_fd); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Failed to open PTY client: %s\n", zx_status_get_string(status)); |
| zx_handle_close(device_channel); |
| zx_handle_close(client_channel); |
| return -1; |
| } |
| return client_fd; |
| } |