- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / policy / policy_loader_win.cc
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 "chrome/browser/policy/policy_loader_win.h"
6
7 #include <rpc.h>      // For struct GUID
8 #include <shlwapi.h>  // For PathIsUNC()
9 #include <userenv.h>  // For GPO functions
10 #include <windows.h>
11
12 #include <string>
13 #include <vector>
14
15 // shlwapi.dll is required for PathIsUNC().
16 #pragma comment(lib, "shlwapi.lib")
17 // userenv.dll is required for various GPO functions.
18 #pragma comment(lib, "userenv.lib")
19
20 #include "base/basictypes.h"
21 #include "base/file_util.h"
22 #include "base/json/json_reader.h"
23 #include "base/lazy_instance.h"
24 #include "base/logging.h"
25 #include "base/scoped_native_library.h"
26 #include "base/sequenced_task_runner.h"
27 #include "base/stl_util.h"
28 #include "base/strings/string16.h"
29 #include "base/strings/string_util.h"
30 #include "chrome/browser/policy/policy_bundle.h"
31 #include "chrome/browser/policy/policy_load_status.h"
32 #include "chrome/browser/policy/policy_map.h"
33 #include "chrome/browser/policy/preg_parser_win.h"
34 #include "chrome/browser/policy/registry_dict_win.h"
35 #include "components/json_schema/json_schema_constants.h"
36 #include "policy/policy_constants.h"
37
38 namespace schema = json_schema_constants;
39
40 namespace policy {
41
42 namespace {
43
44 const char kKeyMandatory[] = "policy";
45 const char kKeyRecommended[] = "recommended";
46 const char kKeySchema[] = "schema";
47 const char kKeyThirdParty[] = "3rdparty";
48
49 // The GUID of the registry settings group policy extension.
50 GUID kRegistrySettingsCSEGUID = REGISTRY_EXTENSION_GUID;
51
52 // A helper class encapsulating run-time-linked function calls to Wow64 APIs.
53 class Wow64Functions {
54  public:
55   Wow64Functions()
56     : kernel32_lib_(base::FilePath(L"kernel32")),
57       is_wow_64_process_(NULL),
58       wow_64_disable_wow_64_fs_redirection_(NULL),
59       wow_64_revert_wow_64_fs_redirection_(NULL) {
60     if (kernel32_lib_.is_valid()) {
61       is_wow_64_process_ = reinterpret_cast<IsWow64Process>(
62           kernel32_lib_.GetFunctionPointer("IsWow64Process"));
63       wow_64_disable_wow_64_fs_redirection_ =
64           reinterpret_cast<Wow64DisableWow64FSRedirection>(
65               kernel32_lib_.GetFunctionPointer(
66                   "Wow64DisableWow64FsRedirection"));
67       wow_64_revert_wow_64_fs_redirection_ =
68           reinterpret_cast<Wow64RevertWow64FSRedirection>(
69               kernel32_lib_.GetFunctionPointer(
70                   "Wow64RevertWow64FsRedirection"));
71     }
72   }
73
74   bool is_valid() {
75     return is_wow_64_process_ &&
76         wow_64_disable_wow_64_fs_redirection_ &&
77         wow_64_revert_wow_64_fs_redirection_;
78  }
79
80   bool IsWow64() {
81     BOOL result = 0;
82     if (!is_wow_64_process_(GetCurrentProcess(), &result))
83       PLOG(WARNING) << "IsWow64ProcFailed";
84     return !!result;
85   }
86
87   bool DisableFsRedirection(PVOID* previous_state) {
88     return !!wow_64_disable_wow_64_fs_redirection_(previous_state);
89   }
90
91   bool RevertFsRedirection(PVOID previous_state) {
92     return !!wow_64_revert_wow_64_fs_redirection_(previous_state);
93   }
94
95  private:
96   typedef BOOL (WINAPI* IsWow64Process)(HANDLE, PBOOL);
97   typedef BOOL (WINAPI* Wow64DisableWow64FSRedirection)(PVOID*);
98   typedef BOOL (WINAPI* Wow64RevertWow64FSRedirection)(PVOID);
99
100   base::ScopedNativeLibrary kernel32_lib_;
101
102   IsWow64Process is_wow_64_process_;
103   Wow64DisableWow64FSRedirection wow_64_disable_wow_64_fs_redirection_;
104   Wow64RevertWow64FSRedirection wow_64_revert_wow_64_fs_redirection_;
105
106   DISALLOW_COPY_AND_ASSIGN(Wow64Functions);
107 };
108
109 // Global Wow64Function instance used by ScopedDisableWow64Redirection below.
110 static base::LazyInstance<Wow64Functions> g_wow_64_functions =
111     LAZY_INSTANCE_INITIALIZER;
112
113 // Scoper that switches off Wow64 File System Redirection during its lifetime.
114 class ScopedDisableWow64Redirection {
115  public:
116   ScopedDisableWow64Redirection()
117     : active_(false),
118       previous_state_(NULL) {
119     Wow64Functions* wow64 = g_wow_64_functions.Pointer();
120     if (wow64->is_valid() && wow64->IsWow64()) {
121       if (wow64->DisableFsRedirection(&previous_state_))
122         active_ = true;
123       else
124         PLOG(WARNING) << "Wow64DisableWow64FSRedirection";
125     }
126   }
127
128   ~ScopedDisableWow64Redirection() {
129     if (active_)
130       CHECK(g_wow_64_functions.Get().RevertFsRedirection(previous_state_));
131   }
132
133   bool is_active() { return active_; }
134
135  private:
136   bool active_;
137   PVOID previous_state_;
138
139   DISALLOW_COPY_AND_ASSIGN(ScopedDisableWow64Redirection);
140 };
141
142 // AppliedGPOListProvider implementation that calls actual Windows APIs.
143 class WinGPOListProvider : public AppliedGPOListProvider {
144  public:
145   virtual ~WinGPOListProvider() {}
146
147   // AppliedGPOListProvider:
148   virtual DWORD GetAppliedGPOList(DWORD flags,
149                                   LPCTSTR machine_name,
150                                   PSID sid_user,
151                                   GUID* extension_guid,
152                                   PGROUP_POLICY_OBJECT* gpo_list) OVERRIDE {
153     return ::GetAppliedGPOList(flags, machine_name, sid_user, extension_guid,
154                                gpo_list);
155   }
156
157   virtual BOOL FreeGPOList(PGROUP_POLICY_OBJECT gpo_list) OVERRIDE {
158     return ::FreeGPOList(gpo_list);
159   }
160 };
161
162 // The default windows GPO list provider used for PolicyLoaderWin.
163 static base::LazyInstance<WinGPOListProvider> g_win_gpo_list_provider =
164     LAZY_INSTANCE_INITIALIZER;
165
166 std::string GetSchemaTypeForValueType(base::Value::Type value_type) {
167   switch (value_type) {
168     case base::Value::TYPE_DICTIONARY:
169       return json_schema_constants::kObject;
170     case base::Value::TYPE_INTEGER:
171       return json_schema_constants::kInteger;
172     case base::Value::TYPE_LIST:
173       return json_schema_constants::kArray;
174     case base::Value::TYPE_BOOLEAN:
175       return json_schema_constants::kBoolean;
176     case base::Value::TYPE_STRING:
177       return json_schema_constants::kString;
178     default:
179       break;
180   }
181
182   NOTREACHED() << "Unsupported policy value type " << value_type;
183   return json_schema_constants::kNull;
184 }
185
186 // Parses |gpo_dict| according to |schema| and writes the resulting policy
187 // settings to |policy| for the given |scope| and |level|.
188 void ParsePolicy(const RegistryDict* gpo_dict,
189                  PolicyLevel level,
190                  PolicyScope scope,
191                  const base::DictionaryValue* schema,
192                  PolicyMap* policy) {
193   if (!gpo_dict)
194     return;
195
196   scoped_ptr<base::Value> policy_value(gpo_dict->ConvertToJSON(schema));
197   const base::DictionaryValue* policy_dict = NULL;
198   if (!policy_value->GetAsDictionary(&policy_dict) || !policy_dict) {
199     LOG(WARNING) << "Root policy object is not a dictionary!";
200     return;
201   }
202
203   policy->LoadFrom(policy_dict, level, scope);
204 }
205
206 }  // namespace
207
208 const base::FilePath::CharType PolicyLoaderWin::kPRegFileName[] =
209     FILE_PATH_LITERAL("Registry.pol");
210
211 PolicyLoaderWin::PolicyLoaderWin(
212     scoped_refptr<base::SequencedTaskRunner> task_runner,
213     const PolicyDefinitionList* policy_list,
214     const string16& chrome_policy_key,
215     AppliedGPOListProvider* gpo_provider)
216     : AsyncPolicyLoader(task_runner),
217       is_initialized_(false),
218       policy_list_(policy_list),
219       chrome_policy_key_(chrome_policy_key),
220       gpo_provider_(gpo_provider),
221       user_policy_changed_event_(false, false),
222       machine_policy_changed_event_(false, false),
223       user_policy_watcher_failed_(false),
224       machine_policy_watcher_failed_(false) {
225   if (!RegisterGPNotification(user_policy_changed_event_.handle(), false)) {
226     DPLOG(WARNING) << "Failed to register user group policy notification";
227     user_policy_watcher_failed_ = true;
228   }
229   if (!RegisterGPNotification(machine_policy_changed_event_.handle(), true)) {
230     DPLOG(WARNING) << "Failed to register machine group policy notification.";
231     machine_policy_watcher_failed_ = true;
232   }
233 }
234
235 PolicyLoaderWin::~PolicyLoaderWin() {
236   user_policy_watcher_.StopWatching();
237   machine_policy_watcher_.StopWatching();
238 }
239
240 // static
241 scoped_ptr<PolicyLoaderWin> PolicyLoaderWin::Create(
242     scoped_refptr<base::SequencedTaskRunner> task_runner,
243     const PolicyDefinitionList* policy_list) {
244   return make_scoped_ptr(
245       new PolicyLoaderWin(task_runner, policy_list, kRegistryChromePolicyKey,
246                           g_win_gpo_list_provider.Pointer()));
247 }
248
249 void PolicyLoaderWin::InitOnBackgroundThread() {
250   is_initialized_ = true;
251   SetupWatches();
252 }
253
254 scoped_ptr<PolicyBundle> PolicyLoaderWin::Load() {
255   // Reset the watches BEFORE reading the individual policies to avoid
256   // missing a change notification.
257   if (is_initialized_)
258     SetupWatches();
259
260   if (chrome_policy_schema_.empty())
261     BuildChromePolicySchema();
262
263   // Policy scope and corresponding hive.
264   static const struct {
265     PolicyScope scope;
266     HKEY hive;
267   } kScopes[] = {
268     { POLICY_SCOPE_MACHINE, HKEY_LOCAL_MACHINE },
269     { POLICY_SCOPE_USER,    HKEY_CURRENT_USER  },
270   };
271
272   // Load policy data for the different scopes/levels and merge them.
273   scoped_ptr<PolicyBundle> bundle(new PolicyBundle());
274   PolicyMap* chrome_policy =
275       &bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
276   for (size_t i = 0; i < arraysize(kScopes); ++i) {
277     PolicyScope scope = kScopes[i].scope;
278     PolicyLoadStatusSample status;
279     RegistryDict gpo_dict;
280
281     // Note: GPO rules mandate a call to EnterCriticalPolicySection() here, and
282     // a matching LeaveCriticalPolicySection() call below after the
283     // ReadPolicyFromGPO() block. Unfortunately, the policy mutex may be
284     // unavailable for extended periods of time, and there are reports of this
285     // happening in the wild: http://crbug.com/265862.
286     //
287     // Blocking for minutes is neither acceptable for Chrome startup, nor on
288     // the FILE thread on which this code runs in steady state. Given that
289     // there have never been any reports of issues due to partially-applied /
290     // corrupt group policy, this code intentionally omits the
291     // EnterCriticalPolicySection() call.
292     //
293     // If there's ever reason to revisit this decision, one option could be to
294     // make the EnterCriticalPolicySection() call on a dedicated thread and
295     // timeout on it more aggressively. For now, there's no justification for
296     // the additional effort this would introduce.
297
298     if (!ReadPolicyFromGPO(scope, &gpo_dict, &status)) {
299       VLOG(1) << "Failed to read GPO files for " << scope
300               << " falling back to registry.";
301       gpo_dict.ReadRegistry(kScopes[i].hive, chrome_policy_key_);
302     }
303
304     // Remove special-cased entries from the GPO dictionary.
305     scoped_ptr<RegistryDict> recommended_dict(
306         gpo_dict.RemoveKey(kKeyRecommended));
307     scoped_ptr<RegistryDict> third_party_dict(
308         gpo_dict.RemoveKey(kKeyThirdParty));
309
310     // Load Chrome policy.
311     LoadChromePolicy(&gpo_dict, POLICY_LEVEL_MANDATORY, scope, chrome_policy);
312     LoadChromePolicy(recommended_dict.get(), POLICY_LEVEL_RECOMMENDED, scope,
313                      chrome_policy);
314
315     // Load 3rd-party policy.
316     if (third_party_dict)
317       Load3rdPartyPolicy(third_party_dict.get(), scope, bundle.get());
318   }
319
320   return bundle.Pass();
321 }
322
323 void PolicyLoaderWin::BuildChromePolicySchema() {
324   scoped_ptr<base::DictionaryValue> properties(new base::DictionaryValue());
325   for (const PolicyDefinitionList::Entry* e = policy_list_->begin;
326        e != policy_list_->end; ++e) {
327     const std::string schema_type = GetSchemaTypeForValueType(e->value_type);
328     scoped_ptr<base::DictionaryValue> entry_schema(new base::DictionaryValue());
329     entry_schema->SetStringWithoutPathExpansion(json_schema_constants::kType,
330                                                 schema_type);
331
332     if (e->value_type == base::Value::TYPE_LIST) {
333       scoped_ptr<base::DictionaryValue> items_schema(
334           new base::DictionaryValue());
335       items_schema->SetStringWithoutPathExpansion(
336           json_schema_constants::kType, json_schema_constants::kString);
337       entry_schema->SetWithoutPathExpansion(json_schema_constants::kItems,
338                                             items_schema.release());
339     }
340     properties->SetWithoutPathExpansion(e->name, entry_schema.release());
341   }
342   chrome_policy_schema_.SetStringWithoutPathExpansion(
343       json_schema_constants::kType, json_schema_constants::kObject);
344   chrome_policy_schema_.SetWithoutPathExpansion(
345       json_schema_constants::kProperties, properties.release());
346 }
347
348 bool PolicyLoaderWin::ReadPRegFile(const base::FilePath& preg_file,
349                                    RegistryDict* policy,
350                                    PolicyLoadStatusSample* status) {
351   // The following deals with the minor annoyance that Wow64 FS redirection
352   // might need to be turned off: This is the case if running as a 32-bit
353   // process on a 64-bit system, in which case Wow64 FS redirection redirects
354   // access to the %WINDIR%/System32/GroupPolicy directory to
355   // %WINDIR%/SysWOW64/GroupPolicy, but the file is actually in the
356   // system-native directory.
357   if (base::PathExists(preg_file)) {
358     return preg_parser::ReadFile(preg_file, chrome_policy_key_, policy, status);
359   } else {
360     // Try with redirection switched off.
361     ScopedDisableWow64Redirection redirection_disable;
362     if (redirection_disable.is_active() && base::PathExists(preg_file)) {
363       status->Add(POLICY_LOAD_STATUS_WOW64_REDIRECTION_DISABLED);
364       return preg_parser::ReadFile(preg_file, chrome_policy_key_, policy,
365                                    status);
366     }
367   }
368
369   // Report the error.
370   LOG(ERROR) << "PReg file doesn't exist: " << preg_file.value();
371   status->Add(POLICY_LOAD_STATUS_MISSING);
372   return false;
373 }
374
375 bool PolicyLoaderWin::LoadGPOPolicy(PolicyScope scope,
376                                     PGROUP_POLICY_OBJECT policy_object_list,
377                                     RegistryDict* policy,
378                                     PolicyLoadStatusSample* status) {
379   RegistryDict parsed_policy;
380   RegistryDict forced_policy;
381   for (GROUP_POLICY_OBJECT* policy_object = policy_object_list;
382        policy_object; policy_object = policy_object->pNext) {
383     if (policy_object->dwOptions & GPO_FLAG_DISABLE)
384       continue;
385
386     if (PathIsUNC(policy_object->lpFileSysPath)) {
387       // UNC path: Assume this is an AD-managed machine, which updates the
388       // registry via GPO's standard registry CSE periodically. Fall back to
389       // reading from the registry in this case.
390       status->Add(POLICY_LOAD_STATUS_INACCCESSIBLE);
391       return false;
392     }
393
394     base::FilePath preg_file_path(
395         base::FilePath(policy_object->lpFileSysPath).Append(kPRegFileName));
396     if (policy_object->dwOptions & GPO_FLAG_FORCE) {
397       RegistryDict new_forced_policy;
398       if (!ReadPRegFile(preg_file_path, &new_forced_policy, status))
399         return false;
400
401       // Merge with existing forced policy, giving precedence to the existing
402       // forced policy.
403       new_forced_policy.Merge(forced_policy);
404       forced_policy.Swap(&new_forced_policy);
405     } else {
406       if (!ReadPRegFile(preg_file_path, &parsed_policy, status))
407         return false;
408     }
409   }
410
411   // Merge, give precedence to forced policy.
412   parsed_policy.Merge(forced_policy);
413   policy->Swap(&parsed_policy);
414
415   return true;
416 }
417
418 bool PolicyLoaderWin::ReadPolicyFromGPO(PolicyScope scope,
419                                         RegistryDict* policy,
420                                         PolicyLoadStatusSample* status) {
421   PGROUP_POLICY_OBJECT policy_object_list = NULL;
422   DWORD flags = scope == POLICY_SCOPE_MACHINE ? GPO_LIST_FLAG_MACHINE : 0;
423   if (gpo_provider_->GetAppliedGPOList(
424           flags, NULL, NULL, &kRegistrySettingsCSEGUID,
425           &policy_object_list) != ERROR_SUCCESS) {
426     PLOG(ERROR) << "GetAppliedGPOList scope " << scope;
427     status->Add(POLICY_LOAD_STATUS_QUERY_FAILED);
428     return false;
429   }
430
431   bool result = true;
432   if (policy_object_list) {
433     result = LoadGPOPolicy(scope, policy_object_list, policy, status);
434     if (!gpo_provider_->FreeGPOList(policy_object_list))
435       LOG(WARNING) << "FreeGPOList";
436   } else {
437     status->Add(POLICY_LOAD_STATUS_NO_POLICY);
438   }
439
440   return result;
441 }
442
443 void PolicyLoaderWin::LoadChromePolicy(const RegistryDict* gpo_dict,
444                                        PolicyLevel level,
445                                        PolicyScope scope,
446                                        PolicyMap* chrome_policy_map) {
447   PolicyMap policy;
448   ParsePolicy(gpo_dict, level, scope, &chrome_policy_schema_, &policy);
449   chrome_policy_map->MergeFrom(policy);
450 }
451
452 void PolicyLoaderWin::Load3rdPartyPolicy(const RegistryDict* gpo_dict,
453                                          PolicyScope scope,
454                                          PolicyBundle* bundle) {
455   // Map of known 3rd party policy domain name to their enum values.
456   static const struct {
457     const char* name;
458     PolicyDomain domain;
459   } k3rdPartyDomains[] = {
460     { "extensions", POLICY_DOMAIN_EXTENSIONS },
461   };
462
463   // Policy level and corresponding path.
464   static const struct {
465     PolicyLevel level;
466     const char* path;
467   } kLevels[] = {
468     { POLICY_LEVEL_MANDATORY,   kKeyMandatory   },
469     { POLICY_LEVEL_RECOMMENDED, kKeyRecommended },
470   };
471
472   for (size_t i = 0; i < arraysize(k3rdPartyDomains); i++) {
473     const char* name = k3rdPartyDomains[i].name;
474     const PolicyDomain domain = k3rdPartyDomains[i].domain;
475     const RegistryDict* domain_dict = gpo_dict->GetKey(name);
476     if (!domain_dict)
477       continue;
478
479     for (RegistryDict::KeyMap::const_iterator component(
480              domain_dict->keys().begin());
481          component != domain_dict->keys().end();
482          ++component) {
483       // Load the schema.
484       const base::DictionaryValue* schema_dict = NULL;
485       scoped_ptr<base::Value> schema;
486       std::string schema_json;
487       const base::Value* schema_value = component->second->GetValue(kKeySchema);
488       if (schema_value && schema_value->GetAsString(&schema_json)) {
489         schema.reset(base::JSONReader::Read(schema_json));
490         if (!schema || !schema->GetAsDictionary(&schema_dict)) {
491           LOG(WARNING) << "Failed to parse 3rd-part policy schema for "
492                        << domain << "/" << component->first;
493         }
494       }
495
496       // Parse policy.
497       for (size_t j = 0; j < arraysize(kLevels); j++) {
498         const RegistryDict* policy_dict =
499             component->second->GetKey(kLevels[j].path);
500         if (!policy_dict)
501           continue;
502
503         PolicyMap policy;
504         ParsePolicy(policy_dict, kLevels[j].level, scope, schema_dict, &policy);
505         PolicyNamespace policy_namespace(domain, component->first);
506         bundle->Get(policy_namespace).MergeFrom(policy);
507       }
508     }
509   }
510 }
511
512 void PolicyLoaderWin::SetupWatches() {
513   DCHECK(is_initialized_);
514   if (!user_policy_watcher_failed_ &&
515       !user_policy_watcher_.GetWatchedObject() &&
516       !user_policy_watcher_.StartWatching(
517           user_policy_changed_event_.handle(), this)) {
518     DLOG(WARNING) << "Failed to start watch for user policy change event";
519     user_policy_watcher_failed_ = true;
520   }
521   if (!machine_policy_watcher_failed_ &&
522       !machine_policy_watcher_.GetWatchedObject() &&
523       !machine_policy_watcher_.StartWatching(
524           machine_policy_changed_event_.handle(), this)) {
525     DLOG(WARNING) << "Failed to start watch for machine policy change event";
526     machine_policy_watcher_failed_ = true;
527   }
528 }
529
530 void PolicyLoaderWin::OnObjectSignaled(HANDLE object) {
531   DCHECK(object == user_policy_changed_event_.handle() ||
532          object == machine_policy_changed_event_.handle())
533       << "unexpected object signaled policy reload, obj = "
534       << std::showbase << std::hex << object;
535   Reload(false);
536 }
537
538 }  // namespace policy