File Coverage

blib/lib/Mail/Procmail.pm
Criterion Covered Total %
statement 59 226 26.1
branch 6 120 5.0
condition 2 28 7.1
subroutine 14 30 46.6
pod 15 15 100.0
total 96 419 22.9


line stmt bran cond sub pod time code
1             my $RCS_Id = '$Id: Procmail.pm,v 1.24 2004-09-19 12:34:56+02 jv Exp jv $ ';
2              
3             # Author : Johan Vromans
4             # Created On : Tue Aug 8 13:53:22 2000
5             # Last Modified By: Johan Vromans
6             # Last Modified On:
7             # Update Count : 254
8             # Status : Unknown, Use with caution!
9              
10             =head1 NAME
11              
12             Mail::Procmail - Procmail-like facility for creating easy mail filters.
13              
14             =head1 SYNOPSIS
15              
16             use Mail::Procmail;
17              
18             # Set up. Log everything up to log level 3.
19             my $m_obj = pm_init ( loglevel => 3 );
20              
21             # Pre-fetch some interesting headers.
22             my $m_from = pm_gethdr("from");
23             my $m_to = pm_gethdr("to");
24             my $m_subject = pm_gethdr("subject");
25              
26             # Default mailbox.
27             my $default = "/var/spool/mail/".getpwuid($>);
28              
29             pm_log(1, "Mail from $m_from");
30              
31             pm_ignore("Non-ASCII in subject")
32             if $m_subject =~ /[\232-\355]{3}/;
33              
34             pm_resend("jojan")
35             if $m_to =~ /jjk@/i;
36              
37             # Make sure I see these.
38             pm_deliver($default, continue => 1)
39             if $m_subject =~ /getopt(ions|(-|::)?long)/i;
40              
41             # And so on ...
42              
43             # Final delivery.
44             pm_deliver($default);
45              
46             =head1 DESCRIPTION
47              
48             F is a great mail filter program, but it has weird recipe
49             format. It's pattern matching capabilities are basic and often
50             insufficient. I wanted something flexible whereby I could filter my
51             mail using the power of Perl.
52              
53             I've been considering to write a procmail replacement in Perl for a
54             while, but it was Simon Cozen's C module, and his article
55             in The Perl Journal #18, that set it off.
56              
57             I first started using Simon's great module, and then decided to write
58             my own since I liked certain things to be done differently. And I
59             couldn't wait for his updates.
60              
61             C allows a piece of email to be logged, examined,
62             delivered into a mailbox, filtered, resent elsewhere, rejected, and so
63             on. It is designed to allow you to easily create filter programs to
64             stick in a F<.forward> or F<.procmailrc> file, or similar.
65              
66             =head1 DIFFERENCES WITH MAIL::AUDIT
67              
68             Note that several changes are due to personal preferences and do not
69             necessarily imply deficiencies in C.
70              
71             =over
72              
73             =item General
74              
75             Not object oriented. Procmail functionality typically involves one
76             single message. All (relevant) functions are exported.
77              
78             =item Delivery
79              
80             Each of the delivery methods is able to continue (except
81             I and I).
82              
83             Each of the delivery methods is able to pretend they did it
84             (for testing a new filter).
85              
86             No default file argument for mailbox delivery, since this is system
87             dependent.
88              
89             Each of the delivery methods logs the line number in the calling
90             program so one can deduce which 'rule' caused the delivery.
91              
92             Message IDs can be checked to suppress duplicate messages.
93              
94             System commands can be executed for their side-effects.
95              
96             I logs a reason as well.
97              
98             I will fake a "No such user" status to the mail transfer agent.
99              
100             =item Logging
101              
102             The logger function is exported as well. Logging is possible to
103             a named file, STDOUT or STDERR.
104              
105             Since several deliveries can take place in parallel, logging is
106             protected against concurrent access, and a timestamp/pid is included
107             in log messages.
108              
109             A log reporting tool is included.
110              
111             =item Robustness
112              
113             Exit with TEMPFAIL instead of die in case of problems.
114              
115             I ignores SIGPIPE.
116              
117             I returns the command exit status if continuation is selected.
118              
119             Commands and pipes can be protected against concurrent access using
120             lockfiles.
121              
122             =back
123              
124             =head1 EXPORTED ROUTINES
125              
126             Note that most delivery routines exit the program unless the attribute
127             "continue=>1" is passed.
128              
129             Also, the delivery routines log the line number in the calling program
130             so it is easy to find out which 'rule' caused a specific delivery to
131             take place.
132              
133             =cut
134              
135             ################ Common stuff ################
136              
137             package Mail::Procmail;
138              
139             $VERSION = "1.08";
140              
141 1     1   949 use strict;
  1         2  
  1         39  
