diff options
| author | fukachan <fukachan> | 2001-01-24 11:06:34 +0000 |
|---|---|---|
| committer | fukachan <fukachan> | 2001-01-24 11:06:34 +0000 |
| commit | bb09195df82acdd0d0a83edebded02a77ecec2f1 (patch) | |
| tree | 3dc1e08006ca9218f63f3f09652414a6a4a7fcf0 | |
| parent | bf8a627cd3fcb0be2149686cf9759f1ef3ac8514 (diff) | |
| download | fml8-bb09195df82acdd0d0a83edebded02a77ecec2f1.tar.gz fml8-bb09195df82acdd0d0a83edebded02a77ecec2f1.tar.bz2 fml8-bb09195df82acdd0d0a83edebded02a77ecec2f1.zip | |
s/Netlib/MailingList/g
| -rw-r--r-- | fml/lib/MailingList/@template | 4 | ||||
| -rw-r--r-- | fml/lib/MailingList/CheckSum.pm | 68 | ||||
| -rw-r--r-- | fml/lib/MailingList/INET4.pm | 83 | ||||
| -rw-r--r-- | fml/lib/MailingList/INET6.pm | 174 | ||||
| -rw-r--r-- | fml/lib/MailingList/Messages.pm | 202 | ||||
| -rw-r--r-- | fml/lib/MailingList/SMTP.ja.pod | 83 | ||||
| -rw-r--r-- | fml/lib/MailingList/SMTP.pm | 774 | ||||
| -rw-r--r-- | fml/lib/MailingList/Utils.pm | 217 | ||||
| -rw-r--r-- | fml/lib/MailingList/index.ja.html | 32 |
9 files changed, 1637 insertions, 0 deletions
diff --git a/fml/lib/MailingList/@template b/fml/lib/MailingList/@template new file mode 100644 index 00000000..81d97312 --- /dev/null +++ b/fml/lib/MailingList/@template @@ -0,0 +1,4 @@ +# Descriptions: +# Arguments: $self $args +# Side Effects: +# Return Value: none diff --git a/fml/lib/MailingList/CheckSum.pm b/fml/lib/MailingList/CheckSum.pm new file mode 100644 index 00000000..c4f2dbf0 --- /dev/null +++ b/fml/lib/MailingList/CheckSum.pm @@ -0,0 +1,68 @@ +#-*- perl -*- +# +# Copyright (C) 2001 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. +# +# $Id$ +# $FML$ +# + +package MailingList::CheckSum; + +use strict; +use vars qw(@ISA @EXPORT @EXPORT_OK); +use Carp; + +require Exporter; +@ISA = qw(Exporter); + + +sub new +{ + my ($self) = @_; + my ($type) = ref($self) || $self; + my $me = {}; + return bless $me, $type; +} + + +sub md5 +{ + ; +} + + +=head1 NAME + +MailingList::CheckSum.pm - what is this + + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +=head2 new + +=item Function() + + +=head1 AUTHOR + +Ken'ichi Fukamachi + +=head1 COPYRIGHT + +Copyright (C) 2001 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 + +MailingList::CheckSum.pm appeared in fml5. + +=cut + + +1; diff --git a/fml/lib/MailingList/INET4.pm b/fml/lib/MailingList/INET4.pm new file mode 100644 index 00000000..9b7336b9 --- /dev/null +++ b/fml/lib/MailingList/INET4.pm @@ -0,0 +1,83 @@ +#-*- perl -*- +# +# Copyright (C) 2001 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. +# +# $Id$ +# $FML$ +# + +package MailingList::INET4; +use strict; +use vars qw(@ISA @EXPORT @EXPORT_OK); +use Carp; +use MailingList::Utils; + +require Exporter; + +@ISA = qw(Exporter); +@EXPORT = qw(_connect4); + +sub _connect4 +{ + my ($self, $args) = @_; + my $mta = $args->{ _mta }; + my $socket = ''; + + # avoid croak() in IO::Socket module; + eval { + local($SIG{ALRM}) = sub { Log("Error: timeout to connect $mta");}; + use IO::Socket; + $socket = new IO::Socket::INET($mta); + }; + if ($@) { + Log("Error: cannot make socket for $mta"); + $self->_error_reason("Error: cannot make socket: $@"); + return undef; + } + + if (defined $socket) { + Log("(debug) o.k. connected to $mta"); + $self->{'_socket'} = $socket; + $socket->autoflush(1); + return $socket; + } + else { + Log("(debug) error. fail to connect $mta"); + $self->_error_reason("Error: cannot open socket: $!"); + return undef; + } +} + + +=head1 NAME + +FML::__HERE_IS_YOUR_MODULE_NAME__.pm - what is this + + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +=head2 new + +=item Function() + + +=head1 AUTHOR + +=head1 COPYRIGHT + +Copyright (C) 2001 __YOUR_NAME__ + +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::__MODULE_NAME__.pm appeared in fml5. + +=cut + +1; diff --git a/fml/lib/MailingList/INET6.pm b/fml/lib/MailingList/INET6.pm new file mode 100644 index 00000000..0055b371 --- /dev/null +++ b/fml/lib/MailingList/INET6.pm @@ -0,0 +1,174 @@ +#-*- perl -*- +# +# Copyright (C) 2001 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. +# +# $Id$ +# $FML$ +# + +package MailingList::INET6; +use strict; +use vars qw(@ISA @EXPORT @EXPORT_OK); +use Carp; +use MailingList::Utils; + +require Exporter; + +@ISA = qw(Exporter); +@EXPORT = qw(is_ipv6_ready is_ipv6_mta_syntax _connect6); + +sub _we_can_use_Socket6 +{ + my ($self, $args) = @_; + + eval q{ + use Socket; + use Socket6; + }; + + if ($@ =~ /Can\'t locate Socket6.pm/) { + $self->{_ipv6_ready} = 'no'; + } + else { + Log("IPv6 ready"); + $self->{_ipv6_ready} = 'yes'; + } +} + + +sub is_ipv6_ready +{ + my ($self, $args) = @_; + + # probe the IPv6 availability for the first time + unless ($self->{_ipv6_ready}) { + _we_can_use_Socket6($self, $args); + }; + + $self->{_ipv6_ready} eq 'yes' ? 1 : 0; +} + + +sub is_ipv6_mta_syntax +{ + my ($self, $host) = @_; + my ($x_host, $x_port); + + # check the mta syntax whether it is ipv6 form or not. + if ( $host =~ /\[([\d:]+)\]:(\d+)/) { + ($x_host, $x_port) = ($1, $2); + return ($x_host, $x_port); + } + else { + return wantarray ? () : undef; + } +} + + +sub _connect6 +{ + my ($self, $args) = @_; + my $mta = $args->{ _mta }; + + # check the mta syntax is $ipv6_addr:$port or not. + my ($host, $port) = $self->is_ipv6_mta_syntax( $args->{ _mta } ); + + # if mta is ipv6 raw address syntax, + # try to parse $mta to $host:$port style. + unless ($host) { + if ($mta =~ /(\S+):(\S+)/) { + ($host, $port) = ($1, $2); + } + } + + # hmm, invalid MTA + unless ($host && $port) { + Log("_connect6: cannot find mta=$mta"); + $self->{_socket} = undef; + return undef; + } + + $self->{_socket} = undef; + return undef; + + eval q{ + use IO::Handle; + use Socket; + use Socket6; + + my ($family, $type, $proto, $saddr, $canonname); + my $fh = new IO::Socket; + my $inet6_family = &AF_INET6; + + # resolve socket info by getaddrinfo() + my @res = getaddrinfo($host, $port, AF_UNSPEC, SOCK_STREAM); + $family = -1; + + LOOP: + while (scalar(@res) >= 5) { + ($family, $type, $proto, $saddr, $canonname, @res) = @res; + + my ($host, $port) = + getnameinfo($saddr, NI_NUMERICHOST | NI_NUMERICSERV); + + # check only IPv6 case here. + next LOOP if $family != $inet6_family; + + socket($fh, $family, $type, $proto) || do { + Log("Error: cannot create IPv6 socket"); + next LOOP; + }; + if (connect($fh, $saddr)) { + Log("(debug6) o.k. connect $host"); + last LOOP; + } + else { + Log("Error: cannot connect via IPv6"); + } + + $family = -1; + } + + if ($family != -1) { + $self->{_socket} = $fh; + Log("connected to $host:$port by IPv6"); + } else { + $self->{_socket} = undef; + Log("(debug6) fail to connect $host:$port by IPv6"); + } + }; +} + + +=head1 NAME + +FML::__HERE_IS_YOUR_MODULE_NAME__.pm - what is this + + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +=head2 new + +=item Function() + + +=head1 AUTHOR + +=head1 COPYRIGHT + +Copyright (C) 2001 __YOUR_NAME__ + +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::__MODULE_NAME__.pm appeared in fml5. + +=cut + +1; diff --git a/fml/lib/MailingList/Messages.pm b/fml/lib/MailingList/Messages.pm new file mode 100644 index 00000000..946a3c9c --- /dev/null +++ b/fml/lib/MailingList/Messages.pm @@ -0,0 +1,202 @@ +#-*- perl -*- +# +# Copyright (C) 2001 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. +# +# $Id$ +# $FML$ +# + +package MailingList::Messages; +use strict; +use vars qw(@ISA @EXPORT @EXPORT_OK); +use Carp; + +require Exporter; +@ISA = qw(Exporter); + + +sub new +{ + my ($self, $args) = @_; + my ($type) = ref($self) || $self; + my $me = {}; + if ($args) { create($me, $args);} + return bless $me, $type; +} + + +###################################################################### +=head1 NAME + +MailingList::Messages -- message manipulators + +=head1 SYNOPSIS + + my $m1 = new MailingList::Messages { content => \$body1 }; + + my $m2 = new MailingList::Messages; + $m2->create( { content => \$body2 }); + + # make a chain of $m1, $m2, ... + $m1->chain( $m2 ); + + # print the contents in the order: $m1, $m2, ... + $m1->print; + +=head1 DESCRIPTION + +A message has the content and a header including the next message +pointer, et. al. Out idea is similar to IPv6. + +Message Format + + %message = { + version => 1.0 + content_type => message/rfc822 + next => \%next_message + prev => \%prev_message + header => { + field_name => field_value + } + content => \$message_body + } + + +=item Function() + +=cut + + +sub create +{ + my ($self, $args) = @_; + + $self->{ version } = $args->{ version } || 1.0; + $self->{ type } = $args->{ type } || 'message/rfc822'; + $self->{ next } = $args->{ next } || undef; + $self->{ prev } = $args->{ prev } || undef; + $self->{ header } = $args->{ header } || undef; + $self->{ content } = $args->{ content } || ''; +} + + +sub next_chain +{ + my ($self, $ref_next_message) = @_; + $self->{ next } = $ref_next_message; +} + + +sub prev_chain +{ + my ($self, $ref_prev_message) = @_; + $self->{ prev } = $ref_prev_message; +} + + +sub print +{ + my ($self, $fd) = @_; + my $msg = $self; + + # if $fd is not given, we use STDOUT. + unless (defined $fd) { $fd = \*STDOUT;} + + MSG: + while (1) { + $self->_print($fd, $msg->{ content }); + last MSG unless $msg->{ next }; + $msg = $self->{ next }; + } +} + + +# Descriptions: send the body part of the message to socket +# replace "\n" in the end of line with "\r\n" on memory. +# We should do it to use as less memory as possible. +# So we use substr() to process each line. +# Arguments: $self $socket $ref_to_body +# Side Effects: none +# Return Value: none +sub _print +{ + my ($self, $fd, $r_body) = @_; + + my $pp = 0; + my $maxlen = length($$r_body); + my $logfp = $self->{ _log_function }; + $logfp = ref($logfp) eq 'CODE' ? $logfp : undef; + + # write each line in buffer + my ($p, $len, $buf, $pbuf); + SMTP_IO: + while (1) { + $p = index($$r_body, "\n", $pp); + $len = $p - $pp + 1; + $buf = substr($$r_body, $pp, ($p < 0 ? $maxlen-$pp : $len)); + if ($buf !~ /\r\n$/) { $buf =~ s/\n$/\r\n/;} + + # ^. -> .. + $buf =~ s/^\./../; + + print $fd $buf; + &$logfp($buf) if $logfp; + + last SMTP_IO if $p < 0; + $pp = $p + 1; + } +} + + +sub size +{ + my ($self) = @_; + my $c = $self->{ content }; + length($$c); +} + + +sub is_empty +{ + my ($self) = @_; + my $size = $self->size; + my $c = $self->{ content }; + + if ($size == 0) { return 1;} + if ($size <= 8) { + if ($$c =~ /^\s*$/) { return 1;} + } + + # false + return 0; +} + + +sub set_log_function +{ + my ($self, $fp) = @_; + $self->{ _log_function } = $fp; # log function pointer +} + + +###################################################################### +=head1 AUTHOR + +Ken'ichi Fukamachi + +=head1 COPYRIGHT + +Copyright (C) 2001 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 + +MailingList::Messages.pm appeared in fml5. + +=cut + +1; diff --git a/fml/lib/MailingList/SMTP.ja.pod b/fml/lib/MailingList/SMTP.ja.pod new file mode 100644 index 00000000..183dc70b --- /dev/null +++ b/fml/lib/MailingList/SMTP.ja.pod @@ -0,0 +1,83 @@ +=head1 NAME + +fml5 のメール配送システムについて + +=head1 DESCRIPTION + +=head2 fml4 と fml5 の相違点 + +fml5 の最大の目的の一つは、メンバーリストの取得と操作の統合と抽象化で +す。MailingList::SMTP は次のように使います。 + + +=head1 使い方 + +=head2 MailingList::* クラスの使い方について + +MailingList::* に属するクラスは SMTP および LMTP 配送へのインターフェイスを +提供します。 + +MailingList::SMTP は次のように使います。 + + use MailingList::SMTP; + my $service = new MailingList::SMTP { + log_function => $fp, + socket_timeout => 2, + }; + + my $ref_to_array = [ 'kenken@nuinui.net' ]; + my $recipient_maps = 'file:/var/spool/ml/elena/actives'; + + $service->deliver( + { + 'mta' => 'localhost:25', + + 'smtp_sender' => 'rudo@nuinui.net', + 'recipient_array' => $ref_to_array, + 'recipient_maps' => $recipient_maps, + + 'header' => $header, + 'body' => $body, + }); + if ($service->error) { Log($service->error); return;} + +ここで $header はヘッダで、FML::Header オブジェクトです。 +そして $body はメール本文で、FML::Body オブジェクトです。 + +=head1 コンポーネント + + MailingList::SMTP >-| + |- MailingList::Utils + |- MailingList::INET4 >--- Socket + |- MailingList::INET6 >--- Socket + | |- Socket6 + | + |- FML::IO::Map + | + |- IO::Socket + +MailingList::SMTP uses MailingList::Utils, MailingList::INET4 and MailingList::INET6. +MailingList::SMTP also uses FML::IO::Map to resolve $recipient_maps +operations. + +=head1 参照 + +L<"MailingList::SMTP">, +L<"FML::IO::Map"> + +=head1 AUTHOR + +Ken'ichi Fukamachi + +=head1 COPYRIGHT + +Copyright (C) 2001 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 + +MailingList class appeared in fml5. +fml5 is fully rewrite of fml4 based on the experience of fml4 +(1993-2001). diff --git a/fml/lib/MailingList/SMTP.pm b/fml/lib/MailingList/SMTP.pm new file mode 100644 index 00000000..5a83e85b --- /dev/null +++ b/fml/lib/MailingList/SMTP.pm @@ -0,0 +1,774 @@ +#-*- perl -*- +# +# Copyright (C) 2000-2001 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. +# +# $Id$ +# $FML$ +# + + +package MailingList::SMTP; +use strict; +use vars qw(@ISA @EXPORT @EXPORT_OK); +use Carp; +use IO::Socket; +use MailingList::Utils; +use MailingList::INET4; +use MailingList::INET6; + +require Exporter; +@ISA = qw(Exporter); + + +BEGIN {} + + +=head1 NAME + +MailingList::SMTP.pm - interface for SMTP service + +=head1 SYNOPSIS + +To initialize, + + use MailingList::SMTP; + my $fp = sub { Log(@_);}; # pointer to the log function + my $sfp = sub { my ($s) = @_; print $s; print "\n" if $s !~ /\n$/o;}; + my $service = new MailingList::SMTP { + log_function => $fp, + smtp_log_function => $sfp, + socket_timeout => 2, # XXX 2 for debug but 10 by default + }; + if ($service->error) { Log($service->error); return;} + +To start delivery, use deliver() method in this way. + + $service->deliver( + { + mta => '127.0.0.1:25', + + smtp_sender => 'rudo@nuinui.net', + recipient_maps => $recipient_maps, + recipient_limit => 1000, + + header => $header_object, + body => $body_object, + }); + +=head1 DESCRIPTION + +=head1 METHODS + +=item C<new()> + +constructor. If you control parameters, specify it in a hash reference +as an argument of new(). + + hash key value + -------------------------------------------- + log_function reference to function for logging + smtp_log_function reference to function for logging + socket_timeout set the timeout associated with the socket + +log_function() is for general purpose. +smtp_log_function() is used to log SMTP transactions. + +=cut + +# Descriptions: MailingList::SMTP constructor +# Arguments: $self $args +# Side Effects: $self ($me) hash has some default values +# Return Value: object +sub new +{ + my ($self, $args) = @_; + my ($type) = ref($self) || $self; + my $me = {}; # malloc new SMTP session struct + + # _recipient_limit: maximum recipients in one smtp session. + # _socket_timeout: basic timeout parameter for smtp session + # _log_function: pointer to the log() function + $me->{_recipient_limit} = $args->{recipient_limit} || 1000; + $me->{_socket_timeout} = $args->{socket_timeout} || 10; + $me->{_log_function} = $args->{log_function}; + $me->{_smtp_log_function} = $args->{smtp_log_function}; + + _initialize_delivery_session($me, $args); + + # define package global pointer to the log() function + $LogFunctionPointer = $args->{log_function}; + $SmtpLogFunctionPointer = $args->{smtp_log_function}; + + return bless $me, $type; +} + + +# Descriptions: send a (SMTP/LMTP) command string to BSD socket +# Arguments: $self $command_string +# Side Effects: log file by _smtplog +# set _last_command and _error_action in object itself +# Return Value: none +sub _send_command +{ + my ($self, $command) = @_; + my $socket = $self->{'_socket'}; + + $self->{_last_command} = $command; + $self->{_error_action} = ''; + $self->smtplog($command); + + if (defined $socket) { + $socket->print($command, "\r\n"); + } + else { + Log("Error: _send_command: undefined socket"); + } +} + + +# Descriptions: receive a reply for a (SMTP/LMTP) command +# Arguments: $self +# Side Effects: log file by _smtplog +# Return Value: none +sub _read_reply +{ + my ($self) = @_; + my $socket = $self->{'_socket'}; + + # unique identifier to clarify the trapped error message + my $id = $$; + + # 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)/) { + $check_attributes = 1; + } + + # XXX Attention! dynamic scope by local() for %SIG is essential. + # See books on Perl for more details on my() and local() difference. + eval { + local($SIG{ALRM}) = sub { die("$id socket timeout")}; + alarm( $self->{_socket_timeout} ); + my $buf = ''; + + SMTP_REPLY: + while (1) { + $buf = $socket->getline; + $self->smtplog($buf); + + # check smtp attributes + if ($check_attributes) { + if ($buf =~ /^250.PIPELINING/i) { + $self->{'_can_use_pipelining'} = 'yes'; + } + if ($buf =~ /^250.ETRN/i) { + $self->{'_can_use_etrn'} = 'yes'; + } + if ($buf =~ /^250.SIZE\s+(\d+)/i) { + $self->{'_size_limit'} = $1; + } + } + + # store the latest status code + if ($buf =~ /^(\d{3})/) { $self->_set_status_code($1);} + + # check status code + if ($buf =~ /^[45]\d{2}\s/) { + Log($buf); + die("$id retry"); + } + + # end of reply e.g. "250 ..." + last SMTP_REPLY if $buf =~ /^\d{3}\s/; + } + }; + + if ($@ =~ /$id retry/) { + $self->{'_error_action'} = "retry"; + } + + if ($@ =~ /$id socket timeout/) { + my $x = $self->{'_last_command'}; + Log("Error: smtp reply for \"$x\" is timeout"); + $self->_error_reason("Error: smtp reply for \"$x\" is timeout"); + } +} + + +# Descriptions: connect(2) +# 1. try connect(2) by IPv6 if we can use Socket6.pm +# 2. try connect(2) by IPv4 +# if $host is not IPv6 raw address e.g. [::1]:25 +# Arguments: $self $args +# Side Effects: set file handle (BSD socket) in $self->{_socket} +# Return Value: file handle (created BSD socket) or undef() +sub _connect +{ + my ($self, $args) = @_; + my $mta = $args->{'_mta'} || '127.0.0.1:25'; + my $socket; + + # 1. try to connect(2) $args->{ _mta } by IPv6 if we can use Socket6. + if ($self->is_ipv6_ready($args)) { + $self->_connect6($args); + my $socket = $self->{_socket}; + return $socket if defined $socket; + } + else { + Log("(debug) IPv6 is not ready"); + } + + # 2. try to connect(2) $args->{ _mta } by IPv4. + # XXX check the _mta syntax. + # XXX if $args->{ _mta } looks [$ipv6_addr]:$port style, + # XXX we do not try to connect the host by IPv4. + if ( $self->is_ipv6_mta_syntax($mta) ) { + Log("(debug) not try MTA $args->{_mta}"); + return undef; + } + else { + $self->_connect4($args); + } +} + + +# Descriptions: close BSD socket +# Arguments: $self +# Side Effects: +# Return Value: none +sub close +{ + my ($self) = @_; + my $socket = $self->{'_socket'}; + + if (defined $socket) { + $socket->close; + } + else { + Log("Error: try to close invalid socket"); + } +} + + +############################################################ +##### +##### SMTP delivery main loop +##### + +=item C<deliver()> + +start delivery process. + + hash key value + -------------------------------------------- + mta 127.0.0.1:25 [::1]:25 + smtp_sender sender's mail address + recipient_maps $recipient_maps + recipient_limit recipients in one SMTP transactions + header FML::Header object + body MailingList::Messages object + +C<mta> is a list of MTA's. +The syntax of each MTA is address:port style. +If you use a raw IPv6 address, use [address]:port syntax. +For example, [::1]:25 (v6 loopback). +You can specify IPv4 and IPv6 addresses. +deliver() automatically tries smtp in both protocols. + +C<smtp_sender> is the sender's email address. It is used at MAIL FROM: +parameter. + +C<recipient_maps> is a list of C<maps>. +See L<IO::MapAdapter> for more details. +For example, + +to read address from a file + + file:/var/spool/ml/elena/recipients + +to read addresses from /etc/group + + unix.group:fml + +C<recipient_limit> is the max number of recipients in one SMTP +transaction. 1000 by default, which corresponds to the limit by Postfix. + +C<header> is FML::Header object. + +C<body> is MailingList::Messages object. +See L<MailingList::Messages> for more details. + +=cut + +# Descriptions: main delivery loop for each recipient_maps and each mta. +# real delivery is done within _deliver() method. +# algorithm: +# for each $map { +# for each $mta { +# call _deliver() +# send recipients up to $recipient_limit +# } +# } +# +# Arguments: $self $args +# Side Effects: See MailingList::Utils for recipient_map utilities +# to track the delivery process status. +# Return Value: none +sub deliver +{ + my ($self, $args) = @_; + + # recipient limit + $self->{_recipient_limit} = $args->{recipient_limit} || 1000; + + # temporary hash to check whether the map/mta is used already. + my %used_mta = (); + my %used_map = (); + + # prepare loop for each mta and map + my @mta = split(/\s+/, $args->{ mta } || '127.0.0.1:25'); + my @maps = (); + if ( $args->{ recipient_maps } ) { + @maps = split(/\s+/, $args->{ recipient_maps }); + } + + # alloc virtual recipient map + if (ref( $args->{ recipient_array } ) eq 'ARRAY') { + my $map = $self->_alloc_recipients_array_on_memory($args); + push(@maps, $map); + } + + + MAP: + for my $map ( @maps ) { + # uniq $map + next if $used_map{ $map }; $used_map{ $map } = 1; + + # try to open $map + eval q{ + use IO::MapAdapter; + my $obj = new IO::MapAdapter $map; + if (defined $obj) { + $obj->open || croak("cannot open $map"); + } + }; + if ($@) { + Log("Error: cannot open and ignore $map"); + next MAP; + } + + $self->_set_target_map($map); + $self->_set_map_status($map, 'not done'); + $self->_set_map_position($map, 0); + + # To avoid infinite loop, we enforce some artificial limit. + # The loop evaluation is limited to "4 * $number_of_mta" for each $map. + my $loop_count = 0; + my $max_loop_count = ($#mta * 4) || 4; + + MTA_RETRY_LOOP: + while (1) { + my $n_mta = 0; + + # check infinite loop + if ($loop_count++ > $max_loop_count) { + Log("Error: infinite loop for map=$map"); + last MTA_RETRY_LOOP; + } + + MTA: + for my $mta (@mta) { + # uniq $mta + next if $used_mta{ $mta }; $used_mta{ $mta } = 1; + + # count the number of effective mta in this inter loop. + $n_mta++; + + # o.k. try to deliver mail by using $mta. + Log("(debug) use $mta for map=$map"); + $args->{ _mta } = $mta; + $self->_deliver($args); + + # remove error messages for the next _deliver() session. + $self->error_reset; + + # we read the whole $map now. + if ($self->_get_map_status($map) eq 'done') { + last MTA; + } + } # end of MTA: loop + + # end of MTA_RETRY_LOOP: loop + if ($self->_get_map_status($map) eq 'done') { + last MTA_RETRY_LOOP; + } + + # NO effective mta in this inter loop. It impiles that + # we used all MTA candidates. We reuse @mta again. + if ($n_mta == 0) { + Log("(debug) we used all MTA candidates. reuse \$mta"); + undef %used_mta; + next MTA_RETRY_LOOP; + } + } + } + + # clean up recipient_map information after "all delivery" + # CAUTION: this mapinfo tracks the delivery status. + $self->_reset_mapinfo; + + if ( $self->{ _num_recipients } ) { + Log( "recipients: ". $self->{ _num_recipients } ); + } +} + + +# Descriptions: ordinary SMTP sequence (see RFC821 for more details) +# >220 I am some MTA ... +# <EHLO/HELO myname +# >250 ok +# <MAIL FROM:<$sender> +# >250 ok +# <RCPT TO:<$recipient> +# >250 oK +# <DATA +# >354 ... +# < message +# <. +# >250 oK +# <QUIT +# >221 good bye +# Arguments: $self $args +# Side Effects: remove error messages when we return from here +# for the next _deliver() session. +# Return Value: none +sub _deliver +{ + my ($self, $args) = @_; + + $self->_initialize_delivery_session($args); + + # prepare smtp information + my $myhostname = $args->{ myhostname } || 'localhost'; + + # 0. create BSD SOCKET as the communication terminal + # IF_ERROR_FOUND: do nothing and return as soon as possible + my $socket = $self->_connect($args); + $socket || return; + + # 1. receive the first "220 .." message + # If you faces some error in this stage, you have to do nothing + # 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;} + + # 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;} + + # 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;} + + # 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; + return; + } + + # 5. DATA; send the mail body itself + # IF_ERROR_FOUND: handled in _send_data_to_mta(), so + # return as soon as possible from here. + $self->_send_data_to_mta($args); + if ($self->error) { return;} + + # 6. QUIT; SMTP session closing ... + # IF_ERROR_FOUND: do nothing ? + $self->_send_command("QUIT"); + $self->_read_reply; + if ($self->error) { $self->_reset_smtp_transaction; return;} +} + + +# Descriptions: initialize _deliver() process +# this routine is called at the first phase in _deliver() +# Arguments: $self $args +# Side Effects: +# Return Value: none +sub _initialize_delivery_session +{ + my ($self, $args) = @_; + $self->{ _last_command } = ''; + $self->{ _status_code } = ''; +} + + +############################################################ +##### +##### MAIL FROM: +##### + +# Descriptions: send SMTP command "MAIL FROM" +# Arguments: $self $args +# Side Effects: none +# Return Value: none +# See Also: RFC821, RFC1123 +# TODO: VERP's +sub _send_mail_from +{ + my ($self, $args) = @_; + my $sender = $args->{ smtp_sender }; + $self->_send_command("MAIL FROM:<$sender>"); + $self->_read_reply; +} + + + +############################################################ +##### +##### RCPT TO: +##### + +# Descriptions: We evaluate recipient_maps parameter here. +# You can use a lot of classes for this directive: e.g. +# file, UNIX's /etc/group, YP, SQL, LDAP, ... +# Example: recipient_maps = file:members +# unix.group:admin +# mysql:toymodel +# IO::MapAdapter class is essential to handle abstract +# $recipient_map. +# Arguments: $self $args +# Side Effects: $self->{ _retry_recipient_table } has recipients which +# causes some errors. +# _{set,get}_map_position() and _{set,get}_map_status() +# tracks the delivery process. +# Return Value: none +sub _send_recipient_list_by_recipient_map +{ + my ($self, $args) = @_; + my $map = $self->_get_target_map; + + # open abstract recipient list objects. + # $map syntax is "type:parameter", e.g., + # file:$filename mysql:$schema_name + use IO::MapAdapter; + my $obj = new IO::MapAdapter $map; + + unless (defined $obj) { + Log("Error: cannot get object for $map by IO::MapAdapter"); + } + else { # $obj is good. + my $rcpt; + my $num_recipients = 0; + my $recipient_limit = $self->{_recipient_limit}; + + $obj->open || do { + $self->_error_reason( $obj->error ); + return undef; + }; + + # roll back the previous file offset + if ($self->_get_map_position($map) > 0) { + $obj->setpos( $self->_get_map_position($map) ); + } + + # XXX $obj->get_recipient returns a mail address. + RCPT_INPUT: + while (defined ($rcpt = $obj->get_recipient)) { + $num_recipients++; + $self->_send_command("RCPT TO:<$rcpt>"); + $self->_read_reply; + + # save addresses to retry later. + if ($self->{_error_action} eq 'retry') { + $self->{ _retry_recipient_table }->{ $rcpt } = 'retry'; + } + + last RCPT_INPUT if $num_recipients >= $recipient_limit; + } + + # save the current position in the file handle + $self->_set_map_position($map, $obj->getpos); + + # done. + if ($obj->eof) { + $self->_set_map_status($map, 'done'); + } + + # ends + $obj->close; + + # count up the total number of recipients + $self->{ _num_recipients } += $num_recipients; + + unless ($num_recipients) { + Log("Error: no recipients for $map"); + $self->_send_command("RSET"); + $self->_read_reply; + } + } +} + + +# Descriptions: send "RCPT TO:<recipient>" to MTA +# Arguments: $self $args +# Side Effects: none +# Return Value: none +sub _send_recipient_list +{ + my ($self, $args) = @_; + + # evaluate recipient_maps + if ( $self->_get_target_map ) { + $self->_send_recipient_list_by_recipient_map($args); + } +} + + +# Descriptions: create CODE REFERECE to handle recipients array on memory +# Arguments: $self $args +# Side Effects: $me is referenced, so not destroyed. It is a closure (?). +# Return Value: CODE REFERENCE +sub _alloc_recipients_array_on_memory +{ + my ($self, $args) = @_; + return sub { + my ($me) = @_; + $me->{ _recipients_array_on_memory } = $args->{ recipient_array }; + }; +} + + +############################################################ +##### +##### DATA: +##### + +# Descriptions: send the header part of the message to socket +# Arguments: $self $socket $ref_to_header +# $ref_to_header is the FML::Header class object. +# Side Effects: none +# Return Value: none +sub _send_header_to_mta +{ + my ($self, $socket, $header) = @_; + + # get header + my $h = $header->as_string($socket); + $h =~ s/\n/\r\n/g; + print $socket $h; + $self->smtplog($h); +} + + +# Descriptions: send the body part of the message to socket +# Arguments: $self $socket "MailingList::Messages object" +# Side Effects: none +# Return Value: none +sub _send_body_to_mta +{ + my ($self, $socket, $msg) = @_; + + # XXX $msg is MailingList::Messages object. + $msg->set_log_function( $SmtpLogFunctionPointer ); + $msg->print($socket); +} + + +# Descriptions: send message itself to file handle (BSD socket here) +# Arguments: $self $args +# Side Effects: +# Return Value: none +# TODO: MIME/multipart +sub _send_data_to_mta +{ + my ($self, $args) = @_; + + # prepare smtp information + my $body = $args->{ body }; + my $header = $args->{ header }; + my $socket = $self->{'_socket'}; + + if (defined $body) { + $self->_send_command("DATA"); + $self->_read_reply; + + # XXX if "DATA" transaction cannot start, retry ? + if ($self->_get_status_code != '354' || $self->error) { + Log($self->error); + return undef; + } + + # 1. header; send header + $self->_send_header_to_mta($socket, $header); + + # 2. separator between header and body + print $socket "\r\n"; + $self->smtplog("\r\n"); + + # 3. body; send(copy) body on memory to socket each line + $self->_send_body_to_mta($socket, $body); + + # end "DATA" transaction + $self->_send_command("."); + $self->_read_reply; + } +} + + +############################################################ +##### +##### QUIT / RSET +##### + +# Descriptions: send the SMTP reset "RSET" command +# Arguments: $self $args +# Side Effects: none +# Return Value: none +sub _reset_smtp_transaction +{ + my ($self, $args) = @_; + $self->_send_command("RSET"); + $self->_read_reply; + Log("Info: reset smtp transcation"); +} + + + +=head1 SEE ALSO + +L<IO::Socket> +L<MailingList::Utils> +L<MailingList::INET4> +L<MailingList::INET6> + + +=head1 AUTHOR + +Ken'ichi Fukamachi + + +=head1 COPYRIGHT + +Copyright (C) 2001 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 + +MailingList::SMTP.pm appeared in fml5. + +=cut + +1; diff --git a/fml/lib/MailingList/Utils.pm b/fml/lib/MailingList/Utils.pm new file mode 100644 index 00000000..2730768f --- /dev/null +++ b/fml/lib/MailingList/Utils.pm @@ -0,0 +1,217 @@ +#-*- perl -*- +# +# Copyright (C) 2000-2001 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. +# +# $Id$ +# $FML$ +# + +package MailingList::Utils; +use strict; +use vars qw(@ISA @EXPORT @EXPORT_OK $LogFunctionPointer $SmtpLogFunctionPointer); +use Carp; + +require Exporter; +@ISA = qw(Exporter); + +@EXPORT = qw( + Log + _smtplog + smtplog + + $LogFunctionPointer + $SmtpLogFunctionPointer + + _error_reason + error + error_reset + + _set_status_code + _get_status_code + + _set_target_map + _get_target_map + _set_map_status + _set_map_position + _get_map_status + _get_map_position + _rollback_map_position + _reset_mapinfo + ); + +sub Log +{ + my ($buf) = @_; + + # function pointer to logging function + my $fp = $LogFunctionPointer; + + if ($fp) { + eval &$fp($buf); + print STDERR $@, "\n" if $@; + } + else { + print STDERR @_, "\n"; + } +} + + +sub smtplog +{ + my ($self, $buf) = @_; + _smtplog($buf); +} + +sub _smtplog +{ + my ($buf) = @_; + + # function pointer to logging function + my $fp = $SmtpLogFunctionPointer; + + if ($fp) { + eval &$fp($buf); + print STDERR $@, "\n" if $@; + } + else { + print STDERR @_, "\n"; + } +} + + +sub _error_reason +{ + my ($self, $mesg) = @_; + $self->{'_error_reason'} = $mesg; +} + + +sub error_reason +{ + my ($self, $mesg) = @_; + $self->_error_reason($mesg); +} + + +sub error +{ + my ($self, $args) = @_; + return $self->{'_error_reason'}; +} + + +sub error_reset +{ + my ($self, $args) = @_; + my $msg = $self->{'_error_reason'}; + undef $self->{'_error_reason'} if defined $self->{'_error_reason'}; + undef $self->{'_error_action'} if defined $self->{'_error_action'}; + return $msg; +} + + +sub _set_status_code +{ + my ($self, $value) = @_; + $self->{'_status_code'} = $value; +} + + +sub _get_status_code +{ + my ($self) = @_; + $self->{'_status_code'}; +} + + + +############################################################ +##### +##### utility functions to operate $recipient_maps +##### + + +sub _set_target_map +{ + my ($self, $map) = @_; + $self->{ _mapinfo }->{ _curmap } = $map; +} + + +sub _get_target_map +{ + my ($self) = @_; + $self->{ _mapinfo }->{ _curmap }; +} + + +sub _set_map_status +{ + my ($self, $map, $status) = @_; + $self->{ _mapinfo }->{ $map }->{prev_status} = + $self->{ _mapinfo }->{ $map }->{status} || 'not done'; + $self->{ _mapinfo }->{ $map }->{status} = $status; +} + +sub _set_map_position +{ + my ($self, $map, $position) = @_; + $self->{ _mapinfo }->{ $map }->{prev_position} = + $self->{ _mapinfo }->{ $map }->{position} || 0; + $self->{ _mapinfo }->{ $map }->{position} = $position; +} + +sub _get_map_status +{ + my ($self, $map) = @_; + $self->{ _mapinfo }->{ $map }->{status}; +} + +sub _get_map_position +{ + my ($self, $map) = @_; + $self->{ _mapinfo }->{ $map }->{position}; +} + + +sub _rollback_map_position +{ + my ($self) = @_; + my $map = $self->_get_target_map; + + # count the number of rollback to avoid infinite loop + if ( $self->{ _map_rollback_info }->{ $map }->{ count } > 2 ) { + Log("Error: not rollback $map to avoid infinite loop"); + return ; + } + else { + $self->{ _map_rollback_info }->{ $map }->{ count }++; + } + + my $prev_pos = $self->{ _mapinfo }->{ $map }->{prev_position}; + my $pos = $self->{ _mapinfo }->{ $map }->{position}; + $self->_set_map_position($map, $prev_pos); + Log("Info: rollback $map from $pos to $prev_pos"); + + my $prev_status = $self->{ _mapinfo }->{ $map }->{prev_status}; + my $status = $self->{ _mapinfo }->{ $map }->{status}; + $self->_set_map_status($map, $prev_status); + Log("Info: rollback status of $map to '$prev_status'"); +} + + +sub _reset_mapinfo +{ + my ($self) = @_; + $self->_set_target_map(''); + delete $self->{ _mapinfo }; + delete $self->{ _map_rollback_info }; +} + + +1; + + +1; diff --git a/fml/lib/MailingList/index.ja.html b/fml/lib/MailingList/index.ja.html new file mode 100644 index 00000000..82ebc1cb --- /dev/null +++ b/fml/lib/MailingList/index.ja.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +<TITLE> +MailingList:: クラス +</TITLE> +<META http-equiv="Content-Type" + content="text/html; charset=EUC-JP"> +</HEAD> + +<CENTER> +MailingList:: クラス +</CENTER> + +<BODY BGCOLOR="#E6E6FA"> +<!-- ================== body =========================================== --> + +<UL> + <LI> + <A HREF=INET6.pm>INET6.pm</A> + + <LI> + <A HREF=SMTP.pm>SMTP.pm</A> + + <LI> + <A HREF=Utils.pm>Utils.pm</A> + +</UL> + +<!-- =================================================================== --> +</BODY> +</HTML> |
