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