// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This file implements the common entry point shared by all Chromoting Host
// processes.

#include "remoting/host/host_main.h"

#include <string>

#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/files/file_path.h"
#include "base/i18n/icu_util.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/stringize_macros.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "mojo/core/embedder/embedder.h"
#include "remoting/base/buildflags.h"
#include "remoting/base/crash/crash_reporting_crashpad.h"
#include "remoting/base/logging.h"

#if BUILDFLAG(IS_LINUX)
#include <sys/stat.h>
#include <unistd.h>

#include "base/files/file_util.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/string_number_conversions.h"
#include "remoting/base/file_path_util_linux.h"
#endif  // BUILDFLAG(IS_LINUX)

#include "base/memory_coordinator/dummy_memory_consumer_registry.h"
#include "remoting/host/base/host_exit_codes.h"
#include "remoting/host/base/switches.h"
#include "remoting/host/evaluate_capability.h"
#include "remoting/host/resources.h"
#include "remoting/host/setup/me2me_native_messaging_host.h"
#include "remoting/host/usage_stats_consent.h"

#if BUILDFLAG(IS_APPLE)
#include "base/apple/scoped_nsautorelease_pool.h"
#endif  // BUILDFLAG(IS_APPLE)

#if BUILDFLAG(IS_WIN)
#include <windows.h>

#include <commctrl.h>
#include <shellapi.h>

#include "remoting/base/crash/crash_reporting_breakpad.h"
#endif  // BUILDFLAG(IS_WIN)

namespace remoting {

// Known entry points.
int SingleProcessHostProcessMain();
#if BUILDFLAG(REMOTING_MULTI_PROCESS)
int NetworkProcessMain();
int DaemonProcessMain();
int DesktopProcessMain();
int PeerConnectionProcessMain();
#endif
#if BUILDFLAG(IS_WIN)
int FileChooserMain();
int RdpDesktopSessionMain();
int UrlForwarderConfiguratorMain();
#endif  // BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_LINUX)
int XSessionChooserMain();
#endif  // BUILDFLAG(IS_LINUX)

namespace {

#if BUILDFLAG(IS_LINUX)
void EnsureVarLibDirectory() {
  if (getuid() != 0) {
    // Only do this in the daemon process, which is always run as root.
    return;
  }

  base::FilePath var_lib_dir = GetVarLibDir();
  if (base::PathExists(var_lib_dir) && !base::DirectoryExists(var_lib_dir)) {
    if (!base::DeletePathRecursively(var_lib_dir)) {
      PLOG(FATAL) << "Failed to delete non-directory " << var_lib_dir;
    }
  }
  base::File::Error error;
  if (!base::CreateDirectoryAndGetError(var_lib_dir, &error)) {
    LOG(FATAL) << "Failed to create " << var_lib_dir << ": "
               << base::File::ErrorToString(error);
  }
  // Allow other users to list and read files and directories, but not write to
  // it.
  if (HANDLE_EINTR(chmod(var_lib_dir.value().c_str(), 0755)) != 0) {
    PLOG(ERROR) << "Failed to chmod " << var_lib_dir;
    if (!base::DeletePathRecursively(var_lib_dir)) {
      PLOG(FATAL) << "Failed to delete " << var_lib_dir;
    }
  }
}
#endif  // BUILDFLAG(IS_LINUX)

typedef int (*MainRoutineFn)();

void Usage(const base::FilePath& program_name) {
  printf(
      "Usage: %s [options]\n"
      "\n"
      "Options:\n"

#if BUILDFLAG(IS_LINUX)
      "  --audio-pipe-name=<pipe> - Sets the pipe name to capture audio on "
      "Linux.\n"
#endif  // BUILDFLAG(IS_LINUX)

#if BUILDFLAG(IS_APPLE)
      "  --list-audio-devices     - List all audio devices and their device "
      "UID.\n"
#endif  // BUILDFLAG(IS_APPLE)

      "  --console                - Runs the daemon interactively.\n"
      "  --elevate=<binary>       - Runs <binary> elevated.\n"
      "  --host-config=<config>   - Specifies the host configuration.\n"
      "  --help, -?               - Prints this message.\n"
      "  --type                   - Specifies process type.\n"
      "  --version                - Prints the host version and exits.\n"
      "  --evaluate-type=<type>   - Evaluates the capability of the host.\n"
      "  --enable-wtmpdb          - Enables recording to wtmpdb on Linux.\n"
      "  --webrtc-trace-event-file=<path> - Enables logging webrtc trace events"
      " to a file.\n",
      program_name.MaybeAsASCII().c_str());
}

#if BUILDFLAG(IS_WIN)

// Runs the binary specified by the command line, elevated.
int RunElevated() {
  const base::CommandLine::SwitchMap& switches =
      base::CommandLine::ForCurrentProcess()->GetSwitches();
  base::CommandLine::StringVector args =
      base::CommandLine::ForCurrentProcess()->GetArgs();

  // Create the child process command line by copying switches from the current
  // command line.
  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
  for (base::CommandLine::SwitchMap::const_iterator i = switches.begin();
       i != switches.end(); ++i) {
    if (i->first != kElevateSwitchName) {
      command_line.AppendSwitchNative(i->first, i->second);
    }
  }
  for (base::CommandLine::StringVector::const_iterator i = args.begin();
       i != args.end(); ++i) {
    command_line.AppendArgNative(*i);
  }

  // Get the name of the binary to launch.
  base::FilePath binary =
      base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
          kElevateSwitchName);
  base::CommandLine::StringType parameters =
      command_line.GetCommandLineString();

