diff -r 000000000000 -r 6474c204b198 netwerk/protocol/http/nsHttpNTLMAuth.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/netwerk/protocol/http/nsHttpNTLMAuth.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,541 @@ +/* vim:set ts=4 sw=4 sts=4 et ci: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// HttpLog.h should generally be included first +#include "HttpLog.h" + +#include "nsHttpNTLMAuth.h" +#include "nsIAuthModule.h" +#include "nsCOMPtr.h" +#include "plbase64.h" +#include "prnetdb.h" + +//----------------------------------------------------------------------------- + +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsIHttpAuthenticableChannel.h" +#include "nsIURI.h" +#ifdef XP_WIN +#include "nsIX509Cert.h" +#include "nsISSLStatus.h" +#include "nsISSLStatusProvider.h" +#endif +#include "mozilla/Attributes.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace net { + +static const char kAllowProxies[] = "network.automatic-ntlm-auth.allow-proxies"; +static const char kAllowNonFqdn[] = "network.automatic-ntlm-auth.allow-non-fqdn"; +static const char kTrustedURIs[] = "network.automatic-ntlm-auth.trusted-uris"; +static const char kForceGeneric[] = "network.auth.force-generic-ntlm"; +static const char kAllowGenericHTTP[] = "network.negotiate-auth.allow-insecure-ntlm-v1"; +static const char kAllowGenericHTTPS[] = "network.negotiate-auth.allow-insecure-ntlm-v1-https"; + +// XXX MatchesBaseURI and TestPref are duplicated in nsHttpNegotiateAuth.cpp, +// but since that file lives in a separate library we cannot directly share it. +// bug 236865 addresses this problem. + +static bool +MatchesBaseURI(const nsCSubstring &matchScheme, + const nsCSubstring &matchHost, + int32_t matchPort, + const char *baseStart, + const char *baseEnd) +{ + // check if scheme://host:port matches baseURI + + // parse the base URI + const char *hostStart, *schemeEnd = strstr(baseStart, "://"); + if (schemeEnd) { + // the given scheme must match the parsed scheme exactly + if (!matchScheme.Equals(Substring(baseStart, schemeEnd))) + return false; + hostStart = schemeEnd + 3; + } + else + hostStart = baseStart; + + // XXX this does not work for IPv6-literals + const char *hostEnd = strchr(hostStart, ':'); + if (hostEnd && hostEnd < baseEnd) { + // the given port must match the parsed port exactly + int port = atoi(hostEnd + 1); + if (matchPort != (int32_t) port) + return false; + } + else + hostEnd = baseEnd; + + + // if we didn't parse out a host, then assume we got a match. + if (hostStart == hostEnd) + return true; + + uint32_t hostLen = hostEnd - hostStart; + + // matchHost must either equal host or be a subdomain of host + if (matchHost.Length() < hostLen) + return false; + + const char *end = matchHost.EndReading(); + if (PL_strncasecmp(end - hostLen, hostStart, hostLen) == 0) { + // if matchHost ends with host from the base URI, then make sure it is + // either an exact match, or prefixed with a dot. we don't want + // "foobar.com" to match "bar.com" + if (matchHost.Length() == hostLen || + *(end - hostLen) == '.' || + *(end - hostLen - 1) == '.') + return true; + } + + return false; +} + +static bool +IsNonFqdn(nsIURI *uri) +{ + nsAutoCString host; + PRNetAddr addr; + + if (NS_FAILED(uri->GetAsciiHost(host))) + return false; + + // return true if host does not contain a dot and is not an ip address + return !host.IsEmpty() && host.FindChar('.') == kNotFound && + PR_StringToNetAddr(host.BeginReading(), &addr) != PR_SUCCESS; +} + +static bool +TestPref(nsIURI *uri, const char *pref) +{ + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!prefs) + return false; + + nsAutoCString scheme, host; + int32_t port; + + if (NS_FAILED(uri->GetScheme(scheme))) + return false; + if (NS_FAILED(uri->GetAsciiHost(host))) + return false; + if (NS_FAILED(uri->GetPort(&port))) + return false; + + char *hostList; + if (NS_FAILED(prefs->GetCharPref(pref, &hostList)) || !hostList) + return false; + + // pseudo-BNF + // ---------- + // + // url-list base-url ( base-url "," LWS )* + // base-url ( scheme-part | host-part | scheme-part host-part ) + // scheme-part scheme "://" + // host-part host [":" port] + // + // for example: + // "https://, http://office.foo.com" + // + + char *start = hostList, *end; + for (;;) { + // skip past any whitespace + while (*start == ' ' || *start == '\t') + ++start; + end = strchr(start, ','); + if (!end) + end = start + strlen(start); + if (start == end) + break; + if (MatchesBaseURI(scheme, host, port, start, end)) + return true; + if (*end == '\0') + break; + start = end + 1; + } + + nsMemory::Free(hostList); + return false; +} + +// Check to see if we should use our generic (internal) NTLM auth module. +static bool +ForceGenericNTLM() +{ + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!prefs) + return false; + bool flag = false; + + if (NS_FAILED(prefs->GetBoolPref(kForceGeneric, &flag))) + flag = false; + + LOG(("Force use of generic ntlm auth module: %d\n", flag)); + return flag; +} + +// Check to see if we should use our generic (internal) NTLM auth module. +static bool +AllowGenericNTLM() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!prefs) + return false; + + bool flag = false; + if (NS_FAILED(prefs->GetBoolPref(kAllowGenericHTTP, &flag))) + flag = false; + + LOG(("Allow use of generic ntlm auth module: %d\n", flag)); + return flag; +} + +// Check to see if we should use our generic (internal) NTLM auth module. +static bool +AllowGenericNTLMforHTTPS(nsIHttpAuthenticableChannel *channel) +{ + bool isSSL = false; + channel->GetIsSSL(&isSSL); + if (!isSSL) + return false; + + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!prefs) + return false; + + bool flag = false; + if (NS_FAILED(prefs->GetBoolPref(kAllowGenericHTTPS, &flag))) + flag = false; + + LOG(("Allow use of generic ntlm auth module for only https: %d\n", flag)); + return flag; +} + +// Check to see if we should use default credentials for this host or proxy. +static bool +CanUseDefaultCredentials(nsIHttpAuthenticableChannel *channel, + bool isProxyAuth) +{ + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!prefs) + return false; + + if (isProxyAuth) { + bool val; + if (NS_FAILED(prefs->GetBoolPref(kAllowProxies, &val))) + val = false; + LOG(("Default credentials allowed for proxy: %d\n", val)); + return val; + } + + nsCOMPtr uri; + channel->GetURI(getter_AddRefs(uri)); + + bool allowNonFqdn; + if (NS_FAILED(prefs->GetBoolPref(kAllowNonFqdn, &allowNonFqdn))) + allowNonFqdn = false; + if (allowNonFqdn && uri && IsNonFqdn(uri)) { + LOG(("Host is non-fqdn, default credentials are allowed\n")); + return true; + } + + bool isTrustedHost = (uri && TestPref(uri, kTrustedURIs)); + LOG(("Default credentials allowed for host: %d\n", isTrustedHost)); + return isTrustedHost; +} + +// Dummy class for session state object. This class doesn't hold any data. +// Instead we use its existence as a flag. See ChallengeReceived. +class nsNTLMSessionState MOZ_FINAL : public nsISupports +{ +public: + NS_DECL_ISUPPORTS +}; +NS_IMPL_ISUPPORTS0(nsNTLMSessionState) + +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsHttpNTLMAuth, nsIHttpAuthenticator) + +NS_IMETHODIMP +nsHttpNTLMAuth::ChallengeReceived(nsIHttpAuthenticableChannel *channel, + const char *challenge, + bool isProxyAuth, + nsISupports **sessionState, + nsISupports **continuationState, + bool *identityInvalid) +{ + LOG(("nsHttpNTLMAuth::ChallengeReceived [ss=%p cs=%p]\n", + *sessionState, *continuationState)); + + // Use the native NTLM if available + mUseNative = true; + + // NOTE: we don't define any session state, but we do use the pointer. + + *identityInvalid = false; + + /* Always fail Negotiate auth for Tor Browser. We don't need it. */ + return NS_ERROR_ABORT; + + // Start a new auth sequence if the challenge is exactly "NTLM". + // If native NTLM auth apis are available and enabled through prefs, + // try to use them. + if (PL_strcasecmp(challenge, "NTLM") == 0) { + nsCOMPtr module; + + // Check to see if we should default to our generic NTLM auth module + // through UseGenericNTLM. (We use native auth by default if the + // system provides it.) If *sessionState is non-null, we failed to + // instantiate a native NTLM module the last time, so skip trying again. + bool forceGeneric = ForceGenericNTLM(); + if (!forceGeneric && !*sessionState) { + // Check for approved default credentials hosts and proxies. If + // *continuationState is non-null, the last authentication attempt + // failed so skip default credential use. + if (!*continuationState && CanUseDefaultCredentials(channel, isProxyAuth)) { + // Try logging in with the user's default credentials. If + // successful, |identityInvalid| is false, which will trigger + // a default credentials attempt once we return. + module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sys-ntlm"); + } +#ifdef XP_WIN + else { + // Try to use native NTLM and prompt the user for their domain, + // username, and password. (only supported by windows nsAuthSSPI module.) + // Note, for servers that use LMv1 a weak hash of the user's password + // will be sent. We rely on windows internal apis to decide whether + // we should support this older, less secure version of the protocol. + module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sys-ntlm"); + *identityInvalid = true; + } +#endif // XP_WIN +#ifdef PR_LOGGING + if (!module) + LOG(("Native sys-ntlm auth module not found.\n")); +#endif + } + +#ifdef XP_WIN + // On windows, never fall back unless the user has specifically requested so. + if (!forceGeneric && !module) + return NS_ERROR_UNEXPECTED; +#endif + + // If no native support was available. Fall back on our internal NTLM implementation. + if (!module) { + if (!*sessionState) { + // Remember the fact that we cannot use the "sys-ntlm" module, + // so we don't ever bother trying again for this auth domain. + *sessionState = new nsNTLMSessionState(); + if (!*sessionState) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*sessionState); + } + + // Use our internal NTLM implementation. Note, this is less secure, + // see bug 520607 for details. + + // For now with default preference settings (i.e. allow-insecure-ntlm-v1-https = true + // and allow-insecure-ntlm-v1 = false) we don't allow authentication to any proxy, + // either http or https. This will be fixed in a followup bug. + if (AllowGenericNTLM() || (!isProxyAuth && AllowGenericNTLMforHTTPS(channel))) { + LOG(("Trying to fall back on internal ntlm auth.\n")); + module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "ntlm"); + } + + mUseNative = false; + + // Prompt user for domain, username, and password. + *identityInvalid = true; + } + + // If this fails, then it means that we cannot do NTLM auth. + if (!module) { + LOG(("No ntlm auth modules available.\n")); + return NS_ERROR_UNEXPECTED; + } + + // A non-null continuation state implies that we failed to authenticate. + // Blow away the old authentication state, and use the new one. + module.swap(*continuationState); + } + return NS_OK; +} + +NS_IMETHODIMP +nsHttpNTLMAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel, + const char *challenge, + bool isProxyAuth, + const char16_t *domain, + const char16_t *user, + const char16_t *pass, + nsISupports **sessionState, + nsISupports **continuationState, + uint32_t *aFlags, + char **creds) + +{ + LOG(("nsHttpNTLMAuth::GenerateCredentials\n")); + + *creds = nullptr; + *aFlags = 0; + + // if user or password is empty, ChallengeReceived returned + // identityInvalid = false, that means we are using default user + // credentials; see nsAuthSSPI::Init method for explanation of this + // condition + if (!user || !pass) + *aFlags = USING_INTERNAL_IDENTITY; + + nsresult rv; + nsCOMPtr module = do_QueryInterface(*continuationState, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + void *inBuf, *outBuf; + uint32_t inBufLen, outBufLen; + + // initial challenge + if (PL_strcasecmp(challenge, "NTLM") == 0) { + // NTLM service name format is 'HTTP@host' for both http and https + nsCOMPtr uri; + rv = authChannel->GetURI(getter_AddRefs(uri)); + if (NS_FAILED(rv)) + return rv; + nsAutoCString serviceName, host; + rv = uri->GetAsciiHost(host); + if (NS_FAILED(rv)) + return rv; + serviceName.AppendLiteral("HTTP@"); + serviceName.Append(host); + // initialize auth module + uint32_t reqFlags = nsIAuthModule::REQ_DEFAULT; + if (isProxyAuth) + reqFlags |= nsIAuthModule::REQ_PROXY_AUTH; + + rv = module->Init(serviceName.get(), reqFlags, domain, user, pass); + if (NS_FAILED(rv)) + return rv; + +// This update enables updated Windows machines (Win7 or patched previous +// versions) and Linux machines running Samba (updated for Channel +// Binding), to perform Channel Binding when authenticating using NTLMv2 +// and an outer secure channel. +// +// Currently only implemented for Windows, linux support will be landing in +// a separate patch, update this #ifdef accordingly then. +#if defined (XP_WIN) /* || defined (LINUX) */ + // We should retrieve the server certificate and compute the CBT, + // but only when we are using the native NTLM implementation and + // not the internal one. + // It is a valid case not having the security info object. This + // occures when we connect an https site through an ntlm proxy. + // After the ssl tunnel has been created, we get here the second + // time and now generate the CBT from now valid security info. + nsCOMPtr channel = do_QueryInterface(authChannel, &rv); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr security; + rv = channel->GetSecurityInfo(getter_AddRefs(security)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr statusProvider = + do_QueryInterface(security); + + if (mUseNative && statusProvider) { + nsCOMPtr status; + rv = statusProvider->GetSSLStatus(getter_AddRefs(status)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr cert; + rv = status->GetServerCert(getter_AddRefs(cert)); + if (NS_FAILED(rv)) + return rv; + + uint32_t length; + uint8_t* certArray; + cert->GetRawDER(&length, &certArray); + + // If there is a server certificate, we pass it along the + // first time we call GetNextToken(). + inBufLen = length; + inBuf = certArray; + } else { + // If there is no server certificate, we don't pass anything. + inBufLen = 0; + inBuf = nullptr; + } +#else // Extended protection update is just for Linux and Windows machines. + inBufLen = 0; + inBuf = nullptr; +#endif + } + else { + // decode challenge; skip past "NTLM " to the start of the base64 + // encoded data. + int len = strlen(challenge); + if (len < 6) + return NS_ERROR_UNEXPECTED; // bogus challenge + challenge += 5; + len -= 5; + + // strip off any padding (see bug 230351) + while (challenge[len - 1] == '=') + len--; + + // decode into the input secbuffer + inBufLen = (len * 3)/4; // sufficient size (see plbase64.h) + inBuf = nsMemory::Alloc(inBufLen); + if (!inBuf) + return NS_ERROR_OUT_OF_MEMORY; + + if (PL_Base64Decode(challenge, len, (char *) inBuf) == nullptr) { + nsMemory::Free(inBuf); + return NS_ERROR_UNEXPECTED; // improper base64 encoding + } + } + + rv = module->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen); + if (NS_SUCCEEDED(rv)) { + // base64 encode data in output buffer and prepend "NTLM " + int credsLen = 5 + ((outBufLen + 2)/3)*4; + *creds = (char *) nsMemory::Alloc(credsLen + 1); + if (!*creds) + rv = NS_ERROR_OUT_OF_MEMORY; + else { + memcpy(*creds, "NTLM ", 5); + PL_Base64Encode((char *) outBuf, outBufLen, *creds + 5); + (*creds)[credsLen] = '\0'; // null terminate + } + // OK, we are done with |outBuf| + nsMemory::Free(outBuf); + } + + if (inBuf) + nsMemory::Free(inBuf); + + return rv; +} + +NS_IMETHODIMP +nsHttpNTLMAuth::GetAuthFlags(uint32_t *flags) +{ + *flags = CONNECTION_BASED | IDENTITY_INCLUDES_DOMAIN | IDENTITY_ENCRYPTED; + return NS_OK; +} + +} // namespace mozilla::net +} // namespace mozilla