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

#include "remoting/host/setup/me2me_native_messaging_host_main.h"

#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/i18n/icu_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "net/url_request/url_fetcher.h"
#include "remoting/base/breakpad.h"
#include "remoting/host/host_exit_codes.h"
#include "remoting/host/logging.h"
#include "remoting/host/pairing_registry_delegate.h"
#include "remoting/host/setup/me2me_native_messaging_host.h"
#include "remoting/host/usage_stats_consent.h"

#if defined(OS_MACOSX)
#include "base/mac/scoped_nsautorelease_pool.h"
#endif  // defined(OS_MACOSX)

#if defined(OS_WIN)
#include "base/win/registry.h"
#include "base/win/windows_version.h"
#include "remoting/host/pairing_registry_delegate_win.h"
#endif  // defined(OS_WIN)

using remoting::protocol::PairingRegistry;

namespace {

const char kParentWindowSwitchName[] = "parent-window";

}  // namespace

namespace remoting {

#if defined(OS_WIN)
bool IsProcessElevated() {
  // Conceptually, all processes running on a pre-VISTA version of Windows can
  // be considered "elevated".
  if (base::win::GetVersion() < base::win::VERSION_VISTA)
    return true;

  HANDLE process_token;
  OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &process_token);

  base::win::ScopedHandle scoped_process_token(process_token);

  // Unlike TOKEN_ELEVATION_TYPE which returns TokenElevationTypeDefault when
  // UAC is turned off, TOKEN_ELEVATION will tell you the process is elevated.
  DWORD size;
  TOKEN_ELEVATION elevation;
  GetTokenInformation(process_token, TokenElevation,
                      &elevation, sizeof(elevation), &size);
  return elevation.TokenIsElevated != 0;
}
#endif  // defined(OS_WIN)

