blob: e126f7e4ee5ece2436987d3b3d7c855332ca40f4 [file] [log] [blame] [edit]
// Copyright 2019 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 "src/developer/sshd-host/service.h"
#include <arpa/inet.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <fuchsia/boot/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/wait.h>
#include <lib/async/default.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/io.h>
#include <lib/fdio/spawn.h>
#include <lib/fit/defer.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/sys/cpp/service_directory.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/job.h>
#include <lib/zx/process.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <memory>
#include <vector>
#include <fbl/string_printf.h>
#include <fbl/unique_fd.h>
#include "src/lib/fsl/tasks/fd_waiter.h"
#include "src/lib/fxl/macros.h"
#include "src/lib/fxl/strings/string_printf.h"
const auto kSshdPath = "/pkg/bin/sshd";
const char* kSshdArgv[] = {kSshdPath, "-ie", "-f", "/config/data/sshd_config", nullptr};
namespace sshd_host {
zx_status_t provision_authorized_keys_from_bootloader_file(
std::shared_ptr<sys::ServiceDirectory> service_directory) {
zx_status_t status;
fuchsia::boot::ItemsSyncPtr boot_items;
status = service_directory->Connect(boot_items.NewRequest());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Provisioning keys from boot item: failed to connect to boot items service: "
<< zx_status_get_string(status);
return status;
}
zx::vmo vmo;
status = boot_items->GetBootloaderFile(std::string(kAuthorizedKeysBootloaderFileName.data(),
kAuthorizedKeysBootloaderFileName.size()),
&vmo);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Provisioning keys from boot item: GetBootloaderFile failed with: "
<< zx_status_get_string(status);
return status;
}
if (!vmo.is_valid()) {
FX_LOGS(INFO) << "Provisioning keys from boot item: bootloader file not found: "
<< kAuthorizedKeysBootloaderFileName;
return ZX_ERR_NOT_FOUND;
}
uint64_t size;
status = vmo.get_property(ZX_PROP_VMO_CONTENT_SIZE, &size, sizeof(size));
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Provisioning keys from boot item: unable to get file size: "
<< zx_status_get_string(status);
return status;
}
auto buffer = std::make_unique<uint8_t[]>(size);
status = vmo.read(buffer.get(), 0, size);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Provisioning keys from boot item: failed to read file: "
<< zx_status_get_string(status);
return status;
}
if (mkdir(kSshDirectory, 0700) && errno != EEXIST) {
FX_LOGS(ERROR) << "Provisioning keys from boot item: failed to create directory: "
<< kSshDirectory << " Error: " << strerror(errno);
return ZX_ERR_IO;
}
fbl::unique_fd kfd(open(kAuthorizedKeysPath, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR));
if (!kfd) {
FX_LOGS(ERROR) << "Provisioning keys from boot item: open failed: " << kAuthorizedKeysPath
<< " error: " << strerror(errno);
return errno == EEXIST ? ZX_ERR_ALREADY_EXISTS : ZX_ERR_IO;
}
if (write(kfd.get(), buffer.get(), size) != static_cast<ssize_t>(size)) {
FX_LOGS(ERROR) << "Provisioning keys from boot item: write failed: " << strerror(errno);
return ZX_ERR_IO;
}
fsync(kfd.get());
if (close(kfd.release())) {
FX_LOGS(ERROR) << "Provisioning keys from boot item: close failed: " << strerror(errno);
return ZX_ERR_IO;
}
FX_LOGS(INFO) << "Provisioning keys from boot item: authorized_keys provisioned";
return ZX_OK;
}
zx_status_t make_child_job(const zx::job& parent, std::string name, zx::job* job) {
zx_status_t s;
if ((s = zx::job::create(parent, 0, job)) != ZX_OK) {
FX_PLOGS(ERROR, s) << "Failed to create child job; parent = " << parent.get();
return s;
}
if ((s = job->set_property(ZX_PROP_NAME, name.data(), name.size())) != ZX_OK) {
FX_PLOGS(ERROR, s) << "Failed to set name of child job; job = " << job->get();
return s;
}
if ((s = job->replace(kChildJobRights, job)) != ZX_OK) {
FX_PLOGS(ERROR, s) << "Failed to set rights on child job; job = " << job->get();
return s;
}
return ZX_OK;
}
Service::Service(uint16_t port) : port_(port) {
sock_ = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
if (sock_ < 0) {
FX_LOGS(ERROR) << "Failed to create socket: " << strerror(errno);
exit(1);
}
const struct sockaddr_in6 addr {
.sin6_family = AF_INET6, .sin6_port = htons(port_), .sin6_addr = in6addr_any,
};
if (bind(sock_, reinterpret_cast<const sockaddr*>(&addr), sizeof addr) < 0) {
FX_LOGS(ERROR) << "Failed to bind to " << port_ << ": " << strerror(errno);
exit(1);
}
FX_SLOG(INFO, "listen() for inbound SSH connections", "port", (int)port_);
if (listen(sock_, 10) < 0) {
FX_LOGS(ERROR) << "Failed to listen: " << strerror(errno);
exit(1);
}
std::string job_name = fxl::StringPrintf("tcp:%d", port);
if (make_child_job(*zx::job::default_job(), job_name, &job_) != ZX_OK) {
exit(1);
}
Wait();
}
Service::~Service() {
for (auto& waiter : process_waiters_) {
zx_status_t s;
if ((s = zx_task_kill(waiter->object())) != ZX_OK) {
FX_PLOGS(ERROR, s) << "Failed kill child task";
}
if ((s = zx_handle_close(waiter->object())) != ZX_OK) {
FX_PLOGS(ERROR, s) << "Failed close child handle";
}
}
}
void Service::Wait() {
waiter_.Wait(
[this](zx_status_t /*success*/, uint32_t /*events*/) {
struct sockaddr_in6 peer_addr {};
socklen_t peer_addr_len = sizeof(peer_addr);
FX_SLOG(INFO, "Waiting for next connection");
int conn = accept(sock_, reinterpret_cast<struct sockaddr*>(&peer_addr), &peer_addr_len);
if (conn < 0) {
if (errno == EPIPE) {
FX_LOGS(ERROR) << "The netstack died. Terminating.";
exit(1);
} else {
FX_LOGS(ERROR) << "Failed to accept: " << strerror(errno);
// Wait for another connection.
Wait();
}
return;
}
std::string peer_name = "unknown";
char host[NI_MAXHOST];
char port[NI_MAXSERV];
if (int res =
getnameinfo(reinterpret_cast<struct sockaddr*>(&peer_addr), peer_addr_len, host,
sizeof(host), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV);
res == 0) {
peer_name = fxl::StringPrintf("%s:%s", host, port);
} else {
FX_LOGS(WARNING)
<< "Error from getnameinfo(.., NI_NUMERICHOST | NI_NUMERICSERV) for peer address: "
<< gai_strerror(res);
}
Launch(conn, peer_name);
Wait();
},
sock_, POLLIN);
}
void Service::Launch(int conn, const std::string& peer_name) {
FX_SLOG(INFO, "accepted connection", "remote", peer_name.c_str());
// Create a new job to run the child in.
zx::job child_job;
if (make_child_job(job_, peer_name, &child_job) != ZX_OK) {
shutdown(conn, SHUT_RDWR);
close(conn);
FX_LOGS(ERROR) << "Child job creation failed, connection closed";
return;
}
fdio_flat_namespace_t* flat_ns = nullptr;
zx_status_t status = fdio_ns_export_root(&flat_ns);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "fdio_ns_export_root failed: " << status;
return;
}
auto cleanup = fit::defer([&flat_ns]() { fdio_ns_free_flat_ns(flat_ns); });
// Room for stdio handles and namespace entries
fdio_spawn_action_t actions[3 + flat_ns->count];
size_t action = 0;
// Transfer the socket as stdin and stdout
actions[action++] = {.action = FDIO_SPAWN_ACTION_CLONE_FD,
.fd = {.local_fd = conn, .target_fd = STDIN_FILENO}};
actions[action++] = {.action = FDIO_SPAWN_ACTION_TRANSFER_FD,
.fd = {.local_fd = conn, .target_fd = STDOUT_FILENO}};
actions[action++] =
// Clone this process' stderr.
{.action = FDIO_SPAWN_ACTION_CLONE_FD,
.fd = {.local_fd = STDERR_FILENO, .target_fd = STDERR_FILENO}};
const char* forward_as_svc;
// Forward either /svc_from_sys or /svc_for_sys as /svc to the child
DIR* dir = opendir("/svc_from_sys");
if (dir) {
closedir(dir);
forward_as_svc = "/svc_from_sys";
} else {
forward_as_svc = "/svc_for_sys";
}
for (size_t i = 0; i < flat_ns->count; ++i) {
const char* path = flat_ns->path[i];
if (strcmp(path, "/svc") == 0) {
// Don't forward our /svc to the child
continue;
} else if (strcmp(path, forward_as_svc) == 0) {
path = "/svc";
}
actions[action++] = {
.action = FDIO_SPAWN_ACTION_ADD_NS_ENTRY,
.ns =
{
.prefix = path,
.handle = flat_ns->handle[i],
},
};
}
const uint32_t kSpawnFlags =
FDIO_SPAWN_CLONE_JOB | FDIO_SPAWN_DEFAULT_LDSVC | FDIO_SPAWN_CLONE_UTC_CLOCK;
zx::process process;
char error[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
status = fdio_spawn_etc(child_job.get(), kSpawnFlags, kSshdPath, kSshdArgv, nullptr, action,
actions, process.reset_and_get_address(), error);
if (status < 0) {
shutdown(conn, SHUT_RDWR);
close(conn);
FX_LOGS(ERROR) << "Error from fdio_spawn_etc: " << error;
return;
}
std::unique_ptr<async::Wait> waiter =
std::make_unique<async::Wait>(process.get(), ZX_PROCESS_TERMINATED);
waiter->set_handler([this, process = std::move(process), job = std::move(child_job)](
async_dispatcher_t*, async::Wait*, zx_status_t /*status*/,
const zx_packet_signal_t* /*signal*/) mutable {
ProcessTerminated(std::move(process), std::move(job));
});
waiter->Begin(async_get_default_dispatcher());
process_waiters_.push_back(std::move(waiter));
}
void Service::ProcessTerminated(zx::process process, zx::job job) {
{
zx_info_process_t info;
if (zx_status_t s = process.get_info(ZX_INFO_PROCESS, &info, sizeof(info), nullptr, nullptr);
s != ZX_OK) {
FX_PLOGS(ERROR, s) << "Failed to get proces info";
}
if (info.return_code != 0) {
FX_LOGS(WARNING) << "Process finished with nonzero status: " << info.return_code;
}
}
// Kill the process and the job.
if (zx_status_t s = process.kill(); s != ZX_OK) {
FX_PLOGS(ERROR, s) << "Failed to kill child process";
}
if (zx_status_t s = job.kill(); s != ZX_OK) {
FX_PLOGS(ERROR, s) << "Failed to kill child job";
}
// Find the waiter.
auto i = std::find_if(
process_waiters_.begin(), process_waiters_.end(),
[&process](const std::unique_ptr<async::Wait>& w) { return w->object() == process.get(); });
// And remove it.
if (i != process_waiters_.end()) {
process_waiters_.erase(i);
}
}
} // namespace sshd_host