Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(201)

Side by Side Diff: remoting/host/linux/remoting_user_session.cc

Issue 2939263003: Add support for CRD user-session to operate setuid (Closed)
Patch Set: Address review feedback Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « remoting/host/installer/linux/debian/chrome-remote-desktop.init ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 // 4 //
5 // This file implements a wrapper to run the virtual me2me session within a 5 // This file implements a wrapper to run the virtual me2me session within a
6 // proper PAM session. It will generally be run as root and drop privileges to 6 // proper PAM session. It will generally be run as root and drop privileges to
7 // the specified user before running the me2me session script. 7 // the specified user before running the me2me session script.
8 8
9 // Usage: user-session start [--foreground] [user]
10 //
11 // Options:
12 // --foreground - Don't daemonize.
13 // user - Create a session for the specified user. Required when
14 // running as root, not allowed when running as a normal user.
15
9 #include <sys/types.h> 16 #include <sys/types.h>
10 #include <sys/stat.h> 17 #include <sys/stat.h>
11 #include <sys/wait.h> 18 #include <sys/wait.h>
12 #include <fcntl.h> 19 #include <fcntl.h>
13 #include <grp.h> 20 #include <grp.h>
21 #include <limits.h>
14 #include <pwd.h> 22 #include <pwd.h>
15 #include <unistd.h> 23 #include <unistd.h>
16 24
17 #include <cerrno> 25 #include <cerrno>
18 #include <cstdio> 26 #include <cstdio>
19 #include <cstdlib> 27 #include <cstdlib>
20 #include <cstring> 28 #include <cstring>
21 #include <ctime> 29 #include <ctime>
22 30
23 #include <map> 31 #include <map>
24 #include <memory> 32 #include <memory>
25 #include <string> 33 #include <string>
26 #include <tuple> 34 #include <tuple>
27 #include <utility> 35 #include <utility>
28 #include <vector> 36 #include <vector>
29 37
30 #include <security/pam_appl.h> 38 #include <security/pam_appl.h>
31 39
32 #include "base/command_line.h"
33 #include "base/environment.h" 40 #include "base/environment.h"
34 #include "base/files/file_path.h" 41 #include "base/files/file_path.h"
42 #include "base/files/file_util.h"
35 #include "base/logging.h" 43 #include "base/logging.h"
36 #include "base/macros.h" 44 #include "base/macros.h"
37 #include "base/optional.h" 45 #include "base/optional.h"
38 #include "base/process/launch.h" 46 #include "base/process/launch.h"
39 #include "base/strings/string_piece.h" 47 #include "base/strings/string_piece.h"
40 48
41 namespace { 49 namespace {
42 50
43 const char kPamName[] = "chrome-remote-desktop"; 51 const char kPamName[] = "chrome-remote-desktop";
44 52 const char kScriptName[] = "chrome-remote-desktop";
45 const char kHelpSwitchName[] = "help"; 53 const char kForegroundFlag[] = "--foreground";
46 const char kQuestionSwitchName[] = "?";
47 const char kUserSwitchName[] = "user";
48 const char kScriptSwitchName[] = "me2me-script";
49 const char kForegroundSwitchName[] = "foreground";
50 54
51 // This template will be formatted by strftime and then used by mkstemp 55 // This template will be formatted by strftime and then used by mkstemp
52 const char kLogFileTemplate[] = 56 const char kLogFileTemplate[] =
53 "/tmp/chrome_remote_desktop_%Y%m%d_%H%M%S_XXXXXX"; 57 "/tmp/chrome_remote_desktop_%Y%m%d_%H%M%S_XXXXXX";
54 58
55 const char kUsageMessage[] = 59 const char kUsageMessage[] =
56 "Usage: %s [options]\n" 60 "This program is not intended to be run by end users. To configure Chrome\n"
57 "\n" 61 "Remote Desktop, please install the app from the Chrome Web Store:\n"
58 "Options:\n" 62 "https://chrome.google.com/remotedesktop\n";
59 " --help, -? - Print this message.\n"
60 " --user=<user> - Create session as the specified user. "
61 "(Must run as root.)\n"
62 " --me2me-script=<script> - Location of the me2me python script "
63 "(required)\n"
64 " --foreground - Don't daemonize.\n";
65 63
66 void PrintUsage(const base::FilePath& program_name) { 64 void PrintUsage() {
67 std::printf(kUsageMessage, program_name.MaybeAsASCII().c_str()); 65 std::fputs(kUsageMessage, stderr);
68 } 66 }
69 67
70 // Shell-escapes a single argument in a way that is compatible with various 68 // Shell-escapes a single argument in a way that is compatible with various
71 // different shells. Returns nullopt when argument contains a newline, which 69 // different shells. Returns nullopt when argument contains a newline, which
72 // can't be represented in a cross-shell fashion. 70 // can't be represented in a cross-shell fashion.
73 base::Optional<std::string> ShellEscapeArgument( 71 base::Optional<std::string> ShellEscapeArgument(
74 const base::StringPiece argument) { 72 const base::StringPiece argument) {
75 std::string result; 73 std::string result;
76 for (char character : argument) { 74 for (char character : argument) {
77 // csh in particular doesn't provide a good way to handle this 75 // csh in particular doesn't provide a good way to handle this
(...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after
236 } 234 }
237 } 235 }
238 236
239 private: 237 private:
240 pam_handle_t* pam_handle_ = nullptr; 238 pam_handle_t* pam_handle_ = nullptr;
241 int last_return_code_ = PAM_SUCCESS; 239 int last_return_code_ = PAM_SUCCESS;
242 240
243 DISALLOW_COPY_AND_ASSIGN(PamHandle); 241 DISALLOW_COPY_AND_ASSIGN(PamHandle);
244 }; 242 };
245 243
244 std::string FindScriptPath() {
245 base::FilePath path;
246 bool result = base::ReadSymbolicLink(base::FilePath("/proc/self/exe"), &path);
247 PCHECK(result) << "Failed to determine binary location";
rkjnsn 2017/07/21 01:55:35 Using PCHECK here assumes the failure stems from t
Jamie 2017/07/24 20:43:10 I think this is fine.
248 CHECK(path.IsAbsolute()) << "Retrieved binary location not absolute";
249
250 return path.DirName().Append(kScriptName).value();
251 }
252
253 // Execs the me2me script.
254 // This function is called after forking and dropping privileges. It never
255 // returns.
256 void ExecMe2MeScript(base::EnvironmentMap environment,
257 const struct passwd* pwinfo) {
258 // By convention, a login shell is signified by preceeding the shell name in
259 // argv[0] with a '-'.
260 std::string shell_name =
261 '-' + base::FilePath(pwinfo->pw_shell).BaseName().value();
262
263 base::Optional<std::string> escaped_script_path =
264 ShellEscapeArgument(FindScriptPath());
265 CHECK(escaped_script_path) << "Could not escape script path";
266
267 std::string shell_arg =
268 *escaped_script_path + " --start --foreground --keep-parent-env";
269
270 environment["USER"] = pwinfo->pw_name;
271 environment["LOGNAME"] = pwinfo->pw_name;
272 environment["HOME"] = pwinfo->pw_dir;
273 environment["SHELL"] = pwinfo->pw_shell;
274 if (!environment.count("PATH")) {
275 environment["PATH"] = "/bin:/usr/bin";
276 }
277
278 std::vector<std::string> env_strings;
279 for (const auto& env_var : environment) {
280 env_strings.emplace_back(env_var.first + "=" + env_var.second);
281 }
282
283 std::vector<const char*> arg_ptrs = {shell_name.c_str(), "-c",
284 shell_arg.c_str(), nullptr};
285 std::vector<const char*> env_ptrs;
286 env_ptrs.reserve(env_strings.size() + 1);
287 for (const auto& env_string : env_strings) {
288 env_ptrs.push_back(env_string.c_str());
289 }
290 env_ptrs.push_back(nullptr);
291
292 execve(pwinfo->pw_shell, const_cast<char* const*>(arg_ptrs.data()),
293 const_cast<char* const*>(env_ptrs.data()));
294 PLOG(FATAL) << "Failed to exec login shell " << pwinfo->pw_shell;
295 }
296
246 // Runs the me2me script in a PAM session. Exits the program on failure. 297 // Runs the me2me script in a PAM session. Exits the program on failure.
247 // If chown_log is true, the owner and group of the file associated with stdout 298 // If chown_log is true, the owner and group of the file associated with stdout
248 // will be changed to the target user. 299 // will be changed to the target user. If match_uid is specified, this function
249 void ExecuteSession(std::string user, base::StringPiece script_path, 300 // will fail if the final user id does not match the one provided.
250 bool chown_log) { 301 void ExecuteSession(std::string user,
251 ////////////////////////////////////////////////////////////////////////////// 302 bool chown_log,
252 // Set up the PAM session 303 base::Optional<uid_t> match_uid) {
253 //////////////////////////////////////////////////////////////////////////////
254
255 PamHandle pam_handle(kPamName, user.c_str(), &kPamConversation); 304 PamHandle pam_handle(kPamName, user.c_str(), &kPamConversation);
256 CHECK(pam_handle.IsInitialized()) << "Failed to initialize PAM"; 305 CHECK(pam_handle.IsInitialized()) << "Failed to initialize PAM";
257 306
258 // Make sure the account is valid and enabled. 307 // Make sure the account is valid and enabled.
259 pam_handle.CheckReturnCode(pam_handle.AccountManagement(0), "Account check"); 308 pam_handle.CheckReturnCode(pam_handle.AccountManagement(0), "Account check");
260 309
261 // PAM may remap the user at any stage. 310 // PAM may remap the user at any stage.
262 user = pam_handle.GetUser().value_or(std::move(user)); 311 user = pam_handle.GetUser().value_or(std::move(user));
263 312
264 // setcred explicitly does not handle user id or group membership, and 313 // setcred explicitly does not handle user id or group membership, and
(...skipping 13 matching lines...) Expand all
278 // as done here, but it may be worth noting that `login` calls open_session 327 // as done here, but it may be worth noting that `login` calls open_session
279 // first. 328 // first.
280 pam_handle.CheckReturnCode(pam_handle.SetCredentials(PAM_ESTABLISH_CRED), 329 pam_handle.CheckReturnCode(pam_handle.SetCredentials(PAM_ESTABLISH_CRED),
281 "Set credentials"); 330 "Set credentials");
282 331
283 pam_handle.CheckReturnCode(pam_handle.OpenSession(0), "Open session"); 332 pam_handle.CheckReturnCode(pam_handle.OpenSession(0), "Open session");
284 333
285 // The above may have remapped the user. 334 // The above may have remapped the user.
286 user = pam_handle.GetUser().value_or(std::move(user)); 335 user = pam_handle.GetUser().value_or(std::move(user));
287 336
288 base::Optional<base::EnvironmentMap> pam_environment =
289 pam_handle.GetEnvironment();
290 CHECK(pam_environment) << "Failed to get environment from PAM";
291
292 //////////////////////////////////////////////////////////////////////////////
293 // Prepare to execute remoting session process
294 //////////////////////////////////////////////////////////////////////////////
295
296 // Callback to be run in child process after fork and before exec.
297 // chdir is called manually instead of using LaunchOptions.current_directory
298 // because it should take place after setuid. (This both makes sure the user
299 // has the proper permissions and also apparently avoids some obscure errors
300 // that can occur when accessing some network filesystems as the wrong user.)
301 class PreExecDelegate : public base::LaunchOptions::PreExecDelegate {
302 public:
303 void RunAsyncSafe() override {
304 // Use RAW_CHECK to avoid allocating post-fork.
305 RAW_CHECK(setuid(pwinfo_->pw_uid) == 0);
306 RAW_CHECK(chdir(pwinfo_->pw_dir) == 0);
307 }
308
309 PreExecDelegate(struct passwd* pwinfo) : pwinfo_(pwinfo) {}
310
311 private:
312 struct passwd* pwinfo_;
313 };
314
315 // Fetch pwinfo again, as it may have been invalidated or the user name might 337 // Fetch pwinfo again, as it may have been invalidated or the user name might
316 // have been remapped. 338 // have been remapped.
317 pwinfo = getpwnam(user.c_str()); 339 pwinfo = getpwnam(user.c_str());
318 PCHECK(pwinfo != nullptr) << "getpwnam failed"; 340 PCHECK(pwinfo != nullptr) << "getpwnam failed";
319 341
342 if (match_uid && pwinfo->pw_uid != *match_uid) {
343 LOG(FATAL) << "PAM remapped username to one with a different user ID.";
344 }
345
320 if (chown_log) { 346 if (chown_log) {
321 int result = fchown(STDOUT_FILENO, pwinfo->pw_uid, pwinfo->pw_gid); 347 int result = fchown(STDOUT_FILENO, pwinfo->pw_uid, pwinfo->pw_gid);
322 PLOG_IF(WARNING, result != 0) << "Failed to change log file owner"; 348 PLOG_IF(WARNING, result != 0) << "Failed to change log file owner";
323 } 349 }
324 350
325 PreExecDelegate pre_exec_delegate(pwinfo); 351 pid_t child_pid = fork();
352 PCHECK(child_pid >= 0) << "fork failed";
353 if (child_pid == 0) {
354 PCHECK(setuid(pwinfo->pw_uid) == 0) << "setuid failed";
355 PCHECK(chdir(pwinfo->pw_dir) == 0) << "chdir to $HOME failed";
356 base::Optional<base::EnvironmentMap> pam_environment =
357 pam_handle.GetEnvironment();
358 CHECK(pam_environment) << "Failed to get environment from PAM";
359 ExecMe2MeScript(std::move(*pam_environment), pwinfo); // Never returns
360 } else {
361 // waitpid will return if the child is ptraced, so loop until the process
362 // actually exits.
363 int status;
364 do {
365 pid_t wait_result = waitpid(child_pid, &status, 0);
326 366
327 base::LaunchOptions launch_options; 367 // Die if wait fails so we don't close the PAM session while the child is
368 // still running.
369 PCHECK(wait_result >= 0) << "wait failed";
370 } while (!WIFEXITED(status) && !WIFSIGNALED(status));
328 371
329 // Required to allow suid binaries to function in the session. 372 if (WIFEXITED(status)) {
330 launch_options.allow_new_privs = true; 373 if (WEXITSTATUS(status) == EXIT_SUCCESS) {
374 LOG(INFO) << "Child exited successfully";
375 } else {
376 LOG(WARNING) << "Child exited with status " << WEXITSTATUS(status);
377 }
378 } else if (WIFSIGNALED(status)) {
379 LOG(WARNING) << "Child terminated by signal " << WTERMSIG(status);
380 }
331 381
332 launch_options.kill_on_parent_death = true; 382 // Best effort PAM cleanup
333 383 if (pam_handle.CloseSession(0) != PAM_SUCCESS) {
334 launch_options.clear_environ = true; 384 LOG(WARNING) << "Failed to close PAM session";
335 launch_options.environ = std::move(*pam_environment); 385 }
336 launch_options.environ["USER"] = pwinfo->pw_name; 386 ignore_result(pam_handle.SetCredentials(PAM_DELETE_CRED));
337 launch_options.environ["LOGNAME"] = pwinfo->pw_name;
338 launch_options.environ["HOME"] = pwinfo->pw_dir;
339 launch_options.environ["SHELL"] = pwinfo->pw_shell;
340 if (!launch_options.environ.count("PATH")) {
341 launch_options.environ["PATH"] = "/bin:/usr/bin";
342 } 387 }
343
344 launch_options.pre_exec_delegate = &pre_exec_delegate;
345
346 // By convention, a login shell is signified by preceeding the shell name in
347 // argv[0] with a '-'.
348 base::CommandLine command_line(base::FilePath(
349 '-' + base::FilePath(pwinfo->pw_shell).BaseName().value()));
350
351 base::Optional<std::string> escaped_script_path =
352 ShellEscapeArgument(script_path);
353
354 CHECK(escaped_script_path) << "Could not escape script path";
355
356 command_line.AppendSwitch("-c");
357 command_line.AppendArg(*escaped_script_path +
358 " --start --foreground --keep-parent-env");
359
360 // Tell LaunchProcess where to find the executable, since argv[0] doesn't
361 // point to it.
362 launch_options.real_path = base::FilePath(pwinfo->pw_shell);
363
364 //////////////////////////////////////////////////////////////////////////////
365 // We're ready to execute the remoting session
366 //////////////////////////////////////////////////////////////////////////////
367
368 base::Process child = base::LaunchProcess(command_line, launch_options);
369
370 if (child.IsValid()) {
371 int exit_code = 0;
372 // Die if wait fails so we don't close the PAM session while the child is
373 // still running.
374 CHECK(child.WaitForExit(&exit_code)) << "Failed to wait for child process";
375 LOG_IF(WARNING, exit_code != 0) << "Child did not exit normally";
376 }
377
378 // Best effort PAM cleanup
379 if (pam_handle.CloseSession(0) != PAM_SUCCESS) {
380 LOG(WARNING) << "Failed to close PAM session";
381 }
382 ignore_result(pam_handle.SetCredentials(PAM_DELETE_CRED));
383 } 388 }
384 389
385 // Opens a temp file for logging. Exits the program on failure. 390 // Opens a temp file for logging. Exits the program on failure.
386 int OpenLogFile() { 391 int OpenLogFile() {
387 char logfile[265]; 392 char logfile[265];
388 std::time_t time = std::time(nullptr); 393 std::time_t time = std::time(nullptr);
389 CHECK_NE(time, (std::time_t)(-1)); 394 CHECK_NE(time, (std::time_t)(-1));
390 // Safe because we're single threaded 395 // Safe because we're single threaded
391 std::tm* localtime = std::localtime(&time); 396 std::tm* localtime = std::localtime(&time);
392 CHECK_NE(std::strftime(logfile, sizeof(logfile), kLogFileTemplate, localtime), 397 CHECK_NE(std::strftime(logfile, sizeof(logfile), kLogFileTemplate, localtime),
393 (std::size_t) 0); 398 (std::size_t) 0);
394 399
395 mode_t mode = umask(0177); 400 mode_t mode = umask(0177);
396 int fd = mkstemp(logfile); 401 int fd = mkstemp(logfile);
397 PCHECK(fd != -1); 402 PCHECK(fd != -1);
398 umask(mode); 403 umask(mode);
399 404
400 return fd; 405 return fd;
401 } 406 }
402 407
408 // Find the username for the current user. If either USER or LOGNAME is set to
409 // a user matching our real user id, we return that. Otherwise, we use getpwuid
410 // to attempt a reverse lookup. Note: It's possible for multiple usernames to
411 // share the same user id (e.g., to allow a user to have logins with different
412 // home directories or group membership, but be considered the same user as far
413 // as file permissions are concerned). Consulting USER/LOGNAME allows us to pick
414 // the correct entry in these circumstances.
415 std::string FindCurrentUsername() {
416 uid_t real_uid = getuid();
417 struct passwd* pwinfo;
418 for (const char* var : {"USER", "LOGNAME"}) {
419 const char* value = getenv(var);
420 if (value) {
421 pwinfo = getpwnam(value);
422 // USER and LOGNAME can be overridden, so make sure the value is valid
423 // and matches the UID of the invoking user.
424 if (pwinfo && pwinfo->pw_uid == real_uid) {
425 return pwinfo->pw_name;
426 }
427 }
428 }
429 errno = 0;
430 pwinfo = getpwuid(real_uid);
431 PCHECK(pwinfo) << "getpwuid failed";
432 return pwinfo->pw_name;
433 }
434
403 // Daemonizes the process. Output is redirected to a file. Exits the program on 435 // Daemonizes the process. Output is redirected to a file. Exits the program on
404 // failure. 436 // failure.
405 // 437 //
406 // This logic is mostly the same as daemonize() in linux_me2me_host.py. Log- 438 // This logic is mostly the same as daemonize() in linux_me2me_host.py. Log-
407 // file redirection especially should be kept in sync. Note that this does 439 // file redirection especially should be kept in sync. Note that this does
408 // not currently wait for the host to start successfully before exiting the 440 // not currently wait for the host to start successfully before exiting the
409 // parent process like the Python script does, as that functionality is 441 // parent process like the Python script does, as that functionality is
410 // probably not useful at boot, where the wrapper is expected to be used. If 442 // probably not useful at boot, where the wrapper is expected to be used. If
411 // it turns out to be desired, it can be implemented by setting up a pipe and 443 // it turns out to be desired, it can be implemented by setting up a pipe and
412 // passing a file descriptor to the Python script. 444 // passing a file descriptor to the Python script.
413 void Daemonize() { 445 void Daemonize() {
414
415 int log_fd = OpenLogFile(); 446 int log_fd = OpenLogFile();
416 int devnull_fd = open("/dev/null", O_RDONLY); 447 int devnull_fd = open("/dev/null", O_RDONLY);
417 PCHECK(devnull_fd != -1); 448 PCHECK(devnull_fd != -1);
418 449
419 PCHECK(dup2(devnull_fd, STDIN_FILENO) != -1); 450 PCHECK(dup2(devnull_fd, STDIN_FILENO) != -1);
420 PCHECK(dup2(log_fd, STDOUT_FILENO) != -1); 451 PCHECK(dup2(log_fd, STDOUT_FILENO) != -1);
421 PCHECK(dup2(log_fd, STDERR_FILENO) != -1); 452 PCHECK(dup2(log_fd, STDERR_FILENO) != -1);
422 453
423 // Close all file descriptors except stdio, including any we may have 454 // Close all file descriptors except stdio, including any we may have
424 // inherited. 455 // inherited.
(...skipping 24 matching lines...) Expand all
449 // dropped privileges, so change to / to make sure we're not keeping any other 480 // dropped privileges, so change to / to make sure we're not keeping any other
450 // directory in use. 481 // directory in use.
451 PCHECK(chdir("/") == 0); 482 PCHECK(chdir("/") == 0);
452 483
453 // Done! 484 // Done!
454 } 485 }
455 486
456 } // namespace 487 } // namespace
457 488
458 int main(int argc, char** argv) { 489 int main(int argc, char** argv) {
459 base::CommandLine::Init(argc, argv); 490 if (argc < 2 || strcmp(argv[1], "start") != 0) {
460 491 PrintUsage();
461 const base::CommandLine* command_line =
462 base::CommandLine::ForCurrentProcess();
463 if (command_line->HasSwitch(kHelpSwitchName) ||
464 command_line->HasSwitch(kQuestionSwitchName)) {
465 PrintUsage(command_line->GetProgram());
466 std::exit(EXIT_SUCCESS);
467 }
468
469 base::FilePath script_path =
470 command_line->GetSwitchValuePath(kScriptSwitchName);
471 std::string user = command_line->GetSwitchValueNative(kUserSwitchName);
472 bool foreground = command_line->HasSwitch(kForegroundSwitchName);
473
474 if (script_path.empty()) {
475 std::fputs("The path to the me2me python script is required.\n", stderr);
476 std::exit(EXIT_FAILURE); 492 std::exit(EXIT_FAILURE);
477 } 493 }
478 494
479 if (user.empty()) { 495 // Skip initial args
480 std::fputs("The target user must be specified.\n", stderr); 496 argc -= 2;
497 argv += 2;
498
499 bool foreground = false;
500 if (argc >= 1 && strcmp(argv[0], kForegroundFlag) == 0) {
501 foreground = true;
502 argc -= 1;
503 argv += 1;
504 }
505
506 if (argc > 1) {
507 std::fputs("Too many command-line arguments.\n", stderr);
481 std::exit(EXIT_FAILURE); 508 std::exit(EXIT_FAILURE);
482 } 509 }
483 510
511 uid_t real_uid = getuid();
512 std::string user;
513
514 // Note: This logic is security sensitive. It is imperative that a non-root
515 // user is not allowed to specify an arbitrary target user.
516 if (argc == 0) {
517 if (real_uid == 0) {
518 std::fputs("Target user must be specified when run as root.\n", stderr);
519 std::exit(EXIT_FAILURE);
520 }
521 user = FindCurrentUsername();
522 } else {
523 if (real_uid != 0) {
524 std::fputs("Target user may not be specified by non-root users.\n",
525 stderr);
526 std::exit(EXIT_FAILURE);
527 }
528 user = argv[0];
529 }
530
484 if (!foreground) { 531 if (!foreground) {
485 Daemonize(); 532 Daemonize();
486 } 533 }
487 534
488 ExecuteSession(std::move(user), script_path.value(), !foreground); 535 // Daemonizing redirects stdout to a log file, which we want to be owned by
536 // the target user.
537 bool chown_stdout = !foreground;
538 ExecuteSession(std::move(user), chown_stdout,
539 real_uid != 0 ? base::make_optional(real_uid) : base::nullopt);
489 } 540 }
OLDNEW
« no previous file with comments | « remoting/host/installer/linux/debian/chrome-remote-desktop.init ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698