|
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/. */ |
|
5 |
|
6 // HttpLog.h should generally be included first |
|
7 #include "HttpLog.h" |
|
8 |
|
9 #include "nsHttpNTLMAuth.h" |
|
10 #include "nsIAuthModule.h" |
|
11 #include "nsCOMPtr.h" |
|
12 #include "plbase64.h" |
|
13 #include "prnetdb.h" |
|
14 |
|
15 //----------------------------------------------------------------------------- |
|
16 |
|
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" |
|
28 |
|
29 namespace mozilla { |
|
30 namespace net { |
|
31 |
|
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"; |
|
38 |
|
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. |
|
42 |
|
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 |
|
51 |
|
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; |
|
62 |
|
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; |
|
73 |
|
74 |
|
75 // if we didn't parse out a host, then assume we got a match. |
|
76 if (hostStart == hostEnd) |
|
77 return true; |
|
78 |
|
79 uint32_t hostLen = hostEnd - hostStart; |
|
80 |
|
81 // matchHost must either equal host or be a subdomain of host |
|
82 if (matchHost.Length() < hostLen) |
|
83 return false; |
|
84 |
|
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 } |
|
95 |
|
96 return false; |
|
97 } |
|
98 |
|
99 static bool |
|
100 IsNonFqdn(nsIURI *uri) |
|
101 { |
|
102 nsAutoCString host; |
|
103 PRNetAddr addr; |
|
104 |
|
105 if (NS_FAILED(uri->GetAsciiHost(host))) |
|
106 return false; |
|
107 |
|
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 } |
|
112 |
|
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; |
|
119 |
|
120 nsAutoCString scheme, host; |
|
121 int32_t port; |
|
122 |
|
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; |
|
129 |
|
130 char *hostList; |
|
131 if (NS_FAILED(prefs->GetCharPref(pref, &hostList)) || !hostList) |
|
132 return false; |
|
133 |
|
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 // |
|
145 |
|
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 } |
|
162 |
|
163 nsMemory::Free(hostList); |
|
164 return false; |
|
165 } |
|
166 |
|
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; |
|
175 |
|
176 if (NS_FAILED(prefs->GetBoolPref(kForceGeneric, &flag))) |
|
177 flag = false; |
|
178 |
|
179 LOG(("Force use of generic ntlm auth module: %d\n", flag)); |
|
180 return flag; |
|
181 } |
|
182 |
|
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()); |
|
188 |
|
189 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); |
|
190 if (!prefs) |
|
191 return false; |
|
192 |
|
193 bool flag = false; |
|
194 if (NS_FAILED(prefs->GetBoolPref(kAllowGenericHTTP, &flag))) |
|
195 flag = false; |
|
196 |
|
197 LOG(("Allow use of generic ntlm auth module: %d\n", flag)); |
|
198 return flag; |
|
199 } |
|
200 |
|
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; |
|
209 |
|
210 MOZ_ASSERT(NS_IsMainThread()); |
|
211 |
|
212 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); |
|
213 if (!prefs) |
|
214 return false; |
|
215 |
|
216 bool flag = false; |
|
217 if (NS_FAILED(prefs->GetBoolPref(kAllowGenericHTTPS, &flag))) |
|
218 flag = false; |
|
219 |
|
220 LOG(("Allow use of generic ntlm auth module for only https: %d\n", flag)); |
|
221 return flag; |
|
222 } |
|
223 |
|
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; |
|
232 |
|
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 } |
|
240 |
|
241 nsCOMPtr<nsIURI> uri; |
|
242 channel->GetURI(getter_AddRefs(uri)); |
|
243 |
|
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 } |
|
251 |
|
252 bool isTrustedHost = (uri && TestPref(uri, kTrustedURIs)); |
|
253 LOG(("Default credentials allowed for host: %d\n", isTrustedHost)); |
|
254 return isTrustedHost; |
|
255 } |
|
256 |
|
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) |
|
265 |
|
266 //----------------------------------------------------------------------------- |
|
267 |
|
268 NS_IMPL_ISUPPORTS(nsHttpNTLMAuth, nsIHttpAuthenticator) |
|
269 |
|
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)); |
|
280 |
|
281 // Use the native NTLM if available |
|
282 mUseNative = true; |
|
283 |
|
284 // NOTE: we don't define any session state, but we do use the pointer. |
|
285 |
|
286 *identityInvalid = false; |
|
287 |
|
288 /* Always fail Negotiate auth for Tor Browser. We don't need it. */ |
|
289 return NS_ERROR_ABORT; |
|
290 |
|
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; |
|
296 |
|
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 } |
|
328 |
|
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 |
|
334 |
|
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 } |
|
345 |
|
346 // Use our internal NTLM implementation. Note, this is less secure, |
|
347 // see bug 520607 for details. |
|
348 |
|
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 } |
|
356 |
|
357 mUseNative = false; |
|
358 |
|
359 // Prompt user for domain, username, and password. |
|
360 *identityInvalid = true; |
|
361 } |
|
362 |
|
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 } |
|
368 |
|
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 } |
|
375 |
|
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) |
|
387 |
|
388 { |
|
389 LOG(("nsHttpNTLMAuth::GenerateCredentials\n")); |
|
390 |
|
391 *creds = nullptr; |
|
392 *aFlags = 0; |
|
393 |
|
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; |
|
400 |
|
401 nsresult rv; |
|
402 nsCOMPtr<nsIAuthModule> module = do_QueryInterface(*continuationState, &rv); |
|
403 NS_ENSURE_SUCCESS(rv, rv); |
|
404 |
|
405 void *inBuf, *outBuf; |
|
406 uint32_t inBufLen, outBufLen; |
|
407 |
|
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; |
|
425 |
|
426 rv = module->Init(serviceName.get(), reqFlags, domain, user, pass); |
|
427 if (NS_FAILED(rv)) |
|
428 return rv; |
|
429 |
|
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; |
|
448 |
|
449 nsCOMPtr<nsISupports> security; |
|
450 rv = channel->GetSecurityInfo(getter_AddRefs(security)); |
|
451 if (NS_FAILED(rv)) |
|
452 return rv; |
|
453 |
|
454 nsCOMPtr<nsISSLStatusProvider> statusProvider = |
|
455 do_QueryInterface(security); |
|
456 |
|
457 if (mUseNative && statusProvider) { |
|
458 nsCOMPtr<nsISSLStatus> status; |
|
459 rv = statusProvider->GetSSLStatus(getter_AddRefs(status)); |
|
460 if (NS_FAILED(rv)) |
|
461 return rv; |
|
462 |
|
463 nsCOMPtr<nsIX509Cert> cert; |
|
464 rv = status->GetServerCert(getter_AddRefs(cert)); |
|
465 if (NS_FAILED(rv)) |
|
466 return rv; |
|
467 |
|
468 uint32_t length; |
|
469 uint8_t* certArray; |
|
470 cert->GetRawDER(&length, &certArray); |
|
471 |
|
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; |
|
494 |
|
495 // strip off any padding (see bug 230351) |
|
496 while (challenge[len - 1] == '=') |
|
497 len--; |
|
498 |
|
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; |
|
504 |
|
505 if (PL_Base64Decode(challenge, len, (char *) inBuf) == nullptr) { |
|
506 nsMemory::Free(inBuf); |
|
507 return NS_ERROR_UNEXPECTED; // improper base64 encoding |
|
508 } |
|
509 } |
|
510 |
|
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 } |
|
526 |
|
527 if (inBuf) |
|
528 nsMemory::Free(inBuf); |
|
529 |
|
530 return rv; |
|
531 } |
|
532 |
|
533 NS_IMETHODIMP |
|
534 nsHttpNTLMAuth::GetAuthFlags(uint32_t *flags) |
|
535 { |
|
536 *flags = CONNECTION_BASED | IDENTITY_INCLUDES_DOMAIN | IDENTITY_ENCRYPTED; |
|
537 return NS_OK; |
|
538 } |
|
539 |
|
540 } // namespace mozilla::net |
|
541 } // namespace mozilla |