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/extensions/api/messaging/native_process_launcher.h" 6 7#include <windows.h> 8 9#include "base/command_line.h" 10#include "base/logging.h" 11#include "base/process/kill.h" 12#include "base/process/launch.h" 13#include "base/strings/string16.h" 14#include "base/strings/string_number_conversions.h" 15#include "base/strings/stringprintf.h" 16#include "base/strings/utf_string_conversions.h" 17#include "base/win/registry.h" 18#include "base/win/scoped_handle.h" 19#include "crypto/random.h" 20 21namespace extensions { 22 23const wchar_t kNativeMessagingRegistryKey[] = 24 L"SOFTWARE\\Google\\Chrome\\NativeMessagingHosts"; 25 26namespace { 27 28// Reads path to the native messaging host manifest from the registry. Returns 29// false if the path isn't found. 30bool GetManifestPathWithFlags(HKEY root_key, 31 DWORD flags, 32 const base::string16& host_name, 33 base::string16* result) { 34 base::win::RegKey key; 35 36 if (key.Open(root_key, kNativeMessagingRegistryKey, 37 KEY_QUERY_VALUE | flags) != ERROR_SUCCESS || 38 key.OpenKey(host_name.c_str(), 39 KEY_QUERY_VALUE | flags) != ERROR_SUCCESS || 40 key.ReadValue(NULL, result) != ERROR_SUCCESS) { 41 return false; 42 } 43 44 return true; 45} 46 47bool GetManifestPath(HKEY root_key, 48 const base::string16& host_name, 49 base::string16* result) { 50 // First check 32-bit registry and then try 64-bit. 51 return GetManifestPathWithFlags( 52 root_key, KEY_WOW64_32KEY, host_name, result) || 53 GetManifestPathWithFlags( 54 root_key, KEY_WOW64_64KEY, host_name, result); 55} 56 57} // namespace 58 59// static 60base::FilePath NativeProcessLauncher::FindManifest( 61 const std::string& host_name, 62 bool allow_user_level_hosts, 63 std::string* error_message) { 64 base::string16 host_name_wide = base::UTF8ToUTF16(host_name); 65 66 // Try to find the key in HKEY_LOCAL_MACHINE and then in HKEY_CURRENT_USER. 67 base::string16 path_str; 68 bool found = false; 69 if (allow_user_level_hosts) 70 found = GetManifestPath(HKEY_CURRENT_USER, host_name_wide, &path_str); 71 if (!found) 72 found = GetManifestPath(HKEY_LOCAL_MACHINE, host_name_wide, &path_str); 73 74 if (!found) { 75 *error_message = 76 "Native messaging host " + host_name + " is not registered."; 77 return base::FilePath(); 78 } 79 80 base::FilePath manifest_path(path_str); 81 if (!manifest_path.IsAbsolute()) { 82 *error_message = "Path to native messaging host manifest must be absolute."; 83 return base::FilePath(); 84 } 85 86 return manifest_path; 87} 88 89// static 90bool NativeProcessLauncher::LaunchNativeProcess( 91 const CommandLine& command_line, 92 base::ProcessHandle* process_handle, 93 base::File* read_file, 94 base::File* write_file) { 95 // Timeout for the IO pipes. 96 const DWORD kTimeoutMs = 5000; 97 98 // Windows will use default buffer size when 0 is passed to 99 // CreateNamedPipeW(). 100 const DWORD kBufferSize = 0; 101 102 if (!command_line.GetProgram().IsAbsolute()) { 103 LOG(ERROR) << "Native Messaging host path must be absolute."; 104 return false; 105 } 106 107 uint64 pipe_name_token; 108 crypto::RandBytes(&pipe_name_token, sizeof(pipe_name_token)); 109 base::string16 out_pipe_name = base::StringPrintf( 110 L"\\\\.\\pipe\\chrome.nativeMessaging.out.%llx", pipe_name_token); 111 base::string16 in_pipe_name = base::StringPrintf( 112 L"\\\\.\\pipe\\chrome.nativeMessaging.in.%llx", pipe_name_token); 113 114 // Create the pipes to read and write from. 115 base::win::ScopedHandle stdout_pipe( 116 CreateNamedPipeW(out_pipe_name.c_str(), 117 PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED | 118 FILE_FLAG_FIRST_PIPE_INSTANCE, 119 PIPE_TYPE_BYTE, 1, kBufferSize, kBufferSize, 120 kTimeoutMs, NULL)); 121 if (!stdout_pipe.IsValid()) { 122 LOG(ERROR) << "Failed to create pipe " << out_pipe_name; 123 return false; 124 } 125 126 base::win::ScopedHandle stdin_pipe( 127 CreateNamedPipeW(in_pipe_name.c_str(), 128 PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED | 129 FILE_FLAG_FIRST_PIPE_INSTANCE, 130 PIPE_TYPE_BYTE, 1, kBufferSize, kBufferSize, 131 kTimeoutMs, NULL)); 132 if (!stdin_pipe.IsValid()) { 133 LOG(ERROR) << "Failed to create pipe " << in_pipe_name; 134 return false; 135 } 136 137 DWORD comspec_length = ::GetEnvironmentVariable(L"COMSPEC", NULL, 0); 138 if (comspec_length == 0) { 139 LOG(ERROR) << "COMSPEC is not set"; 140 return false; 141 } 142 scoped_ptr<wchar_t[]> comspec(new wchar_t[comspec_length]); 143 ::GetEnvironmentVariable(L"COMSPEC", comspec.get(), comspec_length); 144 145 base::string16 command_line_string = command_line.GetCommandLineString(); 146 147 base::string16 command = base::StringPrintf( 148 L"%ls /c %ls < %ls > %ls", 149 comspec.get(), command_line_string.c_str(), 150 in_pipe_name.c_str(), out_pipe_name.c_str()); 151 152 base::LaunchOptions options; 153 options.start_hidden = true; 154 base::win::ScopedHandle cmd_handle; 155 if (!base::LaunchProcess(command.c_str(), options, &cmd_handle)) { 156 LOG(ERROR) << "Error launching process " 157 << command_line.GetProgram().MaybeAsASCII(); 158 return false; 159 } 160 161 bool stdout_connected = ConnectNamedPipe(stdout_pipe.Get(), NULL) ? 162 TRUE : GetLastError() == ERROR_PIPE_CONNECTED; 163 bool stdin_connected = ConnectNamedPipe(stdin_pipe.Get(), NULL) ? 164 TRUE : GetLastError() == ERROR_PIPE_CONNECTED; 165 if (!stdout_connected || !stdin_connected) { 166 base::KillProcess(cmd_handle.Get(), 0, false); 167 LOG(ERROR) << "Failed to connect IO pipes when starting " 168 << command_line.GetProgram().MaybeAsASCII(); 169 return false; 170 } 171 172 *process_handle = cmd_handle.Take(); 173 *read_file = base::File(stdout_pipe.Take()); 174 *write_file = base::File(stdin_pipe.Take()); 175 176 return true; 177} 178 179} // namespace extensions 180