1 : /* vim:set ts=4 sw=4 sts=4 et ci: */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 2003
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Darin Fisher <darin@meer.net>
24 : * Jim Mathies <jmathies@mozilla.com>
25 : * Guillermo Robla Vicario <groblavicario@gmail.com>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either the GNU General Public License Version 2 or later (the "GPL"), or
29 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : #include <stdlib.h>
42 : #include "nsHttp.h"
43 : #include "nsHttpNTLMAuth.h"
44 : #include "nsIComponentManager.h"
45 : #include "nsIAuthModule.h"
46 : #include "nsCOMPtr.h"
47 : #include "plbase64.h"
48 : #include "prnetdb.h"
49 :
50 : //-----------------------------------------------------------------------------
51 :
52 : #include "nsIPrefBranch.h"
53 : #include "nsIPrefService.h"
54 : #include "nsIServiceManager.h"
55 : #include "nsIHttpAuthenticableChannel.h"
56 : #include "nsIURI.h"
57 : #include "nsIX509Cert.h"
58 : #include "nsISSLStatus.h"
59 : #include "nsISSLStatusProvider.h"
60 :
61 : static const char kAllowProxies[] = "network.automatic-ntlm-auth.allow-proxies";
62 : static const char kAllowNonFqdn[] = "network.automatic-ntlm-auth.allow-non-fqdn";
63 : static const char kTrustedURIs[] = "network.automatic-ntlm-auth.trusted-uris";
64 : static const char kForceGeneric[] = "network.auth.force-generic-ntlm";
65 :
66 : // XXX MatchesBaseURI and TestPref are duplicated in nsHttpNegotiateAuth.cpp,
67 : // but since that file lives in a separate library we cannot directly share it.
68 : // bug 236865 addresses this problem.
69 :
70 : static bool
71 0 : MatchesBaseURI(const nsCSubstring &matchScheme,
72 : const nsCSubstring &matchHost,
73 : PRInt32 matchPort,
74 : const char *baseStart,
75 : const char *baseEnd)
76 : {
77 : // check if scheme://host:port matches baseURI
78 :
79 : // parse the base URI
80 0 : const char *hostStart, *schemeEnd = strstr(baseStart, "://");
81 0 : if (schemeEnd) {
82 : // the given scheme must match the parsed scheme exactly
83 0 : if (!matchScheme.Equals(Substring(baseStart, schemeEnd)))
84 0 : return false;
85 0 : hostStart = schemeEnd + 3;
86 : }
87 : else
88 0 : hostStart = baseStart;
89 :
90 : // XXX this does not work for IPv6-literals
91 0 : const char *hostEnd = strchr(hostStart, ':');
92 0 : if (hostEnd && hostEnd < baseEnd) {
93 : // the given port must match the parsed port exactly
94 0 : int port = atoi(hostEnd + 1);
95 0 : if (matchPort != (PRInt32) port)
96 0 : return false;
97 : }
98 : else
99 0 : hostEnd = baseEnd;
100 :
101 :
102 : // if we didn't parse out a host, then assume we got a match.
103 0 : if (hostStart == hostEnd)
104 0 : return true;
105 :
106 0 : PRUint32 hostLen = hostEnd - hostStart;
107 :
108 : // matchHost must either equal host or be a subdomain of host
109 0 : if (matchHost.Length() < hostLen)
110 0 : return false;
111 :
112 0 : const char *end = matchHost.EndReading();
113 0 : if (PL_strncasecmp(end - hostLen, hostStart, hostLen) == 0) {
114 : // if matchHost ends with host from the base URI, then make sure it is
115 : // either an exact match, or prefixed with a dot. we don't want
116 : // "foobar.com" to match "bar.com"
117 0 : if (matchHost.Length() == hostLen ||
118 0 : *(end - hostLen) == '.' ||
119 0 : *(end - hostLen - 1) == '.')
120 0 : return true;
121 : }
122 :
123 0 : return false;
124 : }
125 :
126 : static bool
127 0 : IsNonFqdn(nsIURI *uri)
128 : {
129 0 : nsCAutoString host;
130 : PRNetAddr addr;
131 :
132 0 : if (NS_FAILED(uri->GetAsciiHost(host)))
133 0 : return false;
134 :
135 : // return true if host does not contain a dot and is not an ip address
136 0 : return !host.IsEmpty() && host.FindChar('.') == kNotFound &&
137 0 : PR_StringToNetAddr(host.BeginReading(), &addr) != PR_SUCCESS;
138 : }
139 :
140 : static bool
141 1 : TestPref(nsIURI *uri, const char *pref)
142 : {
143 2 : nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
144 1 : if (!prefs)
145 0 : return false;
146 :
147 2 : nsCAutoString scheme, host;
148 : PRInt32 port;
149 :
150 1 : if (NS_FAILED(uri->GetScheme(scheme)))
151 0 : return false;
152 1 : if (NS_FAILED(uri->GetAsciiHost(host)))
153 0 : return false;
154 1 : if (NS_FAILED(uri->GetPort(&port)))
155 0 : return false;
156 :
157 : char *hostList;
158 1 : if (NS_FAILED(prefs->GetCharPref(pref, &hostList)) || !hostList)
159 0 : return false;
160 :
161 : // pseudo-BNF
162 : // ----------
163 : //
164 : // url-list base-url ( base-url "," LWS )*
165 : // base-url ( scheme-part | host-part | scheme-part host-part )
166 : // scheme-part scheme "://"
167 : // host-part host [":" port]
168 : //
169 : // for example:
170 : // "https://, http://office.foo.com"
171 : //
172 :
173 1 : char *start = hostList, *end;
174 0 : for (;;) {
175 : // skip past any whitespace
176 2 : while (*start == ' ' || *start == '\t')
177 0 : ++start;
178 1 : end = strchr(start, ',');
179 1 : if (!end)
180 1 : end = start + strlen(start);
181 1 : if (start == end)
182 1 : break;
183 0 : if (MatchesBaseURI(scheme, host, port, start, end))
184 0 : return true;
185 0 : if (*end == '\0')
186 0 : break;
187 0 : start = end + 1;
188 : }
189 :
190 1 : nsMemory::Free(hostList);
191 1 : return false;
192 : }
193 :
194 : // Check to see if we should use our generic (internal) NTLM auth module.
195 : static bool
196 1 : ForceGenericNTLM()
197 : {
198 2 : nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
199 1 : if (!prefs)
200 0 : return false;
201 1 : bool flag = false;
202 :
203 1 : if (NS_FAILED(prefs->GetBoolPref(kForceGeneric, &flag)))
204 0 : flag = false;
205 :
206 1 : LOG(("Force use of generic ntlm auth module: %d\n", flag));
207 1 : return flag;
208 : }
209 :
210 : // Check to see if we should use default credentials for this host or proxy.
211 : static bool
212 1 : CanUseDefaultCredentials(nsIHttpAuthenticableChannel *channel,
213 : bool isProxyAuth)
214 : {
215 2 : nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
216 1 : if (!prefs)
217 0 : return false;
218 :
219 1 : if (isProxyAuth) {
220 : bool val;
221 0 : if (NS_FAILED(prefs->GetBoolPref(kAllowProxies, &val)))
222 0 : val = false;
223 0 : LOG(("Default credentials allowed for proxy: %d\n", val));
224 0 : return val;
225 : }
226 :
227 2 : nsCOMPtr<nsIURI> uri;
228 1 : channel->GetURI(getter_AddRefs(uri));
229 :
230 : bool allowNonFqdn;
231 1 : if (NS_FAILED(prefs->GetBoolPref(kAllowNonFqdn, &allowNonFqdn)))
232 0 : allowNonFqdn = false;
233 1 : if (allowNonFqdn && uri && IsNonFqdn(uri)) {
234 0 : LOG(("Host is non-fqdn, default credentials are allowed\n"));
235 0 : return true;
236 : }
237 :
238 1 : bool isTrustedHost = (uri && TestPref(uri, kTrustedURIs));
239 1 : LOG(("Default credentials allowed for host: %d\n", isTrustedHost));
240 1 : return isTrustedHost;
241 : }
242 :
243 : // Dummy class for session state object. This class doesn't hold any data.
244 : // Instead we use its existence as a flag. See ChallengeReceived.
245 : class nsNTLMSessionState : public nsISupports
246 1 : {
247 : public:
248 : NS_DECL_ISUPPORTS
249 : };
250 3 : NS_IMPL_ISUPPORTS0(nsNTLMSessionState)
251 :
252 : //-----------------------------------------------------------------------------
253 :
254 12 : NS_IMPL_ISUPPORTS1(nsHttpNTLMAuth, nsIHttpAuthenticator)
255 :
256 : NS_IMETHODIMP
257 1 : nsHttpNTLMAuth::ChallengeReceived(nsIHttpAuthenticableChannel *channel,
258 : const char *challenge,
259 : bool isProxyAuth,
260 : nsISupports **sessionState,
261 : nsISupports **continuationState,
262 : bool *identityInvalid)
263 : {
264 1 : LOG(("nsHttpNTLMAuth::ChallengeReceived [ss=%p cs=%p]\n",
265 : *sessionState, *continuationState));
266 :
267 : // Use the native NTLM if available
268 1 : mUseNative = true;
269 :
270 : // NOTE: we don't define any session state, but we do use the pointer.
271 :
272 1 : *identityInvalid = false;
273 :
274 : // Start a new auth sequence if the challenge is exactly "NTLM".
275 : // If native NTLM auth apis are available and enabled through prefs,
276 : // try to use them.
277 1 : if (PL_strcasecmp(challenge, "NTLM") == 0) {
278 2 : nsCOMPtr<nsISupports> module;
279 :
280 : // Check to see if we should default to our generic NTLM auth module
281 : // through UseGenericNTLM. (We use native auth by default if the
282 : // system provides it.) If *sessionState is non-null, we failed to
283 : // instantiate a native NTLM module the last time, so skip trying again.
284 1 : bool forceGeneric = ForceGenericNTLM();
285 1 : if (!forceGeneric && !*sessionState) {
286 : // Check for approved default credentials hosts and proxies. If
287 : // *continuationState is non-null, the last authentication attempt
288 : // failed so skip default credential use.
289 1 : if (!*continuationState && CanUseDefaultCredentials(channel, isProxyAuth)) {
290 : // Try logging in with the user's default credentials. If
291 : // successful, |identityInvalid| is false, which will trigger
292 : // a default credentials attempt once we return.
293 0 : module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sys-ntlm");
294 : }
295 : #ifdef XP_WIN
296 : else {
297 : // Try to use native NTLM and prompt the user for their domain,
298 : // username, and password. (only supported by windows nsAuthSSPI module.)
299 : // Note, for servers that use LMv1 a weak hash of the user's password
300 : // will be sent. We rely on windows internal apis to decide whether
301 : // we should support this older, less secure version of the protocol.
302 : module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sys-ntlm");
303 : *identityInvalid = true;
304 : }
305 : #endif // XP_WIN
306 : #ifdef PR_LOGGING
307 1 : if (!module)
308 1 : LOG(("Native sys-ntlm auth module not found.\n"));
309 : #endif
310 : }
311 :
312 : #ifdef XP_WIN
313 : // On windows, never fall back unless the user has specifically requested so.
314 : if (!forceGeneric && !module)
315 : return NS_ERROR_UNEXPECTED;
316 : #endif
317 :
318 : // If no native support was available. Fall back on our internal NTLM implementation.
319 1 : if (!module) {
320 1 : if (!*sessionState) {
321 : // Remember the fact that we cannot use the "sys-ntlm" module,
322 : // so we don't ever bother trying again for this auth domain.
323 1 : *sessionState = new nsNTLMSessionState();
324 1 : if (!*sessionState)
325 0 : return NS_ERROR_OUT_OF_MEMORY;
326 1 : NS_ADDREF(*sessionState);
327 : }
328 :
329 : // Use our internal NTLM implementation. Note, this is less secure,
330 : // see bug 520607 for details.
331 1 : LOG(("Trying to fall back on internal ntlm auth.\n"));
332 1 : module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "ntlm");
333 :
334 1 : mUseNative = false;
335 :
336 : // Prompt user for domain, username, and password.
337 1 : *identityInvalid = true;
338 : }
339 :
340 : // If this fails, then it means that we cannot do NTLM auth.
341 1 : if (!module) {
342 0 : LOG(("No ntlm auth modules available.\n"));
343 0 : return NS_ERROR_UNEXPECTED;
344 : }
345 :
346 : // A non-null continuation state implies that we failed to authenticate.
347 : // Blow away the old authentication state, and use the new one.
348 2 : module.swap(*continuationState);
349 : }
350 1 : return NS_OK;
351 : }
352 :
353 : NS_IMETHODIMP
354 0 : nsHttpNTLMAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel,
355 : const char *challenge,
356 : bool isProxyAuth,
357 : const PRUnichar *domain,
358 : const PRUnichar *user,
359 : const PRUnichar *pass,
360 : nsISupports **sessionState,
361 : nsISupports **continuationState,
362 : PRUint32 *aFlags,
363 : char **creds)
364 :
365 : {
366 0 : LOG(("nsHttpNTLMAuth::GenerateCredentials\n"));
367 :
368 0 : *creds = nsnull;
369 0 : *aFlags = 0;
370 :
371 : // if user or password is empty, ChallengeReceived returned
372 : // identityInvalid = false, that means we are using default user
373 : // credentials; see nsAuthSSPI::Init method for explanation of this
374 : // condition
375 0 : if (!user || !pass)
376 0 : *aFlags = USING_INTERNAL_IDENTITY;
377 :
378 : nsresult rv;
379 0 : nsCOMPtr<nsIAuthModule> module = do_QueryInterface(*continuationState, &rv);
380 0 : NS_ENSURE_SUCCESS(rv, rv);
381 :
382 : void *inBuf, *outBuf;
383 : PRUint32 inBufLen, outBufLen;
384 :
385 : // initial challenge
386 0 : if (PL_strcasecmp(challenge, "NTLM") == 0) {
387 : // NTLM service name format is 'HTTP@host' for both http and https
388 0 : nsCOMPtr<nsIURI> uri;
389 0 : rv = authChannel->GetURI(getter_AddRefs(uri));
390 0 : if (NS_FAILED(rv))
391 0 : return rv;
392 0 : nsCAutoString serviceName, host;
393 0 : rv = uri->GetAsciiHost(host);
394 0 : if (NS_FAILED(rv))
395 0 : return rv;
396 0 : serviceName.AppendLiteral("HTTP@");
397 0 : serviceName.Append(host);
398 : // initialize auth module
399 0 : rv = module->Init(serviceName.get(), nsIAuthModule::REQ_DEFAULT, domain, user, pass);
400 0 : if (NS_FAILED(rv))
401 0 : return rv;
402 :
403 : // This update enables updated Windows machines (Win7 or patched previous
404 : // versions) and Linux machines running Samba (updated for Channel
405 : // Binding), to perform Channel Binding when authenticating using NTLMv2
406 : // and an outer secure channel.
407 : //
408 : // Currently only implemented for Windows, linux support will be landing in
409 : // a separate patch, update this #ifdef accordingly then.
410 : #if defined (XP_WIN) /* || defined (LINUX) */
411 : // We should retrieve the server certificate and compute the CBT,
412 : // but only when we are using the native NTLM implementation and
413 : // not the internal one.
414 : // It is a valid case not having the security info object. This
415 : // occures when we connect an https site through an ntlm proxy.
416 : // After the ssl tunnel has been created, we get here the second
417 : // time and now generate the CBT from now valid security info.
418 : nsCOMPtr<nsIChannel> channel = do_QueryInterface(authChannel, &rv);
419 : if (NS_FAILED(rv))
420 : return rv;
421 :
422 : nsCOMPtr<nsISupports> security;
423 : rv = channel->GetSecurityInfo(getter_AddRefs(security));
424 : if (NS_FAILED(rv))
425 : return rv;
426 :
427 : nsCOMPtr<nsISSLStatusProvider> statusProvider =
428 : do_QueryInterface(security);
429 :
430 : if (mUseNative && statusProvider) {
431 : nsCOMPtr<nsISSLStatus> status;
432 : rv = statusProvider->GetSSLStatus(getter_AddRefs(status));
433 : if (NS_FAILED(rv))
434 : return rv;
435 :
436 : nsCOMPtr<nsIX509Cert> cert;
437 : rv = status->GetServerCert(getter_AddRefs(cert));
438 : if (NS_FAILED(rv))
439 : return rv;
440 :
441 : PRUint32 length;
442 : PRUint8* certArray;
443 : cert->GetRawDER(&length, &certArray);
444 :
445 : // If there is a server certificate, we pass it along the
446 : // first time we call GetNextToken().
447 : inBufLen = length;
448 : inBuf = certArray;
449 : } else {
450 : // If there is no server certificate, we don't pass anything.
451 : inBufLen = 0;
452 : inBuf = nsnull;
453 : }
454 : #else // Extended protection update is just for Linux and Windows machines.
455 0 : inBufLen = 0;
456 0 : inBuf = nsnull;
457 : #endif
458 : }
459 : else {
460 : // decode challenge; skip past "NTLM " to the start of the base64
461 : // encoded data.
462 0 : int len = strlen(challenge);
463 0 : if (len < 6)
464 0 : return NS_ERROR_UNEXPECTED; // bogus challenge
465 0 : challenge += 5;
466 0 : len -= 5;
467 :
468 : // strip off any padding (see bug 230351)
469 0 : while (challenge[len - 1] == '=')
470 0 : len--;
471 :
472 : // decode into the input secbuffer
473 0 : inBufLen = (len * 3)/4; // sufficient size (see plbase64.h)
474 0 : inBuf = nsMemory::Alloc(inBufLen);
475 0 : if (!inBuf)
476 0 : return NS_ERROR_OUT_OF_MEMORY;
477 :
478 0 : if (PL_Base64Decode(challenge, len, (char *) inBuf) == nsnull) {
479 0 : nsMemory::Free(inBuf);
480 0 : return NS_ERROR_UNEXPECTED; // improper base64 encoding
481 : }
482 : }
483 :
484 0 : rv = module->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen);
485 0 : if (NS_SUCCEEDED(rv)) {
486 : // base64 encode data in output buffer and prepend "NTLM "
487 0 : int credsLen = 5 + ((outBufLen + 2)/3)*4;
488 0 : *creds = (char *) nsMemory::Alloc(credsLen + 1);
489 0 : if (!*creds)
490 0 : rv = NS_ERROR_OUT_OF_MEMORY;
491 : else {
492 0 : memcpy(*creds, "NTLM ", 5);
493 0 : PL_Base64Encode((char *) outBuf, outBufLen, *creds + 5);
494 0 : (*creds)[credsLen] = '\0'; // null terminate
495 : }
496 : // OK, we are done with |outBuf|
497 0 : nsMemory::Free(outBuf);
498 : }
499 :
500 0 : if (inBuf)
501 0 : nsMemory::Free(inBuf);
502 :
503 0 : return rv;
504 : }
505 :
506 : NS_IMETHODIMP
507 1 : nsHttpNTLMAuth::GetAuthFlags(PRUint32 *flags)
508 : {
509 1 : *flags = CONNECTION_BASED | IDENTITY_INCLUDES_DOMAIN | IDENTITY_ENCRYPTED;
510 1 : return NS_OK;
511 : }
|