Back to index

lightning-sunbird  0.9+nobinonly
nsHttpNTLMAuth.cpp
Go to the documentation of this file.
00001 /* vim:set ts=4 sw=4 sts=4 et ci: */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 2003
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Darin Fisher <darin@meer.net>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include <stdlib.h>
00040 #include "nsHttp.h"
00041 #include "nsHttpNTLMAuth.h"
00042 #include "nsIComponentManager.h"
00043 #include "nsIAuthModule.h"
00044 #include "nsCOMPtr.h"
00045 #include "plbase64.h"
00046 
00047 //-----------------------------------------------------------------------------
00048 
00049 #ifdef XP_WIN
00050 
00051 #include "nsIPrefBranch.h"
00052 #include "nsIPrefService.h"
00053 #include "nsIServiceManager.h"
00054 #include "nsIHttpChannel.h"
00055 #include "nsIURI.h"
00056 
00057 static const char kAllowProxies[] = "network.automatic-ntlm-auth.allow-proxies";
00058 static const char kTrustedURIs[]  = "network.automatic-ntlm-auth.trusted-uris";
00059 
00060 // XXX MatchesBaseURI and TestPref are duplicated in nsHttpNegotiateAuth.cpp,
00061 // but since that file lives in a separate library we cannot directly share it.
00062 // bug 236865 addresses this problem.
00063 
00064 static PRBool
00065 MatchesBaseURI(const nsCSubstring &matchScheme,
00066                const nsCSubstring &matchHost,
00067                PRInt32             matchPort,
00068                const char         *baseStart,
00069                const char         *baseEnd)
00070 {
00071     // check if scheme://host:port matches baseURI
00072 
00073     // parse the base URI
00074     const char *hostStart, *schemeEnd = strstr(baseStart, "://");
00075     if (schemeEnd) {
00076         // the given scheme must match the parsed scheme exactly
00077         if (!matchScheme.Equals(Substring(baseStart, schemeEnd)))
00078             return PR_FALSE;
00079         hostStart = schemeEnd + 3;
00080     }
00081     else
00082         hostStart = baseStart;
00083 
00084     // XXX this does not work for IPv6-literals
00085     const char *hostEnd = strchr(hostStart, ':');
00086     if (hostEnd && hostEnd <= baseEnd) {
00087         // the given port must match the parsed port exactly
00088         int port = atoi(hostEnd + 1);
00089         if (matchPort != (PRInt32) port)
00090             return PR_FALSE;
00091     }
00092     else
00093         hostEnd = baseEnd;
00094 
00095 
00096     // if we didn't parse out a host, then assume we got a match.
00097     if (hostStart == hostEnd)
00098         return PR_TRUE;
00099 
00100     PRUint32 hostLen = hostEnd - hostStart;
00101 
00102     // matchHost must either equal host or be a subdomain of host
00103     if (matchHost.Length() < hostLen)
00104         return PR_FALSE;
00105 
00106     const char *end = matchHost.EndReading();
00107     if (PL_strncasecmp(end - hostLen, hostStart, hostLen) == 0) {
00108         // if matchHost ends with host from the base URI, then make sure it is
00109         // either an exact match, or prefixed with a dot.  we don't want
00110         // "foobar.com" to match "bar.com"
00111         if (matchHost.Length() == hostLen ||
00112             *(end - hostLen) == '.' ||
00113             *(end - hostLen - 1) == '.')
00114             return PR_TRUE;
00115     }
00116 
00117     return PR_FALSE;
00118 }
00119 
00120 static PRBool
00121 TestPref(nsIURI *uri, const char *pref)
00122 {
00123     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
00124     if (!prefs)
00125         return PR_FALSE;
00126 
00127     nsCAutoString scheme, host;
00128     PRInt32 port;
00129 
00130     if (NS_FAILED(uri->GetScheme(scheme)))
00131         return PR_FALSE;
00132     if (NS_FAILED(uri->GetAsciiHost(host)))
00133         return PR_FALSE;
00134     if (NS_FAILED(uri->GetPort(&port)))
00135         return PR_FALSE;
00136 
00137     char *hostList;
00138     if (NS_FAILED(prefs->GetCharPref(pref, &hostList)) || !hostList)
00139         return PR_FALSE;
00140 
00141     // pseudo-BNF
00142     // ----------
00143     //
00144     // url-list       base-url ( base-url "," LWS )*
00145     // base-url       ( scheme-part | host-part | scheme-part host-part )
00146     // scheme-part    scheme "://"
00147     // host-part      host [":" port]
00148     //
00149     // for example:
00150     //   "https://, http://office.foo.com"
00151     //
00152 
00153     char *start = hostList, *end;
00154     for (;;) {
00155         // skip past any whitespace
00156         while (*start == ' ' || *start == '\t')
00157             ++start;
00158         end = strchr(start, ',');
00159         if (!end)
00160             end = start + strlen(start);
00161         if (start == end)
00162             break;
00163         if (MatchesBaseURI(scheme, host, port, start, end))
00164             return PR_TRUE;
00165         if (*end == '\0')
00166             break;
00167         start = end + 1;
00168     }
00169     
00170     nsMemory::Free(hostList);
00171     return PR_FALSE;
00172 }
00173 
00174 static PRBool
00175 CanUseSysNTLM(nsIHttpChannel *channel, PRBool isProxyAuth)
00176 {
00177     // check prefs
00178 
00179     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
00180     if (!prefs)
00181         return PR_FALSE;
00182 
00183     PRBool val;
00184     if (isProxyAuth) {
00185         if (NS_FAILED(prefs->GetBoolPref(kAllowProxies, &val)))
00186             val = PR_FALSE;
00187         LOG(("sys-ntlm allowed for proxy: %d\n", val));
00188         return val;
00189     }
00190     else {
00191         nsCOMPtr<nsIURI> uri;
00192         channel->GetURI(getter_AddRefs(uri));
00193         if (uri && TestPref(uri, kTrustedURIs)) {
00194             LOG(("sys-ntlm allowed for host\n"));
00195             return PR_TRUE;
00196         }
00197     }
00198 
00199     return PR_FALSE;
00200 }
00201 
00202 // Dummy class for session state object.  This class doesn't hold any data.
00203 // Instead we use its existance as a flag.  See ChallengeReceived.
00204 class nsNTLMSessionState : public nsISupports 
00205 {
00206 public:
00207     NS_DECL_ISUPPORTS
00208 };
00209 NS_IMPL_ISUPPORTS0(nsNTLMSessionState)
00210 
00211 #endif
00212 
00213 //-----------------------------------------------------------------------------
00214 
00215 NS_IMPL_ISUPPORTS1(nsHttpNTLMAuth, nsIHttpAuthenticator)
00216 
00217 NS_IMETHODIMP
00218 nsHttpNTLMAuth::ChallengeReceived(nsIHttpChannel *channel,
00219                                   const char     *challenge,
00220                                   PRBool          isProxyAuth,
00221                                   nsISupports   **sessionState,
00222                                   nsISupports   **continuationState,
00223                                   PRBool         *identityInvalid)
00224 {
00225     LOG(("nsHttpNTLMAuth::ChallengeReceived [ss=%p cs=%p]\n",
00226          *sessionState, *continuationState));
00227 
00228     // NOTE: we don't define any session state
00229 
00230     *identityInvalid = PR_FALSE;
00231     // start new auth sequence if challenge is exactly "NTLM"
00232     if (PL_strcasecmp(challenge, "NTLM") == 0) {
00233         nsCOMPtr<nsISupports> module;
00234 #ifdef XP_WIN
00235         //
00236         // our session state is non-null to indicate that we've flagged
00237         // this auth domain as not accepting the system's default login.
00238         //
00239         PRBool trySysNTLM = (*sessionState == nsnull);
00240 
00241         //
00242         // on windows, we may have access to the built-in SSPI library,
00243         // which could be used to authenticate the user without prompting.
00244         // 
00245         // if the continuationState is null, then we may want to try using
00246         // the SSPI NTLM module.  however, we need to take care to only use
00247         // that module when speaking to a trusted host.  because the SSPI
00248         // may send a weak LMv1 hash of the user's password, we cannot just
00249         // send it to any server.
00250         //
00251         if (trySysNTLM && !*continuationState && CanUseSysNTLM(channel, isProxyAuth)) {
00252             module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sys-ntlm");
00253 #ifdef PR_LOGGING
00254             if (!module)
00255                 LOG(("failed to load sys-ntlm module\n"));
00256 #endif
00257         }
00258 
00259         // it's possible that there is no ntlm-sspi auth module...
00260         if (!module) {
00261             if (!*sessionState) {
00262                 // remember the fact that we cannot use the "sys-ntlm" module,
00263                 // so we don't ever bother trying again for this auth domain.
00264                 *sessionState = new nsNTLMSessionState();
00265                 if (!*sessionState)
00266                     return NS_ERROR_OUT_OF_MEMORY;
00267                 NS_ADDREF(*sessionState);
00268             }
00269 #else
00270         {
00271 #endif 
00272             module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "ntlm");
00273 
00274             // prompt user for domain, username, and password...
00275             *identityInvalid = PR_TRUE;
00276         }
00277 
00278         // if this fails, then it means that we cannot do NTLM auth.
00279         if (!module)
00280             return NS_ERROR_UNEXPECTED;
00281 
00282         // non-null continuation state implies that we failed to authenticate.
00283         // blow away the old authentication state, and use the new one.
00284         module.swap(*continuationState);
00285     }
00286     return NS_OK;
00287 }
00288 
00289 NS_IMETHODIMP
00290 nsHttpNTLMAuth::GenerateCredentials(nsIHttpChannel  *httpChannel,
00291                                     const char      *challenge,
00292                                     PRBool           isProxyAuth,
00293                                     const PRUnichar *domain,
00294                                     const PRUnichar *user,
00295                                     const PRUnichar *pass,
00296                                     nsISupports    **sessionState,
00297                                     nsISupports    **continuationState,
00298                                     char           **creds)
00299 
00300 {
00301     LOG(("nsHttpNTLMAuth::GenerateCredentials\n"));
00302 
00303     *creds = nsnull;
00304 
00305     nsresult rv;
00306     nsCOMPtr<nsIAuthModule> module = do_QueryInterface(*continuationState, &rv);
00307     NS_ENSURE_SUCCESS(rv, rv);
00308 
00309     void *inBuf, *outBuf;
00310     PRUint32 inBufLen, outBufLen;
00311 
00312     // initial challenge
00313     if (PL_strcasecmp(challenge, "NTLM") == 0) {
00314         // initialize auth module
00315         rv = module->Init(nsnull, nsIAuthModule::REQ_DEFAULT, domain, user, pass);
00316         if (NS_FAILED(rv))
00317             return rv;
00318 
00319         inBufLen = 0;
00320         inBuf = nsnull;
00321     }
00322     else {
00323         // decode challenge; skip past "NTLM " to the start of the base64
00324         // encoded data.
00325         int len = strlen(challenge);
00326         if (len < 6)
00327             return NS_ERROR_UNEXPECTED; // bogus challenge
00328         challenge += 5;
00329         len -= 5;
00330 
00331         // decode into the input secbuffer
00332         inBufLen = (len * 3)/4;      // sufficient size (see plbase64.h)
00333         inBuf = nsMemory::Alloc(inBufLen);
00334         if (!inBuf)
00335             return NS_ERROR_OUT_OF_MEMORY;
00336 
00337         // strip off any padding (see bug 230351)
00338         while (challenge[len - 1] == '=')
00339           len--;
00340 
00341         if (PL_Base64Decode(challenge, len, (char *) inBuf) == nsnull) {
00342             nsMemory::Free(inBuf);
00343             return NS_ERROR_UNEXPECTED; // improper base64 encoding
00344         }
00345     }
00346 
00347     rv = module->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen);
00348     if (NS_SUCCEEDED(rv)) {
00349         // base64 encode data in output buffer and prepend "NTLM "
00350         int credsLen = 5 + ((outBufLen + 2)/3)*4;
00351         *creds = (char *) nsMemory::Alloc(credsLen + 1);
00352         if (!*creds)
00353             rv = NS_ERROR_OUT_OF_MEMORY;
00354         else {
00355             memcpy(*creds, "NTLM ", 5);
00356             PL_Base64Encode((char *) outBuf, outBufLen, *creds + 5);
00357             (*creds)[credsLen] = '\0'; // null terminate
00358         }
00359         // OK, we are done with |outBuf|
00360         nsMemory::Free(outBuf);
00361     }
00362 
00363     if (inBuf)
00364         nsMemory::Free(inBuf);
00365 
00366     return rv;
00367 }
00368 
00369 NS_IMETHODIMP
00370 nsHttpNTLMAuth::GetAuthFlags(PRUint32 *flags)
00371 {
00372     *flags = CONNECTION_BASED | IDENTITY_INCLUDES_DOMAIN;
00373     return NS_OK;
00374 }