int StartMe2MeNativeMessagingHost() {
#if defined(OS_MACOSX)
  // Needed so we don't leak objects when threads are created.
  base::mac::ScopedNSAutoreleasePool pool;
#endif  // defined(OS_MACOSX)

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

#if defined(REMOTING_ENABLE_BREAKPAD)
  // Initialize Breakpad 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.
  if (IsUsageStatsAllowed()) {
    InitializeCrashReporting();
  }
#endif  // defined(REMOTING_ENABLE_BREAKPAD)

  // Mac OS X requires that the main thread be a UI message loop in order to
  // receive distributed notifications from the System Preferences pane. An
  // IO thread is needed for the pairing registry and URL context getter.
  base::Thread io_thread("io_thread");
  io_thread.StartWithOptions(
      base::Thread::Options(base::MessageLoop::TYPE_IO, 0));

  base::MessageLoopForUI message_loop;
  base::RunLoop run_loop;

  scoped_refptr<DaemonController> daemon_controller =
      DaemonController::Create();

  // Pass handle of the native view to the controller so that the UAC prompts
  // are focused properly.
  const base::CommandLine* command_line =
      base::CommandLine::ForCurrentProcess();
  int64 native_view_handle = 0;
  if (command_line->HasSwitch(kParentWindowSwitchName)) {
    std::string native_view =
        command_line->GetSwitchValueASCII(kParentWindowSwitchName);
    if (base::StringToInt64(native_view, &native_view_handle)) {
      daemon_controller->SetWindow(reinterpret_cast<void*>(native_view_handle));
    } else {
      LOG(WARNING) << "Invalid parameter value --" << kParentWindowSwitchName
                   << "=" << native_view;
    }
  }

  base::File read_file;
  base::File write_file;
  bool needs_elevation = false;

#if defined(OS_WIN)
  needs_elevation = !IsProcessElevated();

  if (command_line->HasSwitch(kElevatingSwitchName)) {
    DCHECK(!needs_elevation);

    // The "elevate" switch is always accompanied by the "input" and "output"
    // switches whose values name named pipes that should be used in place of
    // stdin and stdout.
    DCHECK(command_line->HasSwitch(kInputSwitchName));
    DCHECK(command_line->HasSwitch(kOutputSwitchName));

    // presubmit: allow wstring
    std::wstring input_pipe_name =
      command_line->GetSwitchValueNative(kInputSwitchName);
    // presubmit: allow wstring
    std::wstring output_pipe_name =
      command_line->GetSwitchValueNative(kOutputSwitchName);

    // A NULL SECURITY_ATTRIBUTES signifies that the handle can't be inherited
    read_file = base::File(CreateFile(
        input_pipe_name.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL, NULL));
    if (!read_file.IsValid()) {
      PLOG(ERROR) << "CreateFile failed on '" << input_pipe_name << "'";
      return kInitializationFailed;
    }

    write_file = base::File(CreateFile(
        output_pipe_name.c_str(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL, NULL));
    if (!write_file.IsValid()) {
      PLOG(ERROR) << "CreateFile failed on '" << output_pipe_name << "'";
      return kInitializationFailed;
    }
  } else {
    // GetStdHandle() returns pseudo-handles for stdin and stdout even if
    // the hosting executable specifies "Windows" subsystem. However the
    // returned handles are invalid in that case unless standard input and
    // output are redirected to a pipe or file.
    read_file = base::File(GetStdHandle(STD_INPUT_HANDLE));
    write_file = base::File(GetStdHandle(STD_OUTPUT_HANDLE));

    // After the native messaging channel starts the native messaging reader
    // will keep doing blocking read operations on the input named pipe.
    // If any other thread tries to perform any operation on STDIN, it will also
    // block because the input named pipe is synchronous (non-overlapped).
    // It is pretty common for a DLL to query the device info (GetFileType) of
    // the STD* handles at startup. So any LoadLibrary request can potentially
    // be blocked. To prevent that from happening we close STDIN and STDOUT
    // handles as soon as we retrieve the corresponding file handles.
    SetStdHandle(STD_INPUT_HANDLE, NULL);
    SetStdHandle(STD_OUTPUT_HANDLE, NULL);
  }
#elif defined(OS_POSIX)
  // The files will be automatically closed.
  read_file = base::File(STDIN_FILENO);
  write_file = base::File(STDOUT_FILENO);
#else
#error Not implemented.
#endif

  // OAuth client (for credential requests).
  scoped_refptr<net::URLRequestContextGetter> url_request_context_getter(
      new URLRequestContextGetter(io_thread.message_loop_proxy()));
  scoped_ptr<OAuthClient> oauth_client(
      new OAuthClient(url_request_context_getter));

  net::URLFetcher::SetIgnoreCertificateRequests(true);

  // Create the pairing registry.
  scoped_refptr<PairingRegistry> pairing_registry;

#if defined(OS_WIN)
  base::win::RegKey root;
  LONG result = root.Open(HKEY_LOCAL_MACHINE, kPairingRegistryKeyName,
                          KEY_READ);
  if (result != ERROR_SUCCESS) {
    SetLastError(result);
    PLOG(ERROR) << "Failed to open HKLM\\" << kPairingRegistryKeyName;
    return kInitializationFailed;
  }

  base::win::RegKey unprivileged;
  result = unprivileged.Open(root.Handle(), kPairingRegistrySecretsKeyName,
                             needs_elevation ? KEY_READ : KEY_READ | KEY_WRITE);
  if (result != ERROR_SUCCESS) {
    SetLastError(result);
    PLOG(ERROR) << "Failed to open HKLM\\" << kPairingRegistrySecretsKeyName
                << "\\" << kPairingRegistrySecretsKeyName;
    return kInitializationFailed;
  }

  // Only try to open the privileged key if the current process is elevated.
  base::win::RegKey privileged;
  if (!needs_elevation) {
    result = privileged.Open(root.Handle(), kPairingRegistryClientsKeyName,
                             KEY_READ | KEY_WRITE);
    if (result != ERROR_SUCCESS) {
      SetLastError(result);
      PLOG(ERROR) << "Failed to open HKLM\\" << kPairingRegistryKeyName << "\\"
                  << kPairingRegistryClientsKeyName;
      return kInitializationFailed;
    }
  }

  // Initialize the pairing registry delegate and set the root keys.
  scoped_ptr<PairingRegistryDelegateWin> delegate(
      new PairingRegistryDelegateWin());
  if (!delegate->SetRootKeys(privileged.Take(), unprivileged.Take()))
    return kInitializationFailed;

  pairing_registry = new PairingRegistry(
      io_thread.message_loop_proxy(),
      delegate.PassAs<PairingRegistry::Delegate>());
#else  // defined(OS_WIN)
  pairing_registry =
      CreatePairingRegistry(io_thread.message_loop_proxy());
#endif  // !defined(OS_WIN)

  // Set up the native messaging channel.
  scoped_ptr<NativeMessagingChannel> channel(
      new NativeMessagingChannel(read_file.Pass(), write_file.Pass()));

  // Create the native messaging host.
  scoped_ptr<Me2MeNativeMessagingHost> host(
      new Me2MeNativeMessagingHost(
          needs_elevation,
          static_cast<intptr_t>(native_view_handle),
          channel.Pass(),
          daemon_controller,
          pairing_registry,
          oauth_client.Pass()));
  host->Start(run_loop.QuitClosure());

  // Run the loop until channel is alive.
  run_loop.Run();
  return kSuccessExitCode;
}

int Me2MeNativeMessagingHostMain(int argc, char** argv) {
  // This object instance is required by Chrome code (such as MessageLoop).
  base::AtExitManager exit_manager;

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

  return StartMe2MeNativeMessagingHost();
}

}  // namespace remoting