142 1     1   29 use 5.005;
  1         4  
  1         39  
143 1     1   14 use vars qw(@ISA @EXPORT $pm_hostname);
  1         2  
  1         105  
144              
145             my $verbose = 0; # verbose processing
146             my $debug = 0; # debugging
147             my $trace = 0; # trace (show process)
148             my $test = 0; # test mode.
149              
150             my $logfile; # log file
151             my $loglevel; # log level
152              
153 1     1   6 use Fcntl qw(:DEFAULT :flock);
  1         2  
  1         575  
154              
155 1     1   6 use constant REJECTED => 67; # fake "no such user"
  1         16  
  1         65  
156 1     1   5 use constant TEMPFAIL => 75;
  1         2  
  1         49  
157 1     1   5 use constant DELIVERED => 0;
  1         2  
  1         41  
158              
159 1     1   1041 use Sys::Hostname;
  1         1634  
  1         105  
160             $pm_hostname = hostname;
161              
162             require Exporter;
163              
164             @ISA = qw(Exporter);
165             @EXPORT = qw(
166             pm_init
167             pm_gethdr
168             pm_gethdr_raw
169             pm_body
170             pm_deliver
171             pm_reject
172             pm_resend
173             pm_pipe_to
174             pm_command
175             pm_ignore
176             pm_dupcheck
177             pm_lockfile
178             pm_unlockfile
179             pm_log
180             pm_report
181             $pm_hostname
182             );
183              
184             ################ The Process ################
185              
186 1     1   1117 use Mail::Internet;
  1         17816  
  1         39  
187 1     1   1031 use LockFile::Simple;
  1         6177  
  1         53  
188              
189 1     1   8 use Carp;
  1         3  
  1         3520  