  // Launch the child process requesting elevation.
  SHELLEXECUTEINFO info = {};
  info.cbSize = sizeof(info);
  info.lpVerb = L"runas";
  info.lpFile = binary.value().c_str();
  info.lpParameters = parameters.c_str();
  info.nShow = SW_SHOWNORMAL;

  if (!ShellExecuteEx(&info)) {
    DWORD exit_code = GetLastError();
    PLOG(ERROR) << "Unable to launch '" << binary.value() << "'";
    return exit_code;
  }

  return kSuccessExitCode;
}

#endif  // !BUILDFLAG(IS_WIN)

// Select the entry point corresponding to the process type.
MainRoutineFn SelectMainRoutine(const std::string& process_type) {
  MainRoutineFn main_routine = nullptr;

  if (process_type == kProcessTypeSingleProcessHost) {
    main_routine = &SingleProcessHostProcessMain;
#if BUILDFLAG(REMOTING_MULTI_PROCESS)
  } else if (process_type == kProcessTypeNetwork) {
    main_routine = &NetworkProcessMain;
  } else if (process_type == kProcessTypeDaemon) {
    main_routine = &DaemonProcessMain;
  } else if (process_type == kProcessTypeDesktop) {
    main_routine = &DesktopProcessMain;
  } else if (process_type == kProcessTypePeerConnection) {
    main_routine = &PeerConnectionProcessMain;
#endif
#if BUILDFLAG(IS_WIN)
  } else if (process_type == kProcessTypeFileChooser) {
    main_routine = &FileChooserMain;
  } else if (process_type == kProcessTypeRdpDesktopSession) {
    main_routine = &RdpDesktopSessionMain;
  } else if (process_type == kProcessTypeUrlForwarderConfigurator) {
    main_routine = &UrlForwarderConfiguratorMain;
#endif  // BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_LINUX)
  } else if (process_type == kProcessTypeXSessionChooser) {
    main_routine = &XSessionChooserMain;
#endif  // BUILDFLAG(IS_LINUX)
  }

  return main_routine;
}

}  // namespace

int HostMain(int argc, char** argv) {
  base::ScopedMemoryConsumerRegistry<base::DummyMemoryConsumerRegistry>
      memory_consumer_registry;

#if BUILDFLAG(IS_APPLE)
  // Needed so we don't leak objects when threads are created.
  base::apple::ScopedNSAutoreleasePool pool;
#endif

  base::CommandLine::Init(argc, argv);

  // Parse the command line.
  const base::CommandLine* command_line =
      base::CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(kHelpSwitchName) ||
      command_line->HasSwitch(kQuestionSwitchName)) {
    Usage(command_line->GetProgram());
    return kSuccessExitCode;
  }

  if (command_line->HasSwitch(kVersionSwitchName)) {
    printf("%s\n", STRINGIZE(VERSION));
    return kSuccessExitCode;
  }

#if BUILDFLAG(IS_WIN)
  if (command_line->HasSwitch(kElevateSwitchName)) {
    return RunElevated();
  }
