blob: 521785c93a4e0eff76ff8f4c03972e3f5381ae38 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2003-2008 VMware, Inc. All rights reserved.
* **********************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
//
// -wrapping event log ids
// -what happens when log is cleared
//
#include "share.h"
#include "elm.h"
#include "events.h"
#ifndef UNIT_TEST
# define BUFFER_SIZE 8192
# define MAX_MSG_STRINGS 16
int control;
HANDLE elm_thread = NULL;
/* if true, the formatted callback is used; otherwise the raw callback */
BOOL format_messages;
DWORD WINAPI
eventLogMonitorThreadProc(LPVOID elm_info_param);
typedef struct eventlogmon_info_ {
eventlog_formatted_callback cb_format;
eventlog_raw_callback cb_raw;
eventlog_error_callback cb_err;
/* this is passed to start_eventlog_monitor; nodemanager keeps
* track of it in the registry. */
DWORD next_record;
} eventlogmon_info;
void
stop_eventlog_monitor()
{
BOOL wait = TRUE;
control = 1;
if (wait) {
WaitForSingleObject(elm_thread, 5000);
}
CloseHandle(elm_thread);
elm_thread = NULL;
}
/* returns a handle to the thread, which can be waited on */
HANDLE
get_elm_thread_handle()
{
return elm_thread;
}
DWORD
start_eventlog_monitor(BOOL use_formatted_callback, eventlog_formatted_callback cb_format,
eventlog_raw_callback cb_raw, eventlog_error_callback cb_err,
DWORD next_eventlog_record)
{
DWORD tid;
eventlogmon_info *elmi;
if ((use_formatted_callback && cb_format == NULL) ||
(!use_formatted_callback && cb_raw == NULL))
return ERROR_INVALID_PARAMETER;
/* make sure there isn't already an elm running */
if (elm_thread != NULL)
return ERROR_ALREADY_INITIALIZED;
elmi = (eventlogmon_info *)malloc(sizeof(eventlogmon_info));
elmi->cb_format = cb_format;
elmi->cb_raw = cb_raw;
elmi->cb_err = cb_err;
elmi->next_record = next_eventlog_record;
format_messages = use_formatted_callback;
elm_thread = CreateThread(NULL, 0, &eventLogMonitorThreadProc, elmi, 0, &tid);
if (elm_thread == NULL)
return GetLastError();
return ERROR_SUCCESS;
}
# define MAX_EVENTLOG_SOURCES 8
BOOL eventsources_loaded = FALSE;
DWORD
get_formatted_message(EVENTLOGRECORD *pevlr, WCHAR *buf, DWORD maxchars)
{
static int num_eventlog_sources = 0;
static HMODULE hm[MAX_EVENTLOG_SOURCES];
HKEY hes = NULL, hel = NULL;
DWORD len;
WCHAR message_file_buf[MAX_PATH];
LPBYTE msgarray[MAX_MSG_STRINGS];
int i = 0;
DWORD res;
/* load sources upon first request */
if (num_eventlog_sources == 0) {
/* We don't use product_key_flags() (PR 244206) b/c it's a pain to link
* with utils.obj, and we don't need the flags for HKLM\System
*/
res = RegOpenKey(HKEY_LOCAL_MACHINE, EVENT_LOG_KEY, &hel);
if (res != ERROR_SUCCESS) {
return res;
goto formatted_msg_error;
}
for (;;) {
if (num_eventlog_sources >= MAX_EVENTLOG_SOURCES) {
res = ERROR_INSUFFICIENT_BUFFER;
goto formatted_msg_error;
}
len = MAX_PATH;
res = RegEnumKeyEx(hel, i++, message_file_buf, &len, 0, NULL, NULL, NULL);
if (res == ERROR_NO_MORE_ITEMS) {
res = ERROR_SUCCESS;
break;
} else if (res != ERROR_SUCCESS) {
goto formatted_msg_error;
}
/* We don't use product_key_flags() (PR 244206) b/c it's a pain to link
* with utils.obj, and we don't need the flags for HKLM\System
*/
res = RegOpenKey(hel, message_file_buf, &hes);
if (res == ERROR_SUCCESS) {
len = MAX_PATH;
res = RegQueryValueEx(hes, L"EventMessageFile", NULL, NULL,
(LPBYTE)message_file_buf, &len);
if (res == ERROR_SUCCESS) {
/* load the module for the message resources */
hm[num_eventlog_sources++] =
LoadLibraryEx(message_file_buf, NULL, LOAD_LIBRARY_AS_DATAFILE);
}
}
if (hes != NULL) {
RegCloseKey(hes);
hes = NULL;
}
}
}
/* now, format */
if (pevlr->NumStrings > MAX_MSG_STRINGS) {
res = ERROR_INSUFFICIENT_BUFFER;
goto formatted_msg_error;
}
msgarray[0] = (LPBYTE)pevlr + pevlr->StringOffset;
for (i = 1; i < pevlr->NumStrings; ++i)
msgarray[i] =
msgarray[i - 1] + sizeof(WCHAR) * (1 + wcslen((LPTSTR)msgarray[i - 1]));
/* try each event source, returning only when FormatMessage succeeds.
* this is a little slutty as we could probably look up the event
* source based from the event itself, but it's not too bad. */
len = 0;
for (i = 0; i < num_eventlog_sources; i++) {
if (hm[i] != NULL) {
len =
FormatMessage(FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY,
hm[i], pevlr->EventID, 0, buf, maxchars, (char **)msgarray);
if (len > 0)
break;
}
}
if (len == 0) {
res = ERROR_PARSE_ERROR;
goto formatted_msg_error;
} else {
buf[maxchars - 1] = L'\0';
res = ERROR_SUCCESS;
}
formatted_msg_error:
if (hes != NULL)
RegCloseKey(hes);
if (hel != NULL)
RegCloseKey(hel);
return res;
}
BOOL do_once = FALSE;
DWORD WINAPI
eventLogMonitorThreadProc(LPVOID elm_info_param)
{
EVENTLOGRECORD *pevlr;
BYTE bBuffer[BUFFER_SIZE] = { 0 };
DWORD dwRead, dwNeeded, res;
DWORD reported_next_record, num_records;
BOOL skip_first = FALSE;
HANDLE log = NULL, event = NULL;
WCHAR msgbuf[BUFFER_SIZE];
eventlogmon_info *elm_info = (eventlogmon_info *)elm_info_param;
control = 0;
log = OpenEventLog(NULL, L_COMPANY_NAME);
if (log == NULL) {
(*elm_info->cb_err)(ELM_ERR_FATAL,
L"Could not open the " L_COMPANY_NAME L" event log.");
goto exit_elm_thread_error;
}
event = CreateEvent(NULL, FALSE, FALSE, NULL);
NotifyChangeEventLog(log, event);
pevlr = (EVENTLOGRECORD *)&bBuffer;
if (!GetNumberOfEventLogRecords(log, &num_records) ||
!GetOldestEventLogRecord(log, &reported_next_record)) {
_snwprintf(msgbuf, BUFFER_SIZE_ELEMENTS(msgbuf),
L"error %d getting eventlog info", GetLastError());
(*elm_info->cb_err)(ELM_ERR_FATAL, msgbuf);
goto exit_elm_thread_error;
}
/* FIXME: we don't handle the situation when the eventlog was cleared,
* but our pointer is less than the number of new records. for this
* we'll probably have to store a timestamp, and compare against the
* event record at next_record. */
if (((int)elm_info->next_record) < 0) {
elm_info->next_record = reported_next_record;
} else if (elm_info->next_record > (reported_next_record + num_records + 1)) {
/* looks like the eventlog was cleared since we last checked
* it. issue a warning and reset */
elm_info->next_record = reported_next_record;
(*elm_info->cb_err)(ELM_ERR_CLEARED, L"Eventlog was cleared!\n");
} else {
/* we need to ensure we SEEK to a valid record; but since
* it's already been reported, don't report it again. */
elm_info->next_record--;
skip_first = TRUE;
}
/* first seek to the last record
* EVENTLOG_FORWARDS_READ indicates we will get messages in
* chronological order.
* FIXME: test to make sure that this works properly on
* overwrite-wrapped logs */
if (!ReadEventLog(log, EVENTLOG_FORWARDS_READ | EVENTLOG_SEEK_READ,
elm_info->next_record, pevlr, BUFFER_SIZE, &dwRead, &dwNeeded)) {
dwRead = 0;
dwNeeded = 0;
}
for (;;) {
do {
/* case 5813: if pevlr->Length is 0, we'll have an infinite
* loop. this could possibly happen if drive is full?
* just abort if we detect it. */
while (dwRead > 0 && pevlr->Length > 0) {
if (format_messages && !skip_first) {
res = get_formatted_message(pevlr, msgbuf, BUFFER_SIZE);
if (res != ERROR_SUCCESS) {
_snwprintf(msgbuf, BUFFER_SIZE_ELEMENTS(msgbuf),
L"FormatMessage error %d\n", res);
(*elm_info->cb_err)(ELM_ERR_WARN, msgbuf);
} else {
/* invoke the callback */
(*elm_info->cb_format)(pevlr->RecordNumber, pevlr->EventType,
msgbuf, pevlr->TimeGenerated);
}
} else if (!skip_first) {
/* xref case 3065: insurance */
if (pevlr->RecordNumber != 0 || pevlr->TimeGenerated != 0) {
/* raw callback */
(*elm_info->cb_raw)(pevlr);
}
} else {
skip_first = FALSE;
}
dwRead -= pevlr->Length;
pevlr = (EVENTLOGRECORD *)((LPBYTE)pevlr + pevlr->Length);
}
pevlr = (EVENTLOGRECORD *)&bBuffer;
} while (ReadEventLog(log, EVENTLOG_FORWARDS_READ | EVENTLOG_SEQUENTIAL_READ, 0,
pevlr, BUFFER_SIZE, &dwRead, &dwNeeded));
if ((res = GetLastError()) != ERROR_HANDLE_EOF) {
// FIXME: assert GetLastError() is appropriate
_snwprintf(msgbuf, BUFFER_SIZE_ELEMENTS(msgbuf),
L"Unexpected error %d reading event log\n", res);
(*elm_info->cb_err)(ELM_ERR_WARN, msgbuf);
}
if (do_once)
break;
/* the event is auto-reset.
timeout because NotifyChangeEventLog is not reliable. */
WaitForSingleObject(event, MINIPULSE);
if (control)
break;
}
exit_elm_thread_error:
if (log != NULL)
CloseEventLog(log);
if (event != NULL)
CloseHandle(event);
free(elm_info);
/* FIXME: need ExitThread()? */
return 0;
}
/* insertion strings are null separated. for pevlr of type
* EVENTLOGRECORD, the first string is at
* ((LPBYTE) pevlr) + pevlr->StringOffset
* undefined results if next_message_string is called more than
* NumStrings times. */
const WCHAR *
next_message_string(const WCHAR *prev_string)
{
return prev_string + wcslen(prev_string) + 1;
}
const WCHAR *
get_message_strings(EVENTLOGRECORD *pevlr)
{
return (WCHAR *)(((LPBYTE)pevlr) + pevlr->StringOffset);
}
const WCHAR *
get_event_exename(EVENTLOGRECORD *pevlr)
{
/* exename is always the first msg string */
WCHAR *exename;
const WCHAR *exepath = get_message_strings(pevlr);
exename = wcsrchr(exepath, L'\\');
if (exename == NULL)
return exepath;
else
return exename + 1;
}
UINT
get_event_pid(EVENTLOGRECORD *pevlr)
{
DO_ASSERT(pevlr != NULL);
/* PID is always the second msg string */
return (UINT)_wtoi(next_message_string(get_message_strings(pevlr)));
}
/* For a MSG_SEC_FORENSICS eventlog record, returns the filename of the forensics file
* generated. */
const WCHAR *
get_forensics_filename(EVENTLOGRECORD *pevlr)
{
DO_ASSERT(pevlr != NULL && pevlr->EventID == MSG_SEC_FORENSICS);
/* forensics file pathname will be third string */
return next_message_string(next_message_string(get_message_strings(pevlr)));
}
BOOL
is_violation_event(DWORD eventType)
{
switch (eventType) {
case MSG_HOT_PATCH_VIOLATION:
case MSG_SEC_VIOLATION_TERMINATED:
case MSG_SEC_VIOLATION_CONTINUE:
case MSG_SEC_VIOLATION_THREAD:
case MSG_SEC_VIOLATION_EXCEPTION: return TRUE;
default: return FALSE;
}
}
const WCHAR *
get_event_threatid(EVENTLOGRECORD *pevlr)
{
DO_ASSERT(pevlr != NULL);
/* The Threat ID, if available, is always the 3rd parameter. */
if (is_violation_event(pevlr->EventID)) {
return next_message_string(next_message_string(get_message_strings(pevlr)));
} else {
return NULL;
}
}
DWORD
clear_eventlog()
{
DWORD res = ERROR_SUCCESS;
HANDLE log = OpenEventLog(NULL, L_COMPANY_NAME);
if (log == NULL)
return GetLastError();
if (!ClearEventLog(log, NULL))
res = GetLastError();
CloseHandle(log);
return res;
}
#else // ifdef UNIT_TEST
int
main()
{
// DWORD res;
printf("All Test Passed\n");
return 0;
}
#endif