Home | History | Annotate | Download | only in win
      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 
     37 namespace {
     38 
     39 void 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 
     83 base::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 
    107 HRESULT 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 
    114 base::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 
    133 class 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 
    399 CloudPrintServiceModule _AtlModule;
    400 
    401 BOOL CloudPrintServiceModule::ConsoleCtrlHandler(DWORD type) {
    402   PostThreadMessage(_AtlModule.m_dwThreadID, WM_QUIT, 0, 0);
    403   return TRUE;
    404 }
    405 
    406 int 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