// Copyright (c) 2012 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/sas_injector.h"

#include <windows.h>
#include <string>

#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/scoped_native_library.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/registry.h"
#include "base/win/windows_version.h"
#include "third_party/webrtc/modules/desktop_capture/win/desktop.h"
#include "third_party/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h"

namespace remoting {

namespace {

// Names of the API and library implementing software SAS generation.
const base::FilePath::CharType kSasDllFileName[] = FILE_PATH_LITERAL("sas.dll");
const char kSendSasName[] = "SendSAS";

// The prototype of SendSAS().
typedef VOID (WINAPI *SendSasFunc)(BOOL);

// The registry key and value holding the policy controlling software SAS
// generation.
const wchar_t kSystemPolicyKeyName[] =
    L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
const wchar_t kSoftwareSasValueName[] = L"SoftwareSASGeneration";

const DWORD kEnableSoftwareSasByServices = 1;

// Toggles the default software SAS generation policy to enable SAS generation
// by services. Non-default policy is not changed.
class ScopedSoftwareSasPolicy {
 public:
  ScopedSoftwareSasPolicy();
  ~ScopedSoftwareSasPolicy();

  bool Apply();

 private:
  // The handle of the registry key were SoftwareSASGeneration policy is stored.
  base::win::RegKey system_policy_;

  // True if the policy needs to be restored.
  bool restore_policy_;

  DISALLOW_COPY_AND_ASSIGN(ScopedSoftwareSasPolicy);
};

ScopedSoftwareSasPolicy::ScopedSoftwareSasPolicy()
    : restore_policy_(false) {
}

ScopedSoftwareSasPolicy::~ScopedSoftwareSasPolicy() {
  // Restore the default policy by deleting the value that we have set.
  if (restore_policy_) {
    LONG result = system_policy_.DeleteValue(kSoftwareSasValueName);
    if (result != ERROR_SUCCESS) {
      SetLastError(result);
      LOG_GETLASTERROR(ERROR)
          << "Failed to restore the software SAS generation policy";
    }
  }
}

bool ScopedSoftwareSasPolicy::Apply() {
  // Query the currently set SoftwareSASGeneration policy.
  LONG result = system_policy_.Open(HKEY_LOCAL_MACHINE,
                                    kSystemPolicyKeyName,
                                    KEY_QUERY_VALUE | KEY_SET_VALUE |
                                        KEY_WOW64_64KEY);
  if (result != ERROR_SUCCESS) {
    SetLastError(result);
    LOG_GETLASTERROR(ERROR) << "Failed to open 'HKLM\\"
                            << kSystemPolicyKeyName << "'";
    return false;
  }

  bool custom_policy = system_policy_.HasValue(kSoftwareSasValueName);

  // Override the default policy (i.e. there is no value in the registry) only.
  if (!custom_policy) {
    result = system_policy_.WriteValue(kSoftwareSasValueName,
                                       kEnableSoftwareSasByServices);
    if (result != ERROR_SUCCESS) {
      SetLastError(result);
      LOG_GETLASTERROR(ERROR)
          << "Failed to enable software SAS generation by services";
      return false;
    } else {
      restore_policy_ = true;
    }
  }

  return true;
}

} // namespace

// Sends Secure Attention Sequence using the SendSAS() function from sas.dll.
// This library is shipped starting from Win7/W2K8 R2 only. However Win7 SDK
// includes a redistributable verion of the same library that works on
// Vista/W2K8. We install the latter along with our binaries.
class SasInjectorWin : public SasInjector {
 public:
  SasInjectorWin();
  virtual ~SasInjectorWin();

  // SasInjector implementation.
  virtual bool InjectSas() OVERRIDE;

 private:
  base::ScopedNativeLibrary sas_dll_;
  SendSasFunc send_sas_;
};

// Emulates Secure Attention Sequence (Ctrl+Alt+Del) by switching to
// the Winlogon desktop and injecting Ctrl+Alt+Del as a hot key.
// N.B. Windows XP/W2K3 only.
class SasInjectorXp : public SasInjector {
 public:
  SasInjectorXp();
  virtual ~SasInjectorXp();

  // SasInjector implementation.
  virtual bool InjectSas() OVERRIDE;
};

SasInjectorWin::SasInjectorWin() : send_sas_(NULL) {
}

SasInjectorWin::~SasInjectorWin() {
}

bool SasInjectorWin::InjectSas() {
  // Load sas.dll. The library is expected to be in the same folder as this
  // binary.
  if (!sas_dll_.is_valid()) {
    base::FilePath dir_path;
    if (!PathService::Get(base::DIR_EXE, &dir_path)) {
      LOG(ERROR) << "Failed to get the executable file name.";
      return false;
    }

    sas_dll_.Reset(base::LoadNativeLibrary(dir_path.Append(kSasDllFileName),
                                           NULL));
  }
  if (!sas_dll_.is_valid()) {
    LOG(ERROR) << "Failed to load '" << kSasDllFileName << "'";
    return false;
  }

  // Get the pointer to sas!SendSAS().
  if (send_sas_ == NULL) {
    send_sas_ = static_cast<SendSasFunc>(
        sas_dll_.GetFunctionPointer(kSendSasName));
  }
  if (send_sas_ == NULL) {
    LOG(ERROR) << "Failed to retrieve the address of '" << kSendSasName
               << "()'";
    return false;
  }

  // Enable software SAS generation by services and send SAS. SAS can still fail
  // if the policy does not allow services to generate software SAS.
  ScopedSoftwareSasPolicy enable_sas;
  if (!enable_sas.Apply())
    return false;

  (*send_sas_)(FALSE);
  return true;
}

SasInjectorXp::SasInjectorXp() {
}

SasInjectorXp::~SasInjectorXp() {
}

bool SasInjectorXp::InjectSas() {
  const wchar_t kWinlogonDesktopName[] = L"Winlogon";
  const wchar_t kSasWindowClassName[] = L"SAS window class";
  const wchar_t kSasWindowTitle[] = L"SAS window";

  scoped_ptr<webrtc::Desktop> winlogon_desktop(
      webrtc::Desktop::GetDesktop(kWinlogonDesktopName));
  if (!winlogon_desktop.get()) {
    LOG_GETLASTERROR(ERROR)
        << "Failed to open '" << kWinlogonDesktopName << "' desktop";
    return false;
  }

  webrtc::ScopedThreadDesktop desktop;
  if (!desktop.SetThreadDesktop(winlogon_desktop.release())) {
    LOG_GETLASTERROR(ERROR)
        << "Failed to switch to '" << kWinlogonDesktopName << "' desktop";
    return false;
  }

  HWND window = FindWindow(kSasWindowClassName, kSasWindowTitle);
  if (!window) {
    LOG_GETLASTERROR(ERROR)
        << "Failed to find '" << kSasWindowTitle << "' window";
    return false;
  }

  if (PostMessage(window,
                  WM_HOTKEY,
                  0,
                  MAKELONG(MOD_ALT | MOD_CONTROL, VK_DELETE)) == 0) {
    LOG_GETLASTERROR(ERROR)
        << "Failed to post WM_HOTKEY message";
    return false;
  }

  return true;
}

scoped_ptr<SasInjector> SasInjector::Create() {
  if (base::win::GetVersion() < base::win::VERSION_VISTA) {
    return scoped_ptr<SasInjector>(new SasInjectorXp());
  } else {
    return scoped_ptr<SasInjector>(new SasInjectorWin());
  }
}

} // namespace remoting