1package Nagios::CheckLogfiles::Search::Eventlog;
2
3use strict;
4use Exporter;
5use File::Basename;
6use Time::Local;
7use IO::File;
8use vars qw(@ISA);
9
10use constant OK => 0;
11use constant WARNING => 1;
12use constant CRITICAL => 2;
13use constant UNKNOWN => 3;
14
15@ISA = qw(Nagios::CheckLogfiles::Search);
16
17sub new {
18  my $self = bless {}, shift;
19  return $self->init(shift);
20}
21
22sub init {
23  my $self = shift;
24  my $params = shift;
25  $self->{logfile} = '/eventlog/is/cool';
26  $self->default_options({ winwarncrit => 0,
27      eventlogformat => '%g %i %m' });
28  $self->SUPER::init($params);
29  if ($self->get_option('lookback')) {
30    if ($self->get_option('lookback') =~ /^(\d+)(s|m|h|d)$/) {
31      if ($2 eq 's') {
32        $self->set_option('lookback', $1);
33      } elsif ($2 eq 'm') {
34        $self->set_option('lookback', $1 * 60);
35      } elsif ($2 eq 'h') {
36        $self->set_option('lookback', $1 * 60 * 60);
37      } elsif ($2 eq 'd') {
38        $self->set_option('lookback', $1 * 60 * 60 * 24);
39      }
40    } else {
41      printf STDERR "illegal time interval (must be <number>[s|m|h|d]\n";
42      $self = undef;
43      return undef;
44    }
45  }
46  if ($self->get_option('winwarncrit')) {
47    push(@{$self->{patterns}->{WARNING}}, "EE_WW_TT");
48    push(@{$self->{patterns}->{CRITICAL}}, "EE_EE_TT");
49    push(@{$self->{patternfuncs}->{WARNING}},
50        eval "sub { local \$_ = shift; return m/EE_WW_TT/o; }");
51    push(@{$self->{patternfuncs}->{CRITICAL}},
52        eval "sub { local \$_ = shift; return m/EE_EE_TT/o; }");
53
54  }
55  push(@{$self->{patterns}->{UNKNOWN}}, "EE_UU_TT");
56  push(@{$self->{patternfuncs}->{UNKNOWN}},
57      eval "sub { local \$_ = shift; return m/EE_UU_TT/o; }");
58  $self->{eventlog} = {
59    # system, security, application
60    eventlog => $params->{eventlog}->{eventlog} || 'system',
61    computer => $params->{eventlog}->{computer} || Win32::NodeName(),
62    username => $params->{eventlog}->{username} || Win32::LoginName(),
63    password => $params->{eventlog}->{password} || '',
64    source => $params->{eventlog}->{source},
65    speedup => $params->{eventlog}->{speedup} || 1,
66    include => $params->{eventlog}->{include} || {},
67    exclude => $params->{eventlog}->{exclude} || {},
68  };
69  $self->resolve_macros(\$self->{eventlog}->{eventlog});
70  $self->resolve_macros(\$self->{eventlog}->{computer});
71  $self->resolve_macros(\$self->{eventlog}->{username}) if $self->{eventlog}->{username};
72  $self->resolve_macros(\$self->{eventlog}->{password}) if $self->{eventlog}->{password};
73  # computer: I changed "\\\\MYPDC" to $dc ($dc = Win32::AdminMisc::GetDC("MYDOMAIN");)
74  # keys fuer include/exclude: source,category,type,eventid
75  foreach my $item (qw(Source Category EventType EventID)) {
76    foreach (keys %{$self->{eventlog}->{include}}) {
77      if (lc $_ eq lc $item) {
78        $self->{eventlog}->{include}->{$item} =
79            lc $self->{eventlog}->{include}->{$_};
80        delete $self->{eventlog}->{include}->{$_} if $_ ne $item;
81      }
82    }
83    foreach (keys %{$self->{eventlog}->{exclude}}) {
84      if (lc $_ eq lc $item) {
85        $self->{eventlog}->{exclude}->{$item} =
86            lc $self->{eventlog}->{exclude}->{$_};
87        delete $self->{eventlog}->{exclude}->{$_} if $_ ne $item;
88      }
89    }
90  }
91  if (! exists $self->{eventlog}->{include}->{operation} ||
92      $self->{eventlog}->{include}->{operation} ne 'or') {
93    $self->{eventlog}->{include}->{operation} = 'and'
94  }
95  if (! exists $self->{eventlog}->{exclude}->{operation} ||
96      $self->{eventlog}->{exclude}->{operation} ne 'and') {
97    $self->{eventlog}->{exclude}->{operation} = 'or'
98  }
99  $self->{orschlorschknorsch} = sprintf "%s/%s.temp_evtlog2file",
100        $self->system_tempdir(), $self->{tag};
101}
102
103sub prepare {
104  my $self = shift;
105  #$self->{options}->{nologfilenocry} = 1;
106  $self->{eventlog}->{thissecond} = time;
107  push(@{$self->{exceptions}->{CRITICAL}}, 'CHECK_LOGFILES INTERNAL ERROR');
108  push(@{$self->{exceptions}->{WARNING}}, 'CHECK_LOGFILES INTERNAL ERROR');
109}
110
111sub loadstate {
112  my $self = shift;
113  $self->SUPER::loadstate();
114  # always scan the whole output. thst's what starttime is for.
115  $self->{laststate}->{logoffset} = 0;
116  # if this is the very first run, look back 5 mintes in the past.
117  # with allyoucaneat set, look back 10 years
118  $self->{laststate}->{logtime} = $self->{laststate}->{logtime} ?
119      $self->{laststate}->{logtime} :
120      $self->{options}->{allyoucaneat} ?
121          $self->{eventlog}->{thissecond} - 315360000 :
122          $self->{eventlog}->{thissecond} - 600;
123}
124
125sub savestate {
126  my $self = shift;
127  foreach (keys %{$self->{laststate}}) {
128    $self->{newstate}->{$_} = $self->{laststate}->{$_};
129  }
130  $self->{newstate}->{logtime} = $self->{eventlog}->{thissecond};
131  $self->SUPER::savestate();
132}
133
134sub analyze_situation {
135  my $self = shift;
136  if ($self->{options}->{lookback}) {
137    $self->{eventlog}->{lastsecond} = time - $self->{options}->{lookback};
138    $self->trace("looking back until %s",
139        scalar localtime $self->{eventlog}->{lastsecond});
140  } else {
141    $self->{eventlog}->{lastsecond} = $self->{laststate}->{logtime};
142    $self->trace("last scanned until %s",
143        scalar localtime $self->{eventlog}->{lastsecond});
144  }
145  $self->trace(sprintf "from %s to %s",
146      scalar localtime $self->{eventlog}->{lastsecond},
147      scalar localtime $self->{eventlog}->{thissecond});
148  if ($self->{eventlog}->{lastsecond} < $self->{eventlog}->{thissecond}) {
149    $self->{logmodified} = 1;
150  } else {
151    # this happens if you call the plugin in too short intervals.
152    $self->trace("please wait for a second");
153  }
154}
155
156sub collectfiles {
157  my $self = shift;
158  $self->trace(sprintf "get everything %d <= event < %d",
159      $self->{eventlog}->{lastsecond},
160      $self->{eventlog}->{thissecond});
161  if ($self->{logmodified}) {
162    open(*FH, ">$self->{orschlorschknorsch}");
163    tie *FH, 'Nagios::CheckLogfiles::Search::Eventlog::Handle',
164        $self->{eventlog},
165        $self->get_option('winwarncrit'),
166        $self->get_option('eventlogformat'),
167        $self->get_option('logfilenocry'),
168        $self->{tivoli},
169        $self->{tracefile};
170    push(@{$self->{relevantfiles}},
171      { filename => "eventlog|",
172        fh => *FH, seekable => 0, statable => 1,
173        modtime => $self->{eventlog}->{thissecond},
174        fingerprint => "0:0" });
175  }
176}
177
178sub getfilefingerprint {
179  return 1;
180}
181
182sub finish {
183  my $self = shift;
184  foreach my $level (qw(CRITICAL WARNING UNKNOWN)) {
185    if (scalar(@{$self->{matchlines}->{$level}})) {
186      foreach my $match (@{$self->{matchlines}->{$level}}) {
187        $match =~ s/EE_WW_TT//;
188        $match =~ s/EE_EE_TT//;
189        $match =~ s/EE_UU_TT//;
190      }
191    }
192    if (exists $self->{lastmsg} && exists $self->{lastmsg}->{$level}) {
193      $self->{lastmsg}->{$level} =~ s/EE_WW_TT//;
194      $self->{lastmsg}->{$level} =~ s/EE_EE_TT//;
195      $self->{lastmsg}->{$level} =~ s/EE_UU_TT//;
196    }
197  }
198  if (-f $self->{orschlorschknorsch}) {
199    unlink $self->{orschlorschknorsch};
200  }
201}
202
203sub rewind {
204  my $self = shift;
205  $self->loadstate();
206  foreach (keys %{$self->{laststate}}) {
207    $self->{newstate}->{$_} = $self->{laststate}->{$_};
208  }
209  $self->addevent(0, "reset");
210  $self->{eventlog}->{thissecond} = 1;
211  $self->savestate();
212  return $self;
213}
214
215
216package Nagios::CheckLogfiles::Search::Eventlog::Handle;
217
218use strict;
219use Exporter;
220use POSIX qw(strftime);
221require Tie::Handle;
222use Win32::EventLog;
223use Win32::TieRegistry (Delimiter => "/");
224use Win32::WinError;
225use IO::File;
226use vars qw(@ISA);
227@ISA = qw(Tie::Handle Nagios::CheckLogfiles::Search::Eventlog);
228our $AUTOLOAD;
229our $tracefile;
230$Win32::EventLog::GetMessageText = 1;
231our @events = ();
232
233
234sub TIEHANDLE {
235  my $class = shift;
236  my $eventlog = shift;
237  my $winwarncrit = shift;
238  my $eventlogformat = shift;
239  my $logfilenocry = shift;
240  my $tivoli = shift;
241  $tracefile = shift;
242  my $self = {};
243  my $oldestoffset = undef;       # oldest event in the eventlog
244  my $numevents = undef;          # number of events in the eventlog
245  my $newestoffset = 0;       # latest event in the eventlog
246  my $save_newestoffset = 0;
247  my $seekoffset = 0;         # temporary pointer
248  my $firstoffset = 0;        # first event created after the last run
249  my $event = {
250      'Length' => undef,
251      'RecordNumber' => undef,
252      'TimeGenerated' => undef,
253      'Timewritten' => undef,
254      'EventID' => undef,
255      'EventType' => undef,
256      'Category' => undef,
257      'ClosingRecordNumber' => undef,
258      'Source' => undef,
259      'Computer' => undef,
260      'Strings' => undef,
261      'Data' => undef,
262      'User' => undef,
263  };
264  @events = ();
265  my $offsetcache = {};
266  my $mustabort = 0;
267  my $internal_error = "";
268  my $lasterror = 0;
269  my $handle = undef;
270  my $must_close_ipc = 0;
271
272  if ($tivoli->{object}) {
273    $eventlogformat = "_tecad_win_";
274  }
275  #
276  # Schritt 1
277  #
278  # Falls es sich um einen Remote-Rechner handelt, muss erst eine
279  # Verbindung zu dessen IPC$-Resource hergestellt werden
280  # Bei einem Server 2008 kann dies ein lokaler Benutzer sein,
281  # der zur Gruppe Ereignisprotokolleser gehoert
282  #
283  if ($eventlog->{computer} ne Win32::NodeName) {
284    my @harmlesscodes = (1219);
285    # 1219 Mehrfache Verbindungen zu einem Server oder ....
286    # net use \\remote\IPC$ /USER:Administrator adminpw
287    eval {
288      require Win32::NetResource;
289    };
290    if ($@) {
291      $mustabort = 1;
292      $internal_error = 'Win32::NetResource not installed';
293    } else {
294      trace(sprintf "connect to %s as %s with password ***",
295          $eventlog->{computer}, $eventlog->{username});
296      if (Win32::NetResource::AddConnection({
297          'Scope' => 0,
298          'Type' => 0,
299          'DisplayType' => 0, # RESOURCEDISPLAYTYPE_GENERIC
300          'Usage' => 0,
301          'RemoteName' => "\\\\".$eventlog->{computer}."\\IPC\$",
302          'LocalName' => '',
303          'Comment' => "check_logfiles",
304          #'Provider' => "Microsoft Windows Network"
305      }, $eventlog->{password}, $eventlog->{username}, 0)) {
306        trace("created ipc channel");
307        $must_close_ipc = 1;
308      } else {
309        Win32::NetResource::GetError($lasterror);
310        if (scalar(grep { $lasterror == $_ } @harmlesscodes) == 0) {
311          $mustabort = 1;
312          $internal_error = 'IPC$ '.Win32::FormatMessage($lasterror);
313          trace("ipc channel could not be established");
314        } else {
315          trace("ipc channel already established");
316        }
317      }
318    }
319  }
320  #
321  # Schritt 2
322  #
323  # Oeffnen der Registry und kontrollieren, ob es das gewuenschte
324  # Eventlog ueberhaupt gibt
325  #
326  if (! $mustabort) {
327    my @haseventlogs = ("application", "system", "security");
328    eval {
329      my $data = undef;
330      if ($eventlog->{computer} ne Win32::NodeName()) {
331        trace("looking into remote registry");
332        $data = $Registry->Connect( $eventlog->{computer},
333            'HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/EventLog/',
334            { Access=>Win32::TieRegistry::KEY_READ(), Delimiter => "/" } );
335      } else {
336        trace("looking into registry");
337        $data = $Registry->Open(
338            'HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/EventLog/',
339            { Access=>Win32::TieRegistry::KEY_READ(), Delimiter => "/" } );
340      }
341      if ($data) {
342        push(@haseventlogs, grep {
343            my $var = $_; ! grep /^$var$/, @haseventlogs
344        } map { lc $_ } $data->SubKeyNames);
345        trace(sprintf "known eventlogs: %s", join(',', @haseventlogs));
346        undef $data;
347      } else {
348        die "no data from HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/EventLog/";
349      }
350    };
351    if ($@) {
352      $mustabort = 1;
353      $internal_error = "Cannot read registry";
354      trace(sprintf "looking into registry failed: %s", $@);
355    } elsif (! scalar(grep { lc $eventlog->{eventlog} eq $_ } @haseventlogs)) {
356      $mustabort = 1;
357      $internal_error = sprintf "Eventlog %s does not exist",
358          $eventlog->{eventlog} if $logfilenocry;
359    }
360  }
361  #
362  # Schritt 3
363  #
364  # Oeffnen des Eventlogs
365  #
366  if (! $mustabort) {
367    my @harmlesscodes = (0, 997);
368    trace(sprintf "opening handle to eventlog %s", $eventlog->{eventlog});
369    $handle =
370        Win32::EventLog->new($eventlog->{eventlog}, $eventlog->{computer});
371    $lasterror = Win32::GetLastError();
372    #    0 Der Vorgang wurde erfolgreich beendet
373    #  997 überlappender E/A-Vorgang wird verarbeitet.
374    #    5 Zugriff verweigert
375    # 1722 Der RPC-Server ist nicht verfügbar.
376
377    # 1722 eventlog gueltig, ip gueltig, aber kein windows-rechner
378    # 1722 eventlog gueltig, ip nicht pingbar
379    #    5 kein secure channel zum remoterechner vorhanden
380
381    if (! $handle || scalar(grep { $lasterror == $_ } @harmlesscodes) == 0) {
382      # sinnlos, weiterzumachen
383      $mustabort = 1;
384      $internal_error = 'open Eventlog '.Win32::FormatMessage($lasterror);
385      trace("opening handle to eventlog failed");
386    }
387  }
388  #
389  # Schritt 4
390  #
391  # Anzahl der Eintraege auslesen.
392  # Dieser Schritt dient ausserdem dazu, die Berechtigung zum Lesen
393  # zu ueberpruefen, da unter Cygwin der letzte Schritt erfolgreich
394  # ausfaellt, auch wenn keine Leseberechtigung besteht.
395  #
396  if (! $mustabort) {
397    $handle->GetOldest($oldestoffset);
398    $handle->GetNumber($numevents);
399    if (! defined $numevents) {
400      # cygwin perl sagt zu allem errorcode=0, auch wenn keine berechtigung
401      # zum zugriff vorliegt. aber ein undef ist zumindest ein indiz, dass
402      # etwas faul ist.
403      $mustabort = 1;
404      $internal_error = "Eventlog permission denied";
405    }
406  }
407  #
408  # Schritt 5
409  #
410  # Jetzt beginnt das eigentliche Auslesen des Eventlogs
411  #
412  if (! $mustabort) {
413    if ($numevents) {
414      $newestoffset = $oldestoffset + $numevents - 1;
415      trace(sprintf "eventlog has offsets %d..%d",
416          $oldestoffset, $newestoffset);
417      if (! $eventlog->{speedup}) {
418        $firstoffset = $oldestoffset;
419      } else {
420        # new method. find the first event which lies within the range
421        # oldestoffset <= offset <= newestoffset
422        $save_newestoffset = $newestoffset;
423        $handle->Read((EVENTLOG_SEEK_READ|EVENTLOG_BACKWARDS_READ),
424            $oldestoffset, $event);
425        $offsetcache->{$oldestoffset} = $event->{Timewritten};
426        if ($event->{Timewritten} >= $eventlog->{lastsecond}) {
427          # even the oldest record was created after the last run
428          # of check_logfiles. the log was cleared or this is the first run ever
429          $firstoffset = $oldestoffset;
430          trace(sprintf "i start from the beginning %d", $firstoffset);
431        } else {
432          $handle->Read((EVENTLOG_SEEK_READ|EVENTLOG_BACKWARDS_READ),
433              $newestoffset, $event);
434          $offsetcache->{$newestoffset} = $event->{Timewritten};
435          if ($event->{Timewritten} >= $eventlog->{lastsecond}) {
436            # the latest event was created after the last run of check_logfiles
437            $seekoffset = $newestoffset;
438            trace(sprintf "start at offset %d", $seekoffset);
439            do {
440              # get the seekoffset's time
441              $handle->Read((EVENTLOG_SEEK_READ|EVENTLOG_BACKWARDS_READ),
442                  $seekoffset, $event);
443              $offsetcache->{$seekoffset} = $event->{Timewritten};
444              if ($event->{Timewritten} >= $eventlog->{lastsecond}) {
445                # inside the search interval. but is it the oldest?
446                if ((exists $offsetcache->{$seekoffset - 1}) &&
447                    ($offsetcache->{$seekoffset - 1} <
448                    $eventlog->{lastsecond})) {
449                  $firstoffset = $seekoffset;
450                  trace(sprintf "found first offset %d (=)", $firstoffset);
451                } else {
452                  $newestoffset = $seekoffset;
453                  $seekoffset =
454                      $oldestoffset + int (($seekoffset - $oldestoffset) / 2);
455                  trace(sprintf "try offset %d (<)", $seekoffset);
456                }
457              } else {
458                # too old. but maybe the next offset?
459                if ((exists $offsetcache->{$seekoffset + 1}) &&
460                    ($offsetcache->{$seekoffset + 1} >=
461                    $eventlog->{lastsecond})) {
462                  $firstoffset = $seekoffset + 1;
463                  trace(sprintf "found first offset %d (+)", $firstoffset);
464                } else {
465                  $oldestoffset = $seekoffset;
466                  $seekoffset =
467                      $seekoffset + int (($newestoffset - $seekoffset) / 2);
468                  trace(sprintf "try offset %d (>)", $seekoffset);
469                }
470              }
471            } while (! $firstoffset);
472            # now position at the first element in question
473            $handle->Read((EVENTLOG_SEEK_READ|EVENTLOG_BACKWARDS_READ),
474                $firstoffset, $event);
475            # adjust the number of elements to scan
476            $newestoffset = $save_newestoffset;
477          } else {
478            # there are no new events
479            # fake firstoffset to avoid entering the while loop
480            $firstoffset = $newestoffset + 1;
481            trace(sprintf "no new events fake %d", $firstoffset);
482          }
483        }
484      }
485      while ($firstoffset <= $newestoffset) {
486        # sequential_reads are not reliable, so better use direct access
487        $handle->Read((EVENTLOG_SEEK_READ|EVENTLOG_FORWARDS_READ),
488            $firstoffset, $event);
489        if (($event->{Timewritten} >= $eventlog->{lastsecond}) &&
490          ($event->{Timewritten} < $eventlog->{thissecond})) {
491          if (included($event, $eventlog->{include}) &&
492              ! excluded($event, $eventlog->{exclude})) {
493            #printf STDERR "passed filter %s\n", Data::Dumper::Dumper($event);
494            my $tmp_event = {};
495            %{$tmp_event} = %{$event};
496            Win32::EventLog::GetMessageText($tmp_event);
497            format_message($eventlogformat, $tmp_event);
498            if ($winwarncrit) {
499              if ($tmp_event->{EventType} == EVENTLOG_WARNING_TYPE) {
500                $tmp_event->{Message} = "EE_WW_TT".$tmp_event->{Message};
501              } elsif ($tmp_event->{EventType} == EVENTLOG_ERROR_TYPE) {
502                $tmp_event->{Message} = "EE_EE_TT".$tmp_event->{Message};
503              }
504            }
505            push(@events, $tmp_event);
506          } else {
507            #printf STDERR "blocked by filter %s\n", Data::Dumper::Dumper($event);
508          }
509        }
510        $firstoffset++;
511      }
512    } else {
513      #printf STDERR "0 events\n";
514    }
515  } else {
516    my $now = time;
517    my $tmp_event = {};
518    $tmp_event->{Message} =
519        "EE_UU_TTCHECK_LOGFILES INTERNAL ERROR ".$internal_error;
520    $tmp_event->{Message} =~ s/\0/ /g;
521    $tmp_event->{Message} =~ s/\s*$//g;
522    $tmp_event->{TimeGenerated} = $now;
523    $tmp_event->{Timewritten} = $now;
524    $tmp_event->{Source} = 'check_logfiles'; # internal usage
525    $tmp_event->{EventType} = EVENTLOG_ERROR_TYPE; # internal usage
526    $tmp_event->{EventID} = 0;
527    format_message($eventlogformat, $tmp_event);
528    push(@events, $tmp_event) if $internal_error;
529  }
530  #
531  # Aufraeumen
532  #
533  $handle->Close() if $handle;
534  if ($must_close_ipc) {
535    if (Win32::NetResource::CancelConnection(
536        "\\\\".$eventlog->{computer}."\\IPC\$", 0, 0)) {
537      trace("closed the ipc connection");
538    } else {
539      trace("could not close the ipc connection");
540      if (Win32::NetResource::CancelConnection(
541          "\\\\".$eventlog->{computer}."\\IPC\$", 0, 1)) {
542        trace("closed the ipc connection by force");
543      } else {
544        trace("could not close the ipc connection even by force");
545      }
546    }
547  }
548  bless $self, $class;
549  return $self;
550}
551
552sub SEEK {
553}
554
555sub STAT {
556}
557
558sub OPEN {
559}
560
561sub CLOSE {
562}
563
564sub GETC {
565}
566
567sub READ {
568}
569
570sub READLINE {
571  if (my $event = shift @events) {
572    return $event->{Message};
573  } else {
574    return undef;
575  }
576}
577
578sub format_message {
579  my $eventlogformat = shift;
580  my $event = shift;
581  # formatstring:
582  # %t EventType
583  # %c Category
584  # %s Source
585  # %i EventID
586  # %m Message
587  # %w Timewritten
588  # %g Timegenerated
589  # %d Date/Time
590  # %u User # not documented @ cpan
591  if ($eventlogformat eq "_tecad_win_") {
592    $eventlogformat = "%__TiVoLi__g %C %t N/A %s %__TiVoLi__i %m";
593  }
594  if (! $event->{Message}) {
595      $event->{Message} = $event->{Strings};
596      $event->{Message} =~ s/\0/ /g;
597      $event->{Message} =~ s/\s*$//g;
598  }
599  $event->{Message} = 'unknown message' if ! $event->{Message};
600  $event->{Message} =~ tr/\r\n/ /d;
601  my $tz = '';
602  my $format = {};
603  $format->{'%t'} =
604      ($event->{EventType} == -1) ?
605          'Internal' :
606      ($event->{EventType} == EVENTLOG_WARNING_TYPE) ?
607          'Warning' :
608      ($event->{EventType} == EVENTLOG_ERROR_TYPE) ?
609          'Error' :
610      ($event->{EventType} == EVENTLOG_INFORMATION_TYPE) ?
611          'Information' :
612      ($event->{EventType} == EVENTLOG_AUDIT_SUCCESS) ?
613          'AuditSuccess' :
614      ($event->{EventType} == EVENTLOG_AUDIT_FAILURE) ?
615          'AuditFailure' :
616      ($event->{EventType} == EVENTLOG_SUCCESS) ?
617          'Success' : 'UnknType';
618  $format->{'%c'} = ! $event->{Category} ? 'None' :
619      join('_', split(" ", $event->{Category}));
620  $format->{'%C'} = ! $event->{Category} ? 'None' : $event->{Category};
621  $format->{'%s'} = join('_', split(" ", $event->{Source}));
622  $format->{'%i'} = sprintf '%04d', $event->{EventID} & 0xffff;
623  $format->{'%__TiVoLi__i'} = sprintf '%s', $event->{EventID} & 0xffff;
624  $format->{'%m'} = $event->{Message};
625  $format->{'%w'} = strftime("%Y-%m-%dT%H:%M:%S",
626      localtime($event->{Timewritten})).$tz;
627  $format->{'%g'} = strftime("%Y-%m-%dT%H:%M:%S",
628      localtime($event->{TimeGenerated})).$tz;
629  $format->{'%W'} = $event->{Timewritten};
630  $format->{'%G'} = $event->{TimeGenerated};
631  $format->{'%u'} = $event->{User} || 'undef';
632  $format->{'%__TiVoLi__g'} = join(" ", (split(/\s+/,
633      scalar localtime $event->{TimeGenerated}))[1,2,3,4]);
634      # month day time and year => %t %s
635  my $message = $eventlogformat;
636  foreach (keys %{$format}) {
637    $message =~ s/$_/$format->{$_}/g;
638  }
639  while ($message =~ /%(\d+)m/) {
640    my $search = "%".$1."m";
641    my $replace = sprintf "%.".$1."s", $event->{Message};
642    $message =~ s/$search/$replace/g;
643  }
644  $event->{Message} = $message;
645}
646
647sub included {
648  my $event = shift;
649  my $filter = shift;
650  my $filters = 0;
651  my $matches = {};
652  # EventCategory ist ein INTEGER!!!
653  # und ausserdem ist pro Source ein eigener Satz von Kategorien moeglich
654  # man muesste deren Bezeichnungen aus der Registry lesen.
655  # in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application
656  # stehen die Sources, die eigene Kategorien definiert haben.
657  # Im Key CategoryMessageFile ist die Datei hinterlegt, der die Kategorien
658  # entnommen werden koennen. In CategoryCount steht die Anzahl der
659  # selbstdefinierten Kategorien.
660  foreach my $attr (qw(Source Category)) {
661    $matches->{$attr} = 0;
662    if (exists $filter->{$attr}) {
663      foreach my $item (split(',', $filter->{$attr})) {
664        #printf "items: %s ? %s\n", $item, $event->{$attr};
665        if (lc $item eq lc $event->{$attr}) {
666          #printf "-> %s eq %s\n", lc $item, lc $event->{$attr};
667          $matches->{$attr}++;
668        }
669      }
670    } else {
671      #printf "no filter for %s\n", $attr;
672      $matches->{$attr}++;
673    }
674  }
675  foreach my $attr (qw(EventID)) {
676    $matches->{$attr} = 0;
677    if (exists $filter->{$attr}) {
678      foreach my $item (split(',', $filter->{$attr})) {
679        #printf "items: %s ? %s\n", $item, $event->{$attr};
680        #if (lc $item eq lc ($event->{$attr} & 0xffff)) {
681        if ($item == ($event->{$attr} & 0xffff)) {
682          #printf "-> %s eq %s\n", lc $item, lc ($event->{$attr} & 0xffff);
683          $matches->{$attr}++;
684        }
685      }
686    } else {
687      #printf "no filter for %s\n", $attr;
688      $matches->{$attr}++;
689    }
690  }
691  foreach my $attr (qw(EventType)) {
692    $matches->{$attr} = 0;
693    if (exists $filter->{$attr}) {
694#printf "succ %s\n", EVENTLOG_SUCCESS;
695#printf "warn %s\n", EVENTLOG_WARNING_TYPE;
696#printf "err %s\n", EVENTLOG_ERROR_TYPE;
697#printf "info %s\n", EVENTLOG_INFORMATION_TYPE;
698#printf "audit %s\n", EVENTLOG_AUDIT_SUCCESS;
699#printf "fail %s\n", EVENTLOG_AUDIT_FAILURE;
700      foreach my $item (split(',', $filter->{$attr})) {
701        if ((lc $item =~ /^succ/ && $event->{$attr} == EVENTLOG_SUCCESS) ||
702            (lc $item =~ /warn/ && $event->{$attr} == EVENTLOG_WARNING_TYPE) ||
703            (lc $item =~ /err/ && $event->{$attr} == EVENTLOG_ERROR_TYPE) ||
704            (lc $item =~ /info/ && $event->{$attr} == EVENTLOG_INFORMATION_TYPE) ||
705            (lc $item =~ /audit.*succ/ && $event->{$attr} == EVENTLOG_AUDIT_SUCCESS) ||
706            (lc $item =~ /fail/ && $event->{$attr} == EVENTLOG_AUDIT_FAILURE)) {
707          $matches->{$attr}++;
708        }
709      }
710    } else {
711      #printf "no filter for %s\n", $attr;
712      $matches->{$attr}++;
713    }
714  }
715  if ($filter->{operation} eq 'and') {
716    return (scalar(grep { $matches->{$_} } keys %{$matches}) == 4) ? 1 : 0;
717  } else {
718    return (scalar(grep { $matches->{$_} } keys %{$matches}) == 0) ? 0 : 1;
719  }
720}
721
722sub excluded {
723  my $event = shift;
724  my $filter = shift;
725  my $filters = 0;
726  my $matches = {};
727  # EventCategory ist ein INTEGER!!!
728  foreach my $attr (qw(Source Category)) {
729    $matches->{$attr} = 0;
730    if (exists $filter->{$attr}) {
731      foreach my $item (split(',', $filter->{$attr})) {
732        #printf "items: %s ? %s\n", $item, $event->{$attr};
733        if (lc $item eq lc $event->{$attr}) {
734          #printf "-> %s eq %s\n", lc $item, lc $event->{$attr};
735          $matches->{$attr}++;
736        }
737      }
738    } else {
739      #printf "no filter for %s\n", $attr;
740      #$matches->{$attr}++;
741    }
742  }
743  foreach my $attr (qw(EventID)) {
744    $matches->{$attr} = 0;
745    if (exists $filter->{$attr}) {
746      foreach my $item (split(',', $filter->{$attr})) {
747        #printf "items: %s ? %s\n", $item, $event->{$attr};
748        #if (lc $item eq lc ($event->{$attr} & 0xffff)) {
749        if ($item == ($event->{$attr} & 0xffff)) {
750          #printf "-> %s eq %s\n", lc $item, lc ($event->{$attr} & 0xffff);
751          $matches->{$attr}++;
752        }
753      }
754    } else {
755      #printf "no filter for %s\n", $attr;
756      #$matches->{$attr}++;
757    }
758  }
759  foreach my $attr (qw(EventType)) {
760    $matches->{$attr} = 0;
761    if (exists $filter->{$attr}) {
762      foreach my $item (split(',', $filter->{$attr})) {
763        if ((lc $item =~ /^succ/ && $event->{$attr} == EVENTLOG_SUCCESS) ||
764            (lc $item =~ /warn/ && $event->{$attr} == EVENTLOG_WARNING_TYPE) ||
765            (lc $item =~ /err/ && $event->{$attr} == EVENTLOG_ERROR_TYPE) ||
766            (lc $item =~ /info/ && $event->{$attr} == EVENTLOG_INFORMATION_TYPE) ||
767            (lc $item =~ /audit.*succ/ && $event->{$attr} == EVENTLOG_AUDIT_SUCCESS) ||
768            (lc $item =~ /fail/ && $event->{$attr} == EVENTLOG_AUDIT_FAILURE)) {
769          #printf "type %s matched\n", $item;
770          $matches->{$attr}++;
771        }
772      }
773    } else {
774      #printf "no filter for %s\n", $attr;
775      #$matches->{$attr}++;
776    }
777  }
778  #printf "%s\n", Data::Dumper::Dumper($matches);
779  if ($filter->{operation} eq 'and') {
780    return (scalar(grep { $matches->{$_} } keys %{$matches}) == 4) ? 1 : 0;
781  } else {
782    return (scalar(grep { $matches->{$_} } keys %{$matches}) == 0) ? 0 : 1;
783  }
784}
785
786sub trace {
787  my $format = shift;
788  if (-f $tracefile) {
789    my $logfh = new IO::File;
790    $logfh->autoflush(1);
791    if ($logfh->open($tracefile, "a")) {
792      $logfh->printf("%s: ", scalar localtime);
793      $logfh->printf($format, @_);
794      $logfh->printf("\n");
795      $logfh->close();
796    }
797  }
798}
799
800
801sub AUTOLOAD {
802 #printf "uarghh %s\n", $AUTOLOAD;
803}
804
8051;
806