File Coverage

Syslog.pm
Criterion Covered Total %
statement 17 923 1.8
branch 0 686 0.0
condition 0 178 0.0
subroutine 6 44 13.6
pod 30 35 85.7
total 53 1866 2.8


line stmt bran cond sub pod time code
1             # Perl Module
2             # Purpose: One Module to provide Syslog functionality
3             # Provide log parser, sender, receiver
4             # Author sparsons@cpan.org
5             #
6             #
7             # Version
8             #
9             # 0.8.0 - initial test release
10             # 0.8.1 - modify listener to make report and verbose > 1 independent
11             # 0.8.2 - add counters for parse, filter, error
12             # 0.9.0 - db storage is indexed
13             # 0.9.1 - single quoted all hash index
14             # 0.9.2 - modify parse engine
15             # 1.0.0 - modify parse engine, user defined TAGS, forwarder in listen object
16             #
17              
18             package Net::Dev::Tools::Syslog;
19              
20 1     1   46118 use strict;
  1         3  
  1         115  
21 1     1   1409 use Time::Local;
  1         3995  
  1         87  
22 1     1   2137 use IO::Socket;
  1         63947  
  1         5  
23 1     1   2874 use Sys::Hostname;
  1         1939  
  1         66  
24              
25              
26             BEGIN {
27 1     1   6 use Exporter();
  1         2  
  1         38  
28 1     1   15 our @ISA = qw(Exporter);
29 1         68617 our $VERSION = 1.0.0;
30             }
31              
32             #
33             # Tags
34             #
35             our @PARSER_func = qw(
36             parse_syslog_line
37             parse_syslog_msg
38             parse_tag
39             syslog_stats_epoch2datestr
40             );
41              
42             our @TIME_func = qw(
43             epoch_to_syslog_timestamp
44             epoch_to_datestr
45             make_timeslots
46             epoch_timeslot_index
47             date_filter_to_epoch
48             );
49              
50             our @SYSLOG_func = qw(
51             normalize_facility
52             normalize_severity
53             decode_PRI
54             );
55              
56             our @REFERENCE_func = qw(
57             syslog_stats_href
58             syslog_device_aref
59             syslog_facility_aref
60             syslog_severity_aref
61             syslog_tag_aref
62             syslog_timeslot_aref
63             );
64              
65             our @COUNTERS = qw(
66             syslog_error_count
67             syslog_filter_count
68             syslog_parse_count
69             );
70              
71              
72              
73              
74              
75             our @EXPORT = ('syslog_error', @PARSER_func, @TIME_func, @SYSLOG_func, @REFERENCE_func, @COUNTERS );
76             our @EXPORT_OK = qw();
77              
78             our %EXPORT_TAGS = (
79             parser => [@PARSER_func],
80             time => [@TIME_func],
81             syslog => [@SYSLOG_func],
82             counter => [@COUNTERS],
83             );
84             #
85             # Global variables
86             #
87             our $SYSLOG_href;
88             our $ERROR;
89             our $ERROR_count;
90             our $FILTER_count;
91             our $PARSE_count;
92             our $DEBUG;
93             our %FH;
94             our %STATS;
95             our @DEVICES;
96             our @TAGS;
97             our @FACILITYS;
98             our @SEVERITYS;
99             our @TIMESLOTS;
100             our %LASTMSG;
101             our $NOTAG = 'noTag';
102              
103             our $YEAR = ((localtime)[5]) + 1900;
104              
105             our %WDAY = (
106             '0' => 'Sun',
107             '1' => 'Mon',
108             '2' => 'Tue',
109             '3' => 'Wed',
110             '4' => 'Thu',
111             '5' => 'Fri',
112             '6' => 'Sat',
113             );
114              
115             our %MON = (
116             1 => 'Jan', 2 => 'Feb', 3 => 'Mar',
117             4 => 'Apr', 5 => 'May', 6 => 'Jun',
118             7 => 'Jul', 8 => 'Aug', 9 => 'Sep',
119             10 => 'Oct', 11 => 'Nov', 12 => 'Dec',
120             );
121              
122              
123             our %MON_index = (
124             'JAN' => 1, 'Jan' => 1, 'jan' => 1,
125             'FEB' => 2, 'Feb' => 2, 'feb' => 2,
126             'MAR' => 3, 'Mar' => 3, 'mar' => 3,
127             'APR' => 4, 'Apr' => 4, 'apr' => 4,
128             'MAY' => 5, 'May' => 5, 'may' => 5,
129             'JUN' => 6, 'Jun' => 6, 'jun' => 6,
130             'JUL' => 7, 'Jul' => 7, 'jul' => 7,
131             'AUG' => 8, 'Aug' => 8, 'aug' => 8,
132             'SEP' => 9, 'Sep' => 9, 'sep' => 9,
133             'OCT' => 10, 'Oct' => 10, 'oct' => 10,
134             'NOV' => 11, 'Nov' => 11, 'nov' => 11,
135             'DEC' => 12, 'Dec' => 12, 'dec' => 12,
136             );
137              
138              
139             our %Syslog_Facility = (
140             'kern' => 0, 'kernel' => 0,
141             'user' => 1,
142             'mail' => 2,
143             'daemon' => 3,
144             'auth' => 4,
145             'syslog' => 5,
146             'lpr' => 6,
147             'news' => 7,
148             'uucp' => 8,
149             'cron' => 9,
150             'authpriv' => 10,
151             'ftp' => 11,
152             'ntp' => 12,
153             'audit' => 13,
154             'alert' => 14,
155             'at' => 15,
156             'local0' => 16,
157             'local1' => 17,
158             'local2' => 18,
159             'local3' => 19,
160             'local4' => 20,
161             'local5' => 21,
162             'local6' => 22,
163             'local7' => 23,
164             );
165              
166              
167             our %Facility_Index = (
168             0 => 'kern',
169             1 => 'user',
170             2 => 'mail',
171             3 => 'daemon',
172             4 => 'auth',
173             5 => 'syslog',
174             6 => 'lpr',
175             7 => 'news',
176             8 => 'uucp',
177             9 => 'cron',
178             10 => 'authpriv',
179             11 => 'ftp',
180             12 => 'ntp',
181             13 => 'audit',
182             14 => 'alert',
183             15 => 'at',
184             16 => 'local0',
185             17 => 'local1',
186             18 => 'local2',
187             19 => 'local3',
188             20 => 'local4',
189             21 => 'local5',
190             22 => 'local6',
191             23 => 'local7',
192             );
193              
194             our %Severity_Index = (
195             0 => 'emerg',
196             1 => 'alert',
197             2 => 'crit',
198             3 => 'err',
199             4 => 'warn',
200             5 => 'notice',
201             6 => 'info',
202             7 => 'debug'
203             );
204              
205              
206              
207              
208             our %Syslog_Severity = (
209             'emerg' => 0, 'emergency' => 0,
210             'alert' => 1,
211             'crit' => 2, 'critical' => 2,
212             'err' => 3, 'error' => 3,
213             'warn' => 4, 'warning' => 4,
214             'notice' => 5,
215             'info' => 6, 'information' => 6, 'informational' => 6,
216             'debug' => 7,
217             );
218              
219              
220             our @FACILITY = qw( kern user mail daemon
221             auth syslog lpr news
222             uucp cron authpriv ftp
223             ntp audit alert at
224             local0 local1 local2 local3
225             local4 local5 local6 local7
226             );
227             our @SEVERITY = qw( emerg alert crit err warn notice info debug);
228              
229             #
230             # syslog message
231             # PRI HEADER MSG
232             # PRI: <0-161>
233             # HEADER: TIMESTAMP HOST
234             # TIMESTAMP Xxx dd hh:mm:ss
235             # Xxx d hh:mm:ss
236             # HOST hostname or ip
237             # MSG: TAG Content
238             # TAG no more than 32 chars
239             #
240             #
241              
242             #
243             # Define Reg expr strings
244             #
245             # PRI
246             # $1 = decimal value of PRI
247             #
248             our $PRI = '<(\d{1,3})>';
249             #
250             # Timestamp
251             # $1 = whole timestamp
252             # $2 = Month string
253             # $3 = Month day (decimal)
254             # $4 = hh:mm::ss
255             #
256             our $TIMESTAMP_strict = '(([JFMASOND]\w\w) {1,2}(\d+) (\d{2}:\d{2}:\d{2}))';
257             our $TIMESTAMP = '(([JFMASONDjfmasond]\w\w) {1,2}(\d+) (\d{2}:\d{2}:\d{2}))';
258              
259             #
260             # Hostname
261             # alphanumeric string including '_', '.'
262             # $1 = hostname
263             #
264             our $HOSTNAME = '([a-zA-Z0-9_\.\-]+)';
265              
266              
267             #
268             # let user define TAG patterns
269             #
270             # $TAG_1 $1 = tag, $2 = pid, $3 = content
271             # $TAG_2 $1 = tag, $2 = content no pid
272             # $TAG_3 $1 = tag, $2 = pid, $3 = content
273              
274              
275             our $TAG_1 = '';
276             our $TAG_2 = '';
277             our $TAG_3 = '';
278              
279              
280             #
281             # Content
282             # $1 = message
283             our $MESSAGE = '(.+)$';
284              
285              
286              
287             our $SYSLOG_pattern = sprintf("^%s{1} %s %s", $TIMESTAMP, $HOSTNAME, $MESSAGE);
288              
289              
290              
291             #
292             #=============================================================================
293             #
294             # Methods and Functions
295             #
296             #
297             # Syslog Constructor
298             #
299             # Use the anonymous hash to hold info for rest of module
300             #
301             # Arguments
302             # dump 0|1 (0) write to file
303             # append 0|1 (1) append to existing report
304             # ext extension (.slp)
305             # report 0|1 (1) create report
306             # interval report time slot interval
307             # rx_time 0|1 determine if we should use msg time or preamble time
308             # lastmsg 0|1 (0) do not use last message values
309             # moreTime 0|1 (0) parse and calculate more time info
310             # parseTag 0|1 (0) parse TAG from SYSLOG MESSAGE
311             # debug 0-3 (0) debug level
312             # format (bsd) syslog format line
313             # filters
314             # tag string in tag to filter
315             # min_date min date mm/dd/yyyy hh:mm:ss
316             # min_date_epoch filter_min_date => epoch
317             # max_date max date mm/dd/yyyy hh:mm:ss
318             # max_date_epoch max_date => epoch
319             # device
320             # format format of expected syslog message
321             # bsd (timestamp, host, tag, content)
322             # noHost (timestamp, tag, content)
323             #
324             #
325            
326             sub parse {
327             # create object
328 0     0 1   my $_proto = shift;
329 0   0       my $_class = ref($_proto) || $_proto;
330 0           my $_this = {};
331             # bless object
332 0           bless($_this, $_class);
333              
334             # get object arguments
335 0           my %_arg = @_;
336 0           my $_a;
337              
338 0           $ERROR = '';
339              
340             #rg{$_a}); define defaults
341 0           $_this->{'ext'} = 'slp';
342 0           $_this->{'dump'} = 0; # default not to dump
343 0           $_this->{'report'} = 1; # default to report
344 0           $_this->{'append'} = 0; # default not to append
345 0           $_this->{'interval'} = 3600; # default timeslot interval
346 0           $_this->{'rx_time'} = 0; # default to not use time stamp from preamble
347 0           $_this->{'lastmsg'} = 0; # default to not redo last message when 'last msg' line
348 0           $_this->{'debug'} = 0;
349 0           $_this->{'filter'} = 0; # default no filtering
350 0           $_this->{'format'} = 'bsd'; # default syslog message string to bsd
351 0           $_this->{'moreTime'} = 0; # default time analysis
352 0           $_this->{'parseTag'} = 0; # default tha parsing of TAGs
353            
354 0           foreach $_a (keys %_arg) {
355 0 0         if ($_a =~ /^-?dump$/i) {$_this->{'dump'} = delete($_arg{$_a}); }
  0 0          
  0 0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
356 0           elsif ($_a =~ /^-?append$/i) {$_this->{'append'} = delete($_arg{$_a}); }
357 0           elsif ($_a =~ /^-?ext$/i) {$_this->{'ext'} = delete($_arg{$_a}); }
358 0           elsif ($_a =~ /^-?report$/i) {$_this->{'report'} = delete($_arg{$_a}); }
359 0           elsif ($_a =~ /^-?interval$/i) {$_this->{'interval'} = delete($_arg{$_a}); }
360 0           elsif ($_a =~ /^-?rx_time$/i) {$_this->{'rx_time'} = delete($_arg{$_a}); }
361 0           elsif ($_a =~ /^-?lastmsg$/i) {$_this->{'lastmsg'} = delete($_arg{$_a}); }
362 0           elsif ($_a =~ /^-?debug$/i) {$_this->{'debug'} = delete($_arg{$_a}); }
363 0           elsif ($_a =~ /^-?min_date$/i) {$_this->{'filter_min_date'} = delete($_arg{$_a}); }
364 0           elsif ($_a =~ /^-?max_date$/i) {$_this->{'filter_max_date'} = delete($_arg{$_a}); }
365 0           elsif ($_a =~ /^-?device$/i) {$_this->{'filter_device'} = delete($_arg{$_a}); }
366 0           elsif ($_a =~ /^-?tag$/i) {$_this->{'filter_tag'} = delete($_arg{$_a}); }
367 0           elsif ($_a =~ /^-?message$/i) {$_this->{'filter_message'} = delete($_arg{$_a}); }
368 0           elsif ($_a =~ /^-?format$/i) {$_this->{'format'} = delete($_arg{$_a}); }
369 0           elsif ($_a =~ /^-?moreTime$/i) {$_this->{'moreTime'} = delete($_arg{$_a}); }
370             elsif ($_a =~ /^-?parseTag$/i) {$_this->{'parseTag'} = delete($_arg{$_a}); }
371             else {
372 0           $ERROR = "unsupported option $_a => $_arg{$_a}";
373 0 0         return(wantarray ? (undef, $ERROR) : undef);
374             }
375             }
376              
377             # set globals
378 0           $DEBUG = $_this->{'debug'};
379 0           $ERROR_count = 0;
380 0           $FILTER_count = 0;
381 0           $PARSE_count = 0;
382              
383             # init stat hash
384 0 0         if ($_this->{'report'}) {%STATS = ();}
  0            
385              
386              
387             # check format
388 0 0 0       if ($_this->{'format'} ne 'bsd' && $_this->{'format'} ne 'noHost') {
389 0           $ERROR = "unsupported format [$_this->{'format'}]";
390 0 0         return(wantarray ? (undef, $ERROR) : undef);
391             }
392              
393             #
394             # check arguments
395             #
396             # if dump is enabled,
397 0 0         if ($_this->{'dump'}) {
398 0           $_this->{'repository'} = $_this->{'dump'};
399             # make sure we have trailing '/' or '\'
400 0 0         if ($^O eq 'MSWin32') {
401 0 0         if ($_this->{'repository'} !~ /\\$/)
  0            
402             {$_this->{'repository'} = $_this->{'repository'} . '\\';}
403             }
404             else {
405 0 0         if ($_this->{'repository'} !~ /\/$/)
  0            
406             {$_this->{'repository'} = $_this->{'repository'} . '/';}
407             }
408             # check if writable
409 0 0         if (!-w $_this->{'repository'}) {
410 0           $ERROR = "dump site not writeable";
411 0           $_this = undef;
412 0 0         return(wantarray ? (undef, $ERROR) : undef);
413             }
414             }
415             #
416             # interval can not be less than 1 min (60 sec), since the index
417             # only goes down to the minute
418             #
419 0 0         if ($_this->{'interval'} < 60) {
420 0           $_this->{'interval'} = 60;
421 0           log_debug(3, "NOTICE: interval changed to 60 seconds\n");
422             }
423             # filtering may need other settings enabled
424 0 0 0       if ($_this->{'filter_min_date'} || $_this->{'filter_max_date'}) {$_this->{'moreTime'} = 1;}
  0            
425 0 0         if ($_this->{'filter_device'}) {$_this->{'format'} = 'bsd';}
  0            
426 0 0         if ($_this->{'filter_tag'}) {$_this->{'parseTag'} = 1;}
  0            
427 0 0         if ($_this->{'report'}) {$_this->{'moreTime'} = 1;}
  0            
428              
429             #
430             # if we have any filters defined, then enable filtering
431             #
432 0 0 0       $_this->{'filter'} = 1 if $_this->{'filter_min_date'} || $_this->{'filter_max_date'} ||
      0        
      0        
      0        
433             $_this->{'filter_device'} || $_this->{'filter_tag'} ||
434             $_this->{'filter_message'};
435              
436             # check min and max date
437 0 0         if ($_this->{'filter_min_date'}) {
438 0           log_debug(3, "convert min date: [%s]\n", $_this->{'filter_min_date'});
439 0           $_this->{'filter_min_date_epoch'} = date_filter_to_epoch($_this->{'filter_min_date'});
440 0 0         unless($_this->{'filter_min_date_epoch'}) {
441 0           $ERROR = "min date filter epoch undefined";
442 0 0         return(wantarray ? (undef, $ERROR) : undef);
443             }
444 0           log_debug(3, "converted min date to: [%s]\n", $_this->{'filter_min_date_epoch'},
445             epoch_to_datestr($_this->{'filter_min_date_epoch'}),
446             );
447             }
448 0 0         if ($_this->{'filter_max_date'}) {
449 0           log_debug(3, "convert max date: [%s]\n", $_this->{'filter_max_date'});
450 0           $_this->{'filter_max_date_epoch'} = date_filter_to_epoch($_this->{'filter_max_date'});
451 0 0         unless($_this->{'filter_max_date_epoch'}) {
452 0           $ERROR = "max date filter epoch undefined";
453 0 0         return(wantarray ? (undef, $ERROR) : undef);
454             }
455 0           log_debug(3, "converted max date to: [%s]\n", $_this->{'filter_max_date_epoch'},
456             epoch_to_datestr($_this->{'filter_max_date_epoch'})
457             );
458             }
459              
460 0 0 0       if ($_this->{'filter_min_date'} && $_this->{'filter_max_date'}) {
461 0           log_debug(3, "check min and max date range\n");
462 0 0         if ($_this->{'filter_min_date_epoch'} >= $_this->{'filter_max_date_epoch'}) {
463 0           $ERROR = sprintf("filter_min_date >= filter_max_date: %s >= %s",
464             _commify($_this->{'filter_min_date_epoch'}),
465             _commify($_this->{'filter_max_date_epoch'}),
466             );
467 0           log_debug(2, "%s\n", $ERROR);
468 0 0         return(wantarray ? (undef, $ERROR) : undef);
469             }
470 0           log_debug(3, "min max date range: [%s] => [%s]\n",
471             $_this->{'filter_min_date'}, $_this->{'filter_max_date'},
472             );
473 0           log_debug(3, "min max date range: [%s] => [%s]\n",
474             _commify($_this->{'filter_min_date_epoch'}), _commify($_this->{'filter_max_date_epoch'})
475             );
476             }
477              
478 0 0         if ($DEBUG) {
479 0           foreach (sort keys %{$_this}) {
  0            
480 0           log_debug(2, "parse object properties: %-12s => [%s]\n", $_, $_this->{$_});
481             }
482             }
483              
484             # return reference to object
485 0 0         return(wantarray ? ($_this, $ERROR) : $_this);
486              
487             } # end sub parse
488             #
489             #.............................................................................
490             #
491             # Function to parse syslog line and populate hash ref $SYSLOG_href
492             #
493             # $SYSLOG_href->{'line'} current line from syslog file
494             # {'timestamp'} timestamp from syslog message
495             # {'device'} device name from syslog message
496             # {'message'} syslog message, from after devname
497             #
498             # {'month_str'} month from syslog message timestamp (Jan, Feb, ..)
499             # {'month'} month index 0->11
500             # {'day'} day from syslog message timestamp
501             # {'time_str'} hh:mm:ss from syslog message timestamp
502             # {'hour'} hh from syslog message timestamp
503             # {'min'} mm from syslog message timestamp
504             # {'sec'} ss from syslog message timestamp
505             # {'year'} year assumed from localtime
506             # {'epoch'} epoch time converted from syslog message timestamp
507             # {'wday'} wday integer derived from epoch (0-6) = (Sun-Sat)
508             # {'wday_str'} wday string converted, (Sun, Mon, ...)
509             # {'date_str'} syslog message {'epoch'} convert to common format
510             #
511             # {'tag'} syslog message content tag
512             # {'pid'} syslog message content tag pid
513             # {'content'} syslog message content after tag parsed out
514             #
515             # {'preamble'}
516             # {'rx_epoch'} extra info: rx time epoch
517             # {'rx_timestamp'} extra info: rx timestamp
518             # {'rx_priority'} extra info: priority (text)
519             # {'rx_facility'} extra info: syslog facility (text)
520             # {'rx_severity'} extra info: syslog severity (text)
521             # {'srcIP'} extra info: src IP address
522             #
523             # {'rx_epoch'} extra info: rx time epoch
524             # {'rx_date_str'} extra info: rx time date string
525             # {'rx_time_str'} extra info: rx time (hh:mm:ss)
526             # {'rx_year'} extra info: rx time year value
527             # {'rx_month'} extra info: rx time month value
528             # {'rx_month_str'} extra info: rx time month value string (Jan, Feb,..)
529             # {'rx_day'} extra info: rx time day value
530             # {'rx_wday'} extra info: rx time weekday (0-6) (Sun, Mon,..)
531             # {'rx_hour'} extra info: rx time hour value
532             # {'rx_min'} extra info: rx time minute value
533             # {'rx_sec'} extra info: rx time second value
534             # Arg
535             # $_[0] - line from syslog file
536             #
537             sub parse_syslog_line {
538 0     0 1   my $_obj = shift;
539 0   0       my $_line = shift || $_;
540              
541 0           my ($_preamble, $_msg, $_ok, $_last);
542 0           my @_pre = ();
543              
544 0           %{$SYSLOG_href} = ();
  0            
545              
546 0           $SYSLOG_href->{'device'} = '';
547 0           $SYSLOG_href->{'format'} = $_obj->{'format'};
548              
549 0           $PARSE_count++;
550 0           $_line =~ s/\n$//;
551 0           $SYSLOG_href->{'line'} = $_line;
552              
553 0           log_debug(2, "func: parse_syslog_line\n");
554 0           log_debug(1, "[%s]: [%s]\n", $., $_line);
555              
556             # if given line is blank ignore it,
557             # it can throw off the stats
558 0 0         if ($_line =~ /^\s*$/) {
559 0           $ERROR = 'disregarding current line: blank line';
560 0           $ERROR_count++;
561 0 0         return(wantarray ? (undef, $ERROR) : undef);
562             }
563              
564             #
565             # Set syslog message parser
566             #
567 0 0         if ($_obj->{'format'} eq "bsd")
  0 0          
    0          
568 0           {$SYSLOG_pattern = sprintf("%s{1} %s %s", $TIMESTAMP,$HOSTNAME,$MESSAGE);}
569             elsif ($_obj->{'format'} eq "noHost")
570             {$SYSLOG_pattern = sprintf("%s{1} %s", $TIMESTAMP, $MESSAGE);}
571             elsif ($_obj->{'format'} eq "self")
572 0           { 1; }
573             else {
574 0           $ERROR = "unsupported syslog message format: $_obj->{'format'}";
575 0           $ERROR_count++;
576 0 0         return(wantarray ? (undef, $ERROR) : undef);
577             }
578 0           log_debug(3, "pattern [%s]: %s\n", $_obj->{'format'}, $SYSLOG_pattern);
579              
580              
581             # see if we have more than just the syslog message
582 0 0         if ($_line =~ /^(\<\d{1,3}\>)?($SYSLOG_pattern)/) {$_preamble = $1; $_msg = $2}
  0 0          
  0 0          
  0            
583 0           elsif ($_line =~ /^(.+)\s+($SYSLOG_pattern)/) {$_preamble = $1; $_msg = $2}
  0            
584 0           elsif ($_line =~ /^(.+),($SYSLOG_pattern)/) {$_preamble = $1; $_msg = $2,
  0            
585             $_preamble =~ s/\,/ /g;
586             }
587 0           else {$_preamble = undef; $_msg = $_line;}
588              
589 0   0       log_debug(2, "syslog preamble: %s\n", $_preamble || 'none');
590 0   0       log_debug(2, "syslog message: %s\n", $_msg || 'NO MESSAGE');
591             #
592             # parse syslog message
593             #
594 0           parse_syslog_msg($_msg, $_obj->{'moreTime'}, $_obj->{'parseTag'}, 1);
595 0 0 0       if ($SYSLOG_href->{'device'} eq '' && $_obj->{'format'} ne 'noHost') {
596 0           $ERROR = 'no device name parsed from line';
597 0           $ERROR_count++;
598             #return(wantarray ? (undef, $ERROR) : undef);
599             }
600             #
601             # if we have a preamble, parse it out
602             #
603 0 0         if ($_preamble) {
604 0           $_preamble =~ s/UTC//;
605 0           log_debug(2, "syslog line contains preamble:\n");
606             # preamble: yyyy-mm-dd hh:mm:ss prio ip
607 0           parse_preamble($_preamble);
608              
609             # determine what time we want to keep
610 0 0         if ($_obj->{'rx_time'}) {
611 0   0       $SYSLOG_href->{'timestamp'} = $SYSLOG_href->{'rx_timestamp'} || $SYSLOG_href->{'timestamp'};
612 0   0       $SYSLOG_href->{'epoch'} = $SYSLOG_href->{'rx_epoch'} || $SYSLOG_href->{'epoch'};
613 0   0       $SYSLOG_href->{'month'} = $SYSLOG_href->{'rx_month'} || $SYSLOG_href->{'month'};
614 0   0       $SYSLOG_href->{'month_str'} = $SYSLOG_href->{'rx_month_str'} || $SYSLOG_href->{'month_str'};
615 0   0       $SYSLOG_href->{'day'} = $SYSLOG_href->{'rx_day'} || $SYSLOG_href->{'day'};
616 0   0       $SYSLOG_href->{'time_str'} = $SYSLOG_href->{'rx_time_str'} || $SYSLOG_href->{'time_str'};
617 0   0       $SYSLOG_href->{'hour'} = $SYSLOG_href->{'rx_hour'} || $SYSLOG_href->{'hour'};
618 0   0       $SYSLOG_href->{'min'} = $SYSLOG_href->{'rx_min'} || $SYSLOG_href->{'min'};
619 0   0       $SYSLOG_href->{'sec'} = $SYSLOG_href->{'rx_sec'} || $SYSLOG_href->{'sec'};
620 0   0       $SYSLOG_href->{'year'} = $SYSLOG_href->{'rx_year'} || $SYSLOG_href->{'year'};
621 0   0       $SYSLOG_href->{'wday'} = $SYSLOG_href->{'rx_wday'} || $SYSLOG_href->{'wday'};
622 0   0       $SYSLOG_href->{'wday_str'} = $SYSLOG_href->{'rx_wday_str'} || $SYSLOG_href->{'wday_str'};
623 0   0       $SYSLOG_href->{'date_str'} = $SYSLOG_href->{'rx_date_str'} || $SYSLOG_href->{'date_str'};
624            
625 0           log_debug(2, "INFO: using rx_time info instead of message timestamp info\n");
626             }
627             }
628              
629             #
630             # make sure we have device name from the syslog line
631             #
632 0           log_debug(2, "device name check: dev: [%s] srcIP: [%s] \n",
633             $SYSLOG_href->{'device'}, $SYSLOG_href->{'rx_srcIP'}
634             );
635              
636             # check we have device name
637 0 0         if ($SYSLOG_href->{'device'}) {
    0          
638 0           log_debug(2, "device name check: keep syslog message device name: %s\n", $SYSLOG_href->{'device'});
639             }
640             elsif ( $SYSLOG_href->{'rx_srcIP'} ) {
641 0           log_debug(2, "device name change dev: [%s] <= srcIP [%s]\n",
642             $SYSLOG_href->{'device'}, $SYSLOG_href->{'rx_srcIP'},
643             );
644 0           $SYSLOG_href->{'device'} = $SYSLOG_href->{'rx_srcIP'};
645             }
646             else {
647 0           log_debug(2, "device name change: no device name or srcIP, change to noHost\n");
648 0           $SYSLOG_href->{'device'} = 'noHost';
649             }
650 0           log_debug(2, "device name: set to [%s]\n", $SYSLOG_href->{'device'});
651              
652              
653              
654             #
655             # check filters
656             #
657 0 0         if ($_obj->{'filter'}) {
658             # check min date filter
659 0 0         if ($_obj->{'filter_min_date_epoch'}) {
660 0           log_debug(3, "INFO: MIN filter: min_date_epoch [%s] [%s]\n",
661             _commify($_obj->{'filter_min_date_epoch'}), $_obj->{'filter_min_date'},
662             );
663 0 0 0       if ($SYSLOG_href->{'rx_epoch'} && $_obj->{'rx_time'}) {
    0          
664 0           log_debug(3, "rx_epoch and rx_time : true\n");
665 0           log_debug(3, "is %s < %s\n", _commify($SYSLOG_href->{'rx_epoch'}),
666             _commify($_obj->{'filter_min_date_epoch'})
667             );
668 0 0         if ($SYSLOG_href->{'rx_epoch'} < $_obj->{'filter_min_date_epoch'}) {
669 0           $ERROR = sprintf("FILTER: rx date %s less than min filter date %s",
670             $SYSLOG_href->{'rx_date_str'}, $_obj->{'filter_min_date'}
671             );
672 0           log_debug(3, "%s\n", $ERROR);
673 0           $FILTER_count++;
674 0 0         return(wantarray ? (undef, $ERROR) : undef);
675             }
676             }
677             elsif ($SYSLOG_href->{'epoch'}) {
678 0           log_debug(3, "examine message timestamp epoch: %s\n", _commify($SYSLOG_href->{'epoch'}));
679 0           log_debug(3, "check %s < %s\n",
680             _commify($SYSLOG_href->{'epoch'}), _commify($_obj->{'filter_min_date_epoch'})
681             );
682 0 0         if ($SYSLOG_href->{'epoch'} < $_obj->{'filter_min_date_epoch'}) {
683 0           $ERROR = sprintf("FILTER: message date %s less than min filter date %s",
684             $SYSLOG_href->{'date_str'}, $_obj->{'filter_min_date'}
685             );
686 0           log_debug(3, "NULL line: %s\n", $ERROR);
687 0           $FILTER_count++;
688 0 0         return(wantarray ? (undef, $ERROR) : undef);
689             }
690 0           log_debug(3, "keep line\n");
691             }
692             else {
693 0           $ERROR = sprintf("assert min date filter: no date from message");
694 0           log_debug(3, "%s\n", $ERROR);
695 0           $FILTER_count++;
696 0           $ERROR_count++;
697 0 0         return(wantarray ? (undef, $ERROR) : undef);
698             }
699             }
700             # check max date filter
701 0 0         if ($_obj->{'filter_max_date_epoch'}) {
702 0           log_debug(3, "INFO: MAX filter: max_date_epoch [%s]\n",
703             _commify($_obj->{'filter_max_date_epoch'}), $_obj->{'filter_max_date'},
704             );
705 0 0 0       if ($SYSLOG_href->{'rx_epoch'} && $_obj->{'rx_time'}) {
    0          
706 0           log_debug(3, "rx_epoch and rx_time : true\n");
707 0           log_debug(3, "is %s < %s\n", _commify($SYSLOG_href->{'rx_epoch'}),
708             _commify($_obj->{'filter_max_date_epoch'})
709             );
710 0 0         if ($SYSLOG_href->{'rx_epoch'} > $_obj->{'filter_max_date_epoch'}) {
711 0           $ERROR = sprintf("FILTER: rx date %s greater than than max filter date %s",
712             $SYSLOG_href->{'rx_date_str'}, $_obj->{'filter_max_date'}
713             );
714 0           log_debug(3, "%s\n", $ERROR);
715 0           $FILTER_count++;
716 0 0         return(wantarray ? (undef, $ERROR) : undef);
717             }
718             }
719             elsif ($SYSLOG_href->{'epoch'}) {
720 0           log_debug(3, "examine message timestamp epoch: %s\n", _commify($SYSLOG_href->{'epoch'}));
721 0           log_debug(3, "check %s < %s\n",
722             _commify($SYSLOG_href->{'epoch'}), _commify($_obj->{'filter_max_date_epoch'})
723             );
724 0 0         if ($SYSLOG_href->{'epoch'} > $_obj->{'filter_max_date_epoch'}) {
725 0           $ERROR = sprintf("FILTER: message date %s greater than max filter date %s",
726             $SYSLOG_href->{'date_str'}, $_obj->{'filter_max_date'}
727             );
728 0           log_debug(3, "NULL line: %s\n", $ERROR);
729 0           $FILTER_count++;
730 0 0         return(wantarray ? (undef, $ERROR) : undef);
731             }
732 0           log_debug(3, "keep line\n");
733             }
734             else {
735 0           $ERROR = sprintf("assert min date filter: no date from message");
736 0           log_debug(3, "%s\n", $ERROR);
737 0           $FILTER_count++;
738 0           $ERROR_count++;
739 0 0         return(wantarray ? (undef, $ERROR) : undef);
740             }
741             }
742             # check device filter
743 0 0         if ($_obj->{'filter_device'}) {
744 0 0         if ($SYSLOG_href->{'device'} !~ /$_obj->{'filter_device'}/) {
745 0           $ERROR = sprintf("FILTER: device [%s] not match filter [%s]",
746             $SYSLOG_href->{'device'}, $_obj->{'filter_device'}
747             );
748 0           $FILTER_count++;
749 0 0         return(wantarray ? (undef, $ERROR) : undef);
750             }
751             }
752             # check tag filter
753 0 0         if ($_obj->{'filter_tag'}) {
754 0 0         if ($SYSLOG_href->{'tag'} !~ /$_obj->{'filter_tag'}/) {
755 0           $ERROR = sprintf("FILTER: tag [%s] not match filter [%s]",
756             $SYSLOG_href->{'tag'}, $_obj->{'filter_tag'}
757             );
758 0           $FILTER_count++;
759 0 0         return(wantarray ? (undef, $ERROR) : undef);
760             }
761             }
762             # check message filter
763 0 0         if ($_obj->{'filter_message'}) {
764 0 0         if ($SYSLOG_href->{'message'} !~ /$_obj->{'filter_message'}/) {
765 0           $ERROR = sprintf("FILTER: message not match filter [%s]", $_obj->{'filter_message'});
766 0           $FILTER_count++;
767 0 0         return(wantarray ? (undef, $ERROR) : undef);
768             }
769             }
770             } # end filtering
771              
772             #
773             # Dump and/or Report line
774             #
775             # if a 'last message line'
776 0 0 0       if ($_obj->{'lastmsg'} && $SYSLOG_href->{'line'} =~ /last message repeated (\d+) time/) {
777 0           $_last = $1;
778 0           $SYSLOG_href = undef;
779 0           %{$SYSLOG_href} = %LASTMSG;
  0            
780 0           log_debug(2, "syslog line repeated: [%s] times\n", $_last);
781 0           foreach (1..$_last) {
782 0           log_debug(3, "syslog line repeat: [%s]\n", $_);
783 0 0         if ($_obj->{'dump'})
  0            
784             {&dump_line_to_file($_obj, $SYSLOG_href->{'device'}, $SYSLOG_href->{'line'});}
785 0 0         if ($_obj->{'report'})
  0            
786             {&syslog_stats;}
787             }
788             }
789             else {
790             # see if we want to dump file
791 0 0 0       if ($_obj->{'dump'} && $SYSLOG_href->{'device'}) {
792 0           ($_ok, $ERROR) = &dump_line_to_file($_obj, $SYSLOG_href->{'device'}, $_line);
793 0 0         unless ($_ok)
  0 0          
794             {return(wantarray ? (undef, $ERROR) : undef);}
795             }
796              
797             # see if we want a report
798 0 0         if ($_obj->{'report'})
  0            
799             {&syslog_stats;}
800             }
801              
802             # store this line for next iteration
803 0           %LASTMSG = %{$SYSLOG_href};
  0            
804              
805 0 0         {return(wantarray ? ($SYSLOG_href, $ERROR) : $SYSLOG_href);}
  0            
806              
807             } # end parse_syslog_line
808             #
809             #.............................................................................
810             #
811             # Function/method to parse portion of syslog line thought to contain
812             # the syslog message
813             #
814             # Break syslog line into parts
815             # timestamp device message
816             # message = tag content
817             #
818             # $_[0] - syslog line or rfc 3164 portion
819             # $_[1] - more time info
820             # 0 - do not derive more time info
821             # 1 - derive more time info
822             # $_[2] - parse tag
823             # 0 - do not try to parse tag info from messag
824             # 1 - parse tag and content
825             # $_[3] - undef | 1
826             # 1 set if called internally, populate hash
827             # 0 returns has reference
828             #
829             # Return
830             # (timestamp, host, message, $ERROR) : \%hash
831              
832              
833             sub parse_syslog_msg {
834              
835 0     0 1   my $_msg = shift;
836 0   0       my $_moretime = shift || 0;
837 0   0       my $_parsetag = shift || 0;
838 0   0       my $_ret = shift || 0;
839              
840 0           my ($_ok, $_err,
841             $_x1, $_x2,
842             );
843              
844 0           log_debug(2, "func parse_syslog_msg:\n");
845 0           log_debug(3, "format: [%s] pattern: %s\n", $SYSLOG_href->{'format'}, $SYSLOG_pattern);
846              
847             #
848             # Match Sylog pattern and extract parts for desired format
849             #
850 0 0         if ($_msg =~ /$SYSLOG_pattern/) {
851 0           log_debug(1, "matched syslog_pattern\n");
852             # bsd format
853 0 0 0       if ($SYSLOG_href->{'format'} eq 'bsd' or $SYSLOG_href->{'format'} eq 'self') {
    0          
854             # timstamp anchors
855 0           $SYSLOG_href->{'timestamp'} = $1;
856 0           $SYSLOG_href->{'month_str'} = $2;
857 0           $SYSLOG_href->{'day'} = $3;
858 0           $SYSLOG_href->{'time_str'} = $4;
859             # hostname
860 0           $SYSLOG_href->{'device'} = $5;
861             # Message
862 0           $SYSLOG_href->{'message'} = $6;
863             }
864             # noHost format
865             elsif ($SYSLOG_href->{'format'} eq 'noHost') {
866             # timstamp anchors
867 0           $SYSLOG_href->{'timestamp'} = $1;
868 0           $SYSLOG_href->{'month_str'} = $2;
869 0           $SYSLOG_href->{'day'} = $3;
870 0           $SYSLOG_href->{'time_str'} = $4;
871             # Message
872 0           $SYSLOG_href->{'message'} = $5;
873             }
874             else {
875 0           $ERROR = "unmatched syslog_pattern: $SYSLOG_href->{'format'}";
876 0           log_debug(1, "%s\n", $ERROR);
877 0 0         if ($_ret) {return(undef);}
  0            
878 0 0         return(wantarray ? (undef, undef, undef, $ERROR) : undef);
879             }
880             }
881             else {
882 0           $ERROR = 'syslog message line does not match syslog_pattern';
883 0           log_debug(1, " %s\n", $ERROR);
884 0 0         if ($_ret) {return(undef);}
  0            
885 0 0         return(wantarray ? (undef, undef, undef, $ERROR) : undef);
886             }
887 0 0         if ($DEBUG) {
888 0           log_debug(1, "timestamp: [%s]\n", $SYSLOG_href->{'timestamp'});
889 0           log_debug(1, "device: [%s]\n", $SYSLOG_href->{'device'});
890 0           log_debug(1, "message: [%s]\n", $SYSLOG_href->{'message'});
891             }
892              
893             #
894             # see if device has been substituted with ip:port info [a.a.a.a.p.p]
895             # such as 10.1.1.1.4.0
896             # convert last two octets to srcPort
897             # convert decimal octet to hex, join together, convert hex to decimal
898 0 0         if ($SYSLOG_href->{'device'} =~ /(\d+\.\d+\.\d+\.\d+)\.(\d+)\.(\d+)/) {
899 0           $SYSLOG_href->{'device'} = $1;
900 0           $SYSLOG_href->{'device_port'} = hex( join('', sprintf("%02x", $2), sprintf("%02x", $3)));
901             }
902             else {
903 0           $SYSLOG_href->{'device_port'} = '?';
904             }
905              
906              
907             #
908             # Get more info from timestamp
909             #
910 0 0         if ($_moretime) {
911 0 0         if ( defined($SYSLOG_href->{'timestamp'}) ) {
912             # Mmm d hh:mm:ss mmm d hh:mm:ss
913             # Mmm dd hh:mm:ss mmm dd hh:mm:ss
914 0 0         if ($SYSLOG_href->{'timestamp'} =~ /[JFMASOND]\w\w\s+\d+\s(\d\d):(\d\d):(\d\d)/i) {
915 0           $SYSLOG_href->{'hour'} = $1;
916 0           $SYSLOG_href->{'min'} = $2;
917 0           $SYSLOG_href->{'sec'} = $3;
918            
919 0           $SYSLOG_href->{'month'} = $MON_index{$SYSLOG_href->{'month_str'}};
920 0           $SYSLOG_href->{'year'} = $YEAR;
921 0           log_debug(2,
922             "syslog message timestamp values: Mmm: [%s] [%s] dd: [%s] hh: [%s] mm: [%s] ss: [%s]\n",
923             $SYSLOG_href->{'month_str'}, $SYSLOG_href->{'month'},
924             $SYSLOG_href->{'day'}, $SYSLOG_href->{'hour'},
925             $SYSLOG_href->{'min'}, $SYSLOG_href->{'sec'}
926             );
927            
928             # determine some time info
929             # year, epoch seconds, weekday
930             #
931 0           ($SYSLOG_href->{'epoch'}, $SYSLOG_href->{'wday'}) = &_extra_time_values(
932             $SYSLOG_href->{'sec'}, $SYSLOG_href->{'min'}, $SYSLOG_href->{'hour'},
933             $SYSLOG_href->{'day'}, $SYSLOG_href->{'month'},
934             );
935 0           $SYSLOG_href->{'wday_str'} = $WDAY{$SYSLOG_href->{'wday'}};
936 0           $SYSLOG_href->{'date_str'} = &epoch_to_datestr($SYSLOG_href->{'epoch'});
937            
938 0           log_debug(2, "syslog message timestamp extra: yyyy: [%s] epoch: [%s] wday: [%s] [%s]\n",
939             $SYSLOG_href->{'year'}, $SYSLOG_href->{'epoch'}, $SYSLOG_href->{'wday'},
940             $SYSLOG_href->{'wday_str'}
941             );
942             }
943             }
944             else {
945 0           $ERROR = "unsupported timestamp syntax: $SYSLOG_href->{'timestamp'}";
946 0           log_debug(1, "%s\n", $ERROR);
947 0 0         if ($_ret) {return(undef);}
  0 0          
  0            
948             else {return(wantarray ? (undef, undef, undef, $ERROR) : undef) }
949             }
950             }
951              
952             #
953             # Check if we got a TAG, if try to find one
954             #
955 0 0         if ($_parsetag) {
956 0 0         if (defined($SYSLOG_href->{'message'})) {
957 0           ($SYSLOG_href->{'tag'}, $SYSLOG_href->{'pid'}, $SYSLOG_href->{'content'}) =
958             parse_tag($SYSLOG_href->{'message'}
959             );
960 0           log_debug(2, "syslog message tag: [%s] pid: [%s]\n",
961             $SYSLOG_href->{'tag'}, $SYSLOG_href->{'pid'}
962             );
963 0           log_debug(2, "syslog message content: %s\n", $SYSLOG_href->{'content'});
964             }
965             else {
966 0           log_debug(2, 'no message to parse tag from');
967             }
968             }
969             else {
970 0           $SYSLOG_href->{'content'} = $SYSLOG_href->{'message'};
971 0           log_debug(2, "parseTag [$_parsetag], content = message\n");
972             }
973              
974              
975             # return some values
976 0 0         if ($_ret) {return(1);}
  0            
977             else {
978 0 0         return(wantarray ? ($SYSLOG_href->{'timestamp'},
979             $SYSLOG_href->{'device'},
980             $SYSLOG_href->{'message'},
981             undef,
982             )
983             : $SYSLOG_href
984             );
985             }
986             } # end parse_syslog_msg
987             #
988             #.............................................................................
989             #
990             # function to parse preamble
991             # yyyy-mm-dd hh:mm::ss facility.severity src_ip
992             # mm-dd-yyyy hh:mm::ss facility.severity src_ip
993             #
994             # Arg
995             # $_[0] preamble
996             #
997             # Return
998             # (epoch, date, facility, severity, srcIP)
999             #
1000             sub parse_preamble {
1001              
1002 0     0 1   my @_tokens = ();
1003 0           my ($_t, $_epoch, $_timestamp, $_date, $_time,
1004             $_yr, $_mon, $_day,
1005             $_hr, $_min, $_sec,
1006             $_prio, $_fac, $_sev, $_srcIp
1007             );
1008              
1009 0 0         if ($_[0] =~ /^<(\d+)>$/) {
1010 0           $_prio = $1;
1011 0           @_tokens= decode_PRI($_prio);
1012 0           $_prio = $_tokens[3];
1013 0           $_fac = $_tokens[4];
1014 0           $_sev = $_tokens[5];
1015             }
1016             else {
1017 0           @_tokens = split(/\s+/, $_[0]);
1018 0           foreach $_t (@_tokens) {
1019             # yyyy-mm-dd
1020 0 0         if ($_t =~ /(\d\d\d\d)\-(\d\d)\-(\d\d)/) {
1021 0           $_yr = $1; $_mon = $2; $_day = $3;
  0            
  0            
1022 0           $_date = $_t;
1023             }
1024             # mm-dd-yyyy
1025 0 0         if ($_t =~ /(\d\d)\-(\d\d)\-(\d\d\d\d)/) {
1026 0           $_mon = $1; $_day = $2; $_yr = $3;
  0            
  0            
1027 0           $_date = $_t;
1028             }
1029             # hh:mm::ss
1030 0 0         if ($_t =~ /(\d\d):(\d\d):(\d\d)/) {
1031 0           $_hr = $1; $_min = $2; $_sec = $3;
  0            
  0            
1032 0           $_time = $_t;
1033             }
1034             # facility.severity
1035 0 0         if ($_t =~ /([a-zA-Z0-9]+)\.([a-zA-Z]+)/)
  0            
1036 0           {$_fac = $1; $_sev = $2; $_prio = $_t;}
  0            
1037             # source IP
1038 0 0         if ($_t =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/)
  0            
1039             {$_srcIp = $1;}
1040             }
1041 0           $_timestamp = sprintf("%s %s", $_date, $_time);
1042 0           $_timestamp =~ s/^\s+//;
1043 0           $_timestamp =~ s/\s+$//;
1044            
1045 0 0 0       if (defined($_hr) && defined($_day) ) {
1046 0           $_epoch = timelocal($_sec, $_min, $_hr, $_day, $_mon-1, $_yr);
1047             }
1048             else {
1049 0           $_epoch = '??';
1050             }
1051             }
1052              
1053 0           $SYSLOG_href->{'preamble'} = $_[0];
1054              
1055 0 0         if ($_prio) {
1056 0           $SYSLOG_href->{'rx_priority'} = $_prio;
1057 0           $SYSLOG_href->{'rx_facility'} = $_fac;
1058 0           $SYSLOG_href->{'rx_severity'} = $_sev;
1059             }
1060              
1061 0 0         if ($_timestamp) {
1062 0           $SYSLOG_href->{'rx_timestamp'} = $_timestamp;
1063 0           $SYSLOG_href->{'rx_epoch'} = $_epoch;
1064 0           $SYSLOG_href->{'rx_date_str'} = epoch_to_datestr($SYSLOG_href->{'rx_epoch'});
1065 0           $SYSLOG_href->{'rx_year'} = $_yr;
1066 0           $SYSLOG_href->{'rx_month'} = $_mon-1;
1067 0           $SYSLOG_href->{'rx_month_str'} = $MON{$_mon-1};
1068 0           $SYSLOG_href->{'rx_day'} = $_day;
1069 0           $SYSLOG_href->{'rx_wday'} = (localtime($SYSLOG_href->{'rx_epoch'}))[6];
1070 0           $SYSLOG_href->{'rx_wday_str'} = $WDAY{$SYSLOG_href->{'rx_wday'}};
1071 0           $SYSLOG_href->{'rx_time_str'} = $_time;
1072 0           $SYSLOG_href->{'rx_hour'} = $_hr;
1073 0           $SYSLOG_href->{'rx_min'} = $_min;
1074 0           $SYSLOG_href->{'rx_sec'} = $_sec;
1075             }
1076              
1077 0 0         if ($_srcIp) {
1078 0           $SYSLOG_href->{'rx_srcIP'} = $_srcIp;
1079             }
1080              
1081             # normalize facility and severity strings
1082 0 0         if (!defined($Syslog_Facility{$SYSLOG_href->{'rx_facility'}})) {
1083 0           $SYSLOG_href->{'rx_facility'} = normalize_facility($SYSLOG_href->{'rx_facility'});
1084 0           log_debug(3, "normalized facility string to [%s]\n",$SYSLOG_href->{'rx_facility'});
1085             }
1086 0 0         if (!defined($Syslog_Severity{$SYSLOG_href->{'rx_severity'}})) {
1087 0           $SYSLOG_href->{'rx_severity'} = normalize_severity($SYSLOG_href->{'rx_severity'});
1088 0           log_debug(3, "normalized severity string to [%s]\n", $SYSLOG_href->{'rx_severity'});
1089             }
1090              
1091              
1092 0 0         if ($DEBUG) {
1093 0           log_debug(2, "syslog line preamble: timestamp: [%s] priority: [%s] srcIP: [%s]\n",
1094             $SYSLOG_href->{'rx_timestamp'}, $SYSLOG_href->{'rx_priority'},
1095             $SYSLOG_href->{'rx_srcIP'}
1096             );
1097 0           log_debug(3, "syslog line preamble: epoch: [%s] facility: [%s] severity: [%s]\n",
1098             $SYSLOG_href->{'rx_epoch'}, $SYSLOG_href->{'rx_facility'}, $SYSLOG_href->{'rx_severity'}
1099             );
1100 0           log_debug(3, "syslog line preamble: datestr: [%s]\n",
1101             $SYSLOG_href->{'rx_date_str'}
1102             );
1103             }
1104              
1105 0           1;
1106              
1107             } # parse_preamble
1108              
1109              
1110             #
1111             #.............................................................................
1112             #
1113             # function to parse tag/pid
1114             # Argument
1115             # $_[0] string to find tag in
1116             # Return
1117             # (tag,pid)
1118             #
1119             sub parse_tag {
1120              
1121 0     0 1   my $_tag = '';
1122 0           my $_pid = '';
1123 0           my $_content = '';
1124 0           my $_match = 0;
1125              
1126 0           log_debug(2, "parse tag from: [%s]\n", $_[0]);
1127              
1128             #
1129             # see if match a user defined pattern
1130             #
1131 0 0 0       if ($TAG_1 || $TAG_2 || $TAG_3) {
      0        
1132 0 0         log_debug(3, "tag match: user defined %s %s %s\n",
    0          
    0          
1133             defined($TAG_1) ? 1 : '-',
1134             defined($TAG_2) ? 2 : '-',
1135             defined($TAG_3) ? 3 : '-',
1136             );
1137 0 0 0       if ($TAG_1 && $_[0] =~ /$TAG_1/) {
    0 0        
    0 0        
1138 0           $_tag = $1;
1139 0           $_pid = $2;
1140 0           $_content = $3;
1141 0           log_debug(3, "tag match: TAG_1 tag: [%s] pid: [%s]\n",
1142             $_tag, $_pid
1143             );
1144             }
1145             elsif ($TAG_2 && $_[0] =~ /$TAG_2/) {
1146 0           $_tag = $1;
1147 0           $_pid = '';
1148 0           $_content = $2;
1149 0           log_debug(3, "tag match: TAG_2 tag: [%s] pid: [%s]\n",
1150             $_tag, $_pid
1151             );
1152             }
1153             elsif ($TAG_3 && $_[0] =~ /$TAG_3/) {
1154 0           $_tag = $1;
1155 0           $_pid = $2;
1156 0           $_content = $3;
1157 0           log_debug(3, "tag match: TAG_3 tag: [%s] pid: [%s]\n",
1158             $_tag, $_pid
1159             );
1160             }
1161             else {
1162 0           log_debug(3, "tag match: user defined not matched\n");
1163             }
1164             #$_content =~ s/^ +//;
1165 0           return($_tag, $_pid, $_content);
1166             }
1167              
1168             #
1169             # If we are this point, try to match some of these common ones
1170             #
1171             #
1172             # tag[pid]: content pid delimited with []
1173 0 0         if ($_[0] =~ /^(([\w\d]+)\[([\w\d]+)\]:){1,32}? *(\w+.+)/) {
    0          
    0          
    0          
    0          
1174 0           $_tag = $2;
1175 0           $_pid = $3;
1176 0           $_content = $4;
1177 0           $_match = 1;
1178             }
1179             #
1180             # tag[pid]: content pid delimited with non-alphnumeric
1181             elsif ($_[0] =~ /^(([\w\d\-_]+)\W([\w\d]+)\W:){1,32}? *(\w+.+)/) {
1182 0           $_tag = $2;
1183 0           $_pid = $3;
1184 0           $_content = $4;
1185 0           $_match = 2;
1186             }
1187             # tag[pid] content pid delimited with non-alphnumeric no colon
1188             elsif ($_[0] =~ /^(([\w\d\-_]+)\W([\w\d]+)\W){1,32}? *(\w+.+)/) {
1189 0           $_tag = $2;
1190 0           $_pid = $3;
1191 0           $_content = $4;
1192 0           $_match = 3;
1193             }
1194             elsif ($_[0] =~ /^(([\w\d\-_]+)\W([\w\d]+)\W){1,32}? *(.+)/) {
1195 0           $_tag = $2;
1196 0           $_pid = $3;
1197 0           $_content = $4;
1198 0           $_match = 4;
1199             }
1200              
1201             # last message
1202             elsif ($_[0] =~ /last message repeated (\d+) time/) {
1203 0           $_tag = 'lastmsg';
1204 0           $_pid = $1;
1205 0           $_content = '';
1206 0           $_match = 'last';
1207             }
1208             else {
1209 0           $_tag = 'NOTAG';
1210 0           $_pid = 'NOPID';
1211 0           $_content = $_[0];
1212 0           log_debug(3, "tag match: none, set content = message\n");
1213             }
1214              
1215 0           log_debug(3, "tag match: pattern [%s] tag: [%s] pid: [%s]\n",
1216             $_match, $_tag, $_pid
1217             );
1218             #$_content =~ s/^ +//;
1219 0           return($_tag, $_pid, $_content);
1220             }
1221              
1222              
1223             #
1224             #.............................................................................
1225             #
1226             # Init the object
1227             sub init {
1228             #$SYSLOG_href = {};
1229 0     0 1   %{$SYSLOG_href} = ();
  0            
1230             }
1231             #
1232             #=======================================================================
1233             #
1234             # Syslog Send Message
1235             #
1236             #=======================================================================
1237             #
1238             #
1239             #........................................................................
1240             #
1241             # Syslog Send message constructor
1242             #
1243             # Args
1244             # server =>
1245             # port => (514)
1246             # facility =>
1247             # severity =>
1248             # tag =>
1249             # timestamp => # timestamp value to use in syslog message
1250             # device => # device name to use in syslog message
1251             # tag => # tag string to use in syslog message
1252             # pid => # pid to append to tag enclosed in []
1253             # message => # message to send
1254             # content => # content of message
1255             # strict => 0|1 # enforce message syntax rules
1256             # noHost => 0|1 # whether we should put HOSTNAME in message
1257             # noTag => 0|1 # hether we should put TAG in message
1258             # debug => 0-5 # debug
1259              
1260             sub send {
1261              
1262             # create object
1263 0     0 1   my $_proto = shift;
1264 0   0       my $_class = ref($_proto) || $_proto;
1265 0           my $_send = {};
1266             # bless object
1267 0           bless($_send, $_class);
1268              
1269 0           $ERROR = '';
1270              
1271 0           my %_arg = @_;
1272 0           my $_a;
1273              
1274             # default some values
1275 0           $_send->{'server'} = '127.0.0.1';
1276 0           $_send->{'port'} = '514';
1277 0           $_send->{'proto'} = 'udp';
1278 0           $_send->{'facility'} = 'user';
1279 0           $_send->{'severity'} = 'debug';
1280 0           $_send->{'strict'} = 1;
1281 0           $_send->{'noHost'} = 0;
1282 0           $_send->{'noTag'} = 0;
1283 0           $_send->{'debug'} = 0;
1284              
1285             # check arguments
1286 0           foreach $_a (keys %_arg) {
1287 0 0         if ($_a =~ /^-?server/) { $_send->{'server'} = delete($_arg{$_a}); }
  0 0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
1288 0           elsif ($_a =~ /^-?port/) { $_send->{'port'} = delete($_arg{$_a}); }
1289 0           elsif ($_a =~ /^-?proto/) { $_send->{'proto'} = delete($_arg{$_a}); }
1290 0           elsif ($_a =~ /^-?facility/) { $_send->{'facility'} = delete($_arg{$_a}); }
1291 0           elsif ($_a =~ /^-?severity/) { $_send->{'severity'} = delete($_arg{$_a}); }
1292 0           elsif ($_a =~ /^-?timestamp/) { $_send->{'timestamp'} = delete($_arg{$_a}); }
1293 0           elsif ($_a =~ /^-?device/) { $_send->{'device'} = delete($_arg{$_a}); }
1294 0           elsif ($_a =~ /^-?tag/) { $_send->{'tag'} = delete($_arg{$_a}); }
1295 0           elsif ($_a =~ /^-?pid/) { $_send->{'pid'} = delete($_arg{$_a}); }
1296 0           elsif ($_a =~ /^-?content/) { $_send->{'content'} = delete($_arg{$_a}); }
1297 0           elsif ($_a =~ /^-?message/) { $_send->{'message'} = delete($_arg{$_a}); }
1298 0           elsif ($_a =~ /^-?strict/) { $_send->{'strict'} = delete($_arg{$_a}); }
1299 0           elsif ($_a =~ /^-?noHost/) { $_send->{'noHost'} = delete($_arg{$_a}); }
1300 0           elsif ($_a =~ /^-?hostname/) { $_send->{'hostname'} = delete($_arg{$_a}); }
1301 0           elsif ($_a =~ /^-?noTag/) { $_send->{'noTag'} = delete($_arg{$_a}); }
1302 0           elsif ($_a =~ /^-?debug/) { $_send->{'debug'} = delete($_arg{$_a}); }
1303             else {
1304 0           $ERROR = sprintf("unsupported argument: %s => %s", $_a, $_arg{$_a});
1305 0 0         return(wantarray ? (undef, $ERROR) : undef);
1306             }
1307             }
1308 0 0         return(wantarray ? ($_send, $ERROR) : $_send);
1309             }
1310              
1311              
1312             #
1313             #.............................................................................
1314             #
1315             # send message
1316             # max length = 1024
1317             # PRI HEADER MSG
1318             # PRI 3,4 or 5 char bounded by '<' '>'
1319             # <#>
1320             #
1321             sub send_message {
1322              
1323 0     0 1   my $_send = shift;
1324 0           my ($_facility, $_severity,
1325             $_timestamp, $_devname, $_tag, $_pid, $_message,
1326             $_pri, $_content, $tx_msg, $msg_l, $tag_l,
1327             $_sock,
1328             );
1329              
1330 0           $ERROR = '';
1331              
1332 0           my %_arg = @_;
1333 0           my $_a;
1334 0           my $_format = '';
1335              
1336             # check arguments
1337 0           foreach $_a (keys %_arg) {
1338 0 0         if ($_a =~ /^-?server/) { $_send->{'server'} = delete($_arg{$_a}); }
  0 0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
1339 0           elsif ($_a =~ /^-?port/) { $_send->{'port'} = delete($_arg{$_a}); }
1340 0           elsif ($_a =~ /^-?facility/) { $_send->{'facility'} = delete($_arg{$_a}); }
1341 0           elsif ($_a =~ /^-?severity/) { $_send->{'severity'} = delete($_arg{$_a}); }
1342 0           elsif ($_a =~ /^-?timestamp/) { $_send->{'timestamp'} = delete($_arg{$_a}); }
1343 0           elsif ($_a =~ /^-?device/) { $_send->{'device'} = delete($_arg{$_a}); }
1344 0           elsif ($_a =~ /^-?tag/) { $_send->{'tag'} = delete($_arg{$_a}); }
1345 0           elsif ($_a =~ /^-?pid/) { $_send->{'pid'} = delete($_arg{$_a}); }
1346 0           elsif ($_a =~ /^-?content/) { $_send->{'content'} = delete($_arg{$_a}); }
1347 0           elsif ($_a =~ /^-?message/) { $_send->{'message'} = delete($_arg{$_a}); }
1348 0           elsif ($_a =~ /^-?strict/) { $_send->{'strict'} = delete($_arg{$_a}); }
1349 0           elsif ($_a =~ /^-?noHost/) { $_send->{'noHost'} = delete($_arg{$_a}); }
1350 0           elsif ($_a =~ /^-?hostname/) { $_send->{'hostname'} = delete($_arg{$_a}); }
1351 0           elsif ($_a =~ /^-?noTag/) { $_send->{'noTag'} = delete($_arg{$_a}); }
1352 0           elsif ($_a =~ /^-?debug/) { $_send->{'debug'} = delete($_arg{$_a}); }
1353             else {
1354 0           $ERROR = sprintf("unsupported argument: %s => %s", $_a, $_arg{$_a});
1355 0 0         return(wantarray ? (undef, $ERROR) : undef);
1356             }
1357             }
1358              
1359             # error check facility and severity value
1360 0 0         if (!defined($Syslog_Facility{$_send->{'facility'}})) {
1361 0           $ERROR = "unsupported argument: facility => $_send->{'facility'}";
1362 0 0         return(wantarray ? (undef, $ERROR) : undef);
1363             }
1364 0 0         if (!defined($Syslog_Severity{$_send->{'severity'}})) {
1365 0           $ERROR = "unsupported argument: severity => $_send->{'severity'}";
1366 0 0         return(wantarray ? (undef, $ERROR) : undef);
1367             }
1368              
1369 0           $_tag = undef;
1370 0           $_message = '';
1371              
1372 0           $DEBUG = $_send->{'debug'};
1373              
1374              
1375 0           $_facility = $Syslog_Facility{$_send->{'facility'}};
1376 0           $_severity = $Syslog_Severity{$_send->{'severity'}};
1377             # PRI = (facility x 8) + severity
1378 0           $_pri = ($_facility * 8) + $_severity;
1379              
1380             #
1381             # TIMESTAMP
1382             #
1383             # use timestamp given
1384 0 0         if ($_send->{'timestamp'}) {
1385 0 0         if (!validate_timestamp_syntax($_send->{'timestamp'})) {
1386 0           $ERROR = "invalid timestamp: $_send->{'timestamp'}";
1387 0 0         return(wantarray ? (undef, $ERROR) : undef);
1388             }
1389 0           $_timestamp = $_send->{'timestamp'};
1390             }
1391             # use system time
1392             else {
1393 0           $_timestamp = epoch_to_syslog_timestamp();
1394             }
1395 0           $_format = 'T';
1396             #
1397             # HOSTNAME (not required)
1398             # if noHost=0 meaning populate hostname
1399 0 0         unless ( $_send->{'noHost'} ) {
1400 0 0         if ($_send->{'device'}) { $_devname = $_send->{'device'}; }
  0 0          
1401 0           elsif ($_send->{'hostname'}) { $_devname = hostname();}
1402 0           else { $_devname = 'netdevsyslog';}
1403 0           $_format = 'TH';
1404             }
1405             #
1406             # MESSAGE or (TAG CONTENT)
1407             #
1408 0 0         if ( defined($_send->{'message'}) ) {
    0          
1409 0           $_content = $_send->{'message'};
1410 0           $_format = $_format . 'M';
1411             }
1412             elsif ( defined($_send->{'content'}) ) {
1413             # -noTag = 0
1414 0 0         unless ($_send->{'noTag'}) {
1415 0 0 0       if ( defined($_send->{'tag'}) && $_send->{'pid'} ) {
    0          
1416 0           $_tag = sprintf("%s[%s]:", $_send->{'tag'}, $_send->{'pid'});
1417             }
1418             elsif ( defined($_send->{'tag'}) ) {
1419 0           $_tag = $_send->{'tag'};
1420             }
1421             else {
1422 0 0         if ( defined($_send->{'pid'}) ) {$_tag = sprintf("NetDevSyslog[%s]:", $_send->{'pid'});}
  0            
  0            
1423             else {$_tag = sprintf("NetDevSyslogp[%s]:", $$);}
1424             }
1425 0           $_format = $_format . 'T'; # THT or TT
1426             }
1427 0           $_content = $_send->{'content'};
1428 0           $_format = $_format . 'C';
1429             }
1430             else {
1431 0           $_content = sprintf("Net::Dev::Syslog TEST Message: facility: %s [%s] severity: %s [%s]",
1432             $_send->{'facility'}, $_facility,
1433             $_send->{'severity'}, $_severity,
1434             );
1435 0           $_format = $_format . 'C';
1436             }
1437              
1438             #
1439             # SYSLOG MESSAGE
1440             #
1441             # timestamp hostname tag content
1442 0 0         if ( $_format eq "THTC") {
    0          
    0          
    0          
    0          
    0          
1443 0           $_message = sprintf("%s %s %s %s", $_timestamp, $_devname, $_tag, $_content);
1444             }
1445             # timestamp hostname content
1446             elsif ( $_format eq "THC") {
1447 0           $_message = sprintf("%s %s %s", $_timestamp, $_devname, $_content);
1448             }
1449             # timestamp tag content
1450             elsif ( $_format eq "TTC") {
1451 0           $_message = sprintf("%s %s %s", $_timestamp, $_tag, $_content);
1452             }
1453             # timestamp content
1454             elsif ( $_format eq "TC") {
1455 0           $_message = sprintf("%s %s", $_timestamp, $_content);
1456             }
1457             # timestamp hostname message
1458             elsif ( $_format eq "THM") {
1459 0           $_message = sprintf("%s %s %s", $_timestamp, $_devname, $_content);
1460             }
1461             # timestamp message
1462             elsif ( $_format eq "TM") {
1463 0           $_message = sprintf("%s %s", $_timestamp, $_content);
1464             }
1465             # ???
1466             else {
1467 0           $ERROR = sprintf("unknown format: [%s] [%s] [%s] [%s]",
1468             $_timestamp, $_devname, $_tag, $_content
1469             );
1470 0 0         return(wantarray ? (undef, $ERROR) : undef);
1471             }
1472              
1473             #
1474             # MESSAGE to transmit
1475             #
1476 0           $tx_msg = sprintf("<%s>%s", $_pri, $_message);
1477 0           $msg_l = length($tx_msg);
1478              
1479             # check allowed lengths
1480 0           $msg_l = length($tx_msg);
1481 0 0         if ($_tag =~ /(.+)\[/)
  0            
1482 0           {$tag_l = length($1);}
1483             else
1484             {$tag_l = length($_tag);}
1485              
1486 0 0         if ($_send->{'strict'}) {
1487             # syslog message length can not exceed 1024
1488 0 0         if ($msg_l > 1024) {
1489 0           $ERROR = "syslog message length $msg_l greater than 1024";
1490 0 0         return(wantarray ? (undef, $ERROR) : undef);
1491             }
1492             # syslog tag length can not exceed 32
1493 0 0         if ($tag_l > 32) {
1494 0           $ERROR = "syslog message tag length $tag_l greater than 32";
1495 0 0         return(wantarray ? (undef, $ERROR) : undef);
1496             }
1497             }
1498            
1499 0 0         if ($_send->{'debug'}){
1500 0           log_debug(1, "sendto: %s port %s proto %s\n",
1501             $_send->{'server'}, $_send->{'port'}, $_send->{'proto'}
1502             );
1503 0           log_debug(2, "pri: %s facility: %s [%s] severity: %s [%s]\n",
1504             $_pri,
1505             $_send->{'facility'}, $_facility,
1506             $_send->{'severity'}, $_severity,
1507             );
1508 0           log_debug(3, "format: %s\n", $_format);
1509 0   0       log_debug(3, "timestamp: %s [%s]\n", $_send->{'timestamp'} || 'localtime', $_timestamp);
1510 0   0       log_debug(3, "device: %s [%s]\n", $_send->{'device'} || 'none', $_devname);
1511 0   0       log_debug(3, "tag: %s [%s] [%s]\n", $_tag || 'noTag', $_send->{'tag'}||'-', $_send->{'pid'}||'-');
      0        
      0        
1512 0           log_debug(4, "content: %s\n", $_content);
1513 0           log_debug(4, "message: %s\n", $_message);
1514 0           log_debug(5, "tx msg: %s\n", $tx_msg);
1515             }
1516            
1517             # send the message
1518 0           $_sock = IO::Socket::INET->new(
1519             PeerAddr => $_send->{'server'},
1520             PeerPort => $_send->{'port'},
1521             Proto => $_send->{'proto'}
1522             );
1523 0 0         unless ($_sock) {
1524 0           $ERROR = sprintf("could not open socket to %s:%s [%s]",
1525             $_send->{'server'}, $_send->{'port'}, $!
1526             );
1527 0 0         return( wantarray ? (undef, $ERROR) : undef) ;
1528             }
1529 0           print $_sock $tx_msg;
1530              
1531 0           $_sock->close();
1532 0 0         return(wantarray ? (1, '') : 1);
1533              
1534             } # end send_message
1535              
1536             #
1537             #=======================================================================
1538             #
1539             # Syslog Receive Message
1540             #
1541             #=======================================================================
1542             #
1543             #
1544             #........................................................................
1545             #
1546             # Syslog Receive message constructor
1547             #
1548             # port => port to listen on (514)
1549             # proto => protocol (udp)
1550             # maxlength => max length of packet (1024)
1551             # verbose => 0|1|2|3 verbose level (0)
1552             # 0 - pure message
1553             # 1 - bsd format
1554             # 2 - bsd_plus format
1555             #
1556             sub listen {
1557              
1558             # create object
1559 0     0 1   my $_proto = shift;
1560 0   0       my $_class = ref($_proto) || $_proto;
1561 0           my $_listen = {};
1562             # bless object
1563 0           bless($_listen, $_class);
1564              
1565 0           $ERROR = '';
1566              
1567             # define CTRL-C
1568 0           $SIG{INT} = \&interupt_listen;
1569              
1570 0           my %_arg = @_;
1571 0           my ($_a, $_err,
1572             $_sock, $_port, $ipaddr, $_ipaddr_packed, $_rhost,
1573             $_msg, $_msg_count,
1574             $_parse_obj, $_parse,
1575             $_report,
1576             $_fwd_sock, $_fwd,
1577             );
1578              
1579              
1580             # set defaults
1581 0           $_listen->{'port'} = 514;
1582 0           $_listen->{'proto'} = 'udp';
1583 0           $_listen->{'maxlength'} = '1024';
1584 0           $_listen->{'verbose'} = 0;
1585 0           $_listen->{'packets'} = -1;
1586              
1587 0           $_listen->{'report'} = 0;
1588 0           $_listen->{'format'} = 'bsd';
1589 0           $_listen->{'moreTime'} = 0;
1590 0           $_listen->{'parseTag'} = 0;
1591              
1592 0           $_listen->{'fwd_port'} = '514';
1593 0           $_listen->{'fwd_proto'} = 'udp';
1594              
1595 0           $_fwd = 0;
1596              
1597              
1598 0           foreach $_a (keys %_arg) {
1599 0 0         if ($_a =~ /^-?port$/) { $_listen->{'port'} = delete($_arg{$_a}); }
  0 0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
1600 0           elsif ($_a =~ /^-?proto$/) { $_listen->{'proto'} = delete($_arg{$_a}); }
1601 0           elsif ($_a =~ /^-?maxlength$/) { $_listen->{'maxlength'} = delete($_arg{$_a}); }
1602 0           elsif ($_a =~ /^-?packets$/) { $_listen->{'packets'} = delete($_arg{$_a}); }
1603 0           elsif ($_a =~ /^-?verbose$/) { $_listen->{'verbose'} = delete($_arg{$_a}); }
  0            
1604             # parser options
1605 0           elsif ($_a =~ /^-?dump$/i) {$_listen->{'dump'} = delete($_arg{$_a}); }
1606 0           elsif ($_a =~ /^-?append$/i) {$_listen->{'append'} = delete($_arg{$_a}); }
1607 0           elsif ($_a =~ /^-?ext$/i) {$_listen->{'ext'} = delete($_arg{$_a}); }
1608 0           elsif ($_a =~ /^-?report$/i) {$_listen->{'report'} = delete($_arg{$_a}); }
1609 0           elsif ($_a =~ /^-?interval$/i) {$_listen->{'interval'} = delete($_arg{$_a}); }
1610 0           elsif ($_a =~ /^-?rx_time$/i) {$_listen->{'rx_time'} = delete($_arg{$_a}); }
1611 0           elsif ($_a =~ /^-?lastmsg$/i) {$_listen->{'lastmsg'} = delete($_arg{$_a}); }
1612 0           elsif ($_a =~ /^-?debug$/i) {$_listen->{'debug'} = delete($_arg{$_a}); }
1613 0           elsif ($_a =~ /^-?min_date$/i) {$_listen->{'filter_min_date'} = delete($_arg{$_a}); }
1614 0           elsif ($_a =~ /^-?max_date$/i) {$_listen->{'filter_max_date'} = delete($_arg{$_a}); }
1615 0           elsif ($_a =~ /^-?device$/i) {$_listen->{'filter_device'} = delete($_arg{$_a}); }
1616 0           elsif ($_a =~ /^-?tag$/i) {$_listen->{'filter_tag'} = delete($_arg{$_a}); }
1617 0           elsif ($_a =~ /^-?message$/i) {$_listen->{'filter_message'} = delete($_arg{$_a}); }
1618 0           elsif ($_a =~ /^-?format$/i) {$_listen->{'format'} = delete($_arg{$_a}); }
1619 0           elsif ($_a =~ /^-?moreTime$/i) {$_listen->{'moreTime'} = delete($_arg{$_a}); }
1620 0           elsif ($_a =~ /^-?parseTag$/i) {$_listen->{'parseTag'} = delete($_arg{$_a}); }
1621 0           elsif ($_a =~ /^-?fwd_server/i) {$_listen->{'fwd_server'} = delete($_arg{$_a}); }
1622 0           elsif ($_a =~ /^-?fwd_port/i) {$_listen->{'fwd_port'} = delete($_arg{$_a}); }
1623             elsif ($_a =~ /^-?fwd_proto/i) {$_listen->{'fwd_proto'} = delete($_arg{$_a}); }
1624             else {
1625 0           $ERROR = sprintf("unsupported argument: %s => %s", $_a, $_arg{$_a});
1626 0 0         return(wantarray ? (undef, $ERROR) : undef);
1627             }
1628             }
1629              
1630             # on unix, you need to be root if port < 1024
1631 0 0         if ($^O !~ /win/i) {
1632 0 0 0       if ($_listen->{'port'} < 1024 && $> != 0) {
1633 0           $ERROR = "must have root uid, not $> for port $_listen->{'port'}";
1634 0 0         return(wantarray ? (undef, $ERROR) : undef);
1635             }
1636             }
1637              
1638             # see if we need to parse the syslog line
1639 0 0 0       if ($_listen->{'report'} || $_listen->{'verbose'} > 1) {
1640 0 0         ($_parse_obj, $_err) = Syslog->parse(
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
1641             -report => $_listen->{'report'},
1642             exists($_listen->{'dump'}) ? (-dump => $_listen->{'dump'}) : (),
1643             exists($_listen->{'append'}) ? (-append => $_listen->{'append'}) : (),
1644             exists($_listen->{'ext'}) ? (-ext => $_listen->{'ext'}) : (),
1645             exists($_listen->{'report'}) ? (-report => $_listen->{'report'}) : (),
1646             exists($_listen->{'interval'}) ? (-interval => $_listen->{'interval'}) : (),
1647             exists($_listen->{'rx_time'}) ? (-rx_time => $_listen->{'rx_time'}) : (),
1648             exists($_listen->{'lastmsg'}) ? (-lastmsg => $_listen->{'lastmsg'}) : (),
1649             exists($_listen->{'debug'}) ? (-debug => $_listen->{'debug'}) : (),
1650             exists($_listen->{'msg_plus'}) ? (-msg_plus => $_listen->{'msg_plus'}) : (),
1651             exists($_listen->{'min_date'}) ? (-min_date => $_listen->{'min_date'}) : (),
1652             exists($_listen->{'max_date'}) ? (-max_date => $_listen->{'max_date'}) : (),
1653             exists($_listen->{'device'}) ? (-device => $_listen->{'device'}) : (),
1654             exists($_listen->{'tag'}) ? (-tag => $_listen->{'tag'}) : (),
1655             exists($_listen->{'message'}) ? (-message => $_listen->{'message'}) : (),
1656             exists($_listen->{'format'}) ? (-format => $_listen->{'format'}) : (),
1657             exists($_listen->{'moreTime'}) ? (-moreTime => $_listen->{'moreTime'}) : (),
1658             exists($_listen->{'parseTag'}) ? (-parseTag => $_listen->{'parseTag'}) : (),
1659             );
1660 0 0         unless($_parse_obj) {
1661 0           $ERROR = "listener failed to open parser: $_err";
1662 0 0         return(wantarray ? (undef, $ERROR) : undef);
1663             }
1664             }
1665              
1666 0 0         $_fwd = 1 if defined($_listen->{'fwd_server'});
1667              
1668             # open socket
1669             #
1670             # fwd socket
1671 0 0         if ($_fwd) {
1672 0           printf("forwarding to %s:%s %s\n",
1673             $_listen->{'fwd_server'}, $_listen->{'fwd_port'}, $_listen->{'fwd_proto'}
1674             );
1675 0           $_fwd_sock = IO::Socket::INET->new(
1676             PeerAddr => $_listen->{'fwd_server'},
1677             PeerPort => $_listen->{'fwd_port'},
1678             Proto => $_listen->{'fwd_proto'}
1679             );
1680 0 0         unless ($_fwd_sock) {
1681 0           $ERROR = sprintf("could not open forward socket to %s:%s [%s]",
1682             $_listen->{'fwd_server'}, $_listen->{'port'}, $!
1683             );
1684 0 0         return(wantarray ? (undef, $ERROR) : undef);
1685             }
1686             }
1687             # rx socket
1688 0 0         printf("opening rx socket port %s proto %s %s\n",
1689             $_listen->{'port'}, $_listen->{'proto'},
1690             $_fwd ? 'forwarding' : 'not forwarding',
1691             );
1692 0           $_sock = IO::Socket::INET->new(
1693             LocalPort => $_listen->{'port'},
1694             Proto => $_listen->{'proto'},
1695             );
1696 0 0         unless ($_sock) {
1697 0           $ERROR = sprintf("socket failed port: %s %s : %s",
1698             $_listen->{'port'}, $_listen->{'proto'}, $@,
1699             );
1700 0 0         return(wantarray ? (undef, $ERROR) : undef);
1701             }
1702              
1703             # listen on socket
1704 0           $_msg_count = 0;
1705 0           while ($_sock->recv($_msg, $_listen->{'maxlength'})) {
1706 0           printf("%s\n", $_msg);
1707 0 0         if ($_fwd) {
1708 0           print $_fwd_sock $_msg;
1709             }
1710 0           $_msg_count++;
1711             # print out little more if we are verbose
1712 0 0         if ($_listen->{'verbose'}) {
1713 0           ($_port, $_ipaddr_packed) = sockaddr_in($_sock->peername);
1714 0           $ipaddr = inet_ntoa($_ipaddr_packed);
1715 0           $_rhost = gethostbyaddr($_ipaddr_packed, AF_INET);
1716 0           printf(" Packet: %s from %s:%s [%s]\n",
1717             $_msg_count, $ipaddr, $_port, $_rhost
1718             );
1719             }
1720             # parse the line if we want a report
1721 0 0 0       if ($_listen->{'report'} || $_listen->{'verbose'} > 1) {
1722 0           ($_parse, $_err) = $_parse_obj->parse_syslog_line($_msg);
1723 0 0         if ($_parse) {
1724 0 0         if ($_listen->{'verbose'} > 1) {
1725 0           printf(" Priority: %s Facility [%s] Severity [%s]\n",
1726             $_parse->{'rx_priority'}, $_parse->{'rx_facility'}, $_parse->{'rx_severity'}
1727             );
1728 0           printf(" Timestamp: %s\n", $_parse->{'timestamp'});
1729 0           printf(" Device: %s\n", $_parse->{'device'});
1730 0           printf(" Tag: %s %s\n", $_parse->{'tag'}, $_parse->{'pid'});
1731 0           printf(" Content: %s\n", $_parse->{'content'});
1732             }
1733             }
1734             else {
1735 0 0         printf("parse_syslog_line failed: %s\n", $_err) if $_listen->{'verbose'};
1736             }
1737             }
1738             # check if we are counting packets
1739             #if ($_listen->{'packets'} > 0) {last if $_msg_count == $_listen->{'packets'};}
1740 0 0         if ($_listen->{'packets'} > 0) {
1741 0 0         if ($_msg_count == $_listen->{'packets'}) {
1742 0           printf("received %s packets set for %s\n", $_msg_count, $_listen->{'packets'});
1743 0           last;
1744             }
1745             }
1746             }
1747 0 0         $_sock->close if $_sock;
1748 0 0         $_fwd_sock->close if $_fwd_sock;
1749              
1750             # close files if we reported and dumped
1751 0 0 0       if ($_listen->{'report'} && $_listen->{'dump'}) {$_parse_obj->close_dumps;}
  0            
1752              
1753              
1754             # function to handle CTRL-C
1755             sub interupt_listen {
1756 0     0 0   printf("CTRL-C detected: closing socket\n");
1757 0           $_sock->shutdown(0);
1758 0 0         $_fwd_sock->shutdown(0) if $_fwd_sock;
1759             }
1760              
1761 0 0         if ($_listen->{'report'}) {
1762 0           printf("Returning object reference\n");
1763 0 0         return(wantarray ? ($_parse, $ERROR) : $_parse);
1764             }
1765             else {
1766 0 0         return(wantarray ? ($_msg_count, "$_msg_count messages") : $_msg_count);
1767             }
1768             } # end sub listen
1769              
1770              
1771              
1772             #
1773             #=============================================================================
1774             #
1775             # handle the files
1776             #
1777             #=============================================================================
1778             #
1779             # function to dump line to file
1780             #
1781             # Arg
1782             # $_[0] class
1783             # $_[1] devicename
1784             # $_[2] line
1785             #
1786             # Return
1787             # 1 or undef
1788             #
1789             sub dump_line_to_file {
1790              
1791 0     0 0   my $_h = $_[1];
1792 0           $_h =~ s/ +//g;
1793 0           my $_dstfile = sprintf("%s%s.%s", $_[0]->{'repository'}, $_[1], $_[0]->{'ext'});
1794              
1795 0           $ERROR = '';
1796              
1797 0           log_debug(3, "syslog line dump to file: [%s]\n", $_dstfile);
1798             # see if we have a file handle
1799 0 0         if (!defined($FH{$_h})) {
1800             # open for overwrite or appending
1801 0 0         if ($_[0]->{'append'} == 1) {
1802 0 0         open($FH{$_h}, ">>$_dstfile") or $ERROR = "open append failed: $_h: $!";
1803             }
1804             else {
1805 0 0         open($FH{$_h}, ">$_dstfile") or $ERROR = "open overwright failed: $_h: $!";
1806             }
1807 0           select $FH{$_h}; $| = 1;
  0            
1808 0           select STDOUT; $| = 1;
  0            
1809             }
1810              
1811             # exit out if we errored
1812 0 0         if ($ERROR) {
1813 0           log_debug(3, "%s\n", $ERROR);
1814 0 0         return(wantarray ? (undef, $ERROR) : $ERROR);
1815             }
1816              
1817 0           my $fh = $FH{$_h};
1818 0           printf $fh ("%s\n", $_[2]);
1819              
1820 0 0         return(wantarray ? (1, $ERROR) : 1);
1821              
1822             }
1823             #
1824             #.............................................................................
1825             #
1826             # function to close all files
1827             #
1828             #
1829             sub close_dumps {
1830 0     0 1   my $_f;
1831             # close any filehandle opened for parse
1832 0           foreach $_f (keys %FH) { close($FH{$_f});}
  0            
1833 0           1;
1834             }
1835              
1836             ##############################################################################
1837             #
1838             # Report Functions
1839             #
1840             #
1841             #.............................................................................
1842             #
1843             #
1844             # function to derive stats
1845             # stats are generated if -report => 1
1846             #
1847             # user will have to access the %STATS hash created
1848             #
1849             #
1850             # @DEVICES = list of each device found
1851             # @TAGS = list of each tag found
1852             # @FACILITYS = list of each facility found
1853             # @SEVERITYS = list of each of each severity found
1854             #
1855             # %STATS{'syslog'}{'messages'}
1856             # {'tag'}{}{'messages'}
1857             # {'facility'}{}{'messages'}
1858             # {'severity'}{}{'messages'}
1859             # {'min_epoch'}
1860             # {'min_date_str'} # done with &syslog_stats_epoch2datestr
1861             # {'max_epoch'}
1862             # {'max_date_str'} # done with &syslog_stats_epoch2datestr
1863             #
1864             #
1865             # {'device'}{}{'messages'}
1866             # {'tag'}{}{'messages'}
1867             # {'facility'}{}{'messages'}
1868             # {'severity'}{}{'messages'}
1869             # {'min_epoch'}
1870             # {'min_date_str'} # done with &syslog_stats_epoch2datestr
1871             # {'max_epoch'}
1872             # {'max_date_str'} # done with &syslog_stats_epoch2datestr
1873             #
1874             #
1875             sub syslog_stats {
1876              
1877 0   0 0 0   my $_tag = $SYSLOG_href->{'tag'} || $NOTAG;
1878 0   0       my $_prio = $SYSLOG_href->{'rx_priority'} || 'noFacility.noSeverity';
1879 0   0       my $_fac = $SYSLOG_href->{'rx_facility'} || 'noFacility';
1880 0   0       my $_sev = $SYSLOG_href->{'rx_severity'} || 'noSeverity';
1881              
1882              
1883             # set min,max epoch for date
1884             # needed to be able to find min max dates
1885 0 0         if (!defined($STATS{'syslog'}{'min_epoch'})) {$STATS{'syslog'}{'min_epoch'} = 2**32;}
  0            
1886 0 0         if (!defined($STATS{'syslog'}{'max_epoch'})) {$STATS{'syslog'}{'max_epoch'} = 1;}
  0            
1887              
1888             #
1889             # populate arrays
1890             #
1891             # device list
1892 0 0         if ( !defined($STATS{'device'}{$SYSLOG_href->{'device'}}) )
1893 0           { push(@DEVICES, $SYSLOG_href->{'device'}); }
1894              
1895             # TAG list
1896 0 0         if ( !defined($STATS{'syslog'}{'tag'}{$_tag}) )
1897 0           { push(@TAGS, $_tag); }
1898              
1899             # FACILITY list
1900 0 0         if ( !defined($STATS{'syslog'}{'facility'}{$_fac}) )
1901 0           { push(@FACILITYS, $_fac); }
1902              
1903             # SEVERITY list
1904 0 0         if ( !defined($STATS{'syslog'}{'severity'}{$_sev}) )
1905 0           { push(@SEVERITYS, $_sev ); }
1906              
1907             #
1908             # per syslog
1909             #
1910 0           $STATS{'syslog'}{'messages'}++;
1911 0           $STATS{'syslog'}{'tag'}{$_tag}{'messages'}++;
1912 0           $STATS{'syslog'}{'facility'}{$_fac}{'messages'}++;
1913 0           $STATS{'syslog'}{'severity'}{$_sev}{'messages'}++;
1914              
1915 0 0         if ($SYSLOG_href->{'epoch'} < $STATS{'syslog'}{'min_epoch'})
  0            
1916             {$STATS{'syslog'}{'min_epoch'} = $SYSLOG_href->{'epoch'};}
1917              
1918 0 0         if ($SYSLOG_href->{'epoch'} > $STATS{'syslog'}{'max_epoch'})
  0            
1919             {$STATS{'syslog'}{'max_epoch'} = $SYSLOG_href->{'epoch'};}
1920              
1921             #
1922             # per device
1923             #
1924 0           $STATS{'device'}{$SYSLOG_href->{'device'}}{'messages'}++;
1925 0           $STATS{'device'}{$SYSLOG_href->{'device'}}{'tag'}{$_tag}{'messages'}++;
1926 0           $STATS{'device'}{$SYSLOG_href->{'device'}}{'facility'}{$_fac}{'messages'}++;
1927 0           $STATS{'device'}{$SYSLOG_href->{'device'}}{'severity'}{$_sev}{'messages'}++;
1928              
1929             # check for min/max epoch existence
1930 0 0         if (!defined($STATS{'device'}{$SYSLOG_href->{'device'}}{'min_epoch'}))
  0            
1931             {$STATS{'device'}{$SYSLOG_href->{'device'}}{'min_epoch'} = 2**32;}
1932 0 0         if (!defined($STATS{'device'}{$SYSLOG_href->{'device'}}{'max_epoch'}))
  0            
1933             {$STATS{'device'}{$SYSLOG_href->{'device'}}{'max_epoch'} = 1;}
1934              
1935             # find min/max epoch per device
1936 0 0         if($SYSLOG_href->{'epoch'} < $STATS{'device'}{$SYSLOG_href->{'device'}}{'min_epoch'})
  0            
1937             {$STATS{'device'}{$SYSLOG_href->{'device'}}{'min_epoch'} = $SYSLOG_href->{'epoch'};}
1938 0 0         if($SYSLOG_href->{'epoch'} > $STATS{'device'}{$SYSLOG_href->{'device'}}{'max_epoch'})
  0            
1939             {$STATS{'device'}{$SYSLOG_href->{'device'}}{'max_epoch'} = $SYSLOG_href->{'epoch'};}
1940              
1941 0           1;
1942              
1943             } # end sub syslog_stats
1944              
1945              
1946             #
1947             #.............................................................................
1948             #
1949             # function to convert epoch values in %STAT to date strings
1950             # do this separate so as to do it once per device and whole syslog
1951             #
1952             #
1953              
1954             sub syslog_stats_epoch2datestr {
1955              
1956 0     0 1   my $dev;
1957              
1958 0           $STATS{'syslog'}{'min_date_str'} = epoch_to_datestr($STATS{'syslog'}{'min_epoch'});
1959 0           $STATS{'syslog'}{'max_date_str'} = epoch_to_datestr($STATS{'syslog'}{'max_epoch'});
1960              
1961 0           foreach $dev (keys %{$STATS{'device'}}) {
  0            
1962 0           $STATS{'device'}{$dev}{'min_date_str'} = epoch_to_datestr($STATS{'device'}{$dev}{'min_epoch'});
1963 0           $STATS{'device'}{$dev}{'max_date_str'} = epoch_to_datestr($STATS{'device'}{$dev}{'max_epoch'});
1964             }
1965              
1966 0           1;
1967             }
1968              
1969              
1970             #############################################################################
1971             #
1972             # Timestamp Functions
1973             #
1974             #.............................................................................
1975             #
1976             # function to convert epoch to (month, day, hour, epoch_start_of_day)
1977             # epoch_time_of_day for this (month, day, hour)
1978             #
1979             # Arg
1980             # epoch seconds
1981             # Return
1982             # (month, day, hr, min, epoch_start_of_day, epoch_end_of_day);
1983             #
1984             sub _epoch_to_mdhm {
1985              
1986             # 0 1 2 3 4 5 6 7 8
1987             # localtime(epoch) = ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
1988 0     0     my @_val = localtime($_[0]);
1989              
1990             # sec min hr mday mon yr
1991 0           my $_epoch_start_of_day = timelocal(0, 0, 0, $_val[3], $_val[4], $YEAR);
1992 0           my $_epoch_end_of_day = timelocal(59, 59, 23, $_val[3], $_val[4], $YEAR);
1993              
1994 0           return($MON{$_val[4]}, $_val[3], $_val[2], $_val[1], $_epoch_start_of_day, $_epoch_end_of_day);
1995             }
1996             #
1997             #.............................................................................
1998             #
1999             # function to convert epoch seconds to timestamp
2000             # if no epoch seconds are given, current epoch seconds are used
2001             #
2002             # Arg
2003             # $_[0] = epoch seconds
2004             # Return
2005             # Mmm d hh:mm:ss
2006             # Mmm dd hh:mm:ss
2007             #
2008             sub epoch_to_syslog_timestamp {
2009              
2010 0   0 0 1   my $epoch = shift || time;
2011 0           my @t = localtime($epoch);
2012              
2013 0           sprintf("%3s %2s %02s:%02s:%02s",
2014             $MON{$t[4]+1}, $t[3], $t[2], $t[1], $t[0]
2015             );
2016              
2017             }
2018             #.............................................................................
2019             #
2020             # function to convert epoch seconds to common date string (datestr)
2021             #
2022             # Arg
2023             # $_[0] = epoch
2024             #
2025             # Return
2026             # date string
2027             #
2028             #
2029             sub epoch_to_datestr {
2030              
2031 0   0 0 1   my $_epoch = shift || time;
2032 0           my $_datestr = '';
2033              
2034 0           my @_tokens = localtime($_epoch);
2035              
2036 0           my $_month = $MON{$_tokens[4]+1};
2037              
2038 0           $_datestr = sprintf("%s/%s/%s %02s:%02s:%02s",
2039             $_month, $_tokens[3], $_tokens[5]+1900,
2040             $_tokens[2], $_tokens[1], $_tokens[0],
2041             );
2042              
2043 0           log_debug(3, "epoch_to_datestr %s => [%s] [%s] [%s] [%s]\n",
2044             $_epoch, $_datestr,
2045             $_month, $_tokens[3], $_tokens[5]+1900,
2046             );
2047              
2048 0           $_datestr;
2049             }
2050              
2051              
2052             #
2053             #.............................................................................
2054             #
2055             # function to convert date give as a filter to an epoch
2056             #
2057             # Arg
2058             # $_[0] = mm/dd/yyyy hh:mm:ss
2059             sub date_filter_to_epoch {
2060              
2061 0     0 1   my $_str = shift;
2062 0           my ($_mon, $_day, $_yr, $_hr, $_min, $_sec, $_epoch);
2063              
2064             # if Mmm/dd/yyyy convert month alpha string to decimal
2065 0 0         if ($_str =~ /([JFMASONDjfmasond]\w\w)\/\d{1,2}\//) {
2066 0           $_str =~ s/$1/$MON_index{$1}/;
2067             }
2068              
2069             # mm/dd/yyyy hh:mm:ss
2070 0 0         if ($_str =~ /^(\d{1,2})\/(\d{1,2})\/(\d{1,4}) (\d{1,2}):(\d{1,2}):(\d{1,2})$/) {
    0          
    0          
    0          
2071 0           $_mon = $1; $_day = $2; $_yr = $3;
  0            
  0            
2072 0           $_hr = $4; $_min = $5; $_sec = $6;
  0            
  0            
2073             }
2074             # mm/dd/yyyy hh:mm
2075             elsif ($_str =~ /^(\d{1,2})\/(\d{1,2})\/(\d{1,4}) (\d{1,2}):(\d{1,2})$/) {
2076 0           $_mon = $1; $_day = $2; $_yr = $3;
  0            
  0            
2077 0           $_hr = $4; $_min = $5; $_sec = 0;
  0            
  0            
2078             }
2079             # mm/dd/yyyy hh
2080             elsif ($_str =~ /^(\d{1,2})\/(\d{1,2})\/(\d{1,4}) (\d{1,2})$/) {
2081 0           $_mon = $1; $_day = $2; $_yr = $3;
  0            
  0            
2082 0           $_hr = $4; $_min = 0; $_sec = 0;
  0            
  0            
2083             }
2084              
2085             # mm/dd/yyyy
2086             elsif ($_str =~ /^(\d{1,2})\/(\d{1,2})\/(\d{1,4})$/) {
2087 0           $_mon = $1; $_day = $2; $_yr = $3;
  0            
  0            
2088 0           $_hr = 23; $_min = 59; $_sec = 59;
  0            
  0            
2089             }
2090             # assert
2091             else {
2092 0           $ERROR = "unsupported date filter: $_str";
2093 0           return(undef);
2094             }
2095              
2096 0           $_epoch = timelocal($_sec, $_min, $_hr, $_day, $_mon-1, $_yr);
2097              
2098 0           return($_epoch);
2099              
2100             } # end date_filter_to_epoch
2101              
2102              
2103             #
2104             #.............................................................................
2105             #
2106             # function to validate syslog timestamp
2107             #
2108             # Mmm d hh:mm:ss
2109             # Mmm dd hh:mm:ss
2110             #
2111             # Arg
2112             # $_[0] = timestamp
2113             #
2114             # Return
2115             # 0 - not valid
2116             # 1 - valid
2117             sub validate_timestamp_syntax {
2118 0 0   0 1   if ($_[0] =~ /[JFMASONDjfmasond]\w\w \d \d\d:\d\d:\d\d/)
  0 0          
2119 0           {return(1);}
2120             elsif ($_[0] =~ /[JFMASONDjfmasond]\w\w \d\d \d\d:\d\d:\d\d/)
2121 0           {return(1);}
2122             else
2123             {return(0);}
2124             }
2125              
2126              
2127             #
2128             #.............................................................................
2129             #
2130             # function to timeslots based on min/max epoch
2131             # make global array
2132             # @TIMESLOTS = ([index, low, high], ...)
2133             # index = Mmm-dd-hh:mm
2134              
2135             #
2136             # Arg
2137             # $_[0] = min epoch
2138             # $_[1] = max epoch
2139             # $_[2] = interval
2140             #
2141             #
2142             sub make_timeslots {
2143              
2144 0     0 1   my $_min_epoch = shift;
2145 0           my $_max_epoch = shift;
2146 0   0       my $_int = shift || 3600;
2147              
2148 0           my ($_time, $_idx);
2149              
2150             # check that we have min/max
2151 0 0 0       if (!$_min_epoch || !$_max_epoch) {
2152 0           $ERROR = "min epoch [$_min_epoch] or max epoch [$_min_epoch] not defined";
2153 0 0         return( wantarray ? (undef, $ERROR) : undef);
2154             }
2155             # check min < max
2156 0 0         if ($_min_epoch > $_max_epoch) {
2157 0           $ERROR = "min epoch [$_min_epoch] > max epoch [$_min_epoch]";
2158 0 0         return( wantarray ? (undef, $ERROR) : undef);
2159             }
2160             # interval can be no less than 60
2161 0 0         if ($_int < 60) {
2162 0           $_int = 60;
2163             }
2164              
2165 0           for ($_time = $_min_epoch; $_time <= $_max_epoch; $_time = $_time + $_int) {
2166 0           log_debug(3, "report time: %s\n", $_time);
2167 0           $_idx = epoch_to_datestr($_time);
2168 0           push(@TIMESLOTS, [$_idx, $_time, $_time + ($_int - 1)]);
2169 0           log_debug(3, "report timeslot: %s %s => %s\n",
2170             $_idx, $_time, $_time + ($_int - 1)
2171             );
2172             }
2173 0 0         return( wantarray ? (1, undef) : 1);
2174             }
2175              
2176              
2177              
2178             #
2179             #.............................................................................
2180             #
2181             # function to return index that tx_time belongs to, info stored @TIMESLOTS
2182             # read in epoch seconds, find element in @INFO whose whose rang include
2183             # this arg value
2184             # return index
2185             # @TIMESLOTS = ([index, low_epoch, high_epoch], ...)
2186             #
2187             # Arg
2188             # $_[0] = epoch of timestamp
2189             #
2190             # Return
2191             # timeslot index for stats
2192             #
2193             sub epoch_timeslot_index {
2194              
2195 0     0 1   my $_i;
2196 0           foreach $_i (@TIMESLOTS) {
2197 0 0 0       if($_[0] >= $_i->[1] && $_[0] <= $_i->[2]) {
2198 0           return($_i->[0]);
2199             }
2200             }
2201 0           undef;
2202              
2203             }
2204              
2205             #
2206             #.............................................................................
2207             #
2208             # function to get extra time info: year and weekday
2209             #
2210             # Arg
2211             # sec, min, hour, day, month
2212             # Return
2213             # wantarray ? ($_epoch, $_wday) : $_epoch
2214             #
2215             sub _extra_time_values {
2216              
2217 0     0     $_[4]--; # 0 base the month
2218              
2219 0           my $_epoch = timelocal(@_, $YEAR);
2220 0           my $_wday = (localtime($_epoch))[6];
2221 0 0         if ($DEBUG) {
2222 0           log_debug(3, "determine epoch and wday: s:%s m:%s h:%s d:%s mon: %s\n",
2223             @_
2224             );
2225 0           log_debug(3, "epoch: %s wday: [%s]\n", $_epoch, $_wday);
2226             }
2227            
2228 0 0         return(wantarray ? ($_epoch, $_wday) : $_epoch);
2229              
2230             }
2231             #
2232             #=============================================================================
2233             #
2234             # function to decode PRI to facility and severity
2235             #
2236             # Arg
2237             # $_[0] = PRI
2238             #
2239             # Return (lower case are decimal, upper case are strings)
2240             # pri, facility, severity, PRI, Facility, Severity
2241              
2242             sub decode_PRI {
2243              
2244 0     0 1   my ($_p, $_f, $_s, $_F, $_S, $_P);
2245              
2246 0           $_p = $_[0];
2247             # strip out '<>' that bound PRI
2248 0 0         if ($_[0] =~ /[<|>]/) {
2249 0           $_p =~ s/
2250 0           $_p =~ s/>//;
2251             }
2252              
2253             # check that decimal number is between 0->191
2254 0 0 0       if ($_p >= 0 && $_p <= 191) {
2255 0           $_f = int($_p/8);
2256 0           $_s = $_p - ($_f*8);
2257              
2258 0   0       $_F = $Facility_Index{$_f} || "?$_f?";
2259 0   0       $_S = $Severity_Index{$_s} || "?$_s?";
2260 0           $_P = sprintf("%s.%s", $_F, $_S);
2261              
2262 0 0         return(wantarray ? ($_p, $_f, $_s, $_P, $_F, $_S) : $_P );
2263             }
2264             # otherwise error out
2265             else {
2266 0 0         return(wantarray ? (-1, -1, -1, 'P?', 'F?', 'S?') : undef );
2267             }
2268              
2269             }
2270              
2271              
2272             #
2273             #.............................................................................
2274             #
2275             # function to normalize facility string
2276             #
2277             sub normalize_facility {
2278              
2279 0     0 1   my $_str = '';
2280              
2281 0 0         if ($_[0] =~ /kern/i) {$_str = 'kern'}
  0 0          
  0 0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
2282 0           elsif ($_[0] =~ /user/i) {$_str = 'user'}
2283 0           elsif ($_[0] =~ /mail/i) {$_str = 'mail'}
2284 0           elsif ($_[0] =~ /daemon/i) {$_str = 'daemon'}
2285 0           elsif ($_[0] =~ /auth/i) {$_str = 'auth'}
2286 0           elsif ($_[0] =~ /syslog/i) {$_str = 'syslog'}
2287 0           elsif ($_[0] =~ /lpr/i) {$_str = 'lpr'}
2288 0           elsif ($_[0] =~ /news/i) {$_str = 'news'}
2289 0           elsif ($_[0] =~ /uucp/i) {$_str = 'uucp'}
2290 0           elsif ($_[0] =~ /cron/i) {$_str = 'cron'}
2291 0           elsif ($_[0] =~ /auth/i) {$_str = 'authpriv'}
2292 0           elsif ($_[0] =~ /ftp/i) {$_str = 'ftp'}
2293 0           elsif ($_[0] =~ /ntp/i) {$_str = 'ntp'}
2294 0           elsif ($_[0] =~ /audit/i) {$_str = 'audit'}
2295 0           elsif ($_[0] =~ /alert/i) {$_str = 'alert'}
2296 0           elsif ($_[0] =~ /at/i) {$_str = 'at'}
2297 0           elsif ($_[0] =~ /local0$/i) {$_str = 'local0'}
2298 0           elsif ($_[0] =~ /local1$/i) {$_str = 'local1'}
2299 0           elsif ($_[0] =~ /local2$/i) {$_str = 'local2'}
2300 0           elsif ($_[0] =~ /local3$/i) {$_str = 'local3'}
2301 0           elsif ($_[0] =~ /local4$/i) {$_str = 'local4'}
2302 0           elsif ($_[0] =~ /local5$/i) {$_str = 'local5'}
2303 0           elsif ($_[0] =~ /local6$/i) {$_str = 'local6'}
2304 0           elsif ($_[0] =~ /local7$/i) {$_str = 'local7'}
2305             else {$_str = $_[0];}
2306              
2307 0           return($_str);
2308             }
2309             #
2310             #.............................................................................
2311             #
2312             # function to normalize severity string
2313             #
2314             sub normalize_severity {
2315              
2316 0     0 1   my $_str = '';
2317              
2318 0 0         if ($_[0] =~ /emerg/i) {$_str = 'emerg'}
  0 0          
  0 0          
    0          
    0          
    0          
    0          
    0          
2319 0           elsif ($_[0] =~ /alert/i) {$_str = 'alert'}
2320 0           elsif ($_[0] =~ /crit/i) {$_str = 'crit'}
2321 0           elsif ($_[0] =~ /err/i) {$_str = 'err'}
2322 0           elsif ($_[0] =~ /warn/i) {$_str = 'warn'}
2323 0           elsif ($_[0] =~ /notice/i) {$_str = 'notice'}
2324 0           elsif ($_[0] =~ /info/i) {$_str = 'info'}
2325 0           elsif ($_[0] =~ /debug/i) {$_str = 'debug'}
2326             else {$_str = $_[0];}
2327              
2328 0           return($_str);
2329              
2330             }
2331              
2332             #
2333             #.............................................................................
2334             #
2335             #
2336             sub log_debug {
2337              
2338 0     0 0   my $_level = shift;
2339 0           my $_format = shift;
2340              
2341              
2342 0 0         if ($_level <= $DEBUG) {
2343 0           printf("debug: %s: $_format", (caller(1))[3], @_);
2344             }
2345              
2346 0           1;
2347             }
2348             #
2349             #.............................................................................
2350             #
2351             # function to set $YEAR
2352             #
2353             # call
2354             # $_obj->set_year(1988);
2355             #
2356             # Arg
2357             # $_[0] = class
2358             # $_[1] = year to set to, else set to current year
2359             #
2360             # Return
2361             # $YEAR
2362             #
2363             #
2364             sub set_year {
2365 0     0 1   my $_class = shift;
2366 0   0       $YEAR = shift || ((localtime)[5]) + 1900;
2367 0           $YEAR;
2368             }
2369              
2370             #
2371             #.............................................................................
2372             #
2373             # functions to return references to data structures
2374             #
2375 0     0 1   sub syslog_stats_href { return(\%STATS); }
2376 0     0 1   sub syslog_device_aref { return(\@DEVICES); }
2377 0     0 1   sub syslog_facility_aref { return(\@FACILITY); }
2378 0     0 1   sub syslog_severity_aref { return(\@SEVERITY); }
2379 0     0 1   sub syslog_tag_aref { return(\@TAGS); }
2380 0     0 0   sub syslog_timeslot_aref { return(\@TIMESLOTS); }
2381 0     0 1   sub syslog_error_count { $ERROR_count; }
2382 0     0 1   sub syslog_filter_count { $FILTER_count; }
2383 0     0 1   sub syslog_parse_count { $PARSE_count; }
2384              
2385 0     0 1   sub syslog_error {return($ERROR);}
2386              
2387              
2388              
2389             #
2390             #.............................................................................
2391             #
2392             # from perl cookbook to put comma's in integer
2393             sub _commify {
2394 0     0     my $text = reverse $_[0];
2395 0           $text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
2396 0           return scalar reverse $text;
2397             }
2398              
2399              
2400             1; # end package Syslog
2401              
2402              
2403             #=============================================================================
2404             #
2405             # POD
2406             #
2407             #=============================================================================
2408              
2409             =pod
2410              
2411             =head1 NAME
2412              
2413             Net::Dev::Tools::Syslog - Send, Listen Parse Syslog messages.
2414              
2415             =head1 VERSION
2416              
2417             Syslog 1.0.0
2418              
2419             =head1 SYNOPSIS
2420              
2421             use Syslog;
2422              
2423             #
2424             # Syslog Parser
2425             #
2426             ($syslog, $error) = Syslog->parse(
2427             -dump => ,
2428             -append => <0|1>,
2429             -ext => ,
2430             -report => <0|1>,
2431             -interval => ,
2432             -debug => <0|1|2|3>,
2433             -rx_time => <0|1>,
2434             -lastmsg => <0|1>,
2435             -min_date => ,
2436             -max_date => ,
2437             -device => ,
2438             -tag => ,
2439             -message => ,
2440             -format =>
2441             -moreTime => <0|1>
2442             -parseTag => <0|1>
2443             );
2444              
2445             $parse = $syslog->parse_syslog_line();
2446              
2447             #
2448             # Syslog Send
2449             #
2450             ($send, $error) = Syslog->send(
2451             -server =>
,
2452             -port => ,
2453             -proto => ,
2454             -facility => ,
2455             -severity => ,
2456             -timestamp => ,
2457             -device => ,
2458             -tag => ,
2459             -pid => ,
2460             -message => ,
2461             -strict => <0|1>,
2462             );
2463              
2464             $send->send_message(
2465             -server =>
,
2466             -port => ,
2467             -proto => ,
2468             -facility => ,
2469             -severity => ,
2470             -timestamp => ,
2471             -device => ,
2472             -tag => ,
2473             -pid => ,
2474             -message => ,
2475             -strict => <0|1>,
2476             );
2477              
2478             #
2479             # Syslog Listen
2480             #
2481             ($listen, $error) = Syslog->listen(
2482             -port => ,
2483             -proto => ,
2484             -maxlength =>
2485             -verbose => <0|1|2|3>,
2486             -fwd_server => ,
2487             -fwd_port => ,
2488             -fwd_proto => ,
2489             );
2490              
2491              
2492             =head1 DESCRIPTION
2493              
2494             Module provides methods to parse syslog files, send syslog messages to
2495             syslog server, listen for syslog message on localhost.
2496              
2497             =over 4
2498              
2499             =item Parser
2500              
2501             parse method creates a class that configures the parser used
2502             on each syslog file entry (line) sent to the parser.
2503             The object is first created with properties that define how
2504             a syslog line is to be worked on. The parse_syslog_line function
2505             (method) is then used to parse the syslog line and return a
2506             reference to a hash.
2507              
2508             =item Send
2509              
2510             send method will send a syslog message to a syslog sever. The user
2511             can provide as much or as little information desired. The class
2512             will then create a syslog message from the information given
2513             or from default values and send the message to the desired server.
2514              
2515             =item Listen
2516              
2517             listen will open the desired port on the local host to listen
2518             for sylog messages. Message received on the port are assumed to
2519             be syslog messages and are printed to STDOUT. Messages can also
2520             be forwarded 'as received' to another address. The functionality
2521             of the parser can also be used when in this mode.
2522              
2523             =back
2524              
2525             See documentation for individual function/methods for more detail
2526             on usage and operation.
2527              
2528              
2529             =head1 REQUIRES
2530              
2531             Time::Local used to convert TIMESTAMP to epoch
2532             IO::Socket used by send and listen methods
2533             Sys::Hostname used to support DNS
2534              
2535             =head1 EXPORTS
2536              
2537             parse_syslog_msg
2538             epoch_to_syslog_timestamp
2539             make_timeslots
2540             epoch_timeslot_index
2541             normalize_facility
2542             normalize_severity
2543              
2544             =head1 EXPORT TAGS
2545              
2546             :parser parse_syslog_msg
2547             :time epoch_to_syslog_timestamp, make_timeslots, epoch_timeslot_index
2548             :syslog normalize_facility, normalize_severity
2549              
2550              
2551             =head1 Parser Methods and Functions
2552              
2553             =head2 parse
2554              
2555             Constructor to create an object to parse the lines of a syslog file.
2556             Arguments are used to define parsing. See function parse_syslog_line
2557             section to see how to parse the line.
2558              
2559             ($syslog, $error) = Syslog->parse(
2560             -dump => ,
2561             -append => <0|1>,
2562             -ext => ,
2563             -report => <0|1>,
2564             -interval => ,
2565             -debug => <0|1|2|3>,
2566             -rx_time => <0|1>,
2567             -lastmsg => <0|1>,
2568             -min_date => ,
2569             -max_date => ,
2570             -device => ,
2571             -tag => ,
2572             -message => ,
2573             -format => ,
2574             -moreTime => <0|1>,
2575             -parseTag => <0|1>,
2576             );
2577              
2578              
2579             Argument Checks:
2580              
2581             If -dump is used, then argument must be a directory, current
2582             directory is not assumed. The directory provided must exist and
2583             allow user write access.
2584              
2585             If -interval is less than 60, it is set to 60.
2586              
2587             If -min_date and/or -max_date are given the syntax and range are checked.
2588              
2589              
2590             Return, in list context will return reference to object and error.
2591             In scalar context returns reference to object. If object fails to create,
2592             then the reference is undef.
2593              
2594             =over 4
2595              
2596             =item -dump
2597              
2598             Enable creation of separate syslog files. Each file created will only
2599             contain lines for the device defined in the syslog message HOSTNAME.
2600             The argument defines a directory to where device specific
2601             syslog files are dumped. Current directory is not assumed.
2602             Directories are checked for existence and writability.
2603              
2604             Default = FALSE, no dumps
2605              
2606             =item -append <0|1>
2607              
2608             If 0, device files created due to -dump are overwritten.
2609             If 1, device files created due to -dump are appended to.
2610             Default = 0, (overwrite)
2611              
2612             =item -ext
2613              
2614             File extension to use for device files created due to -dump
2615             being enabled.
2616             Default = 'slp', (SysLog Parsed)
2617              
2618             =item -report <0|1>
2619              
2620             If 0 no stats are recorded.
2621             If 1 stats are extracted. For each line successfully parsed, information
2622             is stored in a hash. This hash can be referenced with the function
2623             syslog_stats_href.
2624              
2625             =item -interval
2626              
2627             The amount of seconds to use when making timeslots.
2628             make_timeslots function will make timeslot ranging from
2629             min and max time found or given. The timeslot info can then
2630             be used to create stats for desired time intervals.
2631             See @TIMESLOTS for more info.
2632             Min value is 60 (1 minute).
2633             Default is 3600 (1 hour).
2634              
2635             =item -debug <0|1|2|3>
2636              
2637             Set debug level, verbosity increases as number value increases.
2638              
2639              
2640             =item -rx_time <0|1>
2641              
2642             Set flag to use the localhost receive time and not the timestamp from the
2643             sylog message. Some syslog deamon prepend information to the syslog
2644             message when writing to a file. If a receive time is one of these
2645             fields, then it can be used. This will normalize all times to when
2646             they are received by the serever.
2647             Default is 0
2648              
2649             =item -lastmsg <0|1>
2650              
2651             Set flag to to handle last message as previous message.
2652             If true and the syslog message has the pattern
2653             'last message repeated time',then we replace this current
2654             line with the previous line. Otherwise the 'last message' line
2655             is treated as all other syslog lines. The tag will be defined as
2656             'lastmsg', pid and content set to ''.
2657             Default is 0.
2658              
2659              
2660             =item -min_date
2661              
2662             If given, then will be used to filter dates. Only lines with dates
2663             greater to or equal to this will be be parsed. This check is performed
2664             after -rx_time, thus filter applies to whatever date you decide to keep.
2665              
2666             You must enter mm/dd/yyyy, other values will default:
2667             ss defaults to 0 if hh:mm given, 59 if no time given
2668             mm defaults to 0 if hh: given, 59 if no time given
2669             hh defaults to 23 if no time given
2670              
2671             Mmm/dd/yyyy can also be use, where Mmm is Jan, Feb, Mar,...
2672              
2673              
2674             =item -max_date
2675              
2676             If given, then will be used to filter dates. Only lines with dates
2677             less than or equal to this will be be parsed. This check is performed
2678             after -rx_time, thus filter applies to whatever date you decide to keep.
2679              
2680             Apply same syntax rules as -min_date
2681              
2682             =item -device
2683              
2684             If given, only device fields matching the pattern are kept. Text strings
2685             or Perl regexp can be given.
2686              
2687              
2688             =item -tag
2689              
2690             If given, only tag fields matching the pattern are kept. Text strings
2691             or Perl regexp can be given.
2692              
2693             =item -message
2694              
2695             If given, only message fields matching the pattern are kept. Text strings
2696             or Perl regexp can be given.
2697              
2698             =item -format
2699              
2700             Defines how $SYSLOG_pattern is constructed to use match the syslog line
2701             against to parse out the component parts.
2702             bsd will use $TIMESTAMP, $HOSTNAME, $MESSAGE.
2703             noHost will use $TIMESTAMP, $MESSAGE.
2704             self will use $SYSLOG_pattern as defined by the user, by default
2705             $SYSLOG_pattern is defined same as bsd
2706              
2707             =item -moreTime <0|1>
2708              
2709             If defined, derive more time information from the timestamp. This will
2710             convert time strings to epoch second using localtime function. This
2711             will slow down processing. This will be enabled if needed, such as when
2712             -min_date, -max_date, -report are used.
2713              
2714             =item -parseTag <0|1>
2715              
2716             If enabled, will parse TAGs from Syslog Message. TAG syntax varys greatly
2717             from different devices/vendors. This module tries to parse out the common
2718             style but realizes it can not assume a common syntax so the user is allowed
2719             to define their own. See the Data Access section on TAGs for more information.
2720              
2721             Common style is considered to be: foo[123]:
2722              
2723             =back
2724              
2725              
2726             =head2 parse_syslog_line
2727              
2728             ($parse, $error) = $syslog->parse_syslog_line();
2729              
2730             Method to parse the syslog line. If syslog line is not given
2731             as argument then $_ is parsed.
2732              
2733             The pattern used to parse the given line is a pattern made by the strings
2734             defined by $TIMESTAMP,$HOSTNAME,$MESSAGE. If -format is bsd, then
2735             $TIMESTAMP,$HOSTNAME,$MESSAGE are used to make the pattern. If -format
2736             is noHost, then $TIMESTAMP, $MESSAGE are used to make the pattern. These
2737             strings are used to make $SYSLOG_pattern
2738              
2739             Some syslog daemons may prepend other information when writing
2740             syslog message to syslog file. parse_syslog_line will try to detect this
2741             by applying a regexp match for an RFC 3164 syslog message to
2742             $SYSLOG_pattern. The match will be treated as the syslog message, any string
2743             found before the match will be considered a preamble. The preamble will be
2744             parsed for receive time, syslog priority (facility.severity) and
2745             source IP address. This info is extracted and made avaliable to the user.
2746              
2747             parse_syslog_line calls parse_syslog_msg to parse respective information.
2748             The string given to parse_syslog_msg is the string matched by
2749             $SYSLOG_pattern. Any facility or severity parsed is normalized to
2750             the strings listed in @FACILITY and @SEVERITY.
2751              
2752             Syslog messages are the strings matched by $SYSLOG_pattern. Changing
2753             this string to something else allows the user to modify the parser.
2754              
2755             The information parsed from the line given to parse_syslog_line is then
2756             checked against any filters.
2757              
2758             If the filtering determines we keep the line the function checks if the
2759             -dump or -report options are enabled and performs the required task.
2760              
2761              
2762             See Data Access Section for hash structure made by parse_syslog_line.
2763             Each call to this function clears the previous information stored.
2764              
2765             The user has external control over the parser through the global variables
2766             $TIMESTAMP,$HOSTNAME,$MESSAGE, $SYSLOG_pattern. See the Data Access Section
2767             for requirements of these strings.
2768              
2769              
2770             In list context a reference to a hash and error are returned.
2771             In scalar context, a reference to a hash is returned.
2772              
2773             Events to Return Error:
2774              
2775             =over
2776              
2777             =item blank line
2778              
2779             =item outside of date range, if date filters are applied
2780              
2781             =item no date parsed and date filters are applied
2782              
2783             =item unable to dump line to file, if -dump option true
2784              
2785             =back
2786              
2787              
2788             =head2 parse_syslog_msg
2789              
2790             @fields = parse_syslog_msg($msg, [$moreTime, $parseTag, <0|1>]);
2791              
2792             $fields[0] = timestamp portion of syslog message
2793              
2794             $fields[1] = hostname portion of syslog message
2795              
2796             $fields[2] = message portion of syslog message
2797              
2798             $fields[3] = error string
2799              
2800              
2801             This function can be used externally as a function call.
2802             When called internally by parse_syslog_line the 4th argument is set
2803             to 1 and the function populates the respective data structure. If called
2804             externally, then will return in list context (timestamp, device,
2805             message, error) or in scalar context a reference to a hash whose keys
2806             are 'timestamp', 'device', 'message'. If an error occurs then everthing
2807             is undef and the error is a string detailing the error.
2808              
2809              
2810             The given line is pattern matched against $SYSLOG_pattern and the
2811             information is gather from the $1, $2, $3, ... placeholders.
2812             See Data Access for syntax if defining your own $TIMESTAMP,$HOSTNAME,$MESSAGE,
2813             $SYSLOG_pattern strings.
2814              
2815             If the -moreTime argument is true (non zero) then the $TIMESTAMP is picked
2816             apart for more information. This would be the -moreTime argument to
2817             parse_syslog_line when doing OOP, otherwise user must define on a user call.
2818              
2819             If the -parseTag argument is true (non zero) then the parse_tag function
2820             attempts to parse a TAG. This would be the -parseTag argument to
2821             parse_syslog_line when doing OOP, otherwise user must define on a user call.
2822              
2823             See Data Access section, Syslog Line Hash Reference for the syntax of the hash
2824             populated with the information parsed with parse_syslog_msg.
2825              
2826              
2827             =head2 parse_tag
2828              
2829             ($tag, $pid, $content) = parse_tag();
2830              
2831             This function will parse TAG and PID from the given string and return the
2832             TAG and PID and CONTENT. If the user defines $TAG_1 or $TAG_2 or $TAG_3,
2833             then those patterns are used to match. See Data Access for syntax of $TAG_x.
2834             Otherwise will look at beginning of the string for the common practice of
2835             some text string and pid, such as foo[1234]: content. The given string
2836             is assumed to be the MESSAGE portion of the syslog message, the MESSAGE
2837             portion can be made of TAG and CONTENT fields. The MESSAGE portion follows
2838             the HEADER which consist of TIMESTAMP and HOSTNAME, hostname field mandatory.
2839              
2840             When used by the object, hash key/value entries are populated for the
2841             syslog line, see Data Access section. When used as a standalone function
2842             then TAG, PID and CONTENT fields are returned in list context.
2843              
2844              
2845             =head2 parse_preamble
2846              
2847             This function is not external but is described since debugging will
2848             indicate this function.
2849              
2850              
2851             ($epoch, $date, $facility, $severity, $srcIP) = parse_preamble($preamble);
2852              
2853             Some syslog daemon will prepend information to the RFC3164 format. This
2854             information is:
2855              
2856             =over
2857              
2858             =item local sytem time when the message was received
2859              
2860             =item the facility and severity, derived from PRI
2861              
2862             =item the source IP address
2863              
2864             =back
2865              
2866             When used by the object, hash key/value entries are populated for the
2867             syslog line, see Data Access section.
2868              
2869              
2870             This function will attempt to parse this information out and return in
2871             list context, the receive time in epoch seconds, the local receive time,
2872             the syslog message facility, the syslog message severity, the source IP
2873             address. Date information is assumed to be delimited with dash '-',
2874             time information is delimited with ':'. Since the syntax of this information
2875             is unique, any one of the fields are parsed for, thus they can be in any order
2876             in the preamble.
2877              
2878              
2879             =head1 Send Methods and Functions
2880              
2881             =head2 send
2882              
2883             Constructor to create object that define the fields used in syslog messages to
2884             be sent from the localhost. Arguments define all portions of a RFC 3164
2885             Syslog message. Message is sent when &send_message is called.
2886              
2887             Any argument can be defined now or when calling &send_message. This allows the user
2888             to set values that are static for their needs or change dynamically each time
2889             a message is sent.
2890              
2891             ($syslog, $error) = Syslog->send(
2892             -server => ,
2893             [-port => ,]
2894             [-proto => ,]
2895             [-facility => ,]
2896             [-severity => ,]
2897             [-timestamp => ,]
2898             [-device => ,]
2899             [-tag => ,]
2900             [-pid => ,]
2901             [-content => ,]
2902             [-message => ,]
2903             [-strict => <0|1>,]
2904             [-noHost => <0|1>,]
2905             [-hostname => <0|1>,]
2906             [-noTag => <0|1>,]
2907             [-debug => <0-5>,]
2908             );
2909              
2910              
2911             =over
2912              
2913             =item -server
2914              
2915             Destination Syslog Server IP Address. Default 127.0.0.1
2916              
2917             =item -port
2918              
2919             Destination Port. Default is 514.
2920             On UNIX systems, ports 0->1023 require user to be root (UID=0).
2921              
2922             =item -proto
2923              
2924             IP protocol to use, default is udp.
2925              
2926             =item -facility
2927              
2928             Syslog FACILITY (text) to use. Default is 'user'.
2929              
2930             =item -severity
2931              
2932             Syslog SEVERITY (text) to use. Default is 'debug'.
2933              
2934             =item -timestamp
2935              
2936             TIMESTAMP to put in to syslog message. Default is current time.
2937             Syntax is Mmm dd hh:mm:ss or Mmm d hh:mm:ss
2938              
2939             =item -noHost
2940              
2941             Tell function to insert HOSTNAME field into syslog message.
2942             If 1, HOSTNAME field is not inserted into syslog message.
2943             If 0, HOSTNAME field is determined by -device or -hostname arguments.
2944             Default is 0.
2945              
2946             =item -device
2947              
2948             HOSTNAME to put in to syslog message. If not given then -hostname
2949             determines HOSTNAME, if -hostname is not true, then 'netdevsyslog'
2950             is used for HOSTNAME field.
2951              
2952             =item -hostname
2953              
2954             Use sytem hostname value for HOSTNAME field, if -device does not give
2955             a hostname to use, then a call to the systems hostname function
2956             is called to get value for HOSTNAME field of syslog message. If
2957             -device and -hostname are not used then 'netdevsyslog' is used
2958             for HOSTNAME field.
2959              
2960             =item -noTag
2961              
2962             Tell function to insert a TAG into the syslog message.
2963             If 1, do not put a TAG into syslog message.
2964             If 0, insert a TAG into syslog message.
2965              
2966             =item -tag
2967              
2968             Syslog message TAG to insert into syslog message. If this
2969             is given and -noTag = 0 then this string will be used as the TAG of the
2970             syslog MESSAGE, a single space will separate it from the CONTENT.
2971             This value will not be used if -message is given.
2972             If not given and -noTag = 0 the function will create a TAG with the syntax
2973             of 'NetDevSyslog[$pid]:', If -pid is given.
2974             Otherwise 'NetDevSyslogp:[$$]', where system PID is used.
2975              
2976              
2977             =item -pid
2978              
2979             Syslog message TAG PID to use. If given, will create
2980             NetDevSyslog[$pid], otherwise the system PID for the the current
2981             script will be used NetDevSyslogp[$$].
2982             This value will not be used if -message is given.
2983              
2984              
2985              
2986             =item -content
2987              
2988             String to use for syslog message CONTENT. The message portion of a
2989             syslog message is defined as a TAG and CONTENT, with TAG not being
2990             mandatory. This field will be combined with -tag and -pid to
2991             create the syslog MESSAGE portion.
2992              
2993              
2994             =item -message
2995              
2996             String to use as syslog MESSAGE portion. User may opt to not use
2997             the -tag, -pid, -content message and just pass in the complete
2998             MESSAGE portion to follow the TIMESTAMP and HOSTNAME fields.
2999             If this is given it will have precedence over -content.
3000              
3001              
3002              
3003             =item -strict
3004              
3005             By default strict syntax is enforced, this can be disabled with -strict 0.
3006             Strict rules allow message to be no longer than 1024 characters and TAG
3007             within the message to be no longer than 32 characters.
3008              
3009             =back
3010              
3011              
3012             =head2 send_message
3013              
3014             Function will create a RFC 3164 syslog message and send to destination IP:port.
3015             For values not defined by user, defaults will be used. The same arguments given
3016             for the constructor 'send' apply to this function. Thus any value can be changed
3017             before transmission.
3018              
3019             ($ok, $error) = $syslog->send_message(
3020             [-server => ],
3021             [-port => ,]
3022             [-proto => ,]
3023             [-facility => ,]
3024             [-severity => ,]
3025             [-timestamp => ,]
3026             [-device => ,]
3027             [-tag => ,]
3028             [-pid => ,]
3029             [-content => ],
3030             [-message => ,]
3031             [-strict => <0|1>],
3032             [-noHost => <0|1>],
3033             [-hostname => <0|1>],
3034             [-noTag => <0|1>],
3035             [-debug => <0-5>],
3036             );
3037              
3038             See above send method for descriptions of send_message options.
3039              
3040             For any error detected, the message will not be sent and undef returned.
3041             For each message sent, the socket is opened and closed.
3042              
3043             In list context the status and error are returned, in scalar context just the
3044             status is returned. If the message is sent successfully, then status is 1,
3045             otherwise undef. If an error occurs then the error variable is a descriptive
3046             string, otherwise undef.
3047              
3048              
3049             =head3 Syslog Message Creation.
3050              
3051             Using the options/arguments of send and send_message the syslog message is created
3052             with the following logic:
3053              
3054             PRI (decimal) is calculated from FACILITY and SEVERITY values, defaults are
3055             used if not given in function call.
3056              
3057              
3058             TIMESTAMP is either given with -timestamp or taken from local system time.
3059              
3060              
3061             HOSTNAME is created if -noHost set to 0. If -device given, then this
3062             becomes HOSTNAME. Else if -hostname is 1, then system hostname is used.
3063             Else 'netdevsyslog' is used. If -noHost is 1 then no HOSTNAME field is
3064             put into the Syslog message.
3065              
3066              
3067             MESSAGE is created either from user giving the full message with the
3068             -message argument. Otherwise MESSAGE is created by combining TAG, PID,
3069             CONTENT. CONTENT is the message of the sylog line.
3070              
3071             CONTENT creation follows this:
3072              
3073             if -content given, then CONTENT is the given string
3074             if -noTag =0
3075             if -tag and -pid are defined TAG becomes tag[pid]:
3076             if -tag only then TAG is the argument string
3077             if -pid only then NetDevSyslog[pid]:
3078             otherwise NetDevSyslogp[$$]
3079             otherwise CONTENT becomes the default message.
3080              
3081              
3082              
3083              
3084             SYSLOG MESSAGE to be put on the wire is then created by joining
3085             the different variables, $TIMESTAMP, [$HOSTNAME], [$TAG],
3086             $MESSAGE | $CONTENT
3087              
3088              
3089              
3090             =head1 Listen Methods and Functions
3091              
3092             =head2 listen
3093              
3094             Constructor to create object that listens on desired port and prints out
3095             messages received. Message are assumed to be syslog messages. Messages
3096             can also be forward unaltered to a defined address:port.
3097              
3098             ($syslog, $error) = Syslog->listen(
3099             [-port => ,]
3100             [-proto => ,]
3101             [-maxlength => ,]
3102             [-packets => ,]
3103             [-verbose => <0|1|2>,]
3104             [-report => <0|1>],
3105             [-fwd_server => ],
3106             [-fwd_port => port>,]
3107             [-fwd_proto => ,]
3108             );
3109              
3110              
3111             If -report or -verbose is used, then a parse oject is created
3112             and the same options that can be given to the parse object can be given
3113             to this object.
3114              
3115              
3116             Message received will be printed to STDOUT.
3117              
3118              
3119             On UNIX systems, if -port is less than 1024 then the the user
3120             must have UID=0 (root). To listen on ports 1024 and above
3121             any userid is allowed.
3122              
3123              
3124             CTRL-C ($SIG{INT}) is redefined to shutdown the socket and then return
3125             control back to caller, it will not exit your program.
3126              
3127             If -report option is enabled, then a reference to object that can be
3128             used to access %STATS will be returned.
3129              
3130             Otherwise a counter value indicating the number of messages received
3131             is returned.
3132              
3133              
3134             =over
3135              
3136             =item -port
3137              
3138             Local port to listen for messages. Messages are assumed to be syslog messages.
3139             Some OS's may require root access.
3140             Default is 514.
3141              
3142             =item -proto
3143              
3144             Protocol to use.
3145             Default is udp.
3146              
3147              
3148             =item -maxlength
3149              
3150             Max message length. Default is 1024
3151              
3152             =item -packets
3153              
3154             Shutdown the socket listening on after N packets are received on the
3155             given port. At least one packet must be received for packet count
3156             to be checked.
3157              
3158             =item -verbose
3159              
3160             Verbosity level 0-3
3161              
3162              
3163             =item -report
3164              
3165             Perform same reporting as the parse method does. All arguments to the parse
3166             method can be used on this method. Unlike the parse method, reporting
3167             is off by default for listen method.
3168              
3169             =item -fwd_server
3170              
3171             Forward received message to address given. Message is sent as it is received
3172             off the wire. Dotted decimal IP address or DNS name can be given.
3173              
3174             =item -fwd_port
3175              
3176             Define TCP port for forward message to be sent. Default is 514
3177              
3178             =item -fwd_proto
3179              
3180             Define transport protocol for forwarded message to be sent. Default is UDP.
3181             String 'udp' or 'tcp' can be given.
3182              
3183              
3184             =back
3185              
3186              
3187              
3188             =head1 General Functions
3189              
3190             =head2 init
3191              
3192             Initialize the hash storing the current syslog line information.
3193              
3194             $syslog->init();
3195              
3196              
3197             =head2 close_dumps
3198              
3199             Function to loop through all filehandles opened for dumping a syslog
3200             line to a device specific file. The parsing function will take care
3201             of closing any files created with the -dump option, this is available
3202             to give the user the control if needed.
3203              
3204             $syslog->close_dumps();
3205              
3206              
3207             =head2 syslog_stats_epoch2datestr
3208              
3209             Function to convert epoch seconds, in the hash created with -report option,
3210             to a date string of Mmm/dd/yyyy hh:mm:ss. This function acts on the hash
3211             and convert epoch min, max time value for the whole syslog file and per
3212             each device. If you reference the hash before running this function
3213             you will not get the date string. This process is kept separate to save
3214             time in waiting for this to complete if done during parsing, since
3215             we only need to do it after the min and max are found. The syntax
3216             is purposely different than the syntax of a sylog message, but does contain
3217             the same information with a year value added.
3218              
3219             &syslog_stats_epoch2datestr;
3220              
3221              
3222             =head2 epoch_to_syslog_timestamp
3223              
3224             Function to convert epoch seconds to a RFC 3164 syslog message timestamp.
3225             If epoch seconds not given, then current time is used.
3226              
3227             $timestamp = epoch_to_syslog_timestamp($epoch);
3228              
3229             =head2 epoch_to_datestr
3230              
3231             Function to convert epoch seconds to a common date string.
3232             If epoch seconds not given, then current time is used.
3233              
3234             $date_str = epoch_to_datestr($epoch)
3235              
3236             Date string format Mmm/dd/yyyy hh:mm:ss
3237              
3238             =head2 date_filter_to_epoch
3239              
3240             Function to convert date given for a filter to epoch seconds.
3241              
3242             $epoch = date_filter_to_epoch();
3243              
3244             =head2 validate_timestamp_syntax
3245              
3246             Function to validate that a given timestamp matches the syntax
3247             defined by RFC 3164. If valid, then '1' is returned, if invalid
3248             then '0' is returned.
3249              
3250             $ok = validate_timestamp_syntax($timestamp);
3251              
3252             =head2 make_timeslots
3253              
3254             Function to create @TIMESLOTS given the min/max epoch seconds and
3255             the interval. Will start at min epoch value and increment until
3256             reaching or exceeding the max epoch value. For each increment an
3257             index is made based on the min epoch for that interval. The index
3258             is created with &epoch_to_datestr.
3259              
3260             make_timeslots($min_epoch, $max_epoch, $interval);
3261              
3262             Min and max values are mandatory and are checked to be greater or less
3263             than the other value. If $interval is not given, function defaults
3264             to 60 seconds.
3265              
3266             The created list is built as such
3267              
3268             @TIMESLOTS = ([$index, min_epoch, $max_epoch], ...);
3269              
3270             This list can be used to group syslog messages to a specific timeslot.
3271             From the syslog line we have epoch seconds, this list provides a range
3272             to check the epoch seconds against and the index for that range.
3273              
3274             =head2 epoch_timeslot_index
3275              
3276             Function that takes a given epoch second value and returns the timeslot
3277             index value for that value from @TIMESLOTS.
3278              
3279             $index = epoch_timeslot_index($epoch);
3280              
3281             If no match is found, undef is returned.
3282              
3283             =head2 normalize_facility
3284              
3285             Function to take a character string representing a facility and
3286             return a normalize string contained in @FACILITY.
3287              
3288             $facility = normalize_facility($facility);
3289              
3290             If given string is not normailized, it is returned
3291              
3292             =head2 normalize_severity
3293              
3294             Function to take a character string representing a severity and
3295             return a normalize string contained in @SEVERITY.
3296              
3297             $severity = normalize_severity($severity);
3298              
3299             If given string is not normailized, it is returned
3300              
3301             =head2 decode_PRI
3302              
3303             Function to decode PRI in decimal format to a Facility and Severity.
3304             Can accept either decimal number or decimal number bounded by '<' '>'.
3305              
3306             In list context will return list of information, in scalar context will
3307             return respective Facility and Severity strings joined with '.'.
3308              
3309              
3310             @pri = decode_PRI($pri_dec);
3311             $PRI = decode_PRI($pri_dec);
3312              
3313             $pri[0] PRI decimal value
3314             $pri[1] Facility decimal value
3315             $pri[2] Severity decimal value
3316             $pri[3] PRI character string (join facility and severity string)
3317             $pri[4] Facility charater string
3318             $pri[5] Severity charater string
3319              
3320             Given PRI value is checked to be between 0 and 191. If not, then undef
3321             is returned in scalar context and for list values any decimal
3322             number is -1, P?, F?, S? for PRI, Facility Severity character strings
3323             respectively
3324              
3325              
3326             =head2 set_year
3327              
3328             Set the value used by methods and functions of this module to the current
3329             year as known by localtime. Syslog message timestamps do not conatain year
3330             information. A user may need to change this when looking at a syslog from
3331             a different year.
3332              
3333             If no value is given, then the current year is assumed, otherwise
3334             the year is set to the argument.
3335              
3336             $syslog->set_year(2003); # set year to 2003
3337             $syslog->set_year(); # set year to ((localtime)[5]) + 1900
3338              
3339              
3340              
3341             =head2 syslog_stats_href
3342              
3343             Return reference to %STATS, this is the hash created with the -report option
3344             to the parser and listener.
3345              
3346             =head2 syslog_device_aref
3347              
3348             Return reference to @DEVICES, list of HOSTNAMEs parsed.
3349             This is created with the -report option to the parser and listener.
3350              
3351             =head2 syslog_facility_aref
3352              
3353             Return reference to @FACILITY, list of FACILITIES parsed.
3354             This is created with the -report option to the parser and listener.
3355              
3356             =head2 syslog_severity_aref
3357              
3358             Return reference to @SEVERITY, list of SEVERITIES parsed.
3359             This is created with the -report option to the parser and listener.
3360              
3361             =head2 syslog_tag_aref
3362              
3363             Return reference to @TAGS, list of TAGS parsed
3364             This is created with the -report option to the parser and listener.
3365              
3366             =head2 syslog_timeslot_ref
3367              
3368             Return reference to @TIMESLOTS, list of time slots made by
3369             make_timeslot function.
3370              
3371             =head2 syslog_error
3372              
3373             Return last error.
3374              
3375             =head2 syslog_error_count
3376              
3377             Return error counter value, incremented each time the parser errors.
3378              
3379             =head2 syslog_filter_count
3380              
3381             Return filter counter value, incremented each time a line is filtered
3382              
3383             =head2 syslog_parse_count
3384              
3385             Return parser count, increments each time a line is given to parser
3386              
3387              
3388             =head1 Data Access
3389              
3390             =head2 @FACILITY
3391              
3392             List of all syslog facilities strings as defined by RFC 3164.
3393             Any facility string parse or given by the user is normalized
3394             to strings found in this list.
3395              
3396             =head2 @SEVERITY
3397              
3398             List of all syslog severities strings as defined by RFC 3164.
3399             Any severity string parse or given by the user is normalized
3400             to strings found in this list.
3401              
3402             =head2 %Syslog_Facility
3403              
3404             Hash whose keys are syslog facility strings and whose value
3405             is the decimal representation of that facility.
3406              
3407             =head2 %Syslog_Severity
3408              
3409             Hash whose keys are syslog severity strings and whose value
3410             is the decimal representation of that severity.
3411              
3412             =head2 $TIMESTAMP
3413              
3414             The pattern used to parse TIMESTAMP from RFC 3164 syslog message.
3415              
3416             (([JFMASONDjfmasond]\w\w) {1,2}(\d+) (\d{2}:\d{2}:\d{2}))
3417              
3418             $1 = TIMESTAMP
3419              
3420             $2 = Month string
3421              
3422             $3 = Month day (decimal)
3423              
3424             $4 = hh:mm::ss
3425              
3426              
3427             =head2 $HOSTNAME
3428              
3429             The patterm used to parse HOSTNAME from RFC 3164 syslog message.
3430              
3431             ([a-zA-Z0-9_\.\-]+)
3432              
3433             $1 = HOSTNAME
3434              
3435              
3436             =head2 $TAG_1 $TAG_2 $TAG_3
3437              
3438             The user defined pattern used to parse a TAG from RFC 3164 syslog message.
3439             If any of these are defined, then the TAG is to be parsed against these
3440             patterns, otherwise the modules regexp pattern will be used.
3441              
3442             $TAG_1 $1 = task, $2 = pid, $3 = content
3443              
3444             $TAG_2 $1 = task, $2 = content, no pid
3445              
3446             $TAG_3 $1 = task, $2 = pid, $3 = content
3447              
3448              
3449             =head2 $MESSAGE
3450              
3451             The pattern used to parse MESSAGE from RFC 3164 syslog message.
3452              
3453             (.+)$
3454              
3455             $1 = MESSAGE
3456              
3457              
3458             =head2 $SYSLOG_pattern
3459              
3460             The pattern used to parse any RFC 3164 syslog message.
3461             Combination of $TIMESTAMP, $HOSTNAME, $MESSAGE are used to parse the
3462             different parts from RFC 3164 syslog message. The user can
3463             define this by defining the individual variables that make this
3464             up or just define this directly.
3465              
3466             If used when -format is 'self' then
3467              
3468             $1 = TIMESTAMP
3469              
3470             $2 = Month string
3471              
3472             $3 = Month day (decimal)
3473              
3474             $4 = hh:mm::ss
3475              
3476             $5 = HOSTNAME
3477              
3478             $6 = MESSAGE
3479              
3480              
3481              
3482              
3483             =head2 Syslog Line Hash Reference (parse_syslog_line)
3484              
3485             The hash reference returned by function parse_syslog_line has
3486             the following keys:
3487              
3488             ($hash_ref, $error) = $syslog->parse_syslog_line($message);
3489              
3490              
3491             $hash_ref->{'line'} current line from syslog file
3492             {'timestamp'} timestamp from syslog message
3493             {'device'} device name from syslog message
3494             {'message'} syslog message, from after devname
3495             {'month_str'} month from syslog message timestamp (Jan,Feb,...)
3496             {'month'} month index 0->11
3497             {'day'} day from syslog message timestamp
3498             {'time_str'} hh:mm:ss from syslog message timestamp
3499             {'hour'} hh from syslog message timestamp
3500             {'min'} mm from syslog message timestamp
3501             {'sec'} ss from syslog message timestamp
3502             {'year'} year assumed from localtime
3503             {'epoch'} epoch time converted from syslog message timestamp
3504             {'wday'} wday integer derived from epoch (0-6) = (Sun-Sat)
3505             {'wday_str'} wday string converted, (Sun, Mon, ...)
3506             {'date_str'} syslog message {'epoch'} convert to common format
3507             {'tag'} syslog message content tag
3508             {'pid'} syslog message content tag pid
3509             {'content'} syslog message content after tag parsed out
3510             {'preamble'} string prepended to syslog message
3511             {'rx_epoch'} extra info: rx time epoch
3512             {'rx_timestamp'} extra info: rx timestamp
3513             {'rx_priority'} extra info: priority (text)
3514             {'rx_facility'} extra info: syslog facility (text)
3515             {'rx_severity'} extra info: syslog severity (text)
3516             {'srcIP'} extra info: src IP address
3517             {'rx_epoch'} extra info: rx time epoch
3518             {'rx_date_str'} extra info: rx time date string
3519             {'rx_time_str'} extra info: rx time (hh:mm:ss)
3520             {'rx_year'} extra info: rx time year value
3521             {'rx_month'} extra info: rx time month value
3522             {'rx_month_str'} extra info: rx time month value string (Jan,Feb,..)
3523             {'rx_day'} extra info: rx time day value
3524             {'rx_wday'} extra info: rx time weekday (0-6) (Sun, Mon,..)
3525             {'rx_hour'} extra info: rx time hour value
3526             {'rx_min'} extra info: rx time minute value
3527             {'rx_sec'} extra info: rx time second value
3528              
3529              
3530             =head3 More hash key details
3531              
3532             key = timestamp is $TIMESTAMP
3533              
3534             key = device is $HOSTNAME
3535              
3536             key = message is $MESSAGE
3537              
3538             key = month, day, time_str, hour, minute, sec is parsed from $TIMESTAMP.
3539             month_str is derived
3540              
3541             key = tag and pid may come from $TAG_x if defined
3542              
3543             key = content is the message part after a TAG, if parsed, otherwise the whole message
3544             after $HOSTNAME for bsd format, $TIMESTAMP for noHost format.
3545              
3546             key = preamble is the entire preamble string if found.
3547              
3548             key = rx_* is any information found and parsed from the preamble
3549              
3550              
3551             =head2 %STATS
3552              
3553             Multi-level hash (HoH) that store statisticis.
3554             This hash is created as each line is parsed if -report is enabled.
3555             This only represent some basic stats that I thought everyone would want.
3556             A user can derive their own by examining the different fields in hash
3557             reference returned by parse_syslog_line.
3558              
3559             All of the values listed below are incremented (counter).
3560             Strings enclosed in '<' '>' denote keys derived from information
3561             found in the syslog file, in other words they are variable whereas the
3562             single quoted strings are constant (hardcoded).
3563              
3564             $STATS{'syslog'}{'messages'}
3565             {'min_epoch'}
3566             {'max_epoch'}
3567             {'min_date_str'}
3568             {'max_date_str'}
3569             {'tag'}{<$tag>}{'messages'}
3570             {'facility'}{<$rx_facility>}{'messages'}
3571             {'severity'}{<$rx_severity>}{'messages'}
3572              
3573              
3574             $STATS{'device'}{<$dev>}{'messages'}
3575             {'min_epoch'}
3576             {'max_epoch'}
3577             {'min_date_str'}
3578             {'max_date_str'}
3579             {'tag'}{<$tag>}{'messages'}
3580             {'facility'}{<$rx_facility>}{'messages'}
3581             {'severity'}{<$rx_severity>}{'messages'}
3582              
3583              
3584             =head2 @TIMESLOTS
3585              
3586             @TIMESLOTS is a list (AoA) of time intervals ranging from the min
3587             to max value provided to &make_timeslots function.
3588             A @TIMESLOTS element contains 3 values
3589            
3590             @TIMESLOTS = ([index, min_epoch, max_epoch], ...);
3591              
3592             index - Unique string created to indicate start of timeslot
3593             Mmm/dd/yyyy hh:mm
3594             min_epoch - is begining of the timeslot interval in epoch seconds.
3595             max_epoch - is ending of the timeslot interval in epoch seconds.
3596              
3597              
3598             =head2 @DEVICES
3599              
3600             List of devices found. Created when -report is true. When a device
3601             is firsted learned, its device name as known from the syslog message
3602             is pushed on to this list.
3603              
3604             =head2 @TAGS
3605              
3606             List of tags found. Created when -report is true. When a tag
3607             is firsted learned, its name as known from the sylog message
3608             is pushed on to this list.
3609              
3610              
3611             =head2 $ERROR_count
3612              
3613             Counter for each error occuring in parser.
3614              
3615             =head2 $FILTER_count
3616              
3617             Counter for each line filtered by parser.
3618              
3619             =head2 $PARSE_count
3620              
3621             Counter for each line parsed by parser.
3622              
3623              
3624             =head1 Syslog Message Syntax
3625              
3626             RFC 3164 describes the syntax for syslog message. This modules
3627             intends to adhere to this RFC as well as account for common
3628             practices.
3629              
3630             As described in the RFC, 'device' is a machine that can generate a message.
3631             A 'server' is a machine that receives the message and does not relay it to
3632             any other machine. Syslog uses UDP for its transport and port 514 (server side)
3633             has been assigned to syslog. It is suggested that the device source port also
3634             be 514, since this is not mandatory, this module does not enforce it.
3635              
3636             Section 4.1 of RFC 3164 defines syslog message parts, familiarity with these
3637             descriptions will give the user a better understanding of the functions
3638             and arguments of this module. Maximum length of a syslog message must be 1024
3639             bytes. There is no minimum length for a syslog message. A message of 0 bytes
3640             should not be transmitted.
3641              
3642             =head2 PRI
3643              
3644             4.1.1 PRI Part of RFC 3164 describes PRI. The PRI represents the syslog
3645             Priority value which represents the Facility and Severity as a decimal
3646             number bounded by angle brackets '<' '>'. The PRI will have 3,4 or 5 characters.
3647             Since two characters are always the brackets, the decimal number is then
3648             1-3 characters.
3649              
3650             The Facility and Severity of a message are numerically coded with
3651             decimal values.
3652              
3653             Numerical Facility
3654             Code
3655             0 kernel messages
3656             1 user-level messages
3657             2 mail system
3658             3 system daemons
3659             4 security/authorization messages (note 1)
3660             5 messages generated internally by syslogd
3661             6 line printer subsystem
3662             7 network news subsystem
3663             8 UUCP subsystem
3664             9 clock daemon (note 2)
3665             10 security/authorization messages (note 1)
3666             11 FTP daemon
3667             12 NTP subsystem
3668             13 log audit (note 1)
3669             14 log alert (note 1)
3670             15 clock daemon (note 2)
3671             16 local use 0 (local0)
3672             17 local use 1 (local1)
3673             18 local use 2 (local2)
3674             19 local use 3 (local3)
3675             20 local use 4 (local4)
3676             21 local use 5 (local5)
3677             22 local use 6 (local6)
3678             23 local use 7 (local7)
3679              
3680             Note 1 - Various operating systems have been found to utilize
3681             Facilities 4, 10, 13 and 14 for security/authorization,
3682             audit, and alert messages which seem to be similar.
3683             Note 2 - Various operating systems have been found to utilize
3684             both Facilities 9 and 15 for clock (cron/at) messages.
3685              
3686              
3687             Numerical Severity
3688             Code
3689              
3690             0 Emergency: system is unusable
3691             1 Alert: action must be taken immediately
3692             2 Critical: critical conditions
3693             3 Error: error conditions
3694             4 Warning: warning conditions
3695             5 Notice: normal but significant condition
3696             6 Informational: informational messages
3697             7 Debug: debug-level messages
3698              
3699              
3700             Priority is calculated as: (Facility*8) + Severity. After calculating the Priority,
3701             bound it with barckets and its now a PRI. For example a daemon debug would
3702             be (3*8)+7 => 31 Priority, PRI <31>.
3703              
3704             =head2 HEADER
3705              
3706             The header portion contains a timestamp and the device name or IP. The device
3707             name is not mandatory.
3708              
3709             =head3 TIMESTAMP
3710              
3711             The TIMESTAMP immediately follows the trailing ">" from the PRI when received
3712             on the wire.
3713             The TIMESTAMP is separated from the HOSTNAME by single space characters.
3714              
3715             The TIMESTAMP field is the local time of the sending device and is in the i
3716             format of 'Mmm dd hh:mm:ss'.
3717              
3718             Mmm is the month abbreviation, such as:
3719             Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec.
3720              
3721             dd is day of month. If numeric day value is a single digit,
3722             then the first character is a space. This would make the format
3723             'Mmm d hh:mm:ss'.
3724              
3725             hh:mm::ss are hour minute seconds, 0 padded. Hours range from
3726             0-23 and minutes and seconds range from 0-59.
3727            
3728             A single space charater must follow the the TIMESTAMP field.
3729              
3730             =head3 HOSTNAME
3731              
3732             The HOSTNAME is separated from the precedding TIMESTAMP by single
3733             space character. The HOSTNAME will be the name of the device as it
3734             knows itself. If it does not have a hostname, then its IP address is
3735             used.
3736              
3737             =head2 MSG (message part)
3738              
3739             The MSG part will fill the rest of the syslog packet.
3740             The MSG part is made of two parts the TAG and CONTENT.
3741             The TAG value is the name of the originating process and must
3742             not exceed 32 characters.
3743              
3744             Since there is no specific rule governing TAGs, the user of this module
3745             is allowed to define 3 TAG patterns to be matched. Otherwise the module
3746             trys to extract common practices.
3747              
3748             The CONTENT is the details of the message.
3749              
3750             =head1 Examples
3751              
3752             Examples are also in ./examples directory of distribution. This directory
3753             also contains syslog generator script that is a bit more elaborate
3754             than a simple example but is good for generating syslog message
3755             from a range of dummy host name. Can generate a file or be used
3756             to send to a server. The comments in the code should give you
3757             a general idea of what is being done and what the varaibles mean.
3758              
3759              
3760             =head3 Sample script to listen for syslog messages on local host.
3761              
3762             #!/usr/bin/perl
3763             # Perl script to test Net::Dev::Tools::Syslog listen
3764              
3765             use strict;
3766             use Net::Dev::Tools::Syslog;
3767              
3768             my ($listen_obj, $error, $ok);
3769              
3770             my $port = 7971;
3771             my $proto = 'udp';
3772             # create object to listen
3773             # CTRL-C will close sock and return to caller
3774             ($listen_obj, $error) = Syslog->listen(
3775             -port => $port,
3776             -proto => $proto,
3777             #-verbose => 3,
3778             #-packets => 150,
3779             #-parseTag => 1,
3780             );
3781             unless ($listen_obj) {
3782             printf("ERROR: syslog listen failed: %s\n", $error);
3783             exit(1);
3784             }
3785              
3786             exit(0);
3787              
3788              
3789             =head3 Sample script to send syslog messages.
3790              
3791             #!/usr/bin/perl
3792             # Perl script to test Net::Dev::Tools::Syslog sending
3793             #
3794              
3795             use strict;
3796             use Net::Dev::Tools::Syslog;
3797              
3798             my ($send_obj, $error,
3799             $facility, $severity,
3800             $ok,
3801             );
3802              
3803             my $server = '192.168.1.1';
3804             my $port = 7971;
3805             my $proto = 'udp';
3806              
3807             my $test_send_all = 1;
3808             my $sleep = 0;
3809             my $pid = $$;
3810              
3811             # create send object
3812             ($send_obj, $error) = Syslog->send(
3813             -server => $server,
3814             -port => $port,
3815             -proto => $proto,
3816             );
3817             unless ($send_obj) {
3818             myprintf("ERROR: Syslog send failed: %s\n", $error);
3819             exit(1);
3820             }
3821              
3822             # send syslog message
3823             printf("Sending syslog to %s:%s proto: %s pid: %s\n", $server, $port, $proto, $pid );
3824             # send all syslog type message
3825             if ($test_send_all) {
3826             foreach $facility (@Syslog::FACILITY) {
3827             foreach $severity (@Syslog::SEVERITY) {
3828             #printf("send message: %-10s %s\n", $facility, $severity);
3829             ($ok, $error) = $send_obj->send_message(
3830             -facility => $facility,
3831             -severity => $severity,
3832             -hostname => 1,
3833             -device => 'myTestHost',
3834             -noTag => 0,
3835             #-tag => 'myTag',
3836             -pid => 1,
3837             -content => 'my syslog message content',
3838             );
3839             if(!$ok) {
3840             printf("ERROR: syslog->send_msg: %s\n", $error);
3841             }
3842             sleep $sleep;
3843             }
3844             }
3845             }
3846             else {
3847             ($ok, $error) = $send_obj->send_message(
3848             -hostname => 1,
3849             );
3850             if(!$ok) {
3851             printf("ERROR: syslog->send_msg: %s\n", $error);
3852             }
3853             }
3854              
3855             exit(0);
3856              
3857             =head3 Sample script to parse syslog messages from file.
3858              
3859             #!/usr/bin/perl
3860             # Perl script to test Net::Dev::Tools::Syslog parsing
3861             #
3862             use strict;
3863             use Net::Dev::Tools::Syslog;
3864              
3865             # get sylog file from cli
3866             my $syslog_file = shift || die "usage: $0 \n";
3867              
3868             my ($syslog_obj, $error,
3869             $parse_href,
3870             $report_href,
3871             $fh,
3872             $device, $tag, $facility, $severity,
3873             );
3874              
3875             # create syslog parsing object
3876             ($syslog_obj, $error) = Syslog->parse(
3877             -report => 1,
3878             -parseTag => 1,
3879             -dump => './dump2',
3880             -debug => 0,
3881             -moreTime => 1,
3882             -format => 'noHost',
3883             );
3884             unless ($syslog_obj) {
3885             printf("sylog object constructor failed: %s\n", $error);
3886             exit(1);
3887             }
3888              
3889             # open syslog file to parse
3890             printf("parse syslog file: %s\n", $syslog_file);
3891             open ($fh, "$syslog_file") || die "ERROR: open failed: $!\n";
3892             while(<$fh>) {
3893             ($parse_href, $error) = $syslog_obj->parse_syslog_line($_);
3894             unless ($parse_href) {
3895             printf("ERROR: line %s: %s\n", $., $error);
3896             }
3897             }
3898             close($fh);
3899             printf("parse syslog file done: %s lines\n", $.);
3900              
3901             # convert epoch time in report hash
3902             &syslog_stats_epoch2datestr;
3903              
3904             # reference report hash and display
3905             $report_href = &syslog_stats_href;
3906              
3907             # stats for entire syslog file
3908             printf("Syslog: messages %s %s -> %s\n\n\n",
3909             $report_href->{'syslog'}{'messages'},
3910             $report_href->{'syslog'}{'min_date_str'},
3911             $report_href->{'syslog'}{'max_date_str'},
3912             );
3913              
3914             # stats for each device found in syslog
3915             foreach $device (keys %{$report_href->{'device'}}) {
3916             printf("Device: %s messages: %s %s -> %s\n",
3917             $device,
3918             $report_href->{'device'}{$device}{'messages'},
3919             $report_href->{'device'}{$device}{'min_date_str'},
3920             $report_href->{'device'}{$device}{'max_date_str'},
3921             );
3922             printf(" Tags:\n",);
3923             foreach $tag (keys %{$report_href->{'device'}{$device}{'tag'}}) {
3924             printf(" %8s %s\n",
3925             $report_href->{'device'}{$device}{'tag'}{$tag}{'messages'}, $tag
3926             );
3927             }
3928             printf(" Facility:\n",);
3929             foreach $facility (keys %{$report_href->{'device'}{$device}{'facility'}}) {
3930             printf(" %8s %s\n",
3931             $report_href->{'device'}{$device}{'facility'}{$facility}{'messages'},
3932             $facility
3933             );
3934             }
3935             printf(" Severity:\n",);
3936             foreach $severity (keys %{$report_href->{'device'}{$device}{'severity'}}) {
3937             printf(" %8s %s\n",
3938             $report_href->{'device'}{$device}{'severity'}{$severity}{'messages'},
3939             $severity
3940             );
3941             }
3942             printf("\n");
3943             }
3944              
3945             exit(0);
3946              
3947             =cut
3948              
3949              
3950             =head1 AUTHOR
3951              
3952             sparsons@cpan.org
3953              
3954             =head1 COPYRIGHT
3955              
3956             Copyright (c) 2004-2006 Scott Parsons All rights reserved.
3957             This program is free software; you may redistribute it
3958             and/or modify it under the same terms as Perl itself.
3959              
3960              
3961              
3962              
3963