blob: c8b326dde106a68ab6e2d1b1976db2dc0f9c3f9d [file]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/setup/start_host_as_root.h"
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "base/check.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/process/launch.h"
#include "base/strings/string_split.h"
namespace remoting {
constexpr char kChromotingGroupName[] = "chrome-remote-desktop";
void PrintGroupMembershipError(const char* user_name) {
fprintf(stderr,
"%s is not a member of the `%s` group.\n"
" Please add them using this command:\n"
" sudo usermod -G %s %s\n",
user_name, kChromotingGroupName, kChromotingGroupName, user_name);
}
bool CheckChromotingGroupMembership(const char* user_name,
gid_t user_group_id) {
errno = 0;
group* chromoting_group = getgrnam(kChromotingGroupName);
if (!chromoting_group) {
// The installer creates this group so this condition is unexpected but we
// check here in case the machine is in a bad state or our installer was
// modified (broken) in some way such that the group does not exist.
fprintf(stderr,
"Failed to retrieve group info for `%s`. errno = %s(%d)\n"
" Please create this group using this command:\n"
" sudo addgroup --system chrome-remote-desktop\n",
kChromotingGroupName, strerror(errno), errno);
return false;
}
// Figure out how many groups the user is in.
int group_count = 0;
getgrouplist(user_name, user_group_id, nullptr, &group_count);
if (group_count < 1) {
PrintGroupMembershipError(user_name);
return false;
}
// Retrieve the groups.
std::vector<gid_t> groups(group_count);
getgrouplist(user_name, user_group_id, groups.data(), &group_count);
if (!base::Contains(groups, chromoting_group->gr_gid)) {
PrintGroupMembershipError(user_name);
return false;
}
fprintf(stdout, "Verified that %s is a member of %s\n", user_name,
kChromotingGroupName);
return true;
}
int StartHostAsRoot(int argc, char** argv) {
DCHECK(getuid() == 0);
base::CommandLine command_line(argc, argv);
std::string user_name;
if (command_line.HasSwitch("corp-user")) {
// For compat reasons, we support either email or username for this param.
// TODO: joedow - Remove support for the email param around M135 or so.
std::string arg_value = command_line.GetSwitchValueASCII("corp-user");
auto parts = base::SplitStringOnce(arg_value, '@');
if (!parts) {
user_name = std::move(arg_value);
} else {
user_name = std::string(parts->first);
}
} else if (command_line.HasSwitch("cloud-user")) {
std::string arg_value = command_line.GetSwitchValueASCII("cloud-user");
auto parts = base::SplitStringOnce(arg_value, '@');
if (parts) {
user_name = std::string(parts->first);
} else {
fprintf(stderr, "The --cloud-user flag requires an email address.\n");
}
} else if (command_line.HasSwitch("user-name")) {
user_name = command_line.GetSwitchValueASCII("user-name");
}
if (user_name.empty()) {
fprintf(stderr,
"Must specify one of the following arguments when running as root:"
"\n --user-name=<username>\n --corp-user=<username>"
"\n --cloud-user=<email>\n");
return 1;
}
errno = 0;
const passwd* user_struct = getpwnam(user_name.c_str());
if (!user_struct) {
fprintf(stderr,
"Failed to retrieve passwd struct for %s. errno = %s(%d)\n"
"Does this user account exist on the machine?\n",
user_name.c_str(), strerror(errno), errno);
return -1;
}
std::string home_dir = user_struct->pw_dir ?: "";
base::FilePath home_dir_path = base::FilePath(home_dir);
if (!base::DirectoryExists(home_dir_path)) {
fprintf(stderr,
"[WARNING] Can't find home directory (%s) for %s(%d).\n"
"Please run the 'mkhomedir_helper' utility, or similar, to create "
"a home directory for the user.\nThe host setup process will not "
"complete successfully without one.\n",
home_dir.c_str(), user_name.c_str(), user_struct->pw_uid);
} else {
fprintf(stdout, "Verified that home directory (%s) exists for %s(%d)\n",
home_dir.c_str(), user_name.c_str(), user_struct->pw_uid);
}
// This switch is provided for environments where systemd is not being used.
bool use_sysvinit = command_line.HasSwitch("sysvinit");
if (use_sysvinit) {
fprintf(stdout, "Checking sysvinit configuration requirements.\n");
if (!CheckChromotingGroupMembership(user_struct->pw_name,
user_struct->pw_gid)) {
return -1;
}
}
int return_value = 1;
command_line.RemoveSwitch("user-name");
command_line.RemoveSwitch("sysvinit");
command_line.AppendSwitch("no-start");
std::vector<std::string> create_config_command_line{
"/usr/bin/sudo",
"-u",
user_name.c_str(),
};
create_config_command_line.insert(create_config_command_line.end(),
command_line.argv().begin(),
command_line.argv().end());
// LaunchProcess redirects stdin to /dev/null, but start_host prompts for a
// PIN if one isn't specified on the command-line, so dup and remap it.
base::LaunchOptions options;
int stdin_dup = dup(STDIN_FILENO);
options.fds_to_remap.emplace_back(stdin_dup, STDIN_FILENO);
auto create_config_process =
base::LaunchProcess(create_config_command_line, options);
close(stdin_dup);
if (!create_config_process.WaitForExit(&return_value) || return_value != 0) {
fprintf(stderr, "Failed to set new config.\n");
return return_value;
}
std::vector<std::string> start_service_command_line{
"systemctl", "enable", "--now",
std::string("chrome-remote-desktop@") + user_name};
if (use_sysvinit) {
// Using sysvinit is much less common that using systemd so we optimize for
// that codepath and reset the vector if needed.
start_service_command_line = {"/etc/init.d/chrome-remote-desktop", "start",
user_name};
}
return_value = 1;
auto start_service_process =
base::LaunchProcess(start_service_command_line, base::LaunchOptions());
if (!start_service_process.WaitForExit(&return_value) || return_value != 0) {
fprintf(stderr, "Failed to enable host service.\n");
return return_value;
}
printf("Host service started successfully.\n");
return 0;
}
} // namespace remoting