1// Copyright (c) 2011 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// chrome_frame_helper_main.cc : The .exe that bootstraps the
6// chrome_frame_helper.dll.
7//
8// This is a small exe that loads the hook dll to set the event hooks and then
9// waits in a message loop. It is intended to be shut down by looking for a
10// window with the class and title
11// kChromeFrameHelperWindowClassName and kChromeFrameHelperWindowName and then
12// sending that window a WM_CLOSE message.
13//
14
15#include <crtdbg.h>
16#include <objbase.h>
17#include <stdlib.h>
18#include <windows.h>
19
20#include "chrome_frame/chrome_frame_helper_util.h"
21#include "chrome_frame/crash_server_init.h"
22#include "chrome_frame/registry_watcher.h"
23
24namespace {
25
26// Window class and window names.
27const wchar_t kChromeFrameHelperWindowClassName[] =
28    L"ChromeFrameHelperWindowClass";
29const wchar_t kChromeFrameHelperWindowName[] =
30    L"ChromeFrameHelperWindowName";
31
32const wchar_t kChromeFrameClientStateKey[] =
33    L"Software\\Google\\Update\\ClientState\\"
34    L"{8BA986DA-5100-405E-AA35-86F34A02ACBF}";
35const wchar_t kChromeFrameUninstallCmdExeValue[] = L"UninstallString";
36const wchar_t kChromeFrameUninstallCmdArgsValue[] = L"UninstallArguments";
37
38const wchar_t kBHORegistrationPath[] =
39    L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer"
40    L"\\Browser Helper Objects";
41
42const wchar_t kStartupArg[] = L"--startup";
43const wchar_t kForceUninstall[] = L"--force-uninstall";
44
45HWND g_hwnd = NULL;
46const UINT kRegistryWatcherChangeMessage = WM_USER + 42;
47
48}  // namespace
49
50// Small helper class that assists in loading the DLL that contains code
51// to register our event hook callback. Automatically removes the hook and
52// unloads the DLL on destruction.
53class HookDllLoader {
54 public:
55  HookDllLoader() : dll_(NULL), start_proc_(NULL), stop_proc_(NULL) {}
56  ~HookDllLoader() {
57    if (dll_) {
58      Unload();
59    }
60  }
61
62  bool Load() {
63    dll_ = LoadLibrary(L"chrome_frame_helper.dll");
64    if (dll_) {
65      start_proc_ = GetProcAddress(dll_, "StartUserModeBrowserInjection");
66      stop_proc_ = GetProcAddress(dll_, "StopUserModeBrowserInjection");
67    }
68
69    bool result = true;
70    if (!start_proc_ || !stop_proc_) {
71      _ASSERTE(L"failed to load hook dll.");
72      result = false;
73    } else {
74      if (FAILED(start_proc_())) {
75        _ASSERTE(L"failed to initialize hook dll.");
76        result = false;
77      }
78    }
79    return result;
80  }
81
82  void Unload() {
83    if (stop_proc_) {
84      stop_proc_();
85    }
86    if (dll_) {
87      FreeLibrary(dll_);
88    }
89  }
90
91 private:
92  HMODULE dll_;
93  PROC start_proc_;
94  PROC stop_proc_;
95};
96
97// Checks the window title and then class of hwnd. If they match with that
98// of a chrome_frame_helper.exe window, then add it to the list of windows
99// pointed to by lparam.
100BOOL CALLBACK CloseHelperWindowsEnumProc(HWND hwnd, LPARAM lparam) {
101  _ASSERTE(lparam != 0);
102
103  wchar_t title_buffer[MAX_PATH] = {0};
104  if (GetWindowText(hwnd, title_buffer, MAX_PATH)) {
105    if (lstrcmpiW(title_buffer, kChromeFrameHelperWindowName) == 0) {
106      wchar_t class_buffer[MAX_PATH] = {0};
107      if (GetClassName(hwnd, class_buffer, MAX_PATH)) {
108        if (lstrcmpiW(class_buffer, kChromeFrameHelperWindowClassName) == 0) {
109          if (hwnd != reinterpret_cast<HWND>(lparam)) {
110            PostMessage(hwnd, WM_CLOSE, 0, 0);
111          }
112        }
113      }
114    }
115  }
116
117  return TRUE;
118}
119
120// Enumerates all top level windows, looking for those that look like a
121// Chrome Frame helper window. It then closes all of them except for
122// except_me.
123void CloseAllHelperWindowsApartFrom(HWND except_me) {
124  EnumWindows(CloseHelperWindowsEnumProc, reinterpret_cast<LPARAM>(except_me));
125}
126
127LRESULT CALLBACK ChromeFrameHelperWndProc(HWND hwnd,
128                                          UINT message,
129                                          WPARAM wparam,
130                                          LPARAM lparam) {
131  switch (message) {
132    case WM_CREATE:
133      CloseAllHelperWindowsApartFrom(hwnd);
134      break;
135    case kRegistryWatcherChangeMessage:
136      // A system level Chrome appeared. Fall through:
137    case WM_DESTROY:
138      PostQuitMessage(0);
139      break;
140    default:
141      return ::DefWindowProc(hwnd, message, wparam, lparam);
142  }
143  return 0;
144}
145
146HWND RegisterAndCreateWindow(HINSTANCE hinstance) {
147  WNDCLASSEX wcex = {0};
148  wcex.cbSize         = sizeof(WNDCLASSEX);
149  wcex.lpfnWndProc    = ChromeFrameHelperWndProc;
150  wcex.hInstance      = GetModuleHandle(NULL);
151  wcex.hbrBackground  = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1);
152  wcex.lpszClassName  = kChromeFrameHelperWindowClassName;
153  RegisterClassEx(&wcex);
154
155  HWND hwnd = CreateWindow(kChromeFrameHelperWindowClassName,
156      kChromeFrameHelperWindowName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0,
157      CW_USEDEFAULT, 0, NULL, NULL, hinstance, NULL);
158
159  return hwnd;
160}
161
162
163// This method runs the user-level Chrome Frame uninstall command. This is
164// intended to allow it to delegate to an existing system-level install.
165bool UninstallUserLevelChromeFrame() {
166  bool success = false;
167  HKEY reg_handle = NULL;
168  wchar_t reg_path_buffer[MAX_PATH] = {0};
169  LONG result = RegOpenKeyEx(HKEY_CURRENT_USER,
170                             kChromeFrameClientStateKey,
171                             0,
172                             KEY_QUERY_VALUE,
173                             &reg_handle);
174  if (result == ERROR_SUCCESS) {
175    wchar_t exe_buffer[MAX_PATH] = {0};
176    wchar_t args_buffer[MAX_PATH] = {0};
177    LONG exe_result = ReadValue(reg_handle,
178                                kChromeFrameUninstallCmdExeValue,
179                                MAX_PATH,
180                                exe_buffer);
181    LONG args_result = ReadValue(reg_handle,
182                                 kChromeFrameUninstallCmdArgsValue,
183                                 MAX_PATH,
184                                 args_buffer);
185    RegCloseKey(reg_handle);
186    reg_handle = NULL;
187
188    if (exe_result == ERROR_SUCCESS && args_result == ERROR_SUCCESS) {
189      STARTUPINFO startup_info = {0};
190      startup_info.cb = sizeof(startup_info);
191      startup_info.dwFlags = STARTF_USESHOWWINDOW;
192      startup_info.wShowWindow = SW_SHOW;
193      PROCESS_INFORMATION process_info = {0};
194
195      // Quote the command string in the args.
196      wchar_t argument_string[MAX_PATH * 3] = {0};
197      int arg_len = _snwprintf(argument_string,
198                               _countof(argument_string) - 1,
199                               L"\"%s\" %s %s",
200                               exe_buffer,
201                               args_buffer,
202                               kForceUninstall);
203
204      if (arg_len > 0 && CreateProcess(exe_buffer, argument_string,
205                                       NULL, NULL, FALSE, 0, NULL, NULL,
206                                       &startup_info, &process_info)) {
207        // Close handles.
208        CloseHandle(process_info.hThread);
209        CloseHandle(process_info.hProcess);
210        success = true;
211      }
212    }
213  }
214
215  return success;
216}
217
218void WaitCallback() {
219  // Check if the Chrome Frame BHO is now in the list of registered BHOs:
220  if (IsBHOLoadingPolicyRegistered()) {
221    PostMessage(g_hwnd, kRegistryWatcherChangeMessage, 0, 0);
222  }
223}
224
225int APIENTRY wWinMain(HINSTANCE hinstance, HINSTANCE, wchar_t*, int show_cmd) {
226  google_breakpad::scoped_ptr<google_breakpad::ExceptionHandler> breakpad(
227    InitializeCrashReporting(NORMAL));
228
229  if (IsSystemLevelChromeFrameInstalled()) {
230    // If we're running at startup, check for system-level Chrome Frame
231    // installations. If we have one, time
232    // to bail, also schedule user-level CF to be uninstalled at next logon.
233    const wchar_t* cmd_line = ::GetCommandLine();
234    if (cmd_line && wcsstr(cmd_line, kStartupArg) != NULL) {
235      bool uninstalled = UninstallUserLevelChromeFrame();
236      _ASSERTE(uninstalled);
237    }
238    return 0;
239  }
240
241  // Create a window with a known class and title just to listen for WM_CLOSE
242  // messages that will shut us down.
243  g_hwnd = RegisterAndCreateWindow(hinstance);
244  _ASSERTE(IsWindow(g_hwnd));
245
246  // Load the hook dll, and set the event hooks.
247  HookDllLoader loader;
248  bool loaded = loader.Load();
249  _ASSERTE(loaded);
250
251  // Start up the registry watcher
252  RegistryWatcher registry_watcher(HKEY_LOCAL_MACHINE, kBHORegistrationPath,
253                                   WaitCallback);
254  bool watching = registry_watcher.StartWatching();
255  _ASSERTE(watching);
256
257  if (loaded) {
258    MSG msg;
259    BOOL ret;
260    // Main message loop:
261    while ((ret = GetMessage(&msg, NULL, 0, 0))) {
262      if (ret == -1) {
263        break;
264      } else {
265        TranslateMessage(&msg);
266        DispatchMessage(&msg);
267      }
268    }
269  }
270
271  UnregisterClass(kChromeFrameHelperWindowClassName, hinstance);
272  return 0;
273}
274