michael@0: /* vim:set ts=4 sw=4 sts=4 et ci: */ 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: // HttpLog.h should generally be included first michael@0: #include "HttpLog.h" michael@0: michael@0: #include "nsHttpNTLMAuth.h" michael@0: #include "nsIAuthModule.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "plbase64.h" michael@0: #include "prnetdb.h" michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIHttpAuthenticableChannel.h" michael@0: #include "nsIURI.h" michael@0: #ifdef XP_WIN michael@0: #include "nsIX509Cert.h" michael@0: #include "nsISSLStatus.h" michael@0: #include "nsISSLStatusProvider.h" michael@0: #endif michael@0: #include "mozilla/Attributes.h" michael@0: #include "nsThreadUtils.h" michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: static const char kAllowProxies[] = "network.automatic-ntlm-auth.allow-proxies"; michael@0: static const char kAllowNonFqdn[] = "network.automatic-ntlm-auth.allow-non-fqdn"; michael@0: static const char kTrustedURIs[] = "network.automatic-ntlm-auth.trusted-uris"; michael@0: static const char kForceGeneric[] = "network.auth.force-generic-ntlm"; michael@0: static const char kAllowGenericHTTP[] = "network.negotiate-auth.allow-insecure-ntlm-v1"; michael@0: static const char kAllowGenericHTTPS[] = "network.negotiate-auth.allow-insecure-ntlm-v1-https"; michael@0: michael@0: // XXX MatchesBaseURI and TestPref are duplicated in nsHttpNegotiateAuth.cpp, michael@0: // but since that file lives in a separate library we cannot directly share it. michael@0: // bug 236865 addresses this problem. michael@0: michael@0: static bool michael@0: 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: } michael@0: michael@0: static bool michael@0: IsNonFqdn(nsIURI *uri) michael@0: { michael@0: nsAutoCString host; michael@0: PRNetAddr addr; 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: static bool michael@0: 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: // Check to see if we should use our generic (internal) NTLM auth module. michael@0: static bool michael@0: ForceGenericNTLM() michael@0: { michael@0: nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); michael@0: if (!prefs) michael@0: return false; michael@0: bool flag = false; michael@0: michael@0: if (NS_FAILED(prefs->GetBoolPref(kForceGeneric, &flag))) michael@0: flag = false; michael@0: michael@0: LOG(("Force use of generic ntlm auth module: %d\n", flag)); michael@0: return flag; michael@0: } michael@0: michael@0: // Check to see if we should use our generic (internal) NTLM auth module. michael@0: static bool michael@0: AllowGenericNTLM() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); michael@0: if (!prefs) michael@0: return false; michael@0: michael@0: bool flag = false; michael@0: if (NS_FAILED(prefs->GetBoolPref(kAllowGenericHTTP, &flag))) michael@0: flag = false; michael@0: michael@0: LOG(("Allow use of generic ntlm auth module: %d\n", flag)); michael@0: return flag; michael@0: } michael@0: michael@0: // Check to see if we should use our generic (internal) NTLM auth module. michael@0: static bool michael@0: AllowGenericNTLMforHTTPS(nsIHttpAuthenticableChannel *channel) michael@0: { michael@0: bool isSSL = false; michael@0: channel->GetIsSSL(&isSSL); michael@0: if (!isSSL) michael@0: return false; michael@0: michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); michael@0: if (!prefs) michael@0: return false; michael@0: michael@0: bool flag = false; michael@0: if (NS_FAILED(prefs->GetBoolPref(kAllowGenericHTTPS, &flag))) michael@0: flag = false; michael@0: michael@0: LOG(("Allow use of generic ntlm auth module for only https: %d\n", flag)); michael@0: return flag; michael@0: } michael@0: michael@0: // Check to see if we should use default credentials for this host or proxy. michael@0: static bool michael@0: CanUseDefaultCredentials(nsIHttpAuthenticableChannel *channel, michael@0: bool isProxyAuth) michael@0: { michael@0: nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); michael@0: if (!prefs) michael@0: return false; michael@0: michael@0: if (isProxyAuth) { michael@0: bool val; michael@0: if (NS_FAILED(prefs->GetBoolPref(kAllowProxies, &val))) michael@0: val = false; michael@0: LOG(("Default credentials allowed for proxy: %d\n", val)); michael@0: return val; michael@0: } michael@0: michael@0: nsCOMPtr uri; michael@0: channel->GetURI(getter_AddRefs(uri)); michael@0: michael@0: bool allowNonFqdn; michael@0: if (NS_FAILED(prefs->GetBoolPref(kAllowNonFqdn, &allowNonFqdn))) michael@0: allowNonFqdn = false; michael@0: if (allowNonFqdn && uri && IsNonFqdn(uri)) { michael@0: LOG(("Host is non-fqdn, default credentials are allowed\n")); michael@0: return true; michael@0: } michael@0: michael@0: bool isTrustedHost = (uri && TestPref(uri, kTrustedURIs)); michael@0: LOG(("Default credentials allowed for host: %d\n", isTrustedHost)); michael@0: return isTrustedHost; michael@0: } michael@0: michael@0: // Dummy class for session state object. This class doesn't hold any data. michael@0: // Instead we use its existence as a flag. See ChallengeReceived. michael@0: class nsNTLMSessionState MOZ_FINAL : public nsISupports michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: }; michael@0: NS_IMPL_ISUPPORTS0(nsNTLMSessionState) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_ISUPPORTS(nsHttpNTLMAuth, nsIHttpAuthenticator) michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpNTLMAuth::ChallengeReceived(nsIHttpAuthenticableChannel *channel, 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: LOG(("nsHttpNTLMAuth::ChallengeReceived [ss=%p cs=%p]\n", michael@0: *sessionState, *continuationState)); michael@0: michael@0: // Use the native NTLM if available michael@0: mUseNative = true; michael@0: michael@0: // NOTE: we don't define any session state, but we do use the pointer. 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: // Start a new auth sequence if the challenge is exactly "NTLM". michael@0: // If native NTLM auth apis are available and enabled through prefs, michael@0: // try to use them. michael@0: if (PL_strcasecmp(challenge, "NTLM") == 0) { michael@0: nsCOMPtr module; michael@0: michael@0: // Check to see if we should default to our generic NTLM auth module michael@0: // through UseGenericNTLM. (We use native auth by default if the michael@0: // system provides it.) If *sessionState is non-null, we failed to michael@0: // instantiate a native NTLM module the last time, so skip trying again. michael@0: bool forceGeneric = ForceGenericNTLM(); michael@0: if (!forceGeneric && !*sessionState) { michael@0: // Check for approved default credentials hosts and proxies. If michael@0: // *continuationState is non-null, the last authentication attempt michael@0: // failed so skip default credential use. michael@0: if (!*continuationState && CanUseDefaultCredentials(channel, isProxyAuth)) { michael@0: // Try logging in with the user's default credentials. If michael@0: // successful, |identityInvalid| is false, which will trigger michael@0: // a default credentials attempt once we return. michael@0: module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sys-ntlm"); michael@0: } michael@0: #ifdef XP_WIN michael@0: else { michael@0: // Try to use native NTLM and prompt the user for their domain, michael@0: // username, and password. (only supported by windows nsAuthSSPI module.) michael@0: // Note, for servers that use LMv1 a weak hash of the user's password michael@0: // will be sent. We rely on windows internal apis to decide whether michael@0: // we should support this older, less secure version of the protocol. michael@0: module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sys-ntlm"); michael@0: *identityInvalid = true; michael@0: } michael@0: #endif // XP_WIN michael@0: #ifdef PR_LOGGING michael@0: if (!module) michael@0: LOG(("Native sys-ntlm auth module not found.\n")); michael@0: #endif michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: // On windows, never fall back unless the user has specifically requested so. michael@0: if (!forceGeneric && !module) michael@0: return NS_ERROR_UNEXPECTED; michael@0: #endif michael@0: michael@0: // If no native support was available. Fall back on our internal NTLM implementation. michael@0: if (!module) { michael@0: if (!*sessionState) { michael@0: // Remember the fact that we cannot use the "sys-ntlm" module, michael@0: // so we don't ever bother trying again for this auth domain. michael@0: *sessionState = new nsNTLMSessionState(); michael@0: if (!*sessionState) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ADDREF(*sessionState); michael@0: } michael@0: michael@0: // Use our internal NTLM implementation. Note, this is less secure, michael@0: // see bug 520607 for details. michael@0: michael@0: // For now with default preference settings (i.e. allow-insecure-ntlm-v1-https = true michael@0: // and allow-insecure-ntlm-v1 = false) we don't allow authentication to any proxy, michael@0: // either http or https. This will be fixed in a followup bug. michael@0: if (AllowGenericNTLM() || (!isProxyAuth && AllowGenericNTLMforHTTPS(channel))) { michael@0: LOG(("Trying to fall back on internal ntlm auth.\n")); michael@0: module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "ntlm"); michael@0: } michael@0: michael@0: mUseNative = false; michael@0: michael@0: // Prompt user for domain, username, and password. michael@0: *identityInvalid = true; michael@0: } michael@0: michael@0: // If this fails, then it means that we cannot do NTLM auth. michael@0: if (!module) { michael@0: LOG(("No ntlm auth modules available.\n")); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // A non-null continuation state implies that we failed to authenticate. michael@0: // Blow away the old authentication state, and use the new one. michael@0: module.swap(*continuationState); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpNTLMAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel, michael@0: const char *challenge, michael@0: bool isProxyAuth, michael@0: const char16_t *domain, michael@0: const char16_t *user, michael@0: const char16_t *pass, michael@0: nsISupports **sessionState, michael@0: nsISupports **continuationState, michael@0: uint32_t *aFlags, michael@0: char **creds) michael@0: michael@0: { michael@0: LOG(("nsHttpNTLMAuth::GenerateCredentials\n")); michael@0: michael@0: *creds = nullptr; michael@0: *aFlags = 0; michael@0: michael@0: // if user or password is empty, ChallengeReceived returned michael@0: // identityInvalid = false, that means we are using default user michael@0: // credentials; see nsAuthSSPI::Init method for explanation of this michael@0: // condition michael@0: if (!user || !pass) michael@0: *aFlags = USING_INTERNAL_IDENTITY; michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr module = do_QueryInterface(*continuationState, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: void *inBuf, *outBuf; michael@0: uint32_t inBufLen, outBufLen; michael@0: michael@0: // initial challenge michael@0: if (PL_strcasecmp(challenge, "NTLM") == 0) { michael@0: // NTLM service name format is 'HTTP@host' for both http and https michael@0: nsCOMPtr uri; michael@0: rv = authChannel->GetURI(getter_AddRefs(uri)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: nsAutoCString serviceName, host; michael@0: rv = uri->GetAsciiHost(host); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: serviceName.AppendLiteral("HTTP@"); michael@0: serviceName.Append(host); michael@0: // initialize auth module michael@0: uint32_t reqFlags = nsIAuthModule::REQ_DEFAULT; michael@0: if (isProxyAuth) michael@0: reqFlags |= nsIAuthModule::REQ_PROXY_AUTH; michael@0: michael@0: rv = module->Init(serviceName.get(), reqFlags, domain, user, pass); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // This update enables updated Windows machines (Win7 or patched previous michael@0: // versions) and Linux machines running Samba (updated for Channel michael@0: // Binding), to perform Channel Binding when authenticating using NTLMv2 michael@0: // and an outer secure channel. michael@0: // michael@0: // Currently only implemented for Windows, linux support will be landing in michael@0: // a separate patch, update this #ifdef accordingly then. michael@0: #if defined (XP_WIN) /* || defined (LINUX) */ michael@0: // We should retrieve the server certificate and compute the CBT, michael@0: // but only when we are using the native NTLM implementation and michael@0: // not the internal one. michael@0: // It is a valid case not having the security info object. This michael@0: // occures when we connect an https site through an ntlm proxy. michael@0: // After the ssl tunnel has been created, we get here the second michael@0: // time and now generate the CBT from now valid security info. michael@0: nsCOMPtr channel = do_QueryInterface(authChannel, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr security; michael@0: rv = channel->GetSecurityInfo(getter_AddRefs(security)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr statusProvider = michael@0: do_QueryInterface(security); michael@0: michael@0: if (mUseNative && statusProvider) { michael@0: nsCOMPtr status; michael@0: rv = statusProvider->GetSSLStatus(getter_AddRefs(status)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr cert; michael@0: rv = status->GetServerCert(getter_AddRefs(cert)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: uint32_t length; michael@0: uint8_t* certArray; michael@0: cert->GetRawDER(&length, &certArray); michael@0: michael@0: // If there is a server certificate, we pass it along the michael@0: // first time we call GetNextToken(). michael@0: inBufLen = length; michael@0: inBuf = certArray; michael@0: } else { michael@0: // If there is no server certificate, we don't pass anything. michael@0: inBufLen = 0; michael@0: inBuf = nullptr; michael@0: } michael@0: #else // Extended protection update is just for Linux and Windows machines. michael@0: inBufLen = 0; michael@0: inBuf = nullptr; michael@0: #endif michael@0: } michael@0: else { michael@0: // decode challenge; skip past "NTLM " to the start of the base64 michael@0: // encoded data. michael@0: int len = strlen(challenge); michael@0: if (len < 6) michael@0: return NS_ERROR_UNEXPECTED; // bogus challenge michael@0: challenge += 5; michael@0: len -= 5; michael@0: michael@0: // strip off any padding (see bug 230351) michael@0: while (challenge[len - 1] == '=') michael@0: len--; michael@0: michael@0: // decode into the input secbuffer michael@0: inBufLen = (len * 3)/4; // sufficient size (see plbase64.h) michael@0: inBuf = nsMemory::Alloc(inBufLen); michael@0: if (!inBuf) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: if (PL_Base64Decode(challenge, len, (char *) inBuf) == nullptr) { michael@0: nsMemory::Free(inBuf); michael@0: return NS_ERROR_UNEXPECTED; // improper base64 encoding michael@0: } michael@0: } michael@0: michael@0: rv = module->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // base64 encode data in output buffer and prepend "NTLM " michael@0: int credsLen = 5 + ((outBufLen + 2)/3)*4; michael@0: *creds = (char *) nsMemory::Alloc(credsLen + 1); michael@0: if (!*creds) michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: else { michael@0: memcpy(*creds, "NTLM ", 5); michael@0: PL_Base64Encode((char *) outBuf, outBufLen, *creds + 5); michael@0: (*creds)[credsLen] = '\0'; // null terminate michael@0: } michael@0: // OK, we are done with |outBuf| michael@0: nsMemory::Free(outBuf); michael@0: } michael@0: michael@0: if (inBuf) michael@0: nsMemory::Free(inBuf); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpNTLMAuth::GetAuthFlags(uint32_t *flags) michael@0: { michael@0: *flags = CONNECTION_BASED | IDENTITY_INCLUDES_DOMAIN | IDENTITY_ENCRYPTED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace mozilla::net michael@0: } // namespace mozilla