Home | History | Annotate | Download | only in messaging
      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