sas_injector_win.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/utf_string_conversions.h"
15#include "base/win/registry.h"
16#include "base/win/windows_version.h"
17#include "media/video/capture/screen/win/desktop.h"
18#include "media/video/capture/screen/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      LOG_GETLASTERROR(ERROR)
69          << "Failed to restore the software SAS generation policy";
70    }
71  }
72}
73
74bool ScopedSoftwareSasPolicy::Apply() {
75  // Query the currently set SoftwareSASGeneration policy.
76  LONG result = system_policy_.Open(HKEY_LOCAL_MACHINE,
77                                    kSystemPolicyKeyName,
78                                    KEY_QUERY_VALUE | KEY_SET_VALUE |
79                                        KEY_WOW64_64KEY);
80  if (result != ERROR_SUCCESS) {
81    SetLastError(result);
82    LOG_GETLASTERROR(ERROR) << "Failed to open 'HKLM\\"
83                            << kSystemPolicyKeyName << "'";
84    return false;
85  }
86
87  bool custom_policy = system_policy_.HasValue(kSoftwareSasValueName);
88
89  // Override the default policy (i.e. there is no value in the registry) only.
90  if (!custom_policy) {
91    result = system_policy_.WriteValue(kSoftwareSasValueName,
92                                       kEnableSoftwareSasByServices);
93    if (result != ERROR_SUCCESS) {
94      SetLastError(result);
95      LOG_GETLASTERROR(ERROR)
96          << "Failed to enable software SAS generation by services";
97      return false;
98    } else {
99      restore_policy_ = true;
100    }
101  }
102
103  return true;
104}
105
106} // namespace
107
108// Sends Secure Attention Sequence using the SendSAS() function from sas.dll.
109// This library is shipped starting from Win7/W2K8 R2 only. However Win7 SDK
110// includes a redistributable verion of the same library that works on
111// Vista/W2K8. We install the latter along with our binaries.
112class SasInjectorWin : public SasInjector {
113 public:
114  SasInjectorWin();
115  virtual ~SasInjectorWin();
116
117  // SasInjector implementation.
118  virtual bool InjectSas() OVERRIDE;
119
120 private:
121  base::ScopedNativeLibrary sas_dll_;
122  SendSasFunc send_sas_;
123};
124
125// Emulates Secure Attention Sequence (Ctrl+Alt+Del) by switching to
126// the Winlogon desktop and injecting Ctrl+Alt+Del as a hot key.
127// N.B. Windows XP/W2K3 only.
128class SasInjectorXp : public SasInjector {
129 public:
130  SasInjectorXp();
131  virtual ~SasInjectorXp();
132
133  // SasInjector implementation.
134  virtual bool InjectSas() OVERRIDE;
135};
136
137SasInjectorWin::SasInjectorWin() : send_sas_(NULL) {
138}
139
140SasInjectorWin::~SasInjectorWin() {
141}
142
143bool SasInjectorWin::InjectSas() {
144  // Load sas.dll. The library is expected to be in the same folder as this
145  // binary.
146  if (!sas_dll_.is_valid()) {
147    base::FilePath dir_path;
148    if (!PathService::Get(base::DIR_EXE, &dir_path)) {
149      LOG(ERROR) << "Failed to get the executable file name.";
150      return false;
151    }
152
153    sas_dll_.Reset(base::LoadNativeLibrary(dir_path.Append(kSasDllFileName),
154                                           NULL));
155  }
156  if (!sas_dll_.is_valid()) {
157    LOG(ERROR) << "Failed to load '" << kSasDllFileName << "'";
158    return false;
159  }
160
161  // Get the pointer to sas!SendSAS().
162  if (send_sas_ == NULL) {
163    send_sas_ = static_cast<SendSasFunc>(
164        sas_dll_.GetFunctionPointer(kSendSasName));
165  }
166  if (send_sas_ == NULL) {
167    LOG(ERROR) << "Failed to retrieve the address of '" << kSendSasName
168               << "()'";
169    return false;
170  }
171
172  // Enable software SAS generation by services and send SAS. SAS can still fail
173  // if the policy does not allow services to generate software SAS.
174  ScopedSoftwareSasPolicy enable_sas;
175  if (!enable_sas.Apply())
176    return false;
177
178  (*send_sas_)(FALSE);
179  return true;
180}
181
182SasInjectorXp::SasInjectorXp() {
183}
184
185SasInjectorXp::~SasInjectorXp() {
186}
187
188bool SasInjectorXp::InjectSas() {
189  const wchar_t kWinlogonDesktopName[] = L"Winlogon";
190  const wchar_t kSasWindowClassName[] = L"SAS window class";
191  const wchar_t kSasWindowTitle[] = L"SAS window";
192
193  scoped_ptr<media::Desktop> winlogon_desktop(
194      media::Desktop::GetDesktop(kWinlogonDesktopName));
195  if (!winlogon_desktop.get()) {
196    LOG_GETLASTERROR(ERROR)
197        << "Failed to open '" << kWinlogonDesktopName << "' desktop";
198    return false;
199  }
200
201  media::ScopedThreadDesktop desktop;
202  if (!desktop.SetThreadDesktop(winlogon_desktop.Pass())) {
203    LOG_GETLASTERROR(ERROR)
204        << "Failed to switch to '" << kWinlogonDesktopName << "' desktop";
205    return false;
206  }
207
208  HWND window = FindWindow(kSasWindowClassName, kSasWindowTitle);
209  if (!window) {
210    LOG_GETLASTERROR(ERROR)
211        << "Failed to find '" << kSasWindowTitle << "' window";
212    return false;
213  }
214
215  if (PostMessage(window,
216                  WM_HOTKEY,
217                  0,
218                  MAKELONG(MOD_ALT | MOD_CONTROL, VK_DELETE)) == 0) {
219    LOG_GETLASTERROR(ERROR)
220        << "Failed to post WM_HOTKEY message";
221    return false;
222  }
223
224  return true;
225}
226
227scoped_ptr<SasInjector> SasInjector::Create() {
228  if (base::win::GetVersion() < base::win::VERSION_VISTA) {
229    return scoped_ptr<SasInjector>(new SasInjectorXp());
230  } else {
231    return scoped_ptr<SasInjector>(new SasInjectorWin());
232  }
233}
234
235} // namespace remoting
236