PageRenderTime 25ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/remoting/host/mac/host_service_main.cc

http://github.com/chromium/chromium
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
  1. // Copyright 2019 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include <signal.h>
  5. #include <unistd.h>
  6. #include <iostream>
  7. #include <string>
  8. #include "base/at_exit.h"
  9. #include "base/bind.h"
  10. #include "base/command_line.h"
  11. #include "base/files/file_path.h"
  12. #include "base/files/file_util.h"
  13. #include "base/logging.h"
  14. #include "base/mac/mac_util.h"
  15. #include "base/no_destructor.h"
  16. #include "base/path_service.h"
  17. #include "base/process/launch.h"
  18. #include "base/process/process.h"
  19. #include "base/strings/string_number_conversions.h"
  20. #include "base/strings/stringize_macros.h"
  21. #include "base/threading/platform_thread.h"
  22. #include "base/time/time.h"
  23. #include "remoting/base/logging.h"
  24. #include "remoting/host/host_exit_codes.h"
  25. #include "remoting/host/logging.h"
  26. #include "remoting/host/mac/constants_mac.h"
  27. #include "remoting/host/switches.h"
  28. #include "remoting/host/username.h"
  29. #include "remoting/host/version.h"
  30. namespace remoting {
  31. namespace {
  32. constexpr char kSwitchDisable[] = "disable";
  33. constexpr char kSwitchEnable[] = "enable";
  34. constexpr char kSwitchSaveConfig[] = "save-config";
  35. constexpr char kSwitchHostVersion[] = "host-version";
  36. constexpr char kSwitchHostRunFromLaunchd[] = "run-from-launchd";
  37. constexpr char kHostExeFileName[] = "remoting_me2me_host";
  38. constexpr char kNativeMessagingHostPath[] =
  39. "Contents/MacOS/native_messaging_host";
  40. // The exit code returned by 'wait' when a process is terminated by SIGTERM.
  41. constexpr int kSigtermExitCode = 128 + SIGTERM;
  42. // Constants controlling the host process relaunch throttling.
  43. constexpr base::TimeDelta kMinimumRelaunchInterval =
  44. base::TimeDelta::FromMinutes(1);
  45. constexpr int kMaximumHostFailures = 10;
  46. // Exit code 126 is defined by Posix to mean "Command found, but not
  47. // executable", and is returned if the process cannot be launched due to
  48. // parental control.
  49. constexpr int kPermissionDeniedParentalControl = 126;
  50. // This executable works as a proxy between launchd and the host. Signals of
  51. // interest to the host must be forwarded.
  52. constexpr int kSignalList[] = {
  53. SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGEMT,
  54. SIGFPE, SIGBUS, SIGSEGV, SIGSYS, SIGPIPE, SIGALRM, SIGTERM,
  55. SIGURG, SIGTSTP, SIGCONT, SIGTTIN, SIGTTOU, SIGIO, SIGXCPU,
  56. SIGXFSZ, SIGVTALRM, SIGPROF, SIGWINCH, SIGINFO, SIGUSR1, SIGUSR2};
  57. // Current host PID used to forward signals. 0 if host is not running.
  58. static base::ProcessId g_host_pid = 0;
  59. void HandleSignal(int signum) {
  60. if (g_host_pid) {
  61. // All other signals are forwarded to host then ignored except SIGTERM.
  62. // launchd sends SIGTERM when service is being stopped so both the host and
  63. // the host service need to terminate.
  64. HOST_LOG << "Forwarding signal " << signum << " to host process "
  65. << g_host_pid;
  66. kill(g_host_pid, signum);
  67. if (signum == SIGTERM) {
  68. HOST_LOG << "Host service is terminating upon reception of SIGTERM";
  69. exit(kSigtermExitCode);
  70. }
  71. } else {
  72. HOST_LOG << "Signal " << signum
  73. << " will not be forwarded since host is not running.";
  74. exit(128 + signum);
  75. }
  76. }
  77. void RegisterSignalHandler() {
  78. struct sigaction action = {};
  79. sigfillset(&action.sa_mask);
  80. action.sa_flags = 0;
  81. action.sa_handler = &HandleSignal;
  82. for (int signum : kSignalList) {
  83. if (sigaction(signum, &action, nullptr) == -1) {
  84. PLOG(DFATAL) << "Failed to register signal handler for signal " << signum;
  85. }
  86. }
  87. }
  88. class HostService {
  89. public:
  90. HostService();
  91. ~HostService();
  92. bool Disable();
  93. bool Enable();
  94. bool WriteStdinToConfig();
  95. int RunHost();
  96. void PrintHostVersion();
  97. void PrintPid();
  98. private:
  99. int RunHostFromOldScript();
  100. // Runs the permission-checker built into the native-messaging host. Returns
  101. // true if all permissions were granted (or no permission check is needed for
  102. // the version of MacOS).
  103. bool CheckPermission();
  104. bool HostIsEnabled();
  105. base::FilePath old_host_helper_file_;
  106. base::FilePath enabled_file_;
  107. base::FilePath config_file_;
  108. base::FilePath host_exe_file_;
  109. base::FilePath native_messaging_host_exe_file_;
  110. };
  111. HostService::HostService() {
  112. old_host_helper_file_ = base::FilePath(kOldHostHelperScriptPath);
  113. enabled_file_ = base::FilePath(kHostEnabledPath);
  114. config_file_ = base::FilePath(kHostConfigFilePath);
  115. base::FilePath host_service_dir;
  116. base::PathService::Get(base::DIR_EXE, &host_service_dir);
  117. host_exe_file_ = host_service_dir.AppendASCII(kHostExeFileName);
  118. native_messaging_host_exe_file_ =
  119. host_service_dir.AppendASCII(NATIVE_MESSAGING_HOST_BUNDLE_NAME)
  120. .AppendASCII(kNativeMessagingHostPath);
  121. }
  122. HostService::~HostService() = default;
  123. int HostService::RunHost() {
  124. // Mojave users updating from an older host likely have already granted a11y
  125. // permission to the old script. In this case we always delegate RunHost to
  126. // the old script so that they don't need to grant permission to a new app.
  127. if (base::mac::IsOS10_14() && base::PathExists(old_host_helper_file_)) {
  128. HOST_LOG << "RunHost will be delegated to the old host script.";
  129. return RunHostFromOldScript();
  130. }
  131. if (geteuid() != 0 && HostIsEnabled()) {
  132. // Only check for non-root users, as the permission wizard is not actionable
  133. // at the login screen. Also, permission is only needed when host is
  134. // enabled - the launchd service should exit immediately if the host is
  135. // disabled.
  136. if (!CheckPermission()) {
  137. return 1;
  138. }
  139. }
  140. int host_failure_count = 0;
  141. base::TimeTicks host_start_time;
  142. while (true) {
  143. if (!HostIsEnabled()) {
  144. HOST_LOG << "Daemon is disabled.";
  145. return 0;
  146. }
  147. // If this is not the first time the host has run, make sure we don't
  148. // relaunch it too soon.
  149. if (!host_start_time.is_null()) {
  150. base::TimeDelta host_lifetime = base::TimeTicks::Now() - host_start_time;
  151. HOST_LOG << "Host ran for " << host_lifetime;
  152. if (host_lifetime < kMinimumRelaunchInterval) {
  153. // If the host didn't run for very long, assume it crashed. Relaunch
  154. // only after a suitable delay and increase the failure count.
  155. host_failure_count++;
  156. LOG(WARNING) << "Host failure count " << host_failure_count << "/"
  157. << kMaximumHostFailures;
  158. if (host_failure_count >= kMaximumHostFailures) {
  159. LOG(ERROR) << "Too many host failures. Giving up.";
  160. return 1;
  161. }
  162. base::TimeDelta relaunch_in = kMinimumRelaunchInterval - host_lifetime;
  163. HOST_LOG << "Relaunching in " << relaunch_in;
  164. base::PlatformThread::Sleep(relaunch_in);
  165. } else {
  166. // If the host ran for long enough, reset the crash counter.
  167. host_failure_count = 0;
  168. }
  169. }
  170. host_start_time = base::TimeTicks::Now();
  171. base::CommandLine cmdline(host_exe_file_);
  172. cmdline.AppendSwitchPath("host-config", config_file_);
  173. std::string ssh_auth_sockname =
  174. "/tmp/chromoting." + GetUsername() + ".ssh_auth_sock";
  175. cmdline.AppendSwitchASCII("ssh-auth-sockname", ssh_auth_sockname);
  176. base::Process process = base::LaunchProcess(cmdline, base::LaunchOptions());
  177. if (!process.IsValid()) {
  178. LOG(ERROR) << "Failed to launch host process for unknown reason.";
  179. return 1;
  180. }
  181. g_host_pid = process.Pid();
  182. int exit_code;
  183. process.WaitForExit(&exit_code);
  184. g_host_pid = 0;
  185. const char* exit_code_string_ptr = ExitCodeToStringUnchecked(exit_code);
  186. std::string exit_code_string =
  187. exit_code_string_ptr ? (std::string(exit_code_string_ptr) + " (" +
  188. base::NumberToString(exit_code) + ")")
  189. : base::NumberToString(exit_code);
  190. if (exit_code == 0 || exit_code == kSigtermExitCode ||
  191. exit_code == kPermissionDeniedParentalControl ||
  192. (exit_code >= kMinPermanentErrorExitCode &&
  193. exit_code <= kMaxPermanentErrorExitCode)) {
  194. HOST_LOG << "Host returned permanent exit code " << exit_code_string
  195. << " at " << base::Time::Now();
  196. if (exit_code == kInvalidHostIdExitCode ||
  197. exit_code == kHostDeletedExitCode) {
  198. // The host was taken off-line remotely. To prevent the host being
  199. // restarted when the login context changes, try to delete the "enabled"
  200. // file. Since this requires root privileges, this is only possible when
  201. // this executable is launched in the "login" context. In the "aqua"
  202. // context, just exit and try again next time.
  203. HOST_LOG << "Host deleted - disabling";
  204. Disable();
  205. }
  206. return exit_code;
  207. }
  208. // Ignore non-permanent error-code and launch host again. Stop handling
  209. // signals temporarily in case the executable has to sleep to throttle host
  210. // relaunches. While throttling, there is no host process to which to
  211. // forward the signal, so the default behaviour should be restored.
  212. HOST_LOG << "Host returned non-permanent exit code " << exit_code_string
  213. << " at " << base::Time::Now();
  214. }
  215. return 0;
  216. }
  217. bool HostService::Disable() {
  218. return base::DeleteFile(enabled_file_, false);
  219. }
  220. bool HostService::Enable() {
  221. // Ensure the config file is private whilst being written.
  222. base::DeleteFile(config_file_, false);
  223. if (!WriteStdinToConfig()) {
  224. return false;
  225. }
  226. if (!base::SetPosixFilePermissions(config_file_, 0600)) {
  227. LOG(ERROR) << "Failed to set posix permission";
  228. return false;
  229. }
  230. // Ensure the config is readable by the user registering the host.
  231. // We don't seem to have API for adding Mac ACL entry for file. This code just
  232. // uses the chmod binary to do so.
  233. base::CommandLine chmod_cmd(base::FilePath("/bin/chmod"));
  234. chmod_cmd.AppendArg("+a");
  235. chmod_cmd.AppendArg("user:" + GetUsername() + ":allow:read");
  236. chmod_cmd.AppendArgPath(config_file_);
  237. std::string output;
  238. if (!base::GetAppOutputAndError(chmod_cmd, &output)) {
  239. LOG(ERROR) << "Failed to chmod file " << config_file_;
  240. return false;
  241. }
  242. if (!output.empty()) {
  243. HOST_LOG << "Message from chmod: " << output;
  244. }
  245. if (base::WriteFile(enabled_file_, nullptr, 0) < 0) {
  246. LOG(ERROR) << "Failed to write enabled file";
  247. return false;
  248. }
  249. return true;
  250. }
  251. bool HostService::WriteStdinToConfig() {
  252. // Reads from stdin and writes it to the config file.
  253. std::istreambuf_iterator<char> begin(std::cin);
  254. std::istreambuf_iterator<char> end;
  255. std::string config(begin, end);
  256. if (base::WriteFile(config_file_, config.data(), config.size()) !=
  257. static_cast<int>(config.size())) {
  258. LOG(ERROR) << "Failed to write config file";
  259. return false;
  260. }
  261. return true;
  262. }
  263. void HostService::PrintHostVersion() {
  264. printf("%s\n", STRINGIZE(VERSION));
  265. }
  266. void HostService::PrintPid() {
  267. // Caller running host service with privilege waits for the PID to continue,
  268. // so we need to flush it immediately.
  269. printf("%d\n", base::Process::Current().Pid());
  270. fflush(stdout);
  271. }
  272. int HostService::RunHostFromOldScript() {
  273. base::CommandLine cmdline(old_host_helper_file_);
  274. cmdline.AppendSwitch(kSwitchHostRunFromLaunchd);
  275. base::LaunchOptions options;
  276. options.disclaim_responsibility = true;
  277. base::Process process = base::LaunchProcess(cmdline, options);
  278. if (!process.IsValid()) {
  279. LOG(ERROR) << "Failed to launch the old host script for unknown reason.";
  280. return 1;
  281. }
  282. g_host_pid = process.Pid();
  283. int exit_code;
  284. process.WaitForExit(&exit_code);
  285. g_host_pid = 0;
  286. return exit_code;
  287. }
  288. bool HostService::CheckPermission() {
  289. LOG(INFO) << "Checking for host permissions.";
  290. base::CommandLine cmdLine(native_messaging_host_exe_file_);
  291. cmdLine.AppendSwitch(kCheckPermissionSwitchName);
  292. // No need to disclaim responsibility here - the native-messaging host already
  293. // takes care of that.
  294. base::Process process = base::LaunchProcess(cmdLine, base::LaunchOptions());
  295. if (!process.IsValid()) {
  296. LOG(ERROR) << "Unable to launch native-messaging host process";
  297. return false;
  298. }
  299. int exit_code;
  300. process.WaitForExit(&exit_code);
  301. if (exit_code != 0) {
  302. LOG(ERROR) << "A required permission was not granted.";
  303. return false;
  304. }
  305. LOG(INFO) << "All permissions granted!";
  306. return true;
  307. }
  308. bool HostService::HostIsEnabled() {
  309. return base::PathExists(enabled_file_);
  310. }
  311. } // namespace
  312. } // namespace remoting
  313. int main(int argc, char const* argv[]) {
  314. base::AtExitManager exitManager;
  315. base::CommandLine::Init(argc, argv);
  316. remoting::InitHostLogging();
  317. remoting::HostService service;
  318. auto* current_cmdline = base::CommandLine::ForCurrentProcess();
  319. std::string pid = base::NumberToString(base::Process::Current().Pid());
  320. if (current_cmdline->HasSwitch(remoting::kSwitchDisable)) {
  321. service.PrintPid();
  322. if (!service.Disable()) {
  323. LOG(ERROR) << "Failed to disable";
  324. return 1;
  325. }
  326. } else if (current_cmdline->HasSwitch(remoting::kSwitchEnable)) {
  327. service.PrintPid();
  328. if (!service.Enable()) {
  329. LOG(ERROR) << "Failed to enable";
  330. return 1;
  331. }
  332. } else if (current_cmdline->HasSwitch(remoting::kSwitchSaveConfig)) {
  333. service.PrintPid();
  334. if (!service.WriteStdinToConfig()) {
  335. LOG(ERROR) << "Failed to save config";
  336. return 1;
  337. }
  338. } else if (current_cmdline->HasSwitch(remoting::kSwitchHostVersion)) {
  339. service.PrintHostVersion();
  340. } else if (current_cmdline->HasSwitch(remoting::kSwitchHostRunFromLaunchd)) {
  341. remoting::RegisterSignalHandler();
  342. HOST_LOG << "Host started for user " << remoting::GetUsername() << " at "
  343. << base::Time::Now();
  344. return service.RunHost();
  345. } else {
  346. service.PrintPid();
  347. return 1;
  348. }
  349. return 0;
  350. }