190              
191             my $m_obj; # the Mail::Internet object
192             my $m_head; # its Mail::Header object
193              
194             =head2 pm_init
195              
196             This routine performs the basic initialisation. It must be called once.
197              
198             Example:
199              
200             pm_init (logfile => "my.log", loglevel => 3, test => 1);
201              
202             Attributes:
203              
204             =over
205              
206             =item *
207              
208             fh
209              
210             An open file handle to read the message from. Defaults to STDIN.
211              
212             =item *
213              
214             logfile
215              
216             The name of a file to log messages to. Each message will have a timestamp
217             attached.
218              
219             The attribute may be 'STDOUT' or 'STDERR' to achieve logging to
220             standard output or error respectively.
221              
222             =item *
223              
224             loglevel
225              
226             The amount of information that will be logged.
227              
228             =item *
229              
230             test
231              
232             If true, no actual delivery will be done. Suitable to test a new setup.
233             Note that file locks are done, so lockfiles may be created and deleted.
234              
235             =item *
236              
237             debug
238              
239             Provide some debugging info.
240              
241             =item *
242              
243             trace
244              
245             Provide some tracing info, eventually.
246              
247             =item *
248              
249             verbose
250              
251             Produce verbose information, eventually.
252              
253             =back
254              
255             =cut
256              
257             sub pm_init {
258              
259 1     1 1 934 my %atts = (
260             logfile => '',
261             loglevel => 0,
262             fh => undef,
263             verbose => 0,
264             trace => 0,
265             debug => 0,
266             test => 0,
267             @_);
268 1         4 $debug = delete $atts{debug};
269 1         2 $trace = delete $atts{trace};
270 1         3 $test = delete $atts{test};
271 1         2 $verbose = delete $atts{verbose};
272 1         3 $logfile = delete $atts{logfile};
273 1         2 $loglevel = delete $atts{loglevel};
274 1   50     7 my $fh = delete $atts{fh} || \*STDIN;
275              
276 1   33     8 $trace |= ($debug || $test);
277              
278 1 50       4 croak("Unprocessed attributes: ".join(" ",sort keys %atts))
279             if %atts;
280              
281 1         10 $m_obj = Mail::Internet->new($fh);
282 1         979 $m_head = $m_obj->head; # Mail::Header
283              
284 1         6 $m_obj;
285             }
286              
287             =head2 pm_gethdr
288              
289             This routine fetches the contents of a header. The result will have
290             excess whitepace tidied up.
291              
292             The header is reported using warn() if the debug attribute was passed
293             (with a true value) to pm_init();
294              
295             Example:
296              
297             $m_rcvd = pm_gethdr("received"); # get first (or only) Received: header
298             $m_rcvd = pm_gethdr("received",2); # get 3rd Received: header
299             @m_rcvd = pm_gethdr("received"); # get all Received: headers
300              
301             =cut
302              
303             sub pm_gethdr {
304 3     3 1 328 my ($hdr, $ix) = @_;
305 3         4 my @ret;
306 3         13 foreach my $val ( $m_head->get($hdr, $ix) ) {
307 3 50       92 last unless defined $val;
308 3         6 for ( $val ) {
309 3         8 s/^\s+//;
310 3         14 s/\s+$//;
311 3         17 s/\s+/ /g;
312 3         10 s/[\r\n]+$//;
313             }
314 3 50       8 if ( $debug ) {
315 0         0 $hdr =~ s/-(.)/"-".ucfirst($1)/ge;
  0         0  
316 0         0 warn (ucfirst($hdr), ": ", $val, "\n");
317             }
318 3 50       14 return $val unless wantarray;
319 0         0 push (@ret, $val);
320             }
321 0 0       0 wantarray ? @ret : '';
322             }
323              
324             =head2 pm_gethdr_raw
325              
326             Like pm_gethdr, but without whitespace cleanup.
327              
328             =cut
329              
330             sub pm_gethdr_raw {
331 0     0 1 0 my ($hdr, $ix) = @_;
332 0         0 my @ret;
333 0         0 foreach my $val ( $m_head->get($hdr, $ix) ) {
334 0 0       0 last unless defined $val;
335 0 0       0 if ( $debug ) {
336 0         0 $hdr =~ s/-(.)/"-".ucfirst($1)/ge;
  0         0  
337 0         0 warn (ucfirst($hdr), ": ", $val, "\n");
338             }
339 0 0       0 return $val unless wantarray;
340 0         0 push (@ret, $val);
341             }
342 0 0       0 wantarray ? @ret : '';
343             }
344              
345             =head2 pm_body
346              
347             This routine fetches the body of a message, as a reference to an array
348             of lines.
349              
350             Example:
351              
352             $body = pm_body(); # ref of lines
353             $body = join("", @{pm_body()}); # as one string
354              
355             =cut
356              
357             sub pm_body {
358 0     0 1 0 $m_obj->body;
359             }
360              
361             =head2 pm_deliver
362              
363             This routine performs delivery to a Unix style mbox file, or maildir.
364              
365             In case of an mbox file, the file is locked first by acquiring
366             exclusive access. Note that older style locking, with a lockfile with
367             C<.lock> extension, is I supported.
368              
369             Example:
370              
371             pm_deliver("/var/spool/mail/".getpwuid($>));
372              
373             Attributes:
374              
375             =over
376              
377             =item *
378              
379             continue
380              
381             If true, processing will continue after delivery. Otherwise the
382             program will exit with a DELIVERED status.
383              
384             =back
385              
386             =cut
387              
388             sub _pm_msg_size {
389 0   0 0   0 length($m_obj->head->as_string || '') + length(join("", @{$m_obj->body}));
  0         0  
390             }
391              
392             sub pm_deliver {
393 0     0 1 0 my ($target, %atts) = @_;
394 0         0 my $line = (caller(0))[2];
395 0         0 pm_log(2, "deliver[$line]: $target "._pm_msg_size());
396              
397             # Is it a Maildir?
398 0 0 0     0 if ( -d "$target/tmp" && -d "$target/new" ) {
399 0         0 my $msg_file = "/${\time}.$$.$pm_hostname";
  0         0  
400 0         0 my $tmp_path = "$target/tmp/$msg_file";
401 0         0 my $new_path = "$target/new/$msg_file";
402 0         0 pm_log(3,"Looks like maildir, writing to $new_path");
403              
404             # since mutt won't add a lines tag to maildir messages,
405             # we'll add it here
406 0 0       0 unless ( pm_gethdr("lines") ) {
407 0         0 my $body = $m_obj->body;
408 0         0 my $num_lines = @$body;
409 0         0 $m_head->add("Lines", $num_lines);
410 0         0 pm_log(4,"Adding Lines: $num_lines header");
411             }
412 0         0 my $tmp = _new_fh();
413 0 0       0 unless (open ($tmp, ">$tmp_path") ) {
414 0         0 pm_log(0,"Couldn't open $tmp_path! $!");
415 0         0 exit TEMPFAIL;
416             }
417 0         0 print $tmp ($m_obj->as_mbox_string);
418 0         0 close($tmp);
419              
420 0 0       0 unless ( $test ) {
421 0 0       0 unless (link($tmp_path, $new_path) ) {
422 0         0 pm_log(0,"Couldn't link $tmp_path to $new_path : $!");
423 0         0 exit TEMPFAIL;
424             }
425             }
426 0 0       0 unlink($tmp_path) or pm_log(1,"Couldn't unlink $tmp_path: $!");
427             }
428             else {
429             # It's an mbox, I hope.
430 0         0 my $fh = _new_fh();
431 0 0       0 unless (open($fh, ">>$target")) {
432 0         0 pm_log(0,"Couldn't open $target! $!");
433 0         0 exit TEMPFAIL;
434             }
435 0 0       0 flock($fh, LOCK_EX)
436             or pm_log(1,"Couldn't get exclusive lock on $target");
437 0         0 seek($fh, 0, 2); # make sure we're still at the end
438 0 0       0 print $fh ($m_obj->as_mbox_string) unless $test;
439 0 0       0 flock($fh, LOCK_UN)
440             or pm_log(1,"Couldn't unlock on $target");
441 0         0 close($fh);
442             }
443 0 0       0 exit DELIVERED unless $atts{continue};
444             }
445              
446              
447             =head2 pm_pipe_to
448              
449             This routine performs delivery to a command via a pipe.
450              
451             Return the command exit status if the continue attribute is supplied.
452             If execution is skipped due to test mode, the return value will be 0.
453             See also attribute C below.
454              
455             If the name of a lockfile is supplied, multiple deliveries are throttled.
456              
457             Example:
458              
459             pm_pipe_to("my_filter", lockfile => "/tmp/pm.lock");
460              
461             Attributes:
462              
463             =over
464              
465             =item *
466              
467             lockfile
468              
469             The name of a file that is used to guard against multiple deliveries.
470             The program will try to exclusively create this file before proceding.
471             Upon completion, the lock file will be removed.
472              
473             =item *
474              
475             continue
476              
477             If true, processing will continue after delivery. Otherwise the
478             program will exit with a DELIVERED status, I
479             failed>.
480              
481             =item *
482              
483             testalso
484              
485             Do this, even in test mode.
486              
487             =back
488              
489             =cut
490              
491             sub pm_pipe_to {
492 0     0 1 0 my ($target, %atts) = @_;
493 0         0 my $line = (caller(0))[2];
494 0         0 pm_log(2, "pipe_to[$line]: $target "._pm_msg_size());
495              
496 0         0 my $lock;
497 0         0 my $lockfile = $atts{lockfile};
498 0 0       0 $lock = pm_lockfile($lockfile) if $lockfile;
499 0         0 local ($SIG{PIPE}) = 'IGNORE';
500 0         0 my $ret = 0;
501 0 0 0     0 eval {
502 0         0 $ret = undef;
503 0         0 my $pipe = _new_fh();
504 0 0 0     0 open ($pipe, "|".$target)
505             && $m_obj->print($pipe)
506             && close ($pipe);
507 0         0 $ret = $?;
508             } unless $test && !$atts{testalso};
509              
510 0         0 pm_unlockfile($lock);
511 0 0       0 $ret = 0 if $ret < 0; # broken pipe
512 0 0 0     0 pm_log (2, "pipe_to[$line]: command result = ".
    0          
    0          
    0          
513             (defined $ret ? sprintf("0x%x", $ret) : "undef").
514             ($! ? ", \$! = $!" : "").
515             ($@ ? ", \$@ = $@" : ""))
516             unless defined $ret && $ret == 0;
517 0 0       0 return $ret if $atts{continue};
518 0         0 exit DELIVERED;
519             }
520              
521             =head2 pm_command
522              
523             Executes a system command for its side effects.
524              
525             If the name of a lockfile is supplied, multiple executes are
526             throttled. This would be required if the command manipulates external
527             data in an otherwise unprotected manner.
528              
529             Example:
530              
531             pm_command("grep foo some.dat > /tmp/pm.dat",
532             lockfile => "/tmp/pm.dat.lock");
533              
534             Attributes:
535              
536             =over
537              
538             =item *
539              
540             lockfile
541              
542             The name of a file that is used to guard against multiple executions.
543             The program will try to exclusively create this file before proceding.
544             Upon completion, the lock file will be removed.
545              
546             testalso
547              
548             Do this, even in test mode.
549              
550             =back
551              
552             =cut
553              
554             sub pm_command {
555 0     0 1 0 my ($target, %atts) = @_;
556 0         0 my $line = (caller(0))[2];
557 0         0 pm_log(2, "command[$line]: $target "._pm_msg_size());
558              
559 0         0 my $lock;
560 0         0 my $lockfile = $atts{lockfile};
561 0 0       0 $lock = pm_lockfile($lockfile) if $lockfile;
562 0         0 my $ret = 0;
563 0 0       0 $ret = system($target) unless $atts{testalso};
564 0         0 pm_unlockfile($lock);
565 0 0 0     0 pm_log (2, "command[$line]: command result = ".
    0          
566             (defined $ret ? sprintf("0x%x", $ret) : "undef"))
567             unless defined $ret && $ret == 0;
568 0         0 $ret;
569             }
570              
571             =head2 pm_resend
572              
573             Send this message through to some other user.
574              
575             Example:
576              
577             pm_resend("root");
578              
579             Attributes:
580              
581             =over
582              
583             =item *
584              
585             continue
586              
587             If true, processing will continue after delivery. Otherwise the
588             program will exit with a DELIVERED status.
589              
590             =back
591              
592             =cut
593              
594             sub pm_resend {
595 0     0 1 0 my ($target, %atts) = @_;
596 0         0 my $line = (caller(0))[2];
597 0         0 pm_log(2, "resend[$line]: $target "._pm_msg_size());
598 0 0       0 $m_obj->smtpsend(To => $target) unless $test;
599 0 0       0 exit DELIVERED unless $atts{continue};
600             }
601              
602             =head2 pm_reject
603              
604             Reject a message. The sender will get a mail back with the reason for
605             the rejection (unless stderr has been redirected).
606              
607             Example:
608              
609             pm_reject("Non-existent address");
610              
611             =cut
612              
613             sub pm_reject {
614 0     0 1 0 my $reason = shift;
615 0         0 my $line = (caller(0))[2];
616 0         0 pm_log(2, "reject[$line]: $reason "._pm_msg_size());
617 0 0       0 print STDERR ($reason, "\n") unless lc $logfile eq 'stderr';
618 0         0 exit REJECTED;
619             }
620              
621              
622             =head2 pm_ignore
623              
624             Ignore a message. The program will do nothing and just exit with a
625             DELIVERED status. A descriptive text may be passed to log the reason
626             for ignoring.
627              
628             Example:
629              
630             pm_ignore("Another make money fast message");
631              
632             =cut
633              
634             sub pm_ignore {
635 0     0 1 0 my $reason = shift;
636 0         0 my $line = (caller(0))[2];
637 0         0 pm_log(2, "ignore[$line]: $reason "._pm_msg_size());
638 0         0 exit DELIVERED;
639             }
640              
641             =head2 pm_dupcheck
642              
643             Check for duplicate messages. Reject the message if its message ID has
644             already been received.
645              
646             Example:
647              
648             pm_dupcheck(scalar(pm_gethdr("message-id")));
649              
650             Attributes:
651              
652             =over
653              
654             =item *
655              
656             dbm
657              
658             The name of a DBM file (created if necessary) to store the message IDs.
659             The default name is C<.msgids> in the HOME directory.
660              
661             =item *
662              
663             retain
664              
665             The amount of time, in days, that subsequent identical message IDs are
666             considered duplicates. Each new occurrence will refresh the time stamp.
667             The default value is 14 days.
668              
669             =item *
670              
671             continue
672              
673             If true, the routine will return true or false depending on the
674             message ID being duplicate. Otherwise, if it was duplicate, the
675             program will exit with a DELIVERED status.
676              
677             =back
678              
679             I
680             unlimited. A separate tool will be supplied to expire old message IDs.>
681              
682             =cut
683              
684             sub pm_dupcheck {
685 0     0 1 0 my ($msgid) = shift;
686 0         0 my (%atts) = (dbm => $ENV{HOME}."/.msgids",
687             retain => 14,
688             @_);
689 0         0 my $dbm = $atts{dbm};
690              
691 0         0 my %msgid;
692 0         0 my $dup = 0;
693 0 0       0 if ( dbmopen(%msgid, $dbm, 0660) ) {
694 0         0 my $tmp;
695 0 0       0 if ( defined($tmp = $msgid{$msgid}) ) {
696 0 0       0 if ( ($msgid{$msgid} = time) - $tmp < $atts{retain}*24*60*60 ) {
697 0         0 my $line = (caller(0))[2];
698 0         0 pm_log(2, "dup[$line]: $msgid "._pm_msg_size());
699 0         0 $dup++;
700             }
701             }
702             else {
703 0         0 $msgid{$msgid} = time;
704             }
705 0 0       0 dbmclose(%msgid)
706             or pm_log(0, "Error closing $dbm: $!");
707             }
708             else {
709 0         0 pm_log(0, "Error opening $dbm: $!");
710             }
711 0 0 0     0 exit DELIVERED
712             if $dup && !$atts{continue};
713 0         0 $dup;
714             }
715              
716             =head2 pm_lockfile
717              
718             The program will try to get an exclusive lock using this file.
719              
720             Example:
721              
722             $lock_id = pm_lockfile("my.mailbox.lock");
723              
724             The lock id is returned, or undef on failure.
725              
726             =cut
727              
728             my $lockmgr;
729             sub pm_lockfile {
730 0     0 1 0 my ($file) = @_;
731              
732             $lockmgr = LockFile::Simple->make(-hold => 600, -stale => 1,
733             -autoclean => 1,
734 0     0   0 -wfunc => sub { pm_log(2,@_) },
735 0     0   0 -efunc => sub { pm_log(0,@_) },
736             )
737 0 0       0 unless $lockmgr;
738              
739 0         0 $lockmgr->lock($file, "%f");
740             }
741              
742             =head2 pm_unlockfile
743              
744             Unlocks a lock acquired earlier using pm_lockfile().
745              
746             Example:
747              
748             pm_unlockfile($lock_id);
749              
750             If unlocking succeeds, the lock file is removed.
751              
752             =cut
753              
754             sub pm_unlockfile {
755 0 0   0 1 0 shift->release if $_[0];
756             }
757              
758             =head2 pm_log
759              
760             Logging facility. If pm_init() was supplied the name of a log file,
761             this file will be opened, created if necessary. Every log message
762             written will get a timestamp attached. The log level (first argument)
763             must be less than or equal to the loglevel attribute used with
764             pm_init(). If not, this message will be skipped.
765              
766             Example:
767              
768             pm_log(2,"Retrying");
769              
770             =cut
771              
772             my $logfh;
773             sub pm_log {
774 3 50   3 1 73 return unless $logfile;
775 3 50       12 return if shift > $loglevel;
776              
777             # Use sysopen/syswrite for atomicity.
778 0 0         unless ( $logfh ) {
779 0           $logfh = _new_fh();
780 0 0         print STDERR ("Opening logfile $logfile\n") if $debug;
781 0 0 0       if ( lc($logfile) eq "stderr" ) {
    0          
782 0           open ($logfh, ">&STDERR");
783             }
784             elsif ( lc($logfile) eq "stdout" || $logfile eq "-" ) {
785 0           open ($logfh, ">&STDOUT");
786             }
787             else {
788 0 0         sysopen ($logfh, $logfile, O_WRONLY|O_CREAT|O_APPEND)
789             || print STDERR ("$logfile: $!\n");
790             }
791             }
792 0           my @tm = localtime;
793 0           my $msg = sprintf ("%04d%02d%02d%02d%02d%02d.%05d %s\n",
794             $tm[5]+1900, $tm[4]+1, $tm[3], $tm[2], $tm[1], $tm[0],
795             $$, "@_");
796 0 0         print STDERR ($msg) if $debug;
797 0           syswrite ($logfh, $msg);
798             }
799              
800             sub _new_fh {
801 0 0   0     return if $] >= 5.006; # 5.6 will take care itself
802 0           require IO::File;
803 0           IO::File->new();
804             }
805              
806             ################ Reporting ################
807              
808             =head2 pm_report
809              
810             pm_report() produces a summary report from log files from
811             Mail::Procmail applications.
812              
813             Example:
814              
815             pm_report(logfile => "pmlog");
816              
817             The report shows the deliveries, and the rules that caused the
818             deliveries. For example:
819              
820             393 393 deliver[203] /home/jv/Mail/perl5-porters.spool
821             370 370 deliver[203] /home/jv/Mail/perl6-language.spool
822             174 174 deliver[203] /home/jv/Mail/perl6-internals.spool
823             160 81 deliver[311] /var/spool/mail/jv
824             46 deliver[337]
825             23 deliver[363]
826             10 deliver[165]
827              
828             The first column is the total number of deliveries for this target.
829             The second column is the number of deliveries triggered by the
830             indicated rule. If more rules apply to a target, this line is followed
831             by additional lines with an empty first and last column.
832              
833             Attributes:
834              
835             =over
836              
837             =item *
838              
839             logfile
840              
841             The name of the logfile to process.
842              
843             =back
844              
845             If no logfile attribute is passed, pm_report() reads all files
846             supplied on the command line. This makes it straighforward to run from
847             the command line:
848              
849             $ perl -MMail::Procmail -e 'pm_report()' syslog/pm_logs/*
850              
851             =cut
852              
853             sub pm_report {
854              
855 0     0 1   my (%atts) = @_;
856 0           my $logfile = delete($atts{logfile});
857              
858 0 0         local (@ARGV) = $logfile ? ($logfile) : @ARGV;
859              
860 0           my %tally; # master array with data
861 0           my $max1 = 0; # max. delivery
862 0           my $max2 = 0; # max. delivery / rule
863 0           my $max3 = 0; # max length of rules
864 0           my $recs = 0; # records in file
865 0           my $msgs = 0; # messages
866 0           my $dlvr = 0; # deliveries
867              
868 0           while ( <> ) {
869 0           $recs++;
870              
871             # Tally number of incoming messages.
872 0 0         $msgs++, next if /^\d+\.\d+ Mail from/;
873              
874             # Skip non-deliveries.
875 0 0         next unless /^\d+\.\d+ (\w+\[[^\]]+\]):\s+(.+)/;
876 0           $dlvr++;
877              
878             # Update stats and keep track of max values.
879 0           my $t;
880 0 0         $max1 = $t if ($t = ++$tally{$2}->[0]) > $max1;
881 0 0         $max2 = $t if ($t = ++$tally{$2}->[1]->{$1}) > $max2;
882 0 0         $max3 = $t if ($t = length($1)) > $max3;
883             }
884              
885 0           print STDOUT ("$recs records, $msgs messages, $dlvr deliveries.\n\n");
886              
887             # Construct format for report.
888 0           $max1 = length($max1);
889 0           $max2 = length($max2);
890 0           my $fmt = "%${max1}s %${max2}s %-${max3}s %s\n";
891              
892             # Sort on number of deliveries per target.
893 0           foreach my $dest ( sort { $b->[1] <=> $a->[1] }
  0            
  0            
894             map { [ $_, $tally{$_}->[0], $tally{$_}->[1] ] }
895             keys %tally ) {
896 0           my $first = 1;
897             # Sort on deliveries per rule.
898 0           foreach my $rule ( sort { $b->[1] <=> $a->[1] }
  0            
  0            
899 0           map { [ $_, $dest->[2]->{$_} ] }
900             keys %{$dest->[2]} ) {
901 0 0         printf STDOUT ($fmt,
    0          
902             ($first ? $dest->[1] : ""),
903             $rule->[1],
904             $rule->[0],
905             ($first ? $dest->[0] : ""));
906 0           $first = 0;
907             }
908             }
909              
910             }
911              
912             =head1 USING WITH PROCMAIL
913              
914             The following lines at the start of .procmailrc will cause a copy of
915             each incoming message to be saved in $HOME/syslog/mail, after which
916             the procmail-pl is run as a TRAP program (see the procmailrc
917             documentation). As a result, procmail will transfer the exit status of
918             procmail-pl to the mail transfer agent that invoked procmail (e.g.,
919             sendmail, or postfix).
920              
921             LOGFILE=$HOME/syslog/procmail
922             VERBOSE=off
923             LOGABSTRACT=off
924             EXITCODE=
925             TRAP=$HOME/bin/procmail-pl
926              
927             :0:
928             $HOME/syslog/mail
929              
930             B: procmail seems to have problems when $HOME/syslog/mail
931             gets too big (over 50Mb). If you want to maintain a huge archive, you
932             can specify excess extents, like this:
933              
934             :0:
935             $HOME/syslog/mail-ext1
936              
937             :0:
938             $HOME/syslog/mail-ext2
939              
940             =head1 EXAMPLE
941              
942             An extensive example can be found in the examples directory of the
943             C kit.
944              
945             =head1 SEE ALSO
946              
947             L
948              
949             L
950              
951             procmail documentation.
952              
953             =head1 AUTHOR
954              
955             Johan Vromans, Squirrel Consultancy
956              
957             Some parts are shamelessly stolen from Mail::Audit by Simon Cozens
958             , who admitted that he stole most of it from programs
959             by Tom Christiansen.
960              
961             =head1 COPYRIGHT and DISCLAIMER
962              
963             This program is Copyright 2000,2004 by Squirrel Consultancy. All
964             rights reserved.
965              
966             This program is free software; you can redistribute it and/or modify
967             it under the terms of either: a) the GNU General Public License as
968             published by the Free Software Foundation; either version 1, or (at
969             your option) any later version, or b) the "Artistic License" which
970             comes with Perl.
971              
972             This program is distributed in the hope that it will be useful, but
973             WITHOUT ANY WARRANTY; without even the implied warranty of
974             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the
975             GNU General Public License or the Artistic License for more details.
976              
977             =cut
978              
979             1;
980              
981             # Local Variables:
982             # compile-command: "perl -wc -Mlib=$HOME/lib/perl5 Procmail.pm && install -m 0555 Procmail.pm $HOME/lib/perl5/Mail/Procmail.pm"
983             # End: