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 "remoting/host/win/elevated_controller.h"
      6 
      7 #include "base/file_util.h"
      8 #include "base/file_version_info.h"
      9 #include "base/json/json_reader.h"
     10 #include "base/json/json_writer.h"
     11 #include "base/logging.h"
     12 #include "base/memory/scoped_ptr.h"
     13 #include "base/path_service.h"
     14 #include "base/process/memory.h"
     15 #include "base/strings/utf_string_conversions.h"
     16 #include "base/values.h"
     17 #include "base/win/scoped_handle.h"
     18 #include "remoting/host/branding.h"
     19 #include "remoting/host/usage_stats_consent.h"
     20 #include "remoting/host/verify_config_window_win.h"
     21 #include "remoting/host/win/core_resource.h"
     22 #include "remoting/host/win/security_descriptor.h"
     23 
     24 namespace remoting {
     25 
     26 namespace {
     27 
     28 // The maximum size of the configuration file. "1MB ought to be enough" for any
     29 // reasonable configuration we will ever need. 1MB is low enough to make
     30 // the probability of out of memory situation fairly low. OOM is still possible
     31 // and we will crash if it occurs.
     32 const size_t kMaxConfigFileSize = 1024 * 1024;
     33 
     34 // The host configuration file name.
     35 const base::FilePath::CharType kConfigFileName[] = FILE_PATH_LITERAL("host.json");
     36 
     37 // The unprivileged configuration file name.
     38 const base::FilePath::CharType kUnprivilegedConfigFileName[] =
     39     FILE_PATH_LITERAL("host_unprivileged.json");
     40 
     41 // The extension for the temporary file.
     42 const base::FilePath::CharType kTempFileExtension[] = FILE_PATH_LITERAL("json~");
     43 
     44 // The host configuration file security descriptor that enables full access to
     45 // Local System and built-in administrators only.
     46 const char kConfigFileSecurityDescriptor[] =
     47     "O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)";
     48 
     49 const char kUnprivilegedConfigFileSecurityDescriptor[] =
     50     "O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GR;;;AU)";
     51 
     52 // Configuration keys.
     53 const char kHostId[] = "host_id";
     54 const char kXmppLogin[] = "xmpp_login";
     55 const char kHostOwner[] = "host_owner";
     56 const char kHostSecretHash[] = "host_secret_hash";
     57 
     58 // The configuration keys that cannot be specified in UpdateConfig().
     59 const char* const kReadonlyKeys[] = { kHostId, kHostOwner, kXmppLogin };
     60 
     61 // The configuration keys whose values may be read by GetConfig().
     62 const char* const kUnprivilegedConfigKeys[] = { kHostId, kXmppLogin };
     63 
     64 // Determines if the client runs in the security context that allows performing
     65 // administrative tasks (i.e. the user belongs to the adminstrators group and
     66 // the client runs elevated).
     67 bool IsClientAdmin() {
     68   HRESULT hr = CoImpersonateClient();
     69   if (FAILED(hr)) {
     70     return false;
     71   }
     72 
     73   SID_IDENTIFIER_AUTHORITY nt_authority = SECURITY_NT_AUTHORITY;
     74   PSID administrators_group = NULL;
     75   BOOL result = AllocateAndInitializeSid(&nt_authority,
     76                                          2,
     77                                          SECURITY_BUILTIN_DOMAIN_RID,
     78                                          DOMAIN_ALIAS_RID_ADMINS,
     79                                          0, 0, 0, 0, 0, 0,
     80                                          &administrators_group);
     81   if (result) {
     82     if (!CheckTokenMembership(NULL, administrators_group, &result)) {
     83       result = false;
     84     }
     85     FreeSid(administrators_group);
     86   }
     87 
     88   hr = CoRevertToSelf();
     89   CHECK(SUCCEEDED(hr));
     90 
     91   return !!result;
     92 }
     93 
     94 // Reads and parses the configuration file up to |kMaxConfigFileSize| in
     95 // size.
     96 HRESULT ReadConfig(const base::FilePath& filename,
     97                    scoped_ptr<base::DictionaryValue>* config_out) {
     98 
     99   // Read raw data from the configuration file.
    100   base::win::ScopedHandle file(
    101       CreateFileW(filename.value().c_str(),
    102                   GENERIC_READ,
    103                   FILE_SHARE_READ | FILE_SHARE_WRITE,
    104                   NULL,
    105                   OPEN_EXISTING,
    106                   FILE_FLAG_SEQUENTIAL_SCAN,
    107                   NULL));
    108 
    109   if (!file.IsValid()) {
    110     DWORD error = GetLastError();
    111     LOG_GETLASTERROR(ERROR)
    112         << "Failed to open '" << filename.value() << "'";
    113     return HRESULT_FROM_WIN32(error);
    114   }
    115 
    116   scoped_ptr<char[]> buffer(new char[kMaxConfigFileSize]);
    117   DWORD size = kMaxConfigFileSize;
    118   if (!::ReadFile(file, &buffer[0], size, &size, NULL)) {
    119     DWORD error = GetLastError();
    120     LOG_GETLASTERROR(ERROR)
    121         << "Failed to read '" << filename.value() << "'";
    122     return HRESULT_FROM_WIN32(error);
    123   }
    124 
    125   // Parse the JSON configuration, expecting it to contain a dictionary.
    126   std::string file_content(buffer.get(), size);
    127   scoped_ptr<base::Value> value(
    128       base::JSONReader::Read(file_content, base::JSON_ALLOW_TRAILING_COMMAS));
    129 
    130   base::DictionaryValue* dictionary;
    131   if (value.get() == NULL || !value->GetAsDictionary(&dictionary)) {
    132     LOG(ERROR) << "Failed to read '" << filename.value() << "'.";
    133     return E_FAIL;
    134   }
    135 
    136   value.release();
    137   config_out->reset(dictionary);
    138   return S_OK;
    139 }
    140 
    141 base::FilePath GetTempLocationFor(const base::FilePath& filename) {
    142   return filename.ReplaceExtension(kTempFileExtension);
    143 }
    144 
    145 // Writes a config file to a temporary location.
    146 HRESULT WriteConfigFileToTemp(const base::FilePath& filename,
    147                               const char* security_descriptor,
    148                               const char* content,
    149                               size_t length) {
    150   // Create the security descriptor for the configuration file.
    151   ScopedSd sd = ConvertSddlToSd(security_descriptor);
    152   if (!sd) {
    153     DWORD error = GetLastError();
    154     LOG_GETLASTERROR(ERROR) <<
    155         "Failed to create a security descriptor for the configuration file";
    156     return HRESULT_FROM_WIN32(error);
    157   }
    158 
    159   SECURITY_ATTRIBUTES security_attributes = {0};
    160   security_attributes.nLength = sizeof(security_attributes);
    161   security_attributes.lpSecurityDescriptor = sd.get();
    162   security_attributes.bInheritHandle = FALSE;
    163 
    164   // Create a temporary file and write configuration to it.
    165   base::FilePath tempname = GetTempLocationFor(filename);
    166   base::win::ScopedHandle file(
    167       CreateFileW(tempname.value().c_str(),
    168                   GENERIC_WRITE,
    169                   0,
    170                   &security_attributes,
    171                   CREATE_ALWAYS,
    172                   FILE_FLAG_SEQUENTIAL_SCAN,
    173                   NULL));
    174 
    175   if (!file.IsValid()) {
    176     DWORD error = GetLastError();
    177     LOG_GETLASTERROR(ERROR)
    178         << "Failed to create '" << filename.value() << "'";
    179     return HRESULT_FROM_WIN32(error);
    180   }
    181 
    182   DWORD written;
    183   if (!WriteFile(file, content, static_cast<DWORD>(length), &written, NULL)) {
    184     DWORD error = GetLastError();
    185     LOG_GETLASTERROR(ERROR)
    186         << "Failed to write to '" << filename.value() << "'";
    187     return HRESULT_FROM_WIN32(error);
    188   }
    189 
    190   return S_OK;
    191 }
    192 
    193 // Moves a config file from its temporary location to its permanent location.
    194 HRESULT MoveConfigFileFromTemp(const base::FilePath& filename) {
    195   // Now that the configuration is stored successfully replace the actual
    196   // configuration file.
    197   base::FilePath tempname = GetTempLocationFor(filename);
    198   if (!MoveFileExW(tempname.value().c_str(),
    199                    filename.value().c_str(),
    200                    MOVEFILE_REPLACE_EXISTING)) {
    201       DWORD error = GetLastError();
    202       LOG_GETLASTERROR(ERROR)
    203           << "Failed to rename '" << tempname.value() << "' to '"
    204           << filename.value() << "'";
    205       return HRESULT_FROM_WIN32(error);
    206   }
    207 
    208   return S_OK;
    209 }
    210 
    211 // Writes the configuration file up to |kMaxConfigFileSize| in size.
    212 HRESULT WriteConfig(const char* content, size_t length, HWND owner_window) {
    213   if (length > kMaxConfigFileSize) {
    214       return E_FAIL;
    215   }
    216 
    217   // Extract the configuration data that the user will verify.
    218   scoped_ptr<base::Value> config_value(base::JSONReader::Read(content));
    219   if (!config_value.get()) {
    220     return E_FAIL;
    221   }
    222   base::DictionaryValue* config_dict = NULL;
    223   if (!config_value->GetAsDictionary(&config_dict)) {
    224     return E_FAIL;
    225   }
    226   std::string email;
    227   if (!config_dict->GetString(kHostOwner, &email)) {
    228     if (!config_dict->GetString(kXmppLogin, &email)) {
    229       return E_FAIL;
    230     }
    231   }
    232   std::string host_id, host_secret_hash;
    233   if (!config_dict->GetString(kHostId, &host_id) ||
    234       !config_dict->GetString(kHostSecretHash, &host_secret_hash)) {
    235     return E_FAIL;
    236   }
    237 
    238   // Ask the user to verify the configuration (unless the client is admin
    239   // already).
    240   if (!IsClientAdmin()) {
    241     remoting::VerifyConfigWindowWin verify_win(email, host_id,
    242                                                host_secret_hash);
    243     DWORD error = verify_win.DoModal(owner_window);
    244     if (error != ERROR_SUCCESS) {
    245       return HRESULT_FROM_WIN32(error);
    246     }
    247   }
    248 
    249   // Extract the unprivileged fields from the configuration.
    250   base::DictionaryValue unprivileged_config_dict;
    251   for (int i = 0; i < arraysize(kUnprivilegedConfigKeys); ++i) {
    252     const char* key = kUnprivilegedConfigKeys[i];
    253     base::string16 value;
    254     if (config_dict->GetString(key, &value)) {
    255       unprivileged_config_dict.SetString(key, value);
    256     }
    257   }
    258   std::string unprivileged_config_str;
    259   base::JSONWriter::Write(&unprivileged_config_dict, &unprivileged_config_str);
    260 
    261   // Write the full configuration file to a temporary location.
    262   base::FilePath full_config_file_path =
    263       remoting::GetConfigDir().Append(kConfigFileName);
    264   HRESULT hr = WriteConfigFileToTemp(full_config_file_path,
    265                                      kConfigFileSecurityDescriptor,
    266                                      content,
    267                                      length);
    268   if (FAILED(hr)) {
    269     return hr;
    270   }
    271 
    272   // Write the unprivileged configuration file to a temporary location.
    273   base::FilePath unprivileged_config_file_path =
    274       remoting::GetConfigDir().Append(kUnprivilegedConfigFileName);
    275   hr = WriteConfigFileToTemp(unprivileged_config_file_path,
    276                              kUnprivilegedConfigFileSecurityDescriptor,
    277                              unprivileged_config_str.data(),
    278                              unprivileged_config_str.size());
    279   if (FAILED(hr)) {
    280     return hr;
    281   }
    282 
    283   // Move the full configuration file to its permanent location.
    284   hr = MoveConfigFileFromTemp(full_config_file_path);
    285   if (FAILED(hr)) {
    286     return hr;
    287   }
    288 
    289   // Move the unprivileged configuration file to its permanent location.
    290   hr = MoveConfigFileFromTemp(unprivileged_config_file_path);
    291   if (FAILED(hr)) {
    292     return hr;
    293   }
    294 
    295   return S_OK;
    296 }
    297 
    298 } // namespace
    299 
    300 ElevatedController::ElevatedController() : owner_window_(NULL) {
    301 }
    302 
    303 HRESULT ElevatedController::FinalConstruct() {
    304   return S_OK;
    305 }
    306 
    307 void ElevatedController::FinalRelease() {
    308 }
    309 
    310 STDMETHODIMP ElevatedController::GetConfig(BSTR* config_out) {
    311   base::FilePath config_dir = remoting::GetConfigDir();
    312 
    313   // Read the unprivileged part of host configuration.
    314   scoped_ptr<base::DictionaryValue> config;
    315   HRESULT hr = ReadConfig(config_dir.Append(kUnprivilegedConfigFileName),
    316                           &config);
    317   if (FAILED(hr)) {
    318     return hr;
    319   }
    320 
    321   // Convert the config back to a string and return it to the caller.
    322   std::string file_content;
    323   base::JSONWriter::Write(config.get(), &file_content);
    324 
    325   *config_out = ::SysAllocString(UTF8ToUTF16(file_content).c_str());
    326   if (config_out == NULL) {
    327     return E_OUTOFMEMORY;
    328   }
    329 
    330   return S_OK;
    331 }
    332 
    333 STDMETHODIMP ElevatedController::GetVersion(BSTR* version_out) {
    334   // Report the product version number of the daemon controller binary as
    335   // the host version.
    336   HMODULE binary = base::GetModuleFromAddress(
    337       reinterpret_cast<void*>(&ReadConfig));
    338   scoped_ptr<FileVersionInfo> version_info(
    339       FileVersionInfo::CreateFileVersionInfoForModule(binary));
    340 
    341   base::string16 version;
    342   if (version_info.get()) {
    343     version = version_info->product_version();
    344   }
    345 
    346   *version_out = ::SysAllocString(version.c_str());
    347   if (version_out == NULL) {
    348     return E_OUTOFMEMORY;
    349   }
    350 
    351   return S_OK;
    352 }
    353 
    354 STDMETHODIMP ElevatedController::SetConfig(BSTR config) {
    355   // Determine the config directory path and create it if necessary.
    356   base::FilePath config_dir = remoting::GetConfigDir();
    357   if (!base::CreateDirectory(config_dir)) {
    358     return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
    359   }
    360 
    361   std::string file_content = UTF16ToUTF8(
    362     base::string16(static_cast<base::char16*>(config), ::SysStringLen(config)));
    363 
    364   return WriteConfig(file_content.c_str(), file_content.size(), owner_window_);
    365 }
    366 
    367 STDMETHODIMP ElevatedController::SetOwnerWindow(LONG_PTR window_handle) {
    368   owner_window_ = reinterpret_cast<HWND>(window_handle);
    369   return S_OK;
    370 }
    371 
    372 STDMETHODIMP ElevatedController::StartDaemon() {
    373   ScopedScHandle service;
    374   HRESULT hr = OpenService(&service);
    375   if (FAILED(hr)) {
    376     return hr;
    377   }
    378 
    379   // Change the service start type to 'auto'.
    380   if (!::ChangeServiceConfigW(service,
    381                               SERVICE_NO_CHANGE,
    382                               SERVICE_AUTO_START,
    383                               SERVICE_NO_CHANGE,
    384                               NULL,
    385                               NULL,
    386                               NULL,
    387                               NULL,
    388                               NULL,
    389                               NULL,
    390                               NULL)) {
    391     DWORD error = GetLastError();
    392     LOG_GETLASTERROR(ERROR)
    393         << "Failed to change the '" << kWindowsServiceName
    394         << "'service start type to 'auto'";
    395     return HRESULT_FROM_WIN32(error);
    396   }
    397 
    398   // Start the service.
    399   if (!StartService(service, 0, NULL)) {
    400     DWORD error = GetLastError();
    401     if (error != ERROR_SERVICE_ALREADY_RUNNING) {
    402       LOG_GETLASTERROR(ERROR)
    403           << "Failed to start the '" << kWindowsServiceName << "'service";
    404 
    405       return HRESULT_FROM_WIN32(error);
    406     }
    407   }
    408 
    409   return S_OK;
    410 }
    411 
    412 STDMETHODIMP ElevatedController::StopDaemon() {
    413   ScopedScHandle service;
    414   HRESULT hr = OpenService(&service);
    415   if (FAILED(hr)) {
    416     return hr;
    417   }
    418 
    419   // Change the service start type to 'manual'.
    420   if (!::ChangeServiceConfigW(service,
    421                               SERVICE_NO_CHANGE,
    422                               SERVICE_DEMAND_START,
    423                               SERVICE_NO_CHANGE,
    424                               NULL,
    425                               NULL,
    426                               NULL,
    427                               NULL,
    428                               NULL,
    429                               NULL,
    430                               NULL)) {
    431     DWORD error = GetLastError();
    432     LOG_GETLASTERROR(ERROR)
    433         << "Failed to change the '" << kWindowsServiceName
    434         << "'service start type to 'manual'";
    435     return HRESULT_FROM_WIN32(error);
    436   }
    437 
    438   // Stop the service.
    439   SERVICE_STATUS status;
    440   if (!ControlService(service, SERVICE_CONTROL_STOP, &status)) {
    441     DWORD error = GetLastError();
    442     if (error != ERROR_SERVICE_NOT_ACTIVE) {
    443       LOG_GETLASTERROR(ERROR)
    444           << "Failed to stop the '" << kWindowsServiceName << "'service";
    445       return HRESULT_FROM_WIN32(error);
    446     }
    447   }
    448 
    449   return S_OK;
    450 }
    451 
    452 STDMETHODIMP ElevatedController::UpdateConfig(BSTR config) {
    453   // Parse the config.
    454   std::string config_str = UTF16ToUTF8(
    455     base::string16(static_cast<base::char16*>(config), ::SysStringLen(config)));
    456   scoped_ptr<base::Value> config_value(base::JSONReader::Read(config_str));
    457   if (!config_value.get()) {
    458     return E_FAIL;
    459   }
    460   base::DictionaryValue* config_dict = NULL;
    461   if (!config_value->GetAsDictionary(&config_dict)) {
    462     return E_FAIL;
    463   }
    464   // Check for bad keys.
    465   for (int i = 0; i < arraysize(kReadonlyKeys); ++i) {
    466     if (config_dict->HasKey(kReadonlyKeys[i])) {
    467       return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
    468     }
    469   }
    470   // Get the old config.
    471   base::FilePath config_dir = remoting::GetConfigDir();
    472   scoped_ptr<base::DictionaryValue> config_old;
    473   HRESULT hr = ReadConfig(config_dir.Append(kConfigFileName), &config_old);
    474   if (FAILED(hr)) {
    475     return hr;
    476   }
    477   // Merge items from the given config into the old config.
    478   config_old->MergeDictionary(config_dict);
    479   // Write the updated config.
    480   std::string config_updated_str;
    481   base::JSONWriter::Write(config_old.get(), &config_updated_str);
    482   return WriteConfig(config_updated_str.c_str(), config_updated_str.size(),
    483                      owner_window_);
    484 }
    485 
    486 STDMETHODIMP ElevatedController::GetUsageStatsConsent(BOOL* allowed,
    487                                                          BOOL* set_by_policy) {
    488   bool local_allowed;
    489   bool local_set_by_policy;
    490   if (remoting::GetUsageStatsConsent(&local_allowed, &local_set_by_policy)) {
    491     *allowed = local_allowed;
    492     *set_by_policy = local_set_by_policy;
    493     return S_OK;
    494   } else {
    495     return E_FAIL;
    496   }
    497 }
    498 
    499 STDMETHODIMP ElevatedController::SetUsageStatsConsent(BOOL allowed) {
    500   if (remoting::SetUsageStatsConsent(!!allowed)) {
    501     return S_OK;
    502   } else {
    503     return E_FAIL;
    504   }
    505 }
    506 
    507 HRESULT ElevatedController::OpenService(ScopedScHandle* service_out) {
    508   DWORD error;
    509 
    510   ScopedScHandle scmanager(
    511       ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE,
    512                        SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE));
    513   if (!scmanager.IsValid()) {
    514     error = GetLastError();
    515     LOG_GETLASTERROR(ERROR)
    516         << "Failed to connect to the service control manager";
    517 
    518     return HRESULT_FROM_WIN32(error);
    519   }
    520 
    521   DWORD desired_access = SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS |
    522                          SERVICE_START | SERVICE_STOP;
    523   ScopedScHandle service(
    524       ::OpenServiceW(scmanager, kWindowsServiceName, desired_access));
    525   if (!service.IsValid()) {
    526     error = GetLastError();
    527     LOG_GETLASTERROR(ERROR)
    528         << "Failed to open to the '" << kWindowsServiceName << "' service";
    529 
    530     return HRESULT_FROM_WIN32(error);
    531   }
    532 
    533   service_out->Set(service.Take());
    534   return S_OK;
    535 }
    536 
    537 } // namespace remoting
    538