blob: 9ad953e1f65c4171dd71060bb3672ebcc9d5b064 [file] [log] [blame]
// Copyright 2019 The Chromium OS 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 "cups_proxy/daemon.h"
#include <sysexits.h>
#include <string>
#include <utility>
#include <base/bind.h>
#include <base/files/file_util.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/bus.h>
#include <dbus/message.h>
#include <mojo/core/embedder/embedder.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
namespace cups_proxy {
namespace {
constexpr char kCupsProxySocketPath[] = "/run/cups_proxy/cups.sock";
base::ScopedFD InitSocket() {
base::ScopedFD fd(socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0));
if (!fd.is_valid()) {
PLOG(ERROR) << "Failed to create socket";
return {};
}
struct sockaddr_un unix_addr = {};
base::FilePath socket_path(kCupsProxySocketPath);
std::string socket_name = socket_path.value();
unix_addr.sun_family = AF_UNIX;
CHECK(socket_name.size() < sizeof(unix_addr.sun_path));
strncpy(unix_addr.sun_path, socket_name.c_str(), socket_name.size());
size_t unix_addr_len =
offsetof(struct sockaddr_un, sun_path) + socket_name.size();
// Delete any old FS instances.
if (unlink(socket_name.c_str()) < 0 && errno != ENOENT) {
PLOG(ERROR) << "unlink " << socket_name;
return {};
}
// Bind the socket.
if (bind(fd.get(), reinterpret_cast<const sockaddr*>(&unix_addr),
unix_addr_len) < 0) {
PLOG(ERROR) << "bind " << socket_path.value();
return {};
}
// Sets the correct socket permissions.
if (chmod(socket_name.c_str(), 0660) < 0) {
PLOG(ERROR) << "Failed to set permissions";
unlink(socket_name.c_str());
return {};
}
// Start listening on the socket.
if (listen(fd.get(), SOMAXCONN) < 0) {
PLOG(ERROR) << "listen " << socket_path.value();
unlink(socket_name.c_str());
return {};
}
return fd;
}
} // namespace
Daemon::Daemon() : weak_ptr_factory_(this) {}
Daemon::~Daemon() {}
int Daemon::OnInit() {
int exit_code = DBusDaemon::OnInit();
if (exit_code != EX_OK)
return exit_code;
mojo::core::Init();
ipc_support_ = std::make_unique<mojo::core::ScopedIPCSupport>(
base::ThreadTaskRunnerHandle::Get(),
mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST);
CHECK(mojo_handler_.StartThread());
InitDBus();
base::ScopedFD listen_fd = InitSocket();
if (!listen_fd.is_valid()) {
LOG(ERROR) << "Error initializing unix listen socket.";
return EX_UNAVAILABLE;
}
mhd_daemon_ = StartMHDDaemon(std::move(listen_fd), &mojo_handler_);
if (!mhd_daemon_) {
LOG(ERROR) << "Error initializing MHD daemon.";
return EX_UNAVAILABLE;
}
return EX_OK;
}
void Daemon::InitDBus() {
LOG(INFO) << "Registering as handler for CupsProxyDaemon in D-Bus ...";
// Get or create the ExportedObject for the CupsProxyDaemon
dbus::ExportedObject* const cups_proxy_exported_object =
bus_->GetExportedObject(dbus::ObjectPath(printing::kCupsProxyDaemonPath));
CHECK(cups_proxy_exported_object);
// Register a handler of the BootstrapMojoConnection method.
CHECK(cups_proxy_exported_object->ExportMethodAndBlock(
printing::kCupsProxyDaemonInterface,
printing::kBootstrapMojoConnectionMethod,
base::Bind(&Daemon::BootstrapMojoConnection,
weak_ptr_factory_.GetWeakPtr())));
// Take ownership of the CupsProxy service.
CHECK(bus_->RequestOwnershipAndBlock(printing::kCupsProxyDaemonName,
dbus::Bus::REQUIRE_PRIMARY));
}
void Daemon::BootstrapMojoConnection(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
if (mojo_handler_.IsInitialized()) {
LOG(ERROR) << "CupsProxyService already initialized";
std::move(response_sender)
.Run(dbus::ErrorResponse::FromMethodCall(
method_call, DBUS_ERROR_FAILED, "Bootstrap already completed"));
return;
}
base::ScopedFD file_handle;
dbus::MessageReader reader(method_call);
if (!reader.PopFileDescriptor(&file_handle)) {
LOG(ERROR) << "Couldn't extract file descriptor from D-Bus call";
std::move(response_sender)
.Run(dbus::ErrorResponse::FromMethodCall(
method_call, DBUS_ERROR_INVALID_ARGS, "Expected file descriptor"));
return;
}
if (!file_handle.is_valid()) {
LOG(ERROR) << "ScopedFD extracted from D-Bus call was invalid (i.e. empty)";
std::move(response_sender)
.Run(dbus::ErrorResponse::FromMethodCall(
method_call, DBUS_ERROR_INVALID_ARGS,
"Invalid (empty) file descriptor"));
return;
}
if (!base::SetCloseOnExec(file_handle.get())) {
PLOG(ERROR) << "Failed setting FD_CLOEXEC on file descriptor";
std::move(response_sender)
.Run(dbus::ErrorResponse::FromMethodCall(
method_call, DBUS_ERROR_FAILED,
"Failed setting FD_CLOEXEC on file descriptor"));
return;
}
// Connect to mojo in the requesting process.
mojo_handler_.SetupMojoPipe(
std::move(file_handle),
base::Bind(&Daemon::OnConnectionError, base::Unretained(this)));
// Send success response.
std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
}
void Daemon::OnConnectionError() {
// Die upon Mojo error. Reconnection can occur when the daemon is restarted.
// (A future Mojo API may enable Mojo re-bootstrap without a process restart.)
LOG(ERROR) << "CupsProxyDaemon MojoConnectionError; quitting.";
Quit();
}
} // namespace cups_proxy