File Coverage

blib/lib/Parse/Syslog/Mail.pm
Criterion Covered Total %
statement 65 80 81.2
branch 26 62 41.9
condition 2 47 4.2
subroutine 7 7 100.0
pod 2 2 100.0
total 102 198 51.5


line stmt bran cond sub pod time code
1             package Parse::Syslog::Mail;
2 5     5   132828 use strict;
  5         11  
  5         187  
3 5     5   24 use warnings;
  5         13  
  5         137  
4 5     5   26 use Carp;
  5         12  
  5         346  
5 5     5   10457 use Parse::Syslog;
  5         150331  
  5         165  
6              
7             {
8 5     5   118 no strict;
  5         8  
  5         7948  
9             $VERSION = '0.17';
10             }
11              
12             =head1 NAME
13              
14             Parse::Syslog::Mail - Parse mailer logs from syslog
15              
16             =head1 VERSION
17              
18             Version 0.17
19              
20             =head1 SYNOPSIS
21              
22             use Parse::Syslog::Mail;
23              
24             my $maillog = Parse::Syslog::Mail->new('/var/log/syslog');
25            
26             while(my $log = $maillog->next) {
27             # do something with $log
28             # ...
29             }
30              
31             =head1 DESCRIPTION
32              
33             As its names implies, C presents a simple interface
34             to gather mail information from a syslog file. It uses C for
35             reading the syslog, and offer the same simple interface. Currently supported
36             log formats are: Sendmail, Postfix, Qmail.
37              
38              
39             =head1 METHODS
40              
41             =over 4
42              
43             =item B
44              
45             Creates and returns a new C object.
46             A file path or a C object is expected as first argument.
47             Options can follow as a hash. Most are the same as for C<< Parse::Syslog->new() >>.
48              
49             B
50              
51             =over 4
52              
53             =item *
54              
55             C - Format of the syslog stream. Can be one of C<"syslog"> (traditional
56             syslog format) or C<"metalog"> (Metalog format).
57              
58             =item *
59              
60             C - Syslog files usually do store the time of the event without
61             year. With this option you can specify the start-year of this log. If
62             not specified, it will be set to the current year.
63              
64             =item *
65              
66             C - If this option is set, the time in the syslog will be converted
67             assuming it is GMT time instead of local time.
68              
69             =item *
70              
71             C - C will by default repeat xx times events that
72             are followed by messages like C<"last message repeated xx times">. If you
73             set this option to false, it won't do that.
74              
75             =item *
76              
77             C - Specifies an additional locale name or the array of locale
78             names for the parsing of log files with national characters.
79              
80             =item *
81              
82             C - If true will allow for timestamps in the future.
83             Otherwise timestamps of one day in the future and more will not be returned
84             (as a safety measure against wrong configurations, bogus C arguments,
85             etc.)
86              
87             =back
88              
89             B
90              
91             my $syslog = new Parse::Syslog::Mail '/var/log/syslog', allow_future => 1;
92              
93             =cut
94              
95             sub new {
96 19     19 1 14086 my $self = {
97             syslog => undef,
98             };
99 19 100       71 my $class = ref $_[0] ? ref shift : shift;
100 19         54 bless $self, $class;
101              
102 19         53 my $file = shift;
103 19         67 my %args = @_;
104              
105 19 50       80 croak "fatal: Expected an argument" unless defined $file;
106              
107 19 100       41 $self->{syslog} = eval { Parse::Syslog->new($file, %args) } or do {
  19         143  
108 4         1085 $@ =~ s/ at .*$//;
109 4         531 croak "fatal: Can't create new Parse::Syslog object: $@";
110             };
111              
112 15         2749 return $self
113             }
114              
115             =item B
116              
117             Returns the next line of the syslog as a hashref, or C when there
118             is no more lines. The hashref contains at least the following keys:
119              
120             =over 4
121              
122             =item *
123              
124             C - hostname of the machine.
125              
126             =item *
127              
128             C - name of the program.
129              
130             =item *
131              
132             C - Unix timestamp for the event.
133              
134             =item *
135              
136             C - Local transient mail identifier.
137              
138             =item *
139              
140             C - text description.
141              
142             =back
143              
144             Other available keys:
145              
146             =over 4
147              
148             =item *
149              
150             C - Email address of the sender.
151              
152             =item *
153              
154             C - Email addresses of the recipients, coma-separated.
155              
156             =item *
157              
158             C - Message ID.
159              
160             =item *
161              
162             C - MTA host used for relaying the mail.
163              
164             =item *
165              
166             C - Status of the transaction.
167              
168             =item *
169              
170             C - I<(Qmail only)> type of the delivery: C<"local"> or C<"remote">.
171              
172             =item *
173              
174             C - I<(Qmail only)> id number of the delivery.
175              
176             =back
177              
178             B
179              
180             while(my $log = $syslog->next) {
181             # do something with $log
182             }
183              
184             =cut
185              
186             sub next {
187 913     913 1 7806051 my $self = shift;
188 913         2248 my %mail = ();
189 913         3121 my @fields = qw(host program timestamp text);
190 913         1740 my %delivery2id = (); # used to map delivery id with msg id (Qmail)
191              
192 993         4739 LINE: {
193 913         1118 my $log = $self->{syslog}->next;
194 993 100       186364 return undef unless defined $log;
195 980         7122 @mail{@fields} = @$log{@fields};
196 980         2525 my $text = $log->{text};
197              
198             # Sendmail & Postfix format parsing ------------------------------------
199 980 50       4476 if ($log->{program} =~ /^(?:sendmail|sm-mta|postfix)/) {
    0          
    0          
    0          
200 980 50       4119 redo LINE if $text =~ /^(?:NOQUEUE|STARTTLS|TLS:)/;
201 980 50       2678 redo LINE if $text =~ /prescan: (?:token too long|too many tokens|null leading token) *$/;
202 980 50       2775 redo LINE if $text =~ /possible SMTP attack/;
203              
204 980 100       6991 $text =~ s/^(\w+): *// and my $id = $1; # gather the MTA transient id
205 980 100       2761 redo LINE unless $id;
206              
207 900 50       3118 redo LINE if $text =~ /^\s*(?:[<-]--|[Mm]ilter|SYSERR)/; # we don't treat these
208              
209 900         3041 $text =~ s/^(\w+): *clone:/clone=$1/; # handle clone messages
210 900         2269 $text =~ s/stat=/status=/; # renaming 'stat' field to 'status'
211 900         1647 $text =~ s/message-id=/msgid=/; # renaming 'message-id' field to 'msgid' (Postfix)
212 900         4010 $text =~ s/^\s*([^=]+)\s*$/status=$1/; # format other status messages
213              
214             # format other status messages (2)
215 900 100       2795 if ($text =~ s/^\s*([^=]+)\s*;\s*/status=$1, /) {
216 3         56 $text =~ s/(\S+)\s+([\w-]+)=/$1, $2=/g;
217             }
218              
219 900         1944 $text =~ s/collect: /collect=/; # treat collect messages as field identifiers
220 900         13354 $text =~ s/(\S+),\s+([\w-]+)=/$1\t$2=/g; # replace fields seperator with tab character
221              
222 4495         5465 %mail = (%mail, map {
223 900         5409 s/,$//; s/^ +//; s/ +$//; # cleaning spaces
  4495         4809  
  4495         7606  
224 4495         6178 s/^\s+([\w-]+=)/$1/; # cleaning up field names
225 4495         18042 split /=/, $_, 2 # no more than 2 elements
226             } split /\t/, $text);
227              
228 900 100 66     5188 if (exists $mail{ruleset} and exists $mail{arg1}) {
229 6 50       21 $mail{ruleset} eq 'check_mail' and $mail{from} = $mail{arg1};
230 6 50       26 $mail{ruleset} eq 'check_rcpt' and $mail{to} = $mail{arg1};
231 6 50       19 $mail{ruleset} eq 'check_relay' and $mail{relay} = $mail{arg1};
232              
233 6 50       17 unless (exists $mail{status}) {
234 6 50       28 $mail{reject} and $mail{status} = "reject: $mail{reject}";
235 6 50       18 $mail{quarantine} and $mail{status} = "quarantine: $mail{quarantine}";
236             }
237             }
238              
239 900         3436 $mail{id} = $id;
240              
241             # Courier ESMTP -------------------------------------------------------
242             } elsif ($log->{program} =~ /^courier/) {
243 0 0       0 redo LINE if $text =~ /^(?:NOQUEUE|STARTTLS|TLS:)/;
244              
245 0         0 $text =~ s/,status: /,status=/; # treat status as a field
246 0         0 $text =~ s/,(\w+)=/\t$1=/g; # replace fields separator with tab character
247              
248 0         0 %mail = (%mail, map { split /=/, $_, 2 } split /\t/, $text);
  0         0  
249              
250             # Qmail format parsing -------------------------------------------------
251             } elsif ($log->{program} =~ /^qmail/) {
252 0 0       0 $text =~ s/^(\d+\.\d+) // and $mail{qmail_timestamp} = $1; # Qmail timestamp
253             # use Time::TAI64 to parse that timestamp?
254 0 0       0 redo LINE if $text =~ /^(?:status|bounce|warning)/;
255              
256             # record 'new' and 'end' events in the status
257 0 0 0     0 $text =~ s/^(new|end) msg (\d+)$//
      0        
258             and $mail{status} = "$1 message" and $mail{id} = $2 and last;
259              
260             # record 'triple bounce' events in the status
261 0 0 0     0 $text =~ s/^(triple bounce: discarding bounce)\/(\d+)$//
      0        
262             and $mail{status} = $1 and $mail{id} = $2 and last;
263              
264             # mail id and its size
265 0 0 0     0 $text =~ s/^info msg (\d+): bytes (\d+) from (<[^>]*>) //
      0        
266             and $mail{id} = $1 and $mail{size} = $2 and $mail{from} = $3;
267            
268             # begining of the delivery
269 0 0 0     0 $text =~ s/^(starting delivery (\d+)): msg (\d+) to (local|remote) (.+)$//
      0        
      0        
      0        
      0        
270             and $mail{status} = $1 and $mail{id} = $3 and $delivery2id{$2} = $3
271             and $mail{delivery_id} = $2 and $mail{delivery_type} = $4 and $mail{to} = $5;
272              
273 0 0 0     0 $text =~ s/^delivery (\d+): +//
      0        
274             and $mail{delivery_id} = $1 and $mail{id} = $delivery2id{$1} || '';
275            
276             # status of the delivery
277 0 0 0     0 $text =~ s/^(success|deferral|failure): +(\S+)//
278             and $mail{status} = "$1: $2" and $mail{status} =~ tr/_/ /;
279              
280             # in case of missing MTA transient id, generate one
281 0   0     0 $mail{id} ||= 'psm' . time;
282              
283             # Exim format parsing --------------------------------------------------
284             } elsif ($log->{program} =~ /^exim/) {
285             # format seems to be DATE TIME TID DIR ADDRESS ?
286             # where DIR is
287             # => for outgoing email, recipient follows in <>
288             # <= for incoming email
289             # == for informational message
290             # s= for ???
291             #
292             # possible errors/warnings:
293             # cancelled by system filter:
294              
295             } else {
296             redo LINE
297 0         0 }
298             }
299              
300 900         3732 return \%mail
301             }
302              
303             =back
304              
305              
306             =head1 DIAGNOSTICS
307              
308             =over 4
309              
310             =item C
311              
312             B<(F)> Occurs in C. As the message says, we were unable to create
313             a new object of the given class. The rest of the error may give more information.
314              
315             =item C
316              
317             B<(F)> You tried to call C with no argument.
318              
319             =back
320              
321             =head1 SEE ALSO
322              
323             L
324              
325             I, by Philippe Bruhat,
326             published in GNU/Linux Magazine France #92, March 2007
327              
328             =head1 TODO
329              
330             Add support for other mailer daemons (Exim, Courier, Qpsmtpd).
331             Send me logs or, even better, patches, if you want support for your
332             favorite mailer daemon.
333              
334             =head1 AUTHOR
335              
336             SEbastien Aperghis-Tramoni C<< Esebastien (at) aperghis.netE >>
337              
338             =head1 BUGS
339              
340             Please report any bugs or feature requests to
341             C, or through the web interface at
342             L.
343             I will be notified, and then you'll automatically be notified
344             of progress on your bug as I make changes.
345              
346             =head1 CAVEATS
347              
348             Most probably the same as C, see L
349              
350             =head1 COPYRIGHT & LICENSE
351              
352             Copyright 2005, 2006, 2007, 2008 SEbastien Aperghis-Tramoni, All Rights Reserved.
353              
354             This program is free software; you can redistribute it and/or modify it
355             under the same terms as Perl itself.
356              
357             =cut
358              
359             1; # End of Parse::Syslog::Mail