MORE INFORMATION
The
LogonUser API has been available and documented since Windows NT 3.51, and
is commonly used to verify user credentials. This API is available on Windows
NT, Windows 2000, and Windows XP. Unfortunately, there are some restrictions on
using LogonUser that are not always convenient to satisfy.
The first
and biggest of these restrictions is that on Windows NT and Windows 2000, the
process that is calling LogonUser must have the SE_TCB_NAME privilege (in User
Manager, this is the "Act as part of the Operating System" right). The
SE_TCB_NAME privilege is very powerful and should not be granted to any
arbitrary user just so that they can run an application that needs to validate
credentials. The recommended method is to call LogonUser from a service that is
running in the local system account, because the local system account already
has the SE_TCB_NAME privilege.
Note LogonUser Win32 API does not require TCB privilege in Microsoft
Windows Server 2003, however, for downlevel compatibility, this is still the
best approach.
On Windows XP, it is no longer required that a process
have the SE_TCB_NAME privilege in order to call LogonUser. Therefore, the
simplest method to validate a user's credentials on Windows XP, is to call the
LogonUser API.
One other problem with LogonUser is that the API is
not implemented on Windows 95, Windows 98, or Windows Millennium Edition.
As another option, you can use the Security Support Provider
Interface (SSPI) to do a network style logon with provided user credentials.
This method of validation has the advantage of not requiring any special
privilege, as well as working on all versions of Windows. The end result of
using the SSPI services to validate the credentials is a logon that is
analogous to calling the LogonUser API with the LOGON32_LOGON_NETWORK logon
type. The biggest downside to this type of logon is that you cannot access
remote network resources after impersonating a network type logon. If your
application is calling LogonUser with the LOGON32_LOGON_INTERACTIVE logon type
to workaround Windows NT's inability to perform delegation, then the SSPI
logon/validation will probably not be a viable alternative.
The
sample code provided below shows how to call the SSPI services to perform
credential validation.
To use this method on Windows 95, Windows 98,
and Windows Millennium Edition, you also have to enable the NTLM security
services by opening Control Panel, Network, Access Control, and then selecting
User-level access control.
On Windows XP, the ForceGuest registry
value is set to 1 by default in the following registry key:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa
On a Windows XP computer that is a member of a workgroup:
- If ForceGuest is enabled (set to 1), SSPI will always try
to log on using the Guest account.
- If the Guest account is enabled, an SSPI logon will succeed
as Guest for any user credentials.
- If the Guest account is disabled, an SSPI logon will fail
even for valid credentials.
- If ForceGuest is disabled (set to 0), SSPI will log on as
the specified user.
Sample Code
///////////////////////////////////////////////////////////////////////////////
//
// SSPI Authentication Sample
//
// This program demonstrates how to use SSPI to authenticate user credentials.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (C) 2001. Microsoft Corporation. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#define SECURITY_WIN32
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <sspi.h>
// Older versions of WinError.h does not have SEC_I_COMPLETE_NEEDED #define.
// So, in such SDK environment setup, we will include issperr.h which has the
// definition for SEC_I_COMPLETE_NEEDED. Include issperr.h only if
// SEC_I_COMPLETE_NEEDED is not defined.
#ifndef SEC_I_COMPLETE_NEEDED
#include <issperr.h>
#endif
typedef struct _AUTH_SEQ {
BOOL fInitialized;
BOOL fHaveCredHandle;
BOOL fHaveCtxtHandle;
CredHandle hcred;
struct _SecHandle hctxt;
} AUTH_SEQ, *PAUTH_SEQ;
// Function pointers
ACCEPT_SECURITY_CONTEXT_FN _AcceptSecurityContext = NULL;
ACQUIRE_CREDENTIALS_HANDLE_FN _AcquireCredentialsHandle = NULL;
COMPLETE_AUTH_TOKEN_FN _CompleteAuthToken = NULL;
DELETE_SECURITY_CONTEXT_FN _DeleteSecurityContext = NULL;
FREE_CONTEXT_BUFFER_FN _FreeContextBuffer = NULL;
FREE_CREDENTIALS_HANDLE_FN _FreeCredentialsHandle = NULL;
INITIALIZE_SECURITY_CONTEXT_FN _InitializeSecurityContext = NULL;
QUERY_SECURITY_PACKAGE_INFO_FN _QuerySecurityPackageInfo = NULL;
///////////////////////////////////////////////////////////////////////////////
void UnloadSecurityDll(HMODULE hModule) {
if (hModule)
FreeLibrary(hModule);
_AcceptSecurityContext = NULL;
_AcquireCredentialsHandle = NULL;
_CompleteAuthToken = NULL;
_DeleteSecurityContext = NULL;
_FreeContextBuffer = NULL;
_FreeCredentialsHandle = NULL;
_InitializeSecurityContext = NULL;
_QuerySecurityPackageInfo = NULL;
}
///////////////////////////////////////////////////////////////////////////////
HMODULE LoadSecurityDll() {
HMODULE hModule;
BOOL fAllFunctionsLoaded = FALSE;
TCHAR lpszDLL[MAX_PATH];
OSVERSIONINFO VerInfo;
//
// Find out which security DLL to use, depending on
// whether we are on NT or Win95 or 2000 or XP or Windows Server 2003
// We have to use security.dll on Windows NT 4.0.
// All other operating systems, we have to use Secur32.dll
//
VerInfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
if (!GetVersionEx (&VerInfo)) // If this fails, something has gone wrong
{
return FALSE;
}
if (VerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&
VerInfo.dwMajorVersion == 4 &&
VerInfo.dwMinorVersion == 0)
{
lstrcpy (lpszDLL, _T("security.dll"));
}
else
{
lstrcpy (lpszDLL, _T("secur32.dll"));
}
hModule = LoadLibrary(lpszDLL);
if (!hModule)
return NULL;
__try {
_AcceptSecurityContext = (ACCEPT_SECURITY_CONTEXT_FN)
GetProcAddress(hModule, "AcceptSecurityContext");
if (!_AcceptSecurityContext)
__leave;
#ifdef UNICODE
_AcquireCredentialsHandle = (ACQUIRE_CREDENTIALS_HANDLE_FN)
GetProcAddress(hModule, "AcquireCredentialsHandleW");
#else
_AcquireCredentialsHandle = (ACQUIRE_CREDENTIALS_HANDLE_FN)
GetProcAddress(hModule, "AcquireCredentialsHandleA");
#endif
if (!_AcquireCredentialsHandle)
__leave;
// CompleteAuthToken is not present on Windows 9x Secur32.dll
// Do not check for the availablity of the function if it is NULL;
_CompleteAuthToken = (COMPLETE_AUTH_TOKEN_FN)
GetProcAddress(hModule, "CompleteAuthToken");
_DeleteSecurityContext = (DELETE_SECURITY_CONTEXT_FN)
GetProcAddress(hModule, "DeleteSecurityContext");
if (!_DeleteSecurityContext)
__leave;
_FreeContextBuffer = (FREE_CONTEXT_BUFFER_FN)
GetProcAddress(hModule, "FreeContextBuffer");
if (!_FreeContextBuffer)
__leave;
_FreeCredentialsHandle = (FREE_CREDENTIALS_HANDLE_FN)
GetProcAddress(hModule, "FreeCredentialsHandle");
if (!_FreeCredentialsHandle)
__leave;
#ifdef UNICODE
_InitializeSecurityContext = (INITIALIZE_SECURITY_CONTEXT_FN)
GetProcAddress(hModule, "InitializeSecurityContextW");
#else
_InitializeSecurityContext = (INITIALIZE_SECURITY_CONTEXT_FN)
GetProcAddress(hModule, "InitializeSecurityContextA");
#endif
if (!_InitializeSecurityContext)
__leave;
#ifdef UNICODE
_QuerySecurityPackageInfo = (QUERY_SECURITY_PACKAGE_INFO_FN)
GetProcAddress(hModule, "QuerySecurityPackageInfoW");
#else
_QuerySecurityPackageInfo = (QUERY_SECURITY_PACKAGE_INFO_FN)
GetProcAddress(hModule, "QuerySecurityPackageInfoA");
#endif
if (!_QuerySecurityPackageInfo)
__leave;
fAllFunctionsLoaded = TRUE;
} __finally {
if (!fAllFunctionsLoaded) {
UnloadSecurityDll(hModule);
hModule = NULL;
}
}
return hModule;
}
///////////////////////////////////////////////////////////////////////////////
BOOL GenClientContext(PAUTH_SEQ pAS, PSEC_WINNT_AUTH_IDENTITY pAuthIdentity,
PVOID pIn, DWORD cbIn, PVOID pOut, PDWORD pcbOut, PBOOL pfDone) {
/*++
Routine Description:
Optionally takes an input buffer coming from the server and returns
a buffer of information to send back to the server. Also returns
an indication of whether or not the context is complete.
Return Value:
Returns TRUE if successful; otherwise FALSE.
--*/
SECURITY_STATUS ss;
TimeStamp tsExpiry;
SecBufferDesc sbdOut;
SecBuffer sbOut;
SecBufferDesc sbdIn;
SecBuffer sbIn;
ULONG fContextAttr;
if (!pAS->fInitialized) {
ss = _AcquireCredentialsHandle(NULL, _T("NTLM"),
SECPKG_CRED_OUTBOUND, NULL, pAuthIdentity, NULL, NULL,
&pAS->hcred, &tsExpiry);
if (ss < 0) {
fprintf(stderr, "AcquireCredentialsHandle failed with %08X\n", ss);
return FALSE;
}
pAS->fHaveCredHandle = TRUE;
}
// Prepare output buffer
sbdOut.ulVersion = 0;
sbdOut.cBuffers = 1;
sbdOut.pBuffers = &sbOut;
sbOut.cbBuffer = *pcbOut;
sbOut.BufferType = SECBUFFER_TOKEN;
sbOut.pvBuffer = pOut;
// Prepare input buffer
if (pAS->fInitialized) {
sbdIn.ulVersion = 0;
sbdIn.cBuffers = 1;
sbdIn.pBuffers = &sbIn;
sbIn.cbBuffer = cbIn;
sbIn.BufferType = SECBUFFER_TOKEN;
sbIn.pvBuffer = pIn;
}
ss = _InitializeSecurityContext(&pAS->hcred,
pAS->fInitialized ? &pAS->hctxt : NULL, NULL, 0, 0,
SECURITY_NATIVE_DREP, pAS->fInitialized ? &sbdIn : NULL,
0, &pAS->hctxt, &sbdOut, &fContextAttr, &tsExpiry);
if (ss < 0) {
// <winerror.h>
fprintf(stderr, "InitializeSecurityContext failed with %08X\n", ss);
return FALSE;
}
pAS->fHaveCtxtHandle = TRUE;
// If necessary, complete token
if (ss == SEC_I_COMPLETE_NEEDED || ss == SEC_I_COMPLETE_AND_CONTINUE) {
if (_CompleteAuthToken) {
ss = _CompleteAuthToken(&pAS->hctxt, &sbdOut);
if (ss < 0) {
fprintf(stderr, "CompleteAuthToken failed with %08X\n", ss);
return FALSE;
}
}
else {
fprintf (stderr, "CompleteAuthToken not supported.\n");
return FALSE;
}
}
*pcbOut = sbOut.cbBuffer;
if (!pAS->fInitialized)
pAS->fInitialized = TRUE;
*pfDone = !(ss == SEC_I_CONTINUE_NEEDED
|| ss == SEC_I_COMPLETE_AND_CONTINUE );
return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
BOOL GenServerContext(PAUTH_SEQ pAS, PVOID pIn, DWORD cbIn, PVOID pOut,
PDWORD pcbOut, PBOOL pfDone) {
/*++
Routine Description:
Takes an input buffer coming from the client and returns a buffer
to be sent to the client. Also returns an indication of whether or
not the context is complete.
Return Value:
Returns TRUE if successful; otherwise FALSE.
--*/
SECURITY_STATUS ss;
TimeStamp tsExpiry;
SecBufferDesc sbdOut;
SecBuffer sbOut;
SecBufferDesc sbdIn;
SecBuffer sbIn;
ULONG fContextAttr;
if (!pAS->fInitialized) {
ss = _AcquireCredentialsHandle(NULL, _T("NTLM"),
SECPKG_CRED_INBOUND, NULL, NULL, NULL, NULL, &pAS->hcred,
&tsExpiry);
if (ss < 0) {
fprintf(stderr, "AcquireCredentialsHandle failed with %08X\n", ss);
return FALSE;
}
pAS->fHaveCredHandle = TRUE;
}
// Prepare output buffer
sbdOut.ulVersion = 0;
sbdOut.cBuffers = 1;
sbdOut.pBuffers = &sbOut;
sbOut.cbBuffer = *pcbOut;
sbOut.BufferType = SECBUFFER_TOKEN;
sbOut.pvBuffer = pOut;
// Prepare input buffer
sbdIn.ulVersion = 0;
sbdIn.cBuffers = 1;
sbdIn.pBuffers = &sbIn;
sbIn.cbBuffer = cbIn;
sbIn.BufferType = SECBUFFER_TOKEN;
sbIn.pvBuffer = pIn;
ss = _AcceptSecurityContext(&pAS->hcred,
pAS->fInitialized ? &pAS->hctxt : NULL, &sbdIn, 0,
SECURITY_NATIVE_DREP, &pAS->hctxt, &sbdOut, &fContextAttr,
&tsExpiry);
if (ss < 0) {
fprintf(stderr, "AcceptSecurityContext failed with %08X\n", ss);
return FALSE;
}
pAS->fHaveCtxtHandle = TRUE;
// If necessary, complete token
if (ss == SEC_I_COMPLETE_NEEDED || ss == SEC_I_COMPLETE_AND_CONTINUE) {
if (_CompleteAuthToken) {
ss = _CompleteAuthToken(&pAS->hctxt, &sbdOut);
if (ss < 0) {
fprintf(stderr, "CompleteAuthToken failed with %08X\n", ss);
return FALSE;
}
}
else {
fprintf (stderr, "CompleteAuthToken not supported.\n");
return FALSE;
}
}
*pcbOut = sbOut.cbBuffer;
if (!pAS->fInitialized)
pAS->fInitialized = TRUE;
*pfDone = !(ss = SEC_I_CONTINUE_NEEDED
|| ss == SEC_I_COMPLETE_AND_CONTINUE);
return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI SSPLogonUser(LPTSTR szDomain, LPTSTR szUser, LPTSTR szPassword) {
AUTH_SEQ asServer = {0};
AUTH_SEQ asClient = {0};
BOOL fDone = FALSE;
BOOL fResult = FALSE;
DWORD cbOut = 0;
DWORD cbIn = 0;
DWORD cbMaxToken = 0;
PVOID pClientBuf = NULL;
PVOID pServerBuf = NULL;
PSecPkgInfo pSPI = NULL;
HMODULE hModule = NULL;
SEC_WINNT_AUTH_IDENTITY ai;
__try {
hModule = LoadSecurityDll();
if (!hModule)
__leave;
// Get max token size
_QuerySecurityPackageInfo(_T("NTLM"), &pSPI);
cbMaxToken = pSPI->cbMaxToken;
_FreeContextBuffer(pSPI);
// Allocate buffers for client and server messages
pClientBuf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbMaxToken);
pServerBuf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbMaxToken);
// Initialize auth identity structure
ZeroMemory(&ai, sizeof(ai));
#if defined(UNICODE) || defined(_UNICODE)
ai.Domain = szDomain;
ai.DomainLength = lstrlen(szDomain);
ai.User = szUser;
ai.UserLength = lstrlen(szUser);
ai.Password = szPassword;
ai.PasswordLength = lstrlen(szPassword);
ai.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
#else
ai.Domain = (unsigned char *)szDomain;
ai.DomainLength = lstrlen(szDomain);
ai.User = (unsigned char *)szUser;
ai.UserLength = lstrlen(szUser);
ai.Password = (unsigned char *)szPassword;
ai.PasswordLength = lstrlen(szPassword);
ai.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
#endif
// Prepare client message (negotiate) .
cbOut = cbMaxToken;
if (!GenClientContext(&asClient, &ai, NULL, 0, pClientBuf, &cbOut, &fDone))
__leave;
// Prepare server message (challenge) .
cbIn = cbOut;
cbOut = cbMaxToken;
if (!GenServerContext(&asServer, pClientBuf, cbIn, pServerBuf, &cbOut,
&fDone))
__leave;
// Most likely failure: AcceptServerContext fails with SEC_E_LOGON_DENIED
// in the case of bad szUser or szPassword.
// Unexpected Result: Logon will succeed if you pass in a bad szUser and
// the guest account is enabled in the specified domain.
// Prepare client message (authenticate) .
cbIn = cbOut;
cbOut = cbMaxToken;
if (!GenClientContext(&asClient, &ai, pServerBuf, cbIn, pClientBuf, &cbOut,
&fDone))
__leave;
// Prepare server message (authentication) .
cbIn = cbOut;
cbOut = cbMaxToken;
if (!GenServerContext(&asServer, pClientBuf, cbIn, pServerBuf, &cbOut,
&fDone))
__leave;
fResult = TRUE;
} __finally {
// Clean up resources
if (asClient.fHaveCtxtHandle)
_DeleteSecurityContext(&asClient.hctxt);
if (asClient.fHaveCredHandle)
_FreeCredentialsHandle(&asClient.hcred);
if (asServer.fHaveCtxtHandle)
_DeleteSecurityContext(&asServer.hctxt);
if (asServer.fHaveCredHandle)
_FreeCredentialsHandle(&asServer.hcred);
if (hModule)
UnloadSecurityDll(hModule);
HeapFree(GetProcessHeap(), 0, pClientBuf);
HeapFree(GetProcessHeap(), 0, pServerBuf);
}
return fResult;
}
void _tmain(int argc, TCHAR **argv)
{
if (argc != 4) return;
// argv[1] - Domain Name
// argv[2] - User Name
// argv[3] - Password
if (SSPLogonUser(argv[1], argv[2], argv[3]))
{
printf("User Credentials are valid\n");
}
else
printf("User Credentials are NOT valid\n");
}