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.
 
 
 
 
 
 

703 lines
18 KiB

/*++
Copyright (c) 1996, 1997 Microsoft Corporation
Module Name:
winpw.c
Abstract:
This module contains routines for retrieving and verification of the
Windows NT and Windows 95 password associated with the client calling
protected storage.
Author:
Scott Field (sfield) 12-Dec-96
--*/
#include <windows.h>
#include <lmcons.h>
#include <sha.h>
#include "lnklist.h"
#include "winpw.h"
#include "module.h"
#include "unicode.h"
#include "unicode5.h"
#include "debug.h"
#include "secmisc.h"
#define MPR_PROCESS "MPREXE.EXE"
#define MPRSERV_MODULE "MPRSERV.DLL"
#define GLOBAL_USERNAME 0x0E8
#define PWL_USERNAME 0x170
#define GLOBAL_PASSWORD 0x188
#define PWL_PASSWORD 0x210
//
// this one comes and goes only when needed
//
typedef DWORD (WINAPI *WNETVERIFYPASSWORD)(
LPCSTR lpszPassword,
BOOL *pfMatch
);
typedef DWORD (WINAPI *WNETGETUSERA)(
LPCSTR lpName,
LPSTR lpUserName,
LPDWORD lpnLength
);
WNETGETUSERA _WNetGetUserA = NULL;
//
// global Win95 password buffer. Only need one entry because Win95
// only allows one user logged on at a time.
//
static WIN95_PASSWORD g_Win95Password;
BOOL
VerifyWindowsPasswordNT(
LPCWSTR Password
);
BOOL
GetTokenLogonType(
HANDLE hToken,
LPDWORD lpdwLogonType
);
BOOL
GetTokenLogonType(
HANDLE hToken,
LPDWORD lpdwLogonType
)
/*++
This function retrieves the logon type associated with the
access token specified by the hToken parameter. On success,
the DWORD buffer provided by the dwLogonType parameter is
filled with the logon type which corresponds to the currently
known logon types supported by the LogonUser() Windows NT
API call.
The token specified by the hToken parameter must have been
opened with at least TOKEN_QUERY access.
This function is only relevant on Windows NT and should not
be called on Windows 95, as it will always return FALSE.
--*/
{
UCHAR InfoBuffer[1024];
DWORD dwInfoBufferSize = sizeof(InfoBuffer);
PTOKEN_GROUPS SlowBuffer = NULL;
PTOKEN_GROUPS ptgGroups = (PTOKEN_GROUPS)InfoBuffer;
PSID psidInteractive = NULL;
SID_IDENTIFIER_AUTHORITY siaNtAuthority = SECURITY_NT_AUTHORITY;
BOOL bSuccess;
bSuccess = GetTokenInformation(
hToken,
TokenGroups,
ptgGroups,
dwInfoBufferSize,
&dwInfoBufferSize
);
//
// if fast buffer wasn't big enough, allocate enough storage
// and try again.
//
if(!bSuccess && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
SlowBuffer = (PTOKEN_GROUPS)SSAlloc(dwInfoBufferSize);
if(SlowBuffer != NULL) {
ptgGroups = SlowBuffer;
bSuccess = GetTokenInformation(
hToken,
TokenGroups,
ptgGroups,
dwInfoBufferSize,
&dwInfoBufferSize
);
if(!bSuccess) {
SSFree(SlowBuffer);
SlowBuffer = NULL;
}
}
}
if(!bSuccess)
return FALSE;
//
// initialize a single well-known logon Sid, since
// we only compare the prefix and then the Rid
// note that if performance were of utmost importance, we should
// use InitializeSid + GetSidSubAuthority (to _set_ the Rid).
// also note that we could do a simple memcmp against just the sid
// identifier authority, but this assumes that Sid versions/layouts don't
// change
//
bSuccess = AllocateAndInitializeSid(
&siaNtAuthority,
1,
SECURITY_INTERACTIVE_RID,
0, 0, 0, 0, 0, 0, 0,
&psidInteractive
);
if(bSuccess) {
UINT x;
bSuccess = FALSE; // assume no match
//
// loop through groups checking for equality against
// the well-known logon Sids.
//
for(x = 0 ; x < ptgGroups->GroupCount ; x++)
{
DWORD Rid;
//
// first, see if subauthority count matches, since
// not too many sids have only one subauthority.
//
if(*GetSidSubAuthorityCount(ptgGroups->Groups[x].Sid) != 1)
continue;
//
// next, see if the Sid prefix matches, since
// all the logon Sids have the same prefix
// "S-1-5"
//
if(!EqualPrefixSid(psidInteractive, ptgGroups->Groups[x].Sid))
continue;
//
// if it's a logon sid prefix, just compare the Rid
// to the known values.
//
Rid = *GetSidSubAuthority(ptgGroups->Groups[x].Sid, 0);
switch (Rid) {
case SECURITY_INTERACTIVE_RID:
*lpdwLogonType = LOGON32_LOGON_INTERACTIVE;
break;
case SECURITY_BATCH_RID:
*lpdwLogonType = LOGON32_LOGON_BATCH;
break;
case SECURITY_SERVICE_RID:
*lpdwLogonType = LOGON32_LOGON_SERVICE;
break;
case SECURITY_NETWORK_RID:
*lpdwLogonType = LOGON32_LOGON_NETWORK;
break;
default:
continue; // ignore unknown logon type and continue
}
bSuccess = TRUE; // indicate success and bail
break;
}
}
if(SlowBuffer)
SSFree(SlowBuffer);
if(psidInteractive)
FreeSid(psidInteractive);
return bSuccess;
}
BOOL
SetPasswordNT(
PLUID LogonID,
BYTE HashedPassword[A_SHA_DIGEST_LEN]
)
/*++
This function adds the hashed password that is referenced by the specified
Logon ID.
--*/
{
#if 0
return AddNTPassword(LogonID, HashedPassword);
#else
return TRUE; // do nothing, just return success
#endif
}
BOOL
GetPasswordNT(
BYTE HashedPassword[A_SHA_DIGEST_LEN]
)
/*++
This function retrieves the hashed password associated with the calling
thread access token. This requires that the calling thread is impersonating
the user associated with the password request. The credentials associated
with the authentication ID are returned. This is done because WinNT
supports multiple logged on users, and we must return the correct credentials.
--*/
{
#if 0
LUID AuthenticationId;
if(!GetThreadAuthenticationId(
GetCurrentThread(),
&AuthenticationId
)) return FALSE;
return FindNTPassword(&AuthenticationId, HashedPassword);
#else
return FALSE; // no cache to search, just return FALSE
#endif
}
BOOL
GetSpecialCasePasswordNT(
BYTE HashedPassword[A_SHA_DIGEST_LEN], // derived bits when fSpecialCase == TRUE
LPBOOL fSpecialCase // legal special case encountered?
)
/*++
This routine determines if the calling thread's access token is eligible
to recieve a special case hashed password.
If an legal special case is encountered (Local System Account), we
fill the HashPassword buffer with an consistent hash, set fSpecialCase to
TRUE, and return TRUE.
If an illegal special case is encountered (Network SID), fSpecialCase is
set FALSE, and we return FALSE.
If we encounter an access token that appears to have valid credentials,
but we have no way to get at them (Interactive, Batch, Service ... ),
fSpecialCase is set FALSE and we return TRUE.
The calling thread MUST be imperonsating the client in question prior to
making this call.
--*/
{
HANDLE hToken = NULL;
DWORD dwLogonType;
A_SHA_CTX context;
BOOL fSuccess = FALSE;
*fSpecialCase = FALSE;
if(!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken))
return FALSE;
//
// first, get the token logon type.
//
fSuccess = GetTokenLogonType(hToken, &dwLogonType);
//
// if we got the token logon type ok, check if it's an appropriate type.
// otherwise, check for the local system special case.
//
if(fSuccess) {
//
// we only indicate success for the interactive logon type.
// note default is fSuccess == TRUE when going to cleanup
//
if(dwLogonType != LOGON32_LOGON_INTERACTIVE)
fSuccess = FALSE;
goto cleanup;
} else {
SID_IDENTIFIER_AUTHORITY sia = SECURITY_NT_AUTHORITY;
PSID pSystemSid;
PSID pTokenSid;
fSuccess = GetTokenUserSid(hToken, &pTokenSid);
if(!fSuccess)
goto cleanup;
//
// build local system sid and compare.
//
fSuccess = AllocateAndInitializeSid(
&sia,
1,
SECURITY_LOCAL_SYSTEM_RID,
0, 0, 0, 0, 0, 0, 0,
&pSystemSid
);
if( fSuccess ) {
//
// check sid equality. If so, hash and tell the caller about it.
//
if( EqualSid(pSystemSid, pTokenSid) ) {
//
// hash the special case user Sid
//
A_SHAInit(&context);
A_SHAUpdate(&context, (LPBYTE)pTokenSid, GetLengthSid(pTokenSid));
A_SHAFinal(&context, HashedPassword);
*fSpecialCase = TRUE;
}
FreeSid(pSystemSid);
}
SSFree(pTokenSid);
}
cleanup:
if(hToken)
CloseHandle(hToken);
return fSuccess;
}
BOOL
SetPassword95(
BYTE HashedUsername[A_SHA_DIGEST_LEN],
BYTE HashedPassword[A_SHA_DIGEST_LEN]
)
/*++
This function adds the hashed password that is referenced by the hashed
user name.
Set HashedUsername and HashedPassword to NULL when calling to zero-out
the single password entry.
--*/
{
if(HashedUsername == NULL || HashedPassword == NULL) {
g_Win95Password.bValid = FALSE;
RtlSecureZeroMemory(g_Win95Password.HashedPassword, A_SHA_DIGEST_LEN);
RtlSecureZeroMemory(g_Win95Password.HashedUsername, A_SHA_DIGEST_LEN);
return TRUE;
}
memcpy(g_Win95Password.HashedUsername, HashedUsername, A_SHA_DIGEST_LEN);
memcpy(g_Win95Password.HashedPassword, HashedPassword, A_SHA_DIGEST_LEN);
g_Win95Password.bValid = TRUE;
return TRUE;
}
BOOL
GetPassword95(
BYTE HashedPassword[A_SHA_DIGEST_LEN]
)
/*++
This function retrieves the hashed password associated with the calling
thread. In Win95, only one user is logged on, so this operation is
a simple copy from global memory, once the hash of the current user
matches that which was stored with the hashed credential.
--*/
{
A_SHA_CTX context;
BYTE HashUsername[A_SHA_DIGEST_LEN];
CHAR Username[UNLEN+1];
DWORD cchUsername = UNLEN;
//
// don't release credential unless hash of username matches
// sfield: use WNetGetUser() instead of GetUserName() as WNetGetUser()
// will correspond to the password associated with what the network
// provider gave us.
//
if(_WNetGetUserA(NULL, Username, &cchUsername) != NO_ERROR) {
//
// for Win95, if nobody is logged on, empty user name + password
//
if(GetLastError() != ERROR_NOT_LOGGED_ON)
return FALSE;
Username[0] = '\0'; // not really necessary
cchUsername = 1;
} else {
// arg, WNetGetUserA() doesn't fill in cchUsername
cchUsername = lstrlenA(Username) + 1; // include terminal NULL
if(g_Win95Password.bValid == FALSE)
return FALSE;
}
cchUsername--; // do not include terminal NULL
A_SHAInit(&context);
A_SHAUpdate(&context, Username, cchUsername);
A_SHAFinal(&context, HashUsername);
//
// non empty username, may not be empty password
//
if(cchUsername) {
if(memcmp(HashUsername, g_Win95Password.HashedUsername, A_SHA_DIGEST_LEN) != 0) {
//
// rare case on Win95: if we didn't automatically flush the entry
// during a logoff (this can occur if network provider not hooked),
// flush it now because we know the entry cannot possibly be valid.
//
g_Win95Password.bValid = FALSE;
return FALSE;
}
memcpy(HashedPassword, g_Win95Password.HashedPassword, A_SHA_DIGEST_LEN);
return TRUE;
}
//
// empty user name == empty password
//
memcpy(HashedPassword, HashUsername, A_SHA_DIGEST_LEN);
return TRUE;
}
BOOL
VerifyWindowsPassword(
LPCWSTR Password
)
/*++
This function verifies that the specified password matches that of the
current user.
On Windows 95, the current user equates to the user is currently logged
onto the machine.
On Windows NT, the current user equates to the user which is being
impersonated during the call. On Windows NT, the caller MUST be
impersonating the user associated with the validation.
On Windows NT, a side effect of the validation is notification of
a new logon to the credential manager. This is ignored because the
authentication ID present in the new logon does not match the
authentication ID present in the impersonated access token.
--*/
{
return VerifyWindowsPasswordNT(Password);
}
BOOL
VerifyWindowsPasswordNT(
LPCWSTR Password
)
{
HANDLE hPriorToken = NULL;
HANDLE hToken;
HANDLE hLogonToken = NULL;
PTOKEN_USER pTokenInfo = NULL;
DWORD cbTokenInfoSize;
WCHAR User[UNLEN+1];
WCHAR Domain[DNLEN+1];
DWORD cchUser = UNLEN;
DWORD cchDomain = DNLEN;
SID_NAME_USE peUse;
BOOL bSuccess = FALSE;
//
// find out domain and user name associated with current user
//
if(!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken))
return FALSE;
cbTokenInfoSize = 512;
pTokenInfo = (PTOKEN_USER)SSAlloc(cbTokenInfoSize);
if(pTokenInfo == NULL)
goto cleanup;
if(!GetTokenInformation(
hToken,
TokenUser,
pTokenInfo,
cbTokenInfoSize,
&cbTokenInfoSize
)) {
//
// realloc and try again
//
if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
SSFree(pTokenInfo);
pTokenInfo = (PTOKEN_USER)SSAlloc(cbTokenInfoSize);
if(pTokenInfo == NULL)
goto cleanup;
if(!GetTokenInformation(
hToken,
TokenUser,
pTokenInfo,
cbTokenInfoSize,
&cbTokenInfoSize
)) {
goto cleanup;
}
} else {
goto cleanup;
}
}
if(!LookupAccountSidW(
NULL, // default lookup logic
pTokenInfo->User.Sid,
User,
&cchUser,
Domain,
&cchDomain,
&peUse
))
goto cleanup;
//
// WinNT:
// first try network logon type, if that fails, grovel the token
// and try the same logon type which is associated with the impersonation
// token.
//
//
// arg! LogonUser() fails in some cases if we are impersonating!
// so save off impersonation token, revert, and put it back later.
//
if(!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &hPriorToken)) {
hPriorToken = NULL;
} else {
RevertToSelf();
}
//
// network logon type is fastest, and in default NT install, everyone
// has the SeNetworkLogonRight, so it's very likely to pass the logon right
// test
//
if(!LogonUserW(
User,
Domain,
(LPWSTR)Password,
LOGON32_LOGON_NETWORK,
LOGON32_PROVIDER_DEFAULT,
&hLogonToken
)) {
DWORD dwLastError = GetLastError();
DWORD dwLogonType;
//
// retry with different logon type if necessary.
// note: ERROR_LOGON_TYPE_NOT_GRANTED currently only occurs
// if the password matches but user didn't have specified logon
// type. So, currently, we could treat this as a successful validation
// without retrying, but this is subject to change in future, so retry
// anyway.
//
if( dwLastError == ERROR_LOGON_TYPE_NOT_GRANTED &&
GetTokenLogonType(hPriorToken, &dwLogonType)
) {
bSuccess = LogonUserW(
User,
Domain,
(LPWSTR)Password,
dwLogonType,
LOGON32_PROVIDER_DEFAULT,
&hLogonToken
);
}
if(!bSuccess)
hLogonToken = NULL; // LogonUser() has tendency to leave garbage in hToken
goto cleanup;
}
bSuccess = TRUE;
cleanup:
if(hPriorToken != NULL) {
SetThreadToken(NULL, hPriorToken);
CloseHandle(hPriorToken);
}
CloseHandle(hToken);
if(hLogonToken)
CloseHandle(hLogonToken);
if(pTokenInfo)
SSFree(pTokenInfo);
return bSuccess;
}