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

#
# 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]
}