1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 #include "mozilla/net/CaptivePortalService.h"
6 #include "mozilla/AppShutdown.h"
7 #include "mozilla/ClearOnShutdown.h"
8 #include "mozilla/Services.h"
9 #include "mozilla/Preferences.h"
10 #include "nsIObserverService.h"
11 #include "nsServiceManagerUtils.h"
12 #include "nsXULAppAPI.h"
13 #include "xpcpublic.h"
14
15 static constexpr auto kInterfaceName = u"captive-portal-inteface"_ns;
16
17 static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login";
18 static const char kAbortCaptivePortalLoginEvent[] =
19 "captive-portal-login-abort";
20 static const char kCaptivePortalLoginSuccessEvent[] =
21 "captive-portal-login-success";
22
23 namespace mozilla {
24 namespace net {
25
26 static LazyLogModule gCaptivePortalLog("CaptivePortalService");
27 #undef LOG
28 #define LOG(args) MOZ_LOG(gCaptivePortalLog, mozilla::LogLevel::Debug, args)
29
30 NS_IMPL_ISUPPORTS(CaptivePortalService, nsICaptivePortalService, nsIObserver,
31 nsISupportsWeakReference, nsITimerCallback,
32 nsICaptivePortalCallback, nsINamed)
33
34 static StaticRefPtr<CaptivePortalService> gCPService;
35
36 // static
GetSingleton()37 already_AddRefed<nsICaptivePortalService> CaptivePortalService::GetSingleton() {
38 if (gCPService) {
39 return do_AddRef(gCPService);
40 }
41
42 gCPService = new CaptivePortalService();
43 ClearOnShutdown(&gCPService);
44 return do_AddRef(gCPService);
45 }
46
CaptivePortalService()47 CaptivePortalService::CaptivePortalService() {
48 mLastChecked = TimeStamp::Now();
49 }
50
~CaptivePortalService()51 CaptivePortalService::~CaptivePortalService() {
52 LOG(("CaptivePortalService::~CaptivePortalService isParentProcess:%d\n",
53 XRE_GetProcessType() == GeckoProcessType_Default));
54 }
55
PerformCheck()56 nsresult CaptivePortalService::PerformCheck() {
57 LOG(
58 ("CaptivePortalService::PerformCheck mRequestInProgress:%d "
59 "mInitialized:%d mStarted:%d\n",
60 mRequestInProgress, mInitialized, mStarted));
61 // Don't issue another request if last one didn't complete
62 if (mRequestInProgress || !mInitialized || !mStarted) {
63 return NS_OK;
64 }
65 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
66 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
67 }
68 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
69 nsresult rv;
70 if (!mCaptivePortalDetector) {
71 mCaptivePortalDetector =
72 do_CreateInstance("@mozilla.org/toolkit/captive-detector;1", &rv);
73 if (NS_FAILED(rv)) {
74 LOG(("Unable to get a captive portal detector\n"));
75 return rv;
76 }
77 }
78
79 LOG(("CaptivePortalService::PerformCheck - Calling CheckCaptivePortal\n"));
80 mRequestInProgress = true;
81 mCaptivePortalDetector->CheckCaptivePortal(kInterfaceName, this);
82 return NS_OK;
83 }
84
RearmTimer()85 nsresult CaptivePortalService::RearmTimer() {
86 LOG(("CaptivePortalService::RearmTimer\n"));
87 // Start a timer to recheck
88 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
89 if (mTimer) {
90 mTimer->Cancel();
91 }
92
93 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
94 mTimer = nullptr;
95 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
96 }
97
98 // If we have successfully determined the state, and we have never detected
99 // a captive portal, we don't need to keep polling, but will rely on events
100 // to trigger detection.
101 if (mState == NOT_CAPTIVE) {
102 return NS_OK;
103 }
104
105 if (!mTimer) {
106 mTimer = NS_NewTimer();
107 }
108
109 if (mTimer && mDelay > 0) {
110 LOG(("CaptivePortalService - Reloading timer with delay %u\n", mDelay));
111 return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT);
112 }
113
114 return NS_OK;
115 }
116
Initialize()117 nsresult CaptivePortalService::Initialize() {
118 if (mInitialized) {
119 return NS_OK;
120 }
121 mInitialized = true;
122
123 // Only the main process service should actually do anything. The service in
124 // the content process only mirrors the CP state in the main process.
125 if (XRE_GetProcessType() != GeckoProcessType_Default) {
126 return NS_OK;
127 }
128
129 nsCOMPtr<nsIObserverService> observerService =
130 mozilla::services::GetObserverService();
131 if (observerService) {
132 observerService->AddObserver(this, kOpenCaptivePortalLoginEvent, true);
133 observerService->AddObserver(this, kAbortCaptivePortalLoginEvent, true);
134 observerService->AddObserver(this, kCaptivePortalLoginSuccessEvent, true);
135 observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
136 }
137
138 LOG(("Initialized CaptivePortalService\n"));
139 return NS_OK;
140 }
141
Start()142 nsresult CaptivePortalService::Start() {
143 if (!mInitialized) {
144 return NS_ERROR_NOT_INITIALIZED;
145 }
146
147 if (xpc::AreNonLocalConnectionsDisabled() &&
148 !Preferences::GetBool("network.captive-portal-service.testMode", false)) {
149 return NS_ERROR_NOT_AVAILABLE;
150 }
151
152 if (XRE_GetProcessType() != GeckoProcessType_Default) {
153 // Doesn't do anything if called in the content process.
154 return NS_OK;
155 }
156
157 if (mStarted) {
158 return NS_OK;
159 }
160
161 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
162 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
163 }
164
165 MOZ_ASSERT(mState == UNKNOWN, "Initial state should be UNKNOWN");
166 mStarted = true;
167 mEverBeenCaptive = false;
168
169 // Get the delay prefs
170 Preferences::GetUint("network.captive-portal-service.minInterval",
171 &mMinInterval);
172 Preferences::GetUint("network.captive-portal-service.maxInterval",
173 &mMaxInterval);
174 Preferences::GetFloat("network.captive-portal-service.backoffFactor",
175 &mBackoffFactor);
176
177 LOG(("CaptivePortalService::Start min:%u max:%u backoff:%.2f\n", mMinInterval,
178 mMaxInterval, mBackoffFactor));
179
180 mSlackCount = 0;
181 mDelay = mMinInterval;
182
183 // When Start is called, perform a check immediately
184 PerformCheck();
185 RearmTimer();
186 return NS_OK;
187 }
188
Stop()189 nsresult CaptivePortalService::Stop() {
190 LOG(("CaptivePortalService::Stop\n"));
191
192 if (XRE_GetProcessType() != GeckoProcessType_Default) {
193 // Doesn't do anything when called in the content process.
194 return NS_OK;
195 }
196
197 if (!mStarted) {
198 return NS_OK;
199 }
200
201 if (mTimer) {
202 mTimer->Cancel();
203 }
204 mTimer = nullptr;
205 mRequestInProgress = false;
206 mStarted = false;
207 mEverBeenCaptive = false;
208 if (mCaptivePortalDetector) {
209 mCaptivePortalDetector->Abort(kInterfaceName);
210 }
211 mCaptivePortalDetector = nullptr;
212
213 // Clear the state in case anyone queries the state while detection is off.
214 mState = UNKNOWN;
215 return NS_OK;
216 }
217
SetStateInChild(int32_t aState)218 void CaptivePortalService::SetStateInChild(int32_t aState) {
219 // This should only be called in the content process, from ContentChild.cpp
220 // in order to mirror the captive portal state set in the chrome process.
221 MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default);
222
223 mState = aState;
224 mLastChecked = TimeStamp::Now();
225 }
226
227 //-----------------------------------------------------------------------------
228 // CaptivePortalService::nsICaptivePortalService
229 //-----------------------------------------------------------------------------
230
231 NS_IMETHODIMP
GetState(int32_t * aState)232 CaptivePortalService::GetState(int32_t* aState) {
233 *aState = mState;
234 return NS_OK;
235 }
236
237 NS_IMETHODIMP
RecheckCaptivePortal()238 CaptivePortalService::RecheckCaptivePortal() {
239 LOG(("CaptivePortalService::RecheckCaptivePortal\n"));
240
241 if (XRE_GetProcessType() != GeckoProcessType_Default) {
242 // Doesn't do anything if called in the content process.
243 return NS_OK;
244 }
245
246 // This is called for user activity. We need to reset the slack count,
247 // so the checks continue to be quite frequent.
248 mSlackCount = 0;
249 mDelay = mMinInterval;
250
251 PerformCheck();
252 RearmTimer();
253 return NS_OK;
254 }
255
256 NS_IMETHODIMP
GetLastChecked(uint64_t * aLastChecked)257 CaptivePortalService::GetLastChecked(uint64_t* aLastChecked) {
258 double duration = (TimeStamp::Now() - mLastChecked).ToMilliseconds();
259 *aLastChecked = static_cast<uint64_t>(duration);
260 return NS_OK;
261 }
262
263 //-----------------------------------------------------------------------------
264 // CaptivePortalService::nsITimer
265 // This callback gets called every mDelay miliseconds
266 // It issues a checkCaptivePortal operation if one isn't already in progress
267 //-----------------------------------------------------------------------------
268 NS_IMETHODIMP
Notify(nsITimer * aTimer)269 CaptivePortalService::Notify(nsITimer* aTimer) {
270 LOG(("CaptivePortalService::Notify\n"));
271 MOZ_ASSERT(aTimer == mTimer);
272 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
273
274 PerformCheck();
275
276 // This is needed because we don't want to always make requests very often.
277 // Every 10 checks, we the delay is increased mBackoffFactor times
278 // to a maximum delay of mMaxInterval
279 mSlackCount++;
280 if (mSlackCount % 10 == 0) {
281 mDelay = mDelay * mBackoffFactor;
282 }
283 if (mDelay > mMaxInterval) {
284 mDelay = mMaxInterval;
285 }
286
287 // Note - if mDelay is 0, the timer will not be rearmed.
288 RearmTimer();
289
290 return NS_OK;
291 }
292
293 //-----------------------------------------------------------------------------
294 // CaptivePortalService::nsINamed
295 //-----------------------------------------------------------------------------
296
297 NS_IMETHODIMP
GetName(nsACString & aName)298 CaptivePortalService::GetName(nsACString& aName) {
299 aName.AssignLiteral("CaptivePortalService");
300 return NS_OK;
301 }
302
303 //-----------------------------------------------------------------------------
304 // CaptivePortalService::nsIObserver
305 //-----------------------------------------------------------------------------
306 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)307 CaptivePortalService::Observe(nsISupports* aSubject, const char* aTopic,
308 const char16_t* aData) {
309 if (XRE_GetProcessType() != GeckoProcessType_Default) {
310 // Doesn't do anything if called in the content process.
311 return NS_OK;
312 }
313
314 LOG(("CaptivePortalService::Observe() topic=%s\n", aTopic));
315 if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) {
316 // A redirect or altered content has been detected.
317 // The user needs to log in. We are in a captive portal.
318 StateTransition(LOCKED_PORTAL);
319 mLastChecked = TimeStamp::Now();
320 mEverBeenCaptive = true;
321 } else if (!strcmp(aTopic, kCaptivePortalLoginSuccessEvent)) {
322 // The user has successfully logged in. We have connectivity.
323 StateTransition(UNLOCKED_PORTAL);
324 mLastChecked = TimeStamp::Now();
325 mSlackCount = 0;
326 mDelay = mMinInterval;
327
328 RearmTimer();
329 } else if (!strcmp(aTopic, kAbortCaptivePortalLoginEvent)) {
330 // The login has been aborted
331 StateTransition(UNKNOWN);
332 mLastChecked = TimeStamp::Now();
333 mSlackCount = 0;
334 } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
335 Stop();
336 return NS_OK;
337 }
338
339 // Send notification so that the captive portal state is mirrored in the
340 // content process.
341 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
342 if (observerService) {
343 nsCOMPtr<nsICaptivePortalService> cps(this);
344 observerService->NotifyObservers(cps, NS_IPC_CAPTIVE_PORTAL_SET_STATE,
345 nullptr);
346 }
347
348 return NS_OK;
349 }
350
NotifyConnectivityAvailable(bool aCaptive)351 void CaptivePortalService::NotifyConnectivityAvailable(bool aCaptive) {
352 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
353 if (observerService) {
354 nsCOMPtr<nsICaptivePortalService> cps(this);
355 observerService->NotifyObservers(cps, NS_CAPTIVE_PORTAL_CONNECTIVITY,
356 aCaptive ? u"captive" : u"clear");
357 }
358 }
359
360 //-----------------------------------------------------------------------------
361 // CaptivePortalService::nsICaptivePortalCallback
362 //-----------------------------------------------------------------------------
363 NS_IMETHODIMP
Prepare()364 CaptivePortalService::Prepare() {
365 LOG(("CaptivePortalService::Prepare\n"));
366 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
367 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
368 return NS_OK;
369 }
370 // XXX: Finish preparation shouldn't be called until dns and routing is
371 // available.
372 if (mCaptivePortalDetector) {
373 mCaptivePortalDetector->FinishPreparation(kInterfaceName);
374 }
375 return NS_OK;
376 }
377
378 NS_IMETHODIMP
Complete(bool success)379 CaptivePortalService::Complete(bool success) {
380 LOG(("CaptivePortalService::Complete(success=%d) mState=%d\n", success,
381 mState));
382 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
383 mLastChecked = TimeStamp::Now();
384
385 // Note: this callback gets called when:
386 // 1. the request is completed, and content is valid (success == true)
387 // 2. when the request is aborted or times out (success == false)
388
389 if (success) {
390 if (mEverBeenCaptive) {
391 StateTransition(UNLOCKED_PORTAL);
392 NotifyConnectivityAvailable(true);
393 } else {
394 StateTransition(NOT_CAPTIVE);
395 NotifyConnectivityAvailable(false);
396 }
397 }
398
399 mRequestInProgress = false;
400 return NS_OK;
401 }
402
StateTransition(int32_t aNewState)403 void CaptivePortalService::StateTransition(int32_t aNewState) {
404 int32_t oldState = mState;
405 mState = aNewState;
406
407 if ((oldState == UNKNOWN && mState == NOT_CAPTIVE) ||
408 (oldState == LOCKED_PORTAL && mState == UNLOCKED_PORTAL)) {
409 nsCOMPtr<nsIObserverService> observerService =
410 services::GetObserverService();
411 if (observerService) {
412 nsCOMPtr<nsICaptivePortalService> cps(this);
413 observerService->NotifyObservers(
414 cps, NS_CAPTIVE_PORTAL_CONNECTIVITY_CHANGED, nullptr);
415 }
416 }
417 }
418
419 } // namespace net
420 } // namespace mozilla
421