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 21 namespace extensions { 22 23 const wchar_t kNativeMessagingRegistryKey[] = 24 L"SOFTWARE\\Google\\Chrome\\NativeMessagingHosts"; 25 26 namespace { 27 28 // Reads path to the native messaging host manifest from the registry. Returns 29 // empty string if the path isn't found. 30 string16 GetManifestPath(const string16& native_host_name, DWORD flags) { 31 base::win::RegKey key; 32 string16 result; 33 34 if (key.Open(HKEY_LOCAL_MACHINE, kNativeMessagingRegistryKey, 35 KEY_QUERY_VALUE | flags) != ERROR_SUCCESS || 36 key.OpenKey(native_host_name.c_str(), 37 KEY_QUERY_VALUE | flags) != ERROR_SUCCESS || 38 key.ReadValue(NULL, &result) != ERROR_SUCCESS) { 39 return string16(); 40 } 41 42 return result; 43 } 44 45 } // namespace 46 47 // static 48 base::FilePath NativeProcessLauncher::FindManifest( 49 const std::string& native_host_name, 50 std::string* error_message) { 51 string16 native_host_name_wide = UTF8ToUTF16(native_host_name); 52 53 // First check 32-bit registry and then try 64-bit. 54 string16 manifest_path_str = 55 GetManifestPath(native_host_name_wide, KEY_WOW64_32KEY); 56 if (manifest_path_str.empty()) 57 manifest_path_str = GetManifestPath(native_host_name_wide, KEY_WOW64_64KEY); 58 59 if (manifest_path_str.empty()) { 60 *error_message = "Native messaging host " + native_host_name + 61 " is not registered"; 62 return base::FilePath(); 63 } 64 65 base::FilePath manifest_path(manifest_path_str); 66 if (!manifest_path.IsAbsolute()) { 67 *error_message = "Path to native messaging host manifest must be absolute."; 68 return base::FilePath(); 69 } 70 71 return manifest_path; 72 } 73 74 // static 75 bool NativeProcessLauncher::LaunchNativeProcess( 76 const CommandLine& command_line, 77 base::PlatformFile* read_file, 78 base::PlatformFile* write_file) { 79 // Timeout for the IO pipes. 80 const DWORD kTimeoutMs = 5000; 81 82 // Windows will use default buffer size when 0 is passed to 83 // CreateNamedPipeW(). 84 const DWORD kBufferSize = 0; 85 86 if (!command_line.GetProgram().IsAbsolute()) { 87 LOG(ERROR) << "Native Messaging host path must be absolute."; 88 return false; 89 } 90 91 uint64 pipe_name_token; 92 crypto::RandBytes(&pipe_name_token, sizeof(pipe_name_token)); 93 string16 out_pipe_name = base::StringPrintf( 94 L"\\\\.\\pipe\\chrome.nativeMessaging.out.%llx", pipe_name_token); 95 string16 in_pipe_name = base::StringPrintf( 96 L"\\\\.\\pipe\\chrome.nativeMessaging.in.%llx", pipe_name_token); 97 98 // Create the pipes to read and write from. 99 base::win::ScopedHandle stdout_pipe( 100 CreateNamedPipeW(out_pipe_name.c_str(), 101 PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED | 102 FILE_FLAG_FIRST_PIPE_INSTANCE, 103 PIPE_TYPE_BYTE, 1, kBufferSize, kBufferSize, 104 kTimeoutMs, NULL)); 105 if (!stdout_pipe.IsValid()) { 106 LOG(ERROR) << "Failed to create pipe " << out_pipe_name; 107 return false; 108 } 109 110 base::win::ScopedHandle stdin_pipe( 111 CreateNamedPipeW(in_pipe_name.c_str(), 112 PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED | 113 FILE_FLAG_FIRST_PIPE_INSTANCE, 114 PIPE_TYPE_BYTE, 1, kBufferSize, kBufferSize, 115 kTimeoutMs, NULL)); 116 if (!stdin_pipe.IsValid()) { 117 LOG(ERROR) << "Failed to create pipe " << in_pipe_name; 118 return false; 119 } 120 121 DWORD comspec_length = ::GetEnvironmentVariable(L"COMSPEC", NULL, 0); 122 if (comspec_length == 0) { 123 LOG(ERROR) << "COMSPEC is not set"; 124 return false; 125 } 126 scoped_ptr<wchar_t[]> comspec(new wchar_t[comspec_length]); 127 ::GetEnvironmentVariable(L"COMSPEC", comspec.get(), comspec_length); 128 129 string16 command_line_string = command_line.GetCommandLineString(); 130 131 // 'start' command has a moronic syntax: if first argument is quoted then it 132 // interprets it as a command title. Host path may need to be in quotes, so 133 // we always need to specify the title as the first argument. 134 string16 command = base::StringPrintf( 135 L"%ls /c start \"Chrome Native Messaging Host\" /b " 136 L"%ls < %ls > %ls", 137 comspec.get(), command_line_string.c_str(), 138 in_pipe_name.c_str(), out_pipe_name.c_str()); 139 140 base::LaunchOptions options; 141 options.start_hidden = true; 142 base::ProcessHandle cmd_handle; 143 if (!base::LaunchProcess(command.c_str(), options, &cmd_handle)) { 144 LOG(ERROR) << "Error launching process " 145 << command_line.GetProgram().MaybeAsASCII(); 146 return false; 147 } 148 149 bool stdout_connected = ConnectNamedPipe(stdout_pipe.Get(), NULL) ? 150 TRUE : GetLastError() == ERROR_PIPE_CONNECTED; 151 bool stdin_connected = ConnectNamedPipe(stdin_pipe.Get(), NULL) ? 152 TRUE : GetLastError() == ERROR_PIPE_CONNECTED; 153 if (!stdout_connected || !stdin_connected) { 154 base::KillProcess(cmd_handle, 0, false); 155 base::CloseProcessHandle(cmd_handle); 156 LOG(ERROR) << "Failed to connect IO pipes when starting " 157 << command_line.GetProgram().MaybeAsASCII(); 158 return false; 159 } 160 161 // Check that cmd.exe has completed with 0 exit code to make sure it was 162 // able to connect IO pipes. 163 int error_code; 164 if (!base::WaitForExitCodeWithTimeout( 165 cmd_handle, &error_code, 166 base::TimeDelta::FromMilliseconds(kTimeoutMs)) || 167 error_code != 0) { 168 LOG(ERROR) << "cmd.exe did not exit cleanly"; 169 base::KillProcess(cmd_handle, 0, false); 170 base::CloseProcessHandle(cmd_handle); 171 return false; 172 } 173 174 base::CloseProcessHandle(cmd_handle); 175 176 *read_file = stdout_pipe.Take(); 177 *write_file = stdin_pipe.Take(); 178 179 return true; 180 } 181 182 } // namespace extensions 183