You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
391 lines
14 KiB
391 lines
14 KiB
# |
|
# Copyright (c) 2004-2012, Ashok P. Nadkarni |
|
# All rights reserved. |
|
# |
|
# See the file LICENSE for license |
|
|
|
package require registry |
|
|
|
namespace eval twapi { |
|
# We maintain caches so we do not do lookups all the time |
|
# TBD - have a means of clearing this out |
|
variable _eventlog_message_cache |
|
set _eventlog_message_cache {} |
|
} |
|
|
|
|
|
# Read the event log |
|
proc twapi::eventlog_read {hevl args} { |
|
_eventlog_valid_handle $hevl read raise |
|
|
|
array set opts [parseargs args { |
|
seek.int |
|
{direction.arg forward} |
|
}] |
|
|
|
if {[info exists opts(seek)]} { |
|
set flags 2; # Seek |
|
set offset $opts(seek) |
|
} else { |
|
set flags 1; # Sequential read |
|
set offset 0 |
|
} |
|
|
|
switch -glob -- $opts(direction) { |
|
"" - |
|
forw* { |
|
setbits flags 4 |
|
} |
|
back* { |
|
setbits flags 8 |
|
} |
|
default { |
|
error "Invalid value '$opts(direction)' for -direction option" |
|
} |
|
} |
|
|
|
set results [list ] |
|
|
|
trap { |
|
set recs [ReadEventLog $hevl $flags $offset] |
|
} onerror {TWAPI_WIN32 38} { |
|
# EOF - no more |
|
set recs [list ] |
|
} |
|
foreach event $recs { |
|
dict set event -type [string map {0 success 1 error 2 warning 4 information 8 auditsuccess 16 auditfailure} [dict get $event -level]] |
|
lappend results $event |
|
} |
|
|
|
return $results |
|
} |
|
|
|
|
|
# Get the oldest event log record index. $hevl must be read handle |
|
proc twapi::eventlog_oldest {hevl} { |
|
_eventlog_valid_handle $hevl read raise |
|
return [GetOldestEventLogRecord $hevl] |
|
} |
|
|
|
# Get the event log record count. $hevl must be read handle |
|
proc twapi::eventlog_count {hevl} { |
|
_eventlog_valid_handle $hevl read raise |
|
return [GetNumberOfEventLogRecords $hevl] |
|
} |
|
|
|
# Check if the event log is full. $hevl may be either read or write handle |
|
# (only win2k plus) |
|
proc twapi::eventlog_is_full {hevl} { |
|
# Does not matter if $hevl is read or write, but verify it is a handle |
|
_eventlog_valid_handle $hevl read |
|
return [Twapi_IsEventLogFull $hevl] |
|
} |
|
|
|
# Backup the event log |
|
proc twapi::eventlog_backup {hevl file} { |
|
_eventlog_valid_handle $hevl read raise |
|
BackupEventLog $hevl $file |
|
} |
|
|
|
# Clear the event log |
|
proc twapi::eventlog_clear {hevl args} { |
|
_eventlog_valid_handle $hevl read raise |
|
array set opts [parseargs args {backup.arg} -nulldefault] |
|
ClearEventLog $hevl $opts(backup) |
|
} |
|
|
|
|
|
# Formats the given event log record message |
|
# |
|
proc twapi::eventlog_format_message {rec args} { |
|
variable _eventlog_message_cache |
|
|
|
array set opts [parseargs args { |
|
width.int |
|
langid.int |
|
} -nulldefault] |
|
|
|
set source [dict get $rec -source] |
|
set eventid [dict get $rec -eventid] |
|
|
|
if {[dict exists $_eventlog_message_cache $source fmtstring $opts(langid) $eventid]} { |
|
set fmtstring [dict get $_eventlog_message_cache $source fmtstring $opts(langid) $eventid] |
|
dict incr _eventlog_message_cache __fmtstring_hits |
|
} else { |
|
dict incr _eventlog_message_cache __fmtstring_misses |
|
|
|
# Find the registry key if we do not have it already |
|
if {[dict exists $_eventlog_message_cache $source regkey]} { |
|
dict incr _eventlog_message_cache __regkey_hits |
|
set regkey [dict get $_eventlog_message_cache $source regkey] |
|
} else { |
|
set regkey [_find_eventlog_regkey $source] |
|
dict set _eventlog_message_cache $source regkey $regkey |
|
dict incr _eventlog_message_cache __regkey_misses |
|
} |
|
|
|
# Get the message file, if there is one |
|
if {! [catch {registry get $regkey "EventMessageFile"} path]} { |
|
# Try each file listed in turn |
|
foreach dll [split $path \;] { |
|
set dll [expand_environment_strings $dll] |
|
if {! [catch { |
|
set fmtstring [format_message -module $dll -messageid $eventid -width $opts(width) -langid $opts(langid)] |
|
} msg]} { |
|
dict set _eventlog_message_cache $source fmtstring $opts(langid) $eventid $fmtstring |
|
break |
|
} |
|
} |
|
} |
|
} |
|
|
|
if {! [info exists fmtstring]} { |
|
dict incr _eventlog_message_cache __notfound |
|
|
|
set fmt "The message file or event definition for event id [dict get $rec -eventid] from source [dict get $rec -source] was not found. The following information was part of the event: " |
|
set flds [list ] |
|
for {set i 1} {$i <= [llength [dict get $rec -params]]} {incr i} { |
|
lappend flds %$i |
|
} |
|
append fmt [join $flds ", "] |
|
return [format_message -fmtstring $fmt \ |
|
-params [dict get $rec -params] -width $opts(width)] |
|
} |
|
|
|
set msg [format_message -fmtstring $fmtstring -params [dict get $rec -params]] |
|
|
|
# We'd found a message from the message file and replaced the string |
|
# parameters. Now fill in the parameter file values if any. Note these are |
|
# separate from the string parameters passed in through rec(-params) |
|
|
|
# First check if the formatted string itself still has placeholders |
|
# Place holder for the parameters file are supposed to start |
|
# with two % chars. Unfortunately, not all apps, even Microsoft's own |
|
# DCOM obey this. So check for both % and %% |
|
set placeholder_indices [regexp -indices -all -inline {%?%\d+} $msg] |
|
if {[llength $placeholder_indices] == 0} { |
|
# No placeholders. |
|
return $msg |
|
} |
|
|
|
# Loop through to replace placeholders. |
|
set msg2 ""; # Holds result after param replacement |
|
set prev_end 0 |
|
foreach placeholder $placeholder_indices { |
|
lassign $placeholder start end |
|
# Append the stuff between previous placeholder and this one |
|
append msg2 [string range $msg $prev_end [expr {$start-1}]] |
|
set repl [string range $msg $start $end]; # Default if not found |
|
set paramid [string trimleft $repl %]; # Skip "%" |
|
if {[dict exists $_eventlog_message_cache $source paramstring $opts(langid) $paramid]} { |
|
dict incr _eventlog_message_cache __paramstring_hits |
|
set repl [format_message -fmtstring [dict get $_eventlog_message_cache $source paramstring $opts(langid) $paramid] -params [dict get $rec -params]] |
|
} else { |
|
dict incr _eventlog_message_cache __paramstring_misses |
|
# Not in cache, need to look up |
|
if {![info exists paramfiles]} { |
|
# Construct list of parameter string files |
|
|
|
# TBD - cache registry key results? |
|
# Find the registry key if we do not have it already |
|
if {![info exists regkey]} { |
|
if {[dict exists $_eventlog_message_cache $source regkey]} { |
|
dict incr _eventlog_message_cache __regkey_hits |
|
set regkey [dict get $_eventlog_message_cache $source regkey] |
|
} else { |
|
dict incr _eventlog_message_cache __regkey_misses |
|
set regkey [_find_eventlog_regkey $source] |
|
dict set _eventlog_message_cache $source regkey $regkey |
|
} |
|
} |
|
set paramfiles {} |
|
if {! [catch {registry get $regkey "ParameterMessageFile"} path]} { |
|
# Loop through every placeholder, look for the entry in the |
|
# parameters file and replace it if found |
|
foreach paramfile [split $path \;] { |
|
lappend paramfiles [expand_environment_strings $paramfile] |
|
} |
|
} |
|
} |
|
# Try each file listed in turn |
|
foreach paramfile $paramfiles { |
|
if {! [catch { |
|
set paramstring [string trimright [format_message -module $paramfile -messageid $paramid -langid $opts(langid)] \r\n] |
|
} ]} { |
|
# Found the replacement |
|
dict set _eventlog_message_cache $source paramstring $opts(langid) $paramid $paramstring |
|
set repl [format_message -fmtstring $paramstring -params [dict get $rec -params]] |
|
break |
|
} |
|
} |
|
} |
|
append msg2 $repl |
|
set prev_end [incr end] |
|
} |
|
|
|
# Tack on tail after last placeholder |
|
append msg2 [string range $msg $prev_end end] |
|
return $msg2 |
|
} |
|
|
|
# Format the category |
|
proc twapi::eventlog_format_category {rec args} { |
|
|
|
array set opts [parseargs args { |
|
width.int |
|
langid.int |
|
} -nulldefault] |
|
|
|
set category [dict get $rec -category] |
|
if {$category == 0} { |
|
return "" |
|
} |
|
|
|
variable _eventlog_message_cache |
|
|
|
set source [dict get $rec -source] |
|
|
|
# Get the category string from cache, if there is one |
|
if {[dict exists $_eventlog_message_cache $source category $opts(langid) $category]} { |
|
dict incr _eventlog_message_cache __category_hits |
|
set fmtstring [dict get $_eventlog_message_cache $source category $opts(langid) $category] |
|
} else { |
|
dict incr _eventlog_message_cache __category_misses |
|
|
|
# Find the registry key if we do not have it already |
|
if {[dict exists $_eventlog_message_cache $source regkey]} { |
|
dict incr _eventlog_message_cache __regkey_hits |
|
set regkey [dict get $_eventlog_message_cache $source regkey] |
|
} else { |
|
set regkey [_find_eventlog_regkey $source] |
|
dict set _eventlog_message_cache $source regkey $regkey |
|
dict incr _eventlog_message_cache __regkey_misses |
|
} |
|
|
|
if {! [catch {registry get $regkey "CategoryMessageFile"} path]} { |
|
# Try each file listed in turn |
|
foreach dll [split $path \;] { |
|
set dll [expand_environment_strings $dll] |
|
if {! [catch { |
|
set fmtstring [format_message -module $dll -messageid $category -width $opts(width) -langid $opts(langid)] |
|
} msg]} { |
|
dict set _eventlog_message_cache $source category $opts(langid) $category $fmtstring |
|
break |
|
} |
|
} |
|
} |
|
} |
|
|
|
if {![info exists fmtstring]} { |
|
set fmtstring "Category $category" |
|
dict set _eventlog_message_cache $source category $opts(langid) $category $fmtstring |
|
} |
|
|
|
return [format_message -fmtstring $fmtstring -params [dict get $rec -params]] |
|
} |
|
|
|
proc twapi::eventlog_monitor_start {hevl script} { |
|
variable _eventlog_notification_scripts |
|
|
|
set hevent [lindex [CreateEvent [_make_secattr {} 0] 0 0 ""] 0] |
|
if {[catch {NotifyChangeEventLog $hevl $hevent} msg]} { |
|
CloseHandle $hevent |
|
error $msg $::errorInfo $::errorCode |
|
} |
|
|
|
wait_on_handle $hevent -async twapi::_eventlog_notification_handler |
|
set _eventlog_notification_scripts($hevent) $script |
|
|
|
# We do not want the application mistakenly closing the event |
|
# while being waited on by the thread pool. That would be a big NO-NO |
|
# so change the handle type so it cannot be passed to close_handle. |
|
return [list evl $hevent] |
|
} |
|
|
|
# Stop any notifications. Note these will stop even if the event log |
|
# handle is closed but leave the event dangling. |
|
proc twapi::eventlog_monitor_stop {hevent} { |
|
variable _eventlog_notification_scripts |
|
set hevent [lindex $hevent 1] |
|
if {[info exists _eventlog_notification_scripts($hevent)]} { |
|
unset _eventlog_notification_scripts($hevent) |
|
cancel_wait_on_handle $hevent |
|
CloseHandle $hevent |
|
} |
|
} |
|
|
|
proc twapi::_eventlog_notification_handler {hevent event} { |
|
variable _eventlog_notification_scripts |
|
if {[info exists _eventlog_notification_scripts($hevent)] && |
|
$event eq "signalled"} { |
|
uplevel #0 $_eventlog_notification_scripts($hevent) [list [list evl $hevent]] |
|
} |
|
} |
|
|
|
# TBD - document |
|
proc twapi::eventlog_subscribe {source} { |
|
set hevl [eventlog_open -source $source] |
|
set hevent [lindex [CreateEvent [_make_secattr {} 0] 0 0 ""] 0] |
|
if {[catch {NotifyChangeEventLog $hevl $hevent} msg]} { |
|
set erinfo $::errorInfo |
|
set ercode $::errorCode |
|
CloseHandle $hevent |
|
error $hsubscribe $erinfo $ercode |
|
} |
|
|
|
return [list $hevl $hevent] |
|
} |
|
|
|
# Utility procs |
|
|
|
# Find the registry key corresponding the given event log source |
|
proc twapi::_find_eventlog_regkey {source} { |
|
set topkey {HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog} |
|
|
|
# Set a default list of children to work around an issue in |
|
# the Tcl [registry keys] command where a ERROR_MORE_DATA is returned |
|
# instead of a retry with a larger buffer. |
|
set keys {Application Security System} |
|
catch {set keys [registry keys $topkey]} |
|
# Get all keys under this key and look for a source under that |
|
foreach key $keys { |
|
# See above Tcl issue |
|
set srckeys {} |
|
catch {set srckeys [registry keys "${topkey}\\$key"]} |
|
foreach srckey $srckeys { |
|
if {[string equal -nocase $srckey $source]} { |
|
return "${topkey}\\${key}\\$srckey" |
|
} |
|
} |
|
} |
|
|
|
# Default to Application - TBD |
|
return "${topkey}\\Application" |
|
} |
|
|
|
proc twapi::_eventlog_dump {source chan} { |
|
set hevl [eventlog_open -source $source] |
|
while {[llength [set events [eventlog_read $hevl]]]} { |
|
# print out each record |
|
foreach eventrec $events { |
|
array set event $eventrec |
|
set timestamp [clock format $event(-timewritten) -format "%x %X"] |
|
set source $event(-source) |
|
set category [twapi::eventlog_format_category $eventrec -width -1] |
|
set message [twapi::eventlog_format_message $eventrec -width -1] |
|
puts $chan "$timestamp $source $category $message" |
|
} |
|
} |
|
eventlog_close $hevl |
|
} |
|
|
|
|
|
|
|
|
|
# If we are not being sourced from a executable resource, need to |
|
# source the remaining support files. In the former case, they are |
|
# automatically combined into one so the sourcing is not needed. |
|
if {![info exists twapi::twapi_eventlog_rc_sourced]} { |
|
source [file join [file dirname [info script]] evt.tcl] |
|
source [file join [file dirname [info script]] winlog.tcl] |
|
}
|
|
|