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.
 
 
 
 
 
 

1942 lines
46 KiB

/*++
Copyright (c) 1995 Microsoft Corporation
Module Name:
simauth.cpp
Abstract:
This module contains definition for the CSecurityCtx class.
Revision History:
--*/
#if !defined(dllexp)
#define dllexp __declspec( dllexport )
#endif // !defined( dllexp )
#ifdef __cplusplus
extern "C" {
#endif
# include <nt.h>
# include <ntrtl.h>
# include <nturtl.h>
# include <windows.h>
#ifdef __cplusplus
};
#endif
#include <dbgutil.h>
#include <tcpdll.hxx>
#include <inetinfo.h>
#include <simauth2.h>
#include <dbgtrace.h>
//
// SSL and SSPI related include files
//
extern "C" {
#include <rpc.h>
#define SECURITY_WIN32
#include <sspi.h>
#include <issperr.h>
#include <ntlmsp.h>
#include <ntdsapi.h>
}
//
// try/finally macros
//
#define START_TRY __try {
#define END_TRY }
#define TRY_EXCEPT } __except(EXCEPTION_EXECUTE_HANDLER) {
#define START_FINALLY } __finally {
//
// tracing
//
#define ENTER( _x_ ) TraceFunctEnter( _x_ );
#define LEAVE TraceFunctLeave( );
//
// Points to protocol blocks
//
extern BOOL uuencode( BYTE * bufin,
DWORD nbytes,
BUFFER * pbuffEncoded,
BOOL fBase64 );
//
// critsec protecting the following three items
//
CRITICAL_SECTION critProviderPackages;
inline void LockPackages( void ) { EnterCriticalSection( &critProviderPackages ); }
inline void UnlockPackages( void ) { LeaveCriticalSection( &critProviderPackages ); }
//
// "installed" packages the server should support
//
PAUTH_BLOCK ProviderPackages = NULL;
//
// count of "installed" packages the server should support
//
DWORD cProviderPackages = 0;
//
// memory for names of "installed" packages the server should support
//
LPSTR ProviderNames = NULL;
//
// Global gibraltar object and allow guest flag
//
BOOL CSecurityCtx::m_AllowGuest = TRUE;
BOOL CSecurityCtx::m_StartAnonymous = TRUE;
HANDLE CSecurityCtx::m_hTokenAnonymous = NULL;
inline BOOL
IsExperimental(
LPSTR Protocol
)
/*++
Routine Description:
determines if the security package is marked as experimental ( ie X- )
Arguments:
LPSTR: name of the protocol or authentication package
Return Value:
BOOL: TRUE if starts with X-
--*/
{
return (Protocol[0] == 'X' || Protocol[0] == 'x') && Protocol[1] == '-';
}
inline LPSTR
PackageName(
LPSTR Protocol
)
/*++
Routine Description:
returns the core security package name stripping X- if necessary
Arguments:
LPSTR: name of the protocol or authentication package
Return Value:
LPSTR: package name
--*/
{
return IsExperimental( Protocol ) ? Protocol + 2 : Protocol ;
}
BOOL
CSecurityCtx::Initialize(
BOOL fAllowGuest,
BOOL fStartAnonymous
)
/*++
Routine Description:
Activates the security package
Arguments:
PIIS_SERVER_INSTANCE is a ptr to a virtual server instance
Return Value:
TRUE, if successful. FALSE, otherwise.
--*/
{
ENTER("CSecurityCtx::Initialize")
m_AllowGuest = fAllowGuest;
m_StartAnonymous = fStartAnonymous;
if (m_StartAnonymous)
{
// This is only used by NNTP. And - NNTP only call it once in InitializeService
// so we don't need any ref count on it.
// Impersonate Anonymous token on this thread
if (!ImpersonateAnonymousToken(GetCurrentThread()))
{
DWORD dw = GetLastError();
ErrorTrace(0, "ImpersonateAnonymousToken() failed %x", dw);
return FALSE;
}
// Get current thread token
_ASSERT(m_hTokenAnonymous == NULL);
if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE, TRUE, &m_hTokenAnonymous))
{
ErrorTrace(0, "OpenThreadToken() failed %x", GetLastError());
::RevertToSelf();
return FALSE;
}
::RevertToSelf();
}
InitializeCriticalSection( &critProviderPackages );
LEAVE
return(TRUE);
} // Initialize
VOID
CSecurityCtx::Terminate(
VOID
)
/*++
Routine Description:
Terminates the security package
Arguments:
None.
Return Value:
None.
--*/
{
ENTER("CSecurityCtx::Terminate")
//
// Close cached credential handles
//
if (m_hTokenAnonymous)
{
CloseHandle(m_hTokenAnonymous);
m_hTokenAnonymous = NULL;
}
if ( ProviderPackages != NULL )
{
LocalFree( (PVOID)ProviderPackages );
ProviderPackages = NULL;
}
if ( ProviderNames != NULL )
{
LocalFree( (PVOID)ProviderNames );
ProviderNames = NULL;
}
DeleteCriticalSection( &critProviderPackages );
LEAVE
return;
} // Terminate
CSecurityCtx::CSecurityCtx(
PIIS_SERVER_INSTANCE pIisInstance,
DWORD AuthFlags,
DWORD InstanceAuthFlags,
TCP_AUTHENT_INFO *pTcpAuthInfo
) :
TCP_AUTHENT( AuthFlags ),
m_IsAuthenticated( FALSE ),
m_IsAnonymous( FALSE ),
m_IsClearText( FALSE ),
m_IsGuest( FALSE ),
m_LoginName( NULL ),
m_PackageName( NULL ),
m_dwInstanceAuthFlags(InstanceAuthFlags),
m_ProviderNames(NULL),
m_ProviderPackages(NULL),
m_cProviderPackages(0),
m_fBase64((AuthFlags & TCPAUTH_BASE64) ? TRUE : FALSE)
/*++
Routine Description:
Class constructor
Arguments:
None.
Return Value:
None
--*/
{
TraceFunctEnterEx( (LPARAM)this, "CSecurityCtx::CSecurityCtx");
m_szCleartextPackageName[0] = '\0';
m_szMembershipBrokerName[0] = '\0';
//
// The instance will cache this info from the metabase
// and pass it in on the constructor.
//
if ( pTcpAuthInfo )
{
m_TCPAuthentInfo.strAnonUserName.Copy(pTcpAuthInfo->strAnonUserName);
m_TCPAuthentInfo.strAnonUserPassword.Copy(pTcpAuthInfo->strAnonUserPassword);
m_TCPAuthentInfo.strDefaultLogonDomain.Copy(pTcpAuthInfo->strDefaultLogonDomain);
m_TCPAuthentInfo.dwLogonMethod = pTcpAuthInfo->dwLogonMethod;
m_TCPAuthentInfo.fDontUseAnonSubAuth = pTcpAuthInfo->fDontUseAnonSubAuth;
}
if ( m_StartAnonymous )
{
//
// m_dwInstanceAuthFlags is set at class's ctor
//
//
// if Anonymous logon is not allowed return immediately
//
if ( m_dwInstanceAuthFlags & INET_INFO_AUTH_ANONYMOUS )
{
m_IsAnonymous = TRUE;
m_IsAuthenticated = TRUE;
m_IsClearText = TRUE;
}
}
} // CSecurityCtx
CSecurityCtx::~CSecurityCtx(
VOID
)
/*++
Routine Description:
Class destructor
Arguments:
None.
Return Value:
None
--*/
{
//
// no reason to do the remainder of Reset()
//
if ( m_LoginName != NULL )
{
LocalFree( (PVOID)m_LoginName);
m_LoginName = NULL;
}
if ( m_PackageName != NULL )
{
LocalFree( (PVOID)m_PackageName);
m_PackageName = NULL;
}
} // ~CSecurityCtx
HANDLE
CSecurityCtx::QueryImpersonationToken()
/*++
Routine Description:
get impersonation token - overriding base class
if it's nntp anonymous, use m_hTokenAnonymous instead of going into TCP_AUTHENT
Arguments:
None.
Return Value:
token handle
--*/
{
if (m_IsAnonymous) return m_hTokenAnonymous;
else return TCP_AUTHENT::QueryImpersonationToken();
}
VOID
CSecurityCtx::Reset(
VOID
)
/*++
Routine Description:
resets the instance to reauth user
Arguments:
None.
Return Value:
None
--*/
{
if ( m_LoginName != NULL )
{
LocalFree( (PVOID)m_LoginName);
m_LoginName = NULL;
}
if ( m_PackageName != NULL )
{
LocalFree( (PVOID)m_PackageName);
m_PackageName = NULL;
}
m_IsAuthenticated = FALSE;
m_IsAnonymous = FALSE;
m_IsGuest = FALSE;
TCP_AUTHENT::Reset();
} // Reset
VOID
CSecurityCtx::SetCleartextPackageName(
LPSTR szCleartextPackageName,
LPSTR szMembershipBrokerName
)
/*++
Routine Description:
Sets the cleartext auth package name
Arguments:
szCleartextPackageName - Name of package
Return Value:
None
--*/
{
TraceFunctEnter("SetCleartextPackageName");
if (szCleartextPackageName)
lstrcpy(m_szCleartextPackageName, szCleartextPackageName);
else
m_szCleartextPackageName[0] = '\0';
if (szMembershipBrokerName)
lstrcpy(m_szMembershipBrokerName, szMembershipBrokerName);
else
m_szMembershipBrokerName[0] = '\0';
DebugTrace(0,"CleartextPackageName is %s MembershipBrokerName is %s", m_szCleartextPackageName, m_szMembershipBrokerName);
}
BOOL
CSecurityCtx::SetInstanceAuthPackageNames(
DWORD cProviderPackages,
LPSTR ProviderNames,
PAUTH_BLOCK ProviderPackages)
/*++
Routine Description:
set the supported SSPI packages per instance basis
--*/
{
TraceFunctEnter( "CSecurityCtx::SetInstanceAuthPackageNames" );
if (cProviderPackages == 0 || ProviderNames == NULL || ProviderPackages == NULL)
{
ErrorTrace( 0, "Invalid Parameters");
return FALSE;
}
m_cProviderPackages = cProviderPackages;
m_ProviderNames = ProviderNames;
m_ProviderPackages = ProviderPackages;
return TRUE;
}
BOOL
CSecurityCtx::GetInstanceAuthPackageNames(
OUT LPBYTE ReplyString,
IN OUT PDWORD ReplySize,
IN PKG_REPLY_FMT PkgFmt
)
/*++
Routine Description:
get the supported SSPI packages per instance basis.
different than set in that the packages are returned using various
delimeters to make it easier for the client to format the buffer.
Arguments:
ReplyString - Reply to be sent to client.
ReplySize - Size of the reply.
PkgFmt - Format of the reply string.
Return Value:
BOOL: successful ??
--*/
{
TraceFunctEnter( "CSecurityCtx::GetInstanceAuthPackageNames" );
LPSTR pszNext = (LPSTR)ReplyString;
DWORD cbReply = 0;
DWORD cbDelim;
LPSTR pbDelim;
_ASSERT(PkgFmt == PkgFmtSpace || PkgFmt == PkgFmtCrLf);
switch (PkgFmt)
{
case PkgFmtCrLf:
{
pbDelim = "\r\n";
cbDelim = 2;
break;
}
case PkgFmtSpace:
default:
{
pbDelim = " ";
cbDelim = 1;
break;
}
}
//
// while in this loop ensure the contents dont change
//
for ( DWORD i=0; i < m_cProviderPackages; i++ )
{
LPSTR pszName = m_ProviderPackages[i].Name;
DWORD cbName = lstrlen( pszName );
//
// +1 is for trailing space
//
if ( cbReply + cbName + cbDelim > *ReplySize )
{
break;
}
else
{
CopyMemory( pszNext, pszName, cbName );
//
// add the space separator
//
CopyMemory(pszNext + cbName, pbDelim, cbDelim);
//
// inc for loop pass
//
cbReply += cbName + cbDelim;
pszNext += cbName + cbDelim;
}
}
//
// stamp the final trailing space with a NULL char
//
if ( cbReply > 0 && PkgFmt == PkgFmtSpace)
{
cbReply--;
ReplyString[ cbReply ] = '\0';
DebugTrace( 0, "Protocols: %s", ReplyString );
}
*ReplySize = cbReply;
return TRUE;
}
BOOL
CSecurityCtx::SetAuthPackageNames(
LPSTR lpMultiSzProviders,
DWORD cchMultiSzProviders
)
/*++
Routine Description:
set the supported SSPI packages
Arguments:
lpMultiSzProviders is the same format as returned by
RegQueryValueEx for REG_MULTI_SZ values
Return Value:
BOOL: successful ??
--*/
{
TraceFunctEnter( "CSecurityCtx::SetAuthPackageNames" );
LPSTR psz, pszCopy = NULL;
DWORD i, cProviders;
PAUTH_BLOCK pBlock = NULL;
if ( lpMultiSzProviders == NULL || cchMultiSzProviders == 0 )
{
ErrorTrace( 0, "Invalid Parameters: 0x%08X, %d",
lpMultiSzProviders, cchMultiSzProviders );
goto error;
}
pszCopy = (LPSTR)LocalAlloc( 0, cchMultiSzProviders );
if ( pszCopy == NULL )
{
ErrorTrace( 0, "LocalAlloc failed: %d", GetLastError() );
goto error;
}
CopyMemory( pszCopy, lpMultiSzProviders, cchMultiSzProviders );
//
// cchMultiSzProviders-1 is to avoid adding an additional provider
// for the terminating NULL char
//
for ( i=0, cProviders=0, psz=pszCopy; i<cchMultiSzProviders-1; i++, psz++ )
{
if ( *psz == '\0' )
{
cProviders++;
}
}
//
// ensure we're at the end and hence at the second terminating NULL char
//
_ASSERT( *psz == '\0' );
if ( cProviders < 1 )
{
ErrorTrace( 0, "No valid providers were found" );
goto error;
}
pBlock = (PAUTH_BLOCK)LocalAlloc( 0, cProviders * sizeof(AUTH_BLOCK) );
if ( pBlock == NULL )
{
ErrorTrace( 0, "AUTH_BLOCK LocalAlloc failed: %d", GetLastError() );
goto error;
}
//
// start at 1 since 0 indicates the Invalid protocol
//
for ( i=0, psz=pszCopy; i<cProviders; i++ )
{
//
// this would be the place to check whether the package was valid
//
DebugTrace( 0, "Protocol: %s, Package: %s", psz, PackageName(psz) );
pBlock[i].Name = psz;
psz += lstrlen(psz) + 1;
}
//
// set global to new value; autoupdate will require critsec and mem free
//
LockPackages();
//
// if we're replacing already set packages; free their memory
//
if ( ProviderPackages != NULL )
{
LocalFree( (PVOID)ProviderPackages );
ProviderPackages = NULL;
}
if ( ProviderNames != NULL )
{
LocalFree( (PVOID)ProviderNames );
ProviderNames = NULL;
}
ProviderPackages = pBlock;
cProviderPackages = cProviders;
ProviderNames = pszCopy;
UnlockPackages();
return TRUE;
error:
if ( pszCopy != NULL )
{
DebugTrace( 0, "Cleaning up pszCopy" );
_VERIFY( LocalFree( (LPVOID)pszCopy ) == NULL );
}
if ( pBlock != NULL )
{
DebugTrace( 0, "Cleaning up pBlock" );
_VERIFY( LocalFree( (LPVOID)pBlock ) == NULL );
}
return FALSE;
} // SetAuthPackageNames
BOOL
CSecurityCtx::GetAuthPackageNames(
OUT LPBYTE ReplyString,
IN OUT PDWORD ReplySize,
IN PKG_REPLY_FMT PkgFmt
)
/*++
Routine Description:
get the supported SSPI packages
different than set in that the packages are returned using various
delimeters to make it easier for the client to format the buffer.
Arguments:
ReplyString - Reply to be sent to client.
ReplySize - Size of the reply.
PkgFmt - Format of the reply string.
Return Value:
BOOL: successful ??
--*/
{
TraceFunctEnter( "CSecurityCtx::GetAuthPackageNames" );
LPSTR pszNext = (LPSTR)ReplyString;
DWORD cbReply = 0;
DWORD cbDelim;
LPSTR pbDelim;
_ASSERT(PkgFmt == PkgFmtSpace || PkgFmt == PkgFmtCrLf);
switch (PkgFmt)
{
case PkgFmtCrLf:
{
pbDelim = "\r\n";
cbDelim = 2;
break;
}
case PkgFmtSpace:
default:
{
pbDelim = " ";
cbDelim = 1;
break;
}
}
//
// while in this loop ensure the contents dont change
//
LockPackages();
for ( DWORD i=0; i<cProviderPackages; i++ )
{
LPSTR pszName = ProviderPackages[i].Name;
DWORD cbName = lstrlen( pszName );
//
// +1 is for trailing space
//
if ( cbReply + cbName + cbDelim > *ReplySize )
{
break;
}
else
{
CopyMemory( pszNext, pszName, cbName );
//
// add the space separator
//
CopyMemory(pszNext + cbName, pbDelim, cbDelim);
//
// inc for loop pass
//
cbReply += cbName + cbDelim;
pszNext += cbName + cbDelim;
}
}
//
// free access to the list
//
UnlockPackages();
//
// stamp the final trailing space with a NULL char
//
if ( cbReply > 0 && PkgFmt == PkgFmtSpace)
{
cbReply--;
ReplyString[ cbReply ] = '\0';
DebugTrace( 0, "Protocols: %s", ReplyString );
}
*ReplySize = cbReply;
return TRUE;
} // GetAuthPackageNames
BOOL
CSecurityCtx::ProcessUser(
IN PIIS_SERVER_INSTANCE pIisInstance,
IN LPSTR pszUser,
OUT REPLY_LIST* pReply
)
/*++
Routine Description:
Process AUTHINFO user command
Arguments:
pszUser - user name
pReply - ptr to reply string id
Return Value:
successful
--*/
{
TraceFunctEnterEx( (LPARAM)this, "CSecurityCtx::ProcessUser");
DWORD nameLen;
//
// if we're already logged on reset the user credentials
//
if ( m_IsAuthenticated )
{
Reset();
}
//
// Don't allow user to overwrite the existing name.
//
if ( m_LoginName != NULL )
{
*pReply = SecSyntaxErr;
return FALSE;
}
if ( (m_dwInstanceAuthFlags & INET_INFO_AUTH_CLEARTEXT) == 0 )
{
*pReply = SecPermissionDenied;
return FALSE;
}
if ( pszUser == NULL )
{
*pReply = SecSyntaxErr;
return FALSE;
}
nameLen = lstrlen( pszUser ) + 1;
//
// if anonymous is not allowed; fail a zero length user name
//
if ( nameLen <= 1 &&
(m_dwInstanceAuthFlags & INET_INFO_AUTH_ANONYMOUS) == 0 )
{
*pReply = SecPermissionDenied;
return FALSE;
}
m_LoginName = (PCHAR)LocalAlloc( 0, nameLen );
if ( m_LoginName == NULL )
{
*pReply = SecInternalErr;
return FALSE;
}
CopyMemory( m_LoginName, pszUser, nameLen );
//
// Tell client to send the password
//
*pReply = SecNeedPwd;
return TRUE;
}
BOOL
CSecurityCtx::ShouldUseMbs( void )
/*++
Routine Description:
Determines if MBS_BASIC is being used.
Arguments:
Return Value:
TRUE if successful
--*/
{
CHAR *pszCtPackage;
//
// Simple heuristics: if we have a cleartext package
// name, we will use MBS if the current package name
// is NULL,
//
pszCtPackage = PackageName(m_szCleartextPackageName);
if (pszCtPackage[0] != '\0' && !m_PackageName)
{
return(TRUE);
}
return(FALSE);
}
#define __STRCPYX(s, cs, len) \
lstrcpy((s), (cs)); (s) += (len)
BOOL
CSecurityCtx::MbsBasicLogon(
IN LPSTR pszUser,
IN LPSTR pszPass,
OUT BOOL *pfAsGuest,
OUT BOOL *pfAsAnonymous
)
/*++
Routine Description:
Perform a MBS Basic logon sequence
Arguments:
pszUser - Username, can be NULL
pszPass - Password, may be NULL
pfAsGuest - Returns TRUE is logged on as guest
pfAsAnonymous - Returns TRUE is anonymous account used
pReply - Pointer to reply string id
psi - Server information block
Return Value:
successful
--*/
{
TraceFunctEnterEx( (LPARAM)this, "CSecurityCtx::MbsBasicLogon");
BYTE pbBlob[MAX_ACCT_DESC_LEN];
DWORD dwBlobLength;
BUFFER OutBuf;
DWORD dwOutBufSize;
BOOL fMoreData;
BOOL fRet;
CHAR *pTemp;
SecBuffer InSecBuff[2];
SecBufferDesc InSecBuffDesc;
// PU2_BASIC_AUTHENTICATE_MSG pAuthMsg = (PU2_BASIC_AUTHENTICATE_MSG)pbBlob;
_ASSERT(pfAsGuest);
_ASSERT(pfAsAnonymous);
if (!pszUser || !pszPass || !pfAsGuest || !pfAsAnonymous)
{
SetLastError(ERROR_INVALID_PARAMETER);
ErrorTrace((LPARAM)this, "Input parameters NULL");
return(FALSE);
}
*pfAsGuest = FALSE;
*pfAsAnonymous = FALSE;
if (lstrlen(pszUser) > UNLEN)
{
SetLastError(ERROR_INVALID_PARAMETER);
ErrorTrace((LPARAM)this, "Username too long");
return(FALSE);
}
if (lstrlen(pszPass) > PWLEN)
{
SetLastError(ERROR_INVALID_PARAMETER);
ErrorTrace((LPARAM)this, "Password too long");
return(FALSE);
}
//
// With all the user name and password information, we will
// build up a BLOB and then simply call Converse()
//
// The BLOB contains credential string of the format:
// user:password\0
//
pTemp = (CHAR *)pbBlob;
// __STRCPYX(pTemp, psi->QueryServiceName(), lstrlen(psi->QueryServiceName()));
// __STRCPYX(pTemp, ":", 1);
__STRCPYX(pTemp, pszUser, lstrlen(pszUser));
__STRCPYX(pTemp, ":", 1);
__STRCPYX(pTemp, pszPass, lstrlen(pszPass));
//
// Get the size of everything, not just the credentials
//
dwBlobLength = (DWORD)(pTemp - (CHAR *)pbBlob) + 1;
//
// U2 now requires 2 SecBuffer for MBS_BASIC
//
InSecBuffDesc.ulVersion = 0;
InSecBuffDesc.cBuffers = 2;
InSecBuffDesc.pBuffers = &InSecBuff[0];
InSecBuff[0].cbBuffer = dwBlobLength;
InSecBuff[0].BufferType = SECBUFFER_TOKEN;
InSecBuff[0].pvBuffer = pbBlob;
DebugTrace(0,"MbsBasicLogon: cleartext is %s membership is %s", m_szCleartextPackageName, m_szMembershipBrokerName);
BYTE pbServer[sizeof(WCHAR)*MAX_PATH+sizeof(UNICODE_STRING)];
UNICODE_STRING* pusU2Server = (UNICODE_STRING*)pbServer;
WCHAR* pwszU2Server = (WCHAR*)((UNICODE_STRING*)pbServer+1);
if (!MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, m_szMembershipBrokerName, -1, pwszU2Server,
MAX_PATH))
{
return FALSE;
}
pusU2Server->Length = (USHORT) (wcslen(pwszU2Server) * sizeof(WCHAR));
pusU2Server->MaximumLength = (USHORT)(MAX_PATH * sizeof(WCHAR));
pusU2Server->Buffer = (PWSTR)sizeof(UNICODE_STRING);
InSecBuff[1].cbBuffer = sizeof(pbServer);
InSecBuff[1].BufferType = SECBUFFER_PKG_PARAMS;
InSecBuff[1].pvBuffer = (PVOID)pbServer;
//
// Just call Converse() to do all the work!
// We blow away tha anon password immediately after we're done.
//
fRet = ConverseEx(&InSecBuffDesc,
NULL, // SecBuffer is not encoded
&OutBuf,
&dwOutBufSize,
&fMoreData,
&m_TCPAuthentInfo,
m_szCleartextPackageName,
NULL, NULL, NULL);
//
// Check the return values
//
if (fRet)
{
StateTrace((LPARAM)this, "Authentication succeeded");
//
// This is a one-shot deal, so we do not expect any more data
//
if (fMoreData)
{
SetLastError(ERROR_MORE_DATA);
ErrorTrace((LPARAM)this, "Internal error: More data not expected");
return(FALSE);
}
//
// We should also expect zero returned buffer length
//
// _ASSERT(dwOutBufSize == 0);
}
return(fRet);
}
BOOL
CSecurityCtx::ProcessPass(
IN PIIS_SERVER_INSTANCE pIisInstance,
IN LPSTR pszPass,
OUT REPLY_LIST* pReply
)
/*++
Routine Description:
Process AUTHINFO user command
Arguments:
pszPass - password
pReply - ptr to reply string id
Return Value:
successful
--*/
{
TraceFunctEnterEx( (LPARAM)this, "CSecurityCtx::ProcessPass");
DWORD dwTick;
BOOL fRet;
TCP_AUTHENT_INFO tai; // use default ctor
//
// give username first
//
if ( m_LoginName == NULL )
{
*pReply = SecNoUsername;
return FALSE;
}
if ( pszPass == NULL )
{
*pReply = SecSyntaxErr;
return FALSE;
}
//
// Get tick count for tracing
//
dwTick = GetTickCount();
//
// Added for U2 BASIC authentication: We check if the current
// package is the U2 BASIC package. If not, we do the usual
// ClearTextLogon() call. If so, we will call MBS
//
if (ShouldUseMbs())
{
//
// This uses U2 BASIC
//
StateTrace((LPARAM)pIisInstance, "Doing Cleartext auth with package: <%s>",
m_szCleartextPackageName);
fRet = MbsBasicLogon(m_LoginName,
pszPass,
&m_IsGuest,
&m_IsAnonymous);
}
else
{
//
// K2_TODO: need to fill in TCP_AUTHENT_INFO !
//
tai.dwLogonMethod = LOGON32_LOGON_NETWORK;
fRet = ClearTextLogon(
m_LoginName,
pszPass,
&m_IsGuest,
&m_IsAnonymous,
pIisInstance,
&tai
);
}
//
// Trace ticks for logon
//
dwTick = GetTickCount() - dwTick;
DebugTrace( (LPARAM)this,
"ClearTextLogon took %u ticks", dwTick );
if ( fRet )
{
if ( m_IsGuest && m_AllowGuest == FALSE )
{
ErrorTrace( (LPARAM)this, "Guest acct disallowed %s",
m_LoginName );
}
else if ( m_IsAnonymous &&
( m_dwInstanceAuthFlags & INET_INFO_AUTH_ANONYMOUS ) == 0 )
{
ErrorTrace( (LPARAM)this, "Anonymous logon disallowed %s",
m_LoginName );
}
else
{
*pReply = m_IsAnonymous ? SecAuthOkAnon : SecAuthOk ;
return m_IsAuthenticated = TRUE;
}
}
else
{
ErrorTrace( (LPARAM)this,
"ClearTextLogon failed for %s: %d",
m_LoginName, GetLastError());
//
// reset the logon session to force the app to start over again
//
Reset();
}
*pReply = SecPermissionDenied;
return FALSE;
}
BOOL
CSecurityCtx::ProcessTransact(
IN PIIS_SERVER_INSTANCE pIisInstance,
IN LPSTR Blob,
IN OUT LPBYTE ReplyString,
IN OUT PDWORD ReplySize,
OUT REPLY_LIST* pReply,
IN DWORD BlobLength
)
/*++
Routine Description:
Process AUTHINFO user command
Arguments:
pszPass - password
pReply - ptr to reply string id
Return Value:
successful
--*/
{
TraceFunctEnterEx( (LPARAM)this, "CSecurityCtx::ProcessTransact");
//
// if we're already logged on reset the user credentials
//
if ( m_IsAuthenticated )
{
Reset();
}
//
// If this is a new session, the first transact is the
// protocol name
//
if ( m_PackageName == NULL )
{
PAUTH_BLOCK pBlock;
LPSTR protocol;
DWORD i;
BOOL bFound = FALSE;
if ( (m_dwInstanceAuthFlags & INET_INFO_AUTH_NT_AUTH) == 0 )
{
*pReply = SecPermissionDenied;
return FALSE;
}
if ( (protocol = Blob) == NULL )
{
*pReply = SecSyntaxErr;
return FALSE;
}
//
// if its an X- protocol strip the X- header
//
protocol = PackageName( protocol );
//
// See if this is a supported protocol
// while in this loop ensure the contents dont change
//
LockPackages();
for ( i=0; i < m_cProviderPackages; i++ )
{
pBlock = &m_ProviderPackages[i];
//
// get the name of the Block's package and strip any X-
//
LPSTR pszPackageName = PackageName( pBlock->Name );
if ( lstrcmpi( pszPackageName, protocol ) == 0 )
{
//
// See if the package chosen was GSSAPI. If it was, then set
// m_PackageName to "Negotiate". This is required because the
// SASL GSSAPI mechanism maps to the NT Negotiate package
//
LPSTR pszPackageNameToUse = pszPackageName;
if (lstrcmpi( pszPackageName, "GSSAPI") == 0)
pszPackageNameToUse = "Negotiate";
DWORD cb = lstrlen( pszPackageNameToUse ) + 1;
DebugTrace( (LPARAM)this,
"Found: %s, Protocol %s, NT Package %s",
pszPackageName, pBlock->Name, pszPackageNameToUse );
//
// maintain a local copy of the package name in case
// the list changes during the negotiation
//
m_PackageName = (PCHAR)LocalAlloc( 0, cb );
if ( m_PackageName == NULL )
{
*pReply = SecInternalErr;
//
// free access to the list
//
UnlockPackages();
return FALSE;
}
CopyMemory( m_PackageName, pszPackageNameToUse, cb );
bFound = TRUE;
break;
}
}
//
// free access to the list
//
UnlockPackages();
if ( bFound == FALSE )
{
//
// not found
//
ErrorTrace( (LPARAM)this,
"could not find: %s", protocol );
//
// here's where we need to build the response string
// app needs to call us to enum the installed packages
// to the app can properly format the enumerated
// "installed" packages within a protocol specific err msg
//
*pReply = SecProtNS;
return FALSE;
}
else
{
//
// +OK response
//
*pReply = SecProtOk;
return TRUE;
}
}
else
{
DWORD nBuff;
BOOL moreData;
BOOL fRet;
DWORD dwTick;
BUFFER outBuff;
if ( Blob == NULL )
{
*pReply = SecSyntaxErr;
return FALSE;
}
//
// Get tick count for tracing
//
dwTick = GetTickCount();
// m_PackageName must already be set by now
_ASSERT(m_PackageName);
if (!lstrcmpi(m_PackageName, "DPA") && m_szMembershipBrokerName && m_szMembershipBrokerName[0]) {
SecBuffer InSecBuff[2];
SecBufferDesc InSecBuffDesc;
BUFFER DecodedBuf[2]; // scratch pad for decoding the sec buff
DebugTrace(NULL,"DPA broker server is %s", m_szMembershipBrokerName);
//
// for DPA authentication, we need to pass in 2 sec buffers
//
InSecBuffDesc.ulVersion = 0;
InSecBuffDesc.cBuffers = 2;
InSecBuffDesc.pBuffers = &InSecBuff[0];
//
// Fill in the first sec buffer
// This contains the security blob sent by client, and is already encoded
//
InSecBuff[0].cbBuffer = BlobLength ? BlobLength : lstrlen(Blob);
InSecBuff[0].BufferType = SECBUFFER_TOKEN;
InSecBuff[0].pvBuffer = Blob;
//
// Fill in the second sec buffer, which contains the U2 broker id
// Since ConverseEx will decode both sec buf, we need to encode
// the second buf before calling ConverseEx
//
BYTE pbServer[sizeof(WCHAR)*MAX_PATH+sizeof(UNICODE_STRING)];
UNICODE_STRING* pusU2Server = (UNICODE_STRING*)pbServer;
WCHAR* pwszU2Server = (WCHAR*)((UNICODE_STRING*)pbServer+1);
BUFFER EncBuf;
if (!MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, m_szMembershipBrokerName, -1, pwszU2Server,
MAX_PATH))
{
ErrorTrace((LPARAM)this, "MultiByteToWideChar FAILED");
return FALSE;
}
pusU2Server->Length = (USHORT) (wcslen(pwszU2Server) * sizeof(WCHAR));
pusU2Server->MaximumLength = (USHORT)(MAX_PATH * sizeof(WCHAR));
pusU2Server->Buffer = (PWSTR)sizeof(UNICODE_STRING);
DWORD dwSize = MAX_PATH + sizeof(UNICODE_STRING);
if (!uuencode((PBYTE)pbServer, dwSize, &EncBuf, m_fBase64)) {
ErrorTrace((LPARAM)this, "uuencode FAILED");
return FALSE;
}
InSecBuff[1].cbBuffer = EncBuf.QuerySize();
InSecBuff[1].BufferType = SECBUFFER_PKG_PARAMS;
InSecBuff[1].pvBuffer = EncBuf.QueryPtr();
fRet = ConverseEx(&InSecBuffDesc,
DecodedBuf,
&outBuff,
&nBuff,
&moreData,
&m_TCPAuthentInfo,
m_PackageName,
NULL, NULL, NULL);
}
else {
//
// for non-DPA authentication (i.e. NTLM, etc)
//
fRet = Converse(Blob,
BlobLength ? BlobLength : lstrlen(Blob),
&outBuff,
&nBuff,
&moreData,
&m_TCPAuthentInfo,
m_PackageName);
}
//
// Trace ticks for conversing
//
dwTick = GetTickCount() - dwTick;
DebugTrace((LPARAM)this, "Converse(%s) took %u ticks", m_PackageName, dwTick );
if ( fRet )
{
DebugTrace( (LPARAM)this,
"Converse ret TRUE, nBuff: %d, moredata %d",
nBuff, moreData );
if ( moreData )
{
_ASSERT( nBuff != 0 );
CopyMemory( ReplyString, outBuff.QueryPtr(), nBuff );
*ReplySize = nBuff;
//
// reply equals SecNull to tell the app to send
// this buffer to remote client/server
//
*pReply = SecNull;
return TRUE;
} else {
STR strUser; // was BUFFER buff pre-K2
if ( m_IsGuest && m_AllowGuest == FALSE )
{
SetLastError( ERROR_LOGON_FAILURE );
*pReply = SecPermissionDenied;
return FALSE;
}
if ( TCP_AUTHENT::QueryUserName( &strUser ) )
{
m_LoginName = (PCHAR)LocalAlloc( 0, strUser.QuerySize() );
if ( m_LoginName != NULL )
{
CopyMemory( m_LoginName,
strUser.QueryPtr(),
strUser.QuerySize() );
DebugTrace( (LPARAM)this,
"Username: %s, size %d",
m_LoginName, strUser.QuerySize() );
*pReply = SecAuthOk;
}
else
{
ErrorTrace( (LPARAM)this,
"LocalAlloc failed. err: %d",
GetLastError() );
*pReply = SecInternalErr;
}
strUser.Resize(0);
}
else
{
ErrorTrace( (LPARAM)this,
"QueryUserName failed. err: %d",
GetLastError() );
*pReply = SecInternalErr;
//
// Firewall around NT bug where negotiation succeeds even though
// it should really have failed (when an empty buffer is passed
// to AcceptSecurityContext). In this case, the QueryUserName is
// the only valid check - gpulla.
//
return m_IsAuthenticated = FALSE;
}
return m_IsAuthenticated = TRUE;
}
}
else
{
SECURITY_STATUS ss = GetLastError();
ErrorTrace( (LPARAM)this,
"Converse failed. err: 0x%08X", ss );
*pReply = SecPermissionDenied;
}
}
return FALSE;
}
BOOL
CSecurityCtx::ProcessAuthInfo(
IN PIIS_SERVER_INSTANCE pIisInstance,
IN AUTH_COMMAND Command,
IN LPSTR Blob,
IN OUT LPBYTE ReplyString,
IN OUT PDWORD ReplySize,
OUT REPLY_LIST* pReply,
IN OPTIONAL DWORD BlobLength
)
/*++
Routine Description:
Process AUTHINFO commands
Arguments:
Command - Authinfo command received
Blob - Blob accompanying the command
ReplyString - Reply to be sent to client.
ReplySize - Size of the reply.
Return Value:
None.
--*/
{
//
// transition codes to support backward compatibility
// will be removed later when everybody has moved to new version of simauth2
//
if (!m_ProviderPackages) {
m_ProviderPackages = ProviderPackages;
m_ProviderNames = ProviderNames;
m_cProviderPackages = cProviderPackages;
}
TraceFunctEnterEx( (LPARAM)this, "CSecurityCtx::ProcessAuthInfo");
BOOL bSuccess = FALSE;
START_TRY
//
// We currently support USER, PASSWORD, and TRANSACT
//
switch( Command )
{
case AuthCommandUser:
bSuccess = ProcessUser( pIisInstance, Blob, pReply );
break;
case AuthCommandPassword:
bSuccess = ProcessPass( pIisInstance, Blob, pReply );
break;
case AuthCommandTransact:
bSuccess = ProcessTransact( pIisInstance,
Blob,
ReplyString,
ReplySize,
pReply,
BlobLength );
break;
default:
if ( m_IsAuthenticated )
{
Reset();
}
*pReply = SecSyntaxErr;
}
TRY_EXCEPT
END_TRY
_ASSERT( *pReply < NUM_SEC_REPLIES );
if ((DWORD)*pReply >= NUM_SEC_REPLIES)
*pReply = SecInternalErr;
return bSuccess;
} // ProcessAuthInfo
BOOL CSecurityCtx::ClientConverse(
IN VOID * pBuffIn,
IN DWORD cbBuffIn,
OUT BUFFER * pbuffOut,
OUT DWORD * pcbBuffOut,
OUT BOOL * pfNeedMoreData,
IN PTCP_AUTHENT_INFO pTAI,
IN CHAR * pszPackage,
IN CHAR * pszUser,
IN CHAR * pszPassword,
IN PIIS_SERVER_INSTANCE psi)
/*++
Routine Description:
Processes AUTH blobs for a client (ie, for an outbound connection). This is
a simple wrapper around TCP_AUTHENT::Converse; it will map Internet protocol
keywords to NT security package names.
Arguments:
Same as that for TCP_AUTHENT::Converse
Return Value:
Same as that for TCP_AUTHENT::Converse
--*/
{
LPSTR pszPackageToUse = pszPackage;
if (pszPackage != NULL &&
(lstrcmpi(pszPackage, "GSSAPI") == 0) ) {
pszPackageToUse = "Negotiate";
}
return( Converse(
pBuffIn, cbBuffIn,
pbuffOut, pcbBuffOut, pfNeedMoreData,
pTAI, pszPackageToUse,
pszUser, pszPassword,
psi) );
}
//
// Figure out if the local machine is a member of a domain, or in a
// workgroup. If we aren't in a domain then we don't want to call into
// ResetServicePrincipleNames.
//
// This function returns TRUE in error cases, because it is better to
// call into ResetServicePrininpleNames by mistake then it is to
// skip calling it.
//
// Implemented using this algorithm:
//
// There are many ways to find out if you are in a work group. You can
// call LsaOpenPolicy /
// LsaQueryInformationPolicy(PolicyDnsDomainInformation) / LsaClose, and
// check if the SID is non-null. That's authoritative.
//
// -Rich (Richard B. Ward (Exchange))
//
BOOL fInDomain() {
TraceFunctEnter("fInDomain");
LSA_HANDLE lsah;
LSA_OBJECT_ATTRIBUTES objAttr;
POLICY_DNS_DOMAIN_INFO *pDnsInfo;
NTSTATUS ec;
// cache the results of this here. one can't join a domain without
// rebooting, so this safe to do once
static BOOL fDidCheck = FALSE;
static BOOL fRet = TRUE;
if (!fDidCheck) {
ZeroMemory(&objAttr, sizeof(objAttr));
ec = LsaOpenPolicy(NULL,
&objAttr,
POLICY_VIEW_LOCAL_INFORMATION,
&lsah);
if (ec == ERROR_SUCCESS) {
ec = LsaQueryInformationPolicy(lsah,
PolicyDnsDomainInformation,
(void **) &pDnsInfo);
if (ec == ERROR_SUCCESS) {
DebugTrace(0, "pDnsInfo = %x", pDnsInfo);
// we are in a domain if there is a Sid
if (pDnsInfo && pDnsInfo->Sid) {
fRet = TRUE;
} else {
fRet = FALSE;
}
fDidCheck = TRUE;
LsaFreeMemory(pDnsInfo);
} else {
DebugTrace(0, "LsaQueryInformationPolicy failed with %x", ec);
}
LsaClose(lsah);
} else {
DebugTrace(0, "LsaOpenPolicy failed with %x", ec);
}
}
TraceFunctLeave();
return fRet;
}
BOOL
CSecurityCtx::ResetServicePrincipalNames(
IN LPCSTR szServiceClass)
/*++
Routine Description:
Unregisters all service principal names for the given service from the
local machine's computer account object.
Arguments:
szServiceClass: String identifying service class, eg. "SMTP"
Return Value:
None.
--*/
{
DWORD dwErr;
if (fInDomain()) {
dwErr = DsServerRegisterSpnA(
DS_SPN_DELETE_SPN_OP,
szServiceClass,
NULL);
} else {
dwErr = ERROR_SUCCESS;
}
if (dwErr != ERROR_SUCCESS) {
SetLastError(dwErr);
return( FALSE );
} else {
return( TRUE );
}
}
BOOL
CSecurityCtx::RegisterServicePrincipalNames(
IN LPCSTR szServiceClass,
IN LPCSTR szFQDN)
/*++
Routine Description:
Registers service specific SPNs for the provided FQDN. The list of SPNs is
generated by doing a gethostbyname on the FQDN, and using the returned IP
addresses as th SPNs.
Arguments:
szServiceClass: String identifying service class, eg. "SMTP"
szFQDN: The FQDN of the virtual server. It will be used to do a
gethostbyname and retrieve a list of IP addresses to use.
Return Value:
None.
--*/
{
DWORD dwErr, cIPAddresses;
if (fInDomain()) {
dwErr = DsServerRegisterSpnA(
DS_SPN_ADD_SPN_OP,
szServiceClass,
NULL);
} else {
dwErr = ERROR_SUCCESS;
}
if (dwErr != ERROR_SUCCESS) {
SetLastError(dwErr);
return( FALSE );
} else {
return( TRUE );
}
}
#define MAX_SPN 260
BOOL
CSecurityCtx::SetTargetPrincipalName(
IN LPCSTR szServiceClass,
IN LPCSTR szTargetIPOrFQDN)
/*++
Routine Description:
Unregisters all service principal names for the given service from the
local machine's computer account object.
Arguments:
szServiceClass: String identifying service class, eg. "SMTP"
Return Value:
None.
--*/
{
DWORD dwErr, cbTargetSPN;
CHAR szTargetSPN[MAX_SPN];
cbTargetSPN = sizeof(szTargetSPN);
dwErr = DsClientMakeSpnForTargetServerA(
szServiceClass,
szTargetIPOrFQDN,
&cbTargetSPN,
szTargetSPN);
if (dwErr == ERROR_SUCCESS) {
return( SetTargetName(szTargetSPN) );
} else {
SetLastError(dwErr);
return( FALSE );
}
}