#include "remoting/host/setup/start_host_main.h"
#include <stddef.h>
#include <stdio.h>
#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/logging/logging_settings.h"
#include "base/message_loop/message_pump_type.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/task/single_thread_task_executor.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/threading/thread.h"
#include "build/build_config.h"
#include "mojo/core/embedder/embedder.h"
#include "net/ssl/client_cert_store.h"
#include "net/url_request/url_request_context_getter.h"
#include "remoting/base/certificate_helpers.h"
#include "remoting/base/logging.h"
#include "remoting/base/url_request_context_getter.h"
#include "remoting/host/setup/cloud_host_starter.h"
#include "remoting/host/setup/corp_host_starter.h"
#include "remoting/host/setup/host_starter.h"
#include "remoting/host/setup/oauth_host_starter.h"
#include "remoting/host/setup/pin_validator.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/transitional_url_loader_factory_owner.h"
#if BUILDFLAG(IS_POSIX)
#include <termios.h>
#include <unistd.h>
#endif
#if BUILDFLAG(IS_LINUX)
#include "remoting/base/crash/crash_reporting_crashpad.h"
#include "remoting/host/setup/daemon_controller_delegate_linux.h"
#include "remoting/host/setup/start_host_as_root.h"
#endif
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#include "base/process/process_info.h"
#include "remoting/base/crash/crash_reporting_breakpad.h"
#endif
namespace remoting {
namespace {
constexpr char kPinSwitchName[] = "pin";
constexpr char kAuthCodeSwitchName[] = "code";
constexpr char kRedirectUrlSwitchName[] = "redirect-url";
constexpr char kHostOwnerSwitchName[] = "host-owner";
constexpr char kCorpUserSwitchName[] = "corp-user";
constexpr char kCloudUserSwitchName[] = "cloud-user";
constexpr char kCloudApiKeySwitchName[] = "cloud-api-key";
constexpr char kNameSwitchName[] = "name";
constexpr char kDisplayNameSwitchName[] = "display-name";
constexpr char kDisableCrashReportingSwitchName[] = "disable-crash-reporting";
constexpr char kInvalidPinErrorMessage[] =
"Please provide a numeric PIN consisting of at least six digits.\n";
bool g_started = false;
base::SingleThreadTaskExecutor* g_main_thread_task_executor = nullptr;
base::RunLoop* g_active_run_loop = nullptr;
void PrintDefaultHelpMessage(const char* process_name) {
UNSAFE_TODO(fprintf(
stderr,
"Please visit https://remotedesktop.google.com/headless for "
"instructions on running this tool and help generating the command "
"line arguments.\n"
"\n"
"Example usage:\n%s --%s=<auth code> --%s=<redirect url> "
"[--%s=<host display name>] [--%s=<6+ digit PIN>] [--%s]\n",
process_name, kAuthCodeSwitchName, kRedirectUrlSwitchName,
kDisplayNameSwitchName, kPinSwitchName,
kDisableCrashReportingSwitchName));
}
void PrintCorpUserHelpMessage(const char* process_name) {
UNSAFE_TODO(fprintf(
stdout,
"Setting up a machine for a corp user requires the username of that "
"user and an optional display name.\n\nExample usage:\n"
"%s --%s=<username> [--%s=corp-machine-name]\n",
process_name, kCorpUserSwitchName, kDisplayNameSwitchName));
}
void PrintCloudUserHelpMessage(const char* process_name) {
UNSAFE_TODO(fprintf(
stdout,
"Setting up a Compute Engine Instance requires the email address of "
"the user.\n\nAn optional API_KEY, created for the project the "
"Compute Engine Instance is in, can be provided. Otherwise an access "
"token will be retrieved for the default service account.\n\nAn "
"optional display name can also be provided, otherwise the hostname, "
"or FQDN, of the instance will be used.\n\n"
"Example usage:\n%s --%s=<user_email_address> [--%s=<API_KEY>] "
"[--%s=cloud-instance-display-name] [--%s]\n",
process_name, kCloudUserSwitchName, kCloudApiKeySwitchName,
kDisplayNameSwitchName, kDisableCrashReportingSwitchName));
}
void SetEcho(bool echo) {
#if BUILDFLAG(IS_WIN)
DWORD mode;
HANDLE console_handle = GetStdHandle(STD_INPUT_HANDLE);
if (!GetConsoleMode(console_handle, &mode)) {
LOG(ERROR) << "GetConsoleMode failed";
return;
}
SetConsoleMode(console_handle,
(mode & ~ENABLE_ECHO_INPUT) | (echo ? ENABLE_ECHO_INPUT : 0));
#else
termios term;
tcgetattr(STDIN_FILENO, &term);
if (echo) {
term.c_lflag |= ECHO;
} else {
term.c_lflag &= ~ECHO;
}
tcsetattr(STDIN_FILENO, TCSANOW, &term);
#endif
}
std::string ReadString(bool no_echo) {
if (no_echo) {
SetEcho(false);
}
const int kMaxLen = 1024;
std::string str(kMaxLen, 0);
char* result = UNSAFE_TODO(fgets(&str[0], kMaxLen, stdin));
if (no_echo) {
printf("\n");
SetEcho(true);
}
if (!result) {
return std::string();
}
size_t newline_index = str.find('\n');
if (newline_index != std::string::npos) {
str[newline_index] = '\0';
}
str.resize(strlen(&str[0]));
return str;
}
void OnDone(HostStarter::Result result) {
if (!g_main_thread_task_executor->task_runner()->BelongsToCurrentThread()) {
g_main_thread_task_executor->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&OnDone, result));
return;
}
switch (result) {
case HostStarter::START_COMPLETE:
g_started = true;
printf("Host started successfully.\n");
break;
case HostStarter::NETWORK_ERROR:
fprintf(stderr, "Couldn't start host: network error.\n");
break;
case HostStarter::OAUTH_ERROR:
fprintf(stderr, "Couldn't start host: OAuth error.\n");
break;
case HostStarter::PERMISSION_DENIED:
fprintf(stderr, "Couldn't start host: Permission denied.\n");
break;
case HostStarter::REGISTRATION_ERROR:
fprintf(stderr, "Couldn't start host: Registration error.\n");
break;
case HostStarter::START_ERROR:
fprintf(stderr, "Couldn't start host.\n");
break;
}
g_active_run_loop->Quit();
}
bool InitializeParamsForOAuthFlow(HostStarter::Params& params,
const base::CommandLine* command_line) {
if (command_line->HasSwitch("host-id")) {
params.id = command_line->GetSwitchValueASCII("host-id");
}
params.name = command_line->GetSwitchValueASCII(kDisplayNameSwitchName);
if (params.name.empty()) {
params.name = command_line->GetSwitchValueASCII(kNameSwitchName);
}
params.pin = command_line->GetSwitchValueASCII(kPinSwitchName);
params.auth_code = command_line->GetSwitchValueASCII(kAuthCodeSwitchName);
params.redirect_url =
command_line->GetSwitchValueASCII(kRedirectUrlSwitchName);
params.owner_email = base::ToLowerASCII(
command_line->GetSwitchValueASCII(kHostOwnerSwitchName));
params.enable_crash_reporting =
!command_line->HasSwitch(kDisableCrashReportingSwitchName);
if (params.auth_code.empty() || params.redirect_url.empty()) {
return false;
}
if (params.name.empty()) {
fprintf(stdout, "Enter a name for this computer: ");
fflush(stdout);
params.name = ReadString(false);
}
if (params.pin.empty()) {
while (true) {
fprintf(stdout, "Enter a PIN of at least six digits: ");
fflush(stdout);
params.pin = ReadString(true);
if (!remoting::IsPinValid(params.pin)) {
UNSAFE_TODO(fprintf(stdout, kInvalidPinErrorMessage));
fflush(stdout);
continue;
}
std::string pin_confirmation;
fprintf(stdout, "Enter the same PIN again: ");
fflush(stdout);
pin_confirmation = ReadString(true);
if (params.pin != pin_confirmation) {
fprintf(stdout, "You entered different PINs.\n");
fflush(stdout);
continue;
}
break;
}
} else {
if (!remoting::IsPinValid(params.pin)) {
UNSAFE_TODO(fprintf(stderr, kInvalidPinErrorMessage));
return false;
}
}
return true;
}
bool InitializeCorpMachineParams(HostStarter::Params& params,
const base::CommandLine* command_line) {
params.enable_crash_reporting = true;
size_t corp_arg_count = 1;
std::string corp_user_value = base::ToLowerASCII(
command_line->GetSwitchValueASCII(kCorpUserSwitchName));
auto parts = base::SplitStringOnce(corp_user_value, '@');
if (!parts) {
params.username = std::move(corp_user_value);
} else {
params.username = std::move(parts->first);
}
if (command_line->HasSwitch(kDisplayNameSwitchName)) {
corp_arg_count++;
params.name = command_line->GetSwitchValueASCII(kDisplayNameSwitchName);
}
if (command_line->HasSwitch("v")) {
corp_arg_count++;
}
if (command_line->HasSwitch("vmodule")) {
corp_arg_count++;
}
if (command_line->GetSwitches().size() > corp_arg_count) {
return false;
}
return true;
}
bool InitializeCloudMachineParams(HostStarter::Params& params,
const base::CommandLine* command_line) {
size_t cloud_arg_count = 1;
params.owner_email = base::ToLowerASCII(
command_line->GetSwitchValueASCII(kCloudUserSwitchName));
if (command_line->HasSwitch(kDisplayNameSwitchName)) {
cloud_arg_count++;
params.name = command_line->GetSwitchValueASCII(kDisplayNameSwitchName);
}
if (command_line->HasSwitch(kCloudApiKeySwitchName)) {
params.api_key = command_line->GetSwitchValueASCII(kCloudApiKeySwitchName);
cloud_arg_count++;
}
bool has_disable_crash_reporting_switch =
command_line->HasSwitch(kDisableCrashReportingSwitchName);
params.enable_crash_reporting = !has_disable_crash_reporting_switch;
if (has_disable_crash_reporting_switch) {
cloud_arg_count++;
}
if (command_line->HasSwitch("v")) {
cloud_arg_count++;
}
if (command_line->HasSwitch("vmodule")) {
cloud_arg_count++;
}
if (command_line->GetSwitches().size() > cloud_arg_count) {
return false;
}
return true;
}
}
int StartHostMain(int argc, char** argv) {
#if BUILDFLAG(IS_LINUX)
if (getuid() == 0) {
return remoting::StartHostAsRoot(argc, argv);
}
#endif
base::CommandLine::Init(argc, argv);
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
base::AtExitManager exit_manager;
logging::LoggingSettings settings;
settings.logging_dest =
logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR;
logging::InitLogging(settings);
base::ThreadPoolInstance::CreateAndStartWithDefaultParams(
"RemotingHostSetup");
mojo::core::Init();
#if BUILDFLAG(IS_LINUX)
if (command_line->HasSwitch("no-start")) {
DaemonControllerDelegateLinux::set_start_host_after_setup(false);
command_line->RemoveSwitch("no-start");
}
#endif
#if BUILDFLAG(IS_WIN)
if (!base::IsCurrentProcessElevated()) {
UNSAFE_TODO(fprintf(stderr, "Error: %s must be run as an elevated process.",
argv[0]));
return 1;
}
#endif
if (command_line->HasSwitch("help") || command_line->HasSwitch("h") ||
command_line->HasSwitch("?") || !command_line->GetArgs().empty()) {
PrintDefaultHelpMessage(argv[0]);
return 1;
}
HostStarter::Params params;
bool use_corp_machine_flow = command_line->HasSwitch(kCorpUserSwitchName);
bool use_cloud_machine_flow = command_line->HasSwitch(kCloudUserSwitchName);
if (use_corp_machine_flow) {
if (!InitializeCorpMachineParams(params, command_line)) {
PrintCorpUserHelpMessage(argv[0]);
return 1;
}
} else if (use_cloud_machine_flow) {
if (!InitializeCloudMachineParams(params, command_line)) {
PrintCloudUserHelpMessage(argv[0]);
return 1;
}
} else if (!InitializeParamsForOAuthFlow(params, command_line)) {
PrintDefaultHelpMessage(argv[0]);
return 1;
}
#if defined(REMOTING_ENABLE_CRASH_REPORTING)
if (params.enable_crash_reporting) {
#if BUILDFLAG(IS_LINUX)
InitializeCrashpadReporting();
#elif BUILDFLAG(IS_WIN)
InitializeBreakpadReporting();
#endif
}
#endif
base::SingleThreadTaskExecutor main_thread_task_executor;
g_main_thread_task_executor = &main_thread_task_executor;
base::Thread::Options io_thread_options(base::MessagePumpType::IO, 0);
base::Thread io_thread("IO thread");
io_thread.StartWithOptions(std::move(io_thread_options));
scoped_refptr<net::URLRequestContextGetter> url_request_context_getter(
new remoting::URLRequestContextGetter(io_thread.task_runner()));
network::TransitionalURLLoaderFactoryOwner url_loader_factory_owner(
url_request_context_getter, use_corp_machine_flow);
std::unique_ptr<HostStarter> host_starter;
if (use_corp_machine_flow) {
host_starter =
ProvisionCorpMachine(url_loader_factory_owner.GetURLLoaderFactory(),
CreateClientCertStoreInstance());
} else if (use_cloud_machine_flow) {
fprintf(stdout,
"*** Warning: This workflow is experimental and not fully "
"supported at this time ***\n");
host_starter =
ProvisionCloudInstance(url_loader_factory_owner.GetURLLoaderFactory());
} else {
host_starter =
CreateOAuthHostStarter(url_loader_factory_owner.GetURLLoaderFactory());
}
host_starter->StartHost(std::move(params), base::BindOnce(&OnDone));
base::RunLoop run_loop;
g_active_run_loop = &run_loop;
run_loop.Run();
g_main_thread_task_executor = nullptr;
g_active_run_loop = nullptr;
host_starter.reset();
url_request_context_getter = nullptr;
io_thread.Stop();
return g_started ? 0 : 1;
}
}