Bug 1931748. Fix some readability-else-after-return issues in layout code. r=jfkthame
[gecko.git] / extensions / auth / nsHttpNegotiateAuth.cpp
bloba3ec224b59ee4b94b3954db61efdebe6735dd21c
1 /* vim:set ts=4 sw=2 sts=2 et cindent: */
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/. */
6 //
7 // HTTP Negotiate Authentication Support Module
8 //
9 // Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt
10 // (formerly draft-brezak-spnego-http-04.txt)
12 // Also described here:
13 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp
16 #include <stdlib.h>
18 #include "nsAuth.h"
19 #include "nsHttpNegotiateAuth.h"
21 #include "nsIHttpAuthenticableChannel.h"
22 #include "nsIAuthModule.h"
23 #include "nsIPrefBranch.h"
24 #include "nsIPrefService.h"
25 #include "nsIProxyInfo.h"
26 #include "nsIURI.h"
27 #include "nsCOMPtr.h"
28 #include "nsString.h"
29 #include "nsNetCID.h"
30 #include "nsProxyRelease.h"
31 #include "plbase64.h"
32 #include "mozilla/Base64.h"
33 #include "mozilla/Tokenizer.h"
34 #include "mozilla/UniquePtr.h"
35 #include "mozilla/Sprintf.h"
36 #include "nsIChannel.h"
37 #include "nsNetUtil.h"
38 #include "nsThreadUtils.h"
39 #include "nsIHttpAuthenticatorCallback.h"
40 #include "nsICancelable.h"
41 #include "mozilla/net/HttpAuthUtils.h"
42 #include "mozilla/ClearOnShutdown.h"
43 #include "mozilla/net/DNS.h"
44 #include "mozilla/StaticPrefs_browser.h"
46 using mozilla::Base64Decode;
48 //-----------------------------------------------------------------------------
50 static const char kNegotiate[] = "Negotiate";
51 static const char kNegotiateAuthTrustedURIs[] =
52 "network.negotiate-auth.trusted-uris";
53 static const char kNegotiateAuthDelegationURIs[] =
54 "network.negotiate-auth.delegation-uris";
55 static const char kNegotiateAuthAllowProxies[] =
56 "network.negotiate-auth.allow-proxies";
57 static const char kNegotiateAuthAllowNonFqdn[] =
58 "network.negotiate-auth.allow-non-fqdn";
59 static const char kNegotiateAuthSSPI[] = "network.auth.use-sspi";
60 static const char kSSOinPBmode[] = "network.auth.private-browsing-sso";
62 mozilla::StaticRefPtr<nsHttpNegotiateAuth> nsHttpNegotiateAuth::gSingleton;
64 #define kNegotiateLen (sizeof(kNegotiate) - 1)
65 #define DEFAULT_THREAD_TIMEOUT_MS 30000
67 //-----------------------------------------------------------------------------
69 // Return false when the channel comes from a Private browsing window.
70 static bool TestNotInPBMode(nsIHttpAuthenticableChannel* authChannel,
71 bool proxyAuth) {
72 // Proxy should go all the time, it's not considered a privacy leak
73 // to send default credentials to a proxy.
74 if (proxyAuth) {
75 return true;
78 nsCOMPtr<nsIChannel> bareChannel = do_QueryInterface(authChannel);
79 MOZ_ASSERT(bareChannel);
81 if (!NS_UsePrivateBrowsing(bareChannel)) {
82 return true;
85 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
86 if (prefs) {
87 bool ssoInPb;
88 if (NS_SUCCEEDED(prefs->GetBoolPref(kSSOinPBmode, &ssoInPb)) && ssoInPb) {
89 return true;
92 // When the "Never remember history" option is set, all channels are
93 // set PB mode flag, but here we want to make an exception, users
94 // want their credentials go out.
95 if (mozilla::StaticPrefs::browser_privatebrowsing_autostart()) {
96 return true;
100 return false;
103 already_AddRefed<nsIHttpAuthenticator> nsHttpNegotiateAuth::GetOrCreate() {
104 nsCOMPtr<nsIHttpAuthenticator> authenticator;
105 if (gSingleton) {
106 authenticator = gSingleton;
107 } else {
108 gSingleton = new nsHttpNegotiateAuth();
109 mozilla::ClearOnShutdown(&gSingleton);
110 authenticator = gSingleton;
113 return authenticator.forget();
116 NS_IMETHODIMP
117 nsHttpNegotiateAuth::GetAuthFlags(uint32_t* flags) {
119 // Negotiate Auth creds should not be reused across multiple requests.
120 // Only perform the negotiation when it is explicitly requested by the
121 // server. Thus, do *NOT* use the "REUSABLE_CREDENTIALS" flag here.
123 // CONNECTION_BASED is specified instead of REQUEST_BASED since we need
124 // to complete a sequence of transactions with the server over the same
125 // connection.
127 *flags = CONNECTION_BASED | IDENTITY_IGNORED;
128 return NS_OK;
132 // Always set *identityInvalid == FALSE here. This
133 // will prevent the browser from popping up the authentication
134 // prompt window. Because GSSAPI does not have an API
135 // for fetching initial credentials (ex: A Kerberos TGT),
136 // there is no correct way to get the users credentials.
138 NS_IMETHODIMP
139 nsHttpNegotiateAuth::ChallengeReceived(nsIHttpAuthenticableChannel* authChannel,
140 const nsACString& challenge,
141 bool isProxyAuth,
142 nsISupports** sessionState,
143 nsISupports** continuationState,
144 bool* identityInvalid) {
145 nsIAuthModule* rawModule = (nsIAuthModule*)*continuationState;
147 *identityInvalid = false;
148 if (rawModule) {
149 return NS_OK;
152 nsresult rv;
153 nsCOMPtr<nsIAuthModule> module;
155 nsCOMPtr<nsIURI> uri;
156 rv = authChannel->GetURI(getter_AddRefs(uri));
157 if (NS_FAILED(rv)) return rv;
159 uint32_t req_flags = nsIAuthModule::REQ_DEFAULT;
160 nsAutoCString service;
162 if (isProxyAuth) {
163 if (!TestBoolPref(kNegotiateAuthAllowProxies)) {
164 LOG(("nsHttpNegotiateAuth::ChallengeReceived proxy auth blocked\n"));
165 return NS_ERROR_ABORT;
168 req_flags |= nsIAuthModule::REQ_PROXY_AUTH;
169 nsCOMPtr<nsIProxyInfo> proxyInfo;
170 authChannel->GetProxyInfo(getter_AddRefs(proxyInfo));
171 NS_ENSURE_STATE(proxyInfo);
173 proxyInfo->GetHost(service);
174 } else {
175 bool allowed =
176 TestNotInPBMode(authChannel, isProxyAuth) &&
177 (TestNonFqdn(uri) || mozilla::net::auth::URIMatchesPrefPattern(
178 uri, kNegotiateAuthTrustedURIs));
179 if (!allowed) {
180 LOG(("nsHttpNegotiateAuth::ChallengeReceived URI blocked\n"));
181 return NS_ERROR_ABORT;
184 bool delegation = mozilla::net::auth::URIMatchesPrefPattern(
185 uri, kNegotiateAuthDelegationURIs);
186 if (delegation) {
187 LOG((" using REQ_DELEGATE\n"));
188 req_flags |= nsIAuthModule::REQ_DELEGATE;
191 rv = uri->GetAsciiHost(service);
192 if (NS_FAILED(rv)) return rv;
195 LOG((" service = %s\n", service.get()));
198 // The correct service name for IIS servers is "HTTP/f.q.d.n", so
199 // construct the proper service name for passing to "gss_import_name".
201 // TODO: Possibly make this a configurable service name for use
202 // with non-standard servers that use stuff like "khttp/f.q.d.n"
203 // instead.
205 service.InsertLiteral("HTTP@", 0);
207 const char* authType;
208 if (TestBoolPref(kNegotiateAuthSSPI)) {
209 LOG((" using negotiate-sspi\n"));
210 authType = "negotiate-sspi";
211 } else {
212 LOG((" using negotiate-gss\n"));
213 authType = "negotiate-gss";
216 MOZ_ALWAYS_TRUE(module = nsIAuthModule::CreateInstance(authType));
218 rv = module->Init(service, req_flags, u""_ns, u""_ns, u""_ns);
220 if (NS_FAILED(rv)) {
221 return rv;
224 module.forget(continuationState);
225 return NS_OK;
228 NS_IMPL_ISUPPORTS(nsHttpNegotiateAuth, nsIHttpAuthenticator)
230 namespace {
233 // GetNextTokenCompleteEvent
235 // This event is fired on main thread when async call of
236 // nsHttpNegotiateAuth::GenerateCredentials is finished. During the Run()
237 // method the nsIHttpAuthenticatorCallback::OnCredsAvailable is called with
238 // obtained credentials, flags and NS_OK when successful, otherwise
239 // NS_ERROR_FAILURE is returned as a result of failed operation.
241 class GetNextTokenCompleteEvent final : public nsIRunnable,
242 public nsICancelable {
243 public:
244 NS_DECL_THREADSAFE_ISUPPORTS
246 explicit GetNextTokenCompleteEvent(nsIHttpAuthenticatorCallback* aCallback)
247 : mCallback(aCallback) {}
249 nsresult DispatchSuccess(const nsACString& aCreds, uint32_t aFlags,
250 already_AddRefed<nsISupports> aSessionState,
251 already_AddRefed<nsISupports> aContinuationState) {
252 // Called from worker thread
253 MOZ_ASSERT(!NS_IsMainThread());
255 mCreds = aCreds;
256 mFlags = aFlags;
257 mResult = NS_OK;
258 mSessionState = aSessionState;
259 mContinuationState = aContinuationState;
260 return NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
263 nsresult DispatchError(already_AddRefed<nsISupports> aSessionState,
264 already_AddRefed<nsISupports> aContinuationState) {
265 // Called from worker thread
266 MOZ_ASSERT(!NS_IsMainThread());
268 mResult = NS_ERROR_FAILURE;
269 mSessionState = aSessionState;
270 mContinuationState = aContinuationState;
271 return NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
274 NS_IMETHODIMP Run() override {
275 // Runs on main thread
276 MOZ_ASSERT(NS_IsMainThread());
278 if (!mCancelled) {
279 nsCOMPtr<nsIHttpAuthenticatorCallback> callback;
280 callback.swap(mCallback);
281 callback->OnCredsGenerated(mCreds, mFlags, mResult, mSessionState,
282 mContinuationState);
284 return NS_OK;
287 NS_IMETHODIMP Cancel(nsresult aReason) override {
288 // Supposed to be called from main thread
289 MOZ_ASSERT(NS_IsMainThread());
291 mCancelled = true;
292 nsCOMPtr<nsIHttpAuthenticatorCallback> callback = std::move(mCallback);
293 if (callback) {
294 callback->OnCredsGenerated(mCreds, mFlags, aReason, nullptr, nullptr);
296 return NS_OK;
299 private:
300 virtual ~GetNextTokenCompleteEvent() = default;
302 nsCOMPtr<nsIHttpAuthenticatorCallback> mCallback;
303 nsCString mCreds;
304 uint32_t mFlags = 0;
305 nsresult mResult = NS_OK;
306 bool mCancelled = false;
307 nsCOMPtr<nsISupports> mSessionState;
308 nsCOMPtr<nsISupports> mContinuationState;
311 inline nsISupports* ToSupports(GetNextTokenCompleteEvent* aEvent) {
312 return static_cast<nsIRunnable*>(aEvent);
315 NS_IMPL_ISUPPORTS(GetNextTokenCompleteEvent, nsIRunnable, nsICancelable)
318 // GetNextTokenRunnable
320 // This runnable is created by GenerateCredentialsAsync and it runs
321 // on the background thread pool and calls GenerateCredentials.
323 class GetNextTokenRunnable final : public mozilla::Runnable {
324 ~GetNextTokenRunnable() override = default;
326 public:
327 GetNextTokenRunnable(
328 nsMainThreadPtrHandle<nsIHttpAuthenticableChannel>& authChannel,
329 const nsACString& challenge, bool isProxyAuth, const nsAString& domain,
330 const nsAString& username, const nsAString& password,
331 nsISupports* sessionState, nsISupports* continuationState,
332 nsMainThreadPtrHandle<GetNextTokenCompleteEvent>& aCompleteEvent)
333 : mozilla::Runnable("GetNextTokenRunnable"),
334 mAuthChannel(authChannel),
335 mChallenge(challenge),
336 mIsProxyAuth(isProxyAuth),
337 mDomain(domain),
338 mUsername(username),
339 mPassword(password),
340 mSessionState(sessionState),
341 mContinuationState(continuationState),
342 mCompleteEvent(aCompleteEvent) {}
344 NS_IMETHODIMP Run() override {
345 // Runs on worker thread
346 MOZ_ASSERT(!NS_IsMainThread());
348 nsCString creds;
349 uint32_t flags;
350 nsresult rv = ObtainCredentialsAndFlags(creds, &flags);
352 // Passing session and continuation state this way to not touch
353 // referencing of the object that may not be thread safe.
354 // Not having a thread safe referencing doesn't mean the object
355 // cannot be used on multiple threads (one example is nsAuthSSPI.)
356 // This ensures state objects will be destroyed on the main thread
357 // when not changed by GenerateCredentials.
358 if (NS_FAILED(rv)) {
359 return mCompleteEvent->DispatchError(mSessionState.forget(),
360 mContinuationState.forget());
363 return mCompleteEvent->DispatchSuccess(creds, flags, mSessionState.forget(),
364 mContinuationState.forget());
367 NS_IMETHODIMP ObtainCredentialsAndFlags(nsCString& aCreds, uint32_t* aFlags) {
368 nsresult rv;
370 // Use negotiate service to call GenerateCredentials outside of main thread
371 nsCOMPtr<nsIHttpAuthenticator> authenticator = new nsHttpNegotiateAuth();
373 nsISupports* sessionState = mSessionState;
374 nsISupports* continuationState = mContinuationState;
375 // The continuationState is for the sake of completeness propagated
376 // to the caller (despite it is not changed in any GenerateCredentials
377 // implementation).
379 // The only implementation that use sessionState is the
380 // nsHttpDigestAuth::GenerateCredentials. Since there's no reason
381 // to implement nsHttpDigestAuth::GenerateCredentialsAsync
382 // because digest auth does not block the main thread, we won't
383 // propagate changes to sessionState to the caller because of
384 // the change is too complicated on the caller side.
386 // Should any of the session or continuation states change inside
387 // this method, they must be threadsafe.
388 rv = authenticator->GenerateCredentials(
389 mAuthChannel, mChallenge, mIsProxyAuth, mDomain, mUsername, mPassword,
390 &sessionState, &continuationState, aFlags, aCreds);
391 if (mSessionState != sessionState) {
392 mSessionState = sessionState;
394 if (mContinuationState != continuationState) {
395 mContinuationState = continuationState;
397 return rv;
400 private:
401 nsMainThreadPtrHandle<nsIHttpAuthenticableChannel> mAuthChannel;
402 nsCString mChallenge;
403 bool mIsProxyAuth;
404 nsString mDomain;
405 nsString mUsername;
406 nsString mPassword;
407 nsCOMPtr<nsISupports> mSessionState;
408 nsCOMPtr<nsISupports> mContinuationState;
409 nsMainThreadPtrHandle<GetNextTokenCompleteEvent> mCompleteEvent;
412 } // anonymous namespace
414 NS_IMETHODIMP
415 nsHttpNegotiateAuth::GenerateCredentialsAsync(
416 nsIHttpAuthenticableChannel* authChannel,
417 nsIHttpAuthenticatorCallback* aCallback, const nsACString& challenge,
418 bool isProxyAuth, const nsAString& domain, const nsAString& username,
419 const nsAString& password, nsISupports* sessionState,
420 nsISupports* continuationState, nsICancelable** aCancelable) {
421 NS_ENSURE_ARG(aCallback);
422 NS_ENSURE_ARG_POINTER(aCancelable);
424 nsMainThreadPtrHandle<nsIHttpAuthenticableChannel> handle(
425 new nsMainThreadPtrHolder<nsIHttpAuthenticableChannel>(
426 "nsIHttpAuthenticableChannel", authChannel, false));
427 nsMainThreadPtrHandle<GetNextTokenCompleteEvent> cancelEvent(
428 new nsMainThreadPtrHolder<GetNextTokenCompleteEvent>(
429 "GetNextTokenCompleteEvent", new GetNextTokenCompleteEvent(aCallback),
430 false));
431 nsCOMPtr<nsIRunnable> getNextTokenRunnable = new GetNextTokenRunnable(
432 handle, challenge, isProxyAuth, domain, username, password, sessionState,
433 continuationState, cancelEvent);
435 nsresult rv = NS_DispatchBackgroundTask(
436 getNextTokenRunnable, nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK);
437 NS_ENSURE_SUCCESS(rv, rv);
439 RefPtr<GetNextTokenCompleteEvent> cancelable(cancelEvent.get());
440 cancelable.forget(aCancelable);
441 return NS_OK;
445 // GenerateCredentials
447 // This routine is responsible for creating the correct authentication
448 // blob to pass to the server that requested "Negotiate" authentication.
450 NS_IMETHODIMP
451 nsHttpNegotiateAuth::GenerateCredentials(
452 nsIHttpAuthenticableChannel* authChannel, const nsACString& aChallenge,
453 bool isProxyAuth, const nsAString& domain, const nsAString& username,
454 const nsAString& password, nsISupports** sessionState,
455 nsISupports** continuationState, uint32_t* flags, nsACString& creds) {
456 // ChallengeReceived must have been called previously.
457 nsIAuthModule* module = (nsIAuthModule*)*continuationState;
458 NS_ENSURE_TRUE(module, NS_ERROR_NOT_INITIALIZED);
460 *flags = USING_INTERNAL_IDENTITY;
462 LOG(("nsHttpNegotiateAuth::GenerateCredentials() [challenge=%s]\n",
463 aChallenge.BeginReading()));
465 #ifdef DEBUG
466 bool isGssapiAuth = StringBeginsWith(aChallenge, "Negotiate"_ns,
467 nsCaseInsensitiveCStringComparator);
468 NS_ASSERTION(isGssapiAuth, "Unexpected challenge");
469 #endif
472 // If the "Negotiate:" header had some data associated with it,
473 // that data should be used as the input to this call. This may
474 // be a continuation of an earlier call because GSSAPI authentication
475 // often takes multiple round-trips to complete depending on the
476 // context flags given. We want to use MUTUAL_AUTHENTICATION which
477 // generally *does* require multiple round-trips. Don't assume
478 // auth can be completed in just 1 call.
481 nsAutoCString inToken;
482 if (aChallenge.Length() > kNegotiateLen) {
483 nsDependentCSubstring challenge(aChallenge, kNegotiateLen);
484 uint32_t startPos = 0;
485 while (startPos < challenge.Length() && challenge[startPos] == ' ') {
486 startPos++;
488 if (startPos == challenge.Length()) {
489 return NS_ERROR_UNEXPECTED;
492 // strip off any padding (see bug 230351)
493 uint32_t len = challenge.Length();
494 while (len > startPos && challenge[len - 1] == '=') {
495 len--;
499 // Decode the response that followed the "Negotiate" token
501 (void)Base64Decode(
502 nsDependentCSubstring(challenge, startPos, len - startPos), inToken);
505 void* outToken = nullptr;
506 uint32_t outTokenLen = 0;
507 nsresult rv = module->GetNextToken(inToken.get(), inToken.Length(), &outToken,
508 &outTokenLen);
509 if (NS_FAILED(rv)) {
510 if (outToken) {
511 // Technically if the call fails we shouln't have allocated, but
512 // Coverity doesn't know that.
513 free(outToken);
515 return rv;
518 if (outTokenLen == 0) {
519 LOG((" No output token to send, exiting"));
520 return NS_ERROR_FAILURE;
524 // base64 encode the output token.
526 nsAutoCString encodedToken;
527 rv = mozilla::Base64Encode(
528 nsDependentCSubstring((char*)outToken, outTokenLen), encodedToken);
529 free(outToken);
530 if (NS_FAILED(rv)) {
531 return rv;
534 LOG((" Sending a token of length %d\n", outTokenLen));
536 creds = nsPrintfCString("%s %s", kNegotiate, encodedToken.get());
537 return rv;
540 bool nsHttpNegotiateAuth::TestBoolPref(const char* pref) {
541 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
542 if (!prefs) return false;
544 bool val;
545 nsresult rv = prefs->GetBoolPref(pref, &val);
546 if (NS_FAILED(rv)) return false;
548 return val;
551 bool nsHttpNegotiateAuth::TestNonFqdn(nsIURI* uri) {
552 nsAutoCString host;
554 if (!TestBoolPref(kNegotiateAuthAllowNonFqdn)) {
555 return false;
558 if (NS_FAILED(uri->GetAsciiHost(host))) {
559 return false;
562 // return true if host does not contain a dot and is not an ip address
563 return !host.IsEmpty() && !host.Contains('.') &&
564 !mozilla::net::HostIsIPLiteral(host);