Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3217 lines
93 KiB

/*++
Copyright (c) 2001-2002 Microsoft Corporation
Module Name:
ucauth.c
Abstract:
This module implements the Authentication for the client APIs
Author:
Rajesh Sundaram (rajeshsu) 01-Jan-2001
Revision History:
--*/
#include "precomp.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text( PAGEUC, UcpAutoSelectAuthType )
#pragma alloc_text( PAGEUC, UcUpdateAuthorizationHeader )
#pragma alloc_text( PAGEUC, UcInitializeSSPI )
#pragma alloc_text( PAGEUC, UcComputeAuthHeaderSize )
#pragma alloc_text( PAGEUC, UcpGenerateDigestAuthHeader )
#pragma alloc_text( PAGEUC, UcGenerateAuthHeaderFromCredentials )
#pragma alloc_text( PAGEUC, UcGenerateProxyAuthHeaderFromCache )
#pragma alloc_text( PAGEUC, UcpGenerateSSPIAuthHeader )
#pragma alloc_text( PAGEUC, UcFindURIEntry )
#pragma alloc_text( PAGEUC, UcDeleteURIEntry )
#pragma alloc_text( PAGEUC, UcAddURIEntry )
#pragma alloc_text( PAGEUC, UcpProcessUriForPreAuth )
#pragma alloc_text( PAGEUC, UcpAllocateAuthCacheEntry )
#pragma alloc_text( PAGEUC, UcDeleteAllURIEntries )
#pragma alloc_text( PAGEUC, UcpGenerateBasicHeader )
#pragma alloc_text( PAGEUC, UcCopyAuthCredentialsToInternal )
#pragma alloc_text( PAGEUC, UcpProcessAuthParams )
#pragma alloc_text( PAGEUC, UcParseAuthChallenge )
#pragma alloc_text( PAGEUC, UcpAcquireClientCredentialsHandle )
#pragma alloc_text( PAGEUC, UcpGenerateSSPIAuthBlob )
#pragma alloc_text( PAGEUC, UcpGenerateDigestPreAuthHeader )
#pragma alloc_text( PAGEUC, UcpUpdateSSPIAuthHeader )
#pragma alloc_text( PAGEUC, UcDestroyInternalAuth )
#pragma alloc_text( PAGEUC, UcpGeneratePreAuthHeader )
#endif
//
// Used in wide char to multibyte conversion
//
static char DefaultChar = '_';
//
// Known parameters for known auth schemes
//
HTTP_AUTH_PARAM_ATTRIB HttpAuthBasicParams[] = HTTP_AUTH_BASIC_PARAMS_INIT;
HTTP_AUTH_PARAM_ATTRIB HttpAuthDigestParams[] = HTTP_AUTH_DIGEST_PARAMS_INIT;
//
// Auth scheme structs for all supported auth schemes
//
HTTP_AUTH_SCHEME HttpAuthScheme[HttpAuthTypesCount] = HTTP_AUTH_SCHEME_INIT;
//
// The order in which auth schemes are selected for (HttpAuthAutoSelect)
//
HTTP_AUTH_TYPE PreferredAuthTypes[] = PREFERRED_AUTH_TYPES_INIT;
/***************************************************************************++
Routine Description:
Used to initialize the SSPI module. Finds out the auth schemes supported
and the size of the max SSPI blob.
Arguments:
None.
Return Value:
STATUS_SUCCESS
--***************************************************************************/
NTSTATUS
UcInitializeSSPI(
VOID
)
{
PSecPkgInfoW pkgInfo;
UNICODE_STRING Scheme;
SECURITY_STATUS SecStatus;
//
// First try NTLM
//
Scheme.Length = HTTP_AUTH_NTLM_W_LENGTH;
Scheme.MaximumLength = Scheme.Length;
Scheme.Buffer = HTTP_AUTH_NTLM_W;
SecStatus = QuerySecurityPackageInfoW(&Scheme, &pkgInfo);
if (SecStatus == SEC_E_OK)
{
HttpAuthScheme[HttpAuthTypeNTLM].bSupported = TRUE;
SSPI_MAX_TOKEN_SIZE(HttpAuthTypeNTLM) = pkgInfo->cbMaxToken;
}
FreeContextBuffer(pkgInfo);
//
// Then Kerberos
//
Scheme.Length = HTTP_AUTH_KERBEROS_W_LENGTH;
Scheme.MaximumLength = Scheme.Length;
Scheme.Buffer = HTTP_AUTH_KERBEROS_W;
SecStatus = QuerySecurityPackageInfoW(&Scheme, &pkgInfo);
if(SecStatus == SEC_E_OK)
{
HttpAuthScheme[HttpAuthTypeKerberos].bSupported = TRUE;
SSPI_MAX_TOKEN_SIZE(HttpAuthTypeKerberos) = pkgInfo->cbMaxToken;
}
FreeContextBuffer(pkgInfo);
//
// Then Negotiate
//
Scheme.Length = HTTP_AUTH_NEGOTIATE_W_LENGTH;
Scheme.MaximumLength = Scheme.Length;
Scheme.Buffer = HTTP_AUTH_NEGOTIATE_W;
SecStatus = QuerySecurityPackageInfoW(&Scheme, &pkgInfo);
if(SecStatus == SEC_E_OK)
{
HttpAuthScheme[HttpAuthTypeNegotiate].bSupported = TRUE;
SSPI_MAX_TOKEN_SIZE(HttpAuthTypeNegotiate) = pkgInfo->cbMaxToken;
}
FreeContextBuffer(pkgInfo);
//
// Then WDigest
//
Scheme.Length = HTTP_AUTH_WDIGEST_W_LENGTH;
Scheme.MaximumLength = Scheme.Length;
Scheme.Buffer = HTTP_AUTH_WDIGEST_W;
SecStatus = QuerySecurityPackageInfoW(&Scheme, &pkgInfo);
if(SecStatus == SEC_E_OK)
{
HttpAuthScheme[HttpAuthTypeDigest].bSupported = TRUE;
SSPI_MAX_TOKEN_SIZE(HttpAuthTypeDigest) = pkgInfo->cbMaxToken;
}
FreeContextBuffer(pkgInfo);
return STATUS_SUCCESS;
}
/***************************************************************************++
Routine Description:
Select the "strongest" auth type from the WWW-Authenticate header.
Arguments:
pAuth - Supplies AUTH structure.
Return Value:
HTTP_AUTH_TYPE - Selected auth type or HttpAuthTypeAutoSelect
--***************************************************************************/
HTTP_AUTH_TYPE
UcpAutoSelectAuthType(
IN PHTTP_AUTH_CREDENTIALS pAuth
)
{
ULONG i;
NTSTATUS Status;
HTTP_AUTH_PARSED_PARAMS AuthSchemeParams[HttpAuthTypesCount];
// Sanity check
ASSERT(pAuth);
// Can't do anything if the header value is not specified
if (!pAuth->pHeaderValue || pAuth->HeaderValueLength == 0)
{
return HttpAuthTypeAutoSelect;
}
INIT_AUTH_PARSED_PARAMS(AuthSchemeParams, NULL);
//
// Parse the header value
// find which schemes are specified in the header
//
Status = UcParseWWWAuthenticateHeader(
pAuth->pHeaderValue,
pAuth->HeaderValueLength,
AuthSchemeParams
);
if (NT_SUCCESS(Status))
{
//
// Check the scheme in the order of preference
// Return the first scheme that is present in the header
//
for (i = 0; i < DIMENSION(PreferredAuthTypes); i++)
{
if (AuthSchemeParams[PreferredAuthTypes[i]].bPresent)
return PreferredAuthTypes[i];
}
}
// Default return auth select
return HttpAuthTypeAutoSelect;
}
/***************************************************************************++
Routine Description:
This is a wrapper routine that prepares arguments for the SSPI function
AcquireCredentialsHandle and calls it.
Arguments:
SchemeName - Name of the Auth scheme
SchemeNameLength - Length of Auth scheme (in bytes)
pCredentials - User supplied credentials.
pClientCred - Credentials Handle (Return value)
Return Value:
NTSTATUS
--***************************************************************************/
NTSTATUS
UcpAcquireClientCredentialsHandle(
IN PWSTR SchemeName,
IN USHORT SchemeNameLength,
IN PHTTP_AUTH_CREDENTIALS pCredentials,
OUT PCredHandle pClientCred
)
{
SECURITY_STATUS SecStatus;
TimeStamp LifeTime;
SECURITY_STRING PackageName;
SEC_WINNT_AUTH_IDENTITY_W AuthData, *pAuthData;
#ifdef WINNT_50
//
// SSPI in Windows 2000 requires memory to be allocated from the
// process's virtual address space.
//
#error Does not work with WINNT_50!
#endif
// Sanity check
PAGED_CODE();
ASSERT(SchemeName && SchemeNameLength);
ASSERT(pCredentials);
ASSERT(pClientCred);
// Use default credentials, unless specified otherwise!
pAuthData = NULL;
// Did user specify credentials?
if (!(pCredentials->AuthFlags & HTTP_AUTH_FLAGS_DEFAULT_CREDENTIALS))
{
// Yes. Use them.
AuthData.User = (PWSTR)pCredentials->pUserName;
AuthData.UserLength = pCredentials->UserNameLength/sizeof(WCHAR);
AuthData.Domain = (PWSTR)pCredentials->pDomain;
AuthData.DomainLength = pCredentials->DomainLength/sizeof(WCHAR);
AuthData.Password = (PWSTR)pCredentials->pPassword;
AuthData.PasswordLength = pCredentials->PasswordLength/sizeof(WCHAR);
// Above strings are in unicode
AuthData.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
// Use user specified credentials.
pAuthData = &AuthData;
}
// The package we are interested in.
PackageName.Buffer = SchemeName;
PackageName.Length = SchemeNameLength;
PackageName.MaximumLength = SchemeNameLength;
// Call SSPI to acquire credentials handle
SecStatus = AcquireCredentialsHandleW(NULL,
&PackageName,
SECPKG_CRED_OUTBOUND,
NULL,
pAuthData,
NULL,
NULL,
pClientCred,
&LifeTime);
if (!(pCredentials->AuthFlags & HTTP_AUTH_FLAGS_DEFAULT_CREDENTIALS))
{
//
// Once a credentials handle is obtained, there is no need to store
// the user credentials. Erase these values.
//
RtlSecureZeroMemory((PUCHAR)pCredentials->pUserName,
pCredentials->UserNameLength);
RtlSecureZeroMemory((PUCHAR)pCredentials->pDomain,
pCredentials->DomainLength);
RtlSecureZeroMemory((PUCHAR)pCredentials->pPassword,
pCredentials->PasswordLength);
}
//
// It is very important to return an NTSTATUS code from this function
// as the status can be as an IRP completion status.
//
return SecStatusToNtStatus(SecStatus);
}
/***************************************************************************++
Routine Description:
Calls SSPI to get the blob. UUEncodes the blob and writes it in pOutBuffer.
Arguments:
pServInf - Server Information
pUcAuth - Internal auth structure
pOutBuffer - points to output buffer
bRenegotiate - set if further renegotiation is required
Return Value:
NTSTATUS.
--***************************************************************************/
NTSTATUS
UcpGenerateSSPIAuthBlob(
IN PUC_PROCESS_SERVER_INFORMATION pServInfo,
IN PUC_HTTP_AUTH pUcAuth,
IN PUCHAR pOutBuffer,
IN ULONG OutBufferLen,
OUT PULONG pOutBytesTaken,
OUT PBOOLEAN bRenegotiate
)
{
PSecBufferDesc pInputBuffer;
SecBufferDesc InBufferDesc, OutBufferDesc;
SecBuffer InBuffer, OutBuffer;
TimeStamp LifeTime;
NTSTATUS Status;
SECURITY_STATUS SecStatus;
ULONG ContextAttributes;
PSECURITY_STRING pTarget;
UNICODE_STRING ServerName;
PVOID pVirtualBufferStart = 0;
ULONG VirtualBufferSize;
PCtxtHandle pContext;
HTTP_AUTH_TYPE AuthType;
PWSTR pScheme;
ULONG SchemeLength;
// Sanity check
PAGED_CODE();
//
// Initialize Output arguments.
//
*pOutBytesTaken = 0;
*bRenegotiate = FALSE;
AuthType = pUcAuth->Credentials.AuthType;
ASSERT(AuthType == HttpAuthTypeNTLM ||
AuthType == HttpAuthTypeKerberos ||
AuthType == HttpAuthTypeNegotiate);
// See if the auth scheme is supported.
if (HttpAuthScheme[AuthType].bSupported == FALSE)
{
return STATUS_NOT_SUPPORTED;
}
// Retrieve the auth scheme name.
pScheme = (PWSTR)HttpAuthScheme[AuthType].NameW;
SchemeLength = HttpAuthScheme[AuthType].NameWLength;
if(pUcAuth->bValidCredHandle == FALSE)
{
// Get a valid credentials handle from the SSP.
Status = UcpAcquireClientCredentialsHandle(pScheme,
(USHORT)SchemeLength,
&pUcAuth->Credentials,
&pUcAuth->hCredentials);
if (!NT_SUCCESS(Status))
{
return Status;
}
pUcAuth->bValidCredHandle = TRUE;
pInputBuffer = NULL;
pContext = NULL;
}
else
{
//
// Prepare the Input buffers
//
ASSERT(pUcAuth->ChallengeBufferSize != 0 &&
pUcAuth->pChallengeBuffer != NULL);
pInputBuffer = &InBufferDesc;
InBufferDesc.ulVersion = SECBUFFER_VERSION;
InBufferDesc.cBuffers = 1;
InBufferDesc.pBuffers = &InBuffer;
InBuffer.BufferType = SECBUFFER_TOKEN;
InBuffer.cbBuffer = pUcAuth->ChallengeBufferSize;
InBuffer.pvBuffer = pUcAuth->pChallengeBuffer;
pContext = &pUcAuth->hContext;
}
//
// Prepare the output buffer.
//
VirtualBufferSize = HttpAuthScheme[AuthType].SspiMaxTokenSize;
pVirtualBufferStart = UL_ALLOCATE_POOL(PagedPool,
VirtualBufferSize,
UC_SSPI_POOL_TAG);
if(pVirtualBufferStart == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
OutBufferDesc.ulVersion = SECBUFFER_VERSION;
OutBufferDesc.cBuffers = 1;
OutBufferDesc.pBuffers = &OutBuffer;
OutBuffer.cbBuffer = VirtualBufferSize;
OutBuffer.BufferType = SECBUFFER_TOKEN;
OutBuffer.pvBuffer = (PUCHAR) pVirtualBufferStart;
//
// Prepare sever name unicode string
//
pTarget = NULL;
if(HttpAuthScheme[AuthType].bServerNameRequired)
{
pTarget = &ServerName;
ServerName.Length = (USHORT) pServInfo->pServerInfo->ServerNameLength;
ServerName.MaximumLength = ServerName.Length;
ServerName.Buffer = (PWCHAR) pServInfo->pServerInfo->pServerName;
}
// We must have a valid credentials handle at this point
ASSERT(pUcAuth->bValidCredHandle);
// Call SSPI
SecStatus = InitializeSecurityContextW(
&pUcAuth->hCredentials,
pContext,
pTarget,
(ISC_REQ_DELEGATE|ISC_REQ_MUTUAL_AUTH),
0, // reserved
SECURITY_NATIVE_DREP,
pInputBuffer,
0, // reserved
&pUcAuth->hContext,
&OutBufferDesc,
&ContextAttributes,
&LifeTime
);
//
// Convert SecStatus it to NTSTATUS. Status will be returned from this
// function hence it must be NTSTATUS.
//
Status = SecStatusToNtStatus(SecStatus);
if(!NT_SUCCESS(Status))
{
//
// We are computing the max size of a token and we are using that
// So, this API can never return SEC_E_NO_MEMORY.
//
ASSERT(SecStatus != SEC_E_INSUFFICIENT_MEMORY);
}
else
{
// pUcAuth has a valid context handle that must be freed eventually.
pUcAuth->bValidCtxtHandle = TRUE;
if(SEC_I_CONTINUE_NEEDED == SecStatus)
{
*bRenegotiate = TRUE;
Status = BinaryToBase64((PUCHAR)OutBuffer.pvBuffer,
OutBuffer.cbBuffer,
pOutBuffer,
OutBufferLen,
pOutBytesTaken);
}
else if(SEC_E_OK == SecStatus)
{
Status = BinaryToBase64((PUCHAR)OutBuffer.pvBuffer,
OutBuffer.cbBuffer,
pOutBuffer,
OutBufferLen,
pOutBytesTaken);
}
else if(SEC_I_COMPLETE_NEEDED == SecStatus ||
SEC_I_COMPLETE_AND_CONTINUE == SecStatus)
{
//
// NTLM, Negotiate & Kerberos cannot return this status.
// This is returned only by DCE.
//
ASSERT(FALSE);
Status = STATUS_NOT_SUPPORTED;
}
}
UL_FREE_POOL(pVirtualBufferStart, UC_SSPI_POOL_TAG);
return Status;
}
/***************************************************************************++
Routine Description:
Computes the size required for Authentication headers.
Arguments:
pAuth - The Auth config object
UriLength - URI length (required for Digest)
AuthInternalLength - Space required for storing the internal auth structure
Return Value:
The header size.
--***************************************************************************/
ULONG
UcComputeAuthHeaderSize(
PHTTP_AUTH_CREDENTIALS pAuth,
PULONG AuthInternalLength,
PHTTP_AUTH_TYPE pAuthInternalType,
HTTP_HEADER_ID HeaderId
)
{
PHEADER_MAP_ENTRY pEntry;
ULONG AuthHeaderLength;
NTSTATUS Status;
ULONG BinaryLength;
ULONG Base64Length;
ULONG MaxTokenSize;
// Sanity check.
ASSERT(pAuth);
pEntry = &(g_RequestHeaderMapTable[g_RequestHeaderMap[HeaderId]]);
// 1 for SP char?
AuthHeaderLength = (pEntry->HeaderLength + 1 + CRLF_SIZE);
*AuthInternalLength = 0;
*pAuthInternalType = pAuth->AuthType;
// See if the user wants us to select an auth type.
if (*pAuthInternalType == HttpAuthTypeAutoSelect)
{
// Select an auth type.
*pAuthInternalType = UcpAutoSelectAuthType(pAuth);
if (*pAuthInternalType == HttpAuthTypeAutoSelect)
{
// Could not select an auth type.
ExRaiseStatus(STATUS_INVALID_PARAMETER);
}
}
switch(*pAuthInternalType)
{
case HttpAuthTypeBasic:
//
// Basic Authorization header format:
//
// "[Proxy-]Authorization: Basic username:password\r\n"
//
// Where, the substring username:password is Base64 encoded.
//
// AuthHeaderLength is already initialized to account for
// "[Proxy-]Authorization: \r\n". Account space for the
// remaining fields.
//
AuthHeaderLength += HTTP_AUTH_BASIC_LENGTH + 1; // 1 for SP
BinaryLength = pAuth->UserNameLength / sizeof(WCHAR)
+ pAuth->PasswordLength / sizeof(WCHAR)
+ 1;
//
// BinaryLength contains the bytes required to store
// unencoded "username:password" string. Find the number of
// bytes required when this string is Base64 encoded.
//
Status = BinaryToBase64Length(BinaryLength,
&Base64Length);
// No arithmetic overflow can occur here.
ASSERT(Status == STATUS_SUCCESS);
// In auth header, username:password is base64 encoded.
AuthHeaderLength += Base64Length;
//
// Internally, we store more that just the auth header.
// We create a UC_HTTP_AUTH structure and store the
// auth header, username (in WCHAR), password (in WCHAR),
// domain (in WCHAR), unencoded username:password in it.
//
*AuthInternalLength = AuthHeaderLength;
*AuthInternalLength += sizeof(UC_HTTP_AUTH)
+ pAuth->UserNameLength
+ pAuth->PasswordLength
+ pAuth->DomainLength
+ BinaryLength;
break;
case HttpAuthTypeDigest:
AuthHeaderLength +=
(HTTP_AUTH_DIGEST_LENGTH
+ 1 // for SP
+ SSPI_MAX_TOKEN_SIZE(HttpAuthTypeDigest));
*AuthInternalLength += sizeof(UC_HTTP_AUTH) +
pAuth->UserNameLength +
pAuth->PasswordLength +
pAuth->DomainLength +
pAuth->HeaderValueLength +
1; // For a '\0'
break;
case HttpAuthTypeNTLM:
case HttpAuthTypeNegotiate:
case HttpAuthTypeKerberos:
MaxTokenSize = HttpAuthScheme[*pAuthInternalType].SspiMaxTokenSize;
Status = BinaryToBase64Length(MaxTokenSize,
&Base64Length);
// If there was an overflow, return now.
if (!NT_SUCCESS(Status))
{
ExRaiseStatus(Status);
}
//
// Header format:
//
// [Proxy-]Authorization: SchemeName SP AuthBlob
//
// where AuthBlob is Base64 encoded.
//
AuthHeaderLength += HttpAuthScheme[*pAuthInternalType].NameLength
+ 1 // for SP
+ Base64Length;
//
// In the internal structure, we store the blob returned from
// the server in a challenge.
//
// N.B. The server's challenge blob is Base64 encoded.
// We convert the challenge blob into binary before
// storing the blob in the internal structure.
//
*AuthInternalLength = sizeof(UC_HTTP_AUTH)
+ pAuth->UserNameLength
+ pAuth->PasswordLength
+ pAuth->DomainLength
+ MaxTokenSize;
break;
default:
ExRaiseStatus(STATUS_INVALID_PARAMETER);
break;
}
return AuthHeaderLength;
}
/**************************************************************************++
Routine Description:
This routine generate the preauth header in a buffer.
Arguments:
pKeRequest - Supplies the internal request structure.
pInternalAuth - Supplied the internal authentication structure.
HeaderId - Suplies the header that will be generated.
pMethod - Supplies the request method.
MethodLength - Supplied the length of the method in bytes.
pBuffer - Supplies a pointer to the output buffer where the
header will be generated.
BufferLength - Supplies the length of the output buffer in bytes.
BytesTaken - Returns the number of bytes consumed from the output
buffer.
Return Value:
NTSTATUS.
--**************************************************************************/
NTSTATUS
UcpGeneratePreAuthHeader(
IN PUC_HTTP_REQUEST pKeRequest,
IN PUC_HTTP_AUTH pInternalAuth,
IN HTTP_HEADER_ID HeaderId,
IN PSTR pMethod,
IN ULONG MethodLength,
IN PUCHAR pBuffer,
IN ULONG BufferLength,
OUT PULONG pBytesTaken
)
{
NTSTATUS Status;
//
// Sanity check.
//
ASSERT(pInternalAuth);
ASSERT(UC_IS_VALID_HTTP_REQUEST(pKeRequest));
ASSERT(pMethod && MethodLength);
ASSERT(pBuffer && BufferLength);
ASSERT(pBytesTaken);
switch (pInternalAuth->Credentials.AuthType)
{
case HttpAuthTypeBasic:
{
PUCHAR pOrigBuffer = pBuffer;
//
// First generate header name then copy the header value.
//
// In case of Basic authentication, the encoded buffer contains
// the header value in the format
//
// Basic <username:password>CRLF
//
// where <username:password> is base64 encoded.
//
// Is there enought space to copy this header?
if (UC_HEADER_NAME_SP_LENGTH(HeaderId)
+ pInternalAuth->Basic.EncodedBufferLength
> BufferLength)
{
Status = STATUS_BUFFER_TOO_SMALL;
}
else
{
UC_COPY_HEADER_NAME_SP(pBuffer, HeaderId);
RtlCopyMemory(pBuffer,
pInternalAuth->Basic.pEncodedBuffer,
pInternalAuth->Basic.EncodedBufferLength);
pBuffer += pInternalAuth->Basic.EncodedBufferLength;
ASSERT(pBuffer <= pOrigBuffer + BufferLength);
*pBytesTaken = (ULONG)(pBuffer - pOrigBuffer);
Status = STATUS_SUCCESS;
}
break;
}
case HttpAuthTypeDigest:
Status = UcpGenerateDigestPreAuthHeader(HeaderId,
&pInternalAuth->hContext,
pKeRequest->pUri,
pKeRequest->UriLength,
pMethod,
MethodLength,
pBuffer,
BufferLength,
pBytesTaken);
break;
default:
Status = STATUS_NOT_SUPPORTED;
break;
}
return Status;
}
ULONG
_WideCharToMultiByte(
ULONG uCodePage,
ULONG dwFlags,
PCWSTR lpWideCharStr,
int cchWideChar,
PSTR lpMultiByteStr,
int cchMultiByte,
PCSTR lpDefaultChar,
PBOOLEAN lpfUsedDefaultChar
)
{
int i;
UNREFERENCED_PARAMETER(uCodePage);
UNREFERENCED_PARAMETER(dwFlags);
UNREFERENCED_PARAMETER(cchMultiByte);
UNREFERENCED_PARAMETER(lpfUsedDefaultChar);
//
// simply strip the upper byte, it's supposed to be ascii already
//
for (i = 0; i < cchWideChar; ++i)
{
if ((lpWideCharStr[i] & 0xff00) != 0 || IS_HTTP_CTL(lpWideCharStr[i]))
{
lpMultiByteStr[0] = *lpDefaultChar;
}
else
{
lpMultiByteStr[0] = (UCHAR)(lpWideCharStr[i]);
}
lpMultiByteStr += 1;
}
return (ULONG)(i);
} // _WideCharToMultiByte
/***************************************************************************++
Routine Description:
Generates the authorization header for Basic.
Arguments:
pAuth - The HTTP_AUTH_CREDENTIALS structure.
pInternalAuth - Pointer to our internal auth structure.
Return Value:
STATUS_SUCCESS
--***************************************************************************/
NTSTATUS
UcpGenerateBasicHeader(
IN PHTTP_AUTH_CREDENTIALS pAuth,
IN PUC_HTTP_AUTH pInternalAuth
)
{
NTSTATUS Status;
ULONG BytesCopied;
PUCHAR pCurr;
PUCHAR pHeader, pBeginHeader;
ULONG CurrLen;
PUCHAR pScratchBuffer = pInternalAuth->Basic.pEncodedBuffer;
ULONG ScratchBufferSize = pInternalAuth->Basic.EncodedBufferLength;
//
// Temporary EncodedBuffer contains the following:
//
// +--------------------------------------------------------------+
// | username:password | Basic SP Base64(username:password) CRLF |
// +--------------------------------------------------------------+
//
CurrLen = ScratchBufferSize;
pCurr = pScratchBuffer;
//
// Make sure the buffer has space to contain username:password.
//
if (pAuth->UserNameLength/sizeof(WCHAR) // Ansi Username
+ 1 // ':' char
+ pAuth->PasswordLength/sizeof(WCHAR) > CurrLen) // Ansi password
{
ASSERT(FALSE);
return STATUS_BUFFER_TOO_SMALL;
}
// Copy username.
BytesCopied = _WideCharToMultiByte(
0,
0,
pAuth->pUserName,
pAuth->UserNameLength / sizeof(WCHAR),
(PSTR)pCurr,
CurrLen,
&DefaultChar,
NULL
);
ASSERT(BytesCopied <= CurrLen);
pCurr += BytesCopied;
CurrLen -= BytesCopied;
// Copy ':'.
*pCurr++ = ':';
CurrLen--;
// Copy password.
BytesCopied = _WideCharToMultiByte(
0,
0,
pAuth->pPassword,
pAuth->PasswordLength / sizeof(WCHAR),
(PSTR)pCurr,
CurrLen,
&DefaultChar,
NULL
);
ASSERT(BytesCopied <= CurrLen);
pCurr += BytesCopied;
CurrLen -= BytesCopied;
//
// Now, start generating the auth header.
//
pHeader = pBeginHeader = pCurr;
// The buffer must have space to hold "Basic" string and a SP char.
if (HTTP_AUTH_BASIC_LENGTH + 1 > CurrLen)
{
ASSERT(FALSE);
return STATUS_BUFFER_TOO_SMALL;
}
RtlCopyMemory(pHeader, HTTP_AUTH_BASIC, HTTP_AUTH_BASIC_LENGTH);
pHeader += HTTP_AUTH_BASIC_LENGTH;
CurrLen -= HTTP_AUTH_BASIC_LENGTH;
// Copy SP char.
*pHeader++ = SP;
CurrLen--;
//
// Generate base64 encoding of usename:password.
//
Status = BinaryToBase64(pScratchBuffer,
(ULONG)(pBeginHeader - pScratchBuffer),
pHeader,
CurrLen,
&BytesCopied);
if (!NT_SUCCESS(Status))
{
ASSERT(FALSE);
return Status;
}
ASSERT(BytesCopied <= CurrLen);
pHeader += BytesCopied;
CurrLen -= BytesCopied;
//
// Add a CRLF.
//
if (CRLF_SIZE > CurrLen)
{
ASSERT(FALSE);
return STATUS_BUFFER_TOO_SMALL;
}
*((UNALIGNED64 USHORT *)pHeader) = CRLF;
pHeader += CRLF_SIZE;
CurrLen -= CRLF_SIZE;
//
// Now, overwrite the scratch buffer with the generated buffer.
//
pInternalAuth->Basic.EncodedBufferLength = (ULONG)(pHeader - pBeginHeader);
RtlMoveMemory(pInternalAuth->Basic.pEncodedBuffer,
pBeginHeader,
pInternalAuth->Basic.EncodedBufferLength);
return STATUS_SUCCESS;
}
/***************************************************************************++
Routine Description:
Generates the authorization header for Digest.
Arguments:
pInternalAuth - Pointer to our internal auth structure.
pAuth - The HTTP_AUTH_CREDENTIALS structure.
pRequest - Internal request.
pMethod - The method (GET, POST, etc).
MethodLength - Length of the method.
pOutBuffer - Pointer to the input/output buffer.
HeaderID - HttpHeaderAuthorization or
HttpHeaderProxyAuthorization
Return Value:
STATUS_SUCCESS
--***************************************************************************/
NTSTATUS
UcpGenerateDigestAuthHeader(
IN PUC_HTTP_AUTH pInternalAuth,
IN HTTP_HEADER_ID HeaderID,
IN PSTR pMethod,
IN ULONG MethodLength,
IN PSTR pUri,
IN ULONG UriLength,
IN PUCHAR pOutBuffer,
IN ULONG OutBufferLen,
OUT PULONG pOutBytesTaken
)
{
NTSTATUS Status;
SECURITY_STATUS SecStatus;
ULONG ContextFlagsUsed;
TimeStamp LifeTime;
SecBufferDesc InputBuffers;
SecBufferDesc OutputBuffers;
SecBuffer InputTokens[6];
SecBuffer OutputTokens[6];
SECURITY_STRING Uri;
PUCHAR pOutput = pOutBuffer;
PCHAR pUnicodeUri = NULL;
ULONG UnicodeUriLength;
LONG CharsTaken;
// Sanity check
PAGED_CODE();
ASSERT(pInternalAuth);
ASSERT(pUri && UriLength);
ASSERT(pMethod && MethodLength);
ASSERT(pOutBuffer && OutBufferLen);
ASSERT(pOutBytesTaken);
// Initialize output arguments.
*pOutBytesTaken = 0;
// See if WDigest is supported in kernel
if (!HttpAuthScheme[HttpAuthTypeDigest].bSupported)
{
return STATUS_NOT_SUPPORTED;
}
// Do we need to get credentials handle?
if (pInternalAuth->bValidCredHandle == FALSE)
{
Status = UcpAcquireClientCredentialsHandle(
HTTP_AUTH_WDIGEST_W,
HTTP_AUTH_WDIGEST_W_LENGTH,
&pInternalAuth->Credentials,
&pInternalAuth->hCredentials
);
if (!NT_SUCCESS(Status))
{
goto quit;
}
pInternalAuth->bValidCredHandle = TRUE;
}
//
// Digest header format:
// [Proxy-]Authorization: Digest SP Auth_Data CRLF
//
// Make sure the output buffer has the space to hold all of the above.
//
if (UC_HEADER_NAME_SP_LENGTH(HeaderID) // Header name
+ HTTP_AUTH_DIGEST_LENGTH // "Digest" string
+ 1 // SP char
+ SSPI_MAX_TOKEN_SIZE(HttpAuthTypeDigest) // Auth_Data
+ CRLF_SIZE > OutBufferLen) // CRLF
{
Status = STATUS_BUFFER_TOO_SMALL;
goto quit;
}
// Copy header name
UC_COPY_HEADER_NAME_SP(pOutput, HeaderID);
// Copy "Digest" string
RtlCopyMemory(pOutput, HTTP_AUTH_DIGEST, HTTP_AUTH_DIGEST_LENGTH);
pOutput += HTTP_AUTH_DIGEST_LENGTH;
// Followed by a space char
*pOutput++ = SP;
//
// In the worst case, UTF8ToUnicode conversion will take maximum of
// double the UTF8 string size.
//
UnicodeUriLength = UriLength * sizeof(WCHAR);
pUnicodeUri = UL_ALLOCATE_POOL(PagedPool,
UnicodeUriLength,
UC_SSPI_POOL_TAG);
if (pUnicodeUri == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto quit;
}
ASSERT(UriLength <= ANSI_STRING_MAX_CHAR_LEN);
CharsTaken = (LONG)UriLength;
Status = HttpUTF8ToUnicode(pUri,
UriLength,
(PWSTR)pUnicodeUri,
&CharsTaken,
TRUE);
// Because the UTF8 Uri was generated by us, it had better be correct.
ASSERT(CharsTaken <= (LONG)UriLength);
ASSERT(Status == STATUS_SUCCESS);
// Prepare Unicode Uri
Uri.Buffer = (PWSTR)pUnicodeUri;
Uri.Length = (USHORT)CharsTaken * sizeof(WCHAR);
Uri.MaximumLength = Uri.Length;
// Prepare Input buffers
InputBuffers.ulVersion = SECBUFFER_VERSION;
InputBuffers.cBuffers = 3;
InputBuffers.pBuffers = InputTokens;
//
// WWW-Authenticate header value
//
ASSERT(pInternalAuth->AuthSchemeInfo.Length >= HTTP_AUTH_DIGEST_LENGTH);
ASSERT(pInternalAuth->AuthSchemeInfo.pScheme);
ASSERT(_strnicmp(pInternalAuth->AuthSchemeInfo.pScheme,
HTTP_AUTH_DIGEST,
HTTP_AUTH_DIGEST_LENGTH) == 0);
InputTokens[0].BufferType = SECBUFFER_TOKEN;
InputTokens[0].cbBuffer = pInternalAuth->AuthSchemeInfo.Length -
HTTP_AUTH_DIGEST_LENGTH;
InputTokens[0].pvBuffer = (PUCHAR)pInternalAuth->AuthSchemeInfo.pScheme +
HTTP_AUTH_DIGEST_LENGTH;
// HTTP Method
InputTokens[1].BufferType = SECBUFFER_PKG_PARAMS;
InputTokens[1].cbBuffer = MethodLength;
InputTokens[1].pvBuffer = pMethod;
// Entity
InputTokens[2].BufferType = SECBUFFER_PKG_PARAMS;
InputTokens[2].cbBuffer = 0;
InputTokens[2].pvBuffer = NULL;
OutputBuffers.ulVersion = SECBUFFER_VERSION;
OutputBuffers.cBuffers = 1;
OutputBuffers.pBuffers = OutputTokens;
OutputTokens[0].BufferType = SECBUFFER_TOKEN;
OutputTokens[0].cbBuffer = SSPI_MAX_TOKEN_SIZE(HttpAuthTypeDigest);
OutputTokens[0].pvBuffer = pOutput;
// We must have a valid credentials handle at this point
ASSERT(pInternalAuth->bValidCredHandle);
// Call SSP
SecStatus = STATUS_SUCCESS;
__try
{
SecStatus = InitializeSecurityContextW(
&pInternalAuth->hCredentials,
NULL,
&Uri,
ISC_REQ_REPLAY_DETECT | ISC_REQ_CONNECTION,
0,
SECURITY_NATIVE_DREP,
&InputBuffers,
0,
&pInternalAuth->hContext,
&OutputBuffers,
&ContextFlagsUsed,
&LifeTime);
}
__except (UL_EXCEPTION_FILTER())
{
SecStatus = GetExceptionCode();
}
Status = SecStatusToNtStatus(SecStatus);
if (!NT_SUCCESS(Status))
{
goto quit;
}
// pInternalAuth has a valid Context handle that must be freed eventually.
pInternalAuth->bValidCtxtHandle = TRUE;
// advance the pointer by the amount used
pOutput += OutputTokens[0].cbBuffer;
*((UNALIGNED64 USHORT *)pOutput) = CRLF;
pOutput += CRLF_SIZE;
ASSERT(pOutput <= pOutBuffer + OutBufferLen);
*pOutBytesTaken = (ULONG)(pOutput - pOutBuffer);
quit:
if (pUnicodeUri)
{
UL_FREE_POOL(pUnicodeUri, UC_SSPI_POOL_TAG);
}
return Status;
}
/***************************************************************************++
Routine Description:
Generates the preemptive authorization header for Digest.
Assumes that there is at least SSPI_MAX_TOKEN_SIZE(HttpAuthTypeDigest)
space in OutBuffer provided by the caller.
Arguments:
Return Value:
--***************************************************************************/
NTSTATUS
UcpGenerateDigestPreAuthHeader(
IN HTTP_HEADER_ID HeaderID,
IN PCtxtHandle phClientContext,
IN PSTR pUri,
IN ULONG UriLength,
IN PSTR pMethod,
IN ULONG MethodLength,
IN PUCHAR pOutBuffer,
IN ULONG OutBufferLen,
OUT PULONG pOutBytesTaken
)
{
NTSTATUS Status;
SECURITY_STATUS SecStatus;
SecBufferDesc InputBuffers;
SecBuffer InputTokens[5];
PUCHAR pOutput = pOutBuffer;
// Sanity check
PAGED_CODE();
ASSERT(pUri && UriLength);
ASSERT(pMethod && MethodLength);
ASSERT(pOutBuffer && *pOutBuffer);
// See if WDigest is supported in kernel.
if (!HttpAuthScheme[HttpAuthTypeDigest].bSupported)
{
// Nope!
return STATUS_NOT_SUPPORTED;
}
//
// Make sure that the output buffer has space to contain the following
// header:
// [Proxy-]Authorization: Digest SP Auth_Data CRLF
//
if (UC_HEADER_NAME_SP_LENGTH(HeaderID)
+ HTTP_AUTH_DIGEST_LENGTH
+ 1
+ SSPI_MAX_TOKEN_SIZE(HttpAuthTypeDigest)
+ CRLF_SIZE > OutBufferLen)
{
ASSERT(FALSE);
return STATUS_BUFFER_TOO_SMALL;
}
// Copy the header name followed by a ':' to the output
UC_COPY_HEADER_NAME_SP(pOutput, HeaderID);
// Copy "Digest" string to the output
RtlCopyMemory(pOutput, HTTP_AUTH_DIGEST, HTTP_AUTH_DIGEST_LENGTH);
pOutput += HTTP_AUTH_DIGEST_LENGTH;
// Copy a SPACE
*pOutput++ = SP;
//
// Prepare SSPI input buffers
//
InputBuffers.ulVersion = SECBUFFER_VERSION;
InputBuffers.cBuffers = 5;
InputBuffers.pBuffers = InputTokens;
// No Challenge!
InputTokens[0].BufferType = SECBUFFER_TOKEN;
InputTokens[0].cbBuffer = 0;
InputTokens[0].pvBuffer = NULL;
// HTTP Method
InputTokens[1].BufferType = SECBUFFER_PKG_PARAMS;
InputTokens[1].cbBuffer = MethodLength;
InputTokens[1].pvBuffer = pMethod;
// Uri/Realm
InputTokens[2].BufferType = SECBUFFER_PKG_PARAMS;
InputTokens[2].cbBuffer = UriLength;
InputTokens[2].pvBuffer = pUri;
// Entity
InputTokens[3].BufferType = SECBUFFER_PKG_PARAMS;
InputTokens[3].cbBuffer = 0;
InputTokens[3].pvBuffer = NULL;
// Output
InputTokens[4].BufferType = SECBUFFER_PKG_PARAMS;
InputTokens[4].cbBuffer = SSPI_MAX_TOKEN_SIZE(HttpAuthTypeDigest);
InputTokens[4].pvBuffer = pOutput;
SecStatus = MakeSignature(phClientContext, // Handle to security context
0, // QOP
&InputBuffers, // SecBuffers
0); // sequence # (always 0)
Status = SecStatusToNtStatus(SecStatus);
if (!NT_SUCCESS(Status))
{
goto quit;
}
// Advance the pOutput by the output written by SSPI
pOutput += InputTokens[4].cbBuffer;
// Write "\r\n"
*((UNALIGNED64 USHORT *)pOutput) = CRLF;
pOutput += CRLF_SIZE;
ASSERT(pOutput <= pOutBuffer + OutBufferLen);
// Return the bytes taken.
*pOutBytesTaken = (ULONG)(pOutput - pOutBuffer);
quit:
return Status;
}
/***************************************************************************++
Routine Description:
Generates the Authorization header for NTLM, Kerberos & Negotiate.
Arguments:
bServer - Whether to use server for InitializeSecurityContext
pSchemeName - NTLM or Kerberos or Negotiate. Used for generating the
Authorization header
SchemeLength - Length of scheme
pSchemeNameW - Wide char format of scheme name. Used for calling SSPI
which expects wide char names.
SchemeLength - Length of widechar scheme
pOutBuffer - Output buffer. On return contains the output buffer that
is offset by the amount written.
pAuth - HTTP_AUTH_CREDENTIALS.
pInternalAuth - Pointer to internal Auth structure.
HeaderID - HttpHeaderAuthorization or HttpHeaderProxyAuthrorization
Return Value:
STATUS_NOT_SUPPORTED - Invalid Auth scheme
SEC_STATUS - Security SSPI status.
--***************************************************************************/
NTSTATUS
UcpGenerateSSPIAuthHeader(
IN PUC_PROCESS_SERVER_INFORMATION pServInfo,
IN PUC_HTTP_AUTH pInternalAuth,
IN HTTP_HEADER_ID HeaderID,
IN PUCHAR pOutBuffer,
IN ULONG OutBufferLen,
OUT PULONG pOutBytesTaken,
OUT PBOOLEAN bRenegotiate
)
{
PUCHAR pBuffer = pOutBuffer;
PUCHAR pBeginning = pOutBuffer;
ULONG BufferLen = OutBufferLen;
ULONG BytesTaken;
NTSTATUS Status;
HTTP_AUTH_TYPE AuthType;
// Sanity check
PAGED_CODE();
AuthType = pInternalAuth->Credentials.AuthType;
// Sanity check.
ASSERT(AuthType == HttpAuthTypeNTLM ||
AuthType == HttpAuthTypeKerberos ||
AuthType == HttpAuthTypeNegotiate);
//
// First, see if the scheme is supported.
//
if(!HttpAuthScheme[AuthType].bSupported)
{
return STATUS_NOT_SUPPORTED;
}
//
// Must have space to store Header name, auth scheme name, SP char.
//
if (UC_HEADER_NAME_SP_LENGTH(HeaderID)
+ HttpAuthScheme[AuthType].NameLength
+ 1 > BufferLen)
{
ASSERT(FALSE);
return STATUS_BUFFER_TOO_SMALL;
}
//
// Write out the Authorization: header.
//
UC_COPY_HEADER_NAME_SP(pBuffer, HeaderID);
BufferLen -= UC_HEADER_NAME_SP_LENGTH(HeaderID);
//
// Write the auth scheme name.
//
RtlCopyMemory(pBuffer,
HttpAuthScheme[AuthType].Name,
HttpAuthScheme[AuthType].NameLength);
pBuffer += HttpAuthScheme[AuthType].NameLength;
BufferLen -= HttpAuthScheme[AuthType].NameLength;
//
// Add a SP char.
//
*pBuffer++ = ' ';
BufferLen--;
//
// Generate Auth blob.
//
// Remember the auth blob pointer.
pInternalAuth->pRequestAuthBlob = (PUCHAR) pBuffer;
// BlobMaxLength = {Auth header max length} - {"Header-name:SP" length}
pInternalAuth->RequestAuthBlobMaxLength =
pInternalAuth->RequestAuthHeaderMaxLength
- (ULONG)(pBuffer - pBeginning);
Status = UcpGenerateSSPIAuthBlob(pServInfo,
pInternalAuth,
pBuffer,
BufferLen,
&BytesTaken,
bRenegotiate);
if (NT_SUCCESS(Status))
{
ASSERT(BytesTaken <= BufferLen);
pBuffer += BytesTaken;
BufferLen -= BytesTaken;
}
if (CRLF_SIZE > BufferLen)
{
ASSERT(FALSE);
return STATUS_BUFFER_TOO_SMALL;
}
*((UNALIGNED64 USHORT *)pBuffer) = CRLF;
pBuffer += CRLF_SIZE;
ASSERT(pBuffer <= pOutBuffer + OutBufferLen);
*pOutBytesTaken = (ULONG)(pBuffer - pOutBuffer);
return Status;
}
/***************************************************************************++
Routine Description:
Computes & Generates the Authorization header
Arguments:
pServerInfo - The servinfo structure
pRequest - Internal HTTP request
pAuth - The Auth config object
pInternalAuth - pointer to internal auth
AuthInternalLength - Length of Internal Auth
pBuffer - Output buffer
HeaderId - HttpHeaderAuthorization or HttpHeaderProxyAuth
pMethod - The method (required for Digest)
MethodLength - Method Length (required for Digest)
pOutBuffer - Output buffer (after generating headers)
Return Value:
--***************************************************************************/
NTSTATUS
UcGenerateAuthHeaderFromCredentials(
IN PUC_PROCESS_SERVER_INFORMATION pServerInfo,
IN PUC_HTTP_AUTH pInternalAuth,
IN HTTP_HEADER_ID HeaderId,
IN PSTR pMethod,
IN ULONG MethodLength,
IN PSTR pUri,
IN ULONG UriLength,
IN PUCHAR pOutBuffer,
IN ULONG OutBufferLen,
OUT PULONG pBytesTaken,
OUT PBOOLEAN bDontFreeMdls
)
{
PHTTP_AUTH_CREDENTIALS pAuth;
NTSTATUS Status = STATUS_SUCCESS;
// Sanity check
PAGED_CODE();
pAuth = &pInternalAuth->Credentials;
*pBytesTaken = 0;
*bDontFreeMdls = FALSE;
switch(pAuth->AuthType)
{
case HttpAuthTypeBasic:
{
PUCHAR pBuffer = pOutBuffer;
Status = UcpGenerateBasicHeader(pAuth,
pInternalAuth);
//
// Copy it out.
//
UC_COPY_HEADER_NAME_SP(pBuffer, HeaderId);
RtlCopyMemory(pBuffer,
pInternalAuth->Basic.pEncodedBuffer,
pInternalAuth->Basic.EncodedBufferLength);
pBuffer += pInternalAuth->Basic.EncodedBufferLength;
ASSERT(pBuffer <= pOutBuffer + OutBufferLen);
*pBytesTaken = (ULONG)(pBuffer - pOutBuffer);
break;
}
case HttpAuthTypeDigest:
Status = UcpGenerateDigestAuthHeader(
pInternalAuth,
HeaderId,
pMethod,
MethodLength,
pUri,
UriLength,
pOutBuffer,
OutBufferLen,
pBytesTaken
);
break;
case HttpAuthTypeNTLM:
case HttpAuthTypeNegotiate:
case HttpAuthTypeKerberos:
Status = UcpGenerateSSPIAuthHeader(
pServerInfo,
pInternalAuth,
HeaderId,
pOutBuffer,
OutBufferLen,
pBytesTaken,
bDontFreeMdls
);
break;
default:
ASSERT(FALSE);
break;
}
return Status;
}
/***************************************************************************++
Routine Description:
Computes & Generates the Authorization header
Arguments:
pRequest - Internal HTTP request
pBuffer - Output buffer
pMethod - The method (required for Digest)
MethodLength - Method Length (required for Digest)
Return Value:
--***************************************************************************/
NTSTATUS
UcGenerateProxyAuthHeaderFromCache(
IN PUC_HTTP_REQUEST pKeRequest,
IN PSTR pMethod,
IN ULONG MethodLength,
IN PUCHAR pBuffer,
IN ULONG BufferLength,
OUT PULONG pBytesTaken
)
{
PUC_HTTP_AUTH pInternalAuth;
NTSTATUS Status;
//
// See if PreAuth is enabled. We cannot check for the
// pServerInfo->PreAuth flag here. We check for this
// in the UcpComputeAuthHeaderSize function. If we check
// for this here, we cannot be sure that this flag was
// set when we called UcpComputeAuthHeaderSize
//
UlAcquirePushLockExclusive(&pKeRequest->pServerInfo->PushLock);
pInternalAuth = pKeRequest->pServerInfo->pProxyAuthInfo;
Status = STATUS_SUCCESS;
if(pInternalAuth)
{
Status = UcpGeneratePreAuthHeader(pKeRequest,
pInternalAuth,
HttpHeaderProxyAuthorization,
pMethod,
MethodLength,
pBuffer,
BufferLength,
pBytesTaken);
if (NT_SUCCESS(Status))
{
ASSERT(*pBytesTaken <= BufferLength);
pBuffer += *pBytesTaken;
}
}
UlReleasePushLock(&pKeRequest->pServerInfo->PushLock);
return Status;
}
/***************************************************************************++
Routine Description:
Used to update the Authorization header for NTLM or Kerberos or Negotiate
This is called when we re-issue the request to complete the challenge-
response handshake.
Arguments:
pRequest - Internal HTTP request.
bServer - Whether to pass ServerName when calling SSPI.
pScheme - Scheme to pass to SSPI
SchemeLength - SchemeLength to pass to SSPI
pAuth - Internal AUTH structure.
Return Value:
NTSTATUS - SSPI return status.
--***************************************************************************/
NTSTATUS
UcpUpdateSSPIAuthHeader(
IN PUC_HTTP_REQUEST pRequest,
IN PUC_HTTP_AUTH pAuth,
OUT PBOOLEAN bRenegotiate
)
{
PUCHAR pBuffer;
NTSTATUS Status;
PMDL pMdl;
ULONG BufferLen;
ULONG BytesWritten;
// Sanity check
PAGED_CODE();
ASSERT(pRequest);
ASSERT(pAuth);
ASSERT(bRenegotiate);
//
// First adjust the HeaderLength.
//
pRequest->BytesBuffered -= pRequest->HeaderLength;
//
// Call SSPI to get the new blob, based on the old one.
//
pBuffer = pAuth->pRequestAuthBlob;
BufferLen = pAuth->RequestAuthBlobMaxLength;
Status = UcpGenerateSSPIAuthBlob(pRequest->pServerInfo,
pAuth,
pBuffer,
BufferLen,
&BytesWritten,
bRenegotiate);
if(!NT_SUCCESS(Status))
{
return Status;
}
ASSERT(BytesWritten <= BufferLen);
pBuffer += BytesWritten;
BufferLen -= BytesWritten;
*((UNALIGNED64 USHORT *)pBuffer) = CRLF;
pBuffer += CRLF_SIZE;
pRequest->HeaderLength = (ULONG)(pBuffer - pRequest->pHeaders);
//
// If there was a content length at the end of the headers,
// re-generate it.
//
if(pRequest->RequestFlags.ContentLengthLast)
{
Status = UcGenerateContentLength(pRequest->BytesBuffered,
pRequest->pHeaders
+ pRequest->HeaderLength,
pRequest->MaxHeaderLength
- pRequest->HeaderLength,
&BytesWritten);
ASSERT(Status == STATUS_SUCCESS);
pRequest->HeaderLength += BytesWritten;
}
//
// Terminate headers with CRLF.
//
((UNALIGNED64 USHORT *)(pRequest->pHeaders +
pRequest->HeaderLength))[0] = CRLF;
pRequest->HeaderLength += CRLF_SIZE;
pRequest->BytesBuffered += pRequest->HeaderLength;
//
// Build a MDL for the headers. Free the old one & re-allocate a
// new one.
//
pMdl = UlAllocateMdl(
pRequest->pHeaders, // VA
pRequest->HeaderLength, // Length
FALSE, // Secondary Buffer
FALSE, // Charge Quota
NULL // IRP
);
if(!pMdl)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
else
{
MmBuildMdlForNonPagedPool(pMdl);
}
pMdl->Next = pRequest->pMdlHead->Next;
ASSERT(!IS_MDL_LOCKED(pRequest->pMdlHead));
UlFreeMdl(pRequest->pMdlHead);
pRequest->pMdlHead = pMdl;
return STATUS_SUCCESS;
}
/***************************************************************************++
Routine Description:
Used to update the Authorization header. For Digest, this is called when
we compute the entity hash. For NTLM/Kerberos/Negotiate, this is called
from the Re-issue request worker thread.
Arguments:
pRequest - Internal HTTP request.
pAuth - Internal Auth structure (could be for auth or proxy-auth)
bRenegotiate - Returns whether further renegotiation is needed
Return Value:
STATUS_NOT_SUPPORTED - Invalid auth scheme
STATUS_SUCCESS - success.
SEC_STATUS_* - Status returned from SSPI.
--***************************************************************************/
NTSTATUS
UcUpdateAuthorizationHeader(
IN PUC_HTTP_REQUEST pRequest,
IN PUC_HTTP_AUTH pAuth,
OUT PBOOLEAN bRenegotiate
)
{
NTSTATUS Status;
// Sanity check
PAGED_CODE();
ASSERT(pRequest);
ASSERT(pAuth);
ASSERT(bRenegotiate);
*bRenegotiate = FALSE;
if (pAuth->AuthInternalLength == 0)
{
return STATUS_SUCCESS;
}
switch(pAuth->Credentials.AuthType)
{
case HttpAuthTypeNTLM:
case HttpAuthTypeKerberos:
case HttpAuthTypeNegotiate:
Status = UcpUpdateSSPIAuthHeader(pRequest,
pAuth,
bRenegotiate);
break;
default:
Status = STATUS_NOT_SUPPORTED;
break;
}
return Status;
}
/***************************************************************************++
Routine Description:
Do a longest prefix matching search on the URI auth cache. This routine
is used to do pre-authentication. An entry will be used only if it's been
marked valid.
Arguments:
pServInfo : Pointer to the per-process serv info structure.
pInputURI : The Input URI
pOutBuffer: The buffer to which the Authorization header should be written.
Return Value:
STATUS_SUCCESS - Found a valid entry in the URI auth cache and wrote a
Authorization header
STATUS_NOT_FOUND - Did not write the authorization header as no matching
entry was found.
--***************************************************************************/
NTSTATUS
UcFindURIEntry(
IN PUC_PROCESS_SERVER_INFORMATION pServInfo,
IN PSTR pUri,
IN PUC_HTTP_REQUEST pRequest,
IN PSTR pMethod,
IN ULONG MethodLength,
IN PUCHAR pBuffer,
IN ULONG BufferSize,
OUT PULONG pBytesTaken
)
{
PUC_HTTP_AUTH_CACHE pAuth;
PLIST_ENTRY pCurrent;
NTSTATUS Status;
PUCHAR pStart = pBuffer;
ULONG BytesTaken;
PAGED_CODE();
*pBytesTaken = 0;
UlAcquirePushLockExclusive(&pServInfo->PushLock);
pCurrent = pServInfo->pAuthListHead.Flink;
while(pCurrent != &pServInfo->pAuthListHead)
{
// Retrieve the auth structure from the list entry
pAuth = CONTAINING_RECORD(
pCurrent,
UC_HTTP_AUTH_CACHE,
Linkage
);
ASSERT(UC_IS_VALID_AUTH_CACHE(pAuth));
if(pAuth->Valid == TRUE &&
UcpUriCompareLongest(pUri, pAuth->pUri) != 0)
{
// Yes, we found a matching URI. This URI is also the best match
// because we store these elements in a ordered list, largest
// one first.
PUC_HTTP_AUTH pInternalAuth = (PUC_HTTP_AUTH) pAuth->pAuthBlob;
ASSERT(UC_IS_VALID_HTTP_REQUEST(pRequest));
Status = UcpGeneratePreAuthHeader(pRequest,
pInternalAuth,
HttpHeaderAuthorization,
pMethod,
MethodLength,
pBuffer,
BufferSize,
&BytesTaken);
if (NT_SUCCESS(Status))
{
ASSERT(BytesTaken <= BufferSize);
pBuffer += BytesTaken;
}
UlReleasePushLock(&pServInfo->PushLock);
ASSERT(pBuffer <= pStart + BufferSize);
*pBytesTaken = (ULONG)(pBuffer-pStart);
UlTrace(AUTH_CACHE,
("[UcFindURIEntry]: Found matching URI (%s:%s) Valid %d\n",
pAuth->pUri,
pAuth->pRealm,
pAuth->Valid)
);
return Status;
}
pCurrent = pCurrent->Flink;
}
UlReleasePushLock(&pServInfo->PushLock);
UlTrace(AUTH_CACHE,
("[UcFindURIEntry]: No match for URI (%s) \n", pUri));
return STATUS_NOT_FOUND;
}
VOID
UcDeleteURIEntry(
IN PUC_PROCESS_SERVER_INFORMATION pInfo,
IN PUC_HTTP_AUTH_CACHE pAuth
)
{
PLIST_ENTRY pCurrent;
PUC_HTTP_AUTH_CACHE pDependAuth;
if(pAuth->pDependParent)
{
pAuth = pAuth->pDependParent;
}
ASSERT(pAuth->pDependParent == 0);
while(!IsListEmpty(&pAuth->pDigestUriList))
{
pCurrent = RemoveHeadList(&pAuth->pDigestUriList);
pDependAuth = CONTAINING_RECORD(
pCurrent,
UC_HTTP_AUTH_CACHE,
DigestLinkage
);
ASSERT(pDependAuth->pDependParent == pAuth);
ASSERT(pDependAuth->pAuthBlob == pAuth->pAuthBlob);
UlTrace(AUTH_CACHE,
("[UcDeleteURIEntry]: Deleting dependent entry for (%s:%s) \n",
pDependAuth->pUri,
pDependAuth->pRealm)
);
RemoveEntryList(&pDependAuth->Linkage);
//
// Depended structures don't have their own AuthBlobs.
//
UL_FREE_POOL_WITH_QUOTA(
pDependAuth,
UC_AUTH_CACHE_POOL_TAG,
NonPagedPool,
pDependAuth->AuthCacheSize,
pInfo->pProcess
);
}
if(pAuth->pAuthBlob)
{
UcDestroyInternalAuth(pAuth->pAuthBlob,
pInfo->pProcess);
}
UlTrace(AUTH_CACHE,
("[UcDeleteURIEntry]: Deleting entry for (%s:%s) \n",
pAuth->pUri,
pAuth->pRealm)
);
RemoveEntryList(&pAuth->Linkage);
UL_FREE_POOL_WITH_QUOTA(
pAuth,
UC_AUTH_CACHE_POOL_TAG,
NonPagedPool,
pAuth->AuthCacheSize,
pInfo->pProcess
);
}
/***************************************************************************++
Routine Description:
Add an entry to the URI cache. This is typically called from the first
401 that we see for a request.
Arguments:
pServInfo - A pointer to the per-process servinfo
pInputURI - The input URI
pInputRealm - The realm.
Return Value:
STATUS_SUCCESS - Inserted (or updated an old entry)
STATUS_INSUFFICIENT_RESOURCES - no memory.
--***************************************************************************/
NTSTATUS
UcAddURIEntry(
IN HTTP_AUTH_TYPE AuthType,
IN PUC_PROCESS_SERVER_INFORMATION pServInfo,
IN PCSTR pInputURI,
IN USHORT UriLength,
IN PCSTR pInputRealm,
IN ULONG RealmLength,
IN PCSTR pUriList,
IN ULONG UriListLength,
IN PUC_HTTP_AUTH pAuthBlob
)
{
PLIST_ENTRY pCurrent;
PUC_HTTP_AUTH_CACHE pDeleteBegin, pAuth, pTemp, pNew;
LONG Compare;
PCSTR pStart;
NTSTATUS Status = STATUS_SUCCESS;
UcpProcessUriForPreAuth((PSTR) pInputURI, &UriLength);
pDeleteBegin = pAuth = 0;
//
// Logically, we store the URI prefixes in a tree. We implement this tree
// as a length ordered list, with "longest" matches first. So, if the tree
// was
//
// www.foo.com/abc
// / \
// / \
// / \
// www.foo.com/abc/def www.foo.com/abc/xyz
//
// then the list would be
//
// www.foo.com/abc/xyz
// www.foo.com/abc/def
// www.foo.com/abc
//
UlAcquirePushLockExclusive(&pServInfo->PushLock);
//
// Scan the list in order and find the slot where we want to insert this
// URI entry.
//
for(pCurrent = pServInfo->pAuthListHead.Flink;
pCurrent != &pServInfo->pAuthListHead;
pCurrent = pCurrent->Flink)
{
// Retrieve the auth structure from the list entry
pAuth = CONTAINING_RECORD(
pCurrent,
UC_HTTP_AUTH_CACHE,
Linkage
);
ASSERT(UC_IS_VALID_AUTH_CACHE(pAuth));
Compare = UcpUriCompareExact(pInputURI, pAuth->pUri);
if(Compare == 0)
{
//
// We found another instance of the same URI in the cache.
// We'll keep things simple by nuking this from the cache
// and by adding it again.
//
UlTrace(AUTH_CACHE,
("[UcAddURIEntry]: Found existing entry for %s \n",
pInputURI));
UcDeleteURIEntry(pServInfo, pAuth);
//
// Resume the search from the beginning of the list. Potentially
// we have removed quite a few entries from the list, and we
// might not have the correct insert point.
//
// Note that we'll never get into an infinite loop, since we are
// do this only after we remove at least one entry. So at some
// point, the list is going to be empty.
//
pCurrent = pServInfo->pAuthListHead.Flink;
continue;
}
else if(Compare > 0)
{
//
// Input URI is greater than current entry. We have reached our
// insertion point.
break;
}
}
if((pTemp = UcpAllocateAuthCacheEntry(pServInfo,
AuthType,
UriLength,
RealmLength,
pInputURI,
pInputRealm,
pAuthBlob
)) == 0)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto quit;
}
ASSERT(pAuthBlob->RequestAuthHeaderMaxLength);
if(pServInfo->GreatestAuthHeaderMaxLength <
pAuthBlob->RequestAuthHeaderMaxLength)
{
pServInfo->GreatestAuthHeaderMaxLength =
pAuthBlob->RequestAuthHeaderMaxLength;
}
if(pCurrent == &pServInfo->pAuthListHead)
{
// This could be the first insertion in the list or an insertion
// at the very end.
UlTrace(AUTH_CACHE,
("[UcAddURIEntry]: Inserting entry for (%s:%s) "
"at end of the list (Valid = %d) \n",
pTemp->pUri,
pTemp->pRealm,
pTemp->Valid)
);
InsertTailList(&pServInfo->pAuthListHead, &pTemp->Linkage);
}
else
{
UlTrace(AUTH_CACHE,
("[UcAddURIEntry]: Inserting entry for (%s:%s) "
"before (%s:%s), Valid = %d \n",
pTemp->pUri,
pTemp->pRealm,
pAuth->pUri,
pAuth->pRealm,
pAuth->Valid)
);
InsertTailList(&pAuth->Linkage, &pTemp->Linkage);
}
//
// Digest AUTH can define a extended URI protection list.
//
// Let's assume the request URI was /auth. The server can return a
// bunch of URIs in the domain list - All these URIs define/extend the
// protection scope. Now, when we get back a 200 OK for the /auth, we have
// to go back & validate all the "dependent" URIs.
//
// So, maintain a list of dependent URI entries, keyed off the request
// URI.
//
while (UriListLength)
{
pStart = pUriList;
// go to the end of the URI. The URI
// space terminated or ends in \0
while (UriListLength && *pUriList != ' ')
{
pUriList ++;
UriListLength --;
}
//
// We need to NULL terminate that URI before we can compare.
//
// The pUriList is not necessarily NULL terminated. It's basically
// a pointer into the WWW-Authenticate header, which is NULL
// terminated. So, basically, we can be assured that we have a char
// to accomodate the NULL terminator.
//
if (UriListLength)
{
ASSERT(*pUriList == ' ');
*(PSTR)pUriList = '\0';
}
else
{
ASSERT(*pUriList == '\0');
}
UlTrace(AUTH_CACHE,
("[UcAddURIEntry]: Adding dependent URI entry %s \n",
pStart));
for(pCurrent = pServInfo->pAuthListHead.Flink;
pCurrent != &pServInfo->pAuthListHead;
pCurrent = pCurrent->Flink)
{
// Retrieve the auth structure from the list entry
pAuth = CONTAINING_RECORD(
pCurrent,
UC_HTTP_AUTH_CACHE,
Linkage
);
ASSERT(UC_IS_VALID_AUTH_CACHE(pAuth));
if(pAuth == pTemp)
continue;
Compare = UcpUriCompareExact(pStart, pAuth->pUri);
if(Compare == 0)
{
//
// We found another instance of the same URI in the list.
// Too bad, we have to nuke the old entry.
//
if(pAuth->pDependParent == pTemp)
{
//
// The dependent URI list has duplicates.
// Ignore this URI.
//
UlTrace(AUTH_CACHE,
("[UcAddURIEntry]: URI list has duplicate entries"
" for %s (Ignoring) \n", pStart));
goto NextURI;
}
UlTrace(AUTH_CACHE,
("[UcAddURIEntry]: Found existing entry for %s "
" while doing depend insert \n", pStart));
UcDeleteURIEntry(pServInfo, pAuth);
pCurrent = pServInfo->pAuthListHead.Flink;
continue;
}
else if(Compare > 0)
{
//
// Input URI is greater than current entry. We have
// reached our insertion point.
break;
}
}
if((pNew = UcpAllocateAuthCacheEntry(pServInfo,
AuthType,
(ULONG)(pUriList - pStart),
RealmLength,
pStart,
pInputRealm,
pAuthBlob
)) == 0)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto quit;
}
pNew->pDependParent = pTemp;
ASSERT(pNew->pAuthBlob == pTemp->pAuthBlob);
InsertTailList(&pTemp->pDigestUriList, &pNew->DigestLinkage);
if(pCurrent == &pServInfo->pAuthListHead)
{
// This could be the first insertion in the list or an
// insertion at the very end.
UlTrace(AUTH_CACHE,
("[UcAddURIEntry]: Inserting dependent entry for "
" (%s:%s) at end of the list (Valid = %d)\n",
pNew->pUri,
pNew->pRealm,
pNew->Valid)
);
InsertTailList(&pServInfo->pAuthListHead, &pNew->Linkage);
}
else
{
ASSERT(NULL != pAuth);
UlTrace(AUTH_CACHE,
("[UcAddURIEntry]: Inserting dependent entry for (%s:%s)"
" before (%s:%s) (Valid = %d) \n",
pNew->pUri,
pNew->pRealm,
pAuth->pUri,
pAuth->pRealm,
pAuth->Valid)
);
InsertTailList(&pAuth->Linkage, &pNew->Linkage);
}
NextURI:
* (PSTR) pUriList++ = ' ';
}
quit:
UlReleasePushLock(&pServInfo->PushLock);
return Status;
}
/***************************************************************************++
Routine Description:
Process a URI for pre-authentication. We call this just before adding
an entry in the URI auth cache. This is done to "strip" the last portion
of the URI
For e.g. if the request is for /auth/foo/bar.htm, we want to define the
protection scope as /auth/foo, and hence we have to strip everything from
the last '/' character.
Arguments:
pRequest - The Request. This contains a pointer to the URI
Return Value:
STATUS_SUCCESS - Successfully processed
--***************************************************************************/
NTSTATUS
UcpProcessUriForPreAuth(
IN PSTR pUri,
IN PUSHORT UriLength
)
{
//
// the URI MUST be null terminated.
//
ASSERT(pUri[*UriLength] == '\0');
UlTrace(AUTH_CACHE,
("[UcpProcessUriForPreAuth]: Before %d:%s ", *UriLength, pUri));
//
// walk backward and get rid of everything from the trailing
// '/'
// point to the last character.
(*UriLength) --;
while(*UriLength != 0 && pUri[*UriLength] != '/')
{
(*UriLength)--;
}
//
// Null terminate
//
(*UriLength) ++;
pUri[*UriLength] = '\0';
(*UriLength) ++;
UlTrace(AUTH_CACHE, (" After %d:%s \n", *UriLength, pUri));
return STATUS_SUCCESS;
}
/***************************************************************************++
Routine Description:
Allocates an entry for the auth URI cache.
Arguments:
AuthType - The auth type (basic/digest)
UriLength - Uri Length
RealmLength - Realm Length
pInputURI - Input URI
pInputRealm - Input Realm
pAuthBlob - Auth blob
Return Value:
pointer to the allocated entry.
--***************************************************************************/
PUC_HTTP_AUTH_CACHE
UcpAllocateAuthCacheEntry(
IN PUC_PROCESS_SERVER_INFORMATION pServInfo,
IN HTTP_AUTH_TYPE AuthType,
IN ULONG UriLength,
IN ULONG RealmLength,
IN PCSTR pInputURI,
IN PCSTR pInputRealm,
IN PUC_HTTP_AUTH pAuthBlob
)
{
PUC_HTTP_AUTH_CACHE pAuth;
ULONG BytesAllocated;
BytesAllocated = sizeof(UC_HTTP_AUTH_CACHE) + UriLength + RealmLength +
sizeof(CHAR);
//
// UC_BUGBUG (PERF): Use Lookasides
//
pAuth = (PUC_HTTP_AUTH_CACHE)
UL_ALLOCATE_POOL_WITH_QUOTA(
NonPagedPool,
BytesAllocated,
UC_AUTH_CACHE_POOL_TAG,
pServInfo->pProcess
);
if(!pAuth)
{
return NULL;
}
//
// Initialize
//
pAuth->Signature = UC_AUTH_CACHE_SIGNATURE;
pAuth->AuthType = AuthType;
pAuth->pUri = (PSTR)((PUCHAR) pAuth + sizeof(UC_HTTP_AUTH_CACHE));
pAuth->UriLength = UriLength;
pAuth->pRealm = (PSTR)((PUCHAR) pAuth->pUri + pAuth->UriLength);
pAuth->RealmLength = RealmLength + sizeof(CHAR);
pAuth->pAuthBlob = pAuthBlob;
pAuth->pDependParent = 0;
pAuth->Valid = (BOOLEAN)(pAuthBlob != 0);
pAuth->AuthCacheSize = BytesAllocated;
InitializeListHead(&pAuth->pDigestUriList);
//
// This entry is not on any lists.
//
InitializeListHead(&pAuth->DigestLinkage);
RtlCopyMemory(pAuth->pUri, pInputURI, UriLength);
RtlCopyMemory(pAuth->pRealm, pInputRealm, RealmLength);
*((PUCHAR)pAuth->pRealm + RealmLength) = 0;
return pAuth;
}
/***************************************************************************++
Routine Description:
Flushes all entries in the URI cache.
Arguments:
pInfo - The server information
Return Value:
None
--***************************************************************************/
VOID
UcDeleteAllURIEntries(
IN PUC_PROCESS_SERVER_INFORMATION pInfo
)
{
PLIST_ENTRY pEntry;
PUC_HTTP_AUTH_CACHE pAuth;
UlAcquirePushLockExclusive(&pInfo->PushLock);
while ( !IsListEmpty(&pInfo->pAuthListHead) )
{
pEntry = RemoveHeadList(&pInfo->pAuthListHead);
pAuth = CONTAINING_RECORD(pEntry, UC_HTTP_AUTH_CACHE, Linkage);
ASSERT(UC_IS_VALID_AUTH_CACHE(pAuth));
//
// Initialize the list entry so that we remove it only once.
//
InitializeListHead(&pAuth->Linkage);
UcDeleteURIEntry(pInfo, pAuth);
}
UlReleasePushLock(&pInfo->PushLock);
}
/***************************************************************************++
Routine Description:
Destroys a UC_HTTP_AUTH structure
Arguments:
pInternalAuth - Pointer to UC_HTTP_AUTH structure
AuthInternalLength - size of buffer
pProcess - Process that what charged quota for the structure
Return Value:
STATUS_SUCCESS
--***************************************************************************/
NTSTATUS
UcDestroyInternalAuth(
PUC_HTTP_AUTH pUcAuth,
PEPROCESS pProcess
)
{
NTSTATUS Status;
ULONG Length = pUcAuth->AuthInternalLength;
PAGED_CODE();
if (pUcAuth->bValidCredHandle)
{
Status = FreeCredentialsHandle(&pUcAuth->hCredentials);
}
if (pUcAuth->bValidCtxtHandle)
{
Status = DeleteSecurityContext(&pUcAuth->hContext);
}
UL_FREE_POOL_WITH_QUOTA(pUcAuth,
UC_AUTH_CACHE_POOL_TAG,
NonPagedPool,
Length,
pProcess);
return STATUS_SUCCESS;
}
/***************************************************************************++
Routine Description:
Creates a UC_HTTP_AUTH structure from the HTTP_AUTH_CREDENTIALS structure
Arguments:
pInternalAuth - Pointer to UC_HTTP_AUTH
AuthInternalLength - size of buffer
pAuth - The HTTP_AUTH_CREDENTIALS structure.
Return Value:
STATUS_SUCCESS
--***************************************************************************/
NTSTATUS
UcCopyAuthCredentialsToInternal(
IN PUC_HTTP_AUTH pInternalAuth,
IN ULONG AuthInternalLength,
IN HTTP_AUTH_TYPE AuthInternalType,
IN PHTTP_AUTH_CREDENTIALS pAuth,
IN ULONG AuthHeaderLength
)
{
PHTTP_AUTH_CREDENTIALS pIAuth;
PUCHAR pInput;
HTTP_AUTH_PARSED_PARAMS AuthParsedParams[HttpAuthTypesCount];
NTSTATUS Status;
// Sanity check
PAGED_CODE();
ASSERT(pInternalAuth && AuthInternalLength);
ASSERT(pAuth);
pIAuth = &pInternalAuth->Credentials;
pInput = (PUCHAR)(pInternalAuth + 1);
pIAuth->AuthType = pAuth->AuthType;
pIAuth->AuthFlags = pIAuth->AuthFlags;
pInternalAuth->AuthInternalLength = AuthInternalLength;
pInternalAuth->RequestAuthHeaderMaxLength = AuthHeaderLength;
//
// Now, copy out the pointers.
//
if(pAuth->UserNameLength)
{
pIAuth->pUserName = (PWSTR) pInput;
pIAuth->UserNameLength = pAuth->UserNameLength;
pInput += pIAuth->UserNameLength;
RtlCopyMemory((PWSTR) pIAuth->pUserName,
pAuth->pUserName,
pIAuth->UserNameLength);
}
// password
if(pAuth->PasswordLength)
{
pIAuth->PasswordLength = pAuth->PasswordLength;
pIAuth->pPassword = (PWSTR) pInput;
pInput += pIAuth->PasswordLength;
RtlCopyMemory((PWSTR) pIAuth->pPassword,
pAuth->pPassword,
pIAuth->PasswordLength);
}
//domain
if(pAuth->DomainLength)
{
pIAuth->DomainLength = pAuth->DomainLength;
pIAuth->pDomain = (PWSTR) pInput;
pInput += pIAuth->DomainLength;
RtlCopyMemory((PWSTR) pIAuth->pDomain,
pAuth->pDomain,
pIAuth->DomainLength);
}
// If the user wants us to select an authentication type
if (pIAuth->AuthType == HttpAuthTypeAutoSelect)
{
// See if we already determined an auth type before
pIAuth->AuthType = AuthInternalType;
if (pIAuth->AuthType == HttpAuthTypeAutoSelect)
{
// Select an auth type
pIAuth->AuthType = UcpAutoSelectAuthType(pAuth);
// If no auth type found, return error
if (pIAuth->AuthType == HttpAuthTypeAutoSelect)
{
return STATUS_INVALID_PARAMETER;
}
}
}
// Ideally we want to check here to make sure that the header passed by
// the app (i.e the Www-Authenticate header returned by the server)
// matches the selected AUTH scheme. However, we will do this strict
// verification only if we are going to this header. The only scheme that
// currently uses the header is Digest.
//
// If it's basic or Digest, we need to do some additional work.
//
if(pIAuth->AuthType == HttpAuthTypeBasic)
{
pInternalAuth->Basic.pEncodedBuffer = pInput;
pInternalAuth->Basic.EncodedBufferLength =
AuthInternalLength -
(ULONG)(pInput - (PUCHAR)pInternalAuth);
}
else // Other schemes (Supported by SSPI)
{
pInternalAuth->bValidCredHandle = FALSE;
pInternalAuth->bValidCtxtHandle = FALSE;
RtlZeroMemory(&pInternalAuth->hCredentials,
sizeof(pInternalAuth->hCredentials));
RtlZeroMemory(&pInternalAuth->hContext,
sizeof(pInternalAuth->hContext));
pInternalAuth->pChallengeBuffer = pInput;
pInternalAuth->ChallengeBufferSize = 0;
pInternalAuth->ChallengeBufferMaxSize =
(ULONG)(AuthInternalLength - (pInput - (PUCHAR) pInternalAuth));
ASSERT(pInternalAuth->pChallengeBuffer &&
pInternalAuth->ChallengeBufferMaxSize);
// If the scheme is digest, do the additional work
// - Copy WWW-Authenticate header
// - Parse the header
// - Get pointers to the parameter values
// - Get pointer to the header where "Digest" key word begins
// - Get Length of the digest header
if(pIAuth->AuthType == HttpAuthTypeDigest)
{
if(pAuth->HeaderValueLength == 0 || pAuth->pHeaderValue == NULL)
{
return STATUS_INVALID_PARAMETER;
}
pIAuth->HeaderValueLength = pAuth->HeaderValueLength;
pIAuth->pHeaderValue = (PCSTR)pInput;
pInput += pIAuth->HeaderValueLength;
RtlCopyMemory((PUCHAR) pIAuth->pHeaderValue,
pAuth->pHeaderValue,
pIAuth->HeaderValueLength);
// NULL terminate.
*pInput ++ = '\0';
pIAuth->HeaderValueLength++;
//
// We are interested in getting Digest parameters.
// Initialize AuthParsedParams for Digest scheme.
// The parsed parameters for digest scheme will be
// returned in pInternalAuth->ParamValue.
//
INIT_AUTH_PARSED_PARAMS(AuthParsedParams, NULL);
INIT_AUTH_PARSED_PARAMS_FOR_SCHEME(AuthParsedParams,
pInternalAuth->ParamValue,
HttpAuthTypeDigest);
// Parse the header value
Status = UcParseWWWAuthenticateHeader(
pIAuth->pHeaderValue,
pIAuth->HeaderValueLength-1, // ignore '\0'
AuthParsedParams);
if (!NT_SUCCESS(Status))
{
return Status;
}
//
// See if Digest scheme was present in the header. Without, it
// we can't do digest auth.
//
if (AuthParsedParams[HttpAuthTypeDigest].bPresent == FALSE)
{
return STATUS_INVALID_PARAMETER;
}
// Copy digest scheme info to internal auth structure
RtlCopyMemory(&pInternalAuth->AuthSchemeInfo,
&AuthParsedParams[HttpAuthTypeDigest],
sizeof(pInternalAuth->AuthSchemeInfo));
}
}
return STATUS_SUCCESS;
}
/**************************************************************************++
Routine Description:
This is a helper routine that processes parsed parameters for the
authentication schemes NTLM, Kerberos, Negotiate.
Arguments:
pRequest - Request for which the response was received.
pInternalAuth - Internal Auth Structure.
AuthParsedParams - Parsed Parameters for the auth scheme.
AuthType - Authentication scheme.
Return Values:
None.
--**************************************************************************/
__inline
NTSTATUS
UcpProcessAuthParams(
IN PUC_HTTP_REQUEST pRequest,
IN PUC_HTTP_AUTH pInternalAuth,
IN PHTTP_AUTH_PARSED_PARAMS AuthParsedParams,
IN HTTP_AUTH_TYPE AuthType
)
{
NTSTATUS Status;
ULONG Base64Length;
ULONG BinaryLength;
//
// The assumption here is that, if the www-authenticate header
// contains a scheme that has an auth blob, it must be the only
// scheme specified in the header
//
if (pRequest->Renegotiate)
{
UlTrace(PARSER,
("[UcpProcessAuthParams]: Bogus "
" Auth blob for %d auth type renegotiate \n", AuthType));
return STATUS_INVALID_NETWORK_RESPONSE;
}
//
// If auth blob was present, process it.
//
if(AuthParsedParams[AuthType].NumberUnknownParams == 1)
{
if(!pInternalAuth)
{
UlTrace(PARSER,
("[UcpProcessAuthParams]: Bogus "
" %d Renegotiate \n", AuthType));
return STATUS_INVALID_NETWORK_RESPONSE;
}
//
// We have a 401 challenge that we have to re-negotiate.
// The challenge is UUEncoded, so we have to decode it
// and store it.
//
//
// First see if we have enough buffer space to copy the auth blob
//
Base64Length = AuthParsedParams[AuthType].Params[0].Length;
Status = Base64ToBinaryLength(Base64Length, &BinaryLength);
if (!NT_SUCCESS(Status))
{
UlTrace(PARSER,
("[UcpProcessAuthParams]:Auth type %d blob invalid len %d",
AuthType, Base64Length));
return STATUS_INVALID_NETWORK_RESPONSE;
}
if (BinaryLength > pInternalAuth->ChallengeBufferMaxSize)
{
UlTrace(PARSER,
("[UcpProcessAuthParams]: Auth type %d blob too big %d",
AuthType, BinaryLength));
return STATUS_INVALID_NETWORK_RESPONSE;
}
Status = Base64ToBinary(
(PUCHAR)AuthParsedParams[AuthType].Params[0].Value,
(ULONG)AuthParsedParams[AuthType].Params[0].Length,
(PUCHAR)pInternalAuth->pChallengeBuffer,
pInternalAuth->ChallengeBufferMaxSize,
&pInternalAuth->ChallengeBufferSize
);
if (!NT_SUCCESS(Status))
{
UlTrace(PARSER,
("[UcpProcessAuthParams]: Base64ToBinary failed 0x%x",
Status));
return Status;
}
pRequest->Renegotiate = 1;
}
else if(AuthParsedParams[AuthType].NumberUnknownParams == 0)
{
pRequest->Renegotiate = 0;
}
else
{
return STATUS_INVALID_NETWORK_RESPONSE;
}
return STATUS_SUCCESS;
}
/***************************************************************************++
Routine Description:
This routine is called from the response thread of a 401 or a 407.
All we do here is to find out if it's NTLM or Kerberos or Negotiate,
and if so, whether we have to re-negotiate.
Arguments:
pInternalAuth - Pointer to UC_HTTP_AUTH
pBuffer - Pointer to header value. Guaranteed to be NULL
terminated.
pRequest - The UC_HTTP_REQUEST structure.
Return Value:
STATUS_SUCCESS
--***************************************************************************/
NTSTATUS
UcParseAuthChallenge(
IN PUC_HTTP_AUTH pInternalAuth,
IN PCSTR pBuffer,
IN ULONG BufLen,
IN PUC_HTTP_REQUEST pRequest,
OUT PULONG Flags
)
{
ULONG AuthSchemeFlags = 0;
HTTP_AUTH_PARSED_PARAMS AuthParsedParams[HttpAuthTypesCount];
HTTP_AUTH_PARAM_VALUE AuthNTLMParamValue;
HTTP_AUTH_PARAM_VALUE AuthKerberosParamValue;
HTTP_AUTH_PARAM_VALUE AuthNegotiateParamValue;
NTSTATUS Status;
// Sanity check
ASSERT(pBuffer && BufLen);
ASSERT(pRequest);
ASSERT(Flags);
// By default, no renegotiation needed for this request.
pRequest->Renegotiate = 0;
// Zero out parsed params array
INIT_AUTH_PARSED_PARAMS(AuthParsedParams, NULL);
// We are interested in NTLM, Negotiate, Kerberos parameters.
INIT_AUTH_PARSED_PARAMS_FOR_SCHEME(AuthParsedParams,
&AuthNTLMParamValue,
HttpAuthTypeNTLM);
INIT_AUTH_PARSED_PARAMS_FOR_SCHEME(AuthParsedParams,
&AuthNegotiateParamValue,
HttpAuthTypeNegotiate);
INIT_AUTH_PARSED_PARAMS_FOR_SCHEME(AuthParsedParams,
&AuthKerberosParamValue,
HttpAuthTypeKerberos);
// Parse the header and retrive the parameters
Status = UcParseWWWAuthenticateHeader(pBuffer,
BufLen,
AuthParsedParams);
if (!NT_SUCCESS(Status))
{
return Status;
}
// Check if Basic authentication scheme is supported
if (AuthParsedParams[HttpAuthTypeBasic].bPresent)
{
// Yes. We don't parse the parameters now. Just update the flag.
AuthSchemeFlags |= HTTP_RESPONSE_FLAG_AUTH_BASIC;
}
// Check if Digest authentication scheme is supported
if(AuthParsedParams[HttpAuthTypeDigest].bPresent)
{
// Yes. We don't parse the parameters now. Just update the flag.
AuthSchemeFlags |= HTTP_RESPONSE_FLAG_AUTH_DIGEST;
}
//
// Check if any of NTLM, Kerberos, Negotiate authentication schemes
// are present. If an auth scheme is present, update the flag.
// If the auth scheme has an auth blob, copy the auth blob to
// internal structure.
// In any case, AT MOST one scheme should have an auth blob.
//
if(AuthParsedParams[HttpAuthTypeNTLM].bPresent)
{
Status = UcpProcessAuthParams(pRequest,
pInternalAuth,
AuthParsedParams,
HttpAuthTypeNTLM);
if (!NT_SUCCESS(Status))
{
return Status;
}
AuthSchemeFlags |= HTTP_RESPONSE_FLAG_AUTH_NTLM;
}
if(AuthParsedParams[HttpAuthTypeNegotiate].bPresent)
{
Status = UcpProcessAuthParams(pRequest,
pInternalAuth,
AuthParsedParams,
HttpAuthTypeNegotiate);
if (!NT_SUCCESS(Status))
{
return Status;
}
AuthSchemeFlags |= HTTP_RESPONSE_FLAG_AUTH_NEGOTIATE;
}
if(AuthParsedParams[HttpAuthTypeKerberos].bPresent)
{
Status = UcpProcessAuthParams(pRequest,
pInternalAuth,
AuthParsedParams,
HttpAuthTypeKerberos);
if (!NT_SUCCESS(Status))
{
return Status;
}
AuthSchemeFlags |= HTTP_RESPONSE_FLAG_AUTH_KERBEROS;
}
*Flags |= AuthSchemeFlags;
return STATUS_SUCCESS;
}