1 /* -*- Mode: C++; tab-width: 4; 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 "nsReadConfig.h"
7 #include "nsJSConfigTriggers.h"
8 
9 #include "mozilla/Components.h"
10 #include "nsAppDirectoryServiceDefs.h"
11 #include "nsIAppStartup.h"
12 #include "nsContentUtils.h"
13 #include "nsDirectoryServiceDefs.h"
14 #include "nsIFile.h"
15 #include "nsIObserverService.h"
16 #include "nsIPrefBranch.h"
17 #include "nsIPrefService.h"
18 #include "nsIPromptService.h"
19 #include "nsIStringBundle.h"
20 #include "nsNetUtil.h"
21 #include "nsString.h"
22 #include "nsCRT.h"
23 #include "nspr.h"
24 #include "nsXULAppAPI.h"
25 
26 using namespace mozilla;
27 
28 extern bool sandboxEnabled;
29 
30 extern mozilla::LazyLogModule MCD;
31 
32 extern nsresult CentralizedAdminPrefManagerInit(bool aSandboxEnabled);
33 extern nsresult CentralizedAdminPrefManagerFinish();
34 
DisplayError(void)35 static nsresult DisplayError(void) {
36   nsresult rv;
37 
38   nsCOMPtr<nsIPromptService> promptService =
39       do_GetService("@mozilla.org/embedcomp/prompt-service;1");
40   if (!promptService) return NS_ERROR_FAILURE;
41 
42   nsCOMPtr<nsIStringBundleService> bundleService =
43       do_GetService(NS_STRINGBUNDLE_CONTRACTID);
44   if (!bundleService) return NS_ERROR_FAILURE;
45 
46   nsCOMPtr<nsIStringBundle> bundle;
47   bundleService->CreateBundle(
48       "chrome://autoconfig/locale/autoconfig.properties",
49       getter_AddRefs(bundle));
50   if (!bundle) return NS_ERROR_FAILURE;
51 
52   nsAutoString title;
53   rv = bundle->GetStringFromName("readConfigTitle", title);
54   if (NS_FAILED(rv)) return rv;
55 
56   nsAutoString err;
57   rv = bundle->GetStringFromName("readConfigMsg", err);
58   if (NS_FAILED(rv)) return rv;
59 
60   return promptService->Alert(nullptr, title.get(), err.get());
61 }
62 
63 // nsISupports Implementation
64 
NS_IMPL_ISUPPORTS(nsReadConfig,nsIObserver)65 NS_IMPL_ISUPPORTS(nsReadConfig, nsIObserver)
66 
67 nsReadConfig::nsReadConfig() : mRead(false) {}
68 
Init()69 nsresult nsReadConfig::Init() {
70   nsresult rv;
71 
72   nsCOMPtr<nsIObserverService> observerService =
73       do_GetService("@mozilla.org/observer-service;1", &rv);
74 
75   if (observerService) {
76     rv =
77         observerService->AddObserver(this, NS_PREFSERVICE_READ_TOPIC_ID, false);
78   }
79   return (rv);
80 }
81 
~nsReadConfig()82 nsReadConfig::~nsReadConfig() { CentralizedAdminPrefManagerFinish(); }
83 
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * someData)84 NS_IMETHODIMP nsReadConfig::Observe(nsISupports* aSubject, const char* aTopic,
85                                     const char16_t* someData) {
86   nsresult rv = NS_OK;
87 
88   if (!nsCRT::strcmp(aTopic, NS_PREFSERVICE_READ_TOPIC_ID)) {
89     rv = readConfigFile();
90     // Don't show error alerts if the sandbox is enabled, just show
91     // sandbox warning.
92     if (NS_FAILED(rv)) {
93       if (sandboxEnabled) {
94         nsContentUtils::ReportToConsoleNonLocalized(
95             NS_LITERAL_STRING("Autoconfig is sandboxed by default. See "
96                               "https://support.mozilla.org/products/"
97                               "firefox-enterprise for more information."),
98             nsIScriptError::warningFlag, NS_LITERAL_CSTRING("autoconfig"),
99             nullptr);
100       } else {
101         rv = DisplayError();
102         if (NS_FAILED(rv)) {
103           nsCOMPtr<nsIAppStartup> appStartup =
104               components::AppStartup::Service();
105           if (appStartup) appStartup->Quit(nsIAppStartup::eAttemptQuit);
106         }
107       }
108     }
109   }
110   return rv;
111 }
112 
113 /**
114  * This is the blocklist for known bad autoconfig files.
115  */
116 static const char* gBlockedConfigs[] = {"dsengine.cfg"};
117 
readConfigFile()118 nsresult nsReadConfig::readConfigFile() {
119   nsresult rv = NS_OK;
120   nsAutoCString lockFileName;
121   nsAutoCString lockVendor;
122   uint32_t fileNameLen = 0;
123 
124   nsCOMPtr<nsIPrefBranch> defaultPrefBranch;
125   nsCOMPtr<nsIPrefService> prefService =
126       do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
127   if (NS_FAILED(rv)) return rv;
128 
129   rv =
130       prefService->GetDefaultBranch(nullptr, getter_AddRefs(defaultPrefBranch));
131   if (NS_FAILED(rv)) return rv;
132 
133   NS_NAMED_LITERAL_CSTRING(channel, MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL));
134 
135   bool sandboxEnabled =
136       channel.EqualsLiteral("beta") || channel.EqualsLiteral("release");
137 
138   mozilla::Unused << defaultPrefBranch->GetBoolPref(
139       "general.config.sandbox_enabled", &sandboxEnabled);
140 
141   rv = defaultPrefBranch->GetCharPref("general.config.filename", lockFileName);
142 
143   if (NS_FAILED(rv)) return rv;
144 
145   MOZ_LOG(MCD, LogLevel::Debug,
146           ("general.config.filename = %s\n", lockFileName.get()));
147 
148   for (size_t index = 0, len = mozilla::ArrayLength(gBlockedConfigs);
149        index < len; ++index) {
150     if (lockFileName == gBlockedConfigs[index]) {
151       // This is NS_OK because we don't want to show an error to the user
152       return rv;
153     }
154   }
155 
156   // This needs to be read only once.
157   //
158   if (!mRead) {
159     // Initiate the new JS Context for Preference management
160 
161     rv = CentralizedAdminPrefManagerInit(sandboxEnabled);
162     if (NS_FAILED(rv)) return rv;
163 
164     // Open and evaluate function calls to set/lock/unlock prefs
165     rv = openAndEvaluateJSFile("prefcalls.js", 0, false, false);
166     if (NS_FAILED(rv)) return rv;
167 
168     mRead = true;
169   }
170   // If the lockFileName is nullptr return ok, because no lockFile will be used
171 
172   // Once the config file is read, we should check that the vendor name
173   // is consistent By checking for the vendor name after reading the config
174   // file we allow for the preference to be set (and locked) by the creator
175   // of the cfg file meaning the file can not be renamed (successfully).
176 
177   nsCOMPtr<nsIPrefBranch> prefBranch;
178   rv = prefService->GetBranch(nullptr, getter_AddRefs(prefBranch));
179   NS_ENSURE_SUCCESS(rv, rv);
180 
181   int32_t obscureValue = 0;
182   (void)defaultPrefBranch->GetIntPref("general.config.obscure_value",
183                                       &obscureValue);
184   MOZ_LOG(MCD, LogLevel::Debug,
185           ("evaluating .cfg file %s with obscureValue %d\n", lockFileName.get(),
186            obscureValue));
187   rv = openAndEvaluateJSFile(lockFileName.get(), obscureValue, true, true);
188   if (NS_FAILED(rv)) {
189     MOZ_LOG(MCD, LogLevel::Debug,
190             ("error evaluating .cfg file %s %" PRIx32 "\n", lockFileName.get(),
191              static_cast<uint32_t>(rv)));
192     return rv;
193   }
194 
195   rv = prefBranch->GetCharPref("general.config.filename", lockFileName);
196   if (NS_FAILED(rv))
197     // There is NO REASON we should ever get here. This is POST reading
198     // of the config file.
199     return NS_ERROR_FAILURE;
200 
201   rv = prefBranch->GetCharPref("general.config.vendor", lockVendor);
202   // If vendor is not nullptr, do this check
203   if (NS_SUCCEEDED(rv)) {
204     fileNameLen = strlen(lockFileName.get());
205 
206     // lockVendor and lockFileName should be the same with the addtion of
207     // .cfg to the filename by checking this post reading of the cfg file
208     // this value can be set within the cfg file adding a level of security.
209 
210     if (PL_strncmp(lockFileName.get(), lockVendor.get(), fileNameLen - 4) != 0)
211       return NS_ERROR_FAILURE;
212   }
213 
214   // get the value of the autoconfig url
215   nsAutoCString urlName;
216   rv = prefBranch->GetCharPref("autoadmin.global_config_url", urlName);
217   if (NS_SUCCEEDED(rv) && !urlName.IsEmpty()) {
218     // Instantiating nsAutoConfig object if the pref is present
219     mAutoConfig = new nsAutoConfig();
220 
221     rv = mAutoConfig->Init();
222     if (NS_WARN_IF(NS_FAILED(rv))) {
223       return rv;
224     }
225 
226     mAutoConfig->SetConfigURL(urlName.get());
227   }
228 
229   return NS_OK;
230 }  // ReadConfigFile
231 
openAndEvaluateJSFile(const char * aFileName,int32_t obscureValue,bool isEncoded,bool isBinDir)232 nsresult nsReadConfig::openAndEvaluateJSFile(const char* aFileName,
233                                              int32_t obscureValue,
234                                              bool isEncoded, bool isBinDir) {
235   nsresult rv;
236 
237   nsCOMPtr<nsIInputStream> inStr;
238   if (isBinDir) {
239     nsCOMPtr<nsIFile> jsFile;
240     rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(jsFile));
241     if (NS_FAILED(rv)) return rv;
242 
243     rv = jsFile->AppendNative(nsDependentCString(aFileName));
244     if (NS_FAILED(rv)) return rv;
245 
246     rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), jsFile);
247     if (NS_FAILED(rv)) return rv;
248 
249   } else {
250     nsAutoCString location("resource://gre/defaults/autoconfig/");
251     location += aFileName;
252 
253     nsCOMPtr<nsIURI> uri;
254     rv = NS_NewURI(getter_AddRefs(uri), location);
255     NS_ENSURE_SUCCESS(rv, rv);
256 
257     nsCOMPtr<nsIChannel> channel;
258     rv = NS_NewChannel(getter_AddRefs(channel), uri,
259                        nsContentUtils::GetSystemPrincipal(),
260                        nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
261                        nsIContentPolicy::TYPE_OTHER);
262     NS_ENSURE_SUCCESS(rv, rv);
263 
264     rv = channel->Open(getter_AddRefs(inStr));
265     NS_ENSURE_SUCCESS(rv, rv);
266   }
267 
268   uint64_t fs64;
269   uint32_t amt = 0;
270   rv = inStr->Available(&fs64);
271   if (NS_FAILED(rv)) return rv;
272   // This used to use PR_Malloc(), which doesn't support over 4GB.
273   if (fs64 > UINT32_MAX) return NS_ERROR_FILE_TOO_BIG;
274   uint32_t fs = (uint32_t)fs64;
275 
276   char* buf = (char*)malloc(fs * sizeof(char));
277   if (!buf) return NS_ERROR_OUT_OF_MEMORY;
278 
279   rv = inStr->Read(buf, (uint32_t)fs, &amt);
280   NS_ASSERTION((amt == fs), "failed to read the entire configuration file!!");
281   if (NS_SUCCEEDED(rv)) {
282     if (obscureValue > 0) {
283       // Unobscure file by subtracting some value from every char.
284       for (uint32_t i = 0; i < amt; i++) buf[i] -= obscureValue;
285     }
286     rv = EvaluateAdminConfigScript(buf, amt, aFileName, false, true, isEncoded,
287                                    !isBinDir);
288   }
289   inStr->Close();
290   free(buf);
291 
292   return rv;
293 }
294