1 /* vim:set ts=4 sw=4 sts=4 et cindent: */
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 the Negotiateauth
17 * The Initial Developer of the Original Code is Daniel Kouril.
18 * Portions created by the Initial Developer are Copyright (C) 2003
19 * the Initial Developer. All Rights Reserved.
22 * Daniel Kouril <kouril@ics.muni.cz> (original author)
23 * Wyllys Ingersoll <wyllys.ingersoll@sun.com>
24 * Christopher Nebergall <cneberg@sandia.gov>
25 * Darin Fisher <darin@meer.net>
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.
39 * ***** END LICENSE BLOCK ***** */
42 // HTTP Negotiate Authentication Support Module
44 // Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt
45 // (formerly draft-brezak-spnego-http-04.txt)
47 // Also described here:
48 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp
55 #include "nsHttpNegotiateAuth.h"
57 #include "nsIHttpChannel.h"
58 #include "nsIProxiedChannel.h"
59 #include "nsIAuthModule.h"
60 #include "nsIServiceManager.h"
61 #include "nsIPrefService.h"
62 #include "nsIPrefBranch.h"
63 #include "nsIProxyInfo.h"
74 //-----------------------------------------------------------------------------
76 static const char kNegotiate
[] = "Negotiate";
77 static const char kNegotiateAuthTrustedURIs
[] = "network.negotiate-auth.trusted-uris";
78 static const char kNegotiateAuthDelegationURIs
[] = "network.negotiate-auth.delegation-uris";
79 static const char kNegotiateAuthAllowProxies
[] = "network.negotiate-auth.allow-proxies";
80 static const char kNegotiateAuthSSPI
[] = "network.auth.use-sspi";
82 #define kNegotiateLen (sizeof(kNegotiate)-1)
84 //-----------------------------------------------------------------------------
87 nsHttpNegotiateAuth::GetAuthFlags(PRUint32
*flags
)
90 // Negotiate Auth creds should not be reused across multiple requests.
91 // Only perform the negotiation when it is explicitly requested by the
92 // server. Thus, do *NOT* use the "REUSABLE_CREDENTIALS" flag here.
94 // CONNECTION_BASED is specified instead of REQUEST_BASED since we need
95 // to complete a sequence of transactions with the server over the same
98 *flags
= CONNECTION_BASED
| IDENTITY_IGNORED
;
103 // Always set *identityInvalid == FALSE here. This
104 // will prevent the browser from popping up the authentication
105 // prompt window. Because GSSAPI does not have an API
106 // for fetching initial credentials (ex: A Kerberos TGT),
107 // there is no correct way to get the users credentials.
110 nsHttpNegotiateAuth::ChallengeReceived(nsIHttpChannel
*httpChannel
,
111 const char *challenge
,
113 nsISupports
**sessionState
,
114 nsISupports
**continuationState
,
115 PRBool
*identityInvalid
)
117 nsIAuthModule
*module
= (nsIAuthModule
*) *continuationState
;
119 *identityInvalid
= PR_FALSE
;
125 nsCOMPtr
<nsIURI
> uri
;
126 rv
= httpChannel
->GetURI(getter_AddRefs(uri
));
130 PRUint32 req_flags
= nsIAuthModule::REQ_DEFAULT
;
131 nsCAutoString service
;
134 if (!TestBoolPref(kNegotiateAuthAllowProxies
)) {
135 LOG(("nsHttpNegotiateAuth::ChallengeReceived proxy auth blocked\n"));
136 return NS_ERROR_ABORT
;
139 nsCOMPtr
<nsIProxiedChannel
> proxied
=
140 do_QueryInterface(httpChannel
);
141 NS_ENSURE_STATE(proxied
);
143 nsCOMPtr
<nsIProxyInfo
> proxyInfo
;
144 proxied
->GetProxyInfo(getter_AddRefs(proxyInfo
));
145 NS_ENSURE_STATE(proxyInfo
);
147 proxyInfo
->GetHost(service
);
150 PRBool allowed
= TestPref(uri
, kNegotiateAuthTrustedURIs
);
152 LOG(("nsHttpNegotiateAuth::ChallengeReceived URI blocked\n"));
153 return NS_ERROR_ABORT
;
156 PRBool delegation
= TestPref(uri
, kNegotiateAuthDelegationURIs
);
158 LOG((" using REQ_DELEGATE\n"));
159 req_flags
|= nsIAuthModule::REQ_DELEGATE
;
162 rv
= uri
->GetAsciiHost(service
);
167 LOG((" service = %s\n", service
.get()));
170 // The correct service name for IIS servers is "HTTP/f.q.d.n", so
171 // construct the proper service name for passing to "gss_import_name".
173 // TODO: Possibly make this a configurable service name for use
174 // with non-standard servers that use stuff like "khttp/f.q.d.n"
177 service
.Insert("HTTP@", 0);
179 const char *contractID
;
180 if (TestBoolPref(kNegotiateAuthSSPI
)) {
181 LOG((" using negotiate-sspi\n"));
182 contractID
= NS_AUTH_MODULE_CONTRACTID_PREFIX
"negotiate-sspi";
185 LOG((" using negotiate-gss\n"));
186 contractID
= NS_AUTH_MODULE_CONTRACTID_PREFIX
"negotiate-gss";
189 rv
= CallCreateInstance(contractID
, &module
);
192 LOG((" Failed to load Negotiate Module \n"));
196 rv
= module
->Init(service
.get(), req_flags
, nsnull
, nsnull
, nsnull
);
203 *continuationState
= module
;
207 NS_IMPL_ISUPPORTS1(nsHttpNegotiateAuth
, nsIHttpAuthenticator
)
210 // GenerateCredentials
212 // This routine is responsible for creating the correct authentication
213 // blob to pass to the server that requested "Negotiate" authentication.
216 nsHttpNegotiateAuth::GenerateCredentials(nsIHttpChannel
*httpChannel
,
217 const char *challenge
,
219 const PRUnichar
*domain
,
220 const PRUnichar
*username
,
221 const PRUnichar
*password
,
222 nsISupports
**sessionState
,
223 nsISupports
**continuationState
,
226 // ChallengeReceived must have been called previously.
227 nsIAuthModule
*module
= (nsIAuthModule
*) *continuationState
;
228 NS_ENSURE_TRUE(module
, NS_ERROR_NOT_INITIALIZED
);
230 LOG(("nsHttpNegotiateAuth::GenerateCredentials() [challenge=%s]\n", challenge
));
232 NS_ASSERTION(creds
, "null param");
235 PRBool isGssapiAuth
=
236 !PL_strncasecmp(challenge
, kNegotiate
, kNegotiateLen
);
237 NS_ASSERTION(isGssapiAuth
, "Unexpected challenge");
241 // If the "Negotiate:" header had some data associated with it,
242 // that data should be used as the input to this call. This may
243 // be a continuation of an earlier call because GSSAPI authentication
244 // often takes multiple round-trips to complete depending on the
245 // context flags given. We want to use MUTUAL_AUTHENTICATION which
246 // generally *does* require multiple round-trips. Don't assume
247 // auth can be completed in just 1 call.
249 unsigned int len
= strlen(challenge
);
251 void *inToken
, *outToken
;
252 PRUint32 inTokenLen
, outTokenLen
;
254 if (len
> kNegotiateLen
) {
255 challenge
+= kNegotiateLen
;
256 while (*challenge
== ' ')
258 len
= strlen(challenge
);
260 inTokenLen
= (len
* 3)/4;
261 inToken
= malloc(inTokenLen
);
263 return (NS_ERROR_OUT_OF_MEMORY
);
265 // strip off any padding (see bug 230351)
266 while (challenge
[len
- 1] == '=')
270 // Decode the response that followed the "Negotiate" token
272 if (PL_Base64Decode(challenge
, len
, (char *) inToken
) == NULL
) {
274 return(NS_ERROR_UNEXPECTED
);
279 // Initializing, don't use an input token.
285 nsresult rv
= module
->GetNextToken(inToken
, inTokenLen
, &outToken
, &outTokenLen
);
292 if (outTokenLen
== 0) {
293 LOG((" No output token to send, exiting"));
294 return NS_ERROR_FAILURE
;
298 // base64 encode the output token.
300 char *encoded_token
= PL_Base64Encode((char *)outToken
, outTokenLen
, nsnull
);
302 nsMemory::Free(outToken
);
305 return NS_ERROR_OUT_OF_MEMORY
;
307 LOG((" Sending a token of length %d\n", outTokenLen
));
309 // allocate a buffer sizeof("Negotiate" + " " + b64output_token + "\0")
310 *creds
= (char *) nsMemory::Alloc(kNegotiateLen
+ 1 + strlen(encoded_token
) + 1);
311 if (NS_UNLIKELY(!*creds
))
312 rv
= NS_ERROR_OUT_OF_MEMORY
;
314 sprintf(*creds
, "%s %s", kNegotiate
, encoded_token
);
316 PR_Free(encoded_token
);
321 nsHttpNegotiateAuth::TestBoolPref(const char *pref
)
323 nsCOMPtr
<nsIPrefBranch
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
328 nsresult rv
= prefs
->GetBoolPref(pref
, &val
);
336 nsHttpNegotiateAuth::TestPref(nsIURI
*uri
, const char *pref
)
338 nsCOMPtr
<nsIPrefBranch
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
342 nsCAutoString scheme
, host
;
345 if (NS_FAILED(uri
->GetScheme(scheme
)))
347 if (NS_FAILED(uri
->GetAsciiHost(host
)))
349 if (NS_FAILED(uri
->GetPort(&port
)))
353 if (NS_FAILED(prefs
->GetCharPref(pref
, &hostList
)) || !hostList
)
359 // url-list base-url ( base-url "," LWS )*
360 // base-url ( scheme-part | host-part | scheme-part host-part )
361 // scheme-part scheme "://"
362 // host-part host [":" port]
365 // "https://, http://office.foo.com"
368 char *start
= hostList
, *end
;
370 // skip past any whitespace
371 while (*start
== ' ' || *start
== '\t')
373 end
= strchr(start
, ',');
375 end
= start
+ strlen(start
);
378 if (MatchesBaseURI(scheme
, host
, port
, start
, end
))
385 nsMemory::Free(hostList
);
390 nsHttpNegotiateAuth::MatchesBaseURI(const nsCSubstring
&matchScheme
,
391 const nsCSubstring
&matchHost
,
393 const char *baseStart
,
396 // check if scheme://host:port matches baseURI
398 // parse the base URI
399 const char *hostStart
, *schemeEnd
= strstr(baseStart
, "://");
401 // the given scheme must match the parsed scheme exactly
402 if (!matchScheme
.Equals(Substring(baseStart
, schemeEnd
)))
404 hostStart
= schemeEnd
+ 3;
407 hostStart
= baseStart
;
409 // XXX this does not work for IPv6-literals
410 const char *hostEnd
= strchr(hostStart
, ':');
411 if (hostEnd
&& hostEnd
< baseEnd
) {
412 // the given port must match the parsed port exactly
413 int port
= atoi(hostEnd
+ 1);
414 if (matchPort
!= (PRInt32
) port
)
421 // if we didn't parse out a host, then assume we got a match.
422 if (hostStart
== hostEnd
)
425 PRUint32 hostLen
= hostEnd
- hostStart
;
427 // matchHost must either equal host or be a subdomain of host
428 if (matchHost
.Length() < hostLen
)
431 const char *end
= matchHost
.EndReading();
432 if (PL_strncasecmp(end
- hostLen
, hostStart
, hostLen
) == 0) {
433 // if matchHost ends with host from the base URI, then make sure it is
434 // either an exact match, or prefixed with a dot. we don't want
435 // "foobar.com" to match "bar.com"
436 if (matchHost
.Length() == hostLen
||
437 *(end
- hostLen
) == '.' ||
438 *(end
- hostLen
- 1) == '.')