/remoting/host/mac/host_service_main.cc
C++ | 399 lines | 303 code | 49 blank | 47 comment | 51 complexity | 6fba6d1be655e87802a41a56b5a818b1 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.0, BSD-2-Clause, LGPL-2.1, MPL-2.0, 0BSD, EPL-1.0, MPL-2.0-no-copyleft-exception, GPL-2.0, BitTorrent-1.0, CPL-1.0, LGPL-3.0, Unlicense, BSD-3-Clause, CC0-1.0, JSON, MIT, GPL-3.0, CC-BY-SA-3.0, AGPL-1.0
- // Copyright 2019 The Chromium 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 <signal.h>
- #include <unistd.h>
- #include <iostream>
- #include <string>
- #include "base/at_exit.h"
- #include "base/bind.h"
- #include "base/command_line.h"
- #include "base/files/file_path.h"
- #include "base/files/file_util.h"
- #include "base/logging.h"
- #include "base/mac/mac_util.h"
- #include "base/no_destructor.h"
- #include "base/path_service.h"
- #include "base/process/launch.h"
- #include "base/process/process.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/strings/stringize_macros.h"
- #include "base/threading/platform_thread.h"
- #include "base/time/time.h"
- #include "remoting/base/logging.h"
- #include "remoting/host/host_exit_codes.h"
- #include "remoting/host/logging.h"
- #include "remoting/host/mac/constants_mac.h"
- #include "remoting/host/switches.h"
- #include "remoting/host/username.h"
- #include "remoting/host/version.h"
- namespace remoting {
- namespace {
- constexpr char kSwitchDisable[] = "disable";
- constexpr char kSwitchEnable[] = "enable";
- constexpr char kSwitchSaveConfig[] = "save-config";
- constexpr char kSwitchHostVersion[] = "host-version";
- constexpr char kSwitchHostRunFromLaunchd[] = "run-from-launchd";
- constexpr char kHostExeFileName[] = "remoting_me2me_host";
- constexpr char kNativeMessagingHostPath[] =
- "Contents/MacOS/native_messaging_host";
- // The exit code returned by 'wait' when a process is terminated by SIGTERM.
- constexpr int kSigtermExitCode = 128 + SIGTERM;
- // Constants controlling the host process relaunch throttling.
- constexpr base::TimeDelta kMinimumRelaunchInterval =
- base::TimeDelta::FromMinutes(1);
- constexpr int kMaximumHostFailures = 10;
- // Exit code 126 is defined by Posix to mean "Command found, but not
- // executable", and is returned if the process cannot be launched due to
- // parental control.
- constexpr int kPermissionDeniedParentalControl = 126;
- // This executable works as a proxy between launchd and the host. Signals of
- // interest to the host must be forwarded.
- constexpr int kSignalList[] = {
- SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGEMT,
- SIGFPE, SIGBUS, SIGSEGV, SIGSYS, SIGPIPE, SIGALRM, SIGTERM,
- SIGURG, SIGTSTP, SIGCONT, SIGTTIN, SIGTTOU, SIGIO, SIGXCPU,
- SIGXFSZ, SIGVTALRM, SIGPROF, SIGWINCH, SIGINFO, SIGUSR1, SIGUSR2};
- // Current host PID used to forward signals. 0 if host is not running.
- static base::ProcessId g_host_pid = 0;
- void HandleSignal(int signum) {
- if (g_host_pid) {
- // All other signals are forwarded to host then ignored except SIGTERM.
- // launchd sends SIGTERM when service is being stopped so both the host and
- // the host service need to terminate.
- HOST_LOG << "Forwarding signal " << signum << " to host process "
- << g_host_pid;
- kill(g_host_pid, signum);
- if (signum == SIGTERM) {
- HOST_LOG << "Host service is terminating upon reception of SIGTERM";
- exit(kSigtermExitCode);
- }
- } else {
- HOST_LOG << "Signal " << signum
- << " will not be forwarded since host is not running.";
- exit(128 + signum);
- }
- }
- void RegisterSignalHandler() {
- struct sigaction action = {};
- sigfillset(&action.sa_mask);
- action.sa_flags = 0;
- action.sa_handler = &HandleSignal;
- for (int signum : kSignalList) {
- if (sigaction(signum, &action, nullptr) == -1) {
- PLOG(DFATAL) << "Failed to register signal handler for signal " << signum;
- }
- }
- }
- class HostService {
- public:
- HostService();
- ~HostService();
- bool Disable();
- bool Enable();
- bool WriteStdinToConfig();
- int RunHost();
- void PrintHostVersion();
- void PrintPid();
- private:
- int RunHostFromOldScript();
- // Runs the permission-checker built into the native-messaging host. Returns
- // true if all permissions were granted (or no permission check is needed for
- // the version of MacOS).
- bool CheckPermission();
- bool HostIsEnabled();
- base::FilePath old_host_helper_file_;
- base::FilePath enabled_file_;
- base::FilePath config_file_;
- base::FilePath host_exe_file_;
- base::FilePath native_messaging_host_exe_file_;
- };
- HostService::HostService() {
- old_host_helper_file_ = base::FilePath(kOldHostHelperScriptPath);
- enabled_file_ = base::FilePath(kHostEnabledPath);
- config_file_ = base::FilePath(kHostConfigFilePath);
- base::FilePath host_service_dir;
- base::PathService::Get(base::DIR_EXE, &host_service_dir);
- host_exe_file_ = host_service_dir.AppendASCII(kHostExeFileName);
- native_messaging_host_exe_file_ =
- host_service_dir.AppendASCII(NATIVE_MESSAGING_HOST_BUNDLE_NAME)
- .AppendASCII(kNativeMessagingHostPath);
- }
- HostService::~HostService() = default;
- int HostService::RunHost() {
- // Mojave users updating from an older host likely have already granted a11y
- // permission to the old script. In this case we always delegate RunHost to
- // the old script so that they don't need to grant permission to a new app.
- if (base::mac::IsOS10_14() && base::PathExists(old_host_helper_file_)) {
- HOST_LOG << "RunHost will be delegated to the old host script.";
- return RunHostFromOldScript();
- }
- if (geteuid() != 0 && HostIsEnabled()) {
- // Only check for non-root users, as the permission wizard is not actionable
- // at the login screen. Also, permission is only needed when host is
- // enabled - the launchd service should exit immediately if the host is
- // disabled.
- if (!CheckPermission()) {
- return 1;
- }
- }
- int host_failure_count = 0;
- base::TimeTicks host_start_time;
- while (true) {
- if (!HostIsEnabled()) {
- HOST_LOG << "Daemon is disabled.";
- return 0;
- }
- // If this is not the first time the host has run, make sure we don't
- // relaunch it too soon.
- if (!host_start_time.is_null()) {
- base::TimeDelta host_lifetime = base::TimeTicks::Now() - host_start_time;
- HOST_LOG << "Host ran for " << host_lifetime;
- if (host_lifetime < kMinimumRelaunchInterval) {
- // If the host didn't run for very long, assume it crashed. Relaunch
- // only after a suitable delay and increase the failure count.
- host_failure_count++;
- LOG(WARNING) << "Host failure count " << host_failure_count << "/"
- << kMaximumHostFailures;
- if (host_failure_count >= kMaximumHostFailures) {
- LOG(ERROR) << "Too many host failures. Giving up.";
- return 1;
- }
- base::TimeDelta relaunch_in = kMinimumRelaunchInterval - host_lifetime;
- HOST_LOG << "Relaunching in " << relaunch_in;
- base::PlatformThread::Sleep(relaunch_in);
- } else {
- // If the host ran for long enough, reset the crash counter.
- host_failure_count = 0;
- }
- }
- host_start_time = base::TimeTicks::Now();
- base::CommandLine cmdline(host_exe_file_);
- cmdline.AppendSwitchPath("host-config", config_file_);
- std::string ssh_auth_sockname =
- "/tmp/chromoting." + GetUsername() + ".ssh_auth_sock";
- cmdline.AppendSwitchASCII("ssh-auth-sockname", ssh_auth_sockname);
- base::Process process = base::LaunchProcess(cmdline, base::LaunchOptions());
- if (!process.IsValid()) {
- LOG(ERROR) << "Failed to launch host process for unknown reason.";
- return 1;
- }
- g_host_pid = process.Pid();
- int exit_code;
- process.WaitForExit(&exit_code);
- g_host_pid = 0;
- const char* exit_code_string_ptr = ExitCodeToStringUnchecked(exit_code);
- std::string exit_code_string =
- exit_code_string_ptr ? (std::string(exit_code_string_ptr) + " (" +
- base::NumberToString(exit_code) + ")")
- : base::NumberToString(exit_code);
- if (exit_code == 0 || exit_code == kSigtermExitCode ||
- exit_code == kPermissionDeniedParentalControl ||
- (exit_code >= kMinPermanentErrorExitCode &&
- exit_code <= kMaxPermanentErrorExitCode)) {
- HOST_LOG << "Host returned permanent exit code " << exit_code_string
- << " at " << base::Time::Now();
- if (exit_code == kInvalidHostIdExitCode ||
- exit_code == kHostDeletedExitCode) {
- // The host was taken off-line remotely. To prevent the host being
- // restarted when the login context changes, try to delete the "enabled"
- // file. Since this requires root privileges, this is only possible when
- // this executable is launched in the "login" context. In the "aqua"
- // context, just exit and try again next time.
- HOST_LOG << "Host deleted - disabling";
- Disable();
- }
- return exit_code;
- }
- // Ignore non-permanent error-code and launch host again. Stop handling
- // signals temporarily in case the executable has to sleep to throttle host
- // relaunches. While throttling, there is no host process to which to
- // forward the signal, so the default behaviour should be restored.
- HOST_LOG << "Host returned non-permanent exit code " << exit_code_string
- << " at " << base::Time::Now();
- }
- return 0;
- }
- bool HostService::Disable() {
- return base::DeleteFile(enabled_file_, false);
- }
- bool HostService::Enable() {
- // Ensure the config file is private whilst being written.
- base::DeleteFile(config_file_, false);
- if (!WriteStdinToConfig()) {
- return false;
- }
- if (!base::SetPosixFilePermissions(config_file_, 0600)) {
- LOG(ERROR) << "Failed to set posix permission";
- return false;
- }
- // Ensure the config is readable by the user registering the host.
- // We don't seem to have API for adding Mac ACL entry for file. This code just
- // uses the chmod binary to do so.
- base::CommandLine chmod_cmd(base::FilePath("/bin/chmod"));
- chmod_cmd.AppendArg("+a");
- chmod_cmd.AppendArg("user:" + GetUsername() + ":allow:read");
- chmod_cmd.AppendArgPath(config_file_);
- std::string output;
- if (!base::GetAppOutputAndError(chmod_cmd, &output)) {
- LOG(ERROR) << "Failed to chmod file " << config_file_;
- return false;
- }
- if (!output.empty()) {
- HOST_LOG << "Message from chmod: " << output;
- }
- if (base::WriteFile(enabled_file_, nullptr, 0) < 0) {
- LOG(ERROR) << "Failed to write enabled file";
- return false;
- }
- return true;
- }
- bool HostService::WriteStdinToConfig() {
- // Reads from stdin and writes it to the config file.
- std::istreambuf_iterator<char> begin(std::cin);
- std::istreambuf_iterator<char> end;
- std::string config(begin, end);
- if (base::WriteFile(config_file_, config.data(), config.size()) !=
- static_cast<int>(config.size())) {
- LOG(ERROR) << "Failed to write config file";
- return false;
- }
- return true;
- }
- void HostService::PrintHostVersion() {
- printf("%s\n", STRINGIZE(VERSION));
- }
- void HostService::PrintPid() {
- // Caller running host service with privilege waits for the PID to continue,
- // so we need to flush it immediately.
- printf("%d\n", base::Process::Current().Pid());
- fflush(stdout);
- }
- int HostService::RunHostFromOldScript() {
- base::CommandLine cmdline(old_host_helper_file_);
- cmdline.AppendSwitch(kSwitchHostRunFromLaunchd);
- base::LaunchOptions options;
- options.disclaim_responsibility = true;
- base::Process process = base::LaunchProcess(cmdline, options);
- if (!process.IsValid()) {
- LOG(ERROR) << "Failed to launch the old host script for unknown reason.";
- return 1;
- }
- g_host_pid = process.Pid();
- int exit_code;
- process.WaitForExit(&exit_code);
- g_host_pid = 0;
- return exit_code;
- }
- bool HostService::CheckPermission() {
- LOG(INFO) << "Checking for host permissions.";
- base::CommandLine cmdLine(native_messaging_host_exe_file_);
- cmdLine.AppendSwitch(kCheckPermissionSwitchName);
- // No need to disclaim responsibility here - the native-messaging host already
- // takes care of that.
- base::Process process = base::LaunchProcess(cmdLine, base::LaunchOptions());
- if (!process.IsValid()) {
- LOG(ERROR) << "Unable to launch native-messaging host process";
- return false;
- }
- int exit_code;
- process.WaitForExit(&exit_code);
- if (exit_code != 0) {
- LOG(ERROR) << "A required permission was not granted.";
- return false;
- }
- LOG(INFO) << "All permissions granted!";
- return true;
- }
- bool HostService::HostIsEnabled() {
- return base::PathExists(enabled_file_);
- }
- } // namespace
- } // namespace remoting
- int main(int argc, char const* argv[]) {
- base::AtExitManager exitManager;
- base::CommandLine::Init(argc, argv);
- remoting::InitHostLogging();
- remoting::HostService service;
- auto* current_cmdline = base::CommandLine::ForCurrentProcess();
- std::string pid = base::NumberToString(base::Process::Current().Pid());
- if (current_cmdline->HasSwitch(remoting::kSwitchDisable)) {
- service.PrintPid();
- if (!service.Disable()) {
- LOG(ERROR) << "Failed to disable";
- return 1;
- }
- } else if (current_cmdline->HasSwitch(remoting::kSwitchEnable)) {
- service.PrintPid();
- if (!service.Enable()) {
- LOG(ERROR) << "Failed to enable";
- return 1;
- }
- } else if (current_cmdline->HasSwitch(remoting::kSwitchSaveConfig)) {
- service.PrintPid();
- if (!service.WriteStdinToConfig()) {
- LOG(ERROR) << "Failed to save config";
- return 1;
- }
- } else if (current_cmdline->HasSwitch(remoting::kSwitchHostVersion)) {
- service.PrintHostVersion();
- } else if (current_cmdline->HasSwitch(remoting::kSwitchHostRunFromLaunchd)) {
- remoting::RegisterSignalHandler();
- HOST_LOG << "Host started for user " << remoting::GetUsername() << " at "
- << base::Time::Now();
- return service.RunHost();
- } else {
- service.PrintPid();
- return 1;
- }
- return 0;
- }