// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // +build windows package eventlog import ( "fmt" "syscall" "time" "github.com/joeshaw/multierror" "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" "github.com/elastic/beats/winlogbeat/checkpoint" "github.com/elastic/beats/winlogbeat/sys" win "github.com/elastic/beats/winlogbeat/sys/eventlogging" ) const ( // eventLoggingAPIName is the name used to identify the Event Logging API // as both an event type and an API. eventLoggingAPIName = "eventlogging" ) var eventLoggingConfigKeys = append(commonConfigKeys, "ignore_older", "read_buffer_size", "format_buffer_size") type eventLoggingConfig struct { ConfigCommon `config:",inline"` IgnoreOlder time.Duration `config:"ignore_older"` ReadBufferSize uint `config:"read_buffer_size" validate:"min=1"` FormatBufferSize uint `config:"format_buffer_size" validate:"min=1"` } // Validate validates the eventLoggingConfig data and returns an error // describing any problems or nil. func (c *eventLoggingConfig) Validate() error { var errs multierror.Errors if c.Name == "" { errs = append(errs, fmt.Errorf("event log is missing a 'name'")) } if c.ReadBufferSize > win.MaxEventBufferSize { errs = append(errs, fmt.Errorf("'read_buffer_size' must be less than "+ "%d bytes", win.MaxEventBufferSize)) } if c.FormatBufferSize > win.MaxFormatMessageBufferSize { errs = append(errs, fmt.Errorf("'format_buffer_size' must be less than "+ "%d bytes", win.MaxFormatMessageBufferSize)) } return errs.Err() } // Validate that eventLogging implements the EventLog interface. var _ EventLog = &eventLogging{} // eventLogging implements the EventLog interface for reading from the Event // Logging API. type eventLogging struct { config eventLoggingConfig name string // Name of the log that is opened. handle win.Handle // Handle to the event log. readBuf []byte // Buffer for reading in events. formatBuf []byte // Buffer for formatting messages. insertBuf win.StringInserts // Buffer for parsing insert strings. handles *messageFilesCache // Cached mapping of source name to event message file handles. logPrefix string // Prefix to add to all log entries. recordNumber uint32 // First record number to read. seek bool // Read should use seek. ignoreFirst bool // Ignore first message returned from a read. } // Name returns the name of the event log (i.e. Application, Security, etc.). func (l eventLogging) Name() string { return l.name } func (l *eventLogging) Open(state checkpoint.EventLogState) error { detailf("%s Open(recordNumber=%d) calling OpenEventLog(uncServerPath=, "+ "providerName=%s)", l.logPrefix, state.RecordNumber, l.name) handle, err := win.OpenEventLog("", l.name) if err != nil { return err } numRecords, err := win.GetNumberOfEventLogRecords(handle) if err != nil { return err } var oldestRecord, newestRecord uint32 if numRecords > 0 { l.recordNumber = uint32(state.RecordNumber) l.seek = true l.ignoreFirst = true oldestRecord, err = win.GetOldestEventLogRecord(handle) if err != nil { return err } newestRecord = oldestRecord + numRecords - 1 if l.recordNumber < oldestRecord || l.recordNumber > newestRecord { l.recordNumber = oldestRecord l.ignoreFirst = false } } else { l.recordNumber = 0 l.seek = false l.ignoreFirst = false } logp.Info("%s contains %d records. Record number range [%d, %d]. Starting "+ "at %d (ignoringFirst=%t)", l.logPrefix, numRecords, oldestRecord, newestRecord, l.recordNumber, l.ignoreFirst) l.handle = handle return nil } func (l *eventLogging) Read() ([]Record, error) { flags := win.EVENTLOG_SEQUENTIAL_READ | win.EVENTLOG_FORWARDS_READ if l.seek { flags = win.EVENTLOG_SEEK_READ | win.EVENTLOG_FORWARDS_READ l.seek = false } var numBytesRead int err := retry( func() error { l.readBuf = l.readBuf[0:cap(l.readBuf)] // TODO: Use number of bytes to grow the buffer size as needed. var err error numBytesRead, err = win.ReadEventLog( l.handle, flags, l.recordNumber, l.readBuf) return err }, l.readRetryErrorHandler) if err != nil { debugf("%s ReadEventLog returned error %v", l.logPrefix, err) return readErrorHandler(err) } detailf("%s ReadEventLog read %d bytes", l.logPrefix, numBytesRead) l.readBuf = l.readBuf[0:numBytesRead] events, _, err := win.RenderEvents( l.readBuf[:numBytesRead], 0, l.formatBuf, &l.insertBuf, l.handles.get) if err != nil { return nil, err } detailf("%s RenderEvents returned %d events", l.logPrefix, len(events)) records := make([]Record, 0, len(events)) for _, e := range events { // The events do not contain the name of the event log so we must add // the name of the log from which we are reading. e.Channel = l.name err = sys.PopulateAccount(&e.User) if err != nil { debugf("%s SID %s account lookup failed. %v", l.logPrefix, e.User.Identifier, err) } records = append(records, Record{ API: eventLoggingAPIName, Event: e, Offset: checkpoint.EventLogState{ Name: l.name, RecordNumber: e.RecordID, Timestamp: e.TimeCreated.SystemTime, }, }) } if l.ignoreFirst && len(records) > 0 { debugf("%s Ignoring first event with record ID %d", l.logPrefix, records[0].RecordID) records = records[1:] l.ignoreFirst = false } records = filter(records, l.ignoreOlder) debugf("%s Read() is returning %d records", l.logPrefix, len(records)) return records, nil } func (l *eventLogging) Close() error { debugf("%s Closing handle", l.logPrefix) return win.CloseEventLog(l.handle) } // readRetryErrorHandler handles errors returned from the readEventLog function // by attempting to correct the error through closing and reopening the event // log. func (l *eventLogging) readRetryErrorHandler(err error) error { incrementMetric(readErrors, err) if errno, ok := err.(syscall.Errno); ok { var reopen bool switch errno { case win.ERROR_EVENTLOG_FILE_CHANGED: debugf("Re-opening event log because event log file was changed") reopen = true case win.ERROR_EVENTLOG_FILE_CORRUPT: debugf("Re-opening event log because event log file is corrupt") reopen = true } if reopen { l.Close() return l.Open(checkpoint.EventLogState{ Name: l.name, RecordNumber: uint64(l.recordNumber), }) } } return err } // readErrorHandler handles errors returned by the readEventLog function. func readErrorHandler(err error) ([]Record, error) { switch err { case syscall.ERROR_HANDLE_EOF, win.ERROR_EVENTLOG_FILE_CHANGED, win.ERROR_EVENTLOG_FILE_CORRUPT: return []Record{}, nil } return nil, err } // Filter returns a new slice holding only the elements of s that satisfy the // predicate fn(). func filter(in []Record, fn func(*Record) bool) []Record { var out []Record for _, r := range in { if fn(&r) { out = append(out, r) } } return out } // ignoreOlder is a filter predicate that checks the record timestamp and // returns true if the event was not matched by the filter. func (l *eventLogging) ignoreOlder(r *Record) bool { if l.config.IgnoreOlder != 0 && time.Since(r.TimeCreated.SystemTime) > l.config.IgnoreOlder { return false } return true } // newEventLogging creates and returns a new EventLog for reading event logs // using the Event Logging API. func newEventLogging(options *common.Config) (EventLog, error) { c := eventLoggingConfig{ ReadBufferSize: win.MaxEventBufferSize, FormatBufferSize: win.MaxFormatMessageBufferSize, } if err := readConfig(options, &c, eventLoggingConfigKeys); err != nil { return nil, err } return &eventLogging{ config: c, name: c.Name, handles: newMessageFilesCache(c.Name, win.QueryEventMessageFiles, win.FreeLibrary), logPrefix: fmt.Sprintf("EventLogging[%s]", c.Name), readBuf: make([]byte, 0, c.ReadBufferSize), formatBuf: make([]byte, c.FormatBufferSize), }, nil } func init() { // Register eventlogging API if it is available. available, _ := win.IsAvailable() if available { Register(eventLoggingAPIName, 1, newEventLogging, nil) } }