// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/api/messaging/native_process_launcher.h" #include #include #include "base/command_line.h" #include "base/logging.h" #include "base/process/launch.h" #include "base/process/process.h" #include "base/strings/string16.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/win/registry.h" #include "base/win/scoped_handle.h" #include "crypto/random.h" namespace extensions { const wchar_t kNativeMessagingRegistryKey[] = L"SOFTWARE\\Google\\Chrome\\NativeMessagingHosts"; namespace { // Reads path to the native messaging host manifest from the registry. Returns // false if the path isn't found. bool GetManifestPathWithFlags(HKEY root_key, DWORD flags, const base::string16& host_name, base::string16* result) { base::win::RegKey key; if (key.Open(root_key, kNativeMessagingRegistryKey, KEY_QUERY_VALUE | flags) != ERROR_SUCCESS || key.OpenKey(host_name.c_str(), KEY_QUERY_VALUE | flags) != ERROR_SUCCESS || key.ReadValue(NULL, result) != ERROR_SUCCESS) { return false; } return true; } bool GetManifestPath(HKEY root_key, const base::string16& host_name, base::string16* result) { // First check 32-bit registry and then try 64-bit. return GetManifestPathWithFlags( root_key, KEY_WOW64_32KEY, host_name, result) || GetManifestPathWithFlags( root_key, KEY_WOW64_64KEY, host_name, result); } } // namespace // static base::FilePath NativeProcessLauncher::FindManifest( const std::string& host_name, bool allow_user_level_hosts, std::string* error_message) { base::string16 host_name_wide = base::UTF8ToUTF16(host_name); // Try to find the key in HKEY_LOCAL_MACHINE and then in HKEY_CURRENT_USER. base::string16 path_str; bool found = false; if (allow_user_level_hosts) found = GetManifestPath(HKEY_CURRENT_USER, host_name_wide, &path_str); if (!found) found = GetManifestPath(HKEY_LOCAL_MACHINE, host_name_wide, &path_str); if (!found) { *error_message = "Native messaging host " + host_name + " is not registered."; return base::FilePath(); } base::FilePath manifest_path(path_str); if (!manifest_path.IsAbsolute()) { *error_message = "Path to native messaging host manifest must be absolute."; return base::FilePath(); } return manifest_path; } // static bool NativeProcessLauncher::LaunchNativeProcess( const base::CommandLine& command_line, base::Process* process, base::File* read_file, base::File* write_file) { // Timeout for the IO pipes. const DWORD kTimeoutMs = 5000; // Windows will use default buffer size when 0 is passed to // CreateNamedPipeW(). const DWORD kBufferSize = 0; if (!command_line.GetProgram().IsAbsolute()) { LOG(ERROR) << "Native Messaging host path must be absolute."; return false; } uint64_t pipe_name_token; crypto::RandBytes(&pipe_name_token, sizeof(pipe_name_token)); base::string16 out_pipe_name = base::StringPrintf( L"\\\\.\\pipe\\chrome.nativeMessaging.out.%llx", pipe_name_token); base::string16 in_pipe_name = base::StringPrintf( L"\\\\.\\pipe\\chrome.nativeMessaging.in.%llx", pipe_name_token); // Create the pipes to read and write from. base::win::ScopedHandle stdout_pipe( CreateNamedPipeW(out_pipe_name.c_str(), PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE, 1, kBufferSize, kBufferSize, kTimeoutMs, NULL)); if (!stdout_pipe.IsValid()) { LOG(ERROR) << "Failed to create pipe " << out_pipe_name; return false; } base::win::ScopedHandle stdin_pipe( CreateNamedPipeW(in_pipe_name.c_str(), PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE, 1, kBufferSize, kBufferSize, kTimeoutMs, NULL)); if (!stdin_pipe.IsValid()) { LOG(ERROR) << "Failed to create pipe " << in_pipe_name; return false; } DWORD comspec_length = ::GetEnvironmentVariable(L"COMSPEC", NULL, 0); if (comspec_length == 0) { LOG(ERROR) << "COMSPEC is not set"; return false; } scoped_ptr comspec(new wchar_t[comspec_length]); ::GetEnvironmentVariable(L"COMSPEC", comspec.get(), comspec_length); base::string16 command_line_string = command_line.GetCommandLineString(); base::string16 command = base::StringPrintf( L"%ls /c %ls < %ls > %ls", comspec.get(), command_line_string.c_str(), in_pipe_name.c_str(), out_pipe_name.c_str()); base::LaunchOptions options; options.start_hidden = true; base::Process cmd_process = base::LaunchProcess(command.c_str(), options); if (!cmd_process.IsValid()) { LOG(ERROR) << "Error launching process " << command_line.GetProgram().MaybeAsASCII(); return false; } bool stdout_connected = ConnectNamedPipe(stdout_pipe.Get(), NULL) ? TRUE : GetLastError() == ERROR_PIPE_CONNECTED; bool stdin_connected = ConnectNamedPipe(stdin_pipe.Get(), NULL) ? TRUE : GetLastError() == ERROR_PIPE_CONNECTED; if (!stdout_connected || !stdin_connected) { cmd_process.Terminate(0, false); LOG(ERROR) << "Failed to connect IO pipes when starting " << command_line.GetProgram().MaybeAsASCII(); return false; } *process = std::move(cmd_process); *read_file = base::File::CreateForAsyncHandle(stdout_pipe.Take()); *write_file = base::File::CreateForAsyncHandle(stdin_pipe.Take()); return true; } } // namespace extensions