michael@0: /* vim:set ts=4 sw=4 sts=4 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // michael@0: // HTTP Negotiate Authentication Support Module michael@0: // michael@0: // Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt michael@0: // (formerly draft-brezak-spnego-http-04.txt) michael@0: // michael@0: // Also described here: michael@0: // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp michael@0: // michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "nsAuth.h" michael@0: #include "nsHttpNegotiateAuth.h" michael@0: michael@0: #include "nsIHttpAuthenticableChannel.h" michael@0: #include "nsIProxiedChannel.h" michael@0: #include "nsIAuthModule.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsIProxyInfo.h" michael@0: #include "nsIURI.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsString.h" michael@0: #include "nsNetCID.h" michael@0: #include "plbase64.h" michael@0: #include "plstr.h" michael@0: #include "prprf.h" michael@0: #include "prlog.h" michael@0: #include "prmem.h" michael@0: #include "prnetdb.h" michael@0: #include "mozilla/Likely.h" michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: static const char kNegotiate[] = "Negotiate"; michael@0: static const char kNegotiateAuthTrustedURIs[] = "network.negotiate-auth.trusted-uris"; michael@0: static const char kNegotiateAuthDelegationURIs[] = "network.negotiate-auth.delegation-uris"; michael@0: static const char kNegotiateAuthAllowProxies[] = "network.negotiate-auth.allow-proxies"; michael@0: static const char kNegotiateAuthAllowNonFqdn[] = "network.negotiate-auth.allow-non-fqdn"; michael@0: static const char kNegotiateAuthSSPI[] = "network.auth.use-sspi"; michael@0: michael@0: #define kNegotiateLen (sizeof(kNegotiate)-1) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpNegotiateAuth::GetAuthFlags(uint32_t *flags) michael@0: { michael@0: // michael@0: // Negotiate Auth creds should not be reused across multiple requests. michael@0: // Only perform the negotiation when it is explicitly requested by the michael@0: // server. Thus, do *NOT* use the "REUSABLE_CREDENTIALS" flag here. michael@0: // michael@0: // CONNECTION_BASED is specified instead of REQUEST_BASED since we need michael@0: // to complete a sequence of transactions with the server over the same michael@0: // connection. michael@0: // michael@0: *flags = CONNECTION_BASED | IDENTITY_IGNORED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // Always set *identityInvalid == FALSE here. This michael@0: // will prevent the browser from popping up the authentication michael@0: // prompt window. Because GSSAPI does not have an API michael@0: // for fetching initial credentials (ex: A Kerberos TGT), michael@0: // there is no correct way to get the users credentials. michael@0: // michael@0: NS_IMETHODIMP michael@0: nsHttpNegotiateAuth::ChallengeReceived(nsIHttpAuthenticableChannel *authChannel, michael@0: const char *challenge, michael@0: bool isProxyAuth, michael@0: nsISupports **sessionState, michael@0: nsISupports **continuationState, michael@0: bool *identityInvalid) michael@0: { michael@0: nsIAuthModule *module = (nsIAuthModule *) *continuationState; michael@0: michael@0: *identityInvalid = false; michael@0: michael@0: /* Always fail Negotiate auth for Tor Browser. We don't need it. */ michael@0: return NS_ERROR_ABORT; michael@0: michael@0: if (module) michael@0: return NS_OK; michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr uri; michael@0: rv = authChannel->GetURI(getter_AddRefs(uri)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: uint32_t req_flags = nsIAuthModule::REQ_DEFAULT; michael@0: nsAutoCString service; michael@0: michael@0: if (isProxyAuth) { michael@0: if (!TestBoolPref(kNegotiateAuthAllowProxies)) { michael@0: LOG(("nsHttpNegotiateAuth::ChallengeReceived proxy auth blocked\n")); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: michael@0: req_flags |= nsIAuthModule::REQ_PROXY_AUTH; michael@0: nsCOMPtr proxyInfo; michael@0: authChannel->GetProxyInfo(getter_AddRefs(proxyInfo)); michael@0: NS_ENSURE_STATE(proxyInfo); michael@0: michael@0: proxyInfo->GetHost(service); michael@0: } michael@0: else { michael@0: bool allowed = TestNonFqdn(uri) || michael@0: TestPref(uri, kNegotiateAuthTrustedURIs); michael@0: if (!allowed) { michael@0: LOG(("nsHttpNegotiateAuth::ChallengeReceived URI blocked\n")); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: michael@0: bool delegation = TestPref(uri, kNegotiateAuthDelegationURIs); michael@0: if (delegation) { michael@0: LOG((" using REQ_DELEGATE\n")); michael@0: req_flags |= nsIAuthModule::REQ_DELEGATE; michael@0: } michael@0: michael@0: rv = uri->GetAsciiHost(service); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: michael@0: LOG((" service = %s\n", service.get())); michael@0: michael@0: // michael@0: // The correct service name for IIS servers is "HTTP/f.q.d.n", so michael@0: // construct the proper service name for passing to "gss_import_name". michael@0: // michael@0: // TODO: Possibly make this a configurable service name for use michael@0: // with non-standard servers that use stuff like "khttp/f.q.d.n" michael@0: // instead. michael@0: // michael@0: service.Insert("HTTP@", 0); michael@0: michael@0: const char *contractID; michael@0: if (TestBoolPref(kNegotiateAuthSSPI)) { michael@0: LOG((" using negotiate-sspi\n")); michael@0: contractID = NS_AUTH_MODULE_CONTRACTID_PREFIX "negotiate-sspi"; michael@0: } michael@0: else { michael@0: LOG((" using negotiate-gss\n")); michael@0: contractID = NS_AUTH_MODULE_CONTRACTID_PREFIX "negotiate-gss"; michael@0: } michael@0: michael@0: rv = CallCreateInstance(contractID, &module); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG((" Failed to load Negotiate Module \n")); michael@0: return rv; michael@0: } michael@0: michael@0: rv = module->Init(service.get(), req_flags, nullptr, nullptr, nullptr); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: NS_RELEASE(module); michael@0: return rv; michael@0: } michael@0: michael@0: *continuationState = module; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsHttpNegotiateAuth, nsIHttpAuthenticator) michael@0: michael@0: // michael@0: // GenerateCredentials michael@0: // michael@0: // This routine is responsible for creating the correct authentication michael@0: // blob to pass to the server that requested "Negotiate" authentication. michael@0: // michael@0: NS_IMETHODIMP michael@0: nsHttpNegotiateAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel, michael@0: const char *challenge, michael@0: bool isProxyAuth, michael@0: const char16_t *domain, michael@0: const char16_t *username, michael@0: const char16_t *password, michael@0: nsISupports **sessionState, michael@0: nsISupports **continuationState, michael@0: uint32_t *flags, michael@0: char **creds) michael@0: { michael@0: // ChallengeReceived must have been called previously. michael@0: nsIAuthModule *module = (nsIAuthModule *) *continuationState; michael@0: NS_ENSURE_TRUE(module, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: *flags = USING_INTERNAL_IDENTITY; michael@0: michael@0: LOG(("nsHttpNegotiateAuth::GenerateCredentials() [challenge=%s]\n", challenge)); michael@0: michael@0: NS_ASSERTION(creds, "null param"); michael@0: michael@0: #ifdef DEBUG michael@0: bool isGssapiAuth = michael@0: !PL_strncasecmp(challenge, kNegotiate, kNegotiateLen); michael@0: NS_ASSERTION(isGssapiAuth, "Unexpected challenge"); michael@0: #endif michael@0: michael@0: // michael@0: // If the "Negotiate:" header had some data associated with it, michael@0: // that data should be used as the input to this call. This may michael@0: // be a continuation of an earlier call because GSSAPI authentication michael@0: // often takes multiple round-trips to complete depending on the michael@0: // context flags given. We want to use MUTUAL_AUTHENTICATION which michael@0: // generally *does* require multiple round-trips. Don't assume michael@0: // auth can be completed in just 1 call. michael@0: // michael@0: unsigned int len = strlen(challenge); michael@0: michael@0: void *inToken, *outToken; michael@0: uint32_t inTokenLen, outTokenLen; michael@0: michael@0: if (len > kNegotiateLen) { michael@0: challenge += kNegotiateLen; michael@0: while (*challenge == ' ') michael@0: challenge++; michael@0: len = strlen(challenge); michael@0: michael@0: // strip off any padding (see bug 230351) michael@0: while (challenge[len - 1] == '=') michael@0: len--; michael@0: michael@0: inTokenLen = (len * 3)/4; michael@0: inToken = malloc(inTokenLen); michael@0: if (!inToken) michael@0: return (NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: // michael@0: // Decode the response that followed the "Negotiate" token michael@0: // michael@0: if (PL_Base64Decode(challenge, len, (char *) inToken) == nullptr) { michael@0: free(inToken); michael@0: return(NS_ERROR_UNEXPECTED); michael@0: } michael@0: } michael@0: else { michael@0: // michael@0: // Initializing, don't use an input token. michael@0: // michael@0: inToken = nullptr; michael@0: inTokenLen = 0; michael@0: } michael@0: michael@0: nsresult rv = module->GetNextToken(inToken, inTokenLen, &outToken, &outTokenLen); michael@0: michael@0: free(inToken); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (outTokenLen == 0) { michael@0: LOG((" No output token to send, exiting")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // michael@0: // base64 encode the output token. michael@0: // michael@0: char *encoded_token = PL_Base64Encode((char *)outToken, outTokenLen, nullptr); michael@0: michael@0: nsMemory::Free(outToken); michael@0: michael@0: if (!encoded_token) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: LOG((" Sending a token of length %d\n", outTokenLen)); michael@0: michael@0: // allocate a buffer sizeof("Negotiate" + " " + b64output_token + "\0") michael@0: *creds = (char *) nsMemory::Alloc(kNegotiateLen + 1 + strlen(encoded_token) + 1); michael@0: if (MOZ_UNLIKELY(!*creds)) michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: else michael@0: sprintf(*creds, "%s %s", kNegotiate, encoded_token); michael@0: michael@0: PR_Free(encoded_token); michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: nsHttpNegotiateAuth::TestBoolPref(const char *pref) michael@0: { michael@0: nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); michael@0: if (!prefs) michael@0: return false; michael@0: michael@0: bool val; michael@0: nsresult rv = prefs->GetBoolPref(pref, &val); michael@0: if (NS_FAILED(rv)) michael@0: return false; michael@0: michael@0: return val; michael@0: } michael@0: michael@0: bool michael@0: nsHttpNegotiateAuth::TestNonFqdn(nsIURI *uri) michael@0: { michael@0: nsAutoCString host; michael@0: PRNetAddr addr; michael@0: michael@0: if (!TestBoolPref(kNegotiateAuthAllowNonFqdn)) michael@0: return false; michael@0: michael@0: if (NS_FAILED(uri->GetAsciiHost(host))) michael@0: return false; michael@0: michael@0: // return true if host does not contain a dot and is not an ip address michael@0: return !host.IsEmpty() && host.FindChar('.') == kNotFound && michael@0: PR_StringToNetAddr(host.BeginReading(), &addr) != PR_SUCCESS; michael@0: } michael@0: michael@0: bool michael@0: nsHttpNegotiateAuth::TestPref(nsIURI *uri, const char *pref) michael@0: { michael@0: nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); michael@0: if (!prefs) michael@0: return false; michael@0: michael@0: nsAutoCString scheme, host; michael@0: int32_t port; michael@0: michael@0: if (NS_FAILED(uri->GetScheme(scheme))) michael@0: return false; michael@0: if (NS_FAILED(uri->GetAsciiHost(host))) michael@0: return false; michael@0: if (NS_FAILED(uri->GetPort(&port))) michael@0: return false; michael@0: michael@0: char *hostList; michael@0: if (NS_FAILED(prefs->GetCharPref(pref, &hostList)) || !hostList) michael@0: return false; michael@0: michael@0: // pseudo-BNF michael@0: // ---------- michael@0: // michael@0: // url-list base-url ( base-url "," LWS )* michael@0: // base-url ( scheme-part | host-part | scheme-part host-part ) michael@0: // scheme-part scheme "://" michael@0: // host-part host [":" port] michael@0: // michael@0: // for example: michael@0: // "https://, http://office.foo.com" michael@0: // michael@0: michael@0: char *start = hostList, *end; michael@0: for (;;) { michael@0: // skip past any whitespace michael@0: while (*start == ' ' || *start == '\t') michael@0: ++start; michael@0: end = strchr(start, ','); michael@0: if (!end) michael@0: end = start + strlen(start); michael@0: if (start == end) michael@0: break; michael@0: if (MatchesBaseURI(scheme, host, port, start, end)) michael@0: return true; michael@0: if (*end == '\0') michael@0: break; michael@0: start = end + 1; michael@0: } michael@0: michael@0: nsMemory::Free(hostList); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsHttpNegotiateAuth::MatchesBaseURI(const nsCSubstring &matchScheme, michael@0: const nsCSubstring &matchHost, michael@0: int32_t matchPort, michael@0: const char *baseStart, michael@0: const char *baseEnd) michael@0: { michael@0: // check if scheme://host:port matches baseURI michael@0: michael@0: // parse the base URI michael@0: const char *hostStart, *schemeEnd = strstr(baseStart, "://"); michael@0: if (schemeEnd) { michael@0: // the given scheme must match the parsed scheme exactly michael@0: if (!matchScheme.Equals(Substring(baseStart, schemeEnd))) michael@0: return false; michael@0: hostStart = schemeEnd + 3; michael@0: } michael@0: else michael@0: hostStart = baseStart; michael@0: michael@0: // XXX this does not work for IPv6-literals michael@0: const char *hostEnd = strchr(hostStart, ':'); michael@0: if (hostEnd && hostEnd < baseEnd) { michael@0: // the given port must match the parsed port exactly michael@0: int port = atoi(hostEnd + 1); michael@0: if (matchPort != (int32_t) port) michael@0: return false; michael@0: } michael@0: else michael@0: hostEnd = baseEnd; michael@0: michael@0: michael@0: // if we didn't parse out a host, then assume we got a match. michael@0: if (hostStart == hostEnd) michael@0: return true; michael@0: michael@0: uint32_t hostLen = hostEnd - hostStart; michael@0: michael@0: // matchHost must either equal host or be a subdomain of host michael@0: if (matchHost.Length() < hostLen) michael@0: return false; michael@0: michael@0: const char *end = matchHost.EndReading(); michael@0: if (PL_strncasecmp(end - hostLen, hostStart, hostLen) == 0) { michael@0: // if matchHost ends with host from the base URI, then make sure it is michael@0: // either an exact match, or prefixed with a dot. we don't want michael@0: // "foobar.com" to match "bar.com" michael@0: if (matchHost.Length() == hostLen || michael@0: *(end - hostLen) == '.' || michael@0: *(end - hostLen - 1) == '.') michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: }