diff options
Diffstat (limited to 'netwerk/protocol/http/nsHttpNTLMAuth.cpp')
-rw-r--r-- | netwerk/protocol/http/nsHttpNTLMAuth.cpp | 533 |
1 files changed, 533 insertions, 0 deletions
diff --git a/netwerk/protocol/http/nsHttpNTLMAuth.cpp b/netwerk/protocol/http/nsHttpNTLMAuth.cpp new file mode 100644 index 000000000..aa5b1f8f7 --- /dev/null +++ b/netwerk/protocol/http/nsHttpNTLMAuth.cpp @@ -0,0 +1,533 @@ +/* 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 "nsServiceManagerUtils.h" +#include "plbase64.h" +#include "plstr.h" +#include "prnetdb.h" + +//----------------------------------------------------------------------------- + +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsIHttpAuthenticableChannel.h" +#include "nsIURI.h" +#ifdef XP_WIN +#include "nsIChannel.h" +#include "nsIX509Cert.h" +#include "nsISSLStatus.h" +#include "nsISSLStatusProvider.h" +#endif +#include "mozilla/Attributes.h" +#include "mozilla/Base64.h" +#include "mozilla/CheckedInt.h" +#include "nsNetUtil.h" +#include "nsIChannel.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 kSSOinPBmode[] = "network.auth.private-browsing-sso"; + +// 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.Contains('.') && + PR_StringToNetAddr(host.BeginReading(), &addr) != PR_SUCCESS; +} + +static bool +TestPref(nsIURI *uri, const char *pref) +{ + nsCOMPtr<nsIPrefBranch> 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; + } + + free(hostList); + return false; +} + +// Check to see if we should use our generic (internal) NTLM auth module. +static bool +ForceGenericNTLM() +{ + nsCOMPtr<nsIPrefBranch> 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 default credentials for this host or proxy. +static bool +CanUseDefaultCredentials(nsIHttpAuthenticableChannel *channel, + bool isProxyAuth) +{ + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!prefs) { + return false; + } + + // Proxy should go all the time, it's not considered a privacy leak + // to send default credentials to a proxy. + if (isProxyAuth) { + bool val; + if (NS_FAILED(prefs->GetBoolPref(kAllowProxies, &val))) + val = false; + LOG(("Default credentials allowed for proxy: %d\n", val)); + return val; + } + + // Prevent using default credentials for authentication when we are in the + // private browsing mode (but not in "never remember history" mode) and when + // not explicitely allowed. Otherwise, it would cause a privacy data leak. + nsCOMPtr<nsIChannel> bareChannel = do_QueryInterface(channel); + MOZ_ASSERT(bareChannel); + + if (NS_UsePrivateBrowsing(bareChannel)) { + bool ssoInPb; + if (NS_SUCCEEDED(prefs->GetBoolPref(kSSOinPBmode, &ssoInPb)) && + ssoInPb) { + return true; + } + + bool dontRememberHistory; + if (NS_SUCCEEDED(prefs->GetBoolPref("browser.privatebrowsing.autostart", + &dontRememberHistory)) && + !dontRememberHistory) { + return false; + } + } + + nsCOMPtr<nsIURI> 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 final : public nsISupports +{ + ~nsNTLMSessionState() {} +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; + + // 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<nsISupports> 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 + if (!module) + LOG(("Native sys-ntlm auth module not found.\n")); + } + +#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. + 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::GenerateCredentialsAsync(nsIHttpAuthenticableChannel *authChannel, + nsIHttpAuthenticatorCallback* aCallback, + const char *challenge, + bool isProxyAuth, + const char16_t *domain, + const char16_t *username, + const char16_t *password, + nsISupports *sessionState, + nsISupports *continuationState, + nsICancelable **aCancellable) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +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<nsIAuthModule> 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<nsIURI> 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<nsIChannel> channel = do_QueryInterface(authChannel, &rv); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsISupports> security; + rv = channel->GetSecurityInfo(getter_AddRefs(security)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsISSLStatusProvider> statusProvider = + do_QueryInterface(security); + + if (mUseNative && statusProvider) { + nsCOMPtr<nsISSLStatus> status; + rv = statusProvider->GetSSLStatus(getter_AddRefs(status)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIX509Cert> 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 + rv = Base64Decode(challenge, len, (char**)&inBuf, &inBufLen); + if (NS_FAILED(rv)) { + return rv; + } + } + + rv = module->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen); + if (NS_SUCCEEDED(rv)) { + // base64 encode data in output buffer and prepend "NTLM " + CheckedUint32 credsLen = ((CheckedUint32(outBufLen) + 2) / 3) * 4; + credsLen += 5; // "NTLM " + credsLen += 1; // null terminate + + if (!credsLen.isValid()) { + rv = NS_ERROR_FAILURE; + } else { + *creds = (char *) moz_xmalloc(credsLen.value()); + memcpy(*creds, "NTLM ", 5); + PL_Base64Encode((char *) outBuf, outBufLen, *creds + 5); + (*creds)[credsLen.value() - 1] = '\0'; // null terminate + } + + // OK, we are done with |outBuf| + free(outBuf); + } + + if (inBuf) + free(inBuf); + + return rv; +} + +NS_IMETHODIMP +nsHttpNTLMAuth::GetAuthFlags(uint32_t *flags) +{ + *flags = CONNECTION_BASED | IDENTITY_INCLUDES_DOMAIN | IDENTITY_ENCRYPTED; + return NS_OK; +} + +} // namespace net +} // namespace mozilla |