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.
 
 
 
 
 
 

811 lines
21 KiB

/*++
Copyright (c) 1989-1997 Microsoft Corporation
Module Name:
nlmain.c
Abstract:
This file contains the initialization and dispatch routines
for the LAN Manager portions of the MSV1_0 authentication package.
Author:
Jim Kelly 11-Apr-1991
Revision History:
25-Apr-1991 (cliffv)
Added interactive logon support for PDK.
Chandana Surlu 21-Jul-1996
Stolen from \\kernel\razzle3\src\security\msv1_0\nlmain.c
Adam Barr 15-Dec-1997
Modified from private\security\msv_sspi\nlmain.c
--*/
#include <rdrssp.h>
#include <nturtl.h>
#include <align.h>
#define NLP_ALLOCATE
#include "nlp.h"
#undef NLP_ALLOCATE
#include <md4.h>
#include <md5.h>
#include <hmac.h>
VOID
NlpPutString(
IN PUNICODE_STRING OutString,
IN PUNICODE_STRING InString,
IN PUCHAR *Where
)
/*++
Routine Description:
This routine copies the InString string to the memory pointed to by
the Where parameter, and fixes the OutString string to point to that
new copy.
Parameters:
OutString - A pointer to a destination NT string
InString - A pointer to an NT string to be copied
Where - A pointer to space to put the actual string for the
OutString. The pointer is adjusted to point to the first byte
following the copied string.
Return Values:
None.
--*/
{
ASSERT( OutString != NULL );
ASSERT( InString != NULL );
ASSERT( Where != NULL && *Where != NULL);
ASSERT( *Where == ROUND_UP_POINTER( *Where, sizeof(WCHAR) ) );
#ifdef notdef
KdPrint(("NlpPutString: %ld %Z\n", InString->Length, InString ));
KdPrint((" InString: %lx %lx OutString: %lx Where: %lx\n", InString,
InString->Buffer, OutString, *Where ));
#endif
if ( InString->Length > 0 ) {
OutString->Buffer = (PWCH) *Where;
OutString->MaximumLength = (USHORT)(InString->Length + sizeof(WCHAR));
RtlCopyUnicodeString( OutString, InString );
*Where += InString->Length;
// *((WCHAR *)(*Where)) = L'\0';
*(*Where) = '\0';
*(*Where + 1) = '\0';
*Where += 2;
} else {
RtlInitUnicodeString(OutString, NULL);
}
#ifdef notdef
KdPrint((" OutString: %ld %lx\n", OutString->Length, OutString->Buffer));
#endif
return;
}
NTSTATUS
NlpMakePrimaryCredential(
IN PUNICODE_STRING LogonDomainName,
IN PUNICODE_STRING UserName,
IN PUNICODE_STRING CleartextPassword,
OUT PMSV1_0_PRIMARY_CREDENTIAL *CredentialBuffer,
OUT PULONG CredentialSize,
IN BOOLEAN OwfPasswordProvided
)
/*++
Routine Description:
This routine makes a primary credential for the given user nam and
password.
Arguments:
LogonDomainName - Is a string representing the domain in which the user's
account is defined.
UserName - Is a string representing the user's account name. The
name may be up to 255 characters long. The name is treated case
insensitive.
CleartextPassword - Is a string containing the user's cleartext password.
The password may be up to 255 characters long and contain any
UNICODE value.
CredentialBuffer - Returns a pointer to the specified credential allocated
on the LsaHeap. It is the callers responsibility to deallocate
this credential.
CredentialSize - the size of the allocated credential buffer (in bytes).
OwfPasswordProvided - If TRUE, then we assume the password is provided as the LM and NT OWF,
passwords concatenated together.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
STATUS_QUOTA_EXCEEDED - This error indicates that the logon
could not be completed because the client does not have
sufficient quota to allocate the return buffer.
--*/
{
PMSV1_0_PRIMARY_CREDENTIAL Credential;
NTSTATUS Status;
PUCHAR Where;
CHAR LmPassword[LM20_PWLEN+1];
BOOLEAN LmPasswordPresent;
STRING AnsiCleartextPassword;
if (!OwfPasswordProvided) {
//
// Compute the Ansi version to the Cleartext password.
//
// The Ansi version of the Cleartext password is at most 14 bytes long,
// exists in a trailing zero filled 15 byte buffer,
// is uppercased.
//
AnsiCleartextPassword.Buffer = LmPassword;
AnsiCleartextPassword.MaximumLength = sizeof(LmPassword);
RtlZeroMemory( &LmPassword, sizeof(LmPassword) );
Status = RtlUpcaseUnicodeStringToOemString(
&AnsiCleartextPassword,
CleartextPassword,
(BOOLEAN) FALSE );
if ( !NT_SUCCESS(Status) ) {
RtlSecureZeroMemory( &LmPassword, sizeof(LmPassword) );
AnsiCleartextPassword.Length = 0;
LmPasswordPresent = FALSE;
} else {
LmPasswordPresent = TRUE;
}
}
//
// Build the credential
//
*CredentialSize = sizeof(MSV1_0_PRIMARY_CREDENTIAL) +
LogonDomainName->Length + sizeof(WCHAR) +
UserName->Length + sizeof(WCHAR);
Credential = ExAllocatePool ( NonPagedPool, *CredentialSize );
if ( Credential == NULL ) {
KdPrint(("MSV1_0: NlpMakePrimaryCredential: No memory %ld\n",
*CredentialSize ));
return STATUS_QUOTA_EXCEEDED;
}
//
// Put the LogonDomainName into the Credential Buffer.
//
Where = (PUCHAR)(Credential + 1);
NlpPutString( &Credential->LogonDomainName, LogonDomainName, &Where );
//
// Put the UserName into the Credential Buffer.
//
NlpPutString( &Credential->UserName, UserName, &Where );
if (OwfPasswordProvided) {
RtlCopyMemory(&Credential->LmOwfPassword, CleartextPassword->Buffer, LM_OWF_PASSWORD_SIZE);
Credential->LmPasswordPresent = TRUE;
RtlCopyMemory(&Credential->NtOwfPassword, ((PUCHAR)CleartextPassword->Buffer) + LM_OWF_PASSWORD_SIZE, NT_OWF_PASSWORD_SIZE);
Credential->NtPasswordPresent = TRUE;
} else {
//
// Save the OWF encrypted versions of the passwords.
//
Status = RtlCalculateLmOwfPassword( LmPassword,
&Credential->LmOwfPassword );
ASSERT( NT_SUCCESS(Status) );
Credential->LmPasswordPresent = LmPasswordPresent;
Status = RtlCalculateNtOwfPassword( CleartextPassword,
&Credential->NtOwfPassword );
ASSERT( NT_SUCCESS(Status) );
Credential->NtPasswordPresent = ( CleartextPassword->Length != 0 );
//
// Don't leave passwords around in the pagefile
//
RtlSecureZeroMemory( &LmPassword, sizeof(LmPassword) );
}
//
// Return the credential to the caller.
//
*CredentialBuffer = Credential;
return STATUS_SUCCESS;
}
VOID
SspUpcaseUnicodeString(
IN OUT UNICODE_STRING* pUnicodeString
)
/*++
Routine Description:
Upcase unicode string, modifying string in place.
Arguments:
pUnicodeString - string
Return Value:
none
--*/
{
ULONG i;
for (i = 0; i < pUnicodeString->Length / sizeof(WCHAR); i++)
{
pUnicodeString->Buffer[i] = RtlUpcaseUnicodeChar(pUnicodeString->Buffer[i]);
}
}
#define MSV1_0_NTLMV2_OWF_LENGTH MSV1_0_NTLM3_RESPONSE_LENGTH
VOID
SspCalculateNtlmv2Owf(
IN NT_OWF_PASSWORD* pNtOwfPassword,
IN UNICODE_STRING* pUserName,
IN UNICODE_STRING* pLogonDomainName,
OUT UCHAR Ntlmv2Owf[MSV1_0_NTLMV2_OWF_LENGTH]
)
/*++
Routine Description:
Calculate Ntlm v2 OWF, salted with username and logon domain name
Arguments:
pNtOwfPassword - NT OWF
pUserName - user name
pLogonDomainName - logon domain name
Ntlmv2Owf - NTLM v2 OWF
Return Value:
none
--*/
{
HMACMD5_CTX HMACMD5Context;
//
// reserve a scratch buffer
//
WCHAR szUserName[(UNLEN + 4)] = {0};
UNICODE_STRING UserName = {0, sizeof(szUserName), szUserName};
//
// first make a copy then upcase it
//
UserName.Length = min(UserName.MaximumLength, pUserName->Length);
ASSERT(UserName.Length == pUserName->Length);
memcpy(UserName.Buffer, pUserName->Buffer, UserName.Length);
SspUpcaseUnicodeString(&UserName);
//
// Calculate Ntlmv2 OWF -- HMAC(MD4(P), (UserName, LogonDomainName))
//
HMACMD5Init(
&HMACMD5Context,
(UCHAR *) pNtOwfPassword,
sizeof(*pNtOwfPassword)
);
HMACMD5Update(
&HMACMD5Context,
(UCHAR *) UserName.Buffer,
UserName.Length
);
HMACMD5Update(
&HMACMD5Context,
(UCHAR *) pLogonDomainName->Buffer,
pLogonDomainName->Length
);
HMACMD5Final(
&HMACMD5Context,
Ntlmv2Owf
);
}
#define MSV1_0_NTLMV2_RESPONSE_LENGTH MSV1_0_NTLM3_RESPONSE_LENGTH
VOID
SspGetLmv2Response(
IN NT_OWF_PASSWORD* pNtOwfPassword,
IN UNICODE_STRING* pUserName,
IN UNICODE_STRING* pLogonDomainName,
IN UCHAR ChallengeToClient[MSV1_0_CHALLENGE_LENGTH],
IN UCHAR ChallengeFromClient[MSV1_0_CHALLENGE_LENGTH],
OUT UCHAR Response[MSV1_0_NTLMV2_RESPONSE_LENGTH],
OUT USER_SESSION_KEY* pUserSessionKey,
OUT UCHAR LanmanSessionKey[MSV1_0_LANMAN_SESSION_KEY_LENGTH]
)
/*++
Routine Description:
Get LMv2 response
Arguments:
pNtOwfPassword - NT OWF
pUserName - user name
pLogonDomainName - logon domain name
ChallengeToClient - challenge to client
pLmv2Response - Lm v2 response
Routine - response
Return Value:
NTSTATUS
--*/
{
HMACMD5_CTX HMACMD5Context;
UCHAR Ntlmv2Owf[MSV1_0_NTLMV2_OWF_LENGTH];
C_ASSERT(MD5DIGESTLEN == MSV1_0_NTLMV2_RESPONSE_LENGTH);
//
// get Ntlmv2 OWF
//
SspCalculateNtlmv2Owf(
pNtOwfPassword,
pUserName,
pLogonDomainName,
Ntlmv2Owf
);
//
// Calculate Ntlmv2 Response
// HMAC(Ntlmv2Owf, (ChallengeToClient, ChallengeFromClient))
//
HMACMD5Init(
&HMACMD5Context,
Ntlmv2Owf,
MSV1_0_NTLMV2_OWF_LENGTH
);
HMACMD5Update(
&HMACMD5Context,
ChallengeToClient,
MSV1_0_CHALLENGE_LENGTH
);
HMACMD5Update(
&HMACMD5Context,
ChallengeFromClient,
MSV1_0_CHALLENGE_LENGTH
);
HMACMD5Final(
&HMACMD5Context,
Response
);
// now compute the session keys
// HMAC(Kr, R)
HMACMD5Init(
&HMACMD5Context,
Ntlmv2Owf,
MSV1_0_NTLM3_OWF_LENGTH
);
HMACMD5Update(
&HMACMD5Context,
Response,
MSV1_0_NTLM3_RESPONSE_LENGTH
);
ASSERT(MD5DIGESTLEN == MSV1_0_USER_SESSION_KEY_LENGTH);
HMACMD5Final(
&HMACMD5Context,
(PUCHAR)pUserSessionKey
);
ASSERT(MSV1_0_LANMAN_SESSION_KEY_LENGTH <= MSV1_0_USER_SESSION_KEY_LENGTH);
RtlCopyMemory(
LanmanSessionKey,
pUserSessionKey,
MSV1_0_LANMAN_SESSION_KEY_LENGTH
);
}
typedef struct {
UCHAR Response[MSV1_0_NTLM3_RESPONSE_LENGTH];
UCHAR ChallengeFromClient[MSV1_0_CHALLENGE_LENGTH];
} MSV1_0_LM3_RESPONSE, *PMSV1_0_LM3_RESPONSE;
#define NULL_SESSION_REQUESTED 0x10
NTSTATUS
MspLm20GetChallengeResponse (
IN PVOID ProtocolSubmitBuffer,
IN ULONG SubmitBufferSize,
OUT PVOID *ProtocolReturnBuffer,
OUT PULONG ReturnBufferSize,
IN BOOLEAN OwfPasswordProvided
)
/*++
Routine Description:
This routine is the dispatch routine for LsaCallAuthenticationPackage()
with a message type of MsV1_0Lm20GetChallengeResponse. It is called by
the LanMan redirector to determine the Challenge Response to pass to a
server when trying to establish a connection to the server.
This routine is passed a Challenge from the server. This routine encrypts
the challenge with either the specified password or with the password
implied by the specified Logon Id.
Two Challenge responses are returned. One is based on the Unicode password
as given to the Authentication package. The other is based on that
password converted to a multi-byte character set (e.g., ASCII) and upper
cased. The redirector should use whichever (or both) challenge responses
as it needs them.
Arguments:
The first four arguments to this routine are identical to those of LsaApCallPackage.
Only the special attributes of these parameters as they apply to
this routine are mentioned here.
OwfPasswordProvided use is used to distinquish if the password is Owf or not.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
STATUS_QUOTA_EXCEEDED - This error indicates that the logon
could not be completed because the client does not have
sufficient quota to allocate the return buffer.
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
PMSV1_0_GETCHALLENRESP_REQUEST GetRespRequest;
PMSV1_0_GETCHALLENRESP_RESPONSE GetRespResponse;
PMSV1_0_PRIMARY_CREDENTIAL Credential = NULL;
PMSV1_0_PRIMARY_CREDENTIAL BuiltCredential = NULL;
PVOID ClientBuffer = NULL;
PUCHAR ClientStrings;
//
// Responses to return to the caller.
//
MSV1_0_LM3_RESPONSE LmResp = {0};
STRING LmResponseString;
NT_RESPONSE NtResponse = {0};
STRING NtResponseString;
UNICODE_STRING NullUnicodeString = {0};
USER_SESSION_KEY UserSessionKey;
UCHAR LanmanSessionKey[MSV1_0_LANMAN_SESSION_KEY_LENGTH];
ULONG CredentialSize = 0;
RtlZeroMemory( &UserSessionKey, sizeof(UserSessionKey) );
RtlZeroMemory( LanmanSessionKey, sizeof(LanmanSessionKey) );
//
// If no credentials are associated with the client, a null session
// will be used. For a downlevel server, the null session response is
// a 1-byte null string (\0). Initialize LmResponseString to the
// null session response.
//
RtlInitString( &LmResponseString, "" );
LmResponseString.Length = 1;
//
// Initialize the NT response to the NT null session credentials,
// which are zero length.
//
RtlInitString( &NtResponseString, NULL );
//
// Ensure the specified Submit Buffer is of reasonable size and
// relocate all of the pointers to be relative to the LSA allocated
// buffer.
//
if ( SubmitBufferSize < sizeof(MSV1_0_GETCHALLENRESP_REQUEST) ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
GetRespRequest = (PMSV1_0_GETCHALLENRESP_REQUEST) ProtocolSubmitBuffer;
ASSERT( GetRespRequest->MessageType == MsV1_0Lm20GetChallengeResponse );
//
// If the caller wants information from the credentials of a specified
// LogonId, get those credentials from the LSA.
//
// If there are no such credentials,
// tell the caller to use the NULL session.
//
#define PRIMARY_CREDENTIAL_NEEDED \
(RETURN_PRIMARY_USERNAME | \
USE_PRIMARY_PASSWORD )
if ( ((GetRespRequest->ParameterControl & PRIMARY_CREDENTIAL_NEEDED) != 0 ) && ((GetRespRequest->ParameterControl & NULL_SESSION_REQUESTED) == 0)) {
ASSERT(FALSE);
}
//
// If the caller passed in a password to use,
// use it to build a credential.
//
// The password is assumed to be the LM and NT OWF
// passwords concatenated together.
//
if ( (GetRespRequest->ParameterControl & USE_PRIMARY_PASSWORD) == 0 ) {
Status = NlpMakePrimaryCredential( &GetRespRequest->LogonDomainName,
&GetRespRequest->UserName,
&GetRespRequest->Password,
&BuiltCredential,
&CredentialSize,
OwfPasswordProvided
);
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
//
// Use the newly allocated credential to get the password information
// from.
//
Credential = BuiltCredential;
}
//
// Build the appropriate response.
//
if ( Credential != NULL ) {
ASSERT(Credential->UserName.Length);
SspGetLmv2Response(
&Credential->NtOwfPassword,
&Credential->UserName,
&Credential->LogonDomainName,
GetRespRequest->ChallengeToClient,
LmResp.ChallengeFromClient,
LmResp.Response,
&UserSessionKey,
LanmanSessionKey
);
LmResponseString.Buffer = (UCHAR*) &LmResp;
LmResponseString.Length = LmResponseString.MaximumLength = sizeof(LmResp);
NtResponseString.Buffer = (CHAR*) L"";
NtResponseString.Length = 0;
NtResponseString.MaximumLength = sizeof(WCHAR);
//
// Compute the session keys
//
if ( GetRespRequest->ParameterControl & RETURN_NON_NT_USER_SESSION_KEY) {
//
// If the redir didn't negotiate an NT protocol with the server,
// use the lanman session key.
//
if ( Credential->LmPasswordPresent ) {
ASSERT( sizeof(UserSessionKey) >= sizeof(LanmanSessionKey) );
RtlCopyMemory( &UserSessionKey,
&Credential->LmOwfPassword,
sizeof(LanmanSessionKey) );
}
if ( Credential->LmPasswordPresent ) {
RtlCopyMemory( LanmanSessionKey,
&Credential->LmOwfPassword,
sizeof(LanmanSessionKey) );
}
} else {
if ( !Credential->NtPasswordPresent ) {
RtlCopyMemory( &Credential->NtOwfPassword,
&NlpNullNtOwfPassword,
sizeof(Credential->NtOwfPassword) );
}
}
}
//
// Allocate a buffer to return to the caller.
//
*ReturnBufferSize = sizeof(MSV1_0_GETCHALLENRESP_RESPONSE) +
Credential->LogonDomainName.Length + sizeof(WCHAR) +
Credential->UserName.Length + sizeof(WCHAR) +
NtResponseString.Length + sizeof(WCHAR) +
LmResponseString.Length + sizeof(WCHAR);
ClientBuffer = ExAllocatePool(NonPagedPool, *ReturnBufferSize);
if (ClientBuffer == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
GetRespResponse = (PMSV1_0_GETCHALLENRESP_RESPONSE) ClientBuffer;
//
// Fill in the return buffer.
//
GetRespResponse->MessageType = MsV1_0Lm20GetChallengeResponse;
RtlCopyMemory( GetRespResponse->UserSessionKey,
&UserSessionKey,
sizeof(UserSessionKey));
RtlCopyMemory( GetRespResponse->LanmanSessionKey,
LanmanSessionKey,
sizeof(LanmanSessionKey) );
ClientStrings = ((PUCHAR)ClientBuffer) + sizeof(MSV1_0_GETCHALLENRESP_RESPONSE);
//
// Copy the logon domain name (the string may be empty)
//
NlpPutString(
&GetRespResponse->LogonDomainName,
&Credential->LogonDomainName,
&ClientStrings );
//
// Copy the user name (the string may be empty)
//
NlpPutString(
&GetRespResponse->UserName,
&Credential->UserName,
&ClientStrings );
//
// Copy the Challenge Responses to the client buffer.
//
NlpPutString(
(PUNICODE_STRING)
&GetRespResponse->CaseSensitiveChallengeResponse,
(PUNICODE_STRING) &NtResponseString,
&ClientStrings );
NlpPutString(
(PUNICODE_STRING)
&GetRespResponse->CaseInsensitiveChallengeResponse,
(PUNICODE_STRING)&LmResponseString,
&ClientStrings );
*ProtocolReturnBuffer = ClientBuffer;
Cleanup:
//
// If we weren't successful, free the buffer in the clients address space.
//
if ( !NT_SUCCESS(Status) && ( ClientBuffer != NULL ) ) {
ExFreePool(ClientBuffer);
}
//
// Cleanup locally used resources
//
if ( BuiltCredential != NULL ) {
RtlZeroMemory(BuiltCredential, CredentialSize);
ExFreePool(BuiltCredential);
}
//
// Return status to the caller.
//
return Status;
}