#-*- perl -*- # # Copyright (C) 2002,2003,2004,2005,2006 Ken'ichi Fukamachi # All rights reserved. # # $FML: Error.pm,v 1.56 2006/02/15 13:44:04 fukachan Exp $ # package FML::Process::Error; use vars qw($debug @ISA @EXPORT @EXPORT_OK); use strict; use Carp; use FML::Config; use FML::Process::Kernel; @ISA = qw(FML::Process::Kernel); =head1 NAME FML::Process::Error -- error analyzer dispacher. =head1 SYNOPSIS use FML::Process::Error; ... See L for details of fml process flow. =head1 DESCRIPTION C checks incoming message (typically error message), update bounced address database and delete address which causes too many errors. =head1 METHODS =head2 new($args) make fml process object, which inherits C. =cut # Descriptions: constructor. # Arguments: OBJ($self) HASH_REF($args) # Side Effects: inherit FML::Process::Kernel # Return Value: OBJ sub new { my ($self, $args) = @_; my $type = ref($self) || $self; my $curproc = new FML::Process::Kernel $args; return bless $curproc, $type; } =head2 prepare($args) parse argv, load config files and fix @INC. if $use_error_mail_analyzer_function, parse incoming message. =cut # Descriptions: check if $use_error_mail_analyzer_function value is yes. # parse incoming message if this process runs. # Arguments: OBJ($curproc) HASH_REF($args) # Side Effects: none # Return Value: none sub prepare { my ($curproc, $args) = @_; my $config = $curproc->config(); my $eval = $config->get_hook( 'error_mail_analyzer_prepare_start_hook' ); if ($eval) { eval qq{ $eval; }; $curproc->logwarn($@) if $@; } $curproc->ml_variables_resolve(); $curproc->config_cf_files_load(); $curproc->env_fix_perl_include_path(); $curproc->scheduler_init(); $curproc->log_message_init(); if ($config->yes('use_error_mail_analyzer_function')) { $curproc->incoming_message_parse(); } else { exit(0); } $eval = $config->get_hook( 'error_mail_analyzer_prepare_end_hook' ); if ($eval) { eval qq{ $eval; }; $curproc->logwarn($@) if $@; } } =head2 verify_request($args) set up maintainer. =cut # Descriptions: set up maintainer. # Arguments: OBJ($curproc) HASH_REF($args) # Side Effects: none # Return Value: 1 or 0 sub verify_request { my ($curproc, $args) = @_; my $config = $curproc->config(); my $maintainer = $config->{ maintainer } || ''; my $eval; $eval = $config->get_hook('error_mail_analyzer_verify_request_start_hook'); if ($eval) { eval qq{ $eval; }; $curproc->logwarn($@) if $@; } # set dummy sender to avoid unexpected error if ($maintainer) { my $cred = $curproc->credential(); $cred->set( 'sender', $maintainer ); } else { $curproc->logerror("maintainer not defined."); } $eval = $config->get_hook( 'error_mail_analyzer_verify_request_end_hook' ); if ($eval) { eval qq{ $eval; }; $curproc->logwarn($@) if $@; } } =head2 run($args) dispatcher to run correspondig C for C. Standard style follows: lock execute FML::Error::command unlock XXX Each command determines need of lock or not. =cut # Descriptions: analyze error mails and remove error addresses. # Arguments: OBJ($curproc) HASH_REF($args) # Side Effects: update cache and member lists if needed. # Return Value: none sub run { my ($curproc, $args) = @_; my $config = $curproc->config(); my $found = 0; my $pcb = $curproc->pcb(); my $msg = $curproc->incoming_message(); my $eval = $config->get_hook( 'error_mail_analyzer_run_start_hook' ); if ($eval) { eval qq{ $eval; }; $curproc->logwarn($@) if $@; } if ($curproc->_is_qmail_extension_command()) { $curproc->_execute_qmail_extension_command(); $pcb->set("qmail-ext", "found", 1); goto END; # no more normal error handling process. } $curproc->_forward_error_message(); unless ($curproc->is_refused()) { eval q{ use Mail::Bounce; my $bouncer = new Mail::Bounce; $bouncer->analyze( $msg ); use FML::Error; my $error = new FML::Error $curproc; $error->db_open(); for my $address ( $bouncer->address_list ) { my $status = $bouncer->status( $address ); my $reason = $bouncer->reason( $address ); if ($address) { $curproc->log("bounced: address=<$address>"); $curproc->log("bounced: status=$status"); $curproc->log("bounced: reason=\"$reason\""); $error->add({ address => $address, status => $status, reason => $reason, }); $found++; } } $error->db_close(); }; $curproc->logerror($@) if $@; if ($found) { $pcb->set("error", "found", 1); $curproc->_cleanup_bouncers(); } } END: $eval = $config->get_hook( 'error_mail_analyzer_run_end_hook' ); if ($eval) { eval qq{ $eval; }; $curproc->logwarn($@) if $@; } } # Descriptions: run analyzer() if long time spent after the last # analyze. # Arguments: OBJ($curproc) # Side Effects: remove addresses which causes bounces # Return Value: none sub _cleanup_bouncers { my ($curproc) = @_; my $channel = 'error_mail_analyzer'; if ($curproc->is_event_timeout($channel)) { $curproc->logdebug("event timeout"); eval q{ use FML::Error; my $error = new FML::Error $curproc; $error->analyze(); $error->delete_bouncers(); }; $curproc->logerror($@) if $@; # XXX-TODO: 3600 customizable. $curproc->event_set_timeout($channel, time + 3600); } else { $curproc->logdebug("event not timeout"); } } =head2 help() show help. =cut # Descriptions: show help. # Arguments: none # Side Effects: none # Return Value: none sub help { print <<"_EOF_"; Usage: $0 \$ml_home_prefix/\$ml_name [options] For example, process command of elena ML $0 /var/spool/ml/elena _EOF_ } =head2 finish($args) $curproc->reply_message_inform(); =cut # Descriptions: finalize command process. # reply messages, command results et. al. # Arguments: OBJ($curproc) HASH_REF($args) # Side Effects: queue manipulation # Return Value: none sub finish { my ($curproc, $args) = @_; my $config = $curproc->config(); my $pcb = $curproc->pcb(); my $eval = $config->get_hook( 'error_mail_analyzer_finish_start_hook' ); if ($eval) { eval qq{ $eval; }; $curproc->logwarn($@) if $@; } # XXX NOT INFORM ANY RESULTS BUT ONLY LOG IT TO AVOID LOOP. if ($pcb->get("error", "found")) { $curproc->log("error message found"); } elsif ($pcb->get("qmail-ext", "found")) { $curproc->logdebug("qmail-ext found"); } else { $curproc->logwarn("error message not found"); } $curproc->reply_message_inform(); $curproc->queue_flush(); $eval = $config->get_hook( 'error_mail_analyzer_finish_end_hook' ); if ($eval) { eval qq{ $eval; }; $curproc->logwarn($@) if $@; } } =head1 message forwarding forward the error message. =cut # Descriptions: forward the error message. # Arguments: OBJ($curproc) # Side Effects: update reply message queue # Return Value: none sub _forward_error_message { my ($curproc) = @_; my $config = $curproc->config(); my $fml_owner = $curproc->fml_owner_address(); my $maintainer = $config->{ maintainer } || $fml_owner; my $maps = $config->{ maintainer_recipient_maps } || ''; my $msg = $curproc->incoming_message(); if ($maps) { # override Reply-To: and Errors-To: header fields. $config->{ outgoing_mail_header_errors_to } = $fml_owner; $config->{ outgoing_mail_header_reply_to } = $fml_owner; my $maps = $config->get_as_array_ref('maintainer_recipient_maps'); my $msg_args = { smtp_sender => $fml_owner, recipient_maps => $maps, header => { sender => $fml_owner, from => $fml_owner, to => $maintainer, }, }; $curproc->reply_message($msg, $msg_args); } } =head1 QMAIL EXTENSION COMMAND TRAP In qmail environment, you can use the following address as a command: for subscribe request for elena@domain ML. To emulate this special command mail request, libexec/error need to check EXT environment variable and pass the control to FML::Process::Command if needed. =cut # Descriptions: we receive qmail extension command or not ? # Arguments: OBJ($curproc) # Side Effects: none # Return Value: NUM sub _is_qmail_extension_command { my ($curproc) = @_; # environment variable EXT holds extention information. # for example, a mail to ML-subscribe@VIRTUAL.DOMAIN is recognized as # extension with "VIRTUAL.DOMAIN-ML-subscribe" in EXT variable. use FML::Command::QmailExt; my $extension = new FML::Command::QmailExt $curproc; return $extension->match($ENV{EXT}); } # Descriptions: execute qmail extension command emulation. # Arguments: OBJ($curproc) # Side Effects: emulate command process if needed. # Return Value: none sub _execute_qmail_extension_command { my ($curproc) = @_; use FML::Command::QmailExt; my $extension = new FML::Command::QmailExt $curproc; $extension->execute($ENV{EXT}); } =head1 CODING STYLE See C on fml coding style guide. =head1 AUTHOR Ken'ichi Fukamachi =head1 COPYRIGHT Copyright (C) 2002,2003,2004,2005,2006 Ken'ichi Fukamachi All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 HISTORY FML::Process::Error first appeared in fml8 mailing list driver package. See C for more details. =cut 1;