1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "remoting/host/sas_injector.h"
6
7#include <windows.h>
8#include <string>
9
10#include "base/files/file_path.h"
11#include "base/logging.h"
12#include "base/path_service.h"
13#include "base/scoped_native_library.h"
14#include "base/strings/utf_string_conversions.h"
15#include "base/win/registry.h"
16#include "base/win/windows_version.h"
17#include "third_party/webrtc/modules/desktop_capture/win/desktop.h"
18#include "third_party/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h"
19
20namespace remoting {
21
22namespace {
23
24// Names of the API and library implementing software SAS generation.
25const base::FilePath::CharType kSasDllFileName[] = FILE_PATH_LITERAL("sas.dll");
26const char kSendSasName[] = "SendSAS";
27
28// The prototype of SendSAS().
29typedef VOID (WINAPI *SendSasFunc)(BOOL);
30
31// The registry key and value holding the policy controlling software SAS
32// generation.
33const wchar_t kSystemPolicyKeyName[] =
34    L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
35const wchar_t kSoftwareSasValueName[] = L"SoftwareSASGeneration";
36
37const DWORD kEnableSoftwareSasByServices = 1;
38
39// Toggles the default software SAS generation policy to enable SAS generation
40// by services. Non-default policy is not changed.
41class ScopedSoftwareSasPolicy {
42 public:
43  ScopedSoftwareSasPolicy();
44  ~ScopedSoftwareSasPolicy();
45
46  bool Apply();
47
48 private:
49  // The handle of the registry key were SoftwareSASGeneration policy is stored.
50  base::win::RegKey system_policy_;
51
52  // True if the policy needs to be restored.
53  bool restore_policy_;
54
55  DISALLOW_COPY_AND_ASSIGN(ScopedSoftwareSasPolicy);
56};
57
58ScopedSoftwareSasPolicy::ScopedSoftwareSasPolicy()
59    : restore_policy_(false) {
60}
61
62ScopedSoftwareSasPolicy::~ScopedSoftwareSasPolicy() {
63  // Restore the default policy by deleting the value that we have set.
64  if (restore_policy_) {
65    LONG result = system_policy_.DeleteValue(kSoftwareSasValueName);
66    if (result != ERROR_SUCCESS) {
67      SetLastError(result);
68      PLOG(ERROR) << "Failed to restore the software SAS generation policy";
69    }
70  }
71}
72
73bool ScopedSoftwareSasPolicy::Apply() {
74  // Query the currently set SoftwareSASGeneration policy.
75  LONG result = system_policy_.Open(HKEY_LOCAL_MACHINE,
76                                    kSystemPolicyKeyName,
77                                    KEY_QUERY_VALUE | KEY_SET_VALUE |
78                                        KEY_WOW64_64KEY);
79  if (result != ERROR_SUCCESS) {
80    SetLastError(result);
81    PLOG(ERROR) << "Failed to open 'HKLM\\" << kSystemPolicyKeyName << "'";
82    return false;
83  }
84
85  bool custom_policy = system_policy_.HasValue(kSoftwareSasValueName);
86
87  // Override the default policy (i.e. there is no value in the registry) only.
88  if (!custom_policy) {
89    result = system_policy_.WriteValue(kSoftwareSasValueName,
90                                       kEnableSoftwareSasByServices);
91    if (result != ERROR_SUCCESS) {
92      SetLastError(result);
93      PLOG(ERROR) << "Failed to enable software SAS generation by services";
94      return false;
95    } else {
96      restore_policy_ = true;
97    }
98  }
99
100  return true;
101}
102
103} // namespace
104
105// Sends Secure Attention Sequence using the SendSAS() function from sas.dll.
106// This library is shipped starting from Win7/W2K8 R2 only. However Win7 SDK
107// includes a redistributable verion of the same library that works on
108// Vista/W2K8. We install the latter along with our binaries.
109class SasInjectorWin : public SasInjector {
110 public:
111  SasInjectorWin();
112  virtual ~SasInjectorWin();
113
114  // SasInjector implementation.
115  virtual bool InjectSas() OVERRIDE;
116
117 private:
118  base::ScopedNativeLibrary sas_dll_;
119  SendSasFunc send_sas_;
120};
121
122// Emulates Secure Attention Sequence (Ctrl+Alt+Del) by switching to
123// the Winlogon desktop and injecting Ctrl+Alt+Del as a hot key.
124// N.B. Windows XP/W2K3 only.
125class SasInjectorXp : public SasInjector {
126 public:
127  SasInjectorXp();
128  virtual ~SasInjectorXp();
129
130  // SasInjector implementation.
131  virtual bool InjectSas() OVERRIDE;
132};
133
134SasInjectorWin::SasInjectorWin() : send_sas_(NULL) {
135}
136
137SasInjectorWin::~SasInjectorWin() {
138}
139
140bool SasInjectorWin::InjectSas() {
141  // Load sas.dll. The library is expected to be in the same folder as this
142  // binary.
143  if (!sas_dll_.is_valid()) {
144    base::FilePath dir_path;
145    if (!PathService::Get(base::DIR_EXE, &dir_path)) {
146      LOG(ERROR) << "Failed to get the executable file name.";
147      return false;
148    }
149
150    sas_dll_.Reset(base::LoadNativeLibrary(dir_path.Append(kSasDllFileName),
151                                           NULL));
152  }
153  if (!sas_dll_.is_valid()) {
154    LOG(ERROR) << "Failed to load '" << kSasDllFileName << "'";
155    return false;
156  }
157
158  // Get the pointer to sas!SendSAS().
159  if (send_sas_ == NULL) {
160    send_sas_ = reinterpret_cast<SendSasFunc>(
161        sas_dll_.GetFunctionPointer(kSendSasName));
162  }
163  if (send_sas_ == NULL) {
164    LOG(ERROR) << "Failed to retrieve the address of '" << kSendSasName
165               << "()'";
166    return false;
167  }
168
169  // Enable software SAS generation by services and send SAS. SAS can still fail
170  // if the policy does not allow services to generate software SAS.
171  ScopedSoftwareSasPolicy enable_sas;
172  if (!enable_sas.Apply())
173    return false;
174
175  (*send_sas_)(FALSE);
176  return true;
177}
178
179SasInjectorXp::SasInjectorXp() {
180}
181
182SasInjectorXp::~SasInjectorXp() {
183}
184
185bool SasInjectorXp::InjectSas() {
186  const wchar_t kWinlogonDesktopName[] = L"Winlogon";
187  const wchar_t kSasWindowClassName[] = L"SAS window class";
188  const wchar_t kSasWindowTitle[] = L"SAS window";
189
190  scoped_ptr<webrtc::Desktop> winlogon_desktop(
191      webrtc::Desktop::GetDesktop(kWinlogonDesktopName));
192  if (!winlogon_desktop.get()) {
193    PLOG(ERROR) << "Failed to open '" << kWinlogonDesktopName << "' desktop";
194    return false;
195  }
196
197  webrtc::ScopedThreadDesktop desktop;
198  if (!desktop.SetThreadDesktop(winlogon_desktop.release())) {
199    PLOG(ERROR) << "Failed to switch to '" << kWinlogonDesktopName
200                << "' desktop";
201    return false;
202  }
203
204  HWND window = FindWindow(kSasWindowClassName, kSasWindowTitle);
205  if (!window) {
206    PLOG(ERROR) << "Failed to find '" << kSasWindowTitle << "' window";
207    return false;
208  }
209
210  if (PostMessage(window,
211                  WM_HOTKEY,
212                  0,
213                  MAKELONG(MOD_ALT | MOD_CONTROL, VK_DELETE)) == 0) {
214    PLOG(ERROR) << "Failed to post WM_HOTKEY message";
215    return false;
216  }
217
218  return true;
219}
220
221scoped_ptr<SasInjector> SasInjector::Create() {
222  if (base::win::GetVersion() < base::win::VERSION_VISTA) {
223    return scoped_ptr<SasInjector>(new SasInjectorXp());
224  } else {
225    return scoped_ptr<SasInjector>(new SasInjectorWin());
226  }
227}
228
229} // namespace remoting
230