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