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 <atlbase.h>
6#include <security.h>
7
8#include <iomanip>
9#include <iostream>
10#include <iterator>
11#include <string>
12#include <vector>
13
14#include "base/at_exit.h"
15#include "base/bind.h"
16#include "base/callback_helpers.h"
17#include "base/command_line.h"
18#include "base/file_util.h"
19#include "base/guid.h"
20#include "base/logging.h"
21#include "base/path_service.h"
22#include "base/strings/string_util.h"
23#include "base/strings/utf_string_conversions.h"
24#include "base/win/scoped_handle.h"
25#include "chrome/common/chrome_constants.h"
26#include "chrome/common/chrome_switches.h"
27#include "cloud_print/common/win/cloud_print_utils.h"
28#include "cloud_print/service/service_constants.h"
29#include "cloud_print/service/service_state.h"
30#include "cloud_print/service/service_switches.h"
31#include "cloud_print/service/win/chrome_launcher.h"
32#include "cloud_print/service/win/service_controller.h"
33#include "cloud_print/service/win/service_listener.h"
34#include "cloud_print/service/win/service_utils.h"
35#include "cloud_print/service/win/setup_listener.h"
36
37namespace {
38
39void InvalidUsage() {
40  base::FilePath service_path;
41  CHECK(PathService::Get(base::FILE_EXE, &service_path));
42
43  std::cout << cloud_print::LoadLocalString(IDS_COMMAND_LINE_HELP_TITLE);
44  std::cout << " " << service_path.BaseName().value();
45  std::cout << " [";
46    std::cout << "[";
47      std::cout << "[";
48        std::cout << " -" << kInstallSwitch;
49        std::cout << " [ -" << switches::kUserDataDir << "=DIRECTORY ]";
50      std::cout << "]";
51    std::cout << "]";
52    std::cout << " | -" << kUninstallSwitch;
53    std::cout << " | -" << kStartSwitch;
54    std::cout << " | -" << kStopSwitch;
55  std::cout << " ]\n";
56  std::cout << cloud_print::LoadLocalString(IDS_COMMAND_LINE_DESCRIPTION);
57  std::cout << "\n\n";
58
59  struct {
60    const char* name;
61    int description;
62  } kSwitchHelp[] = {{
63    kInstallSwitch, IDS_SWITCH_HELP_INSTALL
64  }, {
65    switches::kUserDataDir, IDS_SWITCH_HELP_DATA_DIR
66  }, {
67    kUninstallSwitch, IDS_SWITCH_HELP_UNINSTALL
68  }, {
69    kStartSwitch, IDS_SWITCH_HELP_START
70  }, {
71    kStopSwitch, IDS_SWITCH_HELP_STOP
72  }};
73
74  for (size_t i = 0; i < arraysize(kSwitchHelp); ++i) {
75    std::cout << std::setiosflags(std::ios::left);
76    std::cout << "  -" << std::setw(16) << kSwitchHelp[i].name;
77    std::cout << cloud_print::LoadLocalString(kSwitchHelp[i].description);
78    std::cout << "\n";
79  }
80  std::cout << "\n";
81}
82
83base::string16 GetOption(int string_id, const base::string16& default,
84                   bool secure) {
85  base::string16 prompt_format = cloud_print::LoadLocalString(string_id);
86  std::vector<base::string16> substitutions(1, default);
87  std::cout << ReplaceStringPlaceholders(prompt_format, substitutions, NULL);
88  base::string16 tmp;
89  if (secure) {
90    DWORD saved_mode = 0;
91    // Don't close.
92    HANDLE stdin_handle = ::GetStdHandle(STD_INPUT_HANDLE);
93    ::GetConsoleMode(stdin_handle, &saved_mode);
94    ::SetConsoleMode(stdin_handle, saved_mode & ~ENABLE_ECHO_INPUT);
95    std::getline(std::wcin, tmp);
96    ::SetConsoleMode(stdin_handle, saved_mode);
97    std::cout << "\n";
98  } else {
99    std::getline(std::wcin, tmp);
100  }
101  if (tmp.empty())
102    return default;
103  return tmp;
104}
105
106HRESULT ReportError(HRESULT hr, int string_id) {
107  LOG(ERROR) << cloud_print::GetErrorMessage(hr);
108  std::cerr << cloud_print::LoadLocalString(string_id);
109  std::cerr << "\n";
110  return hr;
111}
112
113base::string16 StateAsString(ServiceController::State state) {
114  DWORD string_id = 0;
115  switch(state) {
116  case ServiceController::STATE_NOT_FOUND:
117    string_id = IDS_SERVICE_NOT_FOUND;
118    break;
119  case ServiceController::STATE_STOPPED:
120    string_id = IDS_SERVICE_STOPPED;
121    break;
122  case ServiceController::STATE_RUNNING:
123    string_id = IDS_SERVICE_RUNNING;
124    break;
125  }
126  return string_id ? cloud_print::LoadLocalString(string_id) : base::string16();
127}
128
129}  // namespace
130
131
132class CloudPrintServiceModule
133    : public ATL::CAtlServiceModuleT<CloudPrintServiceModule,
134                                     IDS_SERVICE_NAME> {
135 public:
136  typedef ATL::CAtlServiceModuleT<CloudPrintServiceModule,
137                                  IDS_SERVICE_NAME> Base;
138
139  CloudPrintServiceModule()
140      : check_requirements_(false),
141        controller_(new ServiceController()) {
142  }
143
144  static wchar_t* GetAppIdT() {
145    return ServiceController::GetAppIdT();
146  };
147
148  HRESULT InitializeSecurity() {
149    // TODO(gene): Check if we need to call CoInitializeSecurity and provide
150    // the appropriate security settings for service.
151    return S_OK;
152  }
153
154  bool ParseCommandLine(LPCTSTR lpCmdLine, HRESULT* pnRetCode) {
155    CHECK(pnRetCode);
156    CommandLine command_line(CommandLine::NO_PROGRAM);
157    command_line.ParseFromString(lpCmdLine);
158
159    LOG(INFO) << command_line.GetCommandLineString();
160
161    bool is_service = false;
162    *pnRetCode = ParseCommandLine(command_line, &is_service);
163    if (FAILED(*pnRetCode)) {
164      ReportError(*pnRetCode, IDS_OPERATION_FAILED_TITLE);
165    }
166    if (!is_service) {
167      controller_->UpdateState();
168      std::cout << cloud_print::LoadLocalString(IDS_STATE_LABEL);
169      std::cout << " " << StateAsString(controller_->state());
170    }
171    return is_service;
172  }
173
174  HRESULT PreMessageLoop(int nShowCmd) {
175    HRESULT hr = Base::PreMessageLoop(nShowCmd);
176    if (FAILED(hr))
177      return hr;
178
179    if (check_requirements_) {
180      CheckRequirements();
181    } else {
182      HRESULT hr = StartConnector();
183      if (FAILED(hr))
184        return hr;
185    }
186
187    LogEvent(_T("Service started/resumed"));
188    SetServiceStatus(SERVICE_RUNNING);
189
190    return hr;
191  }
192
193  HRESULT PostMessageLoop() {
194    StopConnector();
195    setup_listener_.reset();
196    return Base::PostMessageLoop();
197  }
198
199 private:
200  HRESULT ParseCommandLine(const CommandLine& command_line, bool* is_service) {
201    if (!is_service)
202      return E_INVALIDARG;
203    *is_service = false;
204
205    user_data_dir_switch_ =
206        command_line.GetSwitchValuePath(switches::kUserDataDir);
207    if (!user_data_dir_switch_.empty())
208      user_data_dir_switch_ = base::MakeAbsoluteFilePath(user_data_dir_switch_);
209
210    if (command_line.HasSwitch(kStopSwitch))
211      return controller_->StopService();
212
213    if (command_line.HasSwitch(kUninstallSwitch))
214      return controller_->UninstallService();
215
216    if (command_line.HasSwitch(kInstallSwitch)) {
217      base::string16 run_as_user;
218      base::string16 run_as_password;
219      base::FilePath user_data_dir;
220      std::vector<std::string> printers;
221      HRESULT hr = SelectWindowsAccount(&run_as_user, &run_as_password,
222                                        &user_data_dir, &printers);
223      if (FAILED(hr))
224        return hr;
225
226      DCHECK(user_data_dir_switch_.empty() ||
227             user_data_dir_switch_ == user_data_dir);
228
229      hr = SetupServiceState(user_data_dir, printers);
230      if (FAILED(hr))
231        return hr;
232
233      hr = controller_->InstallConnectorService(
234          run_as_user, run_as_password, user_data_dir_switch_,
235          command_line.HasSwitch(switches::kEnableLogging));
236      if (SUCCEEDED(hr) && command_line.HasSwitch(kStartSwitch))
237        return controller_->StartService();
238
239      return hr;
240    }
241
242    if (command_line.HasSwitch(kStartSwitch))
243      return controller_->StartService();
244
245    if (command_line.HasSwitch(kConsoleSwitch)) {
246      check_requirements_ = command_line.HasSwitch(kRequirementsSwitch);
247      ::SetConsoleCtrlHandler(&ConsoleCtrlHandler, TRUE);
248      HRESULT hr = Run();
249      ::SetConsoleCtrlHandler(NULL, FALSE);
250      return hr;
251    }
252
253    if (command_line.HasSwitch(kServiceSwitch) ||
254        command_line.HasSwitch(kRequirementsSwitch)) {
255      *is_service = true;
256      check_requirements_ = command_line.HasSwitch(kRequirementsSwitch);
257      return S_OK;
258    }
259
260
261    InvalidUsage();
262    return S_FALSE;
263  }
264
265  HRESULT SelectWindowsAccount(base::string16* run_as_user,
266                               base::string16* run_as_password,
267                               base::FilePath* user_data_dir,
268                               std::vector<std::string>* printers) {
269    *run_as_user = GetCurrentUserName();
270    std::cout << cloud_print::LoadLocalString(IDS_WINDOWS_USER_PROMPT1) << "\n";
271    *run_as_user = GetOption(IDS_WINDOWS_USER_PROMPT2, *run_as_user, false);
272    *run_as_user = ReplaceLocalHostInName(*run_as_user);
273    *run_as_password = GetOption(IDS_WINDOWS_PASSWORD_PROMPT, L"", true);
274    SetupListener setup(*run_as_user);
275    HRESULT hr = controller_->InstallCheckService(*run_as_user,
276                                                  *run_as_password,
277                                                  user_data_dir_switch_);
278    if (FAILED(hr)) {
279      return ReportError(hr, IDS_ERROR_FAILED_INSTALL_SERVICE);
280    }
281
282    {
283      // Always uninstall service after requirements check.
284      base::ScopedClosureRunner scoped_uninstall(
285          base::Bind(base::IgnoreResult(&ServiceController::UninstallService),
286                     base::Unretained(controller_.get())));
287
288      hr = controller_->StartService();
289      if (FAILED(hr)) {
290        return ReportError(hr, IDS_ERROR_FAILED_START_SERVICE);
291      }
292
293      if (!setup.WaitResponce(base::TimeDelta::FromSeconds(30))) {
294        return ReportError(E_FAIL, IDS_ERROR_FAILED_START_SERVICE);
295      }
296    }
297
298    if (setup.user_data_dir().empty()) {
299      return ReportError(E_FAIL, IDS_ERROR_NO_DATA_DIR);
300    }
301
302    if (setup.chrome_path().empty()) {
303      return ReportError(E_FAIL, IDS_ERROR_NO_CHROME);
304    }
305
306    if (!setup.is_xps_available()) {
307      return ReportError(E_FAIL, IDS_ERROR_NO_XPS);
308    }
309
310    std::cout << "\n";
311    std::cout << cloud_print::LoadLocalString(IDS_SERVICE_ENV_CHECK);
312    std::cout << "\n";
313    std::cout << cloud_print::LoadLocalString(IDS_SERVICE_ENV_USER);
314    std::cout << "\n  " << setup.user_name() << "\n";
315    std::cout << cloud_print::LoadLocalString(IDS_SERVICE_ENV_CHROME);
316    std::cout << "\n  " << setup.chrome_path().value() << "\n";
317    std::cout << cloud_print::LoadLocalString(IDS_SERVICE_ENV_DATADIR);
318    std::cout << "\n  " << setup.user_data_dir().value() << "\n";
319    std::cout << cloud_print::LoadLocalString(IDS_SERVICE_ENV_PRINTERS);
320    std::cout << "\n  ";
321    std::ostream_iterator<std::string> cout_it(std::cout, "\n  ");
322    std::copy(setup.printers().begin(), setup.printers().end(), cout_it);
323    std::cout << "\n";
324
325    *user_data_dir = setup.user_data_dir();
326    *printers = setup.printers();
327    return S_OK;
328  }
329
330  HRESULT SetupServiceState(const base::FilePath& user_data_dir,
331                            const std::vector<std::string>& printers) {
332    base::FilePath file = user_data_dir.Append(chrome::kServiceStateFileName);
333
334    std::string contents;
335    ServiceState service_state;
336
337    bool is_valid = base::ReadFileToString(file, &contents) &&
338                    service_state.FromString(contents);
339    std::string proxy_id = service_state.proxy_id();
340
341    LOG(INFO) << file.value() << ": " << contents;
342
343    base::string16 message =
344        cloud_print::LoadLocalString(IDS_ADD_PRINTERS_USING_CHROME);
345    std::cout << "\n" << message.c_str() << "\n" ;
346    std::string new_contents =
347        ChromeLauncher::CreateServiceStateFile(proxy_id, printers);
348
349    if (new_contents.empty()) {
350      return ReportError(E_FAIL, IDS_ERROR_FAILED_CREATE_CONFIG);
351    }
352
353    if (new_contents != contents) {
354      size_t  written = base::WriteFile(file, new_contents.c_str(),
355                                              new_contents.size());
356      if (written != new_contents.size()) {
357        return ReportError(cloud_print::GetLastHResult(),
358                           IDS_ERROR_FAILED_CREATE_CONFIG);
359      }
360    }
361
362    return S_OK;
363  }
364
365  void CheckRequirements() {
366    setup_listener_.reset(new ServiceListener(GetUserDataDir()));
367  }
368
369  HRESULT StartConnector() {
370    chrome_.reset(new ChromeLauncher(GetUserDataDir()));
371    return chrome_->Start() ? S_OK : E_FAIL;
372  }
373
374  void StopConnector() {
375    if (chrome_.get()) {
376      chrome_->Stop();
377      chrome_.reset();
378    }
379  }
380
381  base::FilePath GetUserDataDir() const {
382    if (!user_data_dir_switch_.empty())
383      return user_data_dir_switch_;
384    base::FilePath result;
385    CHECK(PathService::Get(base::DIR_LOCAL_APP_DATA, &result));
386    return result.Append(kSubDirectory);
387  }
388
389  static BOOL WINAPI ConsoleCtrlHandler(DWORD type);
390
391  bool check_requirements_;
392  base::FilePath user_data_dir_switch_;
393  scoped_ptr<ChromeLauncher> chrome_;
394  scoped_ptr<ServiceController> controller_;
395  scoped_ptr<ServiceListener> setup_listener_;
396};
397
398CloudPrintServiceModule _AtlModule;
399
400BOOL CloudPrintServiceModule::ConsoleCtrlHandler(DWORD type) {
401  PostThreadMessage(_AtlModule.m_dwThreadID, WM_QUIT, 0, 0);
402  return TRUE;
403}
404
405int main(int argc, char** argv) {
406  CommandLine::Init(argc, argv);
407  base::AtExitManager at_exit;
408
409  logging::LoggingSettings settings;
410  settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
411  logging::InitLogging(settings);
412
413  logging::SetMinLogLevel(
414      CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableLogging) ?
415      logging::LOG_INFO : logging::LOG_FATAL);
416
417  return _AtlModule.WinMain(0);
418}
419
420