Skip to content

Commit

Permalink
check_eventlog: fix time offset parsing (fixes #157)
Browse files Browse the repository at this point in the history
  • Loading branch information
sni committed Sep 25, 2024
1 parent 9a84204 commit 320c678
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 3 deletions.
4 changes: 4 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,13 @@ issues:
- "Magic number: 15, in"
- "Magic number: 16, in"
- "Magic number: 20, in"
- "Magic number: 22, in"
- "Magic number: 23, in"
- "Magic number: 24, in"
- "Magic number: 31, in"
- "Magic number: 32, in"
- "Magic number: 50, in"
- "Magic number: 59, in"
- "Magic number: 60, in"
- "Magic number: 64, in"
- "Magic number: 100, in"
Expand Down
1 change: 1 addition & 0 deletions Changes
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ This file documents the revision history for the SNClient+ agent.
next:
- update windows exporter to 0.29.0
- wmi: always set en_US language in query (#156)
- check_eventlog: fix time offset parsing (#157)

0.27 Mon Sep 2 19:31:14 CEST 2024
- do not use empty-state if warn/crit conditions contain check on 'count'
Expand Down
22 changes: 20 additions & 2 deletions pkg/eventlog/eventlog_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

const (
WMIDateFormat = "20060102150405.000000-070"
WMIDateFormat = "20060102150405.000000"
)

type EventLog struct {
Expand Down Expand Up @@ -47,6 +47,24 @@ type Event struct {

func GetLog(file string, newerThan time.Time) ([]Event, error) {
messages := []Event{}

// Format the time without the timezone offset
formattedTime := newerThan.Format(WMIDateFormat)

// Get the timezone offset in seconds and convert it to minutes
_, offsetSeconds := newerThan.Zone()
offsetMinutes := offsetSeconds / 60

// Determine the sign of the offset
offsetSign := "+"
if offsetMinutes < 0 {
offsetSign = "-"
offsetMinutes = -offsetMinutes // Make offsetMinutes positive for formatting
}

// append the offset with three digits and leading zeros
wmiFormattedTime := fmt.Sprintf("%s%s%03d", formattedTime, offsetSign, offsetMinutes)

query := fmt.Sprintf(`
SELECT
ComputerName,
Expand All @@ -65,7 +83,7 @@ func GetLog(file string, newerThan time.Time) ([]Event, error) {
WHERE
Logfile='%s'
AND TimeGenerated >= '%s'
`, file, newerThan.Format(WMIDateFormat))
`, file, wmiFormattedTime)
err := wmi.QueryDefaultRetry(query, &messages)
if err != nil {
return nil, fmt.Errorf("wmi query failed: %s", err.Error())
Expand Down
98 changes: 97 additions & 1 deletion pkg/snclient/check_eventlog_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package snclient
import (
"context"
"fmt"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -57,7 +58,7 @@ func (l *CheckEventlog) Check(_ context.Context, _ *Agent, check *CheckData, _ [

for i := range fileEvent {
event := fileEvent[i]
timeWritten, _ := time.Parse(eventlog.WMIDateFormat, event.TimeWritten)
timeWritten, _ := l.ParseWMIDateTime(event.TimeWritten)
message := event.Message
if l.truncateMessage > 0 && len(event.Message) > l.truncateMessage {
message = event.Message[:l.truncateMessage]
Expand Down Expand Up @@ -96,3 +97,98 @@ func (l *CheckEventlog) Check(_ context.Context, _ *Agent, check *CheckData, _ [

return check.Finalize()
}

// ParseWMIDateTime parses a WMI datetime string into a time.Time object.
// returns parsed time or an error
func (l *CheckEventlog) ParseWMIDateTime(wmiDateTime string) (time.Time, error) {
// Check if the string has at least 22 characters to avoid slicing errors
if len(wmiDateTime) < 22 {
return time.Time{}, fmt.Errorf("invalid WMI datetime string, must be at least 22 characters long")
}

// Extract the date and time components
yearStr := wmiDateTime[0:4]
monthStr := wmiDateTime[4:6]
dayStr := wmiDateTime[6:8]
hourStr := wmiDateTime[8:10]
minuteStr := wmiDateTime[10:12]
secondStr := wmiDateTime[12:14]
microsecStr := wmiDateTime[15:21] // Skipping the dot at position 14
offsetSign := wmiDateTime[21:22]
offsetStr := wmiDateTime[22:]

year, err := strconv.Atoi(yearStr)
if err2 := l.checkRange("year", year, err, -1, -1); err2 != nil {
return time.Time{}, err2
}

month, err := strconv.Atoi(monthStr)
if err2 := l.checkRange("month", month, err, 1, 12); err2 != nil {
return time.Time{}, err2
}

day, err := strconv.Atoi(dayStr)
if err2 := l.checkRange("day", day, err, 1, 31); err2 != nil {
return time.Time{}, err2
}

hour, err := strconv.Atoi(hourStr)
if err2 := l.checkRange("hour", hour, err, 0, 23); err2 != nil {
return time.Time{}, err2
}

minute, err := strconv.Atoi(minuteStr)
if err2 := l.checkRange("minute", minute, err, 0, 59); err2 != nil {
return time.Time{}, err2
}

second, err := strconv.Atoi(secondStr)
if err2 := l.checkRange("second", second, err, 0, 59); err2 != nil {
return time.Time{}, err2
}

microsec, err := strconv.Atoi(microsecStr)
if err2 := l.checkRange("microsecond", microsec, err, -1, -1); err2 != nil {
return time.Time{}, err2
}

offsetMinutes, err := strconv.Atoi(offsetStr)
if err2 := l.checkRange("offset", offsetMinutes, err, -1, -1); err2 != nil {
return time.Time{}, err2
}

// Apply the sign to the offset
if offsetSign == "-" {
offsetMinutes = -offsetMinutes
} else if offsetSign != "+" {
// Invalid sign, return current time
return time.Time{}, fmt.Errorf("invalid offset sign, must be + or -")
}

// Convert offset from minutes to seconds
offsetSeconds := offsetMinutes * 60

// Create a fixed time zone based on the offset
loc := time.FixedZone("WMI", offsetSeconds)

// Construct the time.Time object
t := time.Date(year, time.Month(month), day, hour, minute, second, microsec*1000, loc)

return t, nil
}

func (l *CheckEventlog) checkRange(name string, value int, err error, minVal, maxVal int) error {
if err != nil {
return fmt.Errorf("invalid %s: %s", name, err.Error())
}

if minVal != -1 && value < minVal {
return fmt.Errorf("%s out of range", name)
}

if maxVal != -1 && value > maxVal {
return fmt.Errorf("%s out of range", name)
}

return nil
}

0 comments on commit 320c678

Please sign in to comment.