| /* ********************************************************** |
| * 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 |