mirror of https://github.com/lianthony/NT4.0
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.
3069 lines
74 KiB
3069 lines
74 KiB
/**********************************************************************/
|
|
/** Microsoft Windows NT **/
|
|
/** Copyright(c) Microsoft Corp., 1993 **/
|
|
/**********************************************************************/
|
|
|
|
/*
|
|
security.c
|
|
|
|
This module manages security for the Internet Services.
|
|
|
|
|
|
FILE HISTORY:
|
|
KeithMo 07-Mar-1993 Created.
|
|
MuraliK 05-Jan-1995 Enable statistics query on RPC to go free.
|
|
|
|
*/
|
|
|
|
|
|
#include "tcpdllp.hxx"
|
|
|
|
#pragma hdrstop
|
|
|
|
extern "C" {
|
|
|
|
#include <ntsam.h>
|
|
#include <ntlsa.h>
|
|
#include <ntmsv1_0.h>
|
|
#include <crypt.h>
|
|
#include <logonmsv.h>
|
|
#include <inetsec.h>
|
|
|
|
#define SECURITY_WIN32
|
|
#include <sspi.h> // Security Support Provider APIs
|
|
|
|
#include <issperr.h>
|
|
}
|
|
|
|
# include <inetinfo.h>
|
|
|
|
# include "tsvcinfo.hxx"
|
|
# include "tsunami.hxx"
|
|
# include "tcpcons.h"
|
|
|
|
#include <tssched.hxx>
|
|
|
|
//
|
|
// Private constants.
|
|
//
|
|
|
|
#define TOKEN_SOURCE_NAME "InetSvcs"
|
|
#define LOGON_PROCESS_NAME "inetsvcs.exe"
|
|
#define LOGON_ORIGIN "Internet Services"
|
|
|
|
#define SUBSYSTEM_NAME L"InetSvcs"
|
|
#define OBJECT_NAME L"InetSvcs"
|
|
#define OBJECTTYPE_NAME L"InetSvcs"
|
|
|
|
//
|
|
// The name we use for the target when dealing with the SSP APIs
|
|
//
|
|
|
|
#define TCPAUTH_TARGET_NAME TOKEN_SOURCE_NAME
|
|
|
|
//
|
|
// Token Cache lock. Controls access to the token cache list
|
|
//
|
|
|
|
#define LockTokenCache() EnterCriticalSection( &csTokenCacheLock )
|
|
#define UnlockTokenCache() LeaveCriticalSection( &csTokenCacheLock )
|
|
|
|
//
|
|
// Converts a cached token handle object to the real token handle
|
|
//
|
|
|
|
#define CTO_TO_TOKEN( ptc ) (((CACHED_TOKEN *)ptc)->_hToken)
|
|
|
|
//
|
|
// The check period for how long a token can be in the cache. Tokens can
|
|
// be in the cache for up to two times this value (in seconds)
|
|
//
|
|
|
|
#define DEFAULT_CACHED_TOKEN_TTL (15 * 60)
|
|
|
|
//
|
|
// An instance of a cached token
|
|
//
|
|
|
|
class CACHED_TOKEN
|
|
{
|
|
public:
|
|
CACHED_TOKEN( VOID )
|
|
: _hToken( NULL ),
|
|
_cRef ( 1 ),
|
|
_TTL ( 2 ),
|
|
m_hImpersonationToken( NULL),
|
|
m_fGuest ( FALSE)
|
|
{
|
|
_ListEntry.Flink = NULL;
|
|
}
|
|
|
|
~CACHED_TOKEN( VOID )
|
|
{
|
|
DBG_ASSERT( _ListEntry.Flink == NULL );
|
|
|
|
if ( m_hImpersonationToken) {
|
|
|
|
DBG_REQUIRE( CloseHandle( m_hImpersonationToken ));
|
|
m_hImpersonationToken = NULL;
|
|
}
|
|
|
|
if ( _hToken )
|
|
{
|
|
DBG_REQUIRE( CloseHandle( _hToken ) );
|
|
_hToken = NULL;
|
|
}
|
|
}
|
|
|
|
static VOID Reference( CACHED_TOKEN * pct )
|
|
{
|
|
DBG_ASSERT( pct->_cRef > 0 );
|
|
InterlockedIncrement( &pct->_cRef );
|
|
}
|
|
|
|
static VOID Dereference( CACHED_TOKEN * pct )
|
|
{
|
|
DBG_ASSERT( pct->_cRef > 0 );
|
|
|
|
if ( !InterlockedDecrement( &pct->_cRef ) )
|
|
{
|
|
delete pct;
|
|
}
|
|
}
|
|
|
|
HANDLE QueryImpersonationToken(VOID) const
|
|
{ return ( m_hImpersonationToken); }
|
|
|
|
VOID SetImpersonationToken(IN HANDLE hImpersonation)
|
|
{
|
|
DBG_ASSERT( m_hImpersonationToken == NULL);
|
|
m_hImpersonationToken = hImpersonation;
|
|
}
|
|
|
|
BOOL IsGuest(VOID) const { return (m_fGuest); }
|
|
VOID SetGuest(IN BOOL fGuest) { m_fGuest = fGuest; }
|
|
|
|
HANDLE _hToken; // Must be first data member
|
|
LIST_ENTRY _ListEntry;
|
|
LONG _cRef;
|
|
DWORD _TTL; // Gets decremented on each timeout, when zero,
|
|
// remove this item from the cache
|
|
BOOL m_fGuest; // Is this token a guest user?
|
|
HANDLE m_hImpersonationToken;
|
|
|
|
CHAR _achUser[UNLEN + 1];
|
|
CHAR _achDomain[DNLEN + 1];
|
|
CHAR _achPwd[PWLEN + 1];
|
|
};
|
|
|
|
//
|
|
// Private globals.
|
|
//
|
|
|
|
|
|
//
|
|
// Well-known SIDs.
|
|
//
|
|
|
|
PSID psidWorld;
|
|
PSID psidLocalSystem;
|
|
PSID psidAdmins;
|
|
PSID psidServerOps;
|
|
PSID psidPowerUsers;
|
|
PSID g_psidGuestUser;
|
|
|
|
# define GUEST_USER_SID_BUFFER_LEN (200)
|
|
BYTE g_GuestUserSidBuffer[GUEST_USER_SID_BUFFER_LEN];
|
|
|
|
|
|
//
|
|
// The API security object. Client access to the TCP Server APIs
|
|
// are validated against this object.
|
|
//
|
|
|
|
PSECURITY_DESCRIPTOR sdApiObject;
|
|
|
|
//
|
|
// This table maps generic rights (like GENERIC_READ) to
|
|
// specific rights (like TCP_QUERY_SECURITY).
|
|
//
|
|
|
|
GENERIC_MAPPING TCPApiObjectMapping =
|
|
{
|
|
TCP_GENERIC_READ, // generic read
|
|
TCP_GENERIC_WRITE, // generic write
|
|
TCP_GENERIC_EXECUTE, // generic execute
|
|
TCP_ALL_ACCESS // generic all
|
|
};
|
|
|
|
#if DBG
|
|
|
|
CHAR * apszAccessTypes[] = { "read",
|
|
"write",
|
|
"create",
|
|
"delete" };
|
|
|
|
#endif // DBG
|
|
|
|
//
|
|
// List of cached tokens, the token list lock and the cookie to the token
|
|
// scavenger schedule item. The token cache TTL gets converted to msecs
|
|
// during startup
|
|
//
|
|
|
|
BOOL IsTokenCacheInitialized = FALSE;
|
|
LIST_ENTRY TokenCacheList;
|
|
CRITICAL_SECTION csTokenCacheLock;
|
|
DWORD dwScheduleCookie = 0;
|
|
DWORD cmsecTokenCacheTTL = DEFAULT_CACHED_TOKEN_TTL;
|
|
|
|
//
|
|
// Private prototypes.
|
|
//
|
|
|
|
DWORD CreateWellKnownSids( VOID );
|
|
|
|
VOID FreeWellKnownSids( VOID );
|
|
|
|
DWORD CreateApiSecurityObject( VOID );
|
|
|
|
VOID DeleteApiSecurityObject( VOID );
|
|
|
|
TS_TOKEN
|
|
ValidateUser(
|
|
TCHAR * pszDomainName,
|
|
TCHAR * pszUserName,
|
|
TCHAR * pszPassword,
|
|
BOOL fAnonymous,
|
|
BOOL * pfAsGuest,
|
|
DWORD dwLogonMethod
|
|
);
|
|
|
|
BOOL
|
|
IsGuestUser(IN HANDLE hToken);
|
|
|
|
BOOL CrackUserAndDomain(
|
|
CHAR * pszDomainAndUser,
|
|
CHAR * * ppszUser,
|
|
CHAR * * ppszDomain
|
|
);
|
|
|
|
BOOL
|
|
GetDefaultDomainName(
|
|
CHAR * pszDomainName,
|
|
DWORD cchDomainName
|
|
);
|
|
|
|
|
|
VOID EnableTcbPrivilege(
|
|
VOID
|
|
);
|
|
|
|
BOOL
|
|
AddTokenToCache(
|
|
IN const CHAR * pszUser,
|
|
IN const CHAR * pszDomain,
|
|
IN const CHAR * pszPwd,
|
|
IN HANDLE hToken,
|
|
OUT CACHED_TOKEN * * ppct
|
|
);
|
|
|
|
BOOL
|
|
FindCachedToken(
|
|
IN const CHAR * pszUser,
|
|
IN const CHAR * pszDomain,
|
|
IN const CHAR * pszPwd,
|
|
IN BOOL fResetTTL,
|
|
OUT CACHED_TOKEN * * ppct
|
|
);
|
|
|
|
VOID
|
|
WINAPI
|
|
TokenCacheScavenger(
|
|
IN const VOID * pContext
|
|
);
|
|
|
|
extern BOOL g_fIgnoreSC;
|
|
|
|
HANDLE g_hProcessToken = NULL;
|
|
|
|
//
|
|
// Public functions.
|
|
//
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: InitializeSecurity
|
|
|
|
SYNOPSIS: Initializes security authentication & impersonation
|
|
routines.
|
|
|
|
RETURNS: DWORD - NO_ERROR if successful, otherwise a Win32
|
|
error code.
|
|
|
|
NOTES: This routine may only be called by a single thread
|
|
of execution; it is not necessarily multi-thread safe.
|
|
|
|
HISTORY:
|
|
KeithMo 07-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
DWORD InitializeSecurity( VOID )
|
|
{
|
|
NTSTATUS ntStatus;
|
|
HANDLE hAsExe;
|
|
DWORD err;
|
|
|
|
IF_DEBUG( DLL_SECURITY )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"initializing security\n" ));
|
|
}
|
|
|
|
//
|
|
// See if we should ignore the service controller (useful for running
|
|
// as an .exe). Inetsvcs.exe creates an event with this name. So
|
|
// if the semaphore creation fails, then we know we're running as an .exe.
|
|
//
|
|
|
|
if ( !(hAsExe = CreateSemaphore( NULL, 1, 1, "Internet_infosvc_as_exe" )))
|
|
{
|
|
g_fIgnoreSC = (GetLastError() == ERROR_INVALID_HANDLE);
|
|
}
|
|
else
|
|
{
|
|
DBG_REQUIRE( CloseHandle( hAsExe ) );
|
|
}
|
|
|
|
if ( g_fIgnoreSC )
|
|
{
|
|
//
|
|
// If the service is running as an .exe, we need to enable
|
|
// the SeTcbPrivilege (Act as part of the operating system).
|
|
// We don't worry about disabling the privilege as this is
|
|
// only used in test debug code
|
|
//
|
|
|
|
EnableTcbPrivilege();
|
|
}
|
|
|
|
//
|
|
// Create well-known SIDs.
|
|
//
|
|
|
|
err = CreateWellKnownSids();
|
|
|
|
//
|
|
// Create the API security object.
|
|
//
|
|
|
|
if( !err )
|
|
{
|
|
err = CreateApiSecurityObject();
|
|
}
|
|
|
|
if ( !err )
|
|
{
|
|
HKEY hkey;
|
|
|
|
IsTokenCacheInitialized = TRUE;
|
|
InitializeListHead( &TokenCacheList );
|
|
InitializeCriticalSection( &csTokenCacheLock );
|
|
|
|
//
|
|
// Get the default token TTL, must be at least one second
|
|
//
|
|
|
|
if ( !RegOpenKeyEx( HKEY_LOCAL_MACHINE,
|
|
INETA_PARAMETERS_KEY,
|
|
0,
|
|
KEY_READ,
|
|
&hkey )) {
|
|
|
|
cmsecTokenCacheTTL = ReadRegistryDword( hkey,
|
|
"UserTokenTTL",
|
|
DEFAULT_CACHED_TOKEN_TTL);
|
|
|
|
RegCloseKey( hkey );
|
|
}
|
|
|
|
cmsecTokenCacheTTL = max( 1, cmsecTokenCacheTTL );
|
|
cmsecTokenCacheTTL *= 1000;
|
|
|
|
IF_DEBUG( DLL_SECURITY )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"Scheduling token cached scavenger to %d seconds\n",
|
|
cmsecTokenCacheTTL/1000 ));
|
|
}
|
|
|
|
//
|
|
// Schedule a work item for the token scavenger
|
|
//
|
|
|
|
dwScheduleCookie = ScheduleWorkItem( (PFN_SCHED_CALLBACK)TokenCacheScavenger,
|
|
NULL,
|
|
cmsecTokenCacheTTL );
|
|
}
|
|
|
|
|
|
//
|
|
// Success!
|
|
//
|
|
|
|
IF_DEBUG( DLL_SECURITY )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"security initialized\n" ));
|
|
}
|
|
|
|
return err;
|
|
|
|
} // InitializeSecurity
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: TerminateSecurity
|
|
|
|
SYNOPSIS: Terminate security authentication & impersonation
|
|
routines.
|
|
|
|
NOTES: This routine may only be called by a single thread
|
|
of execution; it is not necessarily multi-thread safe.
|
|
|
|
HISTORY:
|
|
KeithMo 07-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
VOID TerminateSecurity( VOID )
|
|
{
|
|
CACHED_TOKEN * pct;
|
|
|
|
IF_DEBUG( DLL_SECURITY )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"terminating security\n" ));
|
|
}
|
|
|
|
FreeWellKnownSids();
|
|
DeleteApiSecurityObject();
|
|
|
|
//
|
|
// Remove the scheduled scavenger
|
|
//
|
|
|
|
if ( dwScheduleCookie ) {
|
|
RemoveWorkItem( dwScheduleCookie );
|
|
}
|
|
|
|
//
|
|
// Delete any tokens still in the cache
|
|
//
|
|
|
|
if ( IsTokenCacheInitialized ) {
|
|
|
|
LockTokenCache();
|
|
|
|
while ( !IsListEmpty( &TokenCacheList )) {
|
|
pct = CONTAINING_RECORD( TokenCacheList.Flink,
|
|
CACHED_TOKEN,
|
|
_ListEntry );
|
|
|
|
RemoveEntryList( &pct->_ListEntry );
|
|
pct->_ListEntry.Flink = NULL;
|
|
|
|
//
|
|
// If the ref count isn't zero then somebody didn't delete all of
|
|
// their tokens
|
|
//
|
|
|
|
DBG_ASSERT( pct->_cRef == 1 );
|
|
|
|
CACHED_TOKEN::Dereference( pct );
|
|
}
|
|
|
|
UnlockTokenCache();
|
|
|
|
DeleteCriticalSection( &csTokenCacheLock );
|
|
}
|
|
|
|
IF_DEBUG( DLL_SECURITY ) {
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"Security terminated\n" ));
|
|
}
|
|
|
|
} // TerminateSecurity
|
|
|
|
|
|
/*******************************************************************/
|
|
|
|
TCP_AUTHENT::TCP_AUTHENT(
|
|
DWORD AuthFlags
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Constructor for the Authentication class
|
|
|
|
Arguments:
|
|
|
|
AuthFlags - One of the TCPAUTH_* flags.
|
|
|
|
--*/
|
|
: _hToken ( NULL ),
|
|
_hSSPToken ( NULL ),
|
|
_hSSPPrimaryToken( NULL ),
|
|
_fHaveCredHandle ( FALSE ),
|
|
_fHaveCtxtHandle ( FALSE ),
|
|
_fClient ( FALSE ),
|
|
_fUUEncodeData ( FALSE ),
|
|
_fBase64 ( FALSE ),
|
|
_fKnownToBeGuest ( FALSE )
|
|
{
|
|
if ( AuthFlags & TCPAUTH_SERVER )
|
|
{
|
|
DBG_ASSERT( !(AuthFlags & TCPAUTH_CLIENT));
|
|
}
|
|
|
|
if ( AuthFlags & TCPAUTH_CLIENT )
|
|
{
|
|
_fClient = TRUE;
|
|
}
|
|
|
|
if ( AuthFlags & TCPAUTH_UUENCODE )
|
|
{
|
|
_fUUEncodeData = TRUE;
|
|
}
|
|
|
|
if ( AuthFlags & TCPAUTH_BASE64 )
|
|
{
|
|
_fBase64 = TRUE;
|
|
}
|
|
|
|
DBG_REQUIRE( Reset() );
|
|
}
|
|
|
|
/*******************************************************************/
|
|
|
|
TCP_AUTHENT::~TCP_AUTHENT(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Destructor for the Authentication class
|
|
|
|
--*/
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
BOOL
|
|
TCP_AUTHENT::Reset(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Resets this object in preparation for a brand new conversation
|
|
|
|
--*/
|
|
{
|
|
if ( _hToken != NULL )
|
|
{
|
|
DBG_ASSERT( _fClearText );
|
|
TsDeleteUserToken( _hToken );
|
|
|
|
_hToken = NULL;
|
|
}
|
|
|
|
if ( _hSSPToken )
|
|
{
|
|
CloseHandle( _hSSPToken );
|
|
_hSSPToken = NULL;
|
|
}
|
|
|
|
//
|
|
// We close this token because we duplicated it from _hSSPToken
|
|
//
|
|
|
|
if ( _hSSPPrimaryToken )
|
|
{
|
|
CloseHandle( _hSSPPrimaryToken );
|
|
_hSSPPrimaryToken = NULL;
|
|
}
|
|
|
|
if ( _fHaveCtxtHandle )
|
|
{
|
|
DeleteSecurityContext( &_hctxt );
|
|
_fHaveCtxtHandle = FALSE;
|
|
}
|
|
|
|
if ( _fHaveCredHandle )
|
|
{
|
|
FreeCredentialsHandle( &_hcred );
|
|
_fHaveCredHandle = FALSE;
|
|
}
|
|
|
|
_fNewConversation = TRUE;
|
|
_fClearText = FALSE;
|
|
_cbMaxToken = 0;
|
|
|
|
_fKnownToBeGuest = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*******************************************************************/
|
|
|
|
HANDLE
|
|
TCP_AUTHENT::QueryPrimaryToken(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns a non-impersonated token suitable for use with CreateProcessAsUser
|
|
|
|
--*/
|
|
{
|
|
SECURITY_STATUS sc;
|
|
|
|
if ( _hToken && _fClearText )
|
|
{
|
|
return CTO_TO_TOKEN( _hToken );
|
|
}
|
|
else if ( _fHaveCredHandle )
|
|
{
|
|
if ( !_hSSPPrimaryToken )
|
|
{
|
|
if ( !_hSSPToken )
|
|
{
|
|
sc = QuerySecurityContextToken( &_hctxt,
|
|
&_hSSPToken );
|
|
|
|
if ( !NT_SUCCESS( sc ))
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"[GetUserHandle] QuerySecurityContext failed, error 0x%lx\n",
|
|
sc ));
|
|
|
|
SetLastError( sc );
|
|
|
|
return NULL;
|
|
}
|
|
|
|
AdjustTokenPrivileges( _hSSPToken,
|
|
TRUE,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL );
|
|
}
|
|
|
|
//
|
|
// We need to convert the NTLM impersonation token into a
|
|
// primary token
|
|
//
|
|
|
|
if ( !DuplicateTokenEx( _hSSPToken,
|
|
TOKEN_ALL_ACCESS,
|
|
NULL,
|
|
SecurityImpersonation,
|
|
TokenPrimary,
|
|
&_hSSPPrimaryToken ))
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"[QueryPrimaryToken] DuplicateToken failed, error %lx\n",
|
|
GetLastError() ));
|
|
}
|
|
}
|
|
|
|
return _hSSPPrimaryToken;
|
|
}
|
|
|
|
SetLastError( ERROR_INVALID_HANDLE );
|
|
return NULL;
|
|
}
|
|
|
|
HANDLE
|
|
TCP_AUTHENT::QueryImpersonationToken(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns an impersonation token for use with APIs like AccessCheck.
|
|
|
|
--*/
|
|
{
|
|
SECURITY_STATUS sc;
|
|
|
|
if ( _hToken && _fClearText )
|
|
{
|
|
return ((CACHED_TOKEN *) _hToken)->QueryImpersonationToken();
|
|
}
|
|
else if ( _fHaveCredHandle )
|
|
{
|
|
//
|
|
// We don't need to impersonate since this is already an impersonation
|
|
// token
|
|
//
|
|
if ( !_hSSPToken )
|
|
{
|
|
sc = QuerySecurityContextToken( &_hctxt,
|
|
&_hSSPToken );
|
|
|
|
if ( !NT_SUCCESS( sc ))
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"[QueryImpersonationToken] QuerySecurityContext failed, error 0x%lx\n",
|
|
sc ));
|
|
|
|
SetLastError( sc );
|
|
|
|
return NULL;
|
|
}
|
|
|
|
AdjustTokenPrivileges( _hSSPToken,
|
|
TRUE,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL );
|
|
}
|
|
|
|
return _hSSPToken;
|
|
}
|
|
|
|
SetLastError( ERROR_INVALID_HANDLE );
|
|
return NULL;
|
|
}
|
|
|
|
BOOL
|
|
TCP_AUTHENT::IsGuest(
|
|
BOOL fIsImpersonated
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns TRUE if the account is the guest account
|
|
|
|
--*/
|
|
{
|
|
fIsImpersonated; // Unreferenced variable
|
|
|
|
if ( _fHaveCtxtHandle )
|
|
{
|
|
return _fKnownToBeGuest;
|
|
}
|
|
|
|
return IsGuestUser( GetUserHandle() );
|
|
}
|
|
|
|
BOOL TCP_AUTHENT::EnumAuthPackages(
|
|
BUFFER * pBuff
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Places a double null terminated list of authentication packages on the
|
|
system in pBuff that looks like:
|
|
|
|
NTLM\0
|
|
MSKerberos\0
|
|
Netware\0
|
|
\0
|
|
|
|
Arguments:
|
|
|
|
pBuff - Buffer to receive list
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE otherwise (call GetLastError)
|
|
|
|
--*/
|
|
{
|
|
|
|
SECURITY_STATUS ss;
|
|
PSecPkgInfo pPackageInfo = NULL;
|
|
ULONG cPackages;
|
|
ULONG i;
|
|
ULONG fCaps;
|
|
DWORD cbBuffNew = 0;
|
|
DWORD cbBuffOld = 0;
|
|
|
|
if ( !pBuff->Resize( 64 ) )
|
|
return FALSE;
|
|
|
|
//
|
|
// Get the list of security packages on this machine
|
|
//
|
|
|
|
ss = EnumerateSecurityPackages( &cPackages,
|
|
&pPackageInfo );
|
|
|
|
if ( ss != STATUS_SUCCESS )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"[EnumAuthPackages] Failed with error %d\n",
|
|
ss ));
|
|
|
|
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
|
|
return FALSE;
|
|
}
|
|
|
|
for ( i = 0; i < cPackages ; i++ )
|
|
{
|
|
//
|
|
// We'll only use the security package if it supports connection
|
|
// oriented security and it supports the appropriate side (client
|
|
// or server)
|
|
//
|
|
|
|
fCaps = pPackageInfo[i].fCapabilities;
|
|
|
|
if ( fCaps & SECPKG_FLAG_CONNECTION )
|
|
{
|
|
if ( (fCaps & SECPKG_FLAG_CLIENT_ONLY) && !_fClient )
|
|
continue;
|
|
|
|
cbBuffNew += strlen( pPackageInfo[i].Name ) + 1;
|
|
|
|
if ( pBuff->QuerySize() < cbBuffNew )
|
|
{
|
|
if ( !pBuff->Resize( cbBuffNew + 64 ))
|
|
{
|
|
FreeContextBuffer( pPackageInfo );
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
strcpy( (CHAR *)pBuff->QueryPtr() + cbBuffOld,
|
|
pPackageInfo[i].Name );
|
|
|
|
cbBuffOld = cbBuffNew;
|
|
}
|
|
}
|
|
|
|
*((CHAR *)pBuff->QueryPtr() + cbBuffOld) = '\0';
|
|
|
|
FreeContextBuffer( pPackageInfo );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL TCP_AUTHENT::QueryUserName(
|
|
BUFFER * pBuff
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Queries the name associated with this *authenticated* object
|
|
|
|
Arguments:
|
|
|
|
pBuff - Buffer to receive name
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE otherwise (call GetLastError)
|
|
|
|
--*/
|
|
{
|
|
SECURITY_STATUS ss;
|
|
DWORD cbName;
|
|
SecPkgContext_Names CredNames;
|
|
|
|
ss = QueryContextAttributes( &_hctxt,
|
|
SECPKG_ATTR_NAMES,
|
|
&CredNames );
|
|
|
|
if ( ss != STATUS_SUCCESS )
|
|
{
|
|
SetLastError( ss );
|
|
return FALSE;
|
|
}
|
|
|
|
cbName = strlen( CredNames.sUserName ) + 1;
|
|
|
|
if ( !pBuff->Resize( cbName ))
|
|
{
|
|
FreeContextBuffer( CredNames.sUserName );
|
|
return FALSE;
|
|
}
|
|
|
|
memcpy( pBuff->QueryPtr(), CredNames.sUserName, cbName );
|
|
|
|
FreeContextBuffer( CredNames.sUserName );
|
|
return TRUE;
|
|
}
|
|
/*******************************************************************/
|
|
|
|
BOOL TCP_AUTHENT::Converse(
|
|
VOID * pBuffIn,
|
|
DWORD cbBuffIn,
|
|
BUFFER * pbuffOut,
|
|
DWORD * pcbBuffOut,
|
|
BOOL * pfNeedMoreData,
|
|
CHAR * pszPackage,
|
|
CHAR * pszUser,
|
|
CHAR * pszPassword
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initiates or continues a previously initiated authentication conversation
|
|
|
|
Client calls this first to get the negotiation message which
|
|
it then sends to the server. The server calls this with the
|
|
client result and sends back the result. The conversation
|
|
continues until *pfNeedMoreData is FALSE.
|
|
|
|
On the first call, pszPackage must point to the zero terminated
|
|
authentication package name to be used and pszUser and pszPassword
|
|
should point to the user name and password to authenticated with
|
|
on the client side (server side will always be NULL).
|
|
|
|
Arguments:
|
|
|
|
pBuffIn - Points to SSP message received from the
|
|
client. If TCPAUTH_UUENCODE is used, then this must point to a
|
|
zero terminated uuencoded string (except for the first call).
|
|
cbBuffIn - Number of bytes in pBuffIn or zero if pBuffIn points to a
|
|
zero terminated, uuencoded string.
|
|
pbuffOut - If *pfDone is not set to TRUE, this buffer contains the data
|
|
that should be sent to the other side. If this is zero, then no
|
|
data needs to be sent.
|
|
pcbBuffOut - Number of bytes in pbuffOut
|
|
pfNeedMoreData - Set to TRUE while this side of the conversation is
|
|
expecting more data from the remote side.
|
|
pszPackage - On the first call points to a zero terminate string indicating
|
|
the security package to use
|
|
pszUser - Specifies user or domain\user the first time the client calls
|
|
this method (client side only)
|
|
pszPassword - Specifies the password for pszUser the first time the
|
|
client calls this method (client side only)
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE otherwise (call GetLastError). Access is
|
|
denied if FALSE is returned and GetLastError is ERROR_ACCESS_DENIED.
|
|
|
|
--*/
|
|
{
|
|
SECURITY_STATUS ss;
|
|
TimeStamp Lifetime;
|
|
SecBufferDesc OutBuffDesc;
|
|
SecBuffer OutSecBuff;
|
|
SecBufferDesc InBuffDesc;
|
|
SecBuffer InSecBuff;
|
|
ULONG ContextAttributes;
|
|
BUFFER buffData;
|
|
BUFFER buff;
|
|
|
|
//
|
|
// Decode the data if there's something to decode
|
|
//
|
|
|
|
if ( _fUUEncodeData && pBuffIn )
|
|
{
|
|
if ( !uudecode( (CHAR *) pBuffIn,
|
|
&buffData,
|
|
&cbBuffIn,
|
|
_fBase64
|
|
))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
pBuffIn = buffData.QueryPtr();
|
|
}
|
|
|
|
//
|
|
// If this is a new conversation, then we need to get the credential
|
|
// handle and find out the maximum token size
|
|
//
|
|
|
|
if ( _fNewConversation )
|
|
{
|
|
SecPkgInfo * pspkg;
|
|
SEC_WINNT_AUTH_IDENTITY AuthIdentity;
|
|
SEC_WINNT_AUTH_IDENTITY * pAuthIdentity;
|
|
CHAR * pszDomain = NULL;
|
|
CHAR szDomainAndUser[DNLEN+UNLEN+2];
|
|
|
|
|
|
//
|
|
// If this is the client and a username and password were
|
|
// specified, then fill out the authentication information
|
|
//
|
|
|
|
if ( _fClient &&
|
|
((pszUser != NULL) ||
|
|
(pszPassword != NULL)) )
|
|
{
|
|
pAuthIdentity = &AuthIdentity;
|
|
|
|
//
|
|
// Break out the domain from the username if one was specified
|
|
//
|
|
|
|
if ( pszUser != NULL )
|
|
{
|
|
strcpy( szDomainAndUser, pszUser );
|
|
if ( !CrackUserAndDomain( szDomainAndUser,
|
|
&pszUser,
|
|
&pszDomain ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
memset( &AuthIdentity,
|
|
0,
|
|
sizeof( AuthIdentity ));
|
|
|
|
if ( pszUser != NULL )
|
|
{
|
|
AuthIdentity.User = (unsigned char *) pszUser;
|
|
AuthIdentity.UserLength = strlen( pszUser );
|
|
}
|
|
|
|
if ( pszPassword != NULL )
|
|
{
|
|
AuthIdentity.Password = (unsigned char *) pszPassword;
|
|
AuthIdentity.PasswordLength = strlen( pszPassword );
|
|
}
|
|
|
|
if ( pszDomain != NULL )
|
|
{
|
|
AuthIdentity.Domain = (unsigned char *) pszDomain;
|
|
AuthIdentity.DomainLength = strlen( pszDomain );
|
|
}
|
|
|
|
AuthIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
|
|
}
|
|
else
|
|
{
|
|
pAuthIdentity = NULL;
|
|
}
|
|
|
|
ss = AcquireCredentialsHandle( NULL, // New principal
|
|
pszPackage, // Package name
|
|
(_fClient ? SECPKG_CRED_OUTBOUND :
|
|
SECPKG_CRED_INBOUND),
|
|
NULL, // Logon ID
|
|
pAuthIdentity, // Auth Data
|
|
NULL, // Get key func
|
|
NULL, // Get key arg
|
|
&_hcred,
|
|
&Lifetime );
|
|
|
|
//
|
|
// Need to determine the max token size for this package
|
|
//
|
|
|
|
if ( ss == STATUS_SUCCESS )
|
|
{
|
|
_fHaveCredHandle = TRUE;
|
|
ss = QuerySecurityPackageInfo( (char *) pszPackage,
|
|
&pspkg );
|
|
}
|
|
|
|
if ( ss != STATUS_SUCCESS )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"[Converse] AcquireCredentialsHandle or QuerySecurityPackageInfo failed, error %d\n",
|
|
ss ));
|
|
|
|
SetLastError( ss );
|
|
return FALSE;
|
|
}
|
|
|
|
_cbMaxToken = pspkg->cbMaxToken;
|
|
DBG_ASSERT( pspkg->fCapabilities & SECPKG_FLAG_CONNECTION );
|
|
|
|
FreeContextBuffer( pspkg );
|
|
|
|
}
|
|
|
|
//
|
|
// Prepare our output buffer. We use a temporary buffer because
|
|
// the real output buffer will most likely need to be uuencoded
|
|
//
|
|
|
|
if ( !buff.Resize( _cbMaxToken ))
|
|
return FALSE;
|
|
|
|
OutBuffDesc.ulVersion = 0;
|
|
OutBuffDesc.cBuffers = 1;
|
|
OutBuffDesc.pBuffers = &OutSecBuff;
|
|
|
|
OutSecBuff.cbBuffer = _cbMaxToken;
|
|
OutSecBuff.BufferType = SECBUFFER_TOKEN;
|
|
OutSecBuff.pvBuffer = buff.QueryPtr();
|
|
|
|
//
|
|
// Prepare our Input buffer - Note the server is expecting the client's
|
|
// negotiation packet on the first call
|
|
//
|
|
|
|
if ( pBuffIn )
|
|
{
|
|
InBuffDesc.ulVersion = 0;
|
|
InBuffDesc.cBuffers = 1;
|
|
InBuffDesc.pBuffers = &InSecBuff;
|
|
|
|
InSecBuff.cbBuffer = cbBuffIn;
|
|
InSecBuff.BufferType = SECBUFFER_TOKEN;
|
|
InSecBuff.pvBuffer = pBuffIn;
|
|
}
|
|
|
|
//
|
|
// Client side uses InitializeSecurityContext, server side uses
|
|
// AcceptSecurityContext
|
|
//
|
|
|
|
if ( _fClient )
|
|
{
|
|
//
|
|
// Note the client will return success when its done but we still
|
|
// need to send the out buffer if there are bytes to send
|
|
//
|
|
|
|
ss = InitializeSecurityContext( &_hcred,
|
|
_fNewConversation ? NULL :
|
|
&_hctxt,
|
|
TCPAUTH_TARGET_NAME,
|
|
0,
|
|
0,
|
|
SECURITY_NATIVE_DREP,
|
|
_fNewConversation ? NULL :
|
|
&InBuffDesc,
|
|
0,
|
|
&_hctxt,
|
|
&OutBuffDesc,
|
|
&ContextAttributes,
|
|
&Lifetime );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This is the server side
|
|
//
|
|
|
|
SetLastError ( 0 );
|
|
|
|
ss = AcceptSecurityContext( &_hcred,
|
|
_fNewConversation ? NULL :
|
|
&_hctxt,
|
|
&InBuffDesc,
|
|
0,
|
|
SECURITY_NATIVE_DREP,
|
|
&_hctxt,
|
|
&OutBuffDesc,
|
|
&ContextAttributes,
|
|
&Lifetime );
|
|
}
|
|
|
|
if ( !NT_SUCCESS( ss ) )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"[Converse] Initialize/AcceptCredentialsHandle failed, error %d\n",
|
|
ss ));
|
|
|
|
if ( ss == SEC_E_LOGON_DENIED ||
|
|
ss == SEC_E_INVALID_TOKEN)
|
|
{
|
|
ss = ERROR_LOGON_FAILURE;
|
|
}
|
|
|
|
SetLastError( ss );
|
|
return FALSE;
|
|
}
|
|
|
|
_fHaveCtxtHandle = TRUE;
|
|
|
|
//
|
|
// NTLMSSP will set the last error to ERROR_NO_SUCH_USER
|
|
// if success and Guest account was used
|
|
//
|
|
|
|
if ( GetLastError() == ERROR_NO_SUCH_USER )
|
|
{
|
|
_fKnownToBeGuest = TRUE;
|
|
}
|
|
|
|
//
|
|
// Now we just need to complete the token (if requested) and prepare
|
|
// it for shipping to the other side if needed
|
|
//
|
|
|
|
BOOL fReply = !!OutSecBuff.cbBuffer;
|
|
|
|
if ( (ss == SEC_I_COMPLETE_NEEDED) ||
|
|
(ss == SEC_I_COMPLETE_AND_CONTINUE) )
|
|
{
|
|
ss = CompleteAuthToken( &_hctxt,
|
|
&OutBuffDesc );
|
|
|
|
if ( !NT_SUCCESS( ss ))
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
//
|
|
// Format or copy to the output buffer if we need to reply
|
|
//
|
|
|
|
if ( fReply )
|
|
{
|
|
if ( _fUUEncodeData )
|
|
{
|
|
if ( !uuencode( (BYTE *) OutSecBuff.pvBuffer,
|
|
OutSecBuff.cbBuffer,
|
|
pbuffOut,
|
|
_fBase64))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
*pcbBuffOut = strlen( (CHAR *) pbuffOut->QueryPtr() );
|
|
}
|
|
else
|
|
{
|
|
if ( !pbuffOut->Resize( OutSecBuff.cbBuffer ))
|
|
return FALSE;
|
|
|
|
memcpy( pbuffOut->QueryPtr(),
|
|
OutSecBuff.pvBuffer,
|
|
OutSecBuff.cbBuffer );
|
|
|
|
*pcbBuffOut = OutSecBuff.cbBuffer;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*pcbBuffOut = 0;
|
|
}
|
|
|
|
if ( _fNewConversation )
|
|
_fNewConversation = FALSE;
|
|
|
|
*pfNeedMoreData = ((ss == SEC_I_CONTINUE_NEEDED) ||
|
|
(ss == SEC_I_COMPLETE_AND_CONTINUE));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*******************************************************************/
|
|
|
|
BOOL TCP_AUTHENT::ClearTextLogon(
|
|
CHAR * pszUser,
|
|
CHAR * pszPassword,
|
|
BOOL * pfAsGuest,
|
|
BOOL * pfAsAnonymous,
|
|
LPTSVC_INFO psi
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Gets a network logon token using clear text
|
|
|
|
Arguments:
|
|
|
|
pszUser - User name (optionally with domain)
|
|
pszPassword - password
|
|
pfAsGuest - Set to TRUE if granted with guest access (NOT SUPPORTED)
|
|
pfAsAnonymous - Set to TRUE if the user received the anonymous token
|
|
psi - pointer to Service info struct
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE otherwise (call GetLastError)
|
|
|
|
--*/
|
|
{
|
|
DBG_ASSERT( !_fHaveCredHandle && !_fHaveCtxtHandle );
|
|
|
|
_hToken = TsLogonUser( pszUser,
|
|
pszPassword,
|
|
pfAsGuest,
|
|
pfAsAnonymous,
|
|
psi );
|
|
|
|
if ( !_hToken )
|
|
return FALSE;
|
|
|
|
_fClearText = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*******************************************************************/
|
|
|
|
BOOL TCP_AUTHENT::Impersonate(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Impersonates the authenticated user
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE otherwise (call GetLastError)
|
|
|
|
--*/
|
|
{
|
|
if ( _fClearText )
|
|
{
|
|
return TsImpersonateUser( _hToken );
|
|
}
|
|
else
|
|
{
|
|
DBG_ASSERT( _fHaveCtxtHandle );
|
|
|
|
return !!NT_SUCCESS( ImpersonateSecurityContext( &_hctxt ));
|
|
}
|
|
}
|
|
|
|
/*******************************************************************/
|
|
|
|
BOOL TCP_AUTHENT::RevertToSelf(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Undoes the impersonation
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE otherwise (call GetLastError)
|
|
|
|
--*/
|
|
{
|
|
if ( _fClearText )
|
|
{
|
|
return ::RevertToSelf();
|
|
}
|
|
else
|
|
{
|
|
DBG_ASSERT( _fHaveCtxtHandle );
|
|
|
|
return !!NT_SUCCESS( RevertSecurityContext( &_hctxt ));
|
|
}
|
|
}
|
|
|
|
/*******************************************************************/
|
|
|
|
BOOL TCP_AUTHENT::StartProcessAsUser(
|
|
LPCSTR lpApplicationName,
|
|
LPSTR lpCommandLine,
|
|
BOOL bInheritHandles,
|
|
DWORD dwCreationFlags,
|
|
LPVOID lpEnvironment,
|
|
LPCSTR lpCurrentDirectory,
|
|
LPSTARTUPINFOA lpStartupInfo,
|
|
LPPROCESS_INFORMATION lpProcessInformation
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Creates a process as the authenticated user
|
|
|
|
Arguments:
|
|
|
|
Standard CreateProcess args
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE otherwise (call GetLastError)
|
|
|
|
--*/
|
|
{
|
|
HANDLE htoken;
|
|
BOOL fRet;
|
|
|
|
if ( _fClearText )
|
|
{
|
|
htoken = CTO_TO_TOKEN( _hToken );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Need to extract the impersonation token from the opaque SSP
|
|
// structures
|
|
//
|
|
|
|
if ( !Impersonate() )
|
|
return FALSE;
|
|
|
|
if ( !OpenThreadToken( GetCurrentThread(),
|
|
TOKEN_QUERY,
|
|
TRUE,
|
|
&htoken ))
|
|
{
|
|
RevertToSelf();
|
|
return FALSE;
|
|
}
|
|
|
|
RevertToSelf();
|
|
}
|
|
|
|
fRet = CreateProcessAsUser( htoken,
|
|
lpApplicationName,
|
|
lpCommandLine,
|
|
NULL,
|
|
NULL,
|
|
bInheritHandles,
|
|
dwCreationFlags,
|
|
lpEnvironment,
|
|
lpCurrentDirectory,
|
|
lpStartupInfo,
|
|
lpProcessInformation );
|
|
|
|
if ( !_fClearText )
|
|
DBG_REQUIRE( CloseHandle( htoken ) );
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: TsLogonUser
|
|
|
|
SYNOPSIS: Validates a user's credentials, then sets the
|
|
impersonation for the current thread. In effect,
|
|
the current thread "becomes" the user.
|
|
|
|
ENTRY: pUserData - The user initiating the request (NULL for
|
|
the default account).
|
|
|
|
pszPassword - The user's password. May be NULL.
|
|
|
|
pfAsGuest - Will receive TRUE if the user was validated
|
|
with guest privileges.
|
|
|
|
pfAsAnonymous - Will receive TRUE if the user received the
|
|
services anonymous token
|
|
|
|
RETURNS: HANDLE - Token handle to use for impersonation or NULL
|
|
if the user couldn't be validated. Call GetLastError
|
|
for more information.
|
|
|
|
HISTORY:
|
|
KeithMo 18-Mar-1993 Created.
|
|
Johnl 14-Oct-1994 Mutilated for TCPSvcs
|
|
|
|
********************************************************************/
|
|
|
|
TS_TOKEN
|
|
TsLogonUser(
|
|
CHAR * pszUser,
|
|
CHAR * pszPassword,
|
|
BOOL * pfAsGuest,
|
|
BOOL * pfAsAnonymous,
|
|
LPTSVC_INFO psi
|
|
)
|
|
{
|
|
CHAR szAnonPwd[PWLEN+1];
|
|
CHAR szDomainAndUser[DNLEN+UNLEN+2];
|
|
CHAR szAnonUser[UNLEN+1];
|
|
CHAR * pszUserOnly;
|
|
CHAR * pszDomain;
|
|
TS_TOKEN hToken;
|
|
BOOL fUseDefaultDomain = TRUE;
|
|
|
|
//
|
|
// Make a quick copy of the anonymous user for this server for later
|
|
// usage
|
|
//
|
|
|
|
psi->LockThisForRead();
|
|
|
|
strcpy( szAnonUser,
|
|
psi->QueryAnonUserName() );
|
|
|
|
strcpy( szAnonPwd,
|
|
psi->QueryAnonUserPwd() );
|
|
|
|
psi->UnlockThis();
|
|
|
|
//
|
|
// Empty user defaults to the anonymous user
|
|
//
|
|
|
|
if ( !pszUser || *pszUser == '\0' )
|
|
{
|
|
pszUser = szAnonUser;
|
|
fUseDefaultDomain = FALSE;
|
|
}
|
|
|
|
//
|
|
// Validate parameters & state.
|
|
//
|
|
|
|
if ( strlen(pszUser) >= sizeof(szDomainAndUser) )
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return NULL;
|
|
}
|
|
|
|
DBG_ASSERT( pfAsGuest != NULL );
|
|
DBG_ASSERT( pfAsAnonymous != NULL );
|
|
|
|
if( pszPassword == NULL )
|
|
{
|
|
pszPassword = "";
|
|
}
|
|
else
|
|
{
|
|
if ( strlen(pszPassword) >= PWLEN )
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check for anonymous logon, note this includes the domain name
|
|
//
|
|
|
|
if ( !_stricmp( pszUser, szAnonUser ))
|
|
{
|
|
*pfAsAnonymous = TRUE;
|
|
pszPassword = szAnonPwd;
|
|
}
|
|
else
|
|
{
|
|
*pfAsAnonymous = FALSE;
|
|
}
|
|
|
|
//
|
|
// Save a copy of the domain\user so we can squirrel around
|
|
// with it a bit.
|
|
//
|
|
|
|
int cL = 0;
|
|
|
|
//
|
|
// prepend default logon domain if no domain
|
|
// and the default user name was not used
|
|
//
|
|
|
|
if ( fUseDefaultDomain
|
|
&& strchr( pszUser, '/' ) == NULL
|
|
&& strchr( pszUser, '\\' ) == NULL )
|
|
{
|
|
psi->LockThisForRead();
|
|
PCSTR pD = psi->QueryDefaultLogonDomain();
|
|
PCSTR pL;
|
|
if ( pD != NULL && pD[0] != '\0' )
|
|
{
|
|
if ( ( pL = strchr( pD, '\\' ) ) )
|
|
{
|
|
cL = pL - pD;
|
|
}
|
|
else
|
|
{
|
|
cL = strlen( pD );
|
|
}
|
|
memcpy( szDomainAndUser, pD, cL );
|
|
szDomainAndUser[ cL++ ] = '\\';
|
|
}
|
|
psi->UnlockThis();
|
|
}
|
|
|
|
strcpy( szDomainAndUser + cL, pszUser );
|
|
|
|
//
|
|
// Crack the name into domain/user components.
|
|
//
|
|
|
|
if ( !CrackUserAndDomain( szDomainAndUser,
|
|
&pszUserOnly,
|
|
&pszDomain ))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Validate the domain/user/password combo and create
|
|
// an impersonation token.
|
|
//
|
|
|
|
hToken = ValidateUser( pszDomain,
|
|
pszUserOnly,
|
|
pszPassword,
|
|
*pfAsAnonymous,
|
|
pfAsGuest,
|
|
psi->QueryLogonMethod() );
|
|
|
|
RtlZeroMemory( szAnonPwd, strlen(szAnonPwd) );
|
|
|
|
if( hToken == NULL )
|
|
{
|
|
STR strError;
|
|
const CHAR * psz[2];
|
|
|
|
psi->LoadStr( strError, GetLastError() );
|
|
|
|
psz[0] = pszUser;
|
|
psz[1] = strError.QueryStr();
|
|
|
|
psi->LogEvent( INET_SVCS_FAILED_LOGON,
|
|
2,
|
|
psz,
|
|
GetLastError() );
|
|
|
|
//
|
|
// Validation failure.
|
|
//
|
|
|
|
if ( GetLastError() == ERROR_LOGON_TYPE_NOT_GRANTED )
|
|
{
|
|
SetLastError( ERROR_ACCESS_DENIED );
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Success!
|
|
//
|
|
|
|
return hToken;
|
|
|
|
} // LogonUser
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: ValidateUser
|
|
|
|
SYNOPSIS: Validate a given domain/user/password tuple.
|
|
|
|
ENTRY: pszDomainName - The user's domain (NULL = current).
|
|
|
|
pszUserName - The user's name.
|
|
|
|
pszPassword - The user's (plaintext) password.
|
|
|
|
fAnonymous - TRUE if this is the anonymous user
|
|
|
|
pfAsGuest - Will receive TRUE if the user was validated
|
|
with guest privileges.
|
|
|
|
dwLogonMethod - interactive or batch
|
|
|
|
RETURNS: HANDLE - An impersonation token, NULL if user cannot
|
|
be validated. Call GetLastError for more information.
|
|
|
|
HISTORY:
|
|
KeithMo 07-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
TS_TOKEN ValidateUser(
|
|
CHAR * pszDomainName,
|
|
CHAR * pszUserName,
|
|
CHAR * pszPassword,
|
|
BOOL fAnonymous,
|
|
BOOL * pfAsGuest,
|
|
DWORD dwLogonMethod
|
|
)
|
|
{
|
|
CACHED_TOKEN * pct;
|
|
HANDLE hToken;
|
|
HANDLE hImpersonationToken = NULL;
|
|
|
|
//
|
|
// Is it in the cache? References the token if we find it
|
|
//
|
|
|
|
if ( FindCachedToken( pszUserName,
|
|
pszDomainName,
|
|
pszPassword,
|
|
fAnonymous, // Reset the TTL if anonymous
|
|
&pct ))
|
|
{
|
|
*pfAsGuest = pct->IsGuest();
|
|
return (TS_TOKEN) pct;
|
|
}
|
|
|
|
if ( !LogonUserA( pszUserName,
|
|
pszDomainName,
|
|
pszPassword,
|
|
dwLogonMethod, //LOGON32_LOGON_INTERACTIVE, //LOGON32_LOGON_NETWORK,
|
|
LOGON32_PROVIDER_DEFAULT,
|
|
&hToken ))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
|
|
if ( dwLogonMethod == LOGON32_LOGON_NETWORK )
|
|
{
|
|
hImpersonationToken = hToken;
|
|
if ( !DuplicateTokenEx( hImpersonationToken,
|
|
TOKEN_ALL_ACCESS,
|
|
NULL,
|
|
SecurityImpersonation,
|
|
TokenPrimary,
|
|
&hToken ))
|
|
{
|
|
CloseHandle( hImpersonationToken );
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
*pfAsGuest = IsGuestUser(hToken);
|
|
|
|
//
|
|
// Add this new token to the cache, hToken gets replaced by the
|
|
// cached token object
|
|
//
|
|
|
|
if ( !AddTokenToCache( pszUserName,
|
|
pszDomainName,
|
|
pszPassword,
|
|
hToken,
|
|
&pct ))
|
|
{
|
|
if ( hImpersonationToken != NULL )
|
|
{
|
|
CloseHandle( hImpersonationToken );
|
|
}
|
|
CloseHandle( hToken );
|
|
return NULL;
|
|
}
|
|
|
|
pct->SetGuest(*pfAsGuest);
|
|
|
|
|
|
if ( hImpersonationToken == NULL
|
|
&& !DuplicateToken( hToken, // hSourceToken
|
|
SecurityImpersonation, // Obtain impersonation
|
|
&hImpersonationToken) // hDestinationToken
|
|
) {
|
|
|
|
hImpersonationToken = NULL;
|
|
}
|
|
|
|
pct->SetImpersonationToken( hImpersonationToken);
|
|
|
|
return (TS_TOKEN) pct;
|
|
|
|
} // ValidateUser
|
|
|
|
|
|
|
|
|
|
|
|
# define MAX_TOKEN_USER_INFO (300)
|
|
BOOL
|
|
IsGuestUser(IN HANDLE hToken)
|
|
/*++
|
|
Given a user token, this function determines if the token belongs
|
|
to a guest user. It returns true if the token is a guest user token.
|
|
|
|
Arguments:
|
|
hToken - handle for the Security token for a user.
|
|
|
|
|
|
Returns:
|
|
BOOL.
|
|
|
|
History:
|
|
MuraliK 22-Jan-1996 Created.
|
|
--*/
|
|
{
|
|
BOOL fGuest = FALSE;
|
|
BYTE rgbInfo[MAX_TOKEN_USER_INFO];
|
|
DWORD cbTotalRequired;
|
|
|
|
//
|
|
// Get the user information associated with the token.
|
|
// Using this we can then query to find out if it belongs to a guest user.
|
|
//
|
|
|
|
if (GetTokenInformation( hToken,
|
|
TokenUser,
|
|
(LPVOID ) rgbInfo,
|
|
MAX_TOKEN_USER_INFO,
|
|
&cbTotalRequired)
|
|
) {
|
|
|
|
TOKEN_USER * pTokenUser = (TOKEN_USER *) rgbInfo;
|
|
PSID pSid = pTokenUser->User.Sid;
|
|
|
|
fGuest = EqualSid( pSid, g_psidGuestUser);
|
|
|
|
} else {
|
|
|
|
IF_DEBUG( DLL_SECURITY) {
|
|
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"GetTokenInformation(%08x) failed. Error = %d."
|
|
" sizeof(TOKEN_USER) = %d, cb = %d\n",
|
|
hToken,
|
|
GetLastError(),
|
|
sizeof(TOKEN_USER), cbTotalRequired
|
|
));
|
|
}
|
|
}
|
|
|
|
return ( fGuest);
|
|
|
|
} // IsGuestUser()
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: TsImpersonateUser
|
|
|
|
SYNOPSIS: Causes the current thread to impersonate the user
|
|
represented by the given impersonation token.
|
|
|
|
ENTRY: hToken - A handle to an impersonation token created
|
|
with ValidateUser. This is actually a pointer to
|
|
a cached token object.
|
|
|
|
RETURNS: BOOL - TRUE if successful, FALSE otherwise.
|
|
|
|
HISTORY:
|
|
KeithMo 07-Mar-1993 Created.
|
|
MuraliK 21-Feb-1996 Optimized Token caching
|
|
|
|
********************************************************************/
|
|
BOOL TsImpersonateUser( TS_TOKEN hToken )
|
|
{
|
|
HANDLE hTok;
|
|
|
|
IF_DEBUG( DLL_SECURITY )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"impersonating user token %08lX : Imperonation(%08lx)\n",
|
|
CTO_TO_TOKEN(hToken),
|
|
((CACHED_TOKEN *) hToken)->QueryImpersonationToken()
|
|
));
|
|
}
|
|
|
|
hTok = ((CACHED_TOKEN *) hToken)->QueryImpersonationToken();
|
|
if ( hTok == NULL) {
|
|
// if there is no impersonation token use the normal token itself.
|
|
hTok = CTO_TO_TOKEN(hToken);
|
|
}
|
|
|
|
#if DBG
|
|
if( !ImpersonateLoggedOnUser( hTok ) )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"cannot impersonate user token %08lX, error %08lX\n",
|
|
CTO_TO_TOKEN(hToken),
|
|
GetLastError() ));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
# else
|
|
|
|
return ( ImpersonateLoggedOnUser(hTok));
|
|
|
|
# endif // DBG
|
|
|
|
} // TsImpersonateUser
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: TsDeleteUserToken
|
|
|
|
SYNOPSIS: Deletes a token created with ValidateUser.
|
|
|
|
ENTRY: hToken - An impersonation token created with
|
|
ValidateUser.
|
|
|
|
RETURNS: BOOL - TRUE if successful, FALSE otherwise.
|
|
|
|
HISTORY:
|
|
KeithMo 07-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
BOOL TsDeleteUserToken(
|
|
TS_TOKEN hToken
|
|
)
|
|
{
|
|
NTSTATUS ntStatus = STATUS_SUCCESS;
|
|
|
|
CACHED_TOKEN::Dereference( (CACHED_TOKEN *) hToken );
|
|
|
|
return TRUE;
|
|
|
|
} // DeleteUserToken
|
|
|
|
HANDLE
|
|
TsTokenToHandle(
|
|
TS_TOKEN hToken
|
|
)
|
|
/*++
|
|
Description:
|
|
|
|
Converts the token object into a real impersonation handle
|
|
|
|
Arguments:
|
|
|
|
hToken - pointer to cached token object
|
|
|
|
Returns:
|
|
Handle of real impersonation token
|
|
--*/
|
|
{
|
|
DBG_ASSERT( hToken != NULL );
|
|
|
|
return CTO_TO_TOKEN( hToken );
|
|
}
|
|
|
|
BOOL
|
|
FindCachedToken(
|
|
IN const CHAR * pszUser,
|
|
IN const CHAR * pszDomain,
|
|
IN const CHAR * pszPwd,
|
|
IN BOOL fResetTTL,
|
|
OUT CACHED_TOKEN * * ppct
|
|
)
|
|
/*++
|
|
Description:
|
|
|
|
Checks to see if the specified user token handle is cached
|
|
|
|
Arguments:
|
|
|
|
pszUser - User name attempting to logon
|
|
pszDomain - Domain the user belongs to
|
|
pszPwd - password (case sensitive)
|
|
fResetTTL - Resets the TTL for this token
|
|
ppct - Receives token object
|
|
|
|
|
|
Returns:
|
|
TRUE on success and FALSE if the entry couldn't be found
|
|
|
|
--*/
|
|
{
|
|
LIST_ENTRY * pEntry;
|
|
CACHED_TOKEN * pct;
|
|
|
|
DBG_ASSERT( pszUser != NULL );
|
|
|
|
LockTokenCache();
|
|
|
|
for ( pEntry = TokenCacheList.Flink;
|
|
pEntry != &TokenCacheList;
|
|
pEntry = pEntry->Flink )
|
|
{
|
|
pct = CONTAINING_RECORD( pEntry, CACHED_TOKEN, _ListEntry );
|
|
|
|
if ( !_stricmp( pszUser, pct->_achUser ) &&
|
|
!_stricmp( pszDomain, pct->_achDomain ) &&
|
|
!strcmp( pszPwd, pct->_achPwd ) )
|
|
{
|
|
CACHED_TOKEN::Reference( pct );
|
|
*ppct = pct;
|
|
|
|
//
|
|
// Reset the TTL if this is the anonymous user so items in the
|
|
// cache don't get invalidated (token handle used as a
|
|
// discriminator)
|
|
|
|
if ( fResetTTL )
|
|
{
|
|
pct->_TTL = 2;
|
|
}
|
|
|
|
UnlockTokenCache();
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
UnlockTokenCache();
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
AddTokenToCache(
|
|
IN const CHAR * pszUser,
|
|
IN const CHAR * pszDomain,
|
|
IN const CHAR * pszPwd,
|
|
IN HANDLE hToken,
|
|
OUT CACHED_TOKEN * * ppct
|
|
)
|
|
/*++
|
|
Description:
|
|
|
|
Adds the specified token to the cache and converts the token handle
|
|
to a cached token object
|
|
|
|
Arguments:
|
|
|
|
pszUser - User name attempting to logon
|
|
pszDomain - Domain the user belongs to
|
|
pszPwd - Cast sensitive password
|
|
phToken - Contains the token handle that was just logged on
|
|
ppct - Receives cached token object
|
|
|
|
|
|
Returns:
|
|
TRUE on success and FALSE if the entry couldn't be found
|
|
|
|
--*/
|
|
{
|
|
CACHED_TOKEN * pct;
|
|
|
|
if ( strlen( pszUser ) > UNLEN ||
|
|
strlen( pszDomain ) > DNLEN ||
|
|
strlen( pszPwd ) > PWLEN )
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
pct = new CACHED_TOKEN;
|
|
|
|
if ( !pct )
|
|
{
|
|
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
|
|
return FALSE;
|
|
}
|
|
|
|
pct->_hToken = hToken;
|
|
pct->m_hImpersonationToken = NULL; // initialize to invalid value
|
|
strcpy( pct->_achUser, pszUser );
|
|
strcpy( pct->_achDomain, pszDomain );
|
|
strcpy( pct->_achPwd, pszPwd );
|
|
|
|
CACHED_TOKEN::Reference( pct );
|
|
|
|
//
|
|
// Add the token to the list, we don't care if there are duplicates
|
|
//
|
|
|
|
LockTokenCache();
|
|
|
|
InsertHeadList( &TokenCacheList, &pct->_ListEntry );
|
|
|
|
UnlockTokenCache();
|
|
|
|
*ppct = pct;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
VOID
|
|
WINAPI
|
|
TokenCacheScavenger(
|
|
IN const VOID * pContext
|
|
)
|
|
/*++
|
|
Description:
|
|
|
|
Decrements TTLs and removes tokens that have timed out
|
|
|
|
Arguments:
|
|
|
|
pContext - Not used
|
|
|
|
--*/
|
|
{
|
|
LIST_ENTRY * pEntry;
|
|
LIST_ENTRY * pEntryNext;
|
|
CACHED_TOKEN * pct;
|
|
|
|
|
|
LockTokenCache();
|
|
|
|
for ( pEntry = TokenCacheList.Flink;
|
|
pEntry != &TokenCacheList; )
|
|
{
|
|
pEntryNext = pEntry->Flink;
|
|
|
|
pct = CONTAINING_RECORD( pEntry, CACHED_TOKEN, _ListEntry );
|
|
|
|
if ( !(--pct->_TTL) )
|
|
{
|
|
IF_DEBUG( DLL_SECURITY )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"[TokenCacheScavenger] Timing out token for %s\n",
|
|
pct->_achUser ));
|
|
}
|
|
|
|
//
|
|
// This item has timed out, remove from the list
|
|
//
|
|
|
|
RemoveEntryList( &pct->_ListEntry );
|
|
pct->_ListEntry.Flink = NULL;
|
|
|
|
//
|
|
// Free any handles this user may still have open
|
|
//
|
|
|
|
TsCacheFlushUser( pct->_hToken, FALSE );
|
|
|
|
CACHED_TOKEN::Dereference( pct );
|
|
}
|
|
|
|
pEntry = pEntryNext;
|
|
}
|
|
|
|
UnlockTokenCache();
|
|
|
|
//
|
|
// Reschedule the scavenger
|
|
//
|
|
|
|
dwScheduleCookie = ScheduleWorkItem( (PFN_SCHED_CALLBACK) TokenCacheScavenger,
|
|
NULL,
|
|
cmsecTokenCacheTTL );
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: TsGetAnonymousPassword
|
|
|
|
SYNOPSIS: Retrieves the password for Anonymous logon.
|
|
|
|
ENTRY: pszPassword - Will receive the password. This buffer
|
|
must be at least PWLEN+1 characters in length.
|
|
|
|
RETURNS: BOOL - TRUE if password retrieved, FALSE otherwise.
|
|
|
|
HISTORY:
|
|
KeithMo 13-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
BOOL TsGetAnonymousPassword( CHAR * pszPassword,
|
|
LPTSVC_INFO ptsi )
|
|
{
|
|
BOOL fResult;
|
|
NTSTATUS ntStatus;
|
|
PUNICODE_STRING punicodePassword = NULL;
|
|
UNICODE_STRING unicodeSecret;
|
|
LSA_HANDLE hPolicy;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
|
|
|
|
//
|
|
// Open a policy to the remote LSA
|
|
//
|
|
|
|
InitializeObjectAttributes( &ObjectAttributes,
|
|
NULL,
|
|
0L,
|
|
NULL,
|
|
NULL );
|
|
|
|
ntStatus = LsaOpenPolicy( NULL,
|
|
&ObjectAttributes,
|
|
POLICY_ALL_ACCESS,
|
|
&hPolicy );
|
|
|
|
if ( !NT_SUCCESS( ntStatus ) )
|
|
{
|
|
SetLastError( LsaNtStatusToWinError( ntStatus ) );
|
|
return FALSE;
|
|
}
|
|
|
|
InitUnicodeString( &unicodeSecret, ptsi->QueryAnonPasswordSecretName() );
|
|
|
|
//
|
|
// Query the secret value.
|
|
//
|
|
|
|
ntStatus = LsaRetrievePrivateData( hPolicy,
|
|
&unicodeSecret,
|
|
&punicodePassword );
|
|
|
|
if( NT_SUCCESS(ntStatus) )
|
|
{
|
|
DWORD cch;
|
|
|
|
//
|
|
// Map it to ANSI.
|
|
//
|
|
|
|
cch = WideCharToMultiByte( CP_ACP,
|
|
WC_COMPOSITECHECK,
|
|
punicodePassword->Buffer,
|
|
-1,
|
|
pszPassword,
|
|
PWLEN + 1,
|
|
NULL,
|
|
NULL );
|
|
|
|
RtlZeroMemory( punicodePassword->Buffer,
|
|
punicodePassword->MaximumLength );
|
|
|
|
fResult = cch!=0;
|
|
}
|
|
else
|
|
{
|
|
fResult = NT_SUCCESS(ntStatus);
|
|
}
|
|
|
|
//
|
|
// Cleanup & exit.
|
|
//
|
|
|
|
if( punicodePassword != NULL )
|
|
{
|
|
LsaFreeMemory( (PVOID)punicodePassword );
|
|
}
|
|
|
|
LsaClose( hPolicy );
|
|
|
|
if ( !fResult )
|
|
SetLastError( LsaNtStatusToWinError( ntStatus ));
|
|
|
|
return fResult;
|
|
|
|
} // TsGetAnonymousPassword
|
|
|
|
BOOL
|
|
TsGetSecretW(
|
|
WCHAR * pszSecretName,
|
|
BUFFER * pbufSecret
|
|
)
|
|
/*++
|
|
Description:
|
|
|
|
Retrieves the specified unicode secret
|
|
|
|
Arguments:
|
|
|
|
pszSecretName - LSA Secret to retrieve
|
|
pbufSecret - Receives found secret
|
|
|
|
Returns:
|
|
TRUE on success and FALSE if any failure.
|
|
|
|
--*/
|
|
{
|
|
BOOL fResult;
|
|
NTSTATUS ntStatus;
|
|
PUNICODE_STRING punicodePassword = NULL;
|
|
UNICODE_STRING unicodeSecret;
|
|
LSA_HANDLE hPolicy;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
|
|
|
|
//
|
|
// Open a policy to the remote LSA
|
|
//
|
|
|
|
InitializeObjectAttributes( &ObjectAttributes,
|
|
NULL,
|
|
0L,
|
|
NULL,
|
|
NULL );
|
|
|
|
ntStatus = LsaOpenPolicy( NULL,
|
|
&ObjectAttributes,
|
|
POLICY_ALL_ACCESS,
|
|
&hPolicy );
|
|
|
|
if ( !NT_SUCCESS( ntStatus ) )
|
|
{
|
|
SetLastError( LsaNtStatusToWinError( ntStatus ) );
|
|
return FALSE;
|
|
}
|
|
|
|
InitUnicodeString( &unicodeSecret, pszSecretName );
|
|
|
|
//
|
|
// Query the secret value.
|
|
//
|
|
|
|
ntStatus = LsaRetrievePrivateData( hPolicy,
|
|
&unicodeSecret,
|
|
&punicodePassword );
|
|
|
|
if( NT_SUCCESS(ntStatus) )
|
|
{
|
|
DWORD cbNeeded;
|
|
|
|
cbNeeded = punicodePassword->Length + sizeof(WCHAR);
|
|
|
|
if ( !pbufSecret->Resize( cbNeeded ) )
|
|
{
|
|
ntStatus = STATUS_NO_MEMORY;
|
|
goto Failure;
|
|
}
|
|
|
|
memcpy( pbufSecret->QueryPtr(),
|
|
punicodePassword->Buffer,
|
|
punicodePassword->Length );
|
|
|
|
*((WCHAR *) pbufSecret->QueryPtr() +
|
|
punicodePassword->Length / sizeof(WCHAR)) = L'\0';
|
|
|
|
RtlZeroMemory( punicodePassword->Buffer,
|
|
punicodePassword->MaximumLength );
|
|
}
|
|
|
|
Failure:
|
|
|
|
fResult = NT_SUCCESS(ntStatus);
|
|
|
|
//
|
|
// Cleanup & exit.
|
|
//
|
|
|
|
if( punicodePassword != NULL )
|
|
{
|
|
LsaFreeMemory( (PVOID)punicodePassword );
|
|
}
|
|
|
|
LsaClose( hPolicy );
|
|
|
|
if ( !fResult )
|
|
SetLastError( LsaNtStatusToWinError( ntStatus ));
|
|
|
|
return fResult;
|
|
|
|
} // TsGetSecretW
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: ApiAccessCheck
|
|
|
|
SYNOPSIS: Impersonate the RPC client, then check for valid
|
|
access against our server security object.
|
|
|
|
ENTRY: maskDesiredAccess - Specifies the desired access mask.
|
|
This mask must not contain generic accesses.
|
|
|
|
RETURNS: DWORD - NO_ERROR if access granted, ERROR_ACCESS_DENIED
|
|
if access denied, other Win32 errors if something
|
|
tragic happened.
|
|
|
|
HISTORY:
|
|
KeithMo 26-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
DWORD TsApiAccessCheck( ACCESS_MASK maskDesiredAccess )
|
|
{
|
|
DWORD err;
|
|
BOOL fRet;
|
|
|
|
if ( maskDesiredAccess == TCP_QUERY_STATISTICS) {
|
|
|
|
//
|
|
// Statistics query should be allowed without authentication.
|
|
// Any body can bring up perfmon and request statistics.
|
|
//
|
|
|
|
return ( NO_ERROR);
|
|
}
|
|
|
|
//
|
|
// Impersonate the RPC client.
|
|
//
|
|
|
|
err = (DWORD)RpcImpersonateClient( NULL );
|
|
|
|
if( err != NO_ERROR )
|
|
{
|
|
IF_DEBUG( DLL_SECURITY )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"cannot impersonate rpc client, error %lu\n",
|
|
err ));
|
|
}
|
|
|
|
} else {
|
|
|
|
BOOL fAccessStatus;
|
|
BOOL fGenerateOnClose;
|
|
ACCESS_MASK maskAccessGranted;
|
|
|
|
//
|
|
// Validate access.
|
|
//
|
|
|
|
fRet = AccessCheckAndAuditAlarmW( SUBSYSTEM_NAME,
|
|
NULL,
|
|
OBJECTTYPE_NAME,
|
|
OBJECT_NAME,
|
|
sdApiObject,
|
|
maskDesiredAccess,
|
|
&TCPApiObjectMapping,
|
|
FALSE,
|
|
&maskAccessGranted,
|
|
&fAccessStatus,
|
|
&fGenerateOnClose );
|
|
|
|
if ( !fRet ) {
|
|
|
|
err = GetLastError();
|
|
}
|
|
|
|
//
|
|
// Revert to our former self.
|
|
//
|
|
|
|
DBG_REQUIRE( !RpcRevertToSelf() );
|
|
|
|
//
|
|
// Check the results.
|
|
//
|
|
|
|
if( err != NO_ERROR ) {
|
|
|
|
IF_DEBUG( DLL_SECURITY ) {
|
|
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"cannot check access, error %lu\n",
|
|
err ));
|
|
}
|
|
} else if( !fAccessStatus ) {
|
|
|
|
err = ERROR_ACCESS_DENIED;
|
|
|
|
IF_DEBUG( DLL_SECURITY ) {
|
|
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"bad access status, error %lu\n",
|
|
err ));
|
|
}
|
|
}
|
|
}
|
|
|
|
return (err);
|
|
|
|
} // ApiAccessCheck
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: CreateWellKnownSids
|
|
|
|
SYNOPSIS: Create some well-known SIDs used to create a security
|
|
descriptor for the API security object.
|
|
|
|
RETURNS: NTSTATUS - An NT Status code.
|
|
|
|
HISTORY:
|
|
KeithMo 26-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
|
|
DWORD CreateWellKnownSids( VOID )
|
|
{
|
|
DWORD error = NO_ERROR;
|
|
SID_IDENTIFIER_AUTHORITY siaWorld = SECURITY_WORLD_SID_AUTHORITY;
|
|
SID_IDENTIFIER_AUTHORITY siaNt = SECURITY_NT_AUTHORITY;
|
|
BOOL fRet;
|
|
|
|
fRet = AllocateAndInitializeSid( &siaWorld,
|
|
1,
|
|
SECURITY_WORLD_RID,
|
|
0,0,0,0,0,0,0,
|
|
&psidWorld );
|
|
|
|
if( fRet )
|
|
{
|
|
fRet = AllocateAndInitializeSid( &siaNt,
|
|
1,
|
|
SECURITY_LOCAL_SYSTEM_RID,
|
|
0,0,0,0,0,0,0,
|
|
&psidLocalSystem );
|
|
}
|
|
|
|
if( fRet )
|
|
{
|
|
fRet = AllocateAndInitializeSid( &siaNt,
|
|
2,
|
|
SECURITY_BUILTIN_DOMAIN_RID,
|
|
DOMAIN_ALIAS_RID_ADMINS,
|
|
0,0,0,0,0,0,
|
|
&psidAdmins );
|
|
}
|
|
|
|
if( fRet )
|
|
{
|
|
fRet = AllocateAndInitializeSid( &siaNt,
|
|
2,
|
|
SECURITY_BUILTIN_DOMAIN_RID,
|
|
DOMAIN_ALIAS_RID_SYSTEM_OPS,
|
|
0,0,0,0,0,0,
|
|
&psidServerOps );
|
|
}
|
|
|
|
if( fRet )
|
|
{
|
|
fRet = AllocateAndInitializeSid( &siaNt,
|
|
2,
|
|
SECURITY_BUILTIN_DOMAIN_RID,
|
|
DOMAIN_ALIAS_RID_POWER_USERS,
|
|
0,0,0,0,0,0,
|
|
&psidPowerUsers );
|
|
}
|
|
|
|
if( fRet )
|
|
{
|
|
USER_MODALS_INFO_2 * pUsrModals2 = NULL;
|
|
|
|
//
|
|
// Construct well-known-sid for Guest User on the local computer
|
|
//
|
|
// 1) Obtain the sid for the local machine's domain
|
|
// 2) copy domain sid to guest user sid
|
|
// 3) append DOMAIN_USER_RID_GUEST to the domain sid in GuestUser sid.
|
|
//
|
|
|
|
g_psidGuestUser = (PSID ) g_GuestUserSidBuffer;
|
|
fRet = ( (NetUserModalsGet(NULL, // local computer
|
|
2, // get level 2 information
|
|
(LPBYTE *) &pUsrModals2
|
|
) == 0)
|
|
&&
|
|
CopySid(GUEST_USER_SID_BUFFER_LEN - 4,// Buffer len
|
|
g_psidGuestUser, // psidDestination
|
|
pUsrModals2->usrmod2_domain_id // obtain domain sid.
|
|
)
|
|
);
|
|
|
|
//
|
|
// if successful append the DOMAIN_USER_RID_GUEST.
|
|
//
|
|
if ( fRet) {
|
|
|
|
DWORD lenSid = GetLengthSid( g_psidGuestUser);
|
|
CHAR nSubAuth;
|
|
|
|
//
|
|
// There is no Win32 way to set a SID value.
|
|
// We will munge around on our own.
|
|
// Pretty dangerous thing to do :-(
|
|
//
|
|
|
|
// increment the number of sub authorities
|
|
nSubAuth = *((UCHAR *) ((UCHAR *) g_psidGuestUser + 1));
|
|
nSubAuth++;
|
|
*((UCHAR *) ((UCHAR *) g_psidGuestUser + 1)) = nSubAuth;
|
|
|
|
// Store the new sub authority (Domain User Rid for Guest).
|
|
*((ULONG *) ((BYTE *) g_psidGuestUser + lenSid)) =
|
|
DOMAIN_USER_RID_GUEST;
|
|
} else {
|
|
|
|
g_psidGuestUser = NULL;
|
|
}
|
|
|
|
if ( pUsrModals2 != NULL) {
|
|
|
|
NET_API_STATUS ns = NetApiBufferFree( (LPVOID )pUsrModals2);
|
|
pUsrModals2 = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
if ( !fRet ) {
|
|
error = GetLastError( );
|
|
IF_DEBUG( DLL_SECURITY ) {
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"cannot create well-known sids\n" ));
|
|
}
|
|
}
|
|
|
|
return error;
|
|
|
|
} // CreateWellKnownSids
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: FreeWellKnownSids
|
|
|
|
SYNOPSIS: Frees the SIDs created with CreateWellKnownSids.
|
|
|
|
HISTORY:
|
|
KeithMo 26-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
VOID FreeWellKnownSids( VOID )
|
|
{
|
|
|
|
if( psidWorld != NULL )
|
|
{
|
|
FreeSid( psidWorld );
|
|
psidWorld = NULL;
|
|
}
|
|
|
|
if( psidLocalSystem != NULL )
|
|
{
|
|
FreeSid( psidLocalSystem );
|
|
psidLocalSystem = NULL;
|
|
}
|
|
|
|
if( psidAdmins != NULL )
|
|
{
|
|
FreeSid( psidAdmins );
|
|
psidAdmins = NULL;
|
|
}
|
|
|
|
if( psidServerOps != NULL )
|
|
{
|
|
FreeSid( psidServerOps );
|
|
psidServerOps = NULL;
|
|
}
|
|
|
|
if( psidPowerUsers != NULL )
|
|
{
|
|
FreeSid( psidPowerUsers );
|
|
psidPowerUsers = NULL;
|
|
}
|
|
|
|
} // FreeWellKnownSids
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: CreateApiSecurityObject
|
|
|
|
SYNOPSIS: Create an abstract security object used for validating
|
|
user access to the TCP Server APIs.
|
|
|
|
RETURNS: NTSTATUS - An NT Status code.
|
|
|
|
HISTORY:
|
|
KeithMo 26-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
DWORD CreateApiSecurityObject( VOID )
|
|
{
|
|
DWORD err;
|
|
ACE_DATA aces[] =
|
|
{
|
|
{
|
|
ACCESS_ALLOWED_ACE_TYPE,
|
|
0,
|
|
0,
|
|
TCP_ALL_ACCESS,
|
|
&psidLocalSystem
|
|
},
|
|
|
|
{
|
|
ACCESS_ALLOWED_ACE_TYPE,
|
|
0,
|
|
0,
|
|
TCP_ALL_ACCESS,
|
|
&psidAdmins
|
|
},
|
|
|
|
{
|
|
ACCESS_ALLOWED_ACE_TYPE,
|
|
0,
|
|
0,
|
|
TCP_ALL_ACCESS,
|
|
&psidServerOps
|
|
},
|
|
|
|
{
|
|
ACCESS_ALLOWED_ACE_TYPE,
|
|
0,
|
|
0,
|
|
TCP_ALL_ACCESS,
|
|
&psidPowerUsers
|
|
},
|
|
|
|
{
|
|
ACCESS_ALLOWED_ACE_TYPE,
|
|
0,
|
|
0,
|
|
TCP_GENERIC_EXECUTE,
|
|
&psidWorld
|
|
}
|
|
};
|
|
#define NUM_ACES (sizeof(aces) / sizeof(RTL_ACE_DATA))
|
|
|
|
err = INetCreateSecurityObject( aces,
|
|
NUM_ACES,
|
|
NULL,
|
|
NULL,
|
|
&TCPApiObjectMapping,
|
|
&sdApiObject );
|
|
|
|
|
|
IF_DEBUG( DLL_SECURITY )
|
|
{
|
|
if( err )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"cannot create api security object, error %d\n",
|
|
err ));
|
|
}
|
|
}
|
|
|
|
return err;
|
|
|
|
} // CreateApiSecurityObject
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: DeleteApiSecurityObject
|
|
|
|
SYNOPSIS: Frees the security descriptor created with
|
|
CreateApiSecurityObject.
|
|
|
|
HISTORY:
|
|
KeithMo 26-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
VOID DeleteApiSecurityObject( VOID )
|
|
{
|
|
INetDeleteSecurityObject( &sdApiObject );
|
|
|
|
} // DeleteApiSecurityObject
|
|
|
|
|
|
|
|
//
|
|
// Short routine to enable the TcbPrivilege for testing services running
|
|
// as an executable (rather then a service). Note that the account
|
|
// running the .exe must be added in User Manager's User Right's dialog
|
|
// under "Act as part of the OS"
|
|
//
|
|
|
|
VOID EnableTcbPrivilege(
|
|
VOID
|
|
)
|
|
{
|
|
HANDLE ProcessHandle;
|
|
HANDLE TokenHandle;
|
|
BOOL Result;
|
|
LUID TcbValue;
|
|
LUID AuditValue;
|
|
TOKEN_PRIVILEGES * TokenPrivileges;
|
|
CHAR buf[ 5 * sizeof(TOKEN_PRIVILEGES) ];
|
|
|
|
ProcessHandle = OpenProcess(
|
|
PROCESS_QUERY_INFORMATION,
|
|
FALSE,
|
|
GetCurrentProcessId()
|
|
);
|
|
|
|
if ( ProcessHandle == NULL ) {
|
|
|
|
//
|
|
// This should not happen
|
|
//
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
Result = OpenProcessToken (
|
|
ProcessHandle,
|
|
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
|
|
&TokenHandle
|
|
);
|
|
|
|
if ( !Result ) {
|
|
|
|
//
|
|
// This should not happen
|
|
//
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
//
|
|
// Find out the value of TakeOwnershipPrivilege
|
|
//
|
|
|
|
|
|
Result = LookupPrivilegeValue(
|
|
NULL,
|
|
"SeTcbPrivilege",
|
|
&TcbValue
|
|
);
|
|
|
|
if ( !Result ) {
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Need this for RPC impersonation (calls NtAccessCheckAndAuditAlarm)
|
|
//
|
|
|
|
Result = LookupPrivilegeValue(
|
|
NULL,
|
|
"SeAuditPrivilege",
|
|
&AuditValue
|
|
);
|
|
|
|
if ( !Result ) {
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Set up the privilege set we will need
|
|
//
|
|
|
|
TokenPrivileges = (TOKEN_PRIVILEGES *) buf;
|
|
|
|
TokenPrivileges->PrivilegeCount = 2;
|
|
TokenPrivileges->Privileges[0].Luid = TcbValue;
|
|
TokenPrivileges->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
|
TokenPrivileges->Privileges[1].Luid = AuditValue;
|
|
TokenPrivileges->Privileges[1].Attributes = SE_PRIVILEGE_ENABLED;
|
|
|
|
(VOID) AdjustTokenPrivileges (
|
|
TokenHandle,
|
|
FALSE,
|
|
TokenPrivileges,
|
|
sizeof(buf),
|
|
NULL,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
BOOL CrackUserAndDomain(
|
|
CHAR * pszDomainAndUser,
|
|
CHAR * * ppszUser,
|
|
CHAR * * ppszDomain
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Given a user name potentially in the form domain\user, zero terminates
|
|
the domain name and returns pointers to the domain name and the user name
|
|
|
|
Arguments:
|
|
|
|
pszDomainAndUser - Pointer to user name or domain and user name
|
|
ppszUser - Receives pointer to user portion of name
|
|
ppszDomain - Receives pointer to domain portion of name
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE otherwise (call GetLastError)
|
|
|
|
--*/
|
|
{
|
|
static CHAR szDefaultDomain[MAX_COMPUTERNAME_LENGTH+1];
|
|
|
|
//
|
|
// Crack the name into domain/user components.
|
|
//
|
|
|
|
*ppszDomain = pszDomainAndUser;
|
|
*ppszUser = strpbrk( pszDomainAndUser, "/\\" );
|
|
|
|
if( *ppszUser == NULL )
|
|
{
|
|
//
|
|
// No domain name specified, just the username so we assume the
|
|
// user is on the local machine
|
|
//
|
|
|
|
if ( !*szDefaultDomain )
|
|
{
|
|
if ( !GetDefaultDomainName( szDefaultDomain,
|
|
sizeof(szDefaultDomain)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
*ppszDomain = szDefaultDomain;
|
|
*ppszUser = pszDomainAndUser;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Both domain & user specified, skip delimiter.
|
|
//
|
|
|
|
**ppszUser = '\0';
|
|
(*ppszUser)++;
|
|
|
|
if( ( **ppszUser == '\0' ) ||
|
|
( **ppszUser == '\\' ) ||
|
|
( **ppszUser == '/' ) )
|
|
{
|
|
//
|
|
// Name is of one of the following (invalid) forms:
|
|
//
|
|
// "domain\"
|
|
// "domain\\..."
|
|
// "domain/..."
|
|
//
|
|
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: GetDefaultDomainName
|
|
|
|
SYNOPSIS: Fills in the given array with the name of the default
|
|
domain to use for logon validation.
|
|
|
|
ENTRY: pszDomainName - Pointer to a buffer that will receive
|
|
the default domain name.
|
|
|
|
cchDomainName - The size (in charactesr) of the domain
|
|
name buffer.
|
|
|
|
RETURNS: TRUE if successful, FALSE if not.
|
|
|
|
HISTORY:
|
|
KeithMo 05-Dec-1994 Created.
|
|
|
|
********************************************************************/
|
|
BOOL
|
|
GetDefaultDomainName(
|
|
CHAR * pszDomainName,
|
|
DWORD cchDomainName
|
|
)
|
|
{
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
NTSTATUS NtStatus;
|
|
INT Result;
|
|
DWORD err = 0;
|
|
LSA_HANDLE LsaPolicyHandle = NULL;
|
|
PPOLICY_ACCOUNT_DOMAIN_INFO DomainInfo = NULL;
|
|
|
|
//
|
|
// Open a handle to the local machine's LSA policy object.
|
|
//
|
|
|
|
InitializeObjectAttributes( &ObjectAttributes, // object attributes
|
|
NULL, // name
|
|
0L, // attributes
|
|
NULL, // root directory
|
|
NULL ); // security descriptor
|
|
|
|
NtStatus = LsaOpenPolicy( NULL, // system name
|
|
&ObjectAttributes, // object attributes
|
|
POLICY_EXECUTE, // access mask
|
|
&LsaPolicyHandle ); // policy handle
|
|
|
|
if( !NT_SUCCESS( NtStatus ) )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"cannot open lsa policy, error %08lX\n",
|
|
NtStatus ));
|
|
|
|
err = LsaNtStatusToWinError( NtStatus );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Query the domain information from the policy object.
|
|
//
|
|
|
|
NtStatus = LsaQueryInformationPolicy( LsaPolicyHandle,
|
|
PolicyAccountDomainInformation,
|
|
(PVOID *)&DomainInfo );
|
|
|
|
if( !NT_SUCCESS( NtStatus ) )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"cannot query lsa policy info, error %08lX\n",
|
|
NtStatus ));
|
|
|
|
err = LsaNtStatusToWinError( NtStatus );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Convert the name from UNICODE to ANSI.
|
|
//
|
|
|
|
Result = WideCharToMultiByte( CP_ACP,
|
|
0, // flags
|
|
(LPCWSTR)DomainInfo->DomainName.Buffer,
|
|
DomainInfo->DomainName.Length / sizeof(WCHAR),
|
|
pszDomainName,
|
|
cchDomainName - 1, // save room for '\0'
|
|
NULL,
|
|
NULL );
|
|
|
|
if( Result <= 0 )
|
|
{
|
|
err = GetLastError();
|
|
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"cannot convert domain name to ANSI, error %d\n",
|
|
err ));
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Ensure the ANSI string is zero terminated.
|
|
//
|
|
|
|
DBG_ASSERT( (DWORD)Result < cchDomainName );
|
|
|
|
pszDomainName[Result] = '\0';
|
|
|
|
//
|
|
// Success!
|
|
//
|
|
|
|
DBG_ASSERT( err == 0 );
|
|
|
|
Cleanup:
|
|
|
|
if( DomainInfo != NULL )
|
|
{
|
|
LsaFreeReturnBuffer( (PVOID)DomainInfo );
|
|
}
|
|
|
|
if( LsaPolicyHandle != NULL )
|
|
{
|
|
LsaClose( LsaPolicyHandle );
|
|
}
|
|
|
|
if ( err )
|
|
{
|
|
SetLastError( err );
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
} // GetDefaultDomainName
|