/*++ Copyright (c) 1991-1994 Microsoft Corporation Module Name: logclear.c Abstract: Contains functions used to log an event indicating who cleared the log. This is only called after the security log has been cleared. Author: Dan Lafferty (danl) 01-July-1994 Environment: User Mode -Win32 Revision History: 01-July-1994 danl & robertre Created - Rob supplied the code which I fitted into the eventlog. --*/ #include #include #include #include #include #include #include #include #include #include #define NUM_STRINGS 6 // // LOCAL FUNCTION PROTOTYPES // BOOL GetUserInfo( IN HANDLE Token, OUT LPWSTR *UserName, OUT LPWSTR *DomainName, OUT LPWSTR *AuthenticationId, OUT PSID *UserSid ); LPWSTR GetNameFromIPAddress( LPWSTR pszComputerNameFromBinding); BOOL IsLocal( VOID ); VOID ElfpGenerateLogClearedEvent( IELF_HANDLE LogHandle, LPWSTR pwsClientSidString, LPWSTR pwsComputerName, PTOKEN_USER pToken ) /*++ Routine Description: This function generates an event indicating that the log was cleared. Arguments: LogHandle -- This is a handle to the log that the event is to be placed in. It is intended that this function only be called when the SecurityLog has been cleared. So it is expected the LogHandle will always be a handle to the security log. Return Value: None -- Either it works or it doesn't. If it doesn't, there isn't much we can do about it. --*/ { LPWSTR UserName = L"-"; LPWSTR DomainName = L"-"; LPWSTR AuthenticationId = L"-"; LPWSTR ClientUserName = pwsClientSidString; LPWSTR ClientDomainName = L"-"; LPWSTR ClientAuthenticationId = L"-"; PSID UserSid = NULL; DWORD i; BOOL Result; HANDLE Token; PUNICODE_STRING StringPtrArray[NUM_STRINGS]; UNICODE_STRING StringArray[NUM_STRINGS]; NTSTATUS Status; LARGE_INTEGER Time; ULONG EventTime; ULONG LogHandleGrantedAccess; UNICODE_STRING ComputerNameU; DWORD dwStatus; BOOL bUserNameSet = FALSE; BOOL bClientInfoSet = FALSE; // // Get information about the Eventlog service (i.e., LocalSystem) // Result = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &Token); if (!Result) { ELF_LOG1(ERROR, "ElfpGenerateLogClearedEvent: OpenProcessToken failed %d\n", GetLastError()); } else { Result = GetUserInfo(Token, &UserName, &DomainName, &AuthenticationId, &UserSid); CloseHandle(Token); if (!Result) { ELF_LOG1(ERROR, "ElfpGenerateLogClearedEvent: GetUserInfo failed %d\n", GetLastError()); } else bUserNameSet = TRUE; } if(!bUserNameSet) { UserName = L"-"; DomainName = L"-"; AuthenticationId = L"-"; UserSid = pToken->User.Sid; } ELF_LOG3(TRACE, "ElfpGenerateLogClearedEvent: GetUserInfo returned: \n" "\tUserName = %ws,\n" "\tDomainName = %ws,\n" "\tAuthenticationId = %ws\n", UserName, DomainName, AuthenticationId); // // Now impersonate in order to get the client's information. // This call should never fail. // dwStatus = RpcImpersonateClient(NULL); if (dwStatus != RPC_S_OK) { ELF_LOG1(ERROR, "ElfpGenerateLogClearedEvent: RpcImpersonateClient failed %d\n", dwStatus); } else { Result = OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &Token); if (!Result) { ELF_LOG1(ERROR, "ElfpGenerateLogClearedEvent: OpenThreadToken failed %d\n", GetLastError()); ASSERT(FALSE); } else { Result = GetUserInfo(Token, &ClientUserName, &ClientDomainName, &ClientAuthenticationId, NULL); CloseHandle(Token); if (!Result) { ELF_LOG1(ERROR, "ElfpGenerateLogClearedEvent: GetUserInfo (call 2) failed %d\n", GetLastError()); } else bClientInfoSet = TRUE; } } if(!bClientInfoSet) { ClientUserName = pwsClientSidString; ClientDomainName = L"-"; ClientAuthenticationId = L"-"; } ELF_LOG3(TRACE, "ElfpGenerateLogClearedEvent: GetUserInfo (call 2) returned: \n" "\tUserName = %ws,\n" "\tDomainName = %ws,\n" "\tAuthenticationId = %ws\n", ClientUserName, ClientDomainName, ClientAuthenticationId); RtlInitUnicodeString(&StringArray[0], UserName); RtlInitUnicodeString(&StringArray[1], DomainName); RtlInitUnicodeString(&StringArray[2], AuthenticationId); RtlInitUnicodeString(&StringArray[3], ClientUserName); RtlInitUnicodeString(&StringArray[4], ClientDomainName); RtlInitUnicodeString(&StringArray[5], ClientAuthenticationId); // // Create an array of pointers to UNICODE_STRINGs. // for (i = 0; i < NUM_STRINGS; i++) { StringPtrArray[i] = &StringArray[i]; } // // Generate the time of the event. This is done on the client side // since that is where the event occurred. // NtQuerySystemTime(&Time); RtlTimeToSecondsSince1970(&Time, &EventTime); RtlInitUnicodeString(&ComputerNameU, pwsComputerName); // // Since all processes other than LSA are given read-only access // to the security log, we have to explicitly give the current // process the right to write the "Log cleared" event // LogHandleGrantedAccess = LogHandle->GrantedAccess; LogHandle->GrantedAccess |= ELF_LOGFILE_WRITE; Status = ElfrReportEventW ( LogHandle, // Log Handle EventTime, // Time EVENTLOG_AUDIT_SUCCESS, // Event Type (USHORT)SE_CATEGID_SYSTEM, // Event Category SE_AUDITID_AUDIT_LOG_CLEARED, // EventID NUM_STRINGS, // NumStrings 0, // DataSize &ComputerNameU, // ComputerNameU UserSid, // UserSid StringPtrArray, // *Strings NULL, // Data 0, // Flags NULL, // RecordNumber NULL); // TimeWritten LogHandle->GrantedAccess = LogHandleGrantedAccess; // // We only come down this path if we know for sure that these // first three have been allocated. // if(bUserNameSet) { ElfpFreeBuffer(UserName); ElfpFreeBuffer(DomainName); ElfpFreeBuffer(AuthenticationId); ElfpFreeBuffer(UserSid); } if(bClientInfoSet) { ElfpFreeBuffer(ClientUserName); ElfpFreeBuffer(ClientDomainName); ElfpFreeBuffer(ClientAuthenticationId); } // // Stop impersonating // dwStatus = RpcRevertToSelf(); if (dwStatus != RPC_S_OK) { ELF_LOG1(ERROR, "ElfpGenerateLogClearedEvent: RpcRevertToSelf failed %d\n", GetLastError()); } } BOOL GetUserInfo( IN HANDLE Token, OUT LPWSTR *UserName, OUT LPWSTR *DomainName, OUT LPWSTR *AuthenticationId, OUT PSID *UserSid ) /*++ Routine Description: This function gathers information about the user identified with the token. Arguments: Token - This token identifies the entity for which we are gathering information. UserName - This is the entity's user name. DomainName - This is the entity's domain name. AuthenticationId - This is the entity's Authentication ID. UserSid - This is the entity's SID. NOTE: Memory is allocated by this routine for UserName, DomainName, AuthenticationId, and UserSid. It is the caller's responsibility to free this memory. Return Value: TRUE - If the operation was successful. It is possible that UserSid did not get allocated in the successful case. Therefore, the caller should test prior to freeing. FALSE - If unsuccessful. No memory is allocated in this case. --*/ { PTOKEN_USER Buffer = NULL; LPWSTR Domain = NULL; LPWSTR AccountName = NULL; SID_NAME_USE Use; BOOL Result; DWORD RequiredLength; DWORD AccountNameSize; DWORD DomainNameSize; TOKEN_STATISTICS Statistics; WCHAR LogonIdString[256]; DWORD Status = ERROR_SUCCESS; if(UserSid) *UserSid = NULL; Result = GetTokenInformation(Token, TokenUser, NULL, 0, &RequiredLength); if (!Result) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { ELF_LOG1(TRACE, "GetUserInfo: GetTokenInformation needs %d bytes\n", RequiredLength); Buffer = ElfpAllocateBuffer(RequiredLength); if (Buffer == NULL) { ELF_LOG0(ERROR, "GetUserInfo: Unable to allocate memory for token\n"); return FALSE; } Result = GetTokenInformation(Token, TokenUser, Buffer, RequiredLength, &RequiredLength); if (!Result) { ELF_LOG1(ERROR, "GetUserInfo: GetTokenInformation (call 2) failed %d\n", GetLastError()); ElfpFreeBuffer(Buffer); return FALSE; } } else { ELF_LOG1(ERROR, "GetUserInfo: GetTokenInformation (call 1) failed %d\n", GetLastError()); return FALSE; } } AccountNameSize = 0; DomainNameSize = 0; Result = LookupAccountSidW(L"", Buffer->User.Sid, NULL, &AccountNameSize, NULL, &DomainNameSize, &Use); if (!Result) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { AccountName = ElfpAllocateBuffer((AccountNameSize + 1) * sizeof(WCHAR)); Domain = ElfpAllocateBuffer((DomainNameSize + 1) * sizeof(WCHAR)); if (AccountName == NULL) { ELF_LOG1(ERROR, "GetUserInfo: Unable to allocate %d bytes for AccountName\n", AccountNameSize); goto ErrorCleanup; } if (Domain == NULL) { ELF_LOG1(ERROR, "GetUserInfo: Unable to allocate %d bytes for Domain\n", DomainNameSize); goto ErrorCleanup; } Result = LookupAccountSidW(L"", Buffer->User.Sid, AccountName, &AccountNameSize, Domain, &DomainNameSize, &Use ); if (!Result) { ELF_LOG1(ERROR, "GetUserInfo: LookupAccountSid (call 2) failed %d\n", GetLastError()); goto ErrorCleanup; } } else { ELF_LOG1(ERROR, "GetUserInfo: LookupAccountsid (call 1) failed %d\n", GetLastError()); goto ErrorCleanup; } } else { ELF_LOG0(ERROR, "GetUserInfo: LookupAccountSid succeeded unexpectedly\n"); goto ErrorCleanup; } ELF_LOG2(TRACE, "GetUserInfo: Name = %ws\\%ws\n", Domain, AccountName); Result = GetTokenInformation(Token, TokenStatistics, &Statistics, sizeof(Statistics), &RequiredLength); if (!Result) { ELF_LOG1(ERROR, "GetUserInfo: GetTokenInformation (call 3) failed %d\n", GetLastError()); goto ErrorCleanup; } StringCchPrintfW(LogonIdString, 256, L"(0x%X,0x%X)", Statistics.AuthenticationId.HighPart, Statistics.AuthenticationId.LowPart); ELF_LOG1(TRACE, "GetUserInfo: LogonIdString = %ws\n", LogonIdString); *AuthenticationId = ElfpAllocateBuffer(WCSSIZE(LogonIdString)); if (*AuthenticationId == NULL) { ELF_LOG0(ERROR, "GetUserInfo: Unable to allocate memory for AuthenticationId\n"); goto ErrorCleanup; } StringCchCopyW(*AuthenticationId, wcslen(LogonIdString) +1, LogonIdString); // // Return accumulated information // if(UserSid) { *UserSid = ElfpAllocateBuffer(GetLengthSid(Buffer->User.Sid)); if (*UserSid == NULL) { ELF_LOG0(ERROR, "GetUserInfo: Unable to allocate memory for UserSid\n"); goto ErrorCleanup; } Result = CopySid(GetLengthSid(Buffer->User.Sid), *UserSid, Buffer->User.Sid); } ElfpFreeBuffer(Buffer); *DomainName = Domain; *UserName = AccountName; return TRUE; ErrorCleanup: ElfpFreeBuffer(Buffer); ElfpFreeBuffer(Domain); ElfpFreeBuffer(AccountName); if(UserSid) ElfpFreeBuffer(*UserSid); ElfpFreeBuffer(*AuthenticationId); return FALSE; } LPWSTR GetNameFromIPAddress( LPWSTR pszComputerNameFromBinding) /*++ Routine Description: Examines a string and determines if it looks like an ip address. If it does, then it converts it to a fqdn Arguments: Machine name: Could be xxx.xxx.xxx.xxx or anything else! Return Value: If successful, returns a fully qualified domain name which the caller will free. Returns NULL if there are any problems. --*/ { LPWSTR pComputerName = NULL; DWORD dwAddr; char cName[17]; // should be plenty of room size_t NumConv; HOSTENT FAR * pEnt; DWORD dwSize; WORD wVersionRequested; WSADATA wsaData; int error; NumConv = wcstombs(cName, pszComputerNameFromBinding, 16); if(NumConv == -1 || NumConv == 0) return NULL; // convert the string into a network order dword representation dwAddr = inet_addr(cName); if(dwAddr == INADDR_NONE) return NULL; // initialize sockets. wVersionRequested = MAKEWORD( 2, 2 ); error = WSAStartup( wVersionRequested, &wsaData ); if(error != 0) { ELF_LOG1(TRACE, "GetNameFromIPAddress: failed to initialize sockets, error = 0x%x\n", error); return NULL; } pEnt = gethostbyaddr((char FAR *)&dwAddr, 4, PF_INET); if(pEnt == NULL || pEnt->h_name == NULL) { ELF_LOG1(TRACE, "GetNameFromIPAddress: failed gethostbyaddr, error = 0x%x\n", WSAGetLastError()); WSACleanup(); return NULL; } dwSize = strlen(pEnt->h_name) + 1 ; pComputerName = ElfpAllocateBuffer(2*dwSize); if(pComputerName == NULL) { ELF_LOG0(ERROR, "GetNameFromIPAddress: failed trying to allocate memory\n"); WSACleanup(); return NULL; } pComputerName[0] = 0; mbstowcs(pComputerName, pEnt->h_name, dwSize); WSACleanup(); return pComputerName; } BOOL IsLocal( VOID ) /*++ Routine Description: Determines if the caller is local. Arguments: NONE Return Value: TRUE if definately local. --*/ { UINT LocalFlag; RPC_STATUS RpcStatus; RpcStatus = I_RpcBindingIsClientLocal(0, &LocalFlag); if( RpcStatus != RPC_S_OK ) { ELF_LOG1(ERROR, "IsLocal: I_RpcBindingIsClientLocal failed %d\n", RpcStatus); return FALSE; } if(LocalFlag == 0) return FALSE; else return TRUE; } LPWSTR ElfpGetComputerName( VOID ) /*++ Routine Description: This routine gets the LPWSTR name of the computer. Arguments: NONE Return Value: Returns a pointer to the computer name, or a NULL. Note that the caller is expected to free this via --*/ { RPC_STATUS RpcStatus; handle_t hServerBinding = NULL; LPWSTR pszBinding = NULL; LPWSTR pszComputerNameFromBinding = NULL; LPWSTR pszComputerName = NULL; DWORD dwSize; BOOL bOK; // Check if the connection is local. If it is, then just // call GetComputerName if(IsLocal()) { dwSize = MAX_COMPUTERNAME_LENGTH + 1 ; pszComputerName = ElfpAllocateBuffer(2*dwSize); if(pszComputerName == NULL) { ELF_LOG0(ERROR, "ElfpGetComputerName: failed trying to allocate memory\n"); return NULL; } bOK = GetComputerNameW(pszComputerName, &dwSize); if(bOK == FALSE) { ElfpFreeBuffer(pszComputerName); ELF_LOG1(ERROR, "ElfpGetComputerName: failed calling GetComputerNameW, last error 0x%x\n", GetLastError()); return NULL; } else return pszComputerName; } RpcStatus = RpcBindingServerFromClient( NULL, &hServerBinding ); if( RpcStatus != RPC_S_OK ) { ELF_LOG1(ERROR, "ElfpGetComputerName: RpcBindingServerFromClient failed %d\n", RpcStatus); return NULL; } // This gets us something like "ncacn_np:xxx.xxx.xxx.xxx" or // "ncacn_np:localMachine" RpcStatus = RpcBindingToStringBinding( hServerBinding, &pszBinding ); if( RpcStatus != RPC_S_OK ) { ELF_LOG1(ERROR, "ElfpGetComputerName: RpcBindingToStringBinding failed %d\n", RpcStatus); goto CleanExitGetCompName; } // Acquire just the network address. That will be something like // "xxx.xxx.xxx.xxx" or "mymachine" RpcStatus = RpcStringBindingParse( pszBinding, NULL, NULL, &pszComputerNameFromBinding, NULL, NULL ); if( RpcStatus != RPC_S_OK || pszComputerNameFromBinding == NULL) { ELF_LOG1(ERROR, "ElfpGetComputerName: RpcStringBindingParse failed %d\n", RpcStatus); goto CleanExitGetCompName; } // sometimes the name is an ip address. If it is, then the following determines // that and returns the right string. pszComputerName = GetNameFromIPAddress(pszComputerNameFromBinding); if(pszComputerName == NULL) { dwSize = wcslen(pszComputerNameFromBinding) + 1; pszComputerName = ElfpAllocateBuffer(2*dwSize); if(pszComputerName == NULL) { ELF_LOG0(ERROR, "ElfpGetComputerName: failed trying to allocate memory\n"); } else StringCchCopyW(pszComputerName, dwSize, pszComputerNameFromBinding); } CleanExitGetCompName: if(hServerBinding) RpcStatus = RpcBindingFree(&hServerBinding); if(pszBinding) RpcStatus = RpcStringFree(&pszBinding); if(pszComputerNameFromBinding) RpcStatus = RpcStringFree(&pszComputerNameFromBinding); return pszComputerName; } NTSTATUS ElfpGetClientSidString( LPWSTR * ppwsClientSidString, PTOKEN_USER * ppToken ) /*++ Routine Description: This routine gets the LPWSTR version of the RPC callers SID. Note that upon success, the calling routine should free this via ElfpFreeBuffer Arguments: ppwsClientSidString - upon succes, this will have a fail safe version of the client info which the caller must free Return Value: NTSTATUS value --*/ { NTSTATUS Status, RevertStatus; BOOL bImpersonating = FALSE; HANDLE hToken; BOOL bGotToken = FALSE; DWORD RequiredLength; UNICODE_STRING UnicodeStringSid; BOOL bNeedToFreeUnicodeStr = FALSE; DWORD dwRetStringSize = 0; *ppwsClientSidString = NULL; *ppToken = NULL; // impersonate the client Status = I_RpcMapWin32Status( RpcImpersonateClient( NULL ) ); if ( !NT_SUCCESS( Status ) ) { ELF_LOG1(ERROR, "ElfpGetClientSidString: RpcImpersonateClient failed %#x\n", Status); goto ExitElfpGetClientSidString; } bImpersonating = TRUE; // Get the thread token Status = NtOpenThreadToken (GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken); if ( !NT_SUCCESS( Status ) ) { ELF_LOG1(ERROR, "ElfpGetClientSidString: NtOpenThreadToken failed %#x\n", Status); goto ExitElfpGetClientSidString; } bGotToken = TRUE; // first find out how much memory is needed Status = NtQueryInformationToken (hToken, TokenUser, NULL, 0, &RequiredLength); if (Status != STATUS_BUFFER_TOO_SMALL) { ELF_LOG1(ERROR, "ElfpGetClientSidString: 1stNtQueryInformationToken isnt too small %#x\n", Status); if(NT_SUCCESS( Status )) { // weird case, should never happen, but we dont want caller to think this worked by // accident Status = STATUS_UNSUCCESSFUL; } goto ExitElfpGetClientSidString; } // get the memory and actually read the data *ppToken = ElfpAllocateBuffer(RequiredLength); if (*ppToken == NULL) { ELF_LOG0(ERROR, "ElfpGetClientSidString: Unable to allocate memory for token\n"); Status = STATUS_NO_MEMORY; goto ExitElfpGetClientSidString; } Status = NtQueryInformationToken (hToken, TokenUser, *ppToken, RequiredLength, &RequiredLength); if ( !NT_SUCCESS( Status ) ) { ELF_LOG1(ERROR, "ElfpGetClientSidString: 2nNtQueryInformationToken failed %#x\n", Status); goto ExitElfpGetClientSidString; } // Convert using the Rtl functions Status = RtlConvertSidToUnicodeString( &UnicodeStringSid, (*ppToken)->User.Sid, TRUE ); if ( !NT_SUCCESS( Status ) ) { ELF_LOG1(ERROR, "ElfpGetClientSidString: RtlConvertSidToUnicodeString failed %#x\n", Status); goto ExitElfpGetClientSidString; } bNeedToFreeUnicodeStr = TRUE; // allocate and convert dwRetStringSize = UnicodeStringSid.Length + sizeof(WCHAR); *ppwsClientSidString = ElfpAllocateBuffer(dwRetStringSize); if (*ppwsClientSidString == NULL) { ELF_LOG0(ERROR, "ElfpGetClientSidString: Unable to allocate memory for returned string\n"); Status = STATUS_NO_MEMORY; goto ExitElfpGetClientSidString; } StringCbCopyW( *ppwsClientSidString, dwRetStringSize, UnicodeStringSid.Buffer); Status = STATUS_SUCCESS; ExitElfpGetClientSidString: if(bImpersonating) { RevertStatus = I_RpcMapWin32Status( RpcRevertToSelf() ); if ( !NT_SUCCESS( RevertStatus ) ) { ELF_LOG1(ERROR, "ElfpGetClientSidString: RpcRevertToSelf failed %#x\n", Status); if(NT_SUCCESS( Status )) Status = RevertStatus; } } if(!NT_SUCCESS( Status ) && *ppwsClientSidString) { ElfpFreeBuffer(*ppwsClientSidString); *ppwsClientSidString = NULL; } if(!NT_SUCCESS( Status ) && *ppToken) { ElfpFreeBuffer(*ppToken); *ppToken = NULL; } if(bGotToken) NtClose(hToken); if(bNeedToFreeUnicodeStr) RtlFreeUnicodeString( &UnicodeStringSid ); return Status; }