1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <sysexits.h>
18#include <unistd.h>  // for isatty()
19
20#include <string>
21#include <vector>
22
23#include <base/cancelable_callback.h>
24#include <base/command_line.h>
25#include <base/files/file_util.h>
26#include <base/memory/weak_ptr.h>
27#include <base/strings/string_number_conversions.h>
28#include <base/strings/string_tokenizer.h>
29#include <base/strings/string_util.h>
30#include <base/values.h>
31#include <brillo/daemons/dbus_daemon.h>
32#include <brillo/syslog_logging.h>
33
34#include "libcrosservice/dbus-proxies.h"
35
36using std::unique_ptr;
37
38namespace {
39
40const char kLibCrosProxyResolvedSignalInterface[] =
41    "org.chromium.CrashReporterLibcrosProxyResolvedInterface";
42const char kLibCrosProxyResolvedName[] = "ProxyResolved";
43const char kLibCrosServiceName[] = "org.chromium.LibCrosService";
44const char kNoProxy[] = "direct://";
45
46const int kTimeoutDefaultSeconds = 5;
47
48const char kHelp[] = "help";
49const char kQuiet[] = "quiet";
50const char kTimeout[] = "timeout";
51const char kVerbose[] = "verbose";
52// Help message to show when the --help command line switch is specified.
53const char kHelpMessage[] =
54    "Chromium OS Crash helper: proxy lister\n"
55    "\n"
56    "Available Switches:\n"
57    "  --quiet      Only print the proxies\n"
58    "  --verbose    Print additional messages even when not run from a TTY\n"
59    "  --timeout=N  Set timeout for browser resolving proxies (default is 5)\n"
60    "  --help       Show this help.\n";
61
62// Copied from src/update_engine/chrome_browser_proxy_resolver.cc
63// Parses the browser's answer for resolved proxies.  It returns a
64// list of strings, each of which is a resolved proxy.
65std::vector<std::string> ParseProxyString(const std::string& input) {
66  std::vector<std::string> ret;
67  // Some of this code taken from
68  // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_server.cc and
69  // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_list.cc
70  base::StringTokenizer entry_tok(input, ";");
71  while (entry_tok.GetNext()) {
72    std::string token = entry_tok.token();
73    base::TrimWhitespaceASCII(token, base::TRIM_ALL, &token);
74
75    // Start by finding the first space (if any).
76    std::string::iterator space;
77    for (space = token.begin(); space != token.end(); ++space) {
78      if (base::IsAsciiWhitespace(*space)) {
79        break;
80      }
81    }
82
83    std::string scheme = base::ToLowerASCII(std::string(token.begin(), space));
84    // Chrome uses "socks" to mean socks4 and "proxy" to mean http.
85    if (scheme == "socks")
86      scheme += "4";
87    else if (scheme == "proxy")
88      scheme = "http";
89    else if (scheme != "https" &&
90             scheme != "socks4" &&
91             scheme != "socks5" &&
92             scheme != "direct")
93      continue;  // Invalid proxy scheme
94
95    std::string host_and_port = std::string(space, token.end());
96    base::TrimWhitespaceASCII(host_and_port, base::TRIM_ALL, &host_and_port);
97    if (scheme != "direct" && host_and_port.empty())
98      continue;  // Must supply host/port when non-direct proxy used.
99    ret.push_back(scheme + "://" + host_and_port);
100  }
101  if (ret.empty() || *ret.rbegin() != kNoProxy)
102    ret.push_back(kNoProxy);
103  return ret;
104}
105
106// A class for interfacing with Chrome to resolve proxies for a given source
107// url.  The class is initialized with the given source url to check, the
108// signal interface and name that Chrome will reply to, and how long to wait
109// for the resolve request to timeout.  Once initialized, the Run() function
110// must be called, which blocks on the D-Bus call to Chrome.  The call returns
111// after either the timeout or the proxy has been resolved.  The resolved
112// proxies can then be accessed through the proxies() function.
113class ProxyResolver : public brillo::DBusDaemon {
114 public:
115  ProxyResolver(const std::string& source_url,
116                const std::string& signal_interface,
117                const std::string& signal_name,
118                base::TimeDelta timeout)
119      : source_url_(source_url),
120        signal_interface_(signal_interface),
121        signal_name_(signal_name),
122        timeout_(timeout),
123        weak_ptr_factory_(this),
124        timeout_callback_(base::Bind(&ProxyResolver::HandleBrowserTimeout,
125                                     weak_ptr_factory_.GetWeakPtr())) {}
126
127  ~ProxyResolver() override {}
128
129  const std::vector<std::string>& proxies() {
130    return proxies_;
131  }
132
133  int Run() override {
134    // Add task for if the browser proxy call times out.
135    base::MessageLoop::current()->PostDelayedTask(
136        FROM_HERE,
137        timeout_callback_.callback(),
138        timeout_);
139
140    return brillo::DBusDaemon::Run();
141  }
142
143 protected:
144  // If the browser times out, quit the run loop.
145  void HandleBrowserTimeout() {
146    LOG(ERROR) << "Timeout while waiting for browser to resolve proxy";
147    Quit();
148  }
149
150  // If the signal handler connects successfully, call the browser's
151  // ResolveNetworkProxy D-Bus method.  Otherwise, don't do anything and let
152  // the timeout task quit the run loop.
153  void HandleDBusSignalConnected(const std::string& interface,
154                                 const std::string& signal,
155                                 bool success) {
156    if (!success) {
157      LOG(ERROR) << "Could not connect to signal " << interface << "."
158                 << signal;
159      timeout_callback_.Cancel();
160      Quit();
161      return;
162    }
163
164    brillo::ErrorPtr error;
165    call_proxy_->ResolveNetworkProxy(source_url_,
166                                     signal_interface_,
167                                     signal_name_,
168                                     &error);
169
170    if (error) {
171      LOG(ERROR) << "Call to ResolveNetworkProxy failed: "
172                 << error->GetMessage();
173      timeout_callback_.Cancel();
174      Quit();
175    }
176  }
177
178  // Handle incoming ProxyResolved signal.
179  void HandleProxyResolvedSignal(const std::string& source_url,
180                                 const std::string& proxy_info,
181                                 const std::string& error_message) {
182    timeout_callback_.Cancel();
183    proxies_ = ParseProxyString(proxy_info);
184    LOG(INFO) << "Found proxies via browser signal: "
185              << base::JoinString(proxies_, "x");
186
187    Quit();
188  }
189
190  int OnInit() override {
191    int return_code = brillo::DBusDaemon::OnInit();
192    if (return_code != EX_OK)
193      return return_code;
194
195    // Initialize D-Bus proxies.
196    call_proxy_.reset(
197        new org::chromium::LibCrosServiceInterfaceProxy(bus_,
198                                                        kLibCrosServiceName));
199    signal_proxy_.reset(
200        new org::chromium::CrashReporterLibcrosProxyResolvedInterfaceProxy(
201            bus_,
202            kLibCrosServiceName));
203
204    // Set up the D-Bus signal handler.
205    // TODO(crbug.com/446115): Update ResolveNetworkProxy call to use an
206    //     asynchronous return value rather than a return signal.
207    signal_proxy_->RegisterProxyResolvedSignalHandler(
208        base::Bind(&ProxyResolver::HandleProxyResolvedSignal,
209                   weak_ptr_factory_.GetWeakPtr()),
210        base::Bind(&ProxyResolver::HandleDBusSignalConnected,
211                   weak_ptr_factory_.GetWeakPtr()));
212
213    return EX_OK;
214  }
215
216 private:
217  unique_ptr<org::chromium::LibCrosServiceInterfaceProxy> call_proxy_;
218  unique_ptr<org::chromium::CrashReporterLibcrosProxyResolvedInterfaceProxy>
219      signal_proxy_;
220
221  const std::string source_url_;
222  const std::string signal_interface_;
223  const std::string signal_name_;
224  base::TimeDelta timeout_;
225
226  std::vector<std::string> proxies_;
227  base::WeakPtrFactory<ProxyResolver> weak_ptr_factory_;
228
229  base::CancelableClosure timeout_callback_;
230
231  DISALLOW_COPY_AND_ASSIGN(ProxyResolver);
232};
233
234static bool ShowBrowserProxies(std::string url, base::TimeDelta timeout) {
235  // Initialize and run the proxy resolver to watch for signals.
236  ProxyResolver resolver(url,
237                         kLibCrosProxyResolvedSignalInterface,
238                         kLibCrosProxyResolvedName,
239                         timeout);
240  resolver.Run();
241
242  std::vector<std::string> proxies = resolver.proxies();
243
244  // If proxies is empty, then the timeout was reached waiting for the proxy
245  // resolved signal.  If no proxies are defined, proxies will be populated
246  // with "direct://".
247  if (proxies.empty())
248    return false;
249
250  for (const auto& proxy : proxies) {
251    printf("%s\n", proxy.c_str());
252  }
253  return true;
254}
255
256}  // namespace
257
258int main(int argc, char *argv[]) {
259  base::CommandLine::Init(argc, argv);
260  base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
261
262  if (cl->HasSwitch(kHelp)) {
263    LOG(INFO) << kHelpMessage;
264    return 0;
265  }
266
267  bool quiet = cl->HasSwitch(kQuiet);
268  bool verbose = cl->HasSwitch(kVerbose);
269
270  int timeout = kTimeoutDefaultSeconds;
271  std::string str_timeout = cl->GetSwitchValueASCII(kTimeout);
272  if (!str_timeout.empty() && !base::StringToInt(str_timeout, &timeout)) {
273    LOG(ERROR) << "Invalid timeout value: " << str_timeout;
274    return 1;
275  }
276
277  // Default to logging to syslog.
278  int init_flags = brillo::kLogToSyslog;
279  // Log to stderr if a TTY (and "-quiet" wasn't passed), or if "-verbose"
280  // was passed.
281
282  if ((!quiet && isatty(STDERR_FILENO)) || verbose)
283    init_flags |= brillo::kLogToStderr;
284  brillo::InitLog(init_flags);
285
286  std::string url;
287  base::CommandLine::StringVector urls = cl->GetArgs();
288  if (!urls.empty()) {
289    url = urls[0];
290    LOG(INFO) << "Resolving proxies for URL: " << url;
291  } else {
292    LOG(INFO) << "Resolving proxies without URL";
293  }
294
295  if (!ShowBrowserProxies(url, base::TimeDelta::FromSeconds(timeout))) {
296    LOG(ERROR) << "Error resolving proxies via the browser";
297    LOG(INFO) << "Assuming direct proxy";
298    printf("%s\n", kNoProxy);
299  }
300
301  return 0;
302}
303