daemon_controller_delegate_mac.mm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. // Copyright 2013 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 "remoting/host/setup/daemon_controller_delegate_mac.h"
  5. #include <launch.h>
  6. #include <sys/types.h>
  7. #include <utility>
  8. #include "base/bind.h"
  9. #include "base/command_line.h"
  10. #include "base/files/file_util.h"
  11. #include "base/files/scoped_file.h"
  12. #include "base/logging.h"
  13. #include "base/mac/authorization_util.h"
  14. #include "base/mac/foundation_util.h"
  15. #include "base/mac/launchd.h"
  16. #include "base/mac/mac_logging.h"
  17. #include "base/mac/scoped_authorizationref.h"
  18. #include "base/mac/scoped_launch_data.h"
  19. #include "base/memory/ptr_util.h"
  20. #include "base/message_loop/message_pump_type.h"
  21. #include "base/posix/eintr_wrapper.h"
  22. #include "base/threading/thread_task_runner_handle.h"
  23. #include "base/values.h"
  24. #include "remoting/base/string_resources.h"
  25. #include "remoting/host/host_config.h"
  26. #include "remoting/host/mac/constants_mac.h"
  27. #include "remoting/host/mac/permission_checker.h"
  28. #include "remoting/host/mac/permission_wizard.h"
  29. #include "remoting/host/resources.h"
  30. #include "third_party/abseil-cpp/absl/types/optional.h"
  31. #include "ui/base/l10n/l10n_util.h"
  32. #include "ui/base/l10n/l10n_util_mac.h"
  33. namespace remoting {
  34. namespace {
  35. constexpr char kIoThreadName[] = "DaemonControllerDelegateMac IO thread";
  36. // Simple RAII class to ensure that waitpid() gets called on a child process.
  37. // Neither std::unique_ptr nor base::ScopedGeneric are well suited, because the
  38. // caller wants to examine the returned status of waitpid() from the scoper's
  39. // deleter function.
  40. class ScopedWaitpid {
  41. public:
  42. // -1 is treated as an invalid PID and waitpit() will not be called in this
  43. // case. Note that -1 is the value returned from
  44. // base::mac::ExecuteWithPrivilegesAndGetPID() when the child PID could not be
  45. // determined.
  46. ScopedWaitpid(pid_t pid) : pid_(pid) {}
  47. ~ScopedWaitpid() { MaybeWait(); }
  48. // Executes the waitpid() and resets the scoper. After this, the caller may
  49. // examine error() and exit_status().
  50. void Reset() { MaybeWait(); }
  51. bool error() { return error_; }
  52. int exit_status() { return exit_status_; }
  53. private:
  54. pid_t pid_ = -1;
  55. // Set if waitpid() failed (returned a value not equal to |pid_|).
  56. bool error_ = false;
  57. // The exit status, if waitpid() succeeded.
  58. int exit_status_ = 0;
  59. void MaybeWait() {
  60. if (pid_ != -1) {
  61. pid_t wait_result = HANDLE_EINTR(waitpid(pid_, &exit_status_, 0));
  62. if (wait_result != pid_) {
  63. PLOG(ERROR) << "waitpid failed";
  64. error_ = true;
  65. }
  66. pid_ = -1;
  67. }
  68. }
  69. };
  70. // Runs the helper script as root with the given command-line argument.
  71. // If |input_data| is non-empty, it will be piped to the script via standard
  72. // input. Returns true if successful.
  73. bool RunHelperAsRoot(const std::string& command,
  74. const std::string& input_data) {
  75. NSString* prompt = l10n_util::GetNSStringFWithFixup(
  76. IDS_HOST_AUTHENTICATION_PROMPT,
  77. l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
  78. base::mac::ScopedAuthorizationRef authorization(
  79. base::mac::AuthorizationCreateToRunAsRoot(base::mac::NSToCFCast(prompt)));
  80. if (!authorization.get()) {
  81. LOG(ERROR) << "Failed to obtain authorizationRef";
  82. return false;
  83. }
  84. // TODO(lambroslambrou): Replace the deprecated ExecuteWithPrivileges
  85. // call with a launchd-based helper tool, which is more secure.
  86. // http://crbug.com/120903
  87. const char* arguments[] = {command.c_str(), nullptr};
  88. FILE* pipe = nullptr;
  89. pid_t pid;
  90. OSStatus status = base::mac::ExecuteWithPrivilegesAndGetPID(
  91. authorization.get(), remoting::kHostServiceBinaryPath,
  92. kAuthorizationFlagDefaults, arguments, &pipe, &pid);
  93. if (status != errAuthorizationSuccess) {
  94. LOG(ERROR) << "AuthorizationExecuteWithPrivileges: "
  95. << logging::DescriptionFromOSStatus(status)
  96. << static_cast<int>(status);
  97. return false;
  98. }
  99. // It is safer to order the scopers this way round, to ensure that the pipe is
  100. // closed before calling waitpid(). In the case of sending data to the child,
  101. // the child reads until EOF on its stdin, so calling waitpid() first would
  102. // result in deadlock in this situation.
  103. ScopedWaitpid scoped_pid(pid);
  104. base::ScopedFILE scoped_pipe(pipe);
  105. if (pid == -1) {
  106. LOG(ERROR) << "Failed to get child PID";
  107. return false;
  108. }
  109. if (!pipe) {
  110. LOG(ERROR) << "Unexpected nullptr pipe";
  111. return false;
  112. }
  113. if (!input_data.empty()) {
  114. size_t bytes_written =
  115. fwrite(input_data.data(), sizeof(char), input_data.size(), pipe);
  116. // According to the fwrite manpage, a partial count is returned only if a
  117. // write error has occurred.
  118. if (bytes_written != input_data.size()) {
  119. LOG(ERROR) << "Failed to write data to child process";
  120. return false;
  121. }
  122. // Flush any buffers here to avoid doing it in fclose(), because the
  123. // ScopedFILE does not allow checking for errors from fclose().
  124. if (fflush(pipe) != 0) {
  125. PLOG(ERROR) << "Failed to flush data to child process";
  126. return false;
  127. }
  128. }
  129. // Close the pipe (to send EOF) and wait for the child process to run.
  130. scoped_pipe.reset();
  131. scoped_pid.Reset();
  132. if (scoped_pid.error()) {
  133. PLOG(ERROR) << "waitpid failed";
  134. return false;
  135. }
  136. const int exit_status = scoped_pid.exit_status();
  137. if (WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == 0) {
  138. return true;
  139. }
  140. LOG(ERROR) << remoting::kHostServiceBinaryPath << " failed with exit status "
  141. << exit_status;
  142. return false;
  143. }
  144. void ElevateAndSetConfig(base::Value::Dict config,
  145. DaemonController::CompletionCallback done) {
  146. // Find out if the host service is running.
  147. pid_t job_pid = base::mac::PIDForJob(remoting::kServiceName);
  148. bool service_running = (job_pid > 0);
  149. const char* command = service_running ? "--save-config" : "--enable";
  150. std::string input_data = HostConfigToJson(base::Value(std::move(config)));
  151. if (!RunHelperAsRoot(command, input_data)) {
  152. LOG(ERROR) << "Failed to run the helper tool.";
  153. std::move(done).Run(DaemonController::RESULT_FAILED);
  154. return;
  155. }
  156. if (!service_running) {
  157. base::mac::ScopedLaunchData response(
  158. base::mac::MessageForJob(remoting::kServiceName, LAUNCH_KEY_STARTJOB));
  159. if (!response.is_valid()) {
  160. LOG(ERROR) << "Failed to send STARTJOB to launchd";
  161. std::move(done).Run(DaemonController::RESULT_FAILED);
  162. return;
  163. }
  164. }
  165. std::move(done).Run(DaemonController::RESULT_OK);
  166. }
  167. void ElevateAndStopHost(DaemonController::CompletionCallback done) {
  168. if (!RunHelperAsRoot("--disable", std::string())) {
  169. LOG(ERROR) << "Failed to run the helper tool.";
  170. std::move(done).Run(DaemonController::RESULT_FAILED);
  171. return;
  172. }
  173. // Stop the launchd job. This cannot easily be done by the helper tool,
  174. // since the launchd job runs in the current user's context.
  175. base::mac::ScopedLaunchData response(
  176. base::mac::MessageForJob(remoting::kServiceName, LAUNCH_KEY_STOPJOB));
  177. if (!response.is_valid()) {
  178. LOG(ERROR) << "Failed to send STOPJOB to launchd";
  179. std::move(done).Run(DaemonController::RESULT_FAILED);
  180. return;
  181. }
  182. std::move(done).Run(DaemonController::RESULT_OK);
  183. }
  184. } // namespace
  185. DaemonControllerDelegateMac::DaemonControllerDelegateMac()
  186. : io_thread_(kIoThreadName) {
  187. LoadResources(std::string());
  188. io_task_runner_ = io_thread_.StartWithType(base::MessagePumpType::IO);
  189. }
  190. DaemonControllerDelegateMac::~DaemonControllerDelegateMac() {
  191. UnloadResources();
  192. }
  193. DaemonController::State DaemonControllerDelegateMac::GetState() {
  194. pid_t job_pid = base::mac::PIDForJob(kServiceName);
  195. if (job_pid < 0) {
  196. return DaemonController::STATE_UNKNOWN;
  197. } else if (job_pid == 0) {
  198. // Service is stopped, or a start attempt failed.
  199. return DaemonController::STATE_STOPPED;
  200. } else {
  201. return DaemonController::STATE_STARTED;
  202. }
  203. }
  204. absl::optional<base::Value::Dict> DaemonControllerDelegateMac::GetConfig() {
  205. base::FilePath config_path(kHostConfigFilePath);
  206. absl::optional<base::Value> host_config(HostConfigFromJsonFile(config_path));
  207. if (!host_config.has_value())
  208. return absl::nullopt;
  209. base::Value::Dict config;
  210. std::string* value = host_config->FindStringKey(kHostIdConfigPath);
  211. if (value) {
  212. config.Set(kHostIdConfigPath, *value);
  213. }
  214. value = host_config->FindStringKey(kXmppLoginConfigPath);
  215. if (value) {
  216. config.Set(kXmppLoginConfigPath, *value);
  217. }
  218. return config;
  219. }
  220. void DaemonControllerDelegateMac::CheckPermission(
  221. bool it2me,
  222. DaemonController::BoolCallback callback) {
  223. auto checker = std::make_unique<mac::PermissionChecker>(
  224. it2me ? mac::HostMode::IT2ME : mac::HostMode::ME2ME, io_task_runner_);
  225. permission_wizard_ =
  226. std::make_unique<mac::PermissionWizard>(std::move(checker));
  227. permission_wizard_->SetCompletionCallback(std::move(callback));
  228. permission_wizard_->Start(base::ThreadTaskRunnerHandle::Get());
  229. }
  230. void DaemonControllerDelegateMac::SetConfigAndStart(
  231. base::Value::Dict config,
  232. bool consent,
  233. DaemonController::CompletionCallback done) {
  234. config.Set(kUsageStatsConsentConfigPath, consent);
  235. ElevateAndSetConfig(std::move(config), std::move(done));
  236. }
  237. void DaemonControllerDelegateMac::UpdateConfig(
  238. base::Value::Dict config,
  239. DaemonController::CompletionCallback done) {
  240. base::FilePath config_file_path(kHostConfigFilePath);
  241. absl::optional<base::Value> host_config(
  242. HostConfigFromJsonFile(config_file_path));
  243. if (!host_config.has_value() || !host_config->is_dict()) {
  244. std::move(done).Run(DaemonController::RESULT_FAILED);
  245. return;
  246. }
  247. base::Value::Dict& host_config_dict = host_config->GetDict();
  248. host_config_dict.Merge(std::move(config));
  249. ElevateAndSetConfig(std::move(host_config_dict), std::move(done));
  250. }
  251. void DaemonControllerDelegateMac::Stop(
  252. DaemonController::CompletionCallback done) {
  253. ElevateAndStopHost(std::move(done));
  254. }
  255. DaemonController::UsageStatsConsent
  256. DaemonControllerDelegateMac::GetUsageStatsConsent() {
  257. DaemonController::UsageStatsConsent consent;
  258. consent.supported = true;
  259. consent.allowed = true;
  260. // set_by_policy is not yet supported.
  261. consent.set_by_policy = false;
  262. base::FilePath config_file_path(kHostConfigFilePath);
  263. absl::optional<base::Value> host_config(
  264. HostConfigFromJsonFile(config_file_path));
  265. if (host_config.has_value()) {
  266. absl::optional<bool> host_config_value =
  267. host_config->FindBoolKey(kUsageStatsConsentConfigPath);
  268. if (host_config_value.has_value()) {
  269. consent.allowed = host_config_value.value();
  270. }
  271. }
  272. return consent;
  273. }
  274. scoped_refptr<DaemonController> DaemonController::Create() {
  275. return new DaemonController(
  276. base::WrapUnique(new DaemonControllerDelegateMac()));
  277. }
  278. } // namespace remoting