blob: 977e06faefe75bd227f1dc18ba0dc7411c438138 [file] [log] [blame]
// 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 <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(fxbug.dev/4462): 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 | FDIO_SPAWN_CLONE_UTC_CLOCK;
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, 0);
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;
}