Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
1 /* vim:set ts=4 sw=4 sts=4 et ci: */
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 // HttpLog.h should generally be included first
7 #include "HttpLog.h"
9 #include "nsHttpNTLMAuth.h"
10 #include "nsIAuthModule.h"
11 #include "nsCOMPtr.h"
12 #include "plbase64.h"
13 #include "prnetdb.h"
15 //-----------------------------------------------------------------------------
17 #include "nsIPrefBranch.h"
18 #include "nsIPrefService.h"
19 #include "nsIHttpAuthenticableChannel.h"
20 #include "nsIURI.h"
21 #ifdef XP_WIN
22 #include "nsIX509Cert.h"
23 #include "nsISSLStatus.h"
24 #include "nsISSLStatusProvider.h"
25 #endif
26 #include "mozilla/Attributes.h"
27 #include "nsThreadUtils.h"
29 namespace mozilla {
30 namespace net {
32 static const char kAllowProxies[] = "network.automatic-ntlm-auth.allow-proxies";
33 static const char kAllowNonFqdn[] = "network.automatic-ntlm-auth.allow-non-fqdn";
34 static const char kTrustedURIs[] = "network.automatic-ntlm-auth.trusted-uris";
35 static const char kForceGeneric[] = "network.auth.force-generic-ntlm";
36 static const char kAllowGenericHTTP[] = "network.negotiate-auth.allow-insecure-ntlm-v1";
37 static const char kAllowGenericHTTPS[] = "network.negotiate-auth.allow-insecure-ntlm-v1-https";
39 // XXX MatchesBaseURI and TestPref are duplicated in nsHttpNegotiateAuth.cpp,
40 // but since that file lives in a separate library we cannot directly share it.
41 // bug 236865 addresses this problem.
43 static bool
44 MatchesBaseURI(const nsCSubstring &matchScheme,
45 const nsCSubstring &matchHost,
46 int32_t matchPort,
47 const char *baseStart,
48 const char *baseEnd)
49 {
50 // check if scheme://host:port matches baseURI
52 // parse the base URI
53 const char *hostStart, *schemeEnd = strstr(baseStart, "://");
54 if (schemeEnd) {
55 // the given scheme must match the parsed scheme exactly
56 if (!matchScheme.Equals(Substring(baseStart, schemeEnd)))
57 return false;
58 hostStart = schemeEnd + 3;
59 }
60 else
61 hostStart = baseStart;
63 // XXX this does not work for IPv6-literals
64 const char *hostEnd = strchr(hostStart, ':');
65 if (hostEnd && hostEnd < baseEnd) {
66 // the given port must match the parsed port exactly
67 int port = atoi(hostEnd + 1);
68 if (matchPort != (int32_t) port)
69 return false;
70 }
71 else
72 hostEnd = baseEnd;
75 // if we didn't parse out a host, then assume we got a match.
76 if (hostStart == hostEnd)
77 return true;
79 uint32_t hostLen = hostEnd - hostStart;
81 // matchHost must either equal host or be a subdomain of host
82 if (matchHost.Length() < hostLen)
83 return false;
85 const char *end = matchHost.EndReading();
86 if (PL_strncasecmp(end - hostLen, hostStart, hostLen) == 0) {
87 // if matchHost ends with host from the base URI, then make sure it is
88 // either an exact match, or prefixed with a dot. we don't want
89 // "foobar.com" to match "bar.com"
90 if (matchHost.Length() == hostLen ||
91 *(end - hostLen) == '.' ||
92 *(end - hostLen - 1) == '.')
93 return true;
94 }
96 return false;
97 }
99 static bool
100 IsNonFqdn(nsIURI *uri)
101 {
102 nsAutoCString host;
103 PRNetAddr addr;
105 if (NS_FAILED(uri->GetAsciiHost(host)))
106 return false;
108 // return true if host does not contain a dot and is not an ip address
109 return !host.IsEmpty() && host.FindChar('.') == kNotFound &&
110 PR_StringToNetAddr(host.BeginReading(), &addr) != PR_SUCCESS;
111 }
113 static bool
114 TestPref(nsIURI *uri, const char *pref)
115 {
116 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
117 if (!prefs)
118 return false;
120 nsAutoCString scheme, host;
121 int32_t port;
123 if (NS_FAILED(uri->GetScheme(scheme)))
124 return false;
125 if (NS_FAILED(uri->GetAsciiHost(host)))
126 return false;
127 if (NS_FAILED(uri->GetPort(&port)))
128 return false;
130 char *hostList;
131 if (NS_FAILED(prefs->GetCharPref(pref, &hostList)) || !hostList)
132 return false;
134 // pseudo-BNF
135 // ----------
136 //
137 // url-list base-url ( base-url "," LWS )*
138 // base-url ( scheme-part | host-part | scheme-part host-part )
139 // scheme-part scheme "://"
140 // host-part host [":" port]
141 //
142 // for example:
143 // "https://, http://office.foo.com"
144 //
146 char *start = hostList, *end;
147 for (;;) {
148 // skip past any whitespace
149 while (*start == ' ' || *start == '\t')
150 ++start;
151 end = strchr(start, ',');
152 if (!end)
153 end = start + strlen(start);
154 if (start == end)
155 break;
156 if (MatchesBaseURI(scheme, host, port, start, end))
157 return true;
158 if (*end == '\0')
159 break;
160 start = end + 1;
161 }
163 nsMemory::Free(hostList);
164 return false;
165 }
167 // Check to see if we should use our generic (internal) NTLM auth module.
168 static bool
169 ForceGenericNTLM()
170 {
171 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
172 if (!prefs)
173 return false;
174 bool flag = false;
176 if (NS_FAILED(prefs->GetBoolPref(kForceGeneric, &flag)))
177 flag = false;
179 LOG(("Force use of generic ntlm auth module: %d\n", flag));
180 return flag;
181 }
183 // Check to see if we should use our generic (internal) NTLM auth module.
184 static bool
185 AllowGenericNTLM()
186 {
187 MOZ_ASSERT(NS_IsMainThread());
189 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
190 if (!prefs)
191 return false;
193 bool flag = false;
194 if (NS_FAILED(prefs->GetBoolPref(kAllowGenericHTTP, &flag)))
195 flag = false;
197 LOG(("Allow use of generic ntlm auth module: %d\n", flag));
198 return flag;
199 }
201 // Check to see if we should use our generic (internal) NTLM auth module.
202 static bool
203 AllowGenericNTLMforHTTPS(nsIHttpAuthenticableChannel *channel)
204 {
205 bool isSSL = false;
206 channel->GetIsSSL(&isSSL);
207 if (!isSSL)
208 return false;
210 MOZ_ASSERT(NS_IsMainThread());
212 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
213 if (!prefs)
214 return false;
216 bool flag = false;
217 if (NS_FAILED(prefs->GetBoolPref(kAllowGenericHTTPS, &flag)))
218 flag = false;
220 LOG(("Allow use of generic ntlm auth module for only https: %d\n", flag));
221 return flag;
222 }
224 // Check to see if we should use default credentials for this host or proxy.
225 static bool
226 CanUseDefaultCredentials(nsIHttpAuthenticableChannel *channel,
227 bool isProxyAuth)
228 {
229 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
230 if (!prefs)
231 return false;
233 if (isProxyAuth) {
234 bool val;
235 if (NS_FAILED(prefs->GetBoolPref(kAllowProxies, &val)))
236 val = false;
237 LOG(("Default credentials allowed for proxy: %d\n", val));
238 return val;
239 }
241 nsCOMPtr<nsIURI> uri;
242 channel->GetURI(getter_AddRefs(uri));
244 bool allowNonFqdn;
245 if (NS_FAILED(prefs->GetBoolPref(kAllowNonFqdn, &allowNonFqdn)))
246 allowNonFqdn = false;
247 if (allowNonFqdn && uri && IsNonFqdn(uri)) {
248 LOG(("Host is non-fqdn, default credentials are allowed\n"));
249 return true;
250 }
252 bool isTrustedHost = (uri && TestPref(uri, kTrustedURIs));
253 LOG(("Default credentials allowed for host: %d\n", isTrustedHost));
254 return isTrustedHost;
255 }
257 // Dummy class for session state object. This class doesn't hold any data.
258 // Instead we use its existence as a flag. See ChallengeReceived.
259 class nsNTLMSessionState MOZ_FINAL : public nsISupports
260 {
261 public:
262 NS_DECL_ISUPPORTS
263 };
264 NS_IMPL_ISUPPORTS0(nsNTLMSessionState)
266 //-----------------------------------------------------------------------------
268 NS_IMPL_ISUPPORTS(nsHttpNTLMAuth, nsIHttpAuthenticator)
270 NS_IMETHODIMP
271 nsHttpNTLMAuth::ChallengeReceived(nsIHttpAuthenticableChannel *channel,
272 const char *challenge,
273 bool isProxyAuth,
274 nsISupports **sessionState,
275 nsISupports **continuationState,
276 bool *identityInvalid)
277 {
278 LOG(("nsHttpNTLMAuth::ChallengeReceived [ss=%p cs=%p]\n",
279 *sessionState, *continuationState));
281 // Use the native NTLM if available
282 mUseNative = true;
284 // NOTE: we don't define any session state, but we do use the pointer.
286 *identityInvalid = false;
288 /* Always fail Negotiate auth for Tor Browser. We don't need it. */
289 return NS_ERROR_ABORT;
291 // Start a new auth sequence if the challenge is exactly "NTLM".
292 // If native NTLM auth apis are available and enabled through prefs,
293 // try to use them.
294 if (PL_strcasecmp(challenge, "NTLM") == 0) {
295 nsCOMPtr<nsISupports> module;
297 // Check to see if we should default to our generic NTLM auth module
298 // through UseGenericNTLM. (We use native auth by default if the
299 // system provides it.) If *sessionState is non-null, we failed to
300 // instantiate a native NTLM module the last time, so skip trying again.
301 bool forceGeneric = ForceGenericNTLM();
302 if (!forceGeneric && !*sessionState) {
303 // Check for approved default credentials hosts and proxies. If
304 // *continuationState is non-null, the last authentication attempt
305 // failed so skip default credential use.
306 if (!*continuationState && CanUseDefaultCredentials(channel, isProxyAuth)) {
307 // Try logging in with the user's default credentials. If
308 // successful, |identityInvalid| is false, which will trigger
309 // a default credentials attempt once we return.
310 module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sys-ntlm");
311 }
312 #ifdef XP_WIN
313 else {
314 // Try to use native NTLM and prompt the user for their domain,
315 // username, and password. (only supported by windows nsAuthSSPI module.)
316 // Note, for servers that use LMv1 a weak hash of the user's password
317 // will be sent. We rely on windows internal apis to decide whether
318 // we should support this older, less secure version of the protocol.
319 module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sys-ntlm");
320 *identityInvalid = true;
321 }
322 #endif // XP_WIN
323 #ifdef PR_LOGGING
324 if (!module)
325 LOG(("Native sys-ntlm auth module not found.\n"));
326 #endif
327 }
329 #ifdef XP_WIN
330 // On windows, never fall back unless the user has specifically requested so.
331 if (!forceGeneric && !module)
332 return NS_ERROR_UNEXPECTED;
333 #endif
335 // If no native support was available. Fall back on our internal NTLM implementation.
336 if (!module) {
337 if (!*sessionState) {
338 // Remember the fact that we cannot use the "sys-ntlm" module,
339 // so we don't ever bother trying again for this auth domain.
340 *sessionState = new nsNTLMSessionState();
341 if (!*sessionState)
342 return NS_ERROR_OUT_OF_MEMORY;
343 NS_ADDREF(*sessionState);
344 }
346 // Use our internal NTLM implementation. Note, this is less secure,
347 // see bug 520607 for details.
349 // For now with default preference settings (i.e. allow-insecure-ntlm-v1-https = true
350 // and allow-insecure-ntlm-v1 = false) we don't allow authentication to any proxy,
351 // either http or https. This will be fixed in a followup bug.
352 if (AllowGenericNTLM() || (!isProxyAuth && AllowGenericNTLMforHTTPS(channel))) {
353 LOG(("Trying to fall back on internal ntlm auth.\n"));
354 module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "ntlm");
355 }
357 mUseNative = false;
359 // Prompt user for domain, username, and password.
360 *identityInvalid = true;
361 }
363 // If this fails, then it means that we cannot do NTLM auth.
364 if (!module) {
365 LOG(("No ntlm auth modules available.\n"));
366 return NS_ERROR_UNEXPECTED;
367 }
369 // A non-null continuation state implies that we failed to authenticate.
370 // Blow away the old authentication state, and use the new one.
371 module.swap(*continuationState);
372 }
373 return NS_OK;
374 }
376 NS_IMETHODIMP
377 nsHttpNTLMAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel,
378 const char *challenge,
379 bool isProxyAuth,
380 const char16_t *domain,
381 const char16_t *user,
382 const char16_t *pass,
383 nsISupports **sessionState,
384 nsISupports **continuationState,
385 uint32_t *aFlags,
386 char **creds)
388 {
389 LOG(("nsHttpNTLMAuth::GenerateCredentials\n"));
391 *creds = nullptr;
392 *aFlags = 0;
394 // if user or password is empty, ChallengeReceived returned
395 // identityInvalid = false, that means we are using default user
396 // credentials; see nsAuthSSPI::Init method for explanation of this
397 // condition
398 if (!user || !pass)
399 *aFlags = USING_INTERNAL_IDENTITY;
401 nsresult rv;
402 nsCOMPtr<nsIAuthModule> module = do_QueryInterface(*continuationState, &rv);
403 NS_ENSURE_SUCCESS(rv, rv);
405 void *inBuf, *outBuf;
406 uint32_t inBufLen, outBufLen;
408 // initial challenge
409 if (PL_strcasecmp(challenge, "NTLM") == 0) {
410 // NTLM service name format is 'HTTP@host' for both http and https
411 nsCOMPtr<nsIURI> uri;
412 rv = authChannel->GetURI(getter_AddRefs(uri));
413 if (NS_FAILED(rv))
414 return rv;
415 nsAutoCString serviceName, host;
416 rv = uri->GetAsciiHost(host);
417 if (NS_FAILED(rv))
418 return rv;
419 serviceName.AppendLiteral("HTTP@");
420 serviceName.Append(host);
421 // initialize auth module
422 uint32_t reqFlags = nsIAuthModule::REQ_DEFAULT;
423 if (isProxyAuth)
424 reqFlags |= nsIAuthModule::REQ_PROXY_AUTH;
426 rv = module->Init(serviceName.get(), reqFlags, domain, user, pass);
427 if (NS_FAILED(rv))
428 return rv;
430 // This update enables updated Windows machines (Win7 or patched previous
431 // versions) and Linux machines running Samba (updated for Channel
432 // Binding), to perform Channel Binding when authenticating using NTLMv2
433 // and an outer secure channel.
434 //
435 // Currently only implemented for Windows, linux support will be landing in
436 // a separate patch, update this #ifdef accordingly then.
437 #if defined (XP_WIN) /* || defined (LINUX) */
438 // We should retrieve the server certificate and compute the CBT,
439 // but only when we are using the native NTLM implementation and
440 // not the internal one.
441 // It is a valid case not having the security info object. This
442 // occures when we connect an https site through an ntlm proxy.
443 // After the ssl tunnel has been created, we get here the second
444 // time and now generate the CBT from now valid security info.
445 nsCOMPtr<nsIChannel> channel = do_QueryInterface(authChannel, &rv);
446 if (NS_FAILED(rv))
447 return rv;
449 nsCOMPtr<nsISupports> security;
450 rv = channel->GetSecurityInfo(getter_AddRefs(security));
451 if (NS_FAILED(rv))
452 return rv;
454 nsCOMPtr<nsISSLStatusProvider> statusProvider =
455 do_QueryInterface(security);
457 if (mUseNative && statusProvider) {
458 nsCOMPtr<nsISSLStatus> status;
459 rv = statusProvider->GetSSLStatus(getter_AddRefs(status));
460 if (NS_FAILED(rv))
461 return rv;
463 nsCOMPtr<nsIX509Cert> cert;
464 rv = status->GetServerCert(getter_AddRefs(cert));
465 if (NS_FAILED(rv))
466 return rv;
468 uint32_t length;
469 uint8_t* certArray;
470 cert->GetRawDER(&length, &certArray);
472 // If there is a server certificate, we pass it along the
473 // first time we call GetNextToken().
474 inBufLen = length;
475 inBuf = certArray;
476 } else {
477 // If there is no server certificate, we don't pass anything.
478 inBufLen = 0;
479 inBuf = nullptr;
480 }
481 #else // Extended protection update is just for Linux and Windows machines.
482 inBufLen = 0;
483 inBuf = nullptr;
484 #endif
485 }
486 else {
487 // decode challenge; skip past "NTLM " to the start of the base64
488 // encoded data.
489 int len = strlen(challenge);
490 if (len < 6)
491 return NS_ERROR_UNEXPECTED; // bogus challenge
492 challenge += 5;
493 len -= 5;
495 // strip off any padding (see bug 230351)
496 while (challenge[len - 1] == '=')
497 len--;
499 // decode into the input secbuffer
500 inBufLen = (len * 3)/4; // sufficient size (see plbase64.h)
501 inBuf = nsMemory::Alloc(inBufLen);
502 if (!inBuf)
503 return NS_ERROR_OUT_OF_MEMORY;
505 if (PL_Base64Decode(challenge, len, (char *) inBuf) == nullptr) {
506 nsMemory::Free(inBuf);
507 return NS_ERROR_UNEXPECTED; // improper base64 encoding
508 }
509 }
511 rv = module->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen);
512 if (NS_SUCCEEDED(rv)) {
513 // base64 encode data in output buffer and prepend "NTLM "
514 int credsLen = 5 + ((outBufLen + 2)/3)*4;
515 *creds = (char *) nsMemory::Alloc(credsLen + 1);
516 if (!*creds)
517 rv = NS_ERROR_OUT_OF_MEMORY;
518 else {
519 memcpy(*creds, "NTLM ", 5);
520 PL_Base64Encode((char *) outBuf, outBufLen, *creds + 5);
521 (*creds)[credsLen] = '\0'; // null terminate
522 }
523 // OK, we are done with |outBuf|
524 nsMemory::Free(outBuf);
525 }
527 if (inBuf)
528 nsMemory::Free(inBuf);
530 return rv;
531 }
533 NS_IMETHODIMP
534 nsHttpNTLMAuth::GetAuthFlags(uint32_t *flags)
535 {
536 *flags = CONNECTION_BASED | IDENTITY_INCLUDES_DOMAIN | IDENTITY_ENCRYPTED;
537 return NS_OK;
538 }
540 } // namespace mozilla::net
541 } // namespace mozilla