summaryrefslogtreecommitdiff
path: root/fml/lib/Mail/Delivery
diff options
context:
space:
mode:
authorfukachan <fukachan>2004-05-18 00:14:11 +0000
committerfukachan <fukachan>2004-05-18 00:14:11 +0000
commit367d619df5053960131abf8fd6c19a2f4e166261 (patch)
tree4630880fe9e64c278520c4e9f29b5581c647d0be /fml/lib/Mail/Delivery
parent4bbd72571ef746632fe15a9d80a95c16df16ee77 (diff)
downloadfml8-367d619df5053960131abf8fd6c19a2f4e166261.tar.gz
fml8-367d619df5053960131abf8fd6c19a2f4e166261.tar.bz2
fml8-367d619df5053960131abf8fd6c19a2f4e166261.zip
implement fallback when it fails to send the mail to all MTA's.
failed message is inserted into mail queue for later delivery retry. Also, shorten retry count $max_loop_count since fallback is implemented now. define {set,get}_last_command(), {set,get}_send_command_status(). modified to use them. rename: socket_is_connected -> is_socket_connected
Diffstat (limited to 'fml/lib/Mail/Delivery')
-rw-r--r--fml/lib/Mail/Delivery/SMTP.pm229
1 files changed, 194 insertions, 35 deletions
diff --git a/fml/lib/Mail/Delivery/SMTP.pm b/fml/lib/Mail/Delivery/SMTP.pm
index 50322035..537cd308 100644
--- a/fml/lib/Mail/Delivery/SMTP.pm
+++ b/fml/lib/Mail/Delivery/SMTP.pm
@@ -4,7 +4,7 @@
# All rights reserved. This program is free software; you can
# redistribute it and/or modify it under the same terms as Perl itself.
#
-# $FML: SMTP.pm,v 1.28 2004/05/16 08:16:09 fukachan Exp $
+# $FML: SMTP.pm,v 1.29 2004/05/16 12:25:59 fukachan Exp $
#
@@ -21,11 +21,19 @@ use Mail::Delivery::Net::INET6;
BEGIN {}
END {}
-# STATUS CODE
+# MAP IO STATUS CODE
my $MAP_DONE = 'DONE';
my $MAP_NOT_DONE = 'NOT DONE';
my $MAP_ERR_OPEN = 'CANNOT OPEN';
+# MTA IO STATUS CODE
+my $MTA_OK = 'OK';
+my $MTA_ERR_TIMEOUT = 'TIMEOUT';
+
+# SMTP STATUS
+my $SMTP_OK = 'OK';
+my $SMTP_ERR_RETRY = 'SMTP RETRY';
+
=head1 NAME
@@ -136,12 +144,14 @@ sub new
$me->{ _smtp_log_handle } = $args->{smtp_log_handle} || undef;
$me->{ _num_recipients } = 0;
- _initialize_delivery_session($me, $args);
-
# define package global pointer to the log() function
$LogFunctionPointer = $args->{log_function} || undef;
$SmtpLogFunctionPointer = $args->{smtp_log_function} || undef;
+ bless $me, $type;
+
+ _initialize_delivery_session($me, $args);
+
return bless $me, $type;
}
@@ -156,8 +166,8 @@ sub _send_command
my ($self, $command) = @_;
my $socket = $self->{'_socket'} || undef;
- $self->{_last_command} = $command;
- $self->{_error_action} = '';
+ $self->set_last_command($command);
+ $self->set_send_command_status('');
$self->smtplog($command."\r\n");
if (defined $socket) {
@@ -184,7 +194,8 @@ sub _read_reply
# toggle flag whether we should check SMTP attributes or not.
# we should check it only in HELO phase.
my $check_attributes = 0;
- if ($self->{_last_command} =~ /^(EHLO|HELO|LHLO)/) {
+ my $last_command = $self->get_last_command();
+ if ($last_command =~ /^(EHLO|HELO|LHLO)/o) {
$check_attributes = 1;
}
@@ -196,7 +207,7 @@ sub _read_reply
my $buf = '';
croak("socket is not connected") unless
- $self->socket_is_connected($socket);
+ $self->is_socket_connected($socket);
SMTP_REPLY:
while (1) {
@@ -231,13 +242,13 @@ sub _read_reply
};
if ($@ =~ /$id retry/) {
- $self->{'_error_action'} = "retry";
+ $self->set_send_command_status($SMTP_ERR_RETRY);
Log("need smtp retry");
$self->error_set("need smtp retry");
}
if ($@ =~ /$id socket timeout/) {
- my $x = $self->{'_last_command'};
+ my $x = $self->get_last_command();
Log("Error: smtp reply for \"$x\" is timeout");
$self->error_set("Error: smtp reply for \"$x\" is timeout");
}
@@ -247,6 +258,54 @@ sub _read_reply
}
+# Descriptions: save last command info.
+# Arguments: OBJ($self) STR($command)
+# Side Effects: update $self
+# Return Value: none
+sub set_last_command
+{
+ my ($self, $command) = @_;
+
+ $self->{ _last_command } = $command;
+}
+
+
+# Descriptions: get last command info.
+# Arguments: OBJ($self)
+# Side Effects: update $self
+# Return Value: none
+sub get_last_command
+{
+ my ($self) = @_;
+
+ return( $self->{ _last_command } || '' );
+}
+
+
+# Descriptions: save send command info.
+# Arguments: OBJ($self) STR($command)
+# Side Effects: update $self
+# Return Value: none
+sub set_send_command_status
+{
+ my ($self, $command) = @_;
+
+ $self->{ _send_command_status } = $command;
+}
+
+
+# Descriptions: get send command info.
+# Arguments: OBJ($self)
+# Side Effects: update $self
+# Return Value: none
+sub get_send_command_status
+{
+ my ($self) = @_;
+
+ return( $self->{ _send_command_status } || '' );
+}
+
+
# Descriptions: connect(2)
# 1. try connect(2) by IPv6 if we can use Socket6.pm
# 2. try connect(2) by IPv4
@@ -290,7 +349,7 @@ sub _connect
}
-=head2 socket_is_connected($socket)
+=head2 is_socket_connected($socket)
$socket has peer or not by C<getpeername()>.
@@ -304,7 +363,7 @@ $socket has peer or not by C<getpeername()>.
# Arguments: OBJ($self) HANDLE($socket)
# Side Effects: none
# Return Value: 1 or 0
-sub socket_is_connected
+sub is_socket_connected
{
my ($self, $socket) = @_;
@@ -460,9 +519,9 @@ sub deliver
# XXX-TODO: correct $max_loop_count evaluation ?
# To avoid infinite loop, we enforce some artificial limit.
- # The loop evaluation is limited to "4 * $number_of_mta" for each $map.
+ # The loop evaluation is limited to "2 * $number_of_mta" for each $map.
my $loop_count = 0;
- my $max_loop_count = ($#mta * 4) || 4;
+ my $max_loop_count = int($#mta * 2) || 2;
MTA_RETRY_LOOP:
while (1) {
@@ -470,7 +529,8 @@ sub deliver
# check infinite loop
if ($loop_count++ > $max_loop_count) {
- Log("Error: infinite loop for map=$map");
+ my $r = "too many smtp retry, give up map=$map";
+ $self->error_set($r);
last MTA_RETRY_LOOP;
}
@@ -479,6 +539,11 @@ sub deliver
# uniq $mta
next MTA if $used_mta{ $mta }; $used_mta{ $mta } = 1;
+ # avoid if error occurs.
+ if ($self->get_mta_status($mta) eq $MTA_ERR_TIMEOUT) {
+ next MTA;
+ }
+
# count the number of effective mta in this inter loop.
$n_mta++;
@@ -491,7 +556,7 @@ sub deliver
$self->error_clear;
# we read the whole $map now.
- if ($self->get_map_status($map) eq 'done') {
+ if ($self->get_map_status($map) eq $MAP_DONE) {
last MTA;
}
} # end of MTA: loop
@@ -520,20 +585,89 @@ sub deliver
next MAP if $status eq $MAP_ERR_OPEN;
unless ($self->get_map_status($map) eq $MAP_DONE) {
- my $n = $self->get_map_position($map) || '?';
- Log("map=$map pos=$n status=\"$status\"");
-
- # XXX-TODO: fallback to queue
- # dump todo into queue for later retry (may be by other process).
+ $self->_fallback_into_queue($args, $map, $status);
}
- }
+ }
# clean up recipient_map information after "all delivery".
# CAUTION: this mapinfo tracks the delivery status.
$self->reset_mapinfo;
if ( $self->{ _num_recipients } ) {
- Log( "recipients: total=". $self->{ _num_recipients } );
+ my $n = $self->{ _num_recipients };
+ Log("sent total=$n");
+ }
+}
+
+
+# Descriptions: delivery fallback due to something error.
+# add this transaction into mail queue for later delivery.
+# Arguments: OBJ($self) HASH_REF($args) STR($map) STR($status)
+# Side Effects: add this transaction into mail queue for later delivery.
+# Return Value: none
+sub _fallback_into_queue
+{
+ my ($self, $args, $map, $status) = @_;
+
+ # log current status.
+ my $n = $self->get_map_position($map) || 0;
+ Log("map=$map pos=$n status=\"$status\"");
+
+ # dump into queue.
+ if (defined $args->{ use_queue_dir } && $args->{ use_queue_dir }) {
+ my $queue_dir = $args->{ queue_dir } || '';
+ if ($queue_dir && -d $queue_dir) {
+ my $msg = $args->{ message } || undef;
+ my $sender = $args->{ smtp_sender } || '';
+ my $ra_rcpt = [];
+ my $num_rcpt = 0;
+
+ use IO::Adapter;
+ my $obj = new IO::Adapter $map, $args->{ map_params };
+ if (defined $obj) {
+ $obj->open || do {
+ Log("cannot open $map");
+ Log("fatal: delivery fallback failed.");
+ return;
+ };
+
+ my $rcpt;
+ while (defined ($rcpt = $obj->get_next_key)) {
+ $num_rcpt++;
+ push(@$ra_rcpt, $rcpt);
+ }
+ }
+
+ my $qid = '?';
+ eval q{
+ croak("no sender") unless $sender;
+
+ use Mail::Delivery::Queue;
+ my $queue = new Mail::Delivery::Queue {
+ directory => $queue_dir,
+ };
+
+ $queue->set('sender', $sender);
+
+ if (@$ra_rcpt) {
+ $queue->set('recipients', $ra_rcpt);
+ }
+
+ $queue->in( $msg ) || croak("fail to queue in");
+ unless ($queue->setrunnable()) {
+ croak("fail to queue in");
+ }
+
+ $qid = $queue->id();
+ };
+ unless ($@) {
+ Log("fallback: total=$num_rcpt qid=$qid");
+ }
+ else {
+ Log("fallback error: $@");
+ Log("fatal: delivery fallback failed.");
+ }
+ }
}
}
@@ -566,10 +700,10 @@ sub _deliver
# prepare smtp information
my $myhostname = $args->{ myhostname } || 'localhost';
- # 0. create BSD SOCKET as the communication terminal
+ # 0. create BSD SOCKET as the communication terminal.
# IF_ERROR_FOUND: do nothing and return as soon as possible
my $socket = $self->_connect($args);
- my $is_connected = $self->socket_is_connected($socket);
+ my $is_connected = $self->is_socket_connected($socket);
unless (defined($socket) && $is_connected) {
my $mta = $args->{_mta} || 'unknown';
Log("cannot connected to $mta");
@@ -585,25 +719,28 @@ sub _deliver
# since smtp connection has not established yet.
# IF_ERROR_FOUND: do nothing and return as soon as possible
$self->_read_reply;
- if ($self->error) { return;}
+ if ($self->error) {
+ $self->_set_mta_as_ignored($args);
+ return;
+ }
# 2. EHLO/HELO;
# IF_ERROR_FOUND: do nothing and return as soon as possible
$self->_send_command("EHLO $myhostname");
$self->_read_reply;
- if ($self->error) { $self->_reset_smtp_transaction; return;}
+ if ($self->error) { $self->_reset_smtp_transaction($args); return;}
# 3. MAIL FROM;
# IF_ERROR_FOUND: do nothing and return as soon as possible
$self->_send_mail_from($args);
- if ($self->error) { $self->_reset_smtp_transaction; return;}
+ if ($self->error) { $self->_reset_smtp_transaction($args); return;}
# 4. RCPT TO; ... send list of recipients
# IF_ERROR_FOUND: roll back the process to the state before this
$self->_send_recipient_list($args);
if ($self->error) {
$self->rollback_map_position;
- $self->_reset_smtp_transaction;
+ $self->_reset_smtp_transaction($args);
return;
}
@@ -613,7 +750,7 @@ sub _deliver
$self->_send_data_to_mta($args);
if ($self->error) {
$self->rollback_map_position;
- $self->_reset_smtp_transaction;
+ $self->_reset_smtp_transaction($args);
return;
}
@@ -641,8 +778,9 @@ sub _deliver
sub _initialize_delivery_session
{
my ($self, $args) = @_;
- $self->{ _last_command } = '';
- $self->{ _status_code } = '';
+
+ $self->set_last_command('');
+ $self->set_status_code('');
}
@@ -700,7 +838,7 @@ sub _send_recipient_list_by_recipient_map
my $obj = new IO::Adapter $map, $args->{ map_params };
unless (defined $obj) {
- Log("Error: cannot get object for $map by IO::Adapter");
+ Log("Error: fail to create map=$map object.");
}
else { # $obj is good.
my $rcpt;
@@ -725,8 +863,9 @@ sub _send_recipient_list_by_recipient_map
$self->_read_reply;
# save addresses to retry later.
- if ($self->{_error_action} eq 'retry') {
- $self->{ _retry_recipient_table }->{ $rcpt } = 'retry';
+ my $action = $self->get_send_command_status();
+ if ($action eq $SMTP_ERR_RETRY) {
+ $self->{ _retry_recipient_table }->{ $rcpt } = $SMTP_ERR_RETRY;
}
last RCPT_INPUT if $num_recipients >= $recipient_limit;
@@ -866,12 +1005,32 @@ sub _send_data_to_mta
sub _reset_smtp_transaction
{
my ($self, $args) = @_;
+
+ # mark this mta is invalid.
+ $self->_set_mta_as_ignored($args);
+
+ # reset SMTP transaction.
$self->_send_command("RSET");
$self->_read_reply;
Log("reset smtp transcation");
}
+# Descriptions: mark this mta should be ignored.
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: none
+sub _set_mta_as_ignored
+{
+ my ($self, $args) = @_;
+
+ # mark this mta is invalid.
+ my $mta = $args->{ _mta } || '';
+ if ($mta) {
+ $self->set_mta_status($mta, $MTA_ERR_TIMEOUT);
+ }
+}
+
=head1 SEE ALSO