791 lines
29 KiB
Perl
791 lines
29 KiB
Perl
package Sisimai::Data;
|
|
use feature ':5.10';
|
|
use strict;
|
|
use warnings;
|
|
use Class::Accessor::Lite;
|
|
use Sisimai::Address;
|
|
use Sisimai::String;
|
|
use Sisimai::Reason;
|
|
use Sisimai::Rhost;
|
|
use Sisimai::Time;
|
|
use Sisimai::DateTime;
|
|
|
|
my $rwaccessors = [
|
|
'catch', # [?] Results generated by hook method
|
|
'token', # [String] Message token/MD5 Hex digest value
|
|
'lhost', # [String] local host name/Local MTA
|
|
'rhost', # [String] Remote host name/Remote MTA
|
|
'alias', # [String] Alias of the recipient address
|
|
'listid', # [String] List-Id header of each ML
|
|
'reason', # [String] Bounce reason
|
|
'action', # [String] The value of Action: header
|
|
'subject', # [String] UTF-8 Subject text
|
|
'timestamp', # [Sisimai::Time] Date: header in the original message
|
|
'addresser', # [Sisimai::Address] From address
|
|
'recipient', # [Sisimai::Address] Recipient address which bounced
|
|
'messageid', # [String] Message-Id: header
|
|
'replycode', # [String] SMTP Reply Code
|
|
'smtpagent', # [String] Module(Engine) name
|
|
'softbounce', # [Integer] 1 = Soft bounce, 0 = Hard bounce, -1 = ?
|
|
'smtpcommand', # [String] The last SMTP command
|
|
'destination', # [String] The domain part of the "recipinet"
|
|
'senderdomain', # [String] The domain part of the "addresser"
|
|
'feedbacktype', # [String] Feedback Type
|
|
'diagnosticcode', # [String] Diagnostic-Code: Header
|
|
'diagnostictype', # [String] The 1st part of Diagnostic-Code: Header
|
|
'deliverystatus', # [String] Delivery Status(DSN)
|
|
'timezoneoffset', # [Integer] Time zone offset(seconds)
|
|
];
|
|
Class::Accessor::Lite->mk_accessors(@$rwaccessors);
|
|
|
|
my $EndOfEmail = Sisimai::String->EOM;
|
|
my $RetryIndex = Sisimai::Reason->retry;
|
|
my $RFC822Head = Sisimai::RFC5322->HEADERFIELDS('all');
|
|
my $AddrHeader = {
|
|
'addresser' => $RFC822Head->{'addresser'},
|
|
'recipient' => $RFC822Head->{'recipient'},
|
|
};
|
|
my $ActionHead = { 'failure' => 'failed', 'expired' => 'delayed' };
|
|
|
|
sub new {
|
|
# Constructor of Sisimai::Data
|
|
# @param [Hash] argvs Data
|
|
# @return [Sisimai::Data] Structured email data
|
|
my $class = shift;
|
|
my $argvs = { @_ };
|
|
my $thing = {};
|
|
|
|
# Create email address object
|
|
my $as = Sisimai::Address->make($argvs->{'addresser'});
|
|
my $ar = Sisimai::Address->make({ 'address' => $argvs->{'recipient'} });
|
|
|
|
return undef unless ref $as eq 'Sisimai::Address';
|
|
return undef unless ref $ar eq 'Sisimai::Address';
|
|
|
|
$thing = {
|
|
'addresser' => $as,
|
|
'recipient' => $ar,
|
|
'senderdomain' => $as->host,
|
|
'destination' => $ar->host,
|
|
'alias' => $argvs->{'alias'} || $ar->alias,
|
|
'token' => Sisimai::String->token($as, $ar, $argvs->{'timestamp'}),
|
|
};
|
|
|
|
# Create Sisimai::Time object
|
|
$thing->{'timestamp'} = gmtime Sisimai::Time->new($argvs->{'timestamp'});
|
|
$thing->{'timezoneoffset'} = $argvs->{'timezoneoffset'} // '+0000';
|
|
|
|
# Callback method
|
|
$thing->{'catch'} = $argvs->{'catch'} // undef;
|
|
|
|
my @v1 = (qw|
|
|
listid subject messageid smtpagent diagnosticcode diagnostictype deliverystatus
|
|
reason lhost rhost smtpcommand feedbacktype action softbounce replycode
|
|
|);
|
|
$thing->{ $_ } = $argvs->{ $_ } // '' for @v1;
|
|
$thing->{'replycode'} ||= Sisimai::SMTP::Reply->find($argvs->{'diagnosticcode'});
|
|
|
|
return bless($thing, __PACKAGE__);
|
|
}
|
|
|
|
sub make {
|
|
# Another constructor of Sisimai::Data
|
|
# @param [Hash] argvs Data and orders
|
|
# @option argvs [Sisimai::Message] Data Object
|
|
# @return [Array, Undef] List of Sisimai::Data or Undef if the
|
|
# argument is not Sisimai::Message object
|
|
my $class = shift;
|
|
my $argvs = { @_ };
|
|
|
|
return undef unless exists $argvs->{'data'};
|
|
return undef unless ref $argvs->{'data'} eq 'Sisimai::Message';
|
|
return undef unless $argvs->{'data'}->ds;
|
|
return undef unless $argvs->{'data'}->rfc822;
|
|
|
|
my $delivered1 = $argvs->{'delivered'} // 0;
|
|
my $messageobj = $argvs->{'data'};
|
|
my $rfc822data = $messageobj->rfc822;
|
|
my $fieldorder = { 'recipient' => [], 'addresser' => [] };
|
|
my $objectlist = [];
|
|
my $givenorder = $argvs->{'order'} ? $argvs->{'order'} : {};
|
|
|
|
# Decide the order of email headers: user specified or system default.
|
|
if( ref $givenorder eq 'HASH' && scalar keys %$givenorder ) {
|
|
# If the order of headers for searching is specified, use the order
|
|
# for detecting an email address.
|
|
for my $e ( keys %$fieldorder ) {
|
|
# The order should be "Array Reference".
|
|
next unless $givenorder->{ $e };
|
|
next unless ref $givenorder->{ $e } eq 'ARRAY';
|
|
next unless scalar @{ $givenorder->{ $e } };
|
|
push @{ $fieldorder->{ $e } }, @{ $givenorder->{ $e } };
|
|
}
|
|
}
|
|
|
|
for my $e ( keys %$fieldorder ) {
|
|
# If the order is empty, use default order.
|
|
next if scalar @{ $fieldorder->{ $e } };
|
|
|
|
# Load default order of each accessor.
|
|
$fieldorder->{ $e } = $AddrHeader->{ $e };
|
|
}
|
|
|
|
LOOP_DELIVERY_STATUS: for my $e ( @{ $messageobj->ds } ) {
|
|
# Create parameters for new() constructor.
|
|
my $o = undef; # Sisimai::Data Object
|
|
my $r = undef; # Reason text
|
|
my $p = {
|
|
'catch' => $messageobj->catch // undef,
|
|
'lhost' => $e->{'lhost'} // '',
|
|
'rhost' => $e->{'rhost'} // '',
|
|
'alias' => $e->{'alias'} // '',
|
|
'action' => $e->{'action'} // '',
|
|
'reason' => $e->{'reason'} // '',
|
|
'replycode' => $e->{'replycode'} // '',
|
|
'smtpagent' => $e->{'agent'} // '',
|
|
'recipient' => $e->{'recipient'} // '',
|
|
'softbounce' => $e->{'softbounce'} // '',
|
|
'smtpcommand' => $e->{'command'} // '',
|
|
'feedbacktype' => $e->{'feedbacktype'} // '',
|
|
'diagnosticcode' => $e->{'diagnosis'} // '',
|
|
'diagnostictype' => $e->{'spec'} // '',
|
|
'deliverystatus' => $e->{'status'} // '',
|
|
};
|
|
unless( $delivered1 ) {
|
|
# Skip if the value of "deliverystatus" begins with "2." such as 2.1.5
|
|
next if index($p->{'deliverystatus'}, '2.') == 0;
|
|
}
|
|
|
|
EMAIL_ADDRESS: {
|
|
# Detect email address from message/rfc822 part
|
|
my $h = undef;
|
|
my $j = undef;
|
|
for my $f ( @{ $fieldorder->{'addresser'} } ) {
|
|
# Check each header in message/rfc822 part
|
|
$h = lc $f;
|
|
next unless exists $rfc822data->{ $h };
|
|
next unless $rfc822data->{ $h };
|
|
|
|
$j = Sisimai::Address->find($rfc822data->{ $h }) || [];
|
|
next unless scalar @$j;
|
|
$p->{'addresser'} = $j->[0];
|
|
last;
|
|
}
|
|
|
|
unless( $p->{'addresser'} ) {
|
|
# Fallback: Get the sender address from the header of the bounced
|
|
# email if the address is not set at loop above.
|
|
$j = Sisimai::Address->find($messageobj->{'header'}->{'to'}) || [];
|
|
$p->{'addresser'} = $j->[0] if scalar @$j;
|
|
}
|
|
}
|
|
next unless $p->{'addresser'};
|
|
next unless $p->{'recipient'};
|
|
|
|
TIMESTAMP: {
|
|
# Convert from a time stamp or a date string to a machine time.
|
|
my $datestring = undef;
|
|
my $zoneoffset = 0;
|
|
my @datevalues = ();
|
|
push @datevalues, $e->{'date'} if $e->{'date'};
|
|
|
|
# Date information did not exist in message/delivery-status part,...
|
|
for my $f ( @{ $RFC822Head->{'date'} } ) {
|
|
# Get the value of Date header or other date related header.
|
|
next unless $rfc822data->{ lc $f };
|
|
push @datevalues, $rfc822data->{ lc $f };
|
|
}
|
|
|
|
# Set "date" getting from the value of "Date" in the bounce message
|
|
push @datevalues, $messageobj->{'header'}->{'date'} if scalar(@datevalues) < 2;
|
|
|
|
while( my $v = shift @datevalues ) {
|
|
# Parse each date value in the array
|
|
$datestring = Sisimai::DateTime->parse($v);
|
|
last if $datestring;
|
|
}
|
|
|
|
if( defined $datestring ) {
|
|
# Get the value of timezone offset from $datestring
|
|
if( $datestring =~ /\A(.+)[ ]+([-+]\d{4})\z/ ) {
|
|
# Wed, 26 Feb 2014 06:05:48 -0500
|
|
$datestring = $1;
|
|
$zoneoffset = Sisimai::DateTime->tz2second($2);
|
|
$p->{'timezoneoffset'} = $2;
|
|
}
|
|
}
|
|
|
|
eval {
|
|
# Convert from the date string to an object then calculate time
|
|
# zone offset.
|
|
my $t = Sisimai::Time->strptime($datestring, '%a, %d %b %Y %T');
|
|
$p->{'timestamp'} = ($t->epoch - $zoneoffset) // undef;
|
|
};
|
|
}
|
|
next unless $p->{'timestamp'};
|
|
|
|
OTHER_TEXT_HEADERS: {
|
|
# Scan "Received:" header of the original message
|
|
my $recvheader = $argvs->{'data'}->{'header'}->{'received'} || [];
|
|
|
|
if( scalar @$recvheader ) {
|
|
# Get localhost and remote host name from Received header.
|
|
$e->{'lhost'} ||= shift @{ Sisimai::RFC5322->received($recvheader->[0]) };
|
|
$e->{'rhost'} ||= pop @{ Sisimai::RFC5322->received($recvheader->[-1]) };
|
|
}
|
|
|
|
for my $v ('rhost', 'lhost') {
|
|
$p->{ $v } =~ y/[]()//d; # Remove square brackets and curly brackets from the host variable
|
|
$p->{ $v } =~ s/\A.+=//; # Remove string before "="
|
|
$p->{ $v } =~ s/\r\z//g; # Remove CR at the end of the value
|
|
|
|
# Check space character in each value and get the first element
|
|
$p->{ $v } = (split(' ', $p->{ $v }, 2))[0] if rindex($p->{ $v }, ' ') > -1;
|
|
$p->{ $v } =~ s/[.]\z//; # Remove "." at the end of the value
|
|
}
|
|
|
|
# Subject: header of the original message
|
|
$p->{'subject'} = $rfc822data->{'subject'} // '';
|
|
$p->{'subject'} =~ s/\r\z//g;
|
|
|
|
# The value of "List-Id" header
|
|
$p->{'listid'} = $rfc822data->{'list-id'} // '';
|
|
if( $p->{'listid'} ) {
|
|
# Get the value of List-Id header: "List name <list-id@example.org>"
|
|
$p->{'listid'} = $1 if $p->{'listid'} =~ /\A.*([<].+[>]).*\z/;
|
|
$p->{'listid'} =~ y/<>//d;
|
|
$p->{'listid'} =~ s/\r\z//g;
|
|
$p->{'listid'} = '' if rindex($p->{'listid'}, ' ') > -1;
|
|
}
|
|
|
|
# The value of "Message-Id" header
|
|
$p->{'messageid'} = $rfc822data->{'message-id'} // '';
|
|
if( $p->{'messageid'} ) {
|
|
# Leave only string inside of angle brackets(<>)
|
|
$p->{'messageid'} = $1 if $p->{'messageid'} =~ /\A([^ ]+)[ ].*/;
|
|
$p->{'messageid'} = $1 if $p->{'messageid'} =~ /[<]([^ ]+?)[>]/;
|
|
}
|
|
|
|
CHECK_DELIVERY_STATUS_VALUE: {
|
|
# Cleanup the value of "Diagnostic-Code:" header
|
|
$p->{'diagnosticcode'} =~ s/[ \t.]+$EndOfEmail//;
|
|
$p->{'diagnosticcode'} =~ s/\r\z//g;
|
|
|
|
if( $p->{'diagnosticcode'} ) {
|
|
# Count the number of D.S.N. and SMTP Reply Code
|
|
my $vs = Sisimai::SMTP::Status->find($p->{'diagnosticcode'});
|
|
my $vr = Sisimai::SMTP::Reply->find($p->{'diagnosticcode'});
|
|
my $vm = 0;
|
|
|
|
if( $vs ) {
|
|
# How many times does the D.S.N. appeared
|
|
$vm += 1 while $p->{'diagnosticcode'} =~ /\b\Q$vs\E\b/g;
|
|
$p->{'deliverystatus'} = $vs if $vs =~ /\A[45][.][1-9][.][1-9]\z/;
|
|
}
|
|
|
|
if( $vr ) {
|
|
# How many times does the SMTP reply code appeared
|
|
$vm += 1 while $p->{'diagnosticcode'} =~ /\b$vr\b/g;
|
|
$p->{'replycode'} ||= $vr;
|
|
}
|
|
|
|
if( $vm > 2 ) {
|
|
# Build regular expression for removing string like '550-5.1.1'
|
|
# from the value of "diagnosticcode"
|
|
my $re = qr/[ ]$vr[- ](?:\Q$vs\E)?/;
|
|
|
|
# 550-5.7.1 [192.0.2.222] Our system has detected that this message is
|
|
# 550-5.7.1 likely unsolicited mail. To reduce the amount of spam sent to Gmail,
|
|
# 550-5.7.1 this message has been blocked. Please visit
|
|
# 550 5.7.1 https://support.google.com/mail/answer/188131 for more information.
|
|
$p->{'diagnosticcode'} =~ s/$re/ /g;
|
|
$p->{'diagnosticcode'} = Sisimai::String->sweep($p->{'diagnosticcode'});
|
|
}
|
|
}
|
|
$p->{'diagnostictype'} ||= 'X-UNIX' if $p->{'reason'} eq 'mailererror';
|
|
$p->{'diagnostictype'} ||= 'SMTP' unless $p->{'reason'} =~ /\A(?:feedback|vacation)\z/;
|
|
}
|
|
|
|
# Check the value of SMTP command
|
|
$p->{'smtpcommand'} = '' unless $p->{'smtpcommand'} =~ /\A(?:EHLO|HELO|MAIL|RCPT|DATA|QUIT)\z/;
|
|
|
|
if( $p->{'action'} ) {
|
|
# Action: expanded (to multi-recipient alias)
|
|
$p->{'action'} = $1 if $p->{'action'} =~ /\A(.+?) .+/;
|
|
|
|
unless( $p->{'action'} =~ /\A(?:failed|delayed|delivered|relayed|expanded)\z/ ) {
|
|
# The value of "action" is not in the following values:
|
|
# "failed" / "delayed" / "delivered" / "relayed" / "expanded"
|
|
for my $q ( keys %$ActionHead ) {
|
|
next unless $p->{'action'} eq $q;
|
|
$p->{'action'} = $ActionHead->{ $q };
|
|
last;
|
|
}
|
|
}
|
|
} else {
|
|
if( $p->{'reason'} eq 'expired' ) {
|
|
# Action: delayed
|
|
$p->{'action'} = 'delayed';
|
|
|
|
} elsif( index($p->{'deliverystatus'}, '5') == 0 || index($p->{'deliverystatus'}, '4') == 0 ) {
|
|
# Action: failed
|
|
$p->{'action'} = 'failed';
|
|
}
|
|
}
|
|
}
|
|
$o = __PACKAGE__->new(%$p);
|
|
next unless defined $o;
|
|
|
|
if( $o->reason eq '' || grep { $o->reason eq $_ } @$RetryIndex ) {
|
|
# Decide the reason of email bounce
|
|
$r = Sisimai::Rhost->get($o) if Sisimai::Rhost->match($o->rhost); # Remote host dependent error
|
|
$r ||= Sisimai::Reason->get($o);
|
|
$r ||= 'undefined';
|
|
$o->reason($r);
|
|
}
|
|
|
|
if( $o->reason eq 'delivered' || $o->reason eq 'feedback' || $o->reason eq 'vacation' ) {
|
|
# The value of reason is "delivered", "vacation" or "feedback".
|
|
$o->softbounce(-1);
|
|
$o->replycode('') unless $o->reason eq 'delivered';
|
|
|
|
} else {
|
|
# Bounce message which reason is "feedback" or "vacation" does
|
|
# not have the value of "deliverystatus".
|
|
my $softorhard = undef;
|
|
my $textasargv = undef;
|
|
|
|
unless( length $o->softbounce ) {
|
|
# Set the value of softbounce
|
|
$textasargv = $p->{'deliverystatus'}.' '.$p->{'diagnosticcode'};
|
|
$textasargv =~ s/\A[ ]//g;
|
|
$softorhard = Sisimai::SMTP::Error->soft_or_hard($o->reason, $textasargv);
|
|
|
|
if( $softorhard ) {
|
|
# Returned value is "soft" or "hard"
|
|
$o->softbounce($softorhard eq 'soft' ? 1 : 0);
|
|
|
|
} else {
|
|
# Returned value is an empty string or undef
|
|
$o->softbounce(-1);
|
|
}
|
|
}
|
|
|
|
unless( $o->deliverystatus ) {
|
|
# Set pseudo status code
|
|
my $pseudocode = undef; # Pseudo delivery status code
|
|
my $getchecked = undef; # Permanent error or not
|
|
my $tmpfailure = undef; # Temporary error
|
|
|
|
$textasargv = $o->replycode.' '.$p->{'diagnosticcode'};
|
|
$textasargv =~ s/\A[ ]//g;
|
|
|
|
$getchecked = Sisimai::SMTP::Error->is_permanent($textasargv);
|
|
$tmpfailure = defined $getchecked ? ( $getchecked == 1 ? 0 : 1 ) : 0;
|
|
$pseudocode = Sisimai::SMTP::Status->code($o->reason, $tmpfailure);
|
|
|
|
if( $pseudocode ) {
|
|
# Set the value of "deliverystatus" and "softbounce".
|
|
$o->deliverystatus($pseudocode);
|
|
if( $o->softbounce == -1 ) {
|
|
# Set the value of "softbounce" again when the value is -1
|
|
$softorhard = Sisimai::SMTP::Error->soft_or_hard($o->reason, $pseudocode);
|
|
if( $softorhard ) {
|
|
# Returned value is "soft" or "hard"
|
|
$o->softbounce($softorhard eq 'soft' ? 1 : 0);
|
|
|
|
} else {
|
|
# Returned value is an empty string or undef
|
|
$o->softbounce(-1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( $o->replycode ) {
|
|
# Check both of the first digit of "deliverystatus" and "replycode"
|
|
my $d1 = substr($o->deliverystatus, 0, 1);
|
|
my $r1 = substr($o->replycode, 0, 1);
|
|
$o->replycode('') unless $d1 eq $r1;
|
|
}
|
|
}
|
|
push @$objectlist, $o;
|
|
|
|
} # End of for(LOOP_DELIVERY_STATUS)
|
|
|
|
return $objectlist;
|
|
}
|
|
|
|
sub damn {
|
|
# Convert from object to hash reference
|
|
# @return [Hash] Data in Hash reference
|
|
my $self = shift;
|
|
my $data = undef;
|
|
|
|
eval {
|
|
my $v = {};
|
|
my @stringdata = (qw|
|
|
token lhost rhost listid alias reason subject messageid smtpagent
|
|
smtpcommand destination diagnosticcode senderdomain deliverystatus
|
|
timezoneoffset feedbacktype diagnostictype action replycode catch
|
|
softbounce
|
|
|);
|
|
|
|
for my $e ( @stringdata ) {
|
|
# Copy string data
|
|
$v->{ $e } = $self->$e // '';
|
|
}
|
|
$v->{'addresser'} = $self->addresser->address;
|
|
$v->{'recipient'} = $self->recipient->address;
|
|
$v->{'timestamp'} = $self->timestamp->epoch;
|
|
$data = $v;
|
|
};
|
|
return $data;
|
|
}
|
|
|
|
sub dump {
|
|
# Data dumper
|
|
# @param [String] type Data format: json, yaml
|
|
# @return [String, Undef] Dumped data or Undef if the value of first
|
|
# argument is neither "json" nor "yaml"
|
|
my $self = shift;
|
|
my $type = shift || 'json';
|
|
return undef unless $type =~ /\A(?:json|yaml)\z/;
|
|
|
|
my $dumpeddata = '';
|
|
my $referclass = 'Sisimai::Data::'.uc($type);
|
|
my $modulepath = 'Sisimai/Data/'.uc($type).'.pm';
|
|
|
|
require $modulepath;
|
|
$dumpeddata = $referclass->dump($self);
|
|
|
|
return $dumpeddata;
|
|
}
|
|
|
|
1;
|
|
__END__
|
|
|
|
=encoding utf-8
|
|
|
|
=head1 NAME
|
|
|
|
Sisimai::Data - Parsed data object
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
use Sisimai::Data;
|
|
my $data = Sisimai::Data->make('data' => <Sisimai::Message> object);
|
|
for my $e ( @$data ) {
|
|
print $e->reason; # userunknown, mailboxfull, and so on.
|
|
print $e->recipient->address; # (Sisimai::Address) envelope recipient address
|
|
print $e->bonced->ymd # (Sisimai::Time) Date of bounce
|
|
}
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
Sisimai::Data generate parsed data from Sisimai::Message object.
|
|
|
|
=head1 CLASS METHODS
|
|
|
|
=head2 C<B<make(I<Hash>)>>
|
|
|
|
C<make> generate parsed data and returns an array reference which are
|
|
including Sisimai::Data objects.
|
|
|
|
my $mail = Sisimai::Mail->new('/var/mail/root');
|
|
while( my $r = $mail->read ) {
|
|
my $mesg = Sisimai::Message->new('data' => $r);
|
|
my $data = Sisimai::Data->make('data' => $mesg);
|
|
for my $e ( @$data ) {
|
|
print $e->reason; # userunknown, mailboxfull, and so on.
|
|
print $e->recipient->address; # (Sisimai::Address) envelope recipient address
|
|
print $e->timestamp->ymd # (Sisimai::Time) Date of the email bounce
|
|
}
|
|
}
|
|
|
|
If you want to get bounce records which reason is "delivered", set "delivered"
|
|
option to make() method like the following:
|
|
|
|
my $data = Sisimai::Data->make('data' => $mesg, 'delivered' => 1);
|
|
|
|
Beginning from v4.19.0, `hook` argument is available to callback user defined
|
|
method like the following codes:
|
|
|
|
my $call = sub {
|
|
my $argv = shift;
|
|
my $fish = { 'x-mailer' => '' };
|
|
|
|
if( $argv->{'message'} =~ /^X-Mailer:\s*(.+)$/m ) {
|
|
$fish->{'x-mailer'} = $1;
|
|
}
|
|
|
|
return $fish;
|
|
};
|
|
my $mesg = Sisimai::Message->new('data' => $mailtxt, 'hook' => $call);
|
|
my $data = Sisimai::Data->make('data' => $mesg);
|
|
for my $e ( @$data ) {
|
|
print $e->catch->{'x-mailer'}; # Apple Mail (2.1283)
|
|
}
|
|
|
|
=head1 INSTANCE METHODS
|
|
|
|
=head2 C<B<damn()>>
|
|
|
|
C<damn> convert the object to a hash reference.
|
|
|
|
my $hash = $self->damn;
|
|
print $hash->{'recipient'}; # user@example.jp
|
|
print $hash->{'timestamp'}; # 1393940000
|
|
|
|
=head1 PROPERTIES
|
|
|
|
Sisimai::Data have the following properties:
|
|
|
|
=head2 C<action> (I<String>)
|
|
|
|
C<action> is the value of Action: field in a bounce email message such as
|
|
C<failed> or C<delayed>.
|
|
|
|
Action: failed
|
|
|
|
=head2 C<addresser> (I<Sisimai::Address)>
|
|
|
|
C<addressser> is L<Sisimai::Address> object generated from the sender address.
|
|
When Sisimai::Data object is dumped as JSON, this value converted to an email
|
|
address. Sisimai::Address object have the following accessors:
|
|
|
|
=over
|
|
|
|
=item - user() - the local part of the address
|
|
|
|
=item - host() - the domain part of the address
|
|
|
|
=item - address() - email address
|
|
|
|
=item - verp() - variable envelope return path
|
|
|
|
=item - alias() - alias of the address
|
|
|
|
=back
|
|
|
|
From: "Kijitora Cat" <kijitora@example.org>
|
|
|
|
=head2 C<alias> (I<String>)
|
|
|
|
C<alias> is an alias address of the recipient. When the Original-Recipient:
|
|
field or C<expanded from "address"> string did not exist in a bounce message,
|
|
this value is empty.
|
|
|
|
Original-Recipient: rfc822;kijitora@example.org
|
|
|
|
"|IFS=' ' && exec /usr/local/bin/procmail -f- || exit 75 #kijitora"
|
|
(expanded from: <kijitora@neko.example.edu>)
|
|
|
|
=head2 C<deliverystatus> (I<String>)
|
|
|
|
C<deliverystatus> is the value of Status: field in a bounce message. When the
|
|
message has no Status: field, Sisimai set pseudo value like 5.0.9XX to this
|
|
value. The range of values only C<4.x.x> or C<5.x.x>.
|
|
|
|
Status: 5.0.0 (permanent failure)
|
|
|
|
=head2 C<destination> (I<String>)
|
|
|
|
C<destination> is the domain part of the recipient address. This value is the
|
|
same as the return value from host() method of C<recipient> accessor.
|
|
|
|
=head2 C<diagnosticcode> (I<String>)
|
|
|
|
C<diagnosticcode> is an error message picked from Diagnostic-Code: field or
|
|
message body in a bounce message. This value and the value of C<diagnostictype>,
|
|
C<action>, C<deliverystatus>, C<replycode>, and C<smtpcommand> will be referred
|
|
by L<Sisimai::Reason> to decide the bounce reason.
|
|
|
|
Diagnostic-Code: SMTP; 554 5.4.6 Too many hops
|
|
|
|
=head2 C<diagnostictype> (C<String>)
|
|
|
|
C<diagnostictype> is a type like C<SMTP> or C<X-Unix> picked from Diagnostic-Code:
|
|
field in a bounce message. When there is no Diagnostic-Code: field in the bounce
|
|
message, this value will be empty.
|
|
|
|
Diagnostic-Code: X-Unix; 255
|
|
|
|
=head2 C<feedbacktype> (I<String>)
|
|
|
|
C<feedbacktype> is the value of Feedback-Type: field like C<abuse>, C<fraud>,
|
|
C<opt-out> in a bounce message. When the message is not ARF format or the value
|
|
of C<reason> is not C<feedback>, this value will be empty.
|
|
|
|
Content-Type: message/feedback-report
|
|
|
|
Feedback-Type: abuse
|
|
User-Agent: SMP-FBL
|
|
|
|
=head2 C<lhost> (I<String>)
|
|
|
|
C<lhost> is a local MTA name to be used as a gateway for sending email message
|
|
or the value of Reporting-MTA field in a bounce message. When there is no
|
|
Reporting-MTA field in the bounce message, Sisimai try to get the value from
|
|
Received header.
|
|
|
|
Reporting-MTA: dns; mx4.smtp.example.co.jp
|
|
|
|
=head2 C<listid> (I<String>)
|
|
|
|
C<listid> is the value of List-Id header of the original message. When there
|
|
is no List-Id field in the original message or the bounce message did not
|
|
include the original message, this value will be empty.
|
|
|
|
List-Id: Mailman mailing list management users
|
|
|
|
=head2 C<messageid> (I<String>)
|
|
|
|
C<messageid> is the value of Message-Id header of the original message. When
|
|
the original message did not include Message-Id: header or the bounce message
|
|
did not include the original message, this value will be empty.
|
|
|
|
Message-Id: <201310160515.r9G5FZh9018575@smtpgw.example.jp>
|
|
|
|
|
|
=head2 C<recipient> (I<Sisimai::Address)>
|
|
|
|
C<recipient> is L<Sisimai::Address> object generated from the recipient address.
|
|
When Sisimai::Data object is dumped as JSON, this value converted to an email
|
|
address. Sisimai::Address object have the following accessors:
|
|
|
|
=over
|
|
|
|
=item - user() - the local part of the address
|
|
|
|
=item - host() - the domain part of the address
|
|
|
|
=item - address() - email address
|
|
|
|
=item - verp() - variable envelope return path
|
|
|
|
=item - alias() - alias of the address
|
|
|
|
=back
|
|
|
|
Final-Recipient: RFC822; shironeko@example.ne.jp
|
|
X-Failed-Recipients: kijitora@example.ed.jp
|
|
|
|
=head2 C<reason> (I<String>)
|
|
|
|
C<reason> is the value of bounce reason Sisimai detected. When this value is
|
|
C<undefined> or C<onhold>, it means that Sisimai could not decide the reason.
|
|
All the reasons Sisismai can detect are available at L<Sisimai::Reason> or web
|
|
site L<https://libsisimai.org/en/reason/>.
|
|
|
|
=head2 C<replycode> (I<Integer>)
|
|
|
|
C<replycode> is the value of SMTP reply code picked from the error message or
|
|
the value of Diagnostic-Code: field in a bounce message. The range of values is
|
|
only 4xx or 5xx.
|
|
|
|
----- The following addresses had permanent fatal errors -----
|
|
<userunknown@libsisimai.org>
|
|
(reason: 550 5.1.1 <userunknown@libsisimai.org>... User Unknown)
|
|
|
|
=head2 C<rhost> (I<String>)
|
|
|
|
C<rhost> is a remote MTA name which has rejected the message you sent or the
|
|
value of Remote-MTA: field in a bounce message. When there is no Remote-MTA
|
|
field in the bounce message, Sisimai try to get the value from Received header.
|
|
|
|
Remote-MTA: DNS; g5.example.net
|
|
|
|
=head2 C<senderdomain> (I<String>)
|
|
|
|
C<senderdomain> is the domain part of the sender address. This value is the same
|
|
as the return value from host() method of addresser accessor.
|
|
|
|
=head2 C<smtpagent> (I<String>)
|
|
|
|
C<smtpagent> is a module name to be used for detecting bounce reason. For
|
|
example, when the value is C<Sendmail>, Sisimai used L<Sisimai::Bite::Email::Sendmail>
|
|
to get the recipient address and other delivery status information from a
|
|
bounce message.
|
|
|
|
=head2 C<smtpcommand> (I<String>)
|
|
|
|
C<smtpcommand> is a SMTP command name picked from the error message or the value
|
|
of Diagnostic-Code: field in a bounce message. When there is no SMTP command in
|
|
the bounce message, this value will be empty. The list of values is C<HELO>,
|
|
C<EHLO>, C<MAIL>, C<RCPT>, and C<DATA>.
|
|
|
|
<kijitora@example.go.jp>: host mx1.example.go.jp[192.0.2.127] said: 550 5.1.6 recipient
|
|
no longer on server: kijitora@example.go.jp (in reply to RCPT TO command)
|
|
|
|
=head2 C<softbounce> (I<Integer>)
|
|
|
|
The value of C<softbounce> indicates whether the reason of the bounce is soft
|
|
bounce or hard bounce. This accessor has added in Sisimai 4.1.28. The range of
|
|
the values are the followings:
|
|
|
|
=over
|
|
|
|
=item 1 = Soft bounce
|
|
|
|
=item 0 = Hard bounce
|
|
|
|
=item -1 = Sisimai could not decide
|
|
|
|
=back
|
|
|
|
=head2 C<subject> (I<String>)
|
|
|
|
C<subject> is the value of Subject header of the original message. When the
|
|
original message which is included in a bounce email contains no Subject header
|
|
(removed by remote MTA), this value will be empty.
|
|
If the value of Subject header of the original message contain any multibyte
|
|
character (non-ASCII character), such as MIME encoded Japanese or German and so
|
|
on, the value of subject in parsed data is encoded with UTF-8 again.
|
|
|
|
=head2 C<token> (I<String>)
|
|
|
|
C<token> is an identifier of each email-bounce. The token string is created from
|
|
the sender email address (addresser) and the recipient email address (recipient)
|
|
and the machine time of the date in a bounce message as an MD5 hash value.
|
|
The token value is generated at C<token()> method of L<Sisimai::String> class.
|
|
|
|
If you want to get the same token string at command line, try to run the
|
|
following command:
|
|
|
|
% printf "\x02%s\x1e%s\x1e%d\x03" sender@example.jp recipient@example.org `date '+%s'` | md5
|
|
714d72dfd972242ad04f8053267e7365
|
|
|
|
=head2 C<timestamp> (I<Sisimai::Time>)
|
|
|
|
C<timestamp> is the date which email has bounced as a L<Sisima::Time> (Child
|
|
class of Time::Piece) object. When Sisimai::Data object is dumped as JSON, this
|
|
value will be converted to an UNIX machine time (32 bits integer).
|
|
|
|
Arrival-Date: Thu, 29 Apr 2009 23:45:33 +0900
|
|
|
|
=head2 C<timezomeoffset> (I<String>)
|
|
|
|
C<timezoneoffset> is a time zone offset of a bounce email which its email has
|
|
bounced. The format of this value is String like C<+0900>, C<-0200>.
|
|
If Sisimai has failed to get a value of time zone offset, this value will be
|
|
set as C<+0000>.
|
|
|
|
=head1 SEE ALSO
|
|
|
|
L<https://libsisimai.org/en/data/>
|
|
|
|
=head1 AUTHOR
|
|
|
|
azumakuniyuki
|
|
|
|
=head1 COPYRIGHT
|
|
|
|
Copyright (C) 2014-2018 azumakuniyuki, All rights reserved.
|
|
|
|
=head1 LICENSE
|
|
|
|
This software is distributed under The BSD 2-Clause License.
|
|
|
|
=cut
|