/*++ 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 # include # include # include #ifdef __cplusplus }; #endif #include #include #include #include #include // // SSL and SSPI related include files // extern "C" { #include #define SECURITY_WIN32 #include #include #include #include } // // 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 *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 ); } }