#include #include #include "auth.h" #include "sspspm.h" #include "winctxt.h" extern SspData *g_pSspData; /*----------------------------------------------------------------------------- PLUG_CTX -----------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------- Load ---------------------------------------------------------------------------*/ DWORD PLUG_CTX::Load() { INET_ASSERT(_pSPMData == _pCreds->pSPM); DWORD_PTR dwAuthCode = 0; dwAuthCode = SSPI_InitScheme (GetScheme()); if (!dwAuthCode) { _pSPMData->eState = STATE_ERROR; return ERROR_WINHTTP_INTERNAL_ERROR; } _pSPMData->eState = STATE_LOADED; return ERROR_SUCCESS; } /*--------------------------------------------------------------------------- ClearAuthUser ---------------------------------------------------------------------------*/ DWORD PLUG_CTX::ClearAuthUser(LPVOID *ppvContext, LPSTR szServer) { if (GetState() == AUTHCTX::STATE_LOADED) { __try { UnloadAuthenticateUser(ppvContext, szServer, GetScheme()); } __except (EXCEPTION_EXECUTE_HANDLER) { DEBUG_PRINT(HTTP, ERROR, ("UnloadAuthenticateUser call down faulted\n")); } ENDEXCEPT } *ppvContext = 0; return ERROR_SUCCESS; } /*----------------------------------------------------------------------------- wQueryHeadersAlloc Routine Description: Allocates a HTTP Header String, and queries the HTTP handle for it. Arguments: hRequestMapped - An open HTTP request handle where headers can be quiered dwQuery - The Query Type to pass to HttpQueryHeaders lpdwQueryIndex - The Index of the header to pass to HttpQueryHeaders, make sure to inialize to 0. lppszOutStr - On success, a pointer to Allocated string with header string, lpdwSize - size of the string returned in lppszOutStr Return Value: DWORD Success - ERROR_SUCCESS Failure - One of Several Error codes defined in winerror.h or wininet.w Comments: On Error, lppszOutStr may still contain an allocated string that will need to be freed. -----------------------------------------------------------------------------*/ DWORD PLUG_CTX::wQueryHeadersAlloc ( IN HINTERNET hRequestMapped, IN DWORD dwQuery, OUT LPDWORD lpdwQueryIndex, OUT LPSTR *lppszOutStr, OUT LPDWORD lpdwSize ) { LPSTR lpszRawHeaderBuf = NULL; DWORD dwcbRawHeaderBuf = 0; DWORD error; DWORD length; HTTP_REQUEST_HANDLE_OBJECT * pHttpRequest; INET_ASSERT(lppszOutStr); INET_ASSERT(hRequestMapped); INET_ASSERT(lpdwSize); INET_ASSERT((dwQuery & HTTP_QUERY_HEADER_MASK) != HTTP_QUERY_CUSTOM); *lppszOutStr = NULL; error = ERROR_SUCCESS; pHttpRequest = (HTTP_REQUEST_HANDLE_OBJECT *) hRequestMapped; // Attempt to determine whether our header is there. length = 0; if (pHttpRequest->QueryInfo(dwQuery, NULL, NULL, &length, lpdwQueryIndex) != ERROR_INSUFFICIENT_BUFFER) { // no authentication happening, we're done error = ERROR_HTTP_HEADER_NOT_FOUND; goto quit; } // Allocate a Fixed Size Buffer lpszRawHeaderBuf = (LPSTR) ALLOCATE_MEMORY(LPTR, length); dwcbRawHeaderBuf = length; if ( lpszRawHeaderBuf == NULL ) { error = ERROR_NOT_ENOUGH_MEMORY; goto quit; } error = pHttpRequest->QueryInfo (dwQuery, NULL, lpszRawHeaderBuf, &dwcbRawHeaderBuf, lpdwQueryIndex); INET_ASSERT(error != ERROR_INSUFFICIENT_BUFFER ); INET_ASSERT(error != ERROR_HTTP_HEADER_NOT_FOUND ); quit: if ( error != ERROR_SUCCESS ) { dwcbRawHeaderBuf = 0; if ( lpszRawHeaderBuf ) *lpszRawHeaderBuf = '\0'; } *lppszOutStr = lpszRawHeaderBuf; *lpdwSize = dwcbRawHeaderBuf; return error; } /*----------------------------------------------------------------------------- CrackAuthenticationHeader Routine Description: Attempts to decode a HTTP 1.1 Authentication header into its components. Arguments: hRequestMapped - Mapped Request handle fIsProxy - Whether proxy or server auth lpdwAuthenticationIndex - Index of current HTTP header. ( initally called with 0 ) lppszAuthHeader - allocated pointer which should be freed by client lppszAuthScheme - Pointer to Authentication scheme string. lppszRealm - Pointer to Realm string, lpExtra - Pointer to any Extra String data in the header that is not part of the Realm lpdwExtra - Pointer to Size of Extra data. lppszAuthScheme Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_NOT_ENOUGH_MEMORY, ERROR_HTTP_HEADER_NOT_FOUND Comments: -----------------------------------------------------------------------------*/ DWORD PLUG_CTX::CrackAuthenticationHeader ( IN HINTERNET hRequestMapped, IN BOOL fIsProxy, IN DWORD dwAuthenticationIndex, IN OUT LPSTR *lppszAuthHeader, IN OUT LPSTR *lppszExtra, IN OUT DWORD *lpdwExtra, OUT LPSTR *lppszAuthScheme ) { DWORD error = ERROR_SUCCESS; LPSTR lpszAuthHeader = NULL; DWORD cbAuthHeader = 0; LPSTR lpszExtra = NULL; LPSTR lpszAuthScheme = NULL; LPDWORD lpdwAuthenticationIndex = &dwAuthenticationIndex; INET_ASSERT(lpdwExtra); INET_ASSERT(lppszExtra); INET_ASSERT(lpdwAuthenticationIndex); DWORD dwQuery = fIsProxy? HTTP_QUERY_PROXY_AUTHENTICATE : HTTP_QUERY_WWW_AUTHENTICATE; error = wQueryHeadersAlloc (hRequestMapped, dwQuery, lpdwAuthenticationIndex, &lpszAuthHeader, &cbAuthHeader); if ( error != ERROR_SUCCESS ) { INET_ASSERT(*lpdwAuthenticationIndex || error == ERROR_HTTP_HEADER_NOT_FOUND ); goto quit; } // // Parse Header for Scheme type // lpszAuthScheme = lpszAuthHeader; while ( *lpszAuthScheme == ' ' ) // strip spaces lpszAuthScheme++; lpszExtra = strchr(lpszAuthScheme, ' '); if (lpszExtra) *lpszExtra++ = '\0'; if (lstrcmpi(GetScheme(), lpszAuthScheme)) { DEBUG_PRINT(HTTP, ERROR, ("Authentication: HTTP Scheme has changed!: Scheme=%q\n", lpszAuthScheme)); goto quit; } DEBUG_PRINT (HTTP, INFO, ("Authentication: found in headers: Scheme=%q, Extra=%q\n", lpszAuthScheme, lpszExtra)); quit: *lppszExtra = lpszExtra; *lpdwExtra = lpszExtra ? lstrlen(lpszExtra) : 0; *lppszAuthHeader = lpszAuthHeader; *lppszAuthScheme = lpszAuthScheme; return error; } /*--------------------------------------------------------------------------- ResolveProtocol ---------------------------------------------------------------------------*/ VOID PLUG_CTX::ResolveProtocol() { SECURITY_STATUS ssResult; PWINCONTEXT pWinContext; SecPkgContext_NegotiationInfo SecPkgCtxtInfo; INET_ASSERT(GetSchemeType() == WINHTTP_AUTH_SCHEME_NEGOTIATE); SecPkgCtxtInfo.PackageInfo = NULL; // Call QueryContextAttributes on the context handle. pWinContext = (PWINCONTEXT) (_pvContext); ssResult = (*(g_pSspData->pFuncTbl->QueryContextAttributes)) (pWinContext->pSspContextHandle, SECPKG_ATTR_NEGOTIATION_INFO, &SecPkgCtxtInfo); if (ssResult == SEC_E_OK && (SecPkgCtxtInfo.NegotiationState == SECPKG_NEGOTIATION_COMPLETE || (SecPkgCtxtInfo.NegotiationState == SECPKG_NEGOTIATION_OPTIMISTIC))) { // Resolve actual auth protocol from package name. // update both the auth context and Creds entry. if (!lstrcmpi(SecPkgCtxtInfo.PackageInfo->Name, "NTLM")) { _eSubScheme = WINHTTP_AUTH_SCHEME_NTLM; _dwSubFlags = PLUGIN_AUTH_FLAGS_NO_REALM; } else if (!lstrcmpi(SecPkgCtxtInfo.PackageInfo->Name, "Kerberos")) { _eSubScheme = WINHTTP_AUTH_SCHEME_KERBEROS; _dwSubFlags = PLUGIN_AUTH_FLAGS_KEEP_ALIVE_NOT_REQUIRED | PLUGIN_AUTH_FLAGS_NO_REALM; } // BUGBUG - This faults. // } if (SecPkgCtxtInfo.PackageInfo) { (*(g_pSspData->pFuncTbl->FreeContextBuffer))(SecPkgCtxtInfo.PackageInfo); } } /*--------------------------------------------------------------------------- Constructor ---------------------------------------------------------------------------*/ PLUG_CTX::PLUG_CTX(HTTP_REQUEST_HANDLE_OBJECT *pRequest, BOOL fIsProxy, SPMData *pSPM, AUTH_CREDS* pCreds) : AUTHCTX(pSPM, pCreds) { _fIsProxy = fIsProxy; _pRequest = pRequest; _szAlloc = NULL; _szData = NULL; _cbData = 0; _pRequest->SetAuthState(AUTHSTATE_NONE); _fNTLMProxyAuth = _fIsProxy && (GetSchemeType() == WINHTTP_AUTH_SCHEME_NTLM); _pszFQDN = NULL; } /*--------------------------------------------------------------------------- Destructor ---------------------------------------------------------------------------*/ PLUG_CTX::~PLUG_CTX() { if (GetState() == AUTHCTX::STATE_LOADED) { if (_pCreds) { if (_CtxCriSec.Lock()) { ClearAuthUser(&_pvContext, _pCreds->lpszHost); _CtxCriSec.Unlock(); } } } if (_pRequest) { _pRequest->SetAuthState(AUTHSTATE_NONE); } if (_pszFQDN) { FREE_MEMORY(_pszFQDN); } } /*--------------------------------------------------------------------------- PreAuthUser ---------------------------------------------------------------------------*/ DWORD PLUG_CTX::PreAuthUser(OUT LPSTR pBuf, IN OUT LPDWORD pcbBuf) { if (!_CtxCriSec.Lock()) { return ERROR_NOT_ENOUGH_MEMORY; } INET_ASSERT(_pSPMData == _pCreds->pSPM); DWORD dwError; SECURITY_STATUS ssResult; // Make sure the auth provider is loaded. if (GetState() != AUTHCTX::STATE_LOADED) { if (GetState() != AUTHCTX::STATE_ERROR ) Load(); if (GetState() != AUTHCTX::STATE_LOADED) { dwError = ERROR_WINHTTP_INTERNAL_ERROR; goto exit; } } BOOL fCanUseLogon = _fIsProxy || _pRequest->SilentLogonOK(_pCreds->lpszHost); LPSTR lpszFQDN = GetFQDN(_pCreds->lpszHost); LPSTR lpszHostName = lpszFQDN ? lpszFQDN : _pCreds->lpszHost; __try { ssResult = SEC_E_INTERNAL_ERROR; dwError = PreAuthenticateUser(&_pvContext, lpszHostName, GetScheme(), fCanUseLogon, 0, // dwFlags pBuf, pcbBuf, _pCreds->lpszUser, _pCreds->lpszPass, &ssResult); // Transit to the correct auth state. if (ssResult == SEC_E_OK || ssResult == SEC_I_CONTINUE_NEEDED) { if (GetSchemeType() == WINHTTP_AUTH_SCHEME_NEGOTIATE) ResolveProtocol(); // Kerberos + SEC_E_OK or SEC_I_CONTINUE_NEEDED transits to challenge. // Negotiate does not transit to challenge. // Any other protocol + SEC_E_OK only transits to challenge. if ((GetSchemeType() == WINHTTP_AUTH_SCHEME_KERBEROS && (ssResult == SEC_E_OK || ssResult == SEC_I_CONTINUE_NEEDED)) || (GetSchemeType() != WINHTTP_AUTH_SCHEME_NEGOTIATE && ssResult == SEC_E_OK)) { _pRequest->SetAuthState(AUTHSTATE_CHALLENGE); } } } __except(EXCEPTION_EXECUTE_HANDLER) { DEBUG_PRINT (HTTP, ERROR, ("preAuthenticateUser call down faulted\n")); _pSPMData->eState = STATE_ERROR; dwError = ERROR_WINHTTP_INTERNAL_ERROR; } ENDEXCEPT exit: _CtxCriSec.Unlock(); return dwError; } /*--------------------------------------------------------------------------- UpdateFromHeaders ---------------------------------------------------------------------------*/ DWORD PLUG_CTX::UpdateFromHeaders(HTTP_REQUEST_HANDLE_OBJECT *pRequest, BOOL fIsProxy) { DWORD dwError, cbExtra, dwAuthIdx; LPSTR szAuthHeader, szExtra, szScheme; // Get the auth header index corresponding to the scheme of this ctx. if ((dwError = FindHdrIdxFromScheme(&dwAuthIdx)) != ERROR_SUCCESS) goto quit; // Get the scheme and any extra data. if ((dwError = CrackAuthenticationHeader(pRequest, fIsProxy, dwAuthIdx, &szAuthHeader, &szExtra, &cbExtra, &szScheme)) != ERROR_SUCCESS) goto quit; if (!cbExtra) _pRequest->SetAuthState(AUTHSTATE_NEGOTIATE); // Check if auth scheme requires keep-alive. if (!(GetFlags() & PLUGIN_AUTH_FLAGS_KEEP_ALIVE_NOT_REQUIRED)) { // if in negotiate phase check if we are going via proxy. if (pRequest->GetAuthState() == AUTHSTATE_NEGOTIATE) { // BUGBUG: if via proxy, we are not going to get keep-alive // connection to the server. It would be nice if we knew // a priori the whether proxy would allow us to tunnel to // http port on the server. Otherwise if we try and fail, // we look bad vs. other browsers who are ignorant of ntlm // and fall back to basic. CHAR szBuffer[64]; DWORD dwBufferLength = sizeof(szBuffer); DWORD dwIndex = 0; BOOL fSessionBasedAuth = FALSE; if (pRequest->QueryResponseHeader(HTTP_QUERY_PROXY_SUPPORT, szBuffer, &dwBufferLength, 0, &dwIndex) == ERROR_SUCCESS) { if (!_stricmp(szBuffer, "Session-Based-Authentication")) { fSessionBasedAuth = TRUE; } } if (!fIsProxy && pRequest->IsRequestUsingProxy() && !pRequest->IsTalkingToSecureServerViaProxy() && !fSessionBasedAuth) { // Ignore NTLM via proxy since we won't get k-a to server. dwError = ERROR_HTTP_HEADER_NOT_FOUND; goto quit; } } // Else if in challenge phase, we require a persistent connection. else { // If we don't have a keep-alive connection ... if (!(pRequest->IsPersistentConnection (fIsProxy))) { dwError = ERROR_HTTP_HEADER_NOT_FOUND; goto quit; } } } // end if keep-alive required quit: if (dwError == ERROR_SUCCESS) { // If no password cache is set in the auth context, // find or create one and set it in the handle. if (!_pCreds) { _pCreds = CreateCreds(pRequest, fIsProxy, _pSPMData, NULL); if (!_pCreds) { INET_ASSERT(FALSE); dwError = ERROR_WINHTTP_INTERNAL_ERROR; } else { INET_ASSERT(_pCreds->pSPM == _pSPMData); } } } if (dwError == ERROR_SUCCESS) { // Point to allocated data. _szAlloc = szAuthHeader; _szData = szExtra; _cbData = cbExtra; } else { // Free allocated data. if (_szAlloc) delete _szAlloc; _szAlloc = NULL; _szData = NULL; _cbData = 0; } // Return of non-success will cancel auth session. return dwError; } /*--------------------------------------------------------------------------- PostAuthUser ---------------------------------------------------------------------------*/ DWORD PLUG_CTX::PostAuthUser() { if (!_CtxCriSec.Lock()) { return ERROR_NOT_ENOUGH_MEMORY; } INET_ASSERT(_pSPMData == _pCreds->pSPM); DWORD dwError; // Make sure the auth provider is loaded. if (GetState() != AUTHCTX::STATE_LOADED) { if (GetState() != AUTHCTX::STATE_ERROR ) Load(); if (GetState() != AUTHCTX::STATE_LOADED) { dwError = ERROR_WINHTTP_INTERNAL_ERROR; goto exit; } } BOOL fCanUseLogon = _fIsProxy || _pRequest->SilentLogonOK(_pCreds->lpszHost); LPSTR lpszFQDN = GetFQDN(_pCreds->lpszHost); LPSTR lpszHostName = lpszFQDN ? lpszFQDN : _pCreds->lpszHost; SECURITY_STATUS ssResult; __try { ssResult = SEC_E_INTERNAL_ERROR; dwError = AuthenticateUser(&_pvContext, lpszHostName, GetScheme(), fCanUseLogon, _szData, _cbData, _pCreds->lpszUser, _pCreds->lpszPass, &ssResult); // Kerberos package can get into a bad state. if (GetSchemeType() == WINHTTP_AUTH_SCHEME_KERBEROS && ssResult == SEC_E_WRONG_PRINCIPAL) dwError = ERROR_WINHTTP_INCORRECT_PASSWORD; // Transit to the correct auth state. if (ssResult == SEC_E_OK || ssResult == SEC_I_CONTINUE_NEEDED) { if (GetSchemeType() == WINHTTP_AUTH_SCHEME_NEGOTIATE) ResolveProtocol(); // Kerberos + SEC_E_OK or SEC_I_CONTINUE_NEEDED transits to challenge. // Negotiate does not transit to challenge. // Any other protocol + SEC_E_OK only transits to challenge. if ((GetSchemeType() == WINHTTP_AUTH_SCHEME_KERBEROS && (ssResult == SEC_E_OK || ssResult == SEC_I_CONTINUE_NEEDED)) || (GetSchemeType() != WINHTTP_AUTH_SCHEME_NEGOTIATE && ssResult == SEC_E_OK)) { _pRequest->SetAuthState(AUTHSTATE_CHALLENGE); } } } __except (EXCEPTION_EXECUTE_HANDLER) { DEBUG_PRINT (HTTP, ERROR, ("AuthenticateUser faulted!\n")); dwError = ERROR_BAD_FORMAT; _pSPMData->eState = STATE_ERROR; } ENDEXCEPT if (_szAlloc) { delete _szAlloc; _szAlloc = NULL; _szData = NULL; } _cbData = 0; exit: _CtxCriSec.Unlock(); return dwError; } LPSTR PLUG_CTX::GetFQDN(LPSTR lpszHostName) { if (lstrcmpi(GetScheme(), "Negotiate")) // only need to get FQDN for Kerberos { return NULL; } if (_pszFQDN) { return _pszFQDN; } SERIALIZED_LIST* pResolverCache = GetRootHandle(_pRequest)->GetResolverCache()->GetResolverCacheList(); LPHOSTENT lpHostent; DWORD TTL; if (QueryHostentCache(pResolverCache, (LPSTR)lpszHostName, NULL, &lpHostent, &TTL)) { _pszFQDN = (lpHostent->h_name ? NewString(lpHostent->h_name) : NULL); ReleaseHostentCacheEntry(pResolverCache, lpHostent); return _pszFQDN; } return NULL; }