#-*- perl -*- # # Copyright (C) 2001,2002,2003 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. # # $FML: Bounce.pm,v 1.23 2002/12/20 03:49:15 fukachan Exp $ # package Mail::Bounce; use strict; use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD); use Carp; my $debug = 0; =head1 NAME Mail::Bounce - analyze error messages =head1 SYNOPSIS use Mail::Message; my $msg = Mail::Message->parse( { fd => \*STDIN } ); use Mail::Bounce; my $bouncer = new Mail::Bounce; $bouncer->analyze( $msg ); # show results for my $a ( $bouncer->address_list ) { print "address: $a\n"; print " status: "; print $bouncer->status( $a ); print "\n"; print " reason: "; print $bouncer->reason( $a ); print "\n"; print "\n"; } =head1 DESCRIPTION try to analyze the given error message, which is a Mail::Message object. For non DSN pattern, try to analyze it by using modules in C which can recognize MTA specific and domian specific patterns. For example, Mail::Bounce $msg --------> Mail::Bounce::DSN::analyze() <-------- $result returned C<$result> provides the following information: $result = { address1 => { Original-Recipient => 'rfc822; addr', Final-Recipient => 'rfc822; addr', Diagnostic-Code => 'reason ...', Action => 'failed', Status => '4.0.0', Reporting-MTA => 'dns; server.fml.org', Received-From-MTA => 'DNS; server.fml.org', hints => ... which module matches ..., }, address2 => { ... snip ... }, ... }; =head1 METHODS =head2 C standard new() method. =cut # Descriptions: constructor # Arguments: OBJ($self) # Side Effects: none # Return Value: OBJ sub new { my ($self) = @_; my ($type) = ref($self) || $self; my $me = {}; return bless $me, $type; } =head2 C C<$msg> is a C object. This routine is a top level switch which provides the entrance for C modules, for example, C. C method in each module is the actual model specific analyzer. C<$result> has an answer of analyze if the error message pattern is already known. =cut # Descriptions: top level dispatcher # Arguments: OBJ($self) OBJ($msg) # Side Effects: update $self->{ _result }, which holds several info # Return Value: none sub analyze { my ($self, $msg) = @_; my $result = {}; if ($debug) { my $h = $msg->data_type_list( { debug => 1 } ); print STDERR " ----- dump msg -----\n"; for my $hdr (@$h) { print STDERR " ", $hdr, "\n";} print STDERR " ----- dump msg end -----\n"; } MODEL: for my $pkg (qw( DSN Postfix19991231 Qmail Exim GOO SimpleMatch )) { my $module = "Mail::Bounce::$pkg"; print STDERR "\n --- module: $module\n" if $debug; eval qq { use $module; $module->analyze( \$msg , \$result ); }; croak($@) if $@; if (keys %$result) { print STDERR "\n match $module\n" if $debug; last MODEL; } if ($debug) { print STDERR " * not match $module\n" unless %$result; } } $self->{ _result } = $result; } =head2 C return ARRAY of addresses found in the error message. =cut # Descriptions: return ARRAY of addresses found in the error message. # Arguments: OBJ($self) # Side Effects: none # Return Value: ARRAY sub address_list { my ($self) = @_; my $result = $self->{ _result }; return keys %$result; } =head2 C return status (string) for C<$addr>. The status can be extracted from C analyze() method gives. =head2 C return error reason (string) for C<$addr>. It can be extracted from C analyze() method gives. =cut # XXX-TODO: hmm, we should prepare $addr->status() and $addr->reason() ? # Descriptions: return status (string) for $addr # Arguments: OBJ($self) STR($addr) # Side Effects: none # Return Value: STR sub status { my ($self, $addr) = @_; my $status = $self->{ _result }->{ $addr }->{ 'Status' }; $status =~ s/\s+/ /g; $status =~ s/\s*$//; $status; } # Descriptions: return reason (string) for $addr # Arguments: OBJ($self) STR($addr) # Side Effects: none # Return Value: STR sub reason { my ($self, $addr) = @_; my $reason = $self->{ _result }->{ $addr }->{ 'Diagnostic-Code' }; $reason =~ s/\s+/ /g; $reason =~ s/\s*$//; $reason; } # Descriptions: return hints (string) for $addr # Arguments: OBJ($self) STR($addr) # Side Effects: none # Return Value: STR sub hints { my ($self, $addr) = @_; $self->{ _result }->{ $addr }->{ 'hints' }; } =head2 C return 1 if C looks Japanese one in JIS/SJIS/EUC code. return 0 unless. =cut my $RE_SJIS_C = '[\201-\237\340-\374][\100-\176\200-\374]'; my $RE_SJIS_S = "($RE_SJIS_C)+"; my $RE_EUC_C = '[\241-\376][\241-\376]'; my $RE_EUC_S = "($RE_EUC_C)+"; my $RE_JIN = '\033\$[\@B]'; my $RE_JOUT = '\033\([BJ]'; my @REGEXP = ( $RE_SJIS_C, $RE_SJIS_S, $RE_EUC_C, $RE_EUC_S, $RE_JIN, $RE_JOUT, ); # Descriptions: $buf looks like Japanese ? # Arguments: OBJ($self) STR($buf) # Side Effects: none # Return Value: 1 or 0 sub look_like_japanese { my ($self, $buf) = @_; for my $regexp (@REGEXP) { return 1 if $buf =~ /$regexp/; } 0; } =head2 C clean up C and return it. C gives a special hint for some specific MTA or domain. It is rarely used. =cut # Descriptions: clean up address for further use # Arguments: OBJ($self) STR($hint) STR($addr) # Side Effects: none # Return Value: STR sub address_clean_up { my ($self, $hint, $addr) = @_; if ($debug) { print STDERR "address_clean_up($hint, $addr)\n";} # nuke predecing and trailing strings around user@domain pattern my $prev_addr = $addr; do { $prev_addr = $addr; print STDERR " address_clean_up.in: $prev_addr\n" if $debug; $addr =~ s/\.$//; $addr =~ s/^\$//; $addr =~ s/^\"//; $addr =~ s/\"$//; print STDERR " address_clean_up.out: $addr\n" if $debug; } while ($addr ne $prev_addr); # Mail::Bounce::FixBrokenAddress class provides irrgular # address handlings, so handles domain/MTA specific addresses. # For example, nifty.ne.jp, webtv.ne.jp, ... # XXX-TODO: Mail::Bounce::FixBrokenAddress::FixIt is ugly. use Mail::Bounce::FixBrokenAddress; return Mail::Bounce::FixBrokenAddress::FixIt($hint, $addr); } =head1 SECURITY CONSIDERATION The address returned by Mail::Bounce class may be unsafe. Please validate it by some other class such as FML::Restriction class before use it at some other place. =head1 CODING STYLE See C on fml coding style guide. =head1 AUTHOR Ken'ichi Fukamachi =head1 COPYRIGHT Copyright (C) 2001,2002,2003 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 Mail::Bounce first appeared in fml8 mailing list driver package. See C for more details. =cut 1;