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