Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1351)

Unified Diff: chrome_elf/nt_registry/nt_registry.cc

Issue 2345913003: [chrome_elf] NTRegistry - added wow64 redirection support. (Closed)
Patch Set: clang fixes. Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome_elf/nt_registry/nt_registry.cc
diff --git a/chrome_elf/nt_registry/nt_registry.cc b/chrome_elf/nt_registry/nt_registry.cc
index 373f00945682e20e1c1108a244015bc4ec43ae2e..ff2e8451f0ab4c7623a6c63926879a0b0fbed8ce 100644
--- a/chrome_elf/nt_registry/nt_registry.cc
+++ b/chrome_elf/nt_registry/nt_registry.cc
@@ -4,6 +4,8 @@
#include "chrome_elf/nt_registry/nt_registry.h"
+#include <mutex>
+
namespace {
// Function pointers used for registry access.
@@ -25,7 +27,11 @@ wchar_t g_kRegPathHKCU[g_kMaxPathLen] = L"";
wchar_t g_current_user_sid_string[g_kMaxPathLen] = L"";
wchar_t g_override_path[g_kMaxPathLen] = L"";
-// Not using install_util, to prevent circular dependency.
+//------------------------------------------------------------------------------
+// Initialization - LOCAL
+//------------------------------------------------------------------------------
+
+// Not using install_static, to prevent circular dependency.
bool IsThisProcSystem() {
wchar_t program_dir[MAX_PATH] = {};
wchar_t* cmd_line = GetCommandLineW();
@@ -105,6 +111,263 @@ bool InitNativeRegApi() {
return true;
}
+//------------------------------------------------------------------------------
+// Reg Redirection - LOCAL
+// NOTE: On >= Win7, reflection support was removed.
+//
+// https://msdn.microsoft.com/en-us/library/windows/desktop/aa384253(v=vs.85).aspx
+//------------------------------------------------------------------------------
+
+// By default no WOW64 redirection will be done for any process, unless
+// explicitly requested with the KEY_WOW64_32KEY access flag.
+// This global setting can be changed via nt::ChangeDefaultWow64Redirection().
+bool g_defaultRedirection = false;
+
+typedef struct Node {
grt (UTC plus 2) 2016/09/20 10:39:54 nit: remove typedef
penny 2016/09/23 23:50:58 Done.
+ const wchar_t* to_match;
+ // If a match, the state of whether to redirect or not becomes:
+ bool if_match_set_to;
grt (UTC plus 2) 2016/09/20 10:39:54 consider replacing these two bools with something
penny 2016/09/23 23:50:58 Done. Good idea, thanks.
+ // If redirect state was set to true, the WOW64 subkey should be inserted
+ // right before (or after) this match. Unfortunately not consistent.
+ bool redirect_before;
+ // |next| is nullptr or an array of Nodes.
grt (UTC plus 2) 2016/09/20 10:39:54 nit "...Nodes of length |array_len|."
penny 2016/09/23 23:50:58 Done.
+ size_t array_len;
grt (UTC plus 2) 2016/09/20 10:39:54 nit: swap these two so that they are in the same o
penny 2016/09/23 23:50:58 Done.
+ const Node* next;
+} Node;
+
+const Node classes_subtree[5] = {{L"CLSID", true, true, 0, nullptr},
grt (UTC plus 2) 2016/09/20 10:39:53 constexpr Node... same for the other constants bel
grt (UTC plus 2) 2016/09/20 10:39:54 nit: remove "5" since the initializer makes the si
penny 2016/09/23 23:50:58 Done.
penny 2016/09/23 23:50:58 Done. TIL, thanks!
+ {L"DirectShow", true, true, 0, nullptr},
+ {L"Interface", true, true, 0, nullptr},
+ {L"Media Type", true, true, 0, nullptr},
+ {L"MediaFoundation", true, true, 0, nullptr}};
+
+const Node hklm_software_subtree[49] = {
+ {L"Classes", false, false, sizeof(classes_subtree) / sizeof(Node),
grt (UTC plus 2) 2016/09/20 10:39:54 nit: #include <stdlib.h> and use _countof(classes_
penny 2016/09/23 23:50:57 Done. Excellent, thank you.
+ classes_subtree},
+
+ {L"Clients", false, false, 0, nullptr},
+ {L"Microsoft\\COM3", false, false, 0, nullptr},
+ {L"Microsoft\\Cryptography\\Calais\\Current", false, false, 0, nullptr},
+ {L"Microsoft\\Cryptography\\Calais\\Readers", false, false, 0, nullptr},
+ {L"Microsoft\\Cryptography\\Services", false, false, 0, nullptr},
+
+ {L"Microsoft\\CTF\\SystemShared", false, false, 0, nullptr},
+ {L"Microsoft\\CTF\\TIP", false, false, 0, nullptr},
+ {L"Microsoft\\DFS", false, false, 0, nullptr},
+ {L"Microsoft\\Driver Signing", false, false, 0, nullptr},
+ {L"Microsoft\\EnterpriseCertificates", false, false, 0, nullptr},
+
+ {L"Microsoft\\EventSystem", false, false, 0, nullptr},
+ {L"Microsoft\\MSMQ", false, false, 0, nullptr},
+ {L"Microsoft\\Non-Driver Signing", false, false, 0, nullptr},
+ {L"Microsoft\\Notepad\\DefaultFonts", false, false, 0, nullptr},
+ {L"Microsoft\\OLE", false, false, 0, nullptr},
+
+ {L"Microsoft\\RAS", false, false, 0, nullptr},
+ {L"Microsoft\\RPC", false, false, 0, nullptr},
+ {L"Microsoft\\SOFTWARE\\Microsoft\\Shared Tools\\MSInfo", false, false, 0,
+ nullptr},
+ {L"Microsoft\\SystemCertificates", false, false, 0, nullptr},
+ {L"Microsoft\\TermServLicensing", false, false, 0, nullptr},
+
+ {L"Microsoft\\Transaction Server", false, false, 0, nullptr},
+ {L"Microsoft\\Windows\\CurrentVersion\\App Paths", false, false, 0,
+ nullptr},
+ {L"Microsoft\\Windows\\CurrentVersion\\Control Panel\\Cursors\\Schemes",
+ false, false, 0, nullptr},
+ {L"Microsoft\\Windows\\CurrentVersion\\Explorer\\AutoplayHandlers", false,
+ false, 0, nullptr},
+ {L"Microsoft\\Windows\\CurrentVersion\\Explorer\\DriveIcons", false, false,
+ 0, nullptr},
+
+ {L"Microsoft\\Windows\\CurrentVersion\\Explorer\\KindMap", false, false, 0,
+ nullptr},
+ {L"Microsoft\\Windows\\CurrentVersion\\Group Policy", false, false, 0,
+ nullptr},
+ {L"Microsoft\\Windows\\CurrentVersion\\Policies", false, false, 0, nullptr},
+ {L"Microsoft\\Windows\\CurrentVersion\\PreviewHandlers", false, false, 0,
+ nullptr},
+ {L"Microsoft\\Windows\\CurrentVersion\\Setup", false, false, 0, nullptr},
+
+ {L"Microsoft\\Windows\\CurrentVersion\\Telephony\\Locations", false, false,
+ 0, nullptr},
+ {L"Microsoft\\Windows NT\\CurrentVersion\\Console", false, false, 0,
+ nullptr},
+ {L"Microsoft\\Windows NT\\CurrentVersion\\FontDpi", false, false, 0,
+ nullptr},
+ {L"Microsoft\\Windows NT\\CurrentVersion\\FontLink", false, false, 0,
+ nullptr},
+ {L"Microsoft\\Windows NT\\CurrentVersion\\FontMapper", false, false, 0,
+ nullptr},
+
+ {L"Microsoft\\Windows NT\\CurrentVersion\\Fonts", false, false, 0, nullptr},
+ {L"Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes", false, false, 0,
+ nullptr},
+ {L"Microsoft\\Windows NT\\CurrentVersion\\Gre_Initialize", false, false, 0,
+ nullptr},
+ {L"Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options",
+ false, false, 0, nullptr},
+ {L"Microsoft\\Windows NT\\CurrentVersion\\LanguagePack", false, false, 0,
+ nullptr},
+
+ {L"Microsoft\\Windows NT\\CurrentVersion\\NetworkCards", false, false, 0,
+ nullptr},
+ {L"Microsoft\\Windows NT\\CurrentVersion\\Perflib", false, false, 0,
+ nullptr},
+ {L"Microsoft\\Windows NT\\CurrentVersion\\Ports", false, false, 0, nullptr},
+ {L"Microsoft\\Windows NT\\CurrentVersion\\Print", false, false, 0, nullptr},
+ {L"Microsoft\\Windows NT\\CurrentVersion\\ProfileList", false, false, 0,
+ nullptr},
+
+ {L"Microsoft\\Windows NT\\CurrentVersion\\Time Zones", false, false, 0,
+ nullptr},
+ {L"Policies", false, false, 0, nullptr},
+ {L"RegisteredApplications", false, false, 0, nullptr},
+};
+
+const Node g_redirectionDecisionTreeHKCU = {
grt (UTC plus 2) 2016/09/20 10:39:54 suggested comment: // HKCU is shared by default wi
penny 2016/09/23 23:50:58 Acknowledged.
+ L"SOFTWARE\\Classes", false, false, sizeof(classes_subtree) / sizeof(Node),
+ classes_subtree};
+
+const Node g_redirectionDecisionTreeHKLM = {
grt (UTC plus 2) 2016/09/20 10:39:54 suggested comment: // HKLM\SOFTWARE is redirected
grt (UTC plus 2) 2016/09/21 10:14:06 i think this is more accurate, no? // HKLM\SOFTWAR
penny 2016/09/23 23:50:57 Acknowledged.
penny 2016/09/23 23:50:58 Acknowledged.
+ L"SOFTWARE", true, false, sizeof(hklm_software_subtree) / sizeof(Node),
+ hklm_software_subtree};
+
+// Main redirection handler function.
+// If redirection is required, change is made to |subkey_path| in place.
+//
+// This function should be called BEFORE concatenating |subkey_path| with the
+// root hive or calling ParseFullRegPath(). Also, |subkey_path| should neither
+// start nor end with a path seperator.
grt (UTC plus 2) 2016/09/21 10:14:06 DCHECK/assert this requirement? it seems like an e
penny 2016/09/23 23:50:58 Done.
+void ProcessRedirection(nt::ROOT_KEY root,
+ std::wstring* subkey_path,
grt (UTC plus 2) 2016/09/20 10:39:54 swap the order of the last two args; in/out args s
penny 2016/09/23 23:50:57 Done.
+ ACCESS_MASK access) {
+ const wchar_t* redirect_before = L"WOW6432Node\\";
grt (UTC plus 2) 2016/09/20 10:39:53 static constexpr wchar_t kRedirectBefore[] = ...
penny 2016/09/23 23:50:58 Done.
+ const wchar_t* redirect_after = L"\\WOW6432Node";
+
+ if (subkey_path == nullptr || subkey_path->empty() ||
+ (access & KEY_WOW64_32KEY && access & KEY_WOW64_64KEY))
+ return;
+
+ // Get rid of nt::AUTO.
grt (UTC plus 2) 2016/09/20 10:39:53 nit: "Convert nt::AUTO to the appropriate root key
penny 2016/09/23 23:50:57 Done.
+ nt::ROOT_KEY temp_root = root;
+ if (!root) {
grt (UTC plus 2) 2016/09/20 10:39:54 be explicit rather than assume AUTO == 0, and dens
penny 2016/09/23 23:50:58 Done.
+ if (g_system_install)
+ temp_root = nt::HKLM;
+ else
+ temp_root = nt::HKCU;
+ }
+ // No redirection during testing when there's already an override.
+ if (((temp_root == nt::HKCU) && (::wcslen(nt::HKCU_override) != 0)) ||
grt (UTC plus 2) 2016/09/20 10:39:54 don't compute a string length if you just want to
penny 2016/09/23 23:50:58 Done. Good one, thank you.
+ ((temp_root == nt::HKLM) && (::wcslen(nt::HKLM_override) != 0)))
+ return;
+
+ // WOW64 redirection only supported on x64 architecture. Return if x86.
+ SYSTEM_INFO system_info = {};
+ ::GetNativeSystemInfo(&system_info);
+ if (system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) {
grt (UTC plus 2) 2016/09/20 10:39:54 nit: omit braces for single-line conditional and b
penny 2016/09/23 23:50:58 Thank you - sometimes I slip back into old coding
+ return;
+ }
+
+ // Using BOOL type for compat with IsWow64Process() system API.
+ BOOL use_wow64 = false;
grt (UTC plus 2) 2016/09/20 10:39:54 i think bool is more natural for this since that's
penny 2016/09/23 23:50:58 Done.
+ if (g_defaultRedirection) {
+ // Check if running as wow64 process.
+ typedef decltype(IsWow64Process)* IsWow64ProcessFunc;
grt (UTC plus 2) 2016/09/20 10:39:54 nit: using IsWow64ProcessFunction = decltype(&
penny 2016/09/23 23:50:57 Done. This was a bit of a mind bend for my C mind
+ IsWow64ProcessFunc is_wow64_process = reinterpret_cast<IsWow64ProcessFunc>(
grt (UTC plus 2) 2016/09/20 10:39:54 nit: cache this since it won't change throughout t
penny 2016/09/23 23:50:58 Ack. I've moved this out into IsThisProcWow64().
+ ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "IsWow64Process"));
+ if (is_wow64_process) {
+ is_wow64_process(::GetCurrentProcess(), &use_wow64);
+ }
+ }
+
+ // Consider KEY_WOW64_32KEY and KEY_WOW64_64KEY override access flags.
+ if (access & KEY_WOW64_32KEY)
+ use_wow64 = true;
+
+ if (access & KEY_WOW64_64KEY)
+ use_wow64 = false;
+
+ // If !use_wow64, there's nothing more to do.
+ if (!use_wow64)
+ return;
+
+ // Pick which decision tree to use.
+ const Node* current = nullptr;
grt (UTC plus 2) 2016/09/20 10:39:54 ... = temp_root == nt::HKCU ? &g_redirectionDecisi
penny 2016/09/23 23:50:58 Done.
+ if (temp_root == nt::HKCU)
+ current = &g_redirectionDecisionTreeHKCU;
+ else
+ current = &g_redirectionDecisionTreeHKLM;
+
+ // The following loop works on the |subkey_path| from left to right.
+ // |position| tracks progress along |subkey_path|.
+ const wchar_t* position = subkey_path->data();
grt (UTC plus 2) 2016/09/20 10:39:54 use c_str() rather than data() since the code belo
penny 2016/09/23 23:50:58 Done.
+ // |redirect| holds the latest state of whether redirection is required.
+ bool redirect = false;
+ // |insertion_point| tracks latest spot for redirection subkey to be inserted.
+ const wchar_t* insertion_point = nullptr;
+ // |insert_string| tracks which redirection string would be inserted.
+ const wchar_t* insert_string = nullptr;
+
+ // The root of the tree is an array of 1.
+ size_t array_len = 1;
+ size_t index = 0;
+ while (index < array_len) {
+ size_t len = ::wcslen(current->to_match);
grt (UTC plus 2) 2016/09/21 10:14:06 it should be possible to get rid of this length co
penny 2016/09/23 23:50:58 wow. So you pushed me out of my comfort zone ther
+ // Make sure the remainder of the path is at least as long as the current
+ // subkey to match.
+ if (::wcslen(position) >= len) {
grt (UTC plus 2) 2016/09/20 10:39:54 rather than computing the string length through ea
penny 2016/09/23 23:50:57 Done. Did it a slightly different way - but no mor
+ // Do case insensitive comparisons.
+ if (::wcsnicmp(position, current->to_match, len) == 0) {
+ // Make sure not to match on a substring.
+ if (*(position + len) == L'\\' || *(position + len) == L'\0') {
+ // MATCH!
+ // -------------------------------------------------------------------
+ // 1) Update state of whether to redirect.
+ redirect = current->if_match_set_to;
+ if (redirect) {
+ // 1.5) If |redirect| state is now true,
+ // the new insertion point will be either right before or right
+ // after this match.
+ if (current->redirect_before) {
+ insertion_point = position;
+ insert_string = redirect_before;
+ } else {
+ insertion_point = position + len;
+ insert_string = redirect_after;
+ }
+ }
+ // 2) Adjust |position| along the subkey path.
+ position += len;
+ // 2.5) Increment the position, to move past path seperator(s).
+ while (*position == L'\\')
+ position++;
+ // 3) Move our loop parameters to the |next| array of Nodes.
+ array_len = current->array_len;
+ current = current->next;
+ index = 0;
+ // 4) Finish this loop and start on new array.
+ continue;
+ }
+ }
+ }
+
+ // Move to the next node in the array if we didn't match this loop.
+ current += 1;
grt (UTC plus 2) 2016/09/20 10:39:54 nit: ++current;
penny 2016/09/23 23:50:57 Done.
+ index++;
grt (UTC plus 2) 2016/09/20 10:39:54 personal nit: prefer pre-increment even for scalar
penny 2016/09/23 23:50:57 Done.
+ }
+
+ if (!redirect)
+ return;
+
+ // Insert the redirection into |subkey_path|, at |insertion_point|.
+ subkey_path->insert((insertion_point - subkey_path->data()), insert_string);
+}
+
+//------------------------------------------------------------------------------
+// Reg Path Utilities - LOCAL
+//------------------------------------------------------------------------------
+
const wchar_t* ConvertRootKey(nt::ROOT_KEY root) {
nt::ROOT_KEY key = root;
@@ -198,6 +461,10 @@ bool ParseFullRegPath(const wchar_t* converted_root,
return true;
}
+//------------------------------------------------------------------------------
+// Misc wrapper functions - LOCAL
+//------------------------------------------------------------------------------
+
NTSTATUS CreateKeyWrapper(const std::wstring& key_path,
ACCESS_MASK access,
HANDLE* out_handle,
@@ -232,10 +499,16 @@ bool CreateRegKey(ROOT_KEY root,
if (!g_initialized)
InitNativeRegApi();
+ std::wstring redirected_key_path;
+ if (key_path) {
+ redirected_key_path = key_path;
+ ProcessRedirection(root, &redirected_key_path, access);
+ }
+
std::wstring current_path;
std::vector<std::wstring> subkeys;
- if (!ParseFullRegPath(ConvertRootKey(root), key_path, &current_path,
- &subkeys))
+ if (!ParseFullRegPath(ConvertRootKey(root), redirected_key_path.data(),
+ &current_path, &subkeys))
return false;
// Open the base hive first. It should always exist already.
@@ -312,8 +585,12 @@ bool OpenRegKey(ROOT_KEY root,
OBJECT_ATTRIBUTES obj = {};
*out_handle = INVALID_HANDLE_VALUE;
- std::wstring full_path(ConvertRootKey(root));
- full_path.append(key_path);
+ std::wstring full_path;
+ if (key_path) {
+ full_path = key_path;
+ ProcessRedirection(root, &full_path, access);
+ }
+ full_path.insert(0, ConvertRootKey(root));
g_rtl_init_unicode_string(&key_path_uni, full_path.c_str());
InitializeObjectAttributes(&obj, &key_path_uni, OBJ_CASE_INSENSITIVE, NULL,
@@ -345,10 +622,12 @@ bool DeleteRegKey(HANDLE key) {
}
// wrapper function
-bool DeleteRegKey(ROOT_KEY root, const wchar_t* key_path) {
+bool DeleteRegKey(ROOT_KEY root,
+ WOW64_OVERRIDE wow64_override,
+ const wchar_t* key_path) {
HANDLE key = INVALID_HANDLE_VALUE;
- if (!OpenRegKey(root, key_path, DELETE, &key, nullptr))
+ if (!OpenRegKey(root, key_path, DELETE | wow64_override, &key, nullptr))
return false;
if (!DeleteRegKey(key)) {
@@ -430,13 +709,13 @@ bool QueryRegValueDWORD(HANDLE key,
// wrapper function
bool QueryRegValueDWORD(ROOT_KEY root,
+ WOW64_OVERRIDE wow64_override,
const wchar_t* key_path,
const wchar_t* value_name,
DWORD* out_dword) {
HANDLE key = INVALID_HANDLE_VALUE;
- if (!OpenRegKey(root, key_path, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &key,
- NULL))
+ if (!OpenRegKey(root, key_path, KEY_QUERY_VALUE | wow64_override, &key, NULL))
return false;
if (!QueryRegValueDWORD(key, value_name, out_dword)) {
@@ -468,13 +747,13 @@ bool QueryRegValueSZ(HANDLE key,
// wrapper function
bool QueryRegValueSZ(ROOT_KEY root,
+ WOW64_OVERRIDE wow64_override,
const wchar_t* key_path,
const wchar_t* value_name,
std::wstring* out_sz) {
HANDLE key = INVALID_HANDLE_VALUE;
- if (!OpenRegKey(root, key_path, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &key,
- NULL))
+ if (!OpenRegKey(root, key_path, KEY_QUERY_VALUE | wow64_override, &key, NULL))
return false;
if (!QueryRegValueSZ(key, value_name, out_sz)) {
@@ -522,13 +801,13 @@ bool QueryRegValueMULTISZ(HANDLE key,
// wrapper function
bool QueryRegValueMULTISZ(ROOT_KEY root,
+ WOW64_OVERRIDE wow64_override,
const wchar_t* key_path,
const wchar_t* value_name,
std::vector<std::wstring>* out_multi_sz) {
HANDLE key = INVALID_HANDLE_VALUE;
- if (!OpenRegKey(root, key_path, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &key,
- NULL))
+ if (!OpenRegKey(root, key_path, KEY_QUERY_VALUE | wow64_override, &key, NULL))
return false;
if (!QueryRegValueMULTISZ(key, value_name, out_multi_sz)) {
@@ -574,12 +853,13 @@ bool SetRegValueDWORD(HANDLE key, const wchar_t* value_name, DWORD value) {
// wrapper function
bool SetRegValueDWORD(ROOT_KEY root,
+ WOW64_OVERRIDE wow64_override,
const wchar_t* key_path,
const wchar_t* value_name,
DWORD value) {
HANDLE key = INVALID_HANDLE_VALUE;
- if (!OpenRegKey(root, key_path, KEY_SET_VALUE | KEY_WOW64_32KEY, &key, NULL))
+ if (!OpenRegKey(root, key_path, KEY_SET_VALUE | wow64_override, &key, NULL))
return false;
if (!SetRegValueDWORD(key, value_name, value)) {
@@ -606,12 +886,13 @@ bool SetRegValueSZ(HANDLE key,
// wrapper function
bool SetRegValueSZ(ROOT_KEY root,
+ WOW64_OVERRIDE wow64_override,
const wchar_t* key_path,
const wchar_t* value_name,
const std::wstring& value) {
HANDLE key = INVALID_HANDLE_VALUE;
- if (!OpenRegKey(root, key_path, KEY_SET_VALUE | KEY_WOW64_32KEY, &key, NULL))
+ if (!OpenRegKey(root, key_path, KEY_SET_VALUE | wow64_override, &key, NULL))
return false;
if (!SetRegValueSZ(key, value_name, value)) {
@@ -655,12 +936,13 @@ bool SetRegValueMULTISZ(HANDLE key,
// wrapper function
bool SetRegValueMULTISZ(ROOT_KEY root,
+ WOW64_OVERRIDE wow64_override,
const wchar_t* key_path,
const wchar_t* value_name,
const std::vector<std::wstring>& values) {
HANDLE key = INVALID_HANDLE_VALUE;
- if (!OpenRegKey(root, key_path, KEY_SET_VALUE | KEY_WOW64_32KEY, &key, NULL))
+ if (!OpenRegKey(root, key_path, KEY_SET_VALUE | wow64_override, &key, NULL))
return false;
if (!SetRegValueMULTISZ(key, value_name, values)) {
@@ -682,4 +964,8 @@ const wchar_t* GetCurrentUserSidString() {
return g_current_user_sid_string;
}
+void ChangeDefaultWow64Redirection(bool default_on) {
+ g_defaultRedirection = default_on;
+}
+
}; // namespace nt

Powered by Google App Engine
This is Rietveld 408576698