#endif  // BUILDFLAG(IS_WIN)

  // Assume the single-process host process by default.
  std::string process_type = kProcessTypeSingleProcessHost;
  if (command_line->HasSwitch(kProcessTypeSwitchName)) {
    process_type = command_line->GetSwitchValueASCII(kProcessTypeSwitchName);
  }

  if (process_type == kProcessTypeEvaluateCapability) {
    if (command_line->HasSwitch(kEvaluateCapabilitySwitchName)) {
      return EvaluateCapabilityLocally(
          command_line->GetSwitchValueASCII(kEvaluateCapabilitySwitchName));
    }
    Usage(command_line->GetProgram());
    return kSuccessExitCode;
  }

  // This object instance is required by Chrome code (for example,
  // LazyInstance, MessageLoop).
  base::AtExitManager exit_manager;

  // Enable debug logs.
  InitHostLogging();

#if BUILDFLAG(IS_LINUX)
  EnsureVarLibDirectory();
#endif  // BUILDFLAG(IS_LINUX)

#if defined(REMOTING_ENABLE_CRASH_REPORTING)
  // Initialize crash reporting as early as possible. On Mac the command-line
  // needs to be initialized first, so that the preference for crash-reporting
  // can be looked up in the config file.
  // Note that we enable crash reporting only if the user has opted in to having
  // the crash reports uploaded.
  if (IsUsageStatsAllowed()) {
#if BUILDFLAG(IS_LINUX)
    if (command_line->HasSwitch(kCrashpadHandlerSocketFd)) {
      std::string fd_str =
          command_line->GetSwitchValueASCII(kCrashpadHandlerSocketFd);
      int fd = -1;
      if (base::StringToInt(fd_str, &fd) && fd >= 0) {
        pid_t pid = -1;
        if (command_line->HasSwitch(kCrashpadHandlerPid)) {
          std::string pid_str =
              command_line->GetSwitchValueASCII(kCrashpadHandlerPid);
          int pid_int = -1;
          if (base::StringToInt(pid_str, &pid_int)) {
            pid = static_cast<pid_t>(pid_int);
          }
        }
        if (!InitializeCrashpadClient(base::ScopedFD(fd), pid)) {
          LOG(ERROR) << "Failed to initialize Crashpad client.";
        }
      } else {
        LOG(ERROR) << "Invalid Crashpad handler socket switch values.";
      }
    } else {
      InitializeCrashpadReporting();
    }
#elif BUILDFLAG(IS_WIN)
    // TODO: joedow - Enable crash reporting for the RDP process.
    if (process_type == kProcessTypeDaemon) {
      InitializeBreakpadReporting();
    } else if (process_type == kProcessTypeDesktop) {
      // TODO(garykac): Switch to use InitializeCrashpadReporting();
      InitializeBreakpadReporting();
    } else if (command_line->HasSwitch(kCrashServerPipeHandle)) {
      InitializeOopCrashClient(
          command_line->GetSwitchValueASCII(kCrashServerPipeHandle));
    }
#endif
  }
#endif  // defined(REMOTING_ENABLE_CRASH_REPORTING)

#if BUILDFLAG(IS_WIN)
  // Register and initialize common controls.
  INITCOMMONCONTROLSEX info;
  info.dwSize = sizeof(info);
  info.dwICC = ICC_STANDARD_CLASSES;
  InitCommonControlsEx(&info);
#endif  // BUILDFLAG(IS_WIN)

  MainRoutineFn main_routine = SelectMainRoutine(process_type);
  if (!main_routine) {
    fprintf(stderr, "Unknown process type '%s' specified.",
            process_type.c_str());
    Usage(command_line->GetProgram());
    return kInvalidCommandLineExitCode;
  }

  // Required to find the ICU data file, used by some file_util routines.
  base::i18n::InitializeICU();

  remoting::LoadResources("");

  bool is_broker_process = false;
#if !BUILDFLAG(IS_MAC)
  // The single-process host process should act as the mojo broker, except on
  // Mac, where the broker process is the agent process broker.
  is_broker_process |= main_routine == &SingleProcessHostProcessMain;
#endif
#if BUILDFLAG(REMOTING_MULTI_PROCESS)
  // For multi-process hosts, the daemon process acts as the broker.
  is_broker_process |= main_routine == &DaemonProcessMain;
#endif

  mojo::core::Init({.is_broker_process = is_broker_process});

  // Invoke the entry point.
  int exit_code = main_routine();
  if (exit_code == kInvalidCommandLineExitCode) {
    Usage(command_line->GetProgram());
  }

  remoting::UnloadResources();

  return exit_code;
}

}  // namespace remoting
