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
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/
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
15 * The Original Code is Mozilla.
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.
23 * Darin Fisher <darin@meer.net>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
41 #include "nsHttpNTLMAuth.h"
42 #include "nsIComponentManager.h"
43 #include "nsIAuthModule.h"
47 //-----------------------------------------------------------------------------
49 #include "nsIPrefBranch.h"
50 #include "nsIPrefService.h"
51 #include "nsIServiceManager.h"
52 #include "nsIHttpChannel.h"
55 static const char kAllowProxies
[] = "network.automatic-ntlm-auth.allow-proxies";
56 static const char kTrustedURIs
[] = "network.automatic-ntlm-auth.trusted-uris";
58 // XXX MatchesBaseURI and TestPref are duplicated in nsHttpNegotiateAuth.cpp,
59 // but since that file lives in a separate library we cannot directly share it.
60 // bug 236865 addresses this problem.
63 MatchesBaseURI(const nsCSubstring
&matchScheme
,
64 const nsCSubstring
&matchHost
,
66 const char *baseStart
,
69 // check if scheme://host:port matches baseURI
72 const char *hostStart
, *schemeEnd
= strstr(baseStart
, "://");
74 // the given scheme must match the parsed scheme exactly
75 if (!matchScheme
.Equals(Substring(baseStart
, schemeEnd
)))
77 hostStart
= schemeEnd
+ 3;
80 hostStart
= baseStart
;
82 // XXX this does not work for IPv6-literals
83 const char *hostEnd
= strchr(hostStart
, ':');
84 if (hostEnd
&& hostEnd
< baseEnd
) {
85 // the given port must match the parsed port exactly
86 int port
= atoi(hostEnd
+ 1);
87 if (matchPort
!= (PRInt32
) port
)
94 // if we didn't parse out a host, then assume we got a match.
95 if (hostStart
== hostEnd
)
98 PRUint32 hostLen
= hostEnd
- hostStart
;
100 // matchHost must either equal host or be a subdomain of host
101 if (matchHost
.Length() < hostLen
)
104 const char *end
= matchHost
.EndReading();
105 if (PL_strncasecmp(end
- hostLen
, hostStart
, hostLen
) == 0) {
106 // if matchHost ends with host from the base URI, then make sure it is
107 // either an exact match, or prefixed with a dot. we don't want
108 // "foobar.com" to match "bar.com"
109 if (matchHost
.Length() == hostLen
||
110 *(end
- hostLen
) == '.' ||
111 *(end
- hostLen
- 1) == '.')
119 TestPref(nsIURI
*uri
, const char *pref
)
121 nsCOMPtr
<nsIPrefBranch
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
125 nsCAutoString scheme
, host
;
128 if (NS_FAILED(uri
->GetScheme(scheme
)))
130 if (NS_FAILED(uri
->GetAsciiHost(host
)))
132 if (NS_FAILED(uri
->GetPort(&port
)))
136 if (NS_FAILED(prefs
->GetCharPref(pref
, &hostList
)) || !hostList
)
142 // url-list base-url ( base-url "," LWS )*
143 // base-url ( scheme-part | host-part | scheme-part host-part )
144 // scheme-part scheme "://"
145 // host-part host [":" port]
148 // "https://, http://office.foo.com"
151 char *start
= hostList
, *end
;
153 // skip past any whitespace
154 while (*start
== ' ' || *start
== '\t')
156 end
= strchr(start
, ',');
158 end
= start
+ strlen(start
);
161 if (MatchesBaseURI(scheme
, host
, port
, start
, end
))
168 nsMemory::Free(hostList
);
173 CanUseSysNTLM(nsIHttpChannel
*channel
, PRBool isProxyAuth
)
177 nsCOMPtr
<nsIPrefBranch
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
183 if (NS_FAILED(prefs
->GetBoolPref(kAllowProxies
, &val
)))
185 LOG(("sys-ntlm allowed for proxy: %d\n", val
));
189 nsCOMPtr
<nsIURI
> uri
;
190 channel
->GetURI(getter_AddRefs(uri
));
191 if (uri
&& TestPref(uri
, kTrustedURIs
)) {
192 LOG(("sys-ntlm allowed for host\n"));
200 // Dummy class for session state object. This class doesn't hold any data.
201 // Instead we use its existance as a flag. See ChallengeReceived.
202 class nsNTLMSessionState
: public nsISupports
207 NS_IMPL_ISUPPORTS0(nsNTLMSessionState
)
209 //-----------------------------------------------------------------------------
211 NS_IMPL_ISUPPORTS1(nsHttpNTLMAuth
, nsIHttpAuthenticator
)
214 nsHttpNTLMAuth::ChallengeReceived(nsIHttpChannel
*channel
,
215 const char *challenge
,
217 nsISupports
**sessionState
,
218 nsISupports
**continuationState
,
219 PRBool
*identityInvalid
)
221 LOG(("nsHttpNTLMAuth::ChallengeReceived [ss=%p cs=%p]\n",
222 *sessionState
, *continuationState
));
224 // NOTE: we don't define any session state
226 *identityInvalid
= PR_FALSE
;
227 // start new auth sequence if challenge is exactly "NTLM"
228 if (PL_strcasecmp(challenge
, "NTLM") == 0) {
229 nsCOMPtr
<nsISupports
> module
;
231 // our session state is non-null to indicate that we've flagged
232 // this auth domain as not accepting the system's default login.
234 PRBool trySysNTLM
= (*sessionState
== nsnull
);
237 // we may have access to a built-in SSPI library,
238 // which could be used to authenticate the user without prompting.
240 // if the continuationState is null, then we may want to try using
241 // the SSPI NTLM module. however, we need to take care to only use
242 // that module when speaking to a trusted host. because the SSPI
243 // may send a weak LMv1 hash of the user's password, we cannot just
244 // send it to any server.
246 if (trySysNTLM
&& !*continuationState
&& CanUseSysNTLM(channel
, isProxyAuth
)) {
247 module
= do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX
"sys-ntlm");
250 LOG(("failed to load sys-ntlm module\n"));
254 // it's possible that there is no ntlm-sspi auth module...
256 if (!*sessionState
) {
257 // remember the fact that we cannot use the "sys-ntlm" module,
258 // so we don't ever bother trying again for this auth domain.
259 *sessionState
= new nsNTLMSessionState();
261 return NS_ERROR_OUT_OF_MEMORY
;
262 NS_ADDREF(*sessionState
);
265 module
= do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX
"ntlm");
267 // prompt user for domain, username, and password...
268 *identityInvalid
= PR_TRUE
;
271 // if this fails, then it means that we cannot do NTLM auth.
273 return NS_ERROR_UNEXPECTED
;
275 // non-null continuation state implies that we failed to authenticate.
276 // blow away the old authentication state, and use the new one.
277 module
.swap(*continuationState
);
283 nsHttpNTLMAuth::GenerateCredentials(nsIHttpChannel
*httpChannel
,
284 const char *challenge
,
286 const PRUnichar
*domain
,
287 const PRUnichar
*user
,
288 const PRUnichar
*pass
,
289 nsISupports
**sessionState
,
290 nsISupports
**continuationState
,
294 LOG(("nsHttpNTLMAuth::GenerateCredentials\n"));
299 nsCOMPtr
<nsIAuthModule
> module
= do_QueryInterface(*continuationState
, &rv
);
300 NS_ENSURE_SUCCESS(rv
, rv
);
302 void *inBuf
, *outBuf
;
303 PRUint32 inBufLen
, outBufLen
;
306 if (PL_strcasecmp(challenge
, "NTLM") == 0) {
307 // initialize auth module
308 rv
= module
->Init(nsnull
, nsIAuthModule::REQ_DEFAULT
, domain
, user
, pass
);
316 // decode challenge; skip past "NTLM " to the start of the base64
318 int len
= strlen(challenge
);
320 return NS_ERROR_UNEXPECTED
; // bogus challenge
324 // decode into the input secbuffer
325 inBufLen
= (len
* 3)/4; // sufficient size (see plbase64.h)
326 inBuf
= nsMemory::Alloc(inBufLen
);
328 return NS_ERROR_OUT_OF_MEMORY
;
330 // strip off any padding (see bug 230351)
331 while (challenge
[len
- 1] == '=')
334 if (PL_Base64Decode(challenge
, len
, (char *) inBuf
) == nsnull
) {
335 nsMemory::Free(inBuf
);
336 return NS_ERROR_UNEXPECTED
; // improper base64 encoding
340 rv
= module
->GetNextToken(inBuf
, inBufLen
, &outBuf
, &outBufLen
);
341 if (NS_SUCCEEDED(rv
)) {
342 // base64 encode data in output buffer and prepend "NTLM "
343 int credsLen
= 5 + ((outBufLen
+ 2)/3)*4;
344 *creds
= (char *) nsMemory::Alloc(credsLen
+ 1);
346 rv
= NS_ERROR_OUT_OF_MEMORY
;
348 memcpy(*creds
, "NTLM ", 5);
349 PL_Base64Encode((char *) outBuf
, outBufLen
, *creds
+ 5);
350 (*creds
)[credsLen
] = '\0'; // null terminate
352 // OK, we are done with |outBuf|
353 nsMemory::Free(outBuf
);
357 nsMemory::Free(inBuf
);
363 nsHttpNTLMAuth::GetAuthFlags(PRUint32
*flags
)
365 *flags
= CONNECTION_BASED
| IDENTITY_INCLUDES_DOMAIN
| IDENTITY_ENCRYPTED
;