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
20 namespace remoting {
21
22 namespace {
23
24 // Names of the API and library implementing software SAS generation.
25 const base::FilePath::CharType kSasDllFileName[] = FILE_PATH_LITERAL("sas.dll");
26 const char kSendSasName[] = "SendSAS";
27
28 // The prototype of SendSAS().
29 typedef VOID (WINAPI *SendSasFunc)(BOOL);
30
31 // The registry key and value holding the policy controlling software SAS
32 // generation.
33 const wchar_t kSystemPolicyKeyName[] =
34 L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
35 const wchar_t kSoftwareSasValueName[] = L"SoftwareSASGeneration";
36
37 const 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.
41 class 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
ScopedSoftwareSasPolicy()58 ScopedSoftwareSasPolicy::ScopedSoftwareSasPolicy()
59 : restore_policy_(false) {
60 }
61
~ScopedSoftwareSasPolicy()62 ScopedSoftwareSasPolicy::~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
Apply()74 bool 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.
112 class 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.
128 class SasInjectorXp : public SasInjector {
129 public:
130 SasInjectorXp();
131 virtual ~SasInjectorXp();
132
133 // SasInjector implementation.
134 virtual bool InjectSas() OVERRIDE;
135 };
136
SasInjectorWin()137 SasInjectorWin::SasInjectorWin() : send_sas_(NULL) {
138 }
139
~SasInjectorWin()140 SasInjectorWin::~SasInjectorWin() {
141 }
142
InjectSas()143 bool 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
SasInjectorXp()182 SasInjectorXp::SasInjectorXp() {
183 }
184
~SasInjectorXp()185 SasInjectorXp::~SasInjectorXp() {
186 }
187
InjectSas()188 bool 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<webrtc::Desktop> winlogon_desktop(
194 webrtc::Desktop::GetDesktop(kWinlogonDesktopName));
195 if (!winlogon_desktop.get()) {
196 LOG_GETLASTERROR(ERROR)
197 << "Failed to open '" << kWinlogonDesktopName << "' desktop";
198 return false;
199 }
200
201 webrtc::ScopedThreadDesktop desktop;
202 if (!desktop.SetThreadDesktop(winlogon_desktop.release())) {
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
Create()227 scoped_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