1 // Copyright 2012 The Chromium Authors
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 "net/proxy_resolution/proxy_config_service_linux.h"
6
7 #include <errno.h>
8 #include <limits.h>
9 #include <sys/inotify.h>
10 #include <unistd.h>
11
12 #include <map>
13 #include <memory>
14 #include <utility>
15
16 #include "base/files/file_descriptor_watcher_posix.h"
17 #include "base/files/file_path.h"
18 #include "base/files/file_util.h"
19 #include "base/files/scoped_file.h"
20 #include "base/functional/bind.h"
21 #include "base/logging.h"
22 #include "base/memory/ptr_util.h"
23 #include "base/memory/raw_ptr.h"
24 #include "base/nix/xdg_util.h"
25 #include "base/observer_list.h"
26 #include "base/strings/string_number_conversions.h"
27 #include "base/strings/string_split.h"
28 #include "base/strings/string_tokenizer.h"
29 #include "base/strings/string_util.h"
30 #include "base/task/sequenced_task_runner.h"
31 #include "base/task/single_thread_task_runner.h"
32 #include "base/task/task_traits.h"
33 #include "base/task/thread_pool.h"
34 #include "base/threading/thread_restrictions.h"
35 #include "base/timer/timer.h"
36 #include "net/base/proxy_server.h"
37 #include "net/base/proxy_string_util.h"
38
39 #if defined(USE_GIO)
40 #include <gio/gio.h>
41 #endif // defined(USE_GIO)
42
43 namespace net {
44
45 class ScopedAllowBlockingForSettingGetter : public base::ScopedAllowBlocking {};
46
47 namespace {
48
49 // This turns all rules with a hostname into wildcard matches, which will
50 // match not just the indicated hostname but also any hostname that ends with
51 // it.
RewriteRulesForSuffixMatching(ProxyBypassRules * out)52 void RewriteRulesForSuffixMatching(ProxyBypassRules* out) {
53 // Prepend a wildcard (*) to any hostname based rules, provided it isn't an IP
54 // address.
55 for (size_t i = 0; i < out->rules().size(); ++i) {
56 if (!out->rules()[i]->IsHostnamePatternRule())
57 continue;
58
59 const SchemeHostPortMatcherHostnamePatternRule* prev_rule =
60 static_cast<const SchemeHostPortMatcherHostnamePatternRule*>(
61 out->rules()[i].get());
62 out->ReplaceRule(i, prev_rule->GenerateSuffixMatchingRule());
63 }
64 }
65
66 // Given a proxy hostname from a setting, returns that hostname with
67 // an appropriate proxy server scheme prefix.
68 // scheme indicates the desired proxy scheme: usually http, with
69 // socks 4 or 5 as special cases.
70 // TODO(arindam): Remove URI string manipulation by using MapUrlSchemeToProxy.
FixupProxyHostScheme(ProxyServer::Scheme scheme,std::string host)71 std::string FixupProxyHostScheme(ProxyServer::Scheme scheme,
72 std::string host) {
73 if (scheme == ProxyServer::SCHEME_SOCKS5 &&
74 base::StartsWith(host, "socks4://",
75 base::CompareCase::INSENSITIVE_ASCII)) {
76 // We default to socks 5, but if the user specifically set it to
77 // socks4://, then use that.
78 scheme = ProxyServer::SCHEME_SOCKS4;
79 }
80 // Strip the scheme if any.
81 std::string::size_type colon = host.find("://");
82 if (colon != std::string::npos)
83 host = host.substr(colon + 3);
84 // If a username and perhaps password are specified, give a warning.
85 std::string::size_type at_sign = host.find("@");
86 // Should this be supported?
87 if (at_sign != std::string::npos) {
88 // ProxyConfig does not support authentication parameters, but Chrome
89 // will prompt for the password later. Disregard the
90 // authentication parameters and continue with this hostname.
91 LOG(WARNING) << "Proxy authentication parameters ignored, see bug 16709";
92 host = host.substr(at_sign + 1);
93 }
94 // If this is a socks proxy, prepend a scheme so as to tell
95 // ProxyServer. This also allows ProxyServer to choose the right
96 // default port.
97 if (scheme == ProxyServer::SCHEME_SOCKS4)
98 host = "socks4://" + host;
99 else if (scheme == ProxyServer::SCHEME_SOCKS5)
100 host = "socks5://" + host;
101 // If there is a trailing slash, remove it so |host| will parse correctly
102 // even if it includes a port number (since the slash is not numeric).
103 if (!host.empty() && host.back() == '/')
104 host.resize(host.length() - 1);
105 return host;
106 }
107
GetConfigOrDirect(const absl::optional<ProxyConfigWithAnnotation> & optional_config)108 ProxyConfigWithAnnotation GetConfigOrDirect(
109 const absl::optional<ProxyConfigWithAnnotation>& optional_config) {
110 if (optional_config)
111 return optional_config.value();
112
113 ProxyConfigWithAnnotation config = ProxyConfigWithAnnotation::CreateDirect();
114 return config;
115 }
116
117 } // namespace
118
119 ProxyConfigServiceLinux::Delegate::~Delegate() = default;
120
GetProxyFromEnvVarForScheme(base::StringPiece variable,ProxyServer::Scheme scheme,ProxyServer * result_server)121 bool ProxyConfigServiceLinux::Delegate::GetProxyFromEnvVarForScheme(
122 base::StringPiece variable,
123 ProxyServer::Scheme scheme,
124 ProxyServer* result_server) {
125 std::string env_value;
126 if (!env_var_getter_->GetVar(variable, &env_value))
127 return false;
128
129 if (env_value.empty())
130 return false;
131
132 env_value = FixupProxyHostScheme(scheme, env_value);
133 ProxyServer proxy_server =
134 ProxyUriToProxyServer(env_value, ProxyServer::SCHEME_HTTP);
135 if (proxy_server.is_valid() && !proxy_server.is_direct()) {
136 *result_server = proxy_server;
137 return true;
138 }
139 LOG(ERROR) << "Failed to parse environment variable " << variable;
140 return false;
141 }
142
GetProxyFromEnvVar(base::StringPiece variable,ProxyServer * result_server)143 bool ProxyConfigServiceLinux::Delegate::GetProxyFromEnvVar(
144 base::StringPiece variable,
145 ProxyServer* result_server) {
146 return GetProxyFromEnvVarForScheme(variable, ProxyServer::SCHEME_HTTP,
147 result_server);
148 }
149
150 absl::optional<ProxyConfigWithAnnotation>
GetConfigFromEnv()151 ProxyConfigServiceLinux::Delegate::GetConfigFromEnv() {
152 ProxyConfig config;
153
154 // Check for automatic configuration first, in
155 // "auto_proxy". Possibly only the "environment_proxy" firefox
156 // extension has ever used this, but it still sounds like a good
157 // idea.
158 std::string auto_proxy;
159 if (env_var_getter_->GetVar("auto_proxy", &auto_proxy)) {
160 if (auto_proxy.empty()) {
161 // Defined and empty => autodetect
162 config.set_auto_detect(true);
163 } else {
164 // specified autoconfig URL
165 config.set_pac_url(GURL(auto_proxy));
166 }
167 return ProxyConfigWithAnnotation(
168 config, NetworkTrafficAnnotationTag(traffic_annotation_));
169 }
170 // "all_proxy" is a shortcut to avoid defining {http,https,ftp}_proxy.
171 ProxyServer proxy_server;
172 if (GetProxyFromEnvVar("all_proxy", &proxy_server)) {
173 config.proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST;
174 config.proxy_rules().single_proxies.SetSingleProxyServer(proxy_server);
175 } else {
176 bool have_http = GetProxyFromEnvVar("http_proxy", &proxy_server);
177 if (have_http)
178 config.proxy_rules().proxies_for_http.SetSingleProxyServer(proxy_server);
179 // It would be tempting to let http_proxy apply for all protocols
180 // if https_proxy and ftp_proxy are not defined. Googling turns up
181 // several documents that mention only http_proxy. But then the
182 // user really might not want to proxy https. And it doesn't seem
183 // like other apps do this. So we will refrain.
184 bool have_https = GetProxyFromEnvVar("https_proxy", &proxy_server);
185 if (have_https)
186 config.proxy_rules().proxies_for_https.SetSingleProxyServer(proxy_server);
187 bool have_ftp = GetProxyFromEnvVar("ftp_proxy", &proxy_server);
188 if (have_ftp)
189 config.proxy_rules().proxies_for_ftp.SetSingleProxyServer(proxy_server);
190 if (have_http || have_https || have_ftp) {
191 // mustn't change type unless some rules are actually set.
192 config.proxy_rules().type =
193 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME;
194 }
195 }
196 if (config.proxy_rules().empty()) {
197 // If the above were not defined, try for socks.
198 // For environment variables, we default to version 5, per the gnome
199 // documentation: http://library.gnome.org/devel/gnet/stable/gnet-socks.html
200 ProxyServer::Scheme scheme = ProxyServer::SCHEME_SOCKS5;
201 std::string env_version;
202 if (env_var_getter_->GetVar("SOCKS_VERSION", &env_version)
203 && env_version == "4")
204 scheme = ProxyServer::SCHEME_SOCKS4;
205 if (GetProxyFromEnvVarForScheme("SOCKS_SERVER", scheme, &proxy_server)) {
206 config.proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST;
207 config.proxy_rules().single_proxies.SetSingleProxyServer(proxy_server);
208 }
209 }
210 // Look for the proxy bypass list.
211 std::string no_proxy;
212 env_var_getter_->GetVar("no_proxy", &no_proxy);
213 if (config.proxy_rules().empty()) {
214 // Having only "no_proxy" set, presumably to "*", makes it
215 // explicit that env vars do specify a configuration: having no
216 // rules specified only means the user explicitly asks for direct
217 // connections.
218 return !no_proxy.empty()
219 ? ProxyConfigWithAnnotation(
220 config, NetworkTrafficAnnotationTag(traffic_annotation_))
221 : absl::optional<ProxyConfigWithAnnotation>();
222 }
223 // Note that this uses "suffix" matching. So a bypass of "google.com"
224 // is understood to mean a bypass of "*google.com".
225 config.proxy_rules().bypass_rules.ParseFromString(no_proxy);
226 RewriteRulesForSuffixMatching(&config.proxy_rules().bypass_rules);
227
228 return ProxyConfigWithAnnotation(
229 config, NetworkTrafficAnnotationTag(traffic_annotation_));
230 }
231
232 namespace {
233
234 const int kDebounceTimeoutMilliseconds = 250;
235
236 #if defined(USE_GIO)
237 const char kProxyGSettingsSchema[] = "org.gnome.system.proxy";
238
239 // This setting getter uses gsettings, as used in most GNOME 3 desktops.
240 class SettingGetterImplGSettings
241 : public ProxyConfigServiceLinux::SettingGetter {
242 public:
SettingGetterImplGSettings()243 SettingGetterImplGSettings()
244 : debounce_timer_(std::make_unique<base::OneShotTimer>()) {}
245
246 SettingGetterImplGSettings(const SettingGetterImplGSettings&) = delete;
247 SettingGetterImplGSettings& operator=(const SettingGetterImplGSettings&) =
248 delete;
249
~SettingGetterImplGSettings()250 ~SettingGetterImplGSettings() override {
251 // client_ should have been released before now, from
252 // Delegate::OnDestroy(), while running on the UI thread. However
253 // on exiting the process, it may happen that
254 // Delegate::OnDestroy() task is left pending on the glib loop
255 // after the loop was quit, and pending tasks may then be deleted
256 // without being run.
257 if (client_) {
258 // gsettings client was not cleaned up.
259 if (task_runner_->RunsTasksInCurrentSequence()) {
260 // We are on the UI thread so we can clean it safely.
261 VLOG(1) << "~SettingGetterImplGSettings: releasing gsettings client";
262 ShutDown();
263 } else {
264 LOG(WARNING) << "~SettingGetterImplGSettings: leaking gsettings client";
265 client_ = nullptr;
266 }
267 }
268 DCHECK(!client_);
269 }
270
271 // CheckVersion() must be called *before* Init()!
272 bool CheckVersion(base::Environment* env);
273
Init(const scoped_refptr<base::SingleThreadTaskRunner> & glib_task_runner)274 bool Init(const scoped_refptr<base::SingleThreadTaskRunner>& glib_task_runner)
275 override {
276 DCHECK(glib_task_runner->RunsTasksInCurrentSequence());
277 DCHECK(!client_);
278 DCHECK(!task_runner_.get());
279
280 if (!g_settings_schema_source_lookup(g_settings_schema_source_get_default(),
281 kProxyGSettingsSchema, TRUE) ||
282 !(client_ = g_settings_new(kProxyGSettingsSchema))) {
283 // It's not clear whether/when this can return NULL.
284 LOG(ERROR) << "Unable to create a gsettings client";
285 return false;
286 }
287 task_runner_ = glib_task_runner;
288 // We assume these all work if the above call worked.
289 http_client_ = g_settings_get_child(client_, "http");
290 https_client_ = g_settings_get_child(client_, "https");
291 ftp_client_ = g_settings_get_child(client_, "ftp");
292 socks_client_ = g_settings_get_child(client_, "socks");
293 DCHECK(http_client_ && https_client_ && ftp_client_ && socks_client_);
294 return true;
295 }
296
ShutDown()297 void ShutDown() override {
298 if (client_) {
299 DCHECK(task_runner_->RunsTasksInCurrentSequence());
300 // This also disables gsettings notifications.
301 g_object_unref(socks_client_);
302 g_object_unref(ftp_client_);
303 g_object_unref(https_client_);
304 g_object_unref(http_client_);
305 g_object_unref(client_);
306 // We only need to null client_ because it's the only one that we check.
307 client_ = nullptr;
308 task_runner_ = nullptr;
309 }
310 debounce_timer_.reset();
311 }
312
SetUpNotifications(ProxyConfigServiceLinux::Delegate * delegate)313 bool SetUpNotifications(
314 ProxyConfigServiceLinux::Delegate* delegate) override {
315 DCHECK(client_);
316 DCHECK(task_runner_->RunsTasksInCurrentSequence());
317 notify_delegate_ = delegate;
318 // We could watch for the change-event signal instead of changed, but
319 // since we have to watch more than one object, we'd still have to
320 // debounce change notifications. This is conceptually simpler.
321 g_signal_connect(G_OBJECT(client_.get()), "changed",
322 G_CALLBACK(OnGSettingsChangeNotification), this);
323 g_signal_connect(G_OBJECT(http_client_.get()), "changed",
324 G_CALLBACK(OnGSettingsChangeNotification), this);
325 g_signal_connect(G_OBJECT(https_client_.get()), "changed",
326 G_CALLBACK(OnGSettingsChangeNotification), this);
327 g_signal_connect(G_OBJECT(ftp_client_.get()), "changed",
328 G_CALLBACK(OnGSettingsChangeNotification), this);
329 g_signal_connect(G_OBJECT(socks_client_.get()), "changed",
330 G_CALLBACK(OnGSettingsChangeNotification), this);
331 // Simulate a change to avoid possibly losing updates before this point.
332 OnChangeNotification();
333 return true;
334 }
335
GetNotificationTaskRunner()336 const scoped_refptr<base::SequencedTaskRunner>& GetNotificationTaskRunner()
337 override {
338 return task_runner_;
339 }
340
GetString(StringSetting key,std::string * result)341 bool GetString(StringSetting key, std::string* result) override {
342 DCHECK(client_);
343 switch (key) {
344 case PROXY_MODE:
345 return GetStringByPath(client_, "mode", result);
346 case PROXY_AUTOCONF_URL:
347 return GetStringByPath(client_, "autoconfig-url", result);
348 case PROXY_HTTP_HOST:
349 return GetStringByPath(http_client_, "host", result);
350 case PROXY_HTTPS_HOST:
351 return GetStringByPath(https_client_, "host", result);
352 case PROXY_FTP_HOST:
353 return GetStringByPath(ftp_client_, "host", result);
354 case PROXY_SOCKS_HOST:
355 return GetStringByPath(socks_client_, "host", result);
356 }
357 return false; // Placate compiler.
358 }
GetBool(BoolSetting key,bool * result)359 bool GetBool(BoolSetting key, bool* result) override {
360 DCHECK(client_);
361 switch (key) {
362 case PROXY_USE_HTTP_PROXY:
363 // Although there is an "enabled" boolean in http_client_, it is not set
364 // to true by the proxy config utility. We ignore it and return false.
365 return false;
366 case PROXY_USE_SAME_PROXY:
367 // Similarly, although there is a "use-same-proxy" boolean in client_,
368 // it is never set to false by the proxy config utility. We ignore it.
369 return false;
370 case PROXY_USE_AUTHENTICATION:
371 // There is also no way to set this in the proxy config utility, but it
372 // doesn't hurt us to get the actual setting (unlike the two above).
373 return GetBoolByPath(http_client_, "use-authentication", result);
374 }
375 return false; // Placate compiler.
376 }
GetInt(IntSetting key,int * result)377 bool GetInt(IntSetting key, int* result) override {
378 DCHECK(client_);
379 switch (key) {
380 case PROXY_HTTP_PORT:
381 return GetIntByPath(http_client_, "port", result);
382 case PROXY_HTTPS_PORT:
383 return GetIntByPath(https_client_, "port", result);
384 case PROXY_FTP_PORT:
385 return GetIntByPath(ftp_client_, "port", result);
386 case PROXY_SOCKS_PORT:
387 return GetIntByPath(socks_client_, "port", result);
388 }
389 return false; // Placate compiler.
390 }
GetStringList(StringListSetting key,std::vector<std::string> * result)391 bool GetStringList(StringListSetting key,
392 std::vector<std::string>* result) override {
393 DCHECK(client_);
394 switch (key) {
395 case PROXY_IGNORE_HOSTS:
396 return GetStringListByPath(client_, "ignore-hosts", result);
397 }
398 return false; // Placate compiler.
399 }
400
BypassListIsReversed()401 bool BypassListIsReversed() override {
402 // This is a KDE-specific setting.
403 return false;
404 }
405
UseSuffixMatching()406 bool UseSuffixMatching() override { return false; }
407
408 private:
GetStringByPath(GSettings * client,base::StringPiece key,std::string * result)409 bool GetStringByPath(GSettings* client,
410 base::StringPiece key,
411 std::string* result) {
412 DCHECK(task_runner_->RunsTasksInCurrentSequence());
413 gchar* value = g_settings_get_string(client, key.data());
414 if (!value)
415 return false;
416 *result = value;
417 g_free(value);
418 return true;
419 }
GetBoolByPath(GSettings * client,base::StringPiece key,bool * result)420 bool GetBoolByPath(GSettings* client, base::StringPiece key, bool* result) {
421 DCHECK(task_runner_->RunsTasksInCurrentSequence());
422 *result = static_cast<bool>(g_settings_get_boolean(client, key.data()));
423 return true;
424 }
GetIntByPath(GSettings * client,base::StringPiece key,int * result)425 bool GetIntByPath(GSettings* client, base::StringPiece key, int* result) {
426 DCHECK(task_runner_->RunsTasksInCurrentSequence());
427 *result = g_settings_get_int(client, key.data());
428 return true;
429 }
GetStringListByPath(GSettings * client,base::StringPiece key,std::vector<std::string> * result)430 bool GetStringListByPath(GSettings* client,
431 base::StringPiece key,
432 std::vector<std::string>* result) {
433 DCHECK(task_runner_->RunsTasksInCurrentSequence());
434 gchar** list = g_settings_get_strv(client, key.data());
435 if (!list)
436 return false;
437 for (size_t i = 0; list[i]; ++i) {
438 result->push_back(static_cast<char*>(list[i]));
439 g_free(list[i]);
440 }
441 g_free(list);
442 return true;
443 }
444
445 // This is the callback from the debounce timer.
OnDebouncedNotification()446 void OnDebouncedNotification() {
447 DCHECK(task_runner_->RunsTasksInCurrentSequence());
448 CHECK(notify_delegate_);
449 // Forward to a method on the proxy config service delegate object.
450 notify_delegate_->OnCheckProxyConfigSettings();
451 }
452
OnChangeNotification()453 void OnChangeNotification() {
454 // We don't use Reset() because the timer may not yet be running.
455 // (In that case Stop() is a no-op.)
456 debounce_timer_->Stop();
457 debounce_timer_->Start(
458 FROM_HERE, base::Milliseconds(kDebounceTimeoutMilliseconds), this,
459 &SettingGetterImplGSettings::OnDebouncedNotification);
460 }
461
462 // gsettings notification callback, dispatched on the default glib main loop.
OnGSettingsChangeNotification(GSettings * client,gchar * key,gpointer user_data)463 static void OnGSettingsChangeNotification(GSettings* client, gchar* key,
464 gpointer user_data) {
465 VLOG(1) << "gsettings change notification for key " << key;
466 // We don't track which key has changed, just that something did change.
467 SettingGetterImplGSettings* setting_getter =
468 reinterpret_cast<SettingGetterImplGSettings*>(user_data);
469 setting_getter->OnChangeNotification();
470 }
471
472 raw_ptr<GSettings> client_ = nullptr;
473 raw_ptr<GSettings> http_client_ = nullptr;
474 raw_ptr<GSettings> https_client_ = nullptr;
475 raw_ptr<GSettings> ftp_client_ = nullptr;
476 raw_ptr<GSettings> socks_client_ = nullptr;
477 raw_ptr<ProxyConfigServiceLinux::Delegate> notify_delegate_ = nullptr;
478 std::unique_ptr<base::OneShotTimer> debounce_timer_;
479
480 // Task runner for the thread that we make gsettings calls on. It should
481 // be the UI thread and all our methods should be called on this
482 // thread. Only for assertions.
483 scoped_refptr<base::SequencedTaskRunner> task_runner_;
484 };
485
CheckVersion(base::Environment * env)486 bool SettingGetterImplGSettings::CheckVersion(
487 base::Environment* env) {
488 // CheckVersion() must be called *before* Init()!
489 DCHECK(!client_);
490
491 GSettings* client = nullptr;
492 if (g_settings_schema_source_lookup(g_settings_schema_source_get_default(),
493 kProxyGSettingsSchema, TRUE)) {
494 client = g_settings_new(kProxyGSettingsSchema);
495 }
496 if (!client) {
497 VLOG(1) << "Cannot create gsettings client.";
498 return false;
499 }
500 g_object_unref(client);
501
502 VLOG(1) << "All gsettings tests OK. Will get proxy config from gsettings.";
503 return true;
504 }
505 #endif // defined(USE_GIO)
506
507 // Converts |value| from a decimal string to an int. If there was a failure
508 // parsing, returns |default_value|.
StringToIntOrDefault(base::StringPiece value,int default_value)509 int StringToIntOrDefault(base::StringPiece value, int default_value) {
510 int result;
511 if (base::StringToInt(value, &result))
512 return result;
513 return default_value;
514 }
515
516 // This is the KDE version that reads kioslaverc and simulates gsettings.
517 // Doing this allows the main Delegate code, as well as the unit tests
518 // for it, to stay the same - and the settings map fairly well besides.
519 class SettingGetterImplKDE : public ProxyConfigServiceLinux::SettingGetter {
520 public:
SettingGetterImplKDE(base::Environment * env_var_getter)521 explicit SettingGetterImplKDE(base::Environment* env_var_getter)
522 : debounce_timer_(std::make_unique<base::OneShotTimer>()),
523 env_var_getter_(env_var_getter) {
524 // This has to be called on the UI thread (http://crbug.com/69057).
525 ScopedAllowBlockingForSettingGetter allow_blocking;
526
527 // Derive the location(s) of the kde config dir from the environment.
528 std::string home;
529 if (env_var_getter->GetVar("KDEHOME", &home) && !home.empty()) {
530 // $KDEHOME is set. Use it unconditionally.
531 kde_config_dirs_.emplace_back(KDEHomeToConfigPath(base::FilePath(home)));
532 } else {
533 // $KDEHOME is unset. Try to figure out what to use. This seems to be
534 // the common case on most distributions.
535 if (!env_var_getter->GetVar(base::env_vars::kHome, &home))
536 // User has no $HOME? Give up. Later we'll report the failure.
537 return;
538 auto desktop = base::nix::GetDesktopEnvironment(env_var_getter);
539 if (desktop == base::nix::DESKTOP_ENVIRONMENT_KDE3) {
540 // KDE3 always uses .kde for its configuration.
541 base::FilePath kde_path = base::FilePath(home).Append(".kde");
542 kde_config_dirs_.emplace_back(KDEHomeToConfigPath(kde_path));
543 } else if (desktop == base::nix::DESKTOP_ENVIRONMENT_KDE4) {
544 // Some distributions patch KDE4 to use .kde4 instead of .kde, so that
545 // both can be installed side-by-side. Sadly they don't all do this, and
546 // they don't always do this: some distributions have started switching
547 // back as well. So if there is a .kde4 directory, check the timestamps
548 // of the config directories within and use the newest one.
549 // Note that we should currently be running in the UI thread, because in
550 // the gsettings version, that is the only thread that can access the
551 // proxy settings (a gsettings restriction). As noted below, the initial
552 // read of the proxy settings will be done in this thread anyway, so we
553 // check for .kde4 here in this thread as well.
554 base::FilePath kde3_path = base::FilePath(home).Append(".kde");
555 base::FilePath kde3_config = KDEHomeToConfigPath(kde3_path);
556 base::FilePath kde4_path = base::FilePath(home).Append(".kde4");
557 base::FilePath kde4_config = KDEHomeToConfigPath(kde4_path);
558 bool use_kde4 = false;
559 if (base::DirectoryExists(kde4_path)) {
560 base::File::Info kde3_info;
561 base::File::Info kde4_info;
562 if (base::GetFileInfo(kde4_config, &kde4_info)) {
563 if (base::GetFileInfo(kde3_config, &kde3_info)) {
564 use_kde4 = kde4_info.last_modified >= kde3_info.last_modified;
565 } else {
566 use_kde4 = true;
567 }
568 }
569 }
570 if (use_kde4) {
571 kde_config_dirs_.emplace_back(KDEHomeToConfigPath(kde4_path));
572 } else {
573 kde_config_dirs_.emplace_back(KDEHomeToConfigPath(kde3_path));
574 }
575 } else if (desktop == base::nix::DESKTOP_ENVIRONMENT_KDE5 ||
576 desktop == base::nix::DESKTOP_ENVIRONMENT_KDE6) {
577 // KDE 5 migrated to ~/.config for storing kioslaverc.
578 kde_config_dirs_.emplace_back(base::FilePath(home).Append(".config"));
579
580 // kioslaverc also can be stored in any of XDG_CONFIG_DIRS
581 std::string config_dirs;
582 if (env_var_getter_->GetVar("XDG_CONFIG_DIRS", &config_dirs)) {
583 auto dirs = base::SplitString(config_dirs, ":", base::KEEP_WHITESPACE,
584 base::SPLIT_WANT_NONEMPTY);
585 for (const auto& dir : dirs) {
586 kde_config_dirs_.emplace_back(dir);
587 }
588 }
589
590 // Reverses the order of paths to store them in ascending order of
591 // priority
592 std::reverse(kde_config_dirs_.begin(), kde_config_dirs_.end());
593 }
594 }
595 }
596
597 SettingGetterImplKDE(const SettingGetterImplKDE&) = delete;
598 SettingGetterImplKDE& operator=(const SettingGetterImplKDE&) = delete;
599
~SettingGetterImplKDE()600 ~SettingGetterImplKDE() override {
601 // inotify_fd_ should have been closed before now, from
602 // Delegate::OnDestroy(), while running on the file thread. However
603 // on exiting the process, it may happen that Delegate::OnDestroy()
604 // task is left pending on the file loop after the loop was quit,
605 // and pending tasks may then be deleted without being run.
606 // Here in the KDE version, we can safely close the file descriptor
607 // anyway. (Not that it really matters; the process is exiting.)
608 if (inotify_fd_ >= 0)
609 ShutDown();
610 DCHECK_LT(inotify_fd_, 0);
611 }
612
Init(const scoped_refptr<base::SingleThreadTaskRunner> & glib_task_runner)613 bool Init(const scoped_refptr<base::SingleThreadTaskRunner>& glib_task_runner)
614 override {
615 // This has to be called on the UI thread (http://crbug.com/69057).
616 ScopedAllowBlockingForSettingGetter allow_blocking;
617 DCHECK_LT(inotify_fd_, 0);
618 inotify_fd_ = inotify_init();
619 if (inotify_fd_ < 0) {
620 PLOG(ERROR) << "inotify_init failed";
621 return false;
622 }
623 if (!base::SetNonBlocking(inotify_fd_)) {
624 PLOG(ERROR) << "base::SetNonBlocking failed";
625 close(inotify_fd_);
626 inotify_fd_ = -1;
627 return false;
628 }
629
630 constexpr base::TaskTraits kTraits = {base::TaskPriority::USER_VISIBLE,
631 base::MayBlock()};
632 file_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(kTraits);
633
634 // The initial read is done on the current thread, not
635 // |file_task_runner_|, since we will need to have it for
636 // SetUpAndFetchInitialConfig().
637 UpdateCachedSettings();
638 return true;
639 }
640
ShutDown()641 void ShutDown() override {
642 if (inotify_fd_ >= 0) {
643 ResetCachedSettings();
644 inotify_watcher_.reset();
645 close(inotify_fd_);
646 inotify_fd_ = -1;
647 }
648 debounce_timer_.reset();
649 }
650
SetUpNotifications(ProxyConfigServiceLinux::Delegate * delegate)651 bool SetUpNotifications(
652 ProxyConfigServiceLinux::Delegate* delegate) override {
653 DCHECK_GE(inotify_fd_, 0);
654 DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
655 // We can't just watch the kioslaverc file directly, since KDE will write
656 // a new copy of it and then rename it whenever settings are changed and
657 // inotify watches inodes (so we'll be watching the old deleted file after
658 // the first change, and it will never change again). So, we watch the
659 // directory instead. We then act only on changes to the kioslaverc entry.
660 // TODO(eroman): What if the file is deleted? (handle with IN_DELETE).
661 size_t failed_dirs = 0;
662 for (const auto& kde_config_dir : kde_config_dirs_) {
663 if (inotify_add_watch(inotify_fd_, kde_config_dir.value().c_str(),
664 IN_MODIFY | IN_MOVED_TO) < 0) {
665 ++failed_dirs;
666 }
667 }
668 // Fail if inotify_add_watch failed with every directory
669 if (failed_dirs == kde_config_dirs_.size()) {
670 return false;
671 }
672 notify_delegate_ = delegate;
673 inotify_watcher_ = base::FileDescriptorWatcher::WatchReadable(
674 inotify_fd_,
675 base::BindRepeating(&SettingGetterImplKDE::OnChangeNotification,
676 base::Unretained(this)));
677 // Simulate a change to avoid possibly losing updates before this point.
678 OnChangeNotification();
679 return true;
680 }
681
GetNotificationTaskRunner()682 const scoped_refptr<base::SequencedTaskRunner>& GetNotificationTaskRunner()
683 override {
684 return file_task_runner_;
685 }
686
GetString(StringSetting key,std::string * result)687 bool GetString(StringSetting key, std::string* result) override {
688 auto it = string_table_.find(key);
689 if (it == string_table_.end())
690 return false;
691 *result = it->second;
692 return true;
693 }
GetBool(BoolSetting key,bool * result)694 bool GetBool(BoolSetting key, bool* result) override {
695 // We don't ever have any booleans.
696 return false;
697 }
GetInt(IntSetting key,int * result)698 bool GetInt(IntSetting key, int* result) override {
699 // We don't ever have any integers. (See AddProxy() below about ports.)
700 return false;
701 }
GetStringList(StringListSetting key,std::vector<std::string> * result)702 bool GetStringList(StringListSetting key,
703 std::vector<std::string>* result) override {
704 auto it = strings_table_.find(key);
705 if (it == strings_table_.end())
706 return false;
707 *result = it->second;
708 return true;
709 }
710
BypassListIsReversed()711 bool BypassListIsReversed() override { return reversed_bypass_list_; }
712
UseSuffixMatching()713 bool UseSuffixMatching() override { return true; }
714
715 private:
ResetCachedSettings()716 void ResetCachedSettings() {
717 string_table_.clear();
718 strings_table_.clear();
719 indirect_manual_ = false;
720 auto_no_pac_ = false;
721 reversed_bypass_list_ = false;
722 }
723
KDEHomeToConfigPath(const base::FilePath & kde_home)724 base::FilePath KDEHomeToConfigPath(const base::FilePath& kde_home) {
725 return kde_home.Append("share").Append("config");
726 }
727
AddProxy(StringSetting host_key,const std::string & value)728 void AddProxy(StringSetting host_key, const std::string& value) {
729 if (value.empty() || value.substr(0, 3) == "//:")
730 // No proxy.
731 return;
732 size_t space = value.find(' ');
733 if (space != std::string::npos) {
734 // Newer versions of KDE use a space rather than a colon to separate the
735 // port number from the hostname. If we find this, we need to convert it.
736 std::string fixed = value;
737 fixed[space] = ':';
738 string_table_[host_key] = fixed;
739 } else {
740 // We don't need to parse the port number out; GetProxyFromSettings()
741 // would only append it right back again. So we just leave the port
742 // number right in the host string.
743 string_table_[host_key] = value;
744 }
745 }
746
AddHostList(StringListSetting key,const std::string & value)747 void AddHostList(StringListSetting key, const std::string& value) {
748 std::vector<std::string> tokens;
749 base::StringTokenizer tk(value, ", ");
750 while (tk.GetNext()) {
751 std::string token = tk.token();
752 if (!token.empty())
753 tokens.push_back(token);
754 }
755 strings_table_[key] = tokens;
756 }
757
AddKDESetting(const std::string & key,const std::string & value)758 void AddKDESetting(const std::string& key, const std::string& value) {
759 if (key == "ProxyType") {
760 const char* mode = "none";
761 indirect_manual_ = false;
762 auto_no_pac_ = false;
763 int int_value = StringToIntOrDefault(value, 0);
764 switch (int_value) {
765 case 1: // Manual configuration.
766 mode = "manual";
767 break;
768 case 2: // PAC URL.
769 mode = "auto";
770 break;
771 case 3: // WPAD.
772 mode = "auto";
773 auto_no_pac_ = true;
774 break;
775 case 4: // Indirect manual via environment variables.
776 mode = "manual";
777 indirect_manual_ = true;
778 break;
779 default: // No proxy, or maybe kioslaverc syntax error.
780 break;
781 }
782 string_table_[PROXY_MODE] = mode;
783 } else if (key == "Proxy Config Script") {
784 string_table_[PROXY_AUTOCONF_URL] = value;
785 } else if (key == "httpProxy") {
786 AddProxy(PROXY_HTTP_HOST, value);
787 } else if (key == "httpsProxy") {
788 AddProxy(PROXY_HTTPS_HOST, value);
789 } else if (key == "ftpProxy") {
790 AddProxy(PROXY_FTP_HOST, value);
791 } else if (key == "socksProxy") {
792 // Older versions of KDE configure SOCKS in a weird way involving
793 // LD_PRELOAD and a library that intercepts network calls to SOCKSify
794 // them. We don't support it. KDE 4.8 added a proper SOCKS setting.
795 AddProxy(PROXY_SOCKS_HOST, value);
796 } else if (key == "ReversedException") {
797 // We count "true" or any nonzero number as true, otherwise false.
798 // A failure parsing the integer will also mean false.
799 reversed_bypass_list_ =
800 (value == "true" || StringToIntOrDefault(value, 0) != 0);
801 } else if (key == "NoProxyFor") {
802 AddHostList(PROXY_IGNORE_HOSTS, value);
803 } else if (key == "AuthMode") {
804 // Check for authentication, just so we can warn.
805 int mode = StringToIntOrDefault(value, 0);
806 if (mode) {
807 // ProxyConfig does not support authentication parameters, but
808 // Chrome will prompt for the password later. So we ignore this.
809 LOG(WARNING) <<
810 "Proxy authentication parameters ignored, see bug 16709";
811 }
812 }
813 }
814
ResolveIndirect(StringSetting key)815 void ResolveIndirect(StringSetting key) {
816 auto it = string_table_.find(key);
817 if (it != string_table_.end()) {
818 std::string value;
819 if (env_var_getter_->GetVar(it->second.c_str(), &value))
820 it->second = value;
821 else
822 string_table_.erase(it);
823 }
824 }
825
ResolveIndirectList(StringListSetting key)826 void ResolveIndirectList(StringListSetting key) {
827 auto it = strings_table_.find(key);
828 if (it != strings_table_.end()) {
829 std::string value;
830 if (!it->second.empty() &&
831 env_var_getter_->GetVar(it->second[0].c_str(), &value))
832 AddHostList(key, value);
833 else
834 strings_table_.erase(it);
835 }
836 }
837
838 // The settings in kioslaverc could occur in any order, but some affect
839 // others. Rather than read the whole file in and then query them in an
840 // order that allows us to handle that, we read the settings in whatever
841 // order they occur and do any necessary tweaking after we finish.
ResolveModeEffects()842 void ResolveModeEffects() {
843 if (indirect_manual_) {
844 ResolveIndirect(PROXY_HTTP_HOST);
845 ResolveIndirect(PROXY_HTTPS_HOST);
846 ResolveIndirect(PROXY_FTP_HOST);
847 ResolveIndirect(PROXY_SOCKS_HOST);
848 ResolveIndirectList(PROXY_IGNORE_HOSTS);
849 }
850 if (auto_no_pac_) {
851 // Remove the PAC URL; we're not supposed to use it.
852 string_table_.erase(PROXY_AUTOCONF_URL);
853 }
854 }
855
856 // Reads kioslaverc from all paths one line at a time and calls
857 // AddKDESetting() to add each relevant name-value pair to the appropriate
858 // value table. Each value can be overwritten by values from configs from
859 // the following paths.
UpdateCachedSettings()860 void UpdateCachedSettings() {
861 bool at_least_one_kioslaverc_opened = false;
862 for (const auto& kde_config_dir : kde_config_dirs_) {
863 base::FilePath kioslaverc = kde_config_dir.Append("kioslaverc");
864 base::ScopedFILE input(base::OpenFile(kioslaverc, "r"));
865 if (!input.get())
866 continue;
867
868 // Reset cached settings once only if some config was successfully opened
869 if (!at_least_one_kioslaverc_opened) {
870 ResetCachedSettings();
871 }
872 at_least_one_kioslaverc_opened = true;
873 bool in_proxy_settings = false;
874 bool line_too_long = false;
875 char line[BUFFER_SIZE];
876 // fgets() will return NULL on EOF or error.
877 while (fgets(line, sizeof(line), input.get())) {
878 // fgets() guarantees the line will be properly terminated.
879 size_t length = strlen(line);
880 if (!length)
881 continue;
882 // This should be true even with CRLF endings.
883 if (line[length - 1] != '\n') {
884 line_too_long = true;
885 continue;
886 }
887 if (line_too_long) {
888 // The previous line had no line ending, but this one does. This is
889 // the end of the line that was too long, so warn here and skip it.
890 LOG(WARNING) << "skipped very long line in " << kioslaverc.value();
891 line_too_long = false;
892 continue;
893 }
894 // Remove the LF at the end, and the CR if there is one.
895 line[--length] = '\0';
896 if (length && line[length - 1] == '\r')
897 line[--length] = '\0';
898 // Now parse the line.
899 if (line[0] == '[') {
900 // Switching sections. All we care about is whether this is
901 // the (a?) proxy settings section, for both KDE3 and KDE4.
902 in_proxy_settings = !strncmp(line, "[Proxy Settings]", 16);
903 } else if (in_proxy_settings) {
904 // A regular line, in the (a?) proxy settings section.
905 char* split = strchr(line, '=');
906 // Skip this line if it does not contain an = sign.
907 if (!split)
908 continue;
909 // Split the line on the = and advance |split|.
910 *(split++) = 0;
911 std::string key = line;
912 std::string value = split;
913 base::TrimWhitespaceASCII(key, base::TRIM_ALL, &key);
914 base::TrimWhitespaceASCII(value, base::TRIM_ALL, &value);
915 // Skip this line if the key name is empty.
916 if (key.empty())
917 continue;
918 // Is the value name localized?
919 if (key[key.length() - 1] == ']') {
920 // Find the matching bracket.
921 length = key.rfind('[');
922 // Skip this line if the localization indicator is malformed.
923 if (length == std::string::npos)
924 continue;
925 // Trim the localization indicator off.
926 key.resize(length);
927 // Remove any resulting trailing whitespace.
928 base::TrimWhitespaceASCII(key, base::TRIM_TRAILING, &key);
929 // Skip this line if the key name is now empty.
930 if (key.empty())
931 continue;
932 }
933 // Now fill in the tables.
934 AddKDESetting(key, value);
935 }
936 }
937 if (ferror(input.get()))
938 LOG(ERROR) << "error reading " << kioslaverc.value();
939 }
940 if (at_least_one_kioslaverc_opened) {
941 ResolveModeEffects();
942 }
943 }
944
945 // This is the callback from the debounce timer.
OnDebouncedNotification()946 void OnDebouncedNotification() {
947 DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
948 VLOG(1) << "inotify change notification for kioslaverc";
949 UpdateCachedSettings();
950 CHECK(notify_delegate_);
951 // Forward to a method on the proxy config service delegate object.
952 notify_delegate_->OnCheckProxyConfigSettings();
953 }
954
955 // Called by OnFileCanReadWithoutBlocking() on the file thread. Reads
956 // from the inotify file descriptor and starts up a debounce timer if
957 // an event for kioslaverc is seen.
OnChangeNotification()958 void OnChangeNotification() {
959 DCHECK_GE(inotify_fd_, 0);
960 DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
961 char event_buf[(sizeof(inotify_event) + NAME_MAX + 1) * 4];
962 bool kioslaverc_touched = false;
963 ssize_t r;
964 while ((r = read(inotify_fd_, event_buf, sizeof(event_buf))) > 0) {
965 // inotify returns variable-length structures, which is why we have
966 // this strange-looking loop instead of iterating through an array.
967 char* event_ptr = event_buf;
968 while (event_ptr < event_buf + r) {
969 inotify_event* event = reinterpret_cast<inotify_event*>(event_ptr);
970 // The kernel always feeds us whole events.
971 CHECK_LE(event_ptr + sizeof(inotify_event), event_buf + r);
972 CHECK_LE(event->name + event->len, event_buf + r);
973 if (!strcmp(event->name, "kioslaverc"))
974 kioslaverc_touched = true;
975 // Advance the pointer just past the end of the filename.
976 event_ptr = event->name + event->len;
977 }
978 // We keep reading even if |kioslaverc_touched| is true to drain the
979 // inotify event queue.
980 }
981 if (!r)
982 // Instead of returning -1 and setting errno to EINVAL if there is not
983 // enough buffer space, older kernels (< 2.6.21) return 0. Simulate the
984 // new behavior (EINVAL) so we can reuse the code below.
985 errno = EINVAL;
986 if (errno != EAGAIN) {
987 PLOG(WARNING) << "error reading inotify file descriptor";
988 if (errno == EINVAL) {
989 // Our buffer is not large enough to read the next event. This should
990 // not happen (because its size is calculated to always be sufficiently
991 // large), but if it does we'd warn continuously since |inotify_fd_|
992 // would be forever ready to read. Close it and stop watching instead.
993 LOG(ERROR) << "inotify failure; no longer watching kioslaverc!";
994 inotify_watcher_.reset();
995 close(inotify_fd_);
996 inotify_fd_ = -1;
997 }
998 }
999 if (kioslaverc_touched) {
1000 LOG(ERROR) << "kioslaverc_touched";
1001 // We don't use Reset() because the timer may not yet be running.
1002 // (In that case Stop() is a no-op.)
1003 debounce_timer_->Stop();
1004 debounce_timer_->Start(
1005 FROM_HERE, base::Milliseconds(kDebounceTimeoutMilliseconds), this,
1006 &SettingGetterImplKDE::OnDebouncedNotification);
1007 }
1008 }
1009
1010 typedef std::map<StringSetting, std::string> string_map_type;
1011 typedef std::map<StringListSetting,
1012 std::vector<std::string> > strings_map_type;
1013
1014 int inotify_fd_ = -1;
1015 std::unique_ptr<base::FileDescriptorWatcher::Controller> inotify_watcher_;
1016 raw_ptr<ProxyConfigServiceLinux::Delegate> notify_delegate_ = nullptr;
1017 std::unique_ptr<base::OneShotTimer> debounce_timer_;
1018 std::vector<base::FilePath> kde_config_dirs_;
1019 bool indirect_manual_ = false;
1020 bool auto_no_pac_ = false;
1021 bool reversed_bypass_list_ = false;
1022 // We don't own |env_var_getter_|. It's safe to hold a pointer to it, since
1023 // both it and us are owned by ProxyConfigServiceLinux::Delegate, and have the
1024 // same lifetime.
1025 raw_ptr<base::Environment> env_var_getter_;
1026
1027 // We cache these settings whenever we re-read the kioslaverc file.
1028 string_map_type string_table_;
1029 strings_map_type strings_table_;
1030
1031 // Task runner for doing blocking file IO on, as well as handling inotify
1032 // events on.
1033 scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
1034 };
1035
1036 } // namespace
1037
GetProxyFromSettings(SettingGetter::StringSetting host_key,ProxyServer * result_server)1038 bool ProxyConfigServiceLinux::Delegate::GetProxyFromSettings(
1039 SettingGetter::StringSetting host_key,
1040 ProxyServer* result_server) {
1041 std::string host;
1042 if (!setting_getter_->GetString(host_key, &host) || host.empty()) {
1043 // Unset or empty.
1044 return false;
1045 }
1046 // Check for an optional port.
1047 int port = 0;
1048 SettingGetter::IntSetting port_key =
1049 SettingGetter::HostSettingToPortSetting(host_key);
1050 setting_getter_->GetInt(port_key, &port);
1051 if (port != 0) {
1052 // If a port is set and non-zero:
1053 host += ":" + base::NumberToString(port);
1054 }
1055
1056 // gsettings settings do not appear to distinguish between SOCKS version. We
1057 // default to version 5. For more information on this policy decision, see:
1058 // http://code.google.com/p/chromium/issues/detail?id=55912#c2
1059 ProxyServer::Scheme scheme = (host_key == SettingGetter::PROXY_SOCKS_HOST) ?
1060 ProxyServer::SCHEME_SOCKS5 : ProxyServer::SCHEME_HTTP;
1061 host = FixupProxyHostScheme(scheme, host);
1062 ProxyServer proxy_server =
1063 ProxyUriToProxyServer(host, ProxyServer::SCHEME_HTTP);
1064 if (proxy_server.is_valid()) {
1065 *result_server = proxy_server;
1066 return true;
1067 }
1068 return false;
1069 }
1070
1071 absl::optional<ProxyConfigWithAnnotation>
GetConfigFromSettings()1072 ProxyConfigServiceLinux::Delegate::GetConfigFromSettings() {
1073 ProxyConfig config;
1074 config.set_from_system(true);
1075
1076 std::string mode;
1077 if (!setting_getter_->GetString(SettingGetter::PROXY_MODE, &mode)) {
1078 // We expect this to always be set, so if we don't see it then we probably
1079 // have a gsettings problem, and so we don't have a valid proxy config.
1080 return absl::nullopt;
1081 }
1082 if (mode == "none") {
1083 // Specifically specifies no proxy.
1084 return ProxyConfigWithAnnotation(
1085 config, NetworkTrafficAnnotationTag(traffic_annotation_));
1086 }
1087
1088 if (mode == "auto") {
1089 // Automatic proxy config.
1090 std::string pac_url_str;
1091 if (setting_getter_->GetString(SettingGetter::PROXY_AUTOCONF_URL,
1092 &pac_url_str)) {
1093 if (!pac_url_str.empty()) {
1094 // If the PAC URL is actually a file path, then put file:// in front.
1095 if (pac_url_str[0] == '/')
1096 pac_url_str = "file://" + pac_url_str;
1097 GURL pac_url(pac_url_str);
1098 if (!pac_url.is_valid())
1099 return absl::nullopt;
1100 config.set_pac_url(pac_url);
1101 return ProxyConfigWithAnnotation(
1102 config, NetworkTrafficAnnotationTag(traffic_annotation_));
1103 }
1104 }
1105 config.set_auto_detect(true);
1106 return ProxyConfigWithAnnotation(
1107 config, NetworkTrafficAnnotationTag(traffic_annotation_));
1108 }
1109
1110 if (mode != "manual") {
1111 // Mode is unrecognized.
1112 return absl::nullopt;
1113 }
1114 bool use_http_proxy;
1115 if (setting_getter_->GetBool(SettingGetter::PROXY_USE_HTTP_PROXY,
1116 &use_http_proxy)
1117 && !use_http_proxy) {
1118 // Another master switch for some reason. If set to false, then no
1119 // proxy. But we don't panic if the key doesn't exist.
1120 return ProxyConfigWithAnnotation(
1121 config, NetworkTrafficAnnotationTag(traffic_annotation_));
1122 }
1123
1124 bool same_proxy = false;
1125 // Indicates to use the http proxy for all protocols. This one may
1126 // not exist (presumably on older versions); we assume false in that
1127 // case.
1128 setting_getter_->GetBool(SettingGetter::PROXY_USE_SAME_PROXY,
1129 &same_proxy);
1130
1131 ProxyServer proxy_for_http;
1132 ProxyServer proxy_for_https;
1133 ProxyServer proxy_for_ftp;
1134 ProxyServer socks_proxy; // (socks)
1135
1136 // This counts how many of the above ProxyServers were defined and valid.
1137 size_t num_proxies_specified = 0;
1138
1139 // Extract the per-scheme proxies. If we failed to parse it, or no proxy was
1140 // specified for the scheme, then the resulting ProxyServer will be invalid.
1141 if (GetProxyFromSettings(SettingGetter::PROXY_HTTP_HOST, &proxy_for_http))
1142 num_proxies_specified++;
1143 if (GetProxyFromSettings(SettingGetter::PROXY_HTTPS_HOST, &proxy_for_https))
1144 num_proxies_specified++;
1145 if (GetProxyFromSettings(SettingGetter::PROXY_FTP_HOST, &proxy_for_ftp))
1146 num_proxies_specified++;
1147 if (GetProxyFromSettings(SettingGetter::PROXY_SOCKS_HOST, &socks_proxy))
1148 num_proxies_specified++;
1149
1150 if (same_proxy) {
1151 if (proxy_for_http.is_valid()) {
1152 // Use the http proxy for all schemes.
1153 config.proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST;
1154 config.proxy_rules().single_proxies.SetSingleProxyServer(proxy_for_http);
1155 }
1156 } else if (num_proxies_specified > 0) {
1157 if (socks_proxy.is_valid() && num_proxies_specified == 1) {
1158 // If the only proxy specified was for SOCKS, use it for all schemes.
1159 config.proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST;
1160 config.proxy_rules().single_proxies.SetSingleProxyServer(socks_proxy);
1161 } else {
1162 // Otherwise use the indicated proxies per-scheme.
1163 config.proxy_rules().type =
1164 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME;
1165 config.proxy_rules().proxies_for_http.SetSingleProxyServer(
1166 proxy_for_http);
1167 config.proxy_rules().proxies_for_https.SetSingleProxyServer(
1168 proxy_for_https);
1169 config.proxy_rules().proxies_for_ftp.SetSingleProxyServer(proxy_for_ftp);
1170 config.proxy_rules().fallback_proxies.SetSingleProxyServer(socks_proxy);
1171 }
1172 }
1173
1174 if (config.proxy_rules().empty()) {
1175 // Manual mode but we couldn't parse any rules.
1176 return absl::nullopt;
1177 }
1178
1179 // Check for authentication, just so we can warn.
1180 bool use_auth = false;
1181 setting_getter_->GetBool(SettingGetter::PROXY_USE_AUTHENTICATION,
1182 &use_auth);
1183 if (use_auth) {
1184 // ProxyConfig does not support authentication parameters, but
1185 // Chrome will prompt for the password later. So we ignore
1186 // /system/http_proxy/*auth* settings.
1187 LOG(WARNING) << "Proxy authentication parameters ignored, see bug 16709";
1188 }
1189
1190 // Now the bypass list.
1191 std::vector<std::string> ignore_hosts_list;
1192 config.proxy_rules().bypass_rules.Clear();
1193 if (setting_getter_->GetStringList(SettingGetter::PROXY_IGNORE_HOSTS,
1194 &ignore_hosts_list)) {
1195 for (const auto& rule : ignore_hosts_list) {
1196 config.proxy_rules().bypass_rules.AddRuleFromString(rule);
1197 }
1198 }
1199
1200 if (setting_getter_->UseSuffixMatching()) {
1201 RewriteRulesForSuffixMatching(&config.proxy_rules().bypass_rules);
1202 }
1203
1204 // Note that there are no settings with semantics corresponding to
1205 // bypass of local names in GNOME. In KDE, "<local>" is supported
1206 // as a hostname rule.
1207
1208 // KDE allows one to reverse the bypass rules.
1209 config.proxy_rules().reverse_bypass = setting_getter_->BypassListIsReversed();
1210
1211 return ProxyConfigWithAnnotation(
1212 config, NetworkTrafficAnnotationTag(traffic_annotation_));
1213 }
1214
Delegate(std::unique_ptr<base::Environment> env_var_getter,absl::optional<std::unique_ptr<SettingGetter>> setting_getter,absl::optional<NetworkTrafficAnnotationTag> traffic_annotation)1215 ProxyConfigServiceLinux::Delegate::Delegate(
1216 std::unique_ptr<base::Environment> env_var_getter,
1217 absl::optional<std::unique_ptr<SettingGetter>> setting_getter,
1218 absl::optional<NetworkTrafficAnnotationTag> traffic_annotation)
1219 : env_var_getter_(std::move(env_var_getter)) {
1220 if (traffic_annotation) {
1221 traffic_annotation_ =
1222 MutableNetworkTrafficAnnotationTag(traffic_annotation.value());
1223 }
1224
1225 if (setting_getter) {
1226 setting_getter_ = std::move(setting_getter.value());
1227 return;
1228 }
1229
1230 // Figure out which SettingGetterImpl to use, if any.
1231 switch (base::nix::GetDesktopEnvironment(env_var_getter_.get())) {
1232 case base::nix::DESKTOP_ENVIRONMENT_CINNAMON:
1233 case base::nix::DESKTOP_ENVIRONMENT_DEEPIN:
1234 case base::nix::DESKTOP_ENVIRONMENT_GNOME:
1235 case base::nix::DESKTOP_ENVIRONMENT_PANTHEON:
1236 case base::nix::DESKTOP_ENVIRONMENT_UKUI:
1237 case base::nix::DESKTOP_ENVIRONMENT_UNITY:
1238 #if defined(USE_GIO)
1239 {
1240 auto gs_getter = std::make_unique<SettingGetterImplGSettings>();
1241 // We have to load symbols and check the GNOME version in use to decide
1242 // if we should use the gsettings getter. See CheckVersion().
1243 if (gs_getter->CheckVersion(env_var_getter_.get()))
1244 setting_getter_ = std::move(gs_getter);
1245 }
1246 #endif
1247 break;
1248 case base::nix::DESKTOP_ENVIRONMENT_KDE3:
1249 case base::nix::DESKTOP_ENVIRONMENT_KDE4:
1250 case base::nix::DESKTOP_ENVIRONMENT_KDE5:
1251 case base::nix::DESKTOP_ENVIRONMENT_KDE6:
1252 setting_getter_ =
1253 std::make_unique<SettingGetterImplKDE>(env_var_getter_.get());
1254 break;
1255 case base::nix::DESKTOP_ENVIRONMENT_XFCE:
1256 case base::nix::DESKTOP_ENVIRONMENT_LXQT:
1257 case base::nix::DESKTOP_ENVIRONMENT_OTHER:
1258 break;
1259 }
1260 }
1261
SetUpAndFetchInitialConfig(const scoped_refptr<base::SingleThreadTaskRunner> & glib_task_runner,const scoped_refptr<base::SequencedTaskRunner> & main_task_runner,const NetworkTrafficAnnotationTag & traffic_annotation)1262 void ProxyConfigServiceLinux::Delegate::SetUpAndFetchInitialConfig(
1263 const scoped_refptr<base::SingleThreadTaskRunner>& glib_task_runner,
1264 const scoped_refptr<base::SequencedTaskRunner>& main_task_runner,
1265 const NetworkTrafficAnnotationTag& traffic_annotation) {
1266 traffic_annotation_ = MutableNetworkTrafficAnnotationTag(traffic_annotation);
1267
1268 // We should be running on the default glib main loop thread right
1269 // now. gsettings can only be accessed from this thread.
1270 DCHECK(glib_task_runner->RunsTasksInCurrentSequence());
1271 glib_task_runner_ = glib_task_runner;
1272 main_task_runner_ = main_task_runner;
1273
1274 // If we are passed a NULL |main_task_runner|, then don't set up proxy
1275 // setting change notifications. This should not be the usual case but is
1276 // intended to/ simplify test setups.
1277 if (!main_task_runner_.get())
1278 VLOG(1) << "Monitoring of proxy setting changes is disabled";
1279
1280 // Fetch and cache the current proxy config. The config is left in
1281 // cached_config_, where GetLatestProxyConfig() running on the main TaskRunner
1282 // will expect to find it. This is safe to do because we return
1283 // before this ProxyConfigServiceLinux is passed on to
1284 // the ConfiguredProxyResolutionService.
1285
1286 // Note: It would be nice to prioritize environment variables
1287 // and only fall back to gsettings if env vars were unset. But
1288 // gnome-terminal "helpfully" sets http_proxy and no_proxy, and it
1289 // does so even if the proxy mode is set to auto, which would
1290 // mislead us.
1291
1292 cached_config_ = absl::nullopt;
1293 if (setting_getter_ && setting_getter_->Init(glib_task_runner)) {
1294 cached_config_ = GetConfigFromSettings();
1295 }
1296 if (cached_config_) {
1297 VLOG(1) << "Obtained proxy settings from annotation hash code "
1298 << cached_config_->traffic_annotation().unique_id_hash_code;
1299
1300 // If gsettings proxy mode is "none", meaning direct, then we take
1301 // that to be a valid config and will not check environment
1302 // variables. The alternative would have been to look for a proxy
1303 // wherever we can find one.
1304
1305 // Keep a copy of the config for use from this thread for
1306 // comparison with updated settings when we get notifications.
1307 reference_config_ = cached_config_;
1308
1309 // We only set up notifications if we have the main and file loops
1310 // available. We do this after getting the initial configuration so that we
1311 // don't have to worry about cancelling it if the initial fetch above fails.
1312 // Note that setting up notifications has the side effect of simulating a
1313 // change, so that we won't lose any updates that may have happened after
1314 // the initial fetch and before setting up notifications. We'll detect the
1315 // common case of no changes in OnCheckProxyConfigSettings() (or sooner) and
1316 // ignore it.
1317 if (main_task_runner.get()) {
1318 scoped_refptr<base::SequencedTaskRunner> required_loop =
1319 setting_getter_->GetNotificationTaskRunner();
1320 if (!required_loop.get() || required_loop->RunsTasksInCurrentSequence()) {
1321 // In this case we are already on an acceptable thread.
1322 SetUpNotifications();
1323 } else {
1324 // Post a task to set up notifications. We don't wait for success.
1325 required_loop->PostTask(
1326 FROM_HERE,
1327 base::BindOnce(
1328 &ProxyConfigServiceLinux::Delegate::SetUpNotifications, this));
1329 }
1330 }
1331 }
1332
1333 if (!cached_config_) {
1334 // We fall back on environment variables.
1335 //
1336 // Consulting environment variables doesn't need to be done from the
1337 // default glib main loop, but it's a tiny enough amount of work.
1338 cached_config_ = GetConfigFromEnv();
1339 if (cached_config_) {
1340 VLOG(1) << "Obtained proxy settings from environment variables";
1341 }
1342 }
1343 }
1344
1345 // Depending on the SettingGetter in use, this method will be called
1346 // on either the UI thread (GSettings) or the file thread (KDE).
SetUpNotifications()1347 void ProxyConfigServiceLinux::Delegate::SetUpNotifications() {
1348 scoped_refptr<base::SequencedTaskRunner> required_loop =
1349 setting_getter_->GetNotificationTaskRunner();
1350 DCHECK(!required_loop.get() || required_loop->RunsTasksInCurrentSequence());
1351 if (!setting_getter_->SetUpNotifications(this))
1352 LOG(ERROR) << "Unable to set up proxy configuration change notifications";
1353 }
1354
AddObserver(Observer * observer)1355 void ProxyConfigServiceLinux::Delegate::AddObserver(Observer* observer) {
1356 observers_.AddObserver(observer);
1357 }
1358
RemoveObserver(Observer * observer)1359 void ProxyConfigServiceLinux::Delegate::RemoveObserver(Observer* observer) {
1360 observers_.RemoveObserver(observer);
1361 }
1362
1363 ProxyConfigService::ConfigAvailability
GetLatestProxyConfig(ProxyConfigWithAnnotation * config)1364 ProxyConfigServiceLinux::Delegate::GetLatestProxyConfig(
1365 ProxyConfigWithAnnotation* config) {
1366 // This is called from the main TaskRunner.
1367 DCHECK(!main_task_runner_.get() ||
1368 main_task_runner_->RunsTasksInCurrentSequence());
1369
1370 // Simply return the last proxy configuration that glib_default_loop
1371 // notified us of.
1372 *config = GetConfigOrDirect(cached_config_);
1373
1374 // We return CONFIG_VALID to indicate that *config was filled in. It is always
1375 // going to be available since we initialized eagerly on the UI thread.
1376 // TODO(eroman): do lazy initialization instead, so we no longer need
1377 // to construct ProxyConfigServiceLinux on the UI thread.
1378 // In which case, we may return false here.
1379 return CONFIG_VALID;
1380 }
1381
1382 // Depending on the SettingGetter in use, this method will be called
1383 // on either the UI thread (GSettings) or the file thread (KDE).
OnCheckProxyConfigSettings()1384 void ProxyConfigServiceLinux::Delegate::OnCheckProxyConfigSettings() {
1385 scoped_refptr<base::SequencedTaskRunner> required_loop =
1386 setting_getter_->GetNotificationTaskRunner();
1387 DCHECK(!required_loop.get() || required_loop->RunsTasksInCurrentSequence());
1388 absl::optional<ProxyConfigWithAnnotation> new_config =
1389 GetConfigFromSettings();
1390
1391 // See if it is different from what we had before.
1392 if (new_config.has_value() != reference_config_.has_value() ||
1393 (new_config.has_value() &&
1394 !new_config->value().Equals(reference_config_->value()))) {
1395 // Post a task to the main TaskRunner with the new configuration, so it can
1396 // update |cached_config_|.
1397 main_task_runner_->PostTask(
1398 FROM_HERE,
1399 base::BindOnce(&ProxyConfigServiceLinux::Delegate::SetNewProxyConfig,
1400 this, new_config));
1401 // Update the thread-private copy in |reference_config_| as well.
1402 reference_config_ = new_config;
1403 } else {
1404 VLOG(1) << "Detected no-op change to proxy settings. Doing nothing.";
1405 }
1406 }
1407
SetNewProxyConfig(const absl::optional<ProxyConfigWithAnnotation> & new_config)1408 void ProxyConfigServiceLinux::Delegate::SetNewProxyConfig(
1409 const absl::optional<ProxyConfigWithAnnotation>& new_config) {
1410 DCHECK(main_task_runner_->RunsTasksInCurrentSequence());
1411 VLOG(1) << "Proxy configuration changed";
1412 cached_config_ = new_config;
1413 for (auto& observer : observers_) {
1414 observer.OnProxyConfigChanged(GetConfigOrDirect(new_config),
1415 ProxyConfigService::CONFIG_VALID);
1416 }
1417 }
1418
PostDestroyTask()1419 void ProxyConfigServiceLinux::Delegate::PostDestroyTask() {
1420 if (!setting_getter_)
1421 return;
1422
1423 scoped_refptr<base::SequencedTaskRunner> shutdown_loop =
1424 setting_getter_->GetNotificationTaskRunner();
1425 if (!shutdown_loop.get() || shutdown_loop->RunsTasksInCurrentSequence()) {
1426 // Already on the right thread, call directly.
1427 // This is the case for the unittests.
1428 OnDestroy();
1429 } else {
1430 // Post to shutdown thread. Note that on browser shutdown, we may quit
1431 // this MessageLoop and exit the program before ever running this.
1432 shutdown_loop->PostTask(
1433 FROM_HERE,
1434 base::BindOnce(&ProxyConfigServiceLinux::Delegate::OnDestroy, this));
1435 }
1436 }
OnDestroy()1437 void ProxyConfigServiceLinux::Delegate::OnDestroy() {
1438 scoped_refptr<base::SequencedTaskRunner> shutdown_loop =
1439 setting_getter_->GetNotificationTaskRunner();
1440 DCHECK(!shutdown_loop.get() || shutdown_loop->RunsTasksInCurrentSequence());
1441 setting_getter_->ShutDown();
1442 }
1443
ProxyConfigServiceLinux()1444 ProxyConfigServiceLinux::ProxyConfigServiceLinux()
1445 : delegate_(base::MakeRefCounted<Delegate>(base::Environment::Create(),
1446 absl::nullopt,
1447 absl::nullopt)) {}
1448
~ProxyConfigServiceLinux()1449 ProxyConfigServiceLinux::~ProxyConfigServiceLinux() {
1450 delegate_->PostDestroyTask();
1451 }
1452
ProxyConfigServiceLinux(std::unique_ptr<base::Environment> env_var_getter,const NetworkTrafficAnnotationTag & traffic_annotation)1453 ProxyConfigServiceLinux::ProxyConfigServiceLinux(
1454 std::unique_ptr<base::Environment> env_var_getter,
1455 const NetworkTrafficAnnotationTag& traffic_annotation)
1456 : delegate_(base::MakeRefCounted<Delegate>(std::move(env_var_getter),
1457 absl::nullopt,
1458 traffic_annotation)) {}
1459
ProxyConfigServiceLinux(std::unique_ptr<base::Environment> env_var_getter,std::unique_ptr<SettingGetter> setting_getter,const NetworkTrafficAnnotationTag & traffic_annotation)1460 ProxyConfigServiceLinux::ProxyConfigServiceLinux(
1461 std::unique_ptr<base::Environment> env_var_getter,
1462 std::unique_ptr<SettingGetter> setting_getter,
1463 const NetworkTrafficAnnotationTag& traffic_annotation)
1464 : delegate_(base::MakeRefCounted<Delegate>(std::move(env_var_getter),
1465 std::move(setting_getter),
1466 traffic_annotation)) {}
1467
AddObserver(Observer * observer)1468 void ProxyConfigServiceLinux::AddObserver(Observer* observer) {
1469 delegate_->AddObserver(observer);
1470 }
1471
RemoveObserver(Observer * observer)1472 void ProxyConfigServiceLinux::RemoveObserver(Observer* observer) {
1473 delegate_->RemoveObserver(observer);
1474 }
1475
1476 ProxyConfigService::ConfigAvailability
GetLatestProxyConfig(ProxyConfigWithAnnotation * config)1477 ProxyConfigServiceLinux::GetLatestProxyConfig(
1478 ProxyConfigWithAnnotation* config) {
1479 return delegate_->GetLatestProxyConfig(config);
1480 }
1481
1482 } // namespace net
1483