tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

CaptivePortalService.cpp (13553B)


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