1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "nsISystemProxySettings.h"
7 #include "mozilla/Components.h"
8 #include "nsIURI.h"
9 #include "nsReadableUtils.h"
10 #include "nsArrayUtils.h"
11 #include "prnetdb.h"
12 #include "prenv.h"
13 #include "nsPrintfCString.h"
14 #include "nsNetCID.h"
15 #include "nsNetUtil.h"
16 #include "nsISupportsPrimitives.h"
17 #include "nsIGSettingsService.h"
18 #include "nsInterfaceHashtable.h"
19 #include "mozilla/Attributes.h"
20 #include "nsIURI.h"
21
22 using namespace mozilla;
23
24 class nsUnixSystemProxySettings final : public nsISystemProxySettings {
25 public:
26 NS_DECL_ISUPPORTS
27 NS_DECL_NSISYSTEMPROXYSETTINGS
28
nsUnixSystemProxySettings()29 nsUnixSystemProxySettings() : mSchemeProxySettings(4) {}
30 void Init();
31
32 private:
33 ~nsUnixSystemProxySettings() = default;
34
35 nsCOMPtr<nsIGSettingsService> mGSettings;
36 nsCOMPtr<nsIGSettingsCollection> mProxySettings;
37 nsInterfaceHashtable<nsCStringHashKey, nsIGSettingsCollection>
38 mSchemeProxySettings;
39 nsresult GetProxyFromGSettings(const nsACString& aScheme,
40 const nsACString& aHost, int32_t aPort,
41 nsACString& aResult);
42 nsresult SetProxyResultFromGSettings(const char* aKeyBase, const char* aType,
43 nsACString& aResult);
44 };
45
NS_IMPL_ISUPPORTS(nsUnixSystemProxySettings,nsISystemProxySettings)46 NS_IMPL_ISUPPORTS(nsUnixSystemProxySettings, nsISystemProxySettings)
47
48 NS_IMETHODIMP
49 nsUnixSystemProxySettings::GetMainThreadOnly(bool* aMainThreadOnly) {
50 // dbus prevents us from being threadsafe, but this routine should not block
51 // anyhow
52 *aMainThreadOnly = true;
53 return NS_OK;
54 }
55
Init()56 void nsUnixSystemProxySettings::Init() {
57 mGSettings = do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
58 if (mGSettings) {
59 mGSettings->GetCollectionForSchema(
60 NS_LITERAL_CSTRING("org.gnome.system.proxy"),
61 getter_AddRefs(mProxySettings));
62 }
63 }
64
GetPACURI(nsACString & aResult)65 nsresult nsUnixSystemProxySettings::GetPACURI(nsACString& aResult) {
66 if (mProxySettings) {
67 nsCString proxyMode;
68 // Check if mode is auto
69 nsresult rv =
70 mProxySettings->GetString(NS_LITERAL_CSTRING("mode"), proxyMode);
71 if (rv == NS_OK && proxyMode.EqualsLiteral("auto")) {
72 return mProxySettings->GetString(NS_LITERAL_CSTRING("autoconfig-url"),
73 aResult);
74 }
75 }
76
77 // Return an empty string when auto mode is not set.
78 aResult.Truncate();
79 return NS_OK;
80 }
81
IsInNoProxyList(const nsACString & aHost,int32_t aPort,const char * noProxyVal)82 static bool IsInNoProxyList(const nsACString& aHost, int32_t aPort,
83 const char* noProxyVal) {
84 NS_ASSERTION(aPort >= 0, "Negative port?");
85
86 nsAutoCString noProxy(noProxyVal);
87 if (noProxy.EqualsLiteral("*")) return true;
88
89 noProxy.StripWhitespace();
90
91 nsReadingIterator<char> pos;
92 nsReadingIterator<char> end;
93 noProxy.BeginReading(pos);
94 noProxy.EndReading(end);
95 while (pos != end) {
96 nsReadingIterator<char> last = pos;
97 nsReadingIterator<char> nextPos;
98 if (FindCharInReadable(',', last, end)) {
99 nextPos = last;
100 ++nextPos;
101 } else {
102 last = end;
103 nextPos = end;
104 }
105
106 nsReadingIterator<char> colon = pos;
107 int32_t port = -1;
108 if (FindCharInReadable(':', colon, last)) {
109 ++colon;
110 nsDependentCSubstring portStr(colon, last);
111 nsAutoCString portStr2(
112 portStr); // We need this for ToInteger. String API's suck.
113 nsresult err;
114 port = portStr2.ToInteger(&err);
115 if (NS_FAILED(err)) {
116 port = -2; // don't match any port, so we ignore this pattern
117 }
118 --colon;
119 } else {
120 colon = last;
121 }
122
123 if (port == -1 || port == aPort) {
124 nsDependentCSubstring hostStr(pos, colon);
125 // By using StringEndsWith instead of an equality comparator, we can
126 // include sub-domains
127 if (StringEndsWith(aHost, hostStr, nsCaseInsensitiveCStringComparator))
128 return true;
129 }
130
131 pos = nextPos;
132 }
133
134 return false;
135 }
136
SetProxyResult(const char * aType,const nsACString & aHost,int32_t aPort,nsACString & aResult)137 static void SetProxyResult(const char* aType, const nsACString& aHost,
138 int32_t aPort, nsACString& aResult) {
139 aResult.AssignASCII(aType);
140 aResult.Append(' ');
141 aResult.Append(aHost);
142 if (aPort > 0) {
143 aResult.Append(':');
144 aResult.AppendInt(aPort);
145 }
146 }
147
SetProxyResultDirect(nsACString & aResult)148 static void SetProxyResultDirect(nsACString& aResult) {
149 aResult.AssignLiteral("DIRECT");
150 }
151
GetProxyFromEnvironment(const nsACString & aScheme,const nsACString & aHost,int32_t aPort,nsACString & aResult)152 static nsresult GetProxyFromEnvironment(const nsACString& aScheme,
153 const nsACString& aHost, int32_t aPort,
154 nsACString& aResult) {
155 nsAutoCString envVar;
156 envVar.Append(aScheme);
157 envVar.AppendLiteral("_proxy");
158 const char* proxyVal = PR_GetEnv(envVar.get());
159 if (!proxyVal) {
160 proxyVal = PR_GetEnv("all_proxy");
161 if (!proxyVal) {
162 // Return failure so that the caller can detect the failure and
163 // fall back to other proxy detection (e.g., WPAD)
164 return NS_ERROR_FAILURE;
165 }
166 }
167
168 const char* noProxyVal = PR_GetEnv("no_proxy");
169 if (noProxyVal && IsInNoProxyList(aHost, aPort, noProxyVal)) {
170 SetProxyResultDirect(aResult);
171 return NS_OK;
172 }
173
174 // Use our URI parser to crack the proxy URI
175 nsCOMPtr<nsIURI> proxyURI;
176 nsresult rv = NS_NewURI(getter_AddRefs(proxyURI), proxyVal);
177 NS_ENSURE_SUCCESS(rv, rv);
178
179 // Is there a way to specify "socks://" or something in these environment
180 // variables? I can't find any documentation.
181 if (!proxyURI->SchemeIs("http")) {
182 return NS_ERROR_UNKNOWN_PROTOCOL;
183 }
184
185 nsAutoCString proxyHost;
186 rv = proxyURI->GetHost(proxyHost);
187 NS_ENSURE_SUCCESS(rv, rv);
188
189 int32_t proxyPort;
190 rv = proxyURI->GetPort(&proxyPort);
191 NS_ENSURE_SUCCESS(rv, rv);
192
193 SetProxyResult("PROXY", proxyHost, proxyPort, aResult);
194 return NS_OK;
195 }
196
SetProxyResultFromGSettings(const char * aKeyBase,const char * aType,nsACString & aResult)197 nsresult nsUnixSystemProxySettings::SetProxyResultFromGSettings(
198 const char* aKeyBase, const char* aType, nsACString& aResult) {
199 nsDependentCString key(aKeyBase);
200
201 nsCOMPtr<nsIGSettingsCollection> proxy_settings =
202 mSchemeProxySettings.Get(key);
203 nsresult rv;
204 if (!proxy_settings) {
205 rv =
206 mGSettings->GetCollectionForSchema(key, getter_AddRefs(proxy_settings));
207 NS_ENSURE_SUCCESS(rv, rv);
208
209 mSchemeProxySettings.Put(key, proxy_settings);
210 }
211
212 nsAutoCString host;
213 rv = proxy_settings->GetString(NS_LITERAL_CSTRING("host"), host);
214 NS_ENSURE_SUCCESS(rv, rv);
215 if (host.IsEmpty()) return NS_ERROR_FAILURE;
216
217 int32_t port;
218 rv = proxy_settings->GetInt(NS_LITERAL_CSTRING("port"), &port);
219 NS_ENSURE_SUCCESS(rv, rv);
220
221 /* When port is 0, proxy is not considered as enabled even if host is set. */
222 if (port == 0) return NS_ERROR_FAILURE;
223
224 SetProxyResult(aType, host, port, aResult);
225 return NS_OK;
226 }
227
228 /* copied from nsProtocolProxyService.cpp --- we should share this! */
proxy_MaskIPv6Addr(PRIPv6Addr & addr,uint16_t mask_len)229 static void proxy_MaskIPv6Addr(PRIPv6Addr& addr, uint16_t mask_len) {
230 if (mask_len == 128) return;
231
232 if (mask_len > 96) {
233 addr.pr_s6_addr32[3] =
234 PR_htonl(PR_ntohl(addr.pr_s6_addr32[3]) & (~0L << (128 - mask_len)));
235 } else if (mask_len > 64) {
236 addr.pr_s6_addr32[3] = 0;
237 addr.pr_s6_addr32[2] =
238 PR_htonl(PR_ntohl(addr.pr_s6_addr32[2]) & (~0L << (96 - mask_len)));
239 } else if (mask_len > 32) {
240 addr.pr_s6_addr32[3] = 0;
241 addr.pr_s6_addr32[2] = 0;
242 addr.pr_s6_addr32[1] =
243 PR_htonl(PR_ntohl(addr.pr_s6_addr32[1]) & (~0L << (64 - mask_len)));
244 } else {
245 addr.pr_s6_addr32[3] = 0;
246 addr.pr_s6_addr32[2] = 0;
247 addr.pr_s6_addr32[1] = 0;
248 addr.pr_s6_addr32[0] =
249 PR_htonl(PR_ntohl(addr.pr_s6_addr32[0]) & (~0L << (32 - mask_len)));
250 }
251 }
252
ConvertToIPV6Addr(const nsACString & aName,PRIPv6Addr * aAddr,int32_t * aMask)253 static bool ConvertToIPV6Addr(const nsACString& aName, PRIPv6Addr* aAddr,
254 int32_t* aMask) {
255 PRNetAddr addr;
256 // try to convert hostname to IP
257 if (PR_StringToNetAddr(PromiseFlatCString(aName).get(), &addr) != PR_SUCCESS)
258 return false;
259
260 // convert parsed address to IPv6
261 if (addr.raw.family == PR_AF_INET) {
262 // convert to IPv4-mapped address
263 PR_ConvertIPv4AddrToIPv6(addr.inet.ip, aAddr);
264 if (aMask) {
265 if (*aMask <= 32)
266 *aMask += 96;
267 else
268 return false;
269 }
270 } else if (addr.raw.family == PR_AF_INET6) {
271 // copy the address
272 memcpy(aAddr, &addr.ipv6.ip, sizeof(PRIPv6Addr));
273 } else {
274 return false;
275 }
276
277 return true;
278 }
279
HostIgnoredByProxy(const nsACString & aIgnore,const nsACString & aHost)280 static bool HostIgnoredByProxy(const nsACString& aIgnore,
281 const nsACString& aHost) {
282 if (aIgnore.Equals(aHost, nsCaseInsensitiveCStringComparator)) return true;
283
284 if (aIgnore.First() == '*' &&
285 StringEndsWith(aHost, nsDependentCSubstring(aIgnore, 1),
286 nsCaseInsensitiveCStringComparator))
287 return true;
288
289 int32_t mask = 128;
290 nsReadingIterator<char> start;
291 nsReadingIterator<char> slash;
292 nsReadingIterator<char> end;
293 aIgnore.BeginReading(start);
294 aIgnore.BeginReading(slash);
295 aIgnore.EndReading(end);
296 if (FindCharInReadable('/', slash, end)) {
297 ++slash;
298 nsDependentCSubstring maskStr(slash, end);
299 nsAutoCString maskStr2(maskStr);
300 nsresult err;
301 mask = maskStr2.ToInteger(&err);
302 if (NS_FAILED(err)) {
303 mask = 128;
304 }
305 --slash;
306 } else {
307 slash = end;
308 }
309
310 nsDependentCSubstring ignoreStripped(start, slash);
311 PRIPv6Addr ignoreAddr, hostAddr;
312 if (!ConvertToIPV6Addr(ignoreStripped, &ignoreAddr, &mask) ||
313 !ConvertToIPV6Addr(aHost, &hostAddr, nullptr))
314 return false;
315
316 proxy_MaskIPv6Addr(ignoreAddr, mask);
317 proxy_MaskIPv6Addr(hostAddr, mask);
318
319 return memcmp(&ignoreAddr, &hostAddr, sizeof(PRIPv6Addr)) == 0;
320 }
321
GetProxyFromGSettings(const nsACString & aScheme,const nsACString & aHost,int32_t aPort,nsACString & aResult)322 nsresult nsUnixSystemProxySettings::GetProxyFromGSettings(
323 const nsACString& aScheme, const nsACString& aHost, int32_t aPort,
324 nsACString& aResult) {
325 nsCString proxyMode;
326 nsresult rv =
327 mProxySettings->GetString(NS_LITERAL_CSTRING("mode"), proxyMode);
328 NS_ENSURE_SUCCESS(rv, rv);
329
330 // return NS_ERROR_FAILURE when no proxy is set
331 if (!proxyMode.EqualsLiteral("manual")) {
332 return NS_ERROR_FAILURE;
333 }
334
335 nsCOMPtr<nsIArray> ignoreList;
336 if (NS_SUCCEEDED(mProxySettings->GetStringList(
337 NS_LITERAL_CSTRING("ignore-hosts"), getter_AddRefs(ignoreList))) &&
338 ignoreList) {
339 uint32_t len = 0;
340 ignoreList->GetLength(&len);
341 for (uint32_t i = 0; i < len; ++i) {
342 nsCOMPtr<nsISupportsCString> str = do_QueryElementAt(ignoreList, i);
343 if (str) {
344 nsCString s;
345 if (NS_SUCCEEDED(str->GetData(s)) && !s.IsEmpty()) {
346 if (HostIgnoredByProxy(s, aHost)) {
347 SetProxyResultDirect(aResult);
348 return NS_OK;
349 }
350 }
351 }
352 }
353 }
354
355 if (aScheme.LowerCaseEqualsLiteral("http")) {
356 rv = SetProxyResultFromGSettings("org.gnome.system.proxy.http", "PROXY",
357 aResult);
358 } else if (aScheme.LowerCaseEqualsLiteral("https")) {
359 rv = SetProxyResultFromGSettings("org.gnome.system.proxy.https", "PROXY",
360 aResult);
361 /* Try to use HTTP proxy when HTTPS proxy is not explicitly defined */
362 if (rv != NS_OK)
363 rv = SetProxyResultFromGSettings("org.gnome.system.proxy.http", "PROXY",
364 aResult);
365 } else if (aScheme.LowerCaseEqualsLiteral("ftp")) {
366 rv = SetProxyResultFromGSettings("org.gnome.system.proxy.ftp", "PROXY",
367 aResult);
368 } else {
369 rv = NS_ERROR_FAILURE;
370 }
371 if (rv != NS_OK) {
372 /* If proxy for scheme is not specified, use SOCKS proxy for all schemes */
373 rv = SetProxyResultFromGSettings("org.gnome.system.proxy.socks", "SOCKS",
374 aResult);
375 }
376
377 if (NS_FAILED(rv)) {
378 SetProxyResultDirect(aResult);
379 }
380
381 return NS_OK;
382 }
383
GetProxyForURI(const nsACString & aSpec,const nsACString & aScheme,const nsACString & aHost,const int32_t aPort,nsACString & aResult)384 nsresult nsUnixSystemProxySettings::GetProxyForURI(const nsACString& aSpec,
385 const nsACString& aScheme,
386 const nsACString& aHost,
387 const int32_t aPort,
388 nsACString& aResult) {
389 if (mProxySettings) {
390 nsresult rv = GetProxyFromGSettings(aScheme, aHost, aPort, aResult);
391 if (NS_SUCCEEDED(rv)) return rv;
392 }
393
394 return GetProxyFromEnvironment(aScheme, aHost, aPort, aResult);
395 }
396
NS_IMPL_COMPONENT_FACTORY(nsUnixSystemProxySettings)397 NS_IMPL_COMPONENT_FACTORY(nsUnixSystemProxySettings) {
398 auto result = MakeRefPtr<nsUnixSystemProxySettings>();
399 result->Init();
400 return result.forget().downcast<nsISupports>();
401 }
402