#include <fcntl.h>
#include <grp.h>
#include <limits.h>
#include <pwd.h>
#include <security/pam_appl.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "base/environment.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/process/launch.h"
#include "base/strings/strcat.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace {
const int kMessageFd = 202;
const int kRelaunchExitCode = 41;
const char kPamName[] = "chrome-remote-desktop";
const char kScriptName[] = "chrome-remote-desktop";
const char kStartCommand[] = "start";
const char kForegroundFlag[] = "--foreground";
const char kUserFlag[] = "--user";
const char kExeSymlink[] = "/proc/self/exe";
const char kLogFileTemplate[] =
"/tmp/chrome_remote_desktop_%Y%m%d_%H%M%S_XXXXXX";
constexpr char kLatestLogSymlink[] = "/tmp/chrome_remote_desktop.latest";
const char kUsageMessage[] =
"This program is not intended to be run by end users. To configure Chrome\n"
"Remote Desktop, please install the app from the Chrome Web Store:\n"
"https://chrome.google.com/remotedesktop\n";
const char* const kPassthroughVariables[] = {
"GOOGLE_CLIENT_ID_REMOTING", "GOOGLE_CLIENT_ID_REMOTING_HOST",
"GOOGLE_CLIENT_SECRET_REMOTING", "GOOGLE_CLIENT_SECRET_REMOTING_HOST",
"CHROME_REMOTE_DESKTOP_HOST_EXTRA_PARAMS"};
char gExecutablePath[PATH_MAX] = {};
void PrintUsage() {
std::fputs(kUsageMessage, stderr);
}
absl::optional<std::string> ShellEscapeArgument(
const base::StringPiece argument) {
std::string result;
for (char character : argument) {
if (character == '\n') {
return absl::nullopt;
}
if (!((character >= '0' && character <= '9') ||
(character >= 'A' && character <= 'Z') ||
(character >= 'a' && character <= 'z') ||
(character == '-' || character == '_'))) {
result.push_back('\\');
}
result.push_back(character);
}
return result;
}
extern "C" int Converse(int num_messages,
const struct pam_message** messages,
struct pam_response** responses,
void* context) {
bool failed = false;
for (int i = 0; i < num_messages; ++i) {
const pam_message* message = messages[i];
switch (message->msg_style) {
case PAM_PROMPT_ECHO_OFF:
case PAM_PROMPT_ECHO_ON:
LOG(WARNING) << "PAM requested user input (unsupported): "
<< (message->msg ? message->msg : "");
failed = true;
break;
case PAM_TEXT_INFO:
LOG(INFO) << "[PAM] " << (message->msg ? message->msg : "");
break;
case PAM_ERROR_MSG:
LOG(WARNING) << "[PAM] " << (message->msg ? message->msg : "");
break;
default:
LOG(WARNING) << "Encountered unknown PAM message style";
failed = true;
break;
}
}
if (failed) {
return PAM_CONV_ERR;
}
pam_response* response_list = static_cast<pam_response*>(
std::calloc(num_messages, sizeof(*response_list)));
if (response_list == nullptr) {
return PAM_BUF_ERR;
}
*responses = response_list;
return PAM_SUCCESS;
}
const struct pam_conv kPamConversation = {Converse, nullptr};
class PamHandle {
public:
PamHandle(const char* service_name,
const char* user,
const struct pam_conv* pam_conversation) {
last_return_code_ =
pam_start(service_name, user, pam_conversation, &pam_handle_);
if (last_return_code_ != PAM_SUCCESS) {
pam_handle_ = nullptr;
}
}
PamHandle(const PamHandle&) = delete;
PamHandle& operator=(const PamHandle&) = delete;
~PamHandle() {
if (pam_handle_ != nullptr) {
pam_end(pam_handle_, last_return_code_);
}
}
bool IsInitialized() const { return pam_handle_ != nullptr; }
int AccountManagement(int flags) {
return last_return_code_ = pam_acct_mgmt(pam_handle_, flags);
}
int SetCredentials(int flags) {
return last_return_code_ = pam_setcred(pam_handle_, flags);
}
int OpenSession(int flags) {
return last_return_code_ = pam_open_session(pam_handle_, flags);
}
int CloseSession(int flags) {
return last_return_code_ = pam_close_session(pam_handle_, flags);
}
int SetItem(int item_type, const char* value) {
return last_return_code_ = pam_set_item(pam_handle_, item_type, value);
}
absl::optional<std::string> GetUser() {
const char* user;
last_return_code_ = pam_get_item(pam_handle_, PAM_USER,
reinterpret_cast<const void**>(&user));
if (last_return_code_ != PAM_SUCCESS || user == nullptr) {
return absl::nullopt;
}
return std::string(user);
}
int PutEnv(base::StringPiece name, base::StringPiece value) {
std::string name_value = base::StrCat({name, "=", value});
return last_return_code_ = pam_putenv(pam_handle_, name_value.c_str());
}
absl::optional<base::EnvironmentMap> GetEnvironment() {
char** environment = pam_getenvlist(pam_handle_);
if (environment == nullptr) {
return absl::nullopt;
}
base::EnvironmentMap environment_map;
for (char** variable = environment; *variable != nullptr; ++variable) {
char* delimiter = std::strchr(*variable, '=');
if (delimiter != nullptr) {
environment_map[std::string(*variable, delimiter)] =
std::string(delimiter + 1);
}
std::free(*variable);
}
std::free(environment);
return environment_map;
}
const char* ErrorString(int return_code) {
return pam_strerror(pam_handle_, return_code);
}
void CheckReturnCode(int return_code, base::StringPiece what) {
if (return_code != PAM_SUCCESS) {
LOG(FATAL) << "[PAM] " << what << ": " << ErrorString(return_code);
}
}
private:
RAW_PTR_EXCLUSION pam_handle_t* pam_handle_ = nullptr;
int last_return_code_ = PAM_SUCCESS;
};
void DetermineExecutablePath() {
ssize_t path_size =
readlink(kExeSymlink, gExecutablePath, std::size(gExecutablePath));
PCHECK(path_size >= 0) << "Failed to determine executable location";
CHECK(path_size < PATH_MAX) << "Executable path too long";
gExecutablePath[path_size] = '\0';
CHECK(gExecutablePath[0] == '/') << "Executable path not absolute";
}
std::string FindScriptPath() {
return base::FilePath(gExecutablePath).DirName().Append(kScriptName).value();
}
[[noreturn]] void ExecMe2MeScript(base::EnvironmentMap environment,
const struct passwd* pwinfo,
const std::vector<std::string>& script_args) {
std::string login_shell = pwinfo->pw_shell;
if (login_shell.empty()) {
login_shell = "/bin/sh";
}
std::string shell_name = '-' + base::FilePath(login_shell).BaseName().value();
absl::optional<std::string> escaped_script_path =
ShellEscapeArgument(FindScriptPath());
CHECK(escaped_script_path) << "Could not escape script path";
std::string shell_arg = *escaped_script_path + " --start --child-process";
for (const std::string& arg : script_args) {
absl::optional<std::string> escaped_arg = ShellEscapeArgument(arg);
CHECK(escaped_arg) << "Could not escape script argument";
shell_arg += " ";
shell_arg += *escaped_arg;
}
environment["USER"] = pwinfo->pw_name;
environment["LOGNAME"] = pwinfo->pw_name;
environment["HOME"] = pwinfo->pw_dir;
environment["SHELL"] = login_shell;
if (!environment.count("PATH")) {
environment["PATH"] = "/bin:/usr/bin";
}
environment["CHROME_REMOTE_DESKTOP_SESSION"] = "1";
std::vector<std::string> env_strings;
for (const auto& env_var : environment) {
env_strings.emplace_back(env_var.first + "=" + env_var.second);
}
std::vector<const char*> arg_ptrs = {shell_name.c_str(), "-c",
shell_arg.c_str(), nullptr};
std::vector<const char*> env_ptrs;
env_ptrs.reserve(env_strings.size() + 1);
for (const auto& env_string : env_strings) {
env_ptrs.push_back(env_string.c_str());
}
env_ptrs.push_back(nullptr);
execve(login_shell.c_str(), const_cast<char* const*>(arg_ptrs.data()),
const_cast<char* const*>(env_ptrs.data()));
PLOG(FATAL) << "Failed to exec login shell " << login_shell;
std::exit(EXIT_FAILURE);
}
void Relaunch(const absl::optional<std::string>& user,
const std::vector<std::string>& script_args) {
CHECK(user.has_value() == (getuid() == 0));
std::vector<const char*> arg_ptrs = {gExecutablePath, kStartCommand,
kForegroundFlag};
if (user) {
arg_ptrs.push_back(kUserFlag);
arg_ptrs.push_back(user->c_str());
}
arg_ptrs.push_back("--");
for (const std::string& arg : script_args) {
arg_ptrs.push_back(arg.c_str());
}
arg_ptrs.push_back(nullptr);
execv(gExecutablePath, const_cast<char* const*>(arg_ptrs.data()));
PCHECK(false) << "Failed to exec self";
}
bool ExecuteSession(std::string user,
bool chown_log,
absl::optional<uid_t> match_uid,
const std::vector<std::string>& script_args) {
PamHandle pam_handle(kPamName, user.c_str(), &kPamConversation);
CHECK(pam_handle.IsInitialized()) << "Failed to initialize PAM";
for (const char* variable : kPassthroughVariables) {
char* value = std::getenv(variable);
if (value != nullptr) {
pam_handle.CheckReturnCode(pam_handle.PutEnv(variable, value),
"Environment passthrough");
}
}
clearenv();
pam_handle.CheckReturnCode(pam_handle.PutEnv("XDG_SESSION_CLASS", "user"),
"Set session class");
pam_handle.CheckReturnCode(pam_handle.PutEnv("XDG_SESSION_TYPE", "x11"),
"Set session type");
pam_handle.CheckReturnCode(
pam_handle.SetItem(PAM_TTY, "chrome-remote-desktop"), "Set PAM_TTY");
pam_handle.CheckReturnCode(pam_handle.AccountManagement(0), "Account check");
user = pam_handle.GetUser().value_or(std::move(user));
errno = 0;
struct passwd* pwinfo = getpwnam(user.c_str());
PCHECK(pwinfo != nullptr) << "getpwnam failed";
PCHECK(setreuid(pwinfo->pw_uid, -1) == 0) << "setreuid failed";
PCHECK(setgid(pwinfo->pw_gid) == 0) << "setgid failed";
PCHECK(initgroups(pwinfo->pw_name, pwinfo->pw_gid) == 0)
<< "initgroups failed";
pam_handle.CheckReturnCode(pam_handle.SetCredentials(PAM_ESTABLISH_CRED),
"Set credentials");
pam_handle.CheckReturnCode(pam_handle.OpenSession(0), "Open session");
user = pam_handle.GetUser().value_or(std::move(user));
pwinfo = getpwnam(user.c_str());
PCHECK(pwinfo != nullptr) << "getpwnam failed";
if (match_uid && pwinfo->pw_uid != *match_uid) {
LOG(FATAL) << "PAM remapped username to one with a different user ID.";
}
if (chown_log) {
int result = fchown(STDOUT_FILENO, pwinfo->pw_uid, pwinfo->pw_gid);
PLOG_IF(WARNING, result != 0) << "Failed to change log file owner";
result = lchown(kLatestLogSymlink, pwinfo->pw_uid, pwinfo->pw_gid);
PLOG_IF(WARNING, result != 0)
<< "Failed to change latest log symlink owner";
}
pid_t child_pid = fork();
PCHECK(child_pid >= 0) << "fork failed";
if (child_pid == 0) {
PCHECK(setuid(pwinfo->pw_uid) == 0) << "setuid failed";
PCHECK(chdir(pwinfo->pw_dir) == 0) << "chdir to $HOME failed";
absl::optional<base::EnvironmentMap> pam_environment =
pam_handle.GetEnvironment();
CHECK(pam_environment) << "Failed to get environment from PAM";
ExecMe2MeScript(std::move(*pam_environment), pwinfo, script_args);
} else {
close(kMessageFd);
int status;
do {
pid_t wait_result = waitpid(child_pid, &status, 0);
PCHECK(wait_result >= 0) << "wait failed";
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
bool relaunch = false;
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) == EXIT_SUCCESS) {
LOG(INFO) << "Child exited successfully";
} else if (WEXITSTATUS(status) == kRelaunchExitCode) {
LOG(INFO) << "Restarting session";
relaunch = true;
} else {
LOG(WARNING) << "Child exited with status " << WEXITSTATUS(status);
}
} else if (WIFSIGNALED(status)) {
LOG(WARNING) << "Child terminated by signal " << WTERMSIG(status);
}
if (pam_handle.CloseSession(0) != PAM_SUCCESS) {
LOG(WARNING) << "Failed to close PAM session";
}
std::ignore = pam_handle.SetCredentials(PAM_DELETE_CRED);
return relaunch;
}
}
struct LogFile {
int fd;
std::string path;
};
LogFile OpenLogFile() {
char logfile[265];
std::time_t time = std::time(nullptr);
CHECK_NE(time, (std::time_t)(-1));
std::tm* localtime = std::localtime(&time);
CHECK_NE(std::strftime(logfile, sizeof(logfile), kLogFileTemplate, localtime),
static_cast<std::size_t>(0))
<< "Failed to format log file name";
mode_t mode = umask(0177);
int fd = mkstemp(logfile);
PCHECK(fd != -1) << "Failed to open log file";
int symlink_ret = symlink(logfile, kLatestLogSymlink);
if (symlink_ret != 0 && errno == EEXIST) {
unlink(kLatestLogSymlink);
symlink_ret = symlink(logfile, kLatestLogSymlink);
}
PLOG_IF(ERROR, symlink_ret != 0)
<< "Failed to create log symlink to " << logfile;
umask(mode);
return {fd, logfile};
}
std::string FindCurrentUsername() {
uid_t real_uid = getuid();
struct passwd* pwinfo;
for (const char* var : {"USER", "LOGNAME"}) {
const char* value = getenv(var);
if (value) {
pwinfo = getpwnam(value);
if (pwinfo && pwinfo->pw_uid == real_uid) {
return pwinfo->pw_name;
}
}
}
errno = 0;
pwinfo = getpwuid(real_uid);
PCHECK(pwinfo) << "getpwuid failed";
return pwinfo->pw_name;
}
void HandleInterrupt(int signal) {
static const char kInterruptedMessage[] =
"Interrupted. The daemon is still running in the background.\n";
std::ignore = write(STDERR_FILENO, kInterruptedMessage,
std::size(kInterruptedMessage) - 1);
raise(signal);
}
void HandleAlarm(int) {
static const char kTimeoutMessage[] =
"Timeout waiting for session to start. It may have crashed, or may still "
"be running in the background.\n";
std::ignore =
write(STDERR_FILENO, kTimeoutMessage, std::size(kTimeoutMessage) - 1);
std::_Exit(EXIT_SUCCESS);
}
void WaitForMessagesAndExit(int read_fd, const std::string& log_name) {
static const base::StringPiece kMessagePrefix = "MSG:";
static const base::StringPiece kReady = "READY\n";
struct sigaction action = {};
sigemptyset(&action.sa_mask);
action.sa_flags = SA_RESETHAND | SA_NODEFER;
action.sa_handler = HandleInterrupt;
sigaction(SIGINT, &action, nullptr);
sigaction(SIGTERM, &action, nullptr);
action.sa_handler = HandleAlarm;
sigaction(SIGALRM, &action, nullptr);
alarm(120);
std::FILE* stream = fdopen(read_fd, "r");
char* buffer = nullptr;
std::size_t buffer_size = 0;
ssize_t line_size;
bool message_received = false;
bool host_ready = false;
while ((line_size = getline(&buffer, &buffer_size, stream)) >= 0) {
message_received = true;
base::StringPiece line(buffer, line_size);
if (base::StartsWith(line, kMessagePrefix, base::CompareCase::SENSITIVE)) {
line.remove_prefix(kMessagePrefix.size());
std::fwrite(line.data(), sizeof(char), line.size(), stderr);
} else if (line == kReady) {
host_ready = true;
} else {
std::fputs("Unrecognized command: ", stderr);
std::fwrite(line.data(), sizeof(char), line.size(), stderr);
}
}
if (!std::feof(stream) || !message_received) {
LOG(WARNING) << "Failed to read from message pipe. Please check log to "
"determine host status.\n";
host_ready = true;
}
std::fprintf(stderr, "Log file: %s\n", log_name.c_str());
std::exit(host_ready ? EXIT_SUCCESS : EXIT_FAILURE);
}
void Daemonize() {
LogFile log_file = OpenLogFile();
int devnull_fd = open("/dev/null", O_RDONLY);
PCHECK(devnull_fd != -1) << "Failed to open /dev/null";
uid_t real_uid = getuid();
bool pipe_created = false;
int read_fd;
if (real_uid != 0) {
int pipe_fd[2];
int pipe_result = ::pipe(pipe_fd);
if (pipe_result != 0 || dup2(pipe_fd[1], kMessageFd) != kMessageFd) {
PLOG(WARNING) << "Failed to create message pipe. Please check log to "
"determine host status.\n";
} else {
pipe_created = true;
read_fd = pipe_fd[0];
close(pipe_fd[1]);
}
}
pid_t pid = fork();
PCHECK(pid != -1) << "fork failed";
if (pid != 0) {
if (!pipe_created) {
std::exit(EXIT_SUCCESS);
} else {
PCHECK(setuid(real_uid) == 0) << "setuid failed";
close(kMessageFd);
WaitForMessagesAndExit(read_fd, log_file.path);
CHECK(false);
}
}
PCHECK(setsid() != -1) << "setsid failed";
pid = fork();
PCHECK(pid != -1) << "fork failed";
if (pid != 0) {
std::exit(EXIT_SUCCESS);
}
LOG(INFO) << "Daemon process started in the background, logging to '"
<< log_file.path << "'";
PCHECK(chdir("/") == 0) << "chdir / failed";
PCHECK(dup2(devnull_fd, STDIN_FILENO) != -1) << "dup2 failed";
PCHECK(dup2(log_file.fd, STDOUT_FILENO) != -1) << "dup2 failed";
PCHECK(dup2(log_file.fd, STDERR_FILENO) != -1) << "dup2 failed";
if (pipe_created) {
base::CloseSuperfluousFds(
{base::InjectionArc(kMessageFd, kMessageFd, false)});
} else {
base::CloseSuperfluousFds(base::InjectiveMultimap());
}
}
}
int main(int argc, char** argv) {
DetermineExecutablePath();
if (geteuid() != 0) {
std::fprintf(stderr,
"%s not installed setuid root. Host must be started by "
"administrator.\n",
gExecutablePath);
std::exit(EXIT_FAILURE);
}
if (argc < 2 || std::strcmp(argv[1], kStartCommand) != 0) {
PrintUsage();
std::exit(EXIT_FAILURE);
}
argc -= 2;
argv += 2;
bool foreground = false;
absl::optional<std::string> user;
std::vector<std::string> script_args;
while (argc > 0) {
if (std::strcmp(argv[0], kForegroundFlag) == 0) {
foreground = true;
argc -= 1;
argv += 1;
} else if (std::strcmp(argv[0], kUserFlag) == 0 && argc >= 2) {
user = std::string(argv[1]);
argc -= 2;
argv += 2;
} else if (std::strcmp(argv[0], "--") == 0) {
argc -= 1;
argv += 1;
while (argc > 0) {
script_args.emplace_back(argv[0]);
argc -= 1;
argv += 1;
}
} else {
PrintUsage();
std::exit(EXIT_FAILURE);
}
}
uid_t real_uid = getuid();
if (real_uid != 0) {
if (user) {
std::fputs("Target user may not be specified by non-root users.\n",
stderr);
std::exit(EXIT_FAILURE);
}
user = FindCurrentUsername();
} else {
if (!user) {
std::fputs("Target user must be specified when run as root.\n", stderr);
std::exit(EXIT_FAILURE);
}
}
if (!foreground) {
Daemonize();
}
bool chown_stdout = !foreground;
absl::optional<uid_t> match_uid =
real_uid != 0 ? absl::make_optional(real_uid) : absl::nullopt;
pid_t child_pid = fork();
PCHECK(child_pid >= 0) << "fork failed";
if (child_pid == 0) {
bool relaunch = ExecuteSession(std::move(*user), chown_stdout, match_uid,
std::move(script_args));
std::exit(relaunch ? kRelaunchExitCode : EXIT_SUCCESS);
} else {
close(kMessageFd);
int status;
do {
pid_t wait_result = waitpid(child_pid, &status, 0);
PCHECK(wait_result >= 0) << "wait failed";
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
if (WIFEXITED(status) && WEXITSTATUS(status) == kRelaunchExitCode) {
Relaunch(real_uid == 0 ? user : absl::nullopt, script_args);
}
}
return EXIT_SUCCESS;
}