// LsaStuff.cpp // // LSA-dependent code // // HISTORY // 09-Jul-97 jonn Creation. // #include "stdafx.h" #include "DynamLnk.h" // DynamicDLL extern "C" { #define NTSTATUS LONG #define PNTSTATUS NTSTATUS* #define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) #define SE_SHUTDOWN_PRIVILEGE (19L) // stuff taken from ntdef.h typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; #ifdef MIDL_PASS [size_is(MaximumLength / 2), length_is((Length) / 2) ] USHORT * Buffer; #else // MIDL_PASS PWSTR Buffer; #endif // MIDL_PASS } UNICODE_STRING; typedef UNICODE_STRING *PUNICODE_STRING; #include #define _NTDEF_ typedef struct _OBJECT_ATTRIBUTES { ULONG Length; HANDLE RootDirectory; PUNICODE_STRING ObjectName; ULONG Attributes; PVOID SecurityDescriptor; // Points to type SECURITY_DESCRIPTOR PVOID SecurityQualityOfService; // Points to type SECURITY_QUALITY_OF_SERVICE } OBJECT_ATTRIBUTES; typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES; #define InitializeObjectAttributes( p, n, a, r, s ) { \ (p)->Length = sizeof( OBJECT_ATTRIBUTES ); \ (p)->RootDirectory = r; \ (p)->Attributes = a; \ (p)->ObjectName = n; \ (p)->SecurityDescriptor = s; \ (p)->SecurityQualityOfService = NULL; \ } #define _NTDEF // from ntstatus.h #define STATUS_OBJECT_NAME_NOT_FOUND ((NTSTATUS)0xC0000034L) #include } #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif typedef enum _Netapi32ApiIndex { BUFFERFREE_ENUM = 0, USERMODALSGET_ENUM }; // not subject to localization static LPCSTR g_apchNetapi32FunctionNames[] = { "NetApiBufferFree", "NetUserModalsGet", NULL }; typedef NET_API_STATUS (*BUFFERFREEPROC)(LPVOID); typedef NET_API_STATUS (*USERMODALSGETPROC)(LPCWSTR, DWORD, LPBYTE*); // not subject to localization DynamicDLL g_LSASTUFF_Netapi32DLL( _T("NETAPI32.DLL"), g_apchNetapi32FunctionNames ); /******************************************************************* NAME: ::FillUnicodeString SYNOPSIS: Standalone method for filling in a UNICODE_STRING ENTRY: punistr - Unicode string to be filled in. nls - Source for filling the unistr EXIT: NOTES: punistr->Buffer is allocated here and must be deallocated by the caller using FreeUnicodeString. HISTORY: jonn 07/09/97 copied from net\ui\common\src\lmobj\lmobj\uintmem.cxx ********************************************************************/ VOID FillUnicodeString( LSA_UNICODE_STRING * punistr, LPCWSTR psz ) { if ( NULL == punistr || NULL == psz ) { ASSERT(FALSE); return; } size_t cTchar = ::wcslen(psz); punistr->Buffer = new WCHAR[cTchar + 1]; ASSERT( NULL != punistr->Buffer ); if ( NULL != punistr->Buffer ) { ::wcscpy( punistr->Buffer, psz ); // Length and MaximumLength are counts of bytes. punistr->Length = (USHORT)(cTchar * sizeof(WCHAR)); punistr->MaximumLength = punistr->Length + sizeof(WCHAR); } else ::ZeroMemory( punistr, sizeof(*punistr) ); } /******************************************************************* NAME: ::FreeUnicodeString SYNOPSIS: Standalone method for freeing in a UNICODE_STRING ENTRY: unistr - Unicode string whose Buffer is to be freed. EXIT: HISTORY: jonn 07/09/97 copied from net\ui\common\src\lmobj\lmobj\uintmem.cxx ********************************************************************/ VOID FreeUnicodeString( LSA_UNICODE_STRING * punistr ) { if ( punistr && punistr->Buffer ) { delete punistr->Buffer; ::ZeroMemory( punistr, sizeof(*punistr) ); } } /******************************************************************* NAME: InitObjectAttributes SYNOPSIS: This function initializes the given Object Attributes structure, including Security Quality Of Service. Memory must be allcated for both ObjectAttributes and Security QOS by the caller. ENTRY: poa - Pointer to Object Attributes to be initialized. psqos - Pointer to Security QOS to be initialized. EXIT: NOTES: HISTORY: jonn 07/09/97 copied from net\ui\common\src\lmobj\lmobj\uintlsa.cxx ********************************************************************/ VOID InitObjectAttributes( PLSA_OBJECT_ATTRIBUTES poa, PSECURITY_QUALITY_OF_SERVICE psqos ) { ASSERT( poa != NULL ); ASSERT( psqos != NULL ); psqos->Length = sizeof(SECURITY_QUALITY_OF_SERVICE); psqos->ImpersonationLevel = SecurityImpersonation; psqos->ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; psqos->EffectiveOnly = FALSE; // // Set up the object attributes prior to opening the LSA. // InitializeObjectAttributes( poa, NULL, 0L, NULL, NULL ); // // The InitializeObjectAttributes macro presently stores NULL for // the psqos field, so we must manually copy that // structure for now. // poa->SecurityQualityOfService = psqos; } BOOL I_CheckLSAAccount( LSA_UNICODE_STRING* punistrServerName, LPCTSTR pszLogOnAccountName, DWORD* pdwMsgID ) // *pdsMsgID is always set if this fails { ASSERT( NULL != pdwMsgID ); BOOL fSuccess = FALSE; LSA_HANDLE hlsa = NULL; LSA_HANDLE hlsaAccount = NULL; PSID psidAccount = NULL; do { // false loop // // Determine whether the target machine is a BDC, and if so, get the PDC // // if an error occurs now, it is a read error *pdwMsgID = IDS_LSAERR_READ_FAILED; // // Get LSA_POLICY handle // LSA_OBJECT_ATTRIBUTES oa; SECURITY_QUALITY_OF_SERVICE sqos; InitObjectAttributes( &oa, &sqos ); // 563140-2002/03/04 JonN POLICY_ALL_ACCESS was too much authority. // LsaLookupNames only requires POLICY_LOOKUP_NAMES. // LsaOpenAccount requres no rights (MarkPu). // LsaCreateAccount requires POLICY_CREATE_ACCOUNT (MarkPu). NTSTATUS ntstatus = ::LsaOpenPolicy( punistrServerName, &oa, POLICY_LOOKUP_NAMES | POLICY_CREATE_ACCOUNT, &hlsa ); if ( !NT_SUCCESS(ntstatus) ) break; // // Remove ".\" or "thismachine\" from the head of the account name if it is present // CString strAccountName = pszLogOnAccountName; int iBackslash = strAccountName.Find( _T('\\') ); if ( -1 < iBackslash ) { CString strPrefix = strAccountName.Left(iBackslash); if ( !lstrcmp(strPrefix, _T(".")) || IsLocalComputername(strPrefix) ) { strAccountName = strAccountName.Mid(iBackslash+1); } } // // determine the SID of the account // PLSA_REFERENCED_DOMAIN_LIST plsardl = NULL; PLSA_TRANSLATED_SID plsasid = NULL; LSA_UNICODE_STRING unistrAccountName; ::FillUnicodeString( &unistrAccountName, strAccountName ); ntstatus = ::LsaLookupNames( hlsa, 1, &unistrAccountName, &plsardl, &plsasid ); ::FreeUnicodeString( &unistrAccountName ); if ( !NT_SUCCESS(ntstatus) ) break; if ( !plsardl || !plsasid || !plsardl->Domains[0].Sid ) { ASSERT(FALSE); ntstatus = E_FAIL; break; } // ISSUE-2002-03-04-JonN There is a potential issue here // if LsaLookupNames returns an invalid SID. This is a fairly // remote risk however. // // Build the SID of the account by taking the SID of the domain // and adding at the end the RID of the account // PSID psidDomain = plsardl->Domains[0].Sid; DWORD ridAccount = plsasid[0].RelativeId; DWORD cbNewSid = ::GetLengthSid(psidDomain)+sizeof(ridAccount); psidAccount = (PSID) new BYTE[cbNewSid]; ASSERT( NULL != psidAccount ); (void) ::CopySid( cbNewSid, psidAccount, psidDomain ); UCHAR* pcSubAuthorities = ::GetSidSubAuthorityCount( psidAccount ) ; (*pcSubAuthorities)++; DWORD* pdwSubAuthority = ::GetSidSubAuthority( psidAccount, (*pcSubAuthorities)-1 ); *pdwSubAuthority = ridAccount; (void) ::LsaFreeMemory( plsardl ); (void) ::LsaFreeMemory( plsasid ); // 563140-2002/04/08 JonN POLICY_ALL_ACCESS | DELETE was too much authority. // CliffV says: // LsaGetSystemAccessAccount only requires ACCOUNT_VIEW. // LsaSetSystemAccessAccount only requires ACCOUNT_ADJUST_SYSTEM_ACCESS. // // Determine whether this LSA account exists, create it if not // ntstatus = ::LsaOpenAccount( hlsa, psidAccount, ACCOUNT_VIEW | ACCOUNT_ADJUST_SYSTEM_ACCESS, &hlsaAccount ); ULONG ulSystemAccessCurrent = 0; if (STATUS_OBJECT_NAME_NOT_FOUND == ntstatus) { // handle account-not-found case // if an error occurs now, it is a write error *pdwMsgID = IDS_LSAERR_WRITE_FAILED; ntstatus = ::LsaCreateAccount( hlsa, psidAccount, ACCOUNT_ADJUST_SYSTEM_ACCESS, &hlsaAccount ); // 4/8/02 JonN: CliffV confirms that the account is created without any privileges } else { ntstatus = ::LsaGetSystemAccessAccount( hlsaAccount, &ulSystemAccessCurrent ); } if ( !NT_SUCCESS(ntstatus) ) break; // // Determine whether this LSA account has POLICY_MODE_SERVICE privilege, // grant it if not // if ( POLICY_MODE_SERVICE != (ulSystemAccessCurrent & POLICY_MODE_SERVICE ) ) { // if an error occurs now, it is a write error *pdwMsgID = IDS_LSAERR_WRITE_FAILED; ntstatus = ::LsaSetSystemAccessAccount( hlsaAccount, ulSystemAccessCurrent | POLICY_MODE_SERVICE ); if ( !NT_SUCCESS(ntstatus) ) break; // CODEWORK could check for STATUS_BACKUP_CONTROLLER // display the write-succeeded message *pdwMsgID = IDS_LSAERR_WRITE_SUCCEEDED; } else { *pdwMsgID = 0; } fSuccess = TRUE; } while (FALSE); // false loop // CODEWORK should check for special error code for NT5 non-DC // using local policy object if (NULL != hlsa) { ::LsaClose( hlsa ); } if (NULL != hlsaAccount) { ::LsaClose( hlsaAccount ); } if (NULL != psidAccount) { delete[] psidAccount; } return fSuccess; } // I_CheckLSAAccount() ///////////////////////////////////////////////////////////////////// // FCheckLSAAccount() // VOID CServicePropertyData::FCheckLSAAccount() { LSA_UNICODE_STRING unistrServerName; PLSA_UNICODE_STRING punistrServerName = NULL ; USER_MODALS_INFO_1* pum1 = NULL; DWORD dwMsgID = 0; TRACE1("INFO: Checking LSA permissions for account %s...\n", (LPCTSTR)m_strLogOnAccountName); if ( !m_strMachineName.IsEmpty() ) { ::FillUnicodeString( &unistrServerName, m_strMachineName ); punistrServerName = &unistrServerName; } do // false loop { // check on the local machine // this will always set dwMsgID if it fails if (I_CheckLSAAccount(punistrServerName, m_strLogOnAccountName, &dwMsgID)) break; // this succeeded, we can stop now // check whether this is a Backup Domain Controller if ( !g_LSASTUFF_Netapi32DLL.LoadFunctionPointers() ) { ASSERT(FALSE); return; } DWORD err = ((USERMODALSGETPROC)g_LSASTUFF_Netapi32DLL[USERMODALSGET_ENUM])( (m_strMachineName.IsEmpty()) ? NULL : const_cast((LPCTSTR)m_strMachineName), 1, reinterpret_cast(&pum1) ); if (NERR_Success != err) break; ASSERT( NULL != pum1 ); if (UAS_ROLE_BACKUP != pum1->usrmod1_role) break; // not a backup controller if (NULL == pum1->usrmod1_primary ) { ASSERT(FALSE); break; } // Try it on the PDC (void) I_CheckLSAAccount(punistrServerName, pum1->usrmod1_primary, &dwMsgID); } while (FALSE); // false loop if ( NULL != punistrServerName ) { ::FreeUnicodeString( punistrServerName ); } if ( NULL != pum1 ) { if ( !g_LSASTUFF_Netapi32DLL.LoadFunctionPointers() ) { ASSERT(FALSE); return; } ((BUFFERFREEPROC)g_LSASTUFF_Netapi32DLL[BUFFERFREE_ENUM])( pum1 ); } if (0 != dwMsgID) { DoServicesErrMsgBox( GetActiveWindow(), MB_OK | MB_ICONEXCLAMATION, 0, dwMsgID, (LPCTSTR)m_strLogOnAccountName ); } } // FCheckLSAAccount()