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