| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Sisimai::Fact; |
|
2
|
84
|
|
|
84
|
|
516128
|
use v5.26; |
|
|
84
|
|
|
|
|
290
|
|
|
3
|
84
|
|
|
84
|
|
404
|
use strict; |
|
|
84
|
|
|
|
|
117
|
|
|
|
84
|
|
|
|
|
1964
|
|
|
4
|
84
|
|
|
84
|
|
332
|
use warnings; |
|
|
84
|
|
|
|
|
117
|
|
|
|
84
|
|
|
|
|
4081
|
|
|
5
|
84
|
|
|
84
|
|
49875
|
use Digest::SHA; |
|
|
84
|
|
|
|
|
318173
|
|
|
|
84
|
|
|
|
|
6754
|
|
|
6
|
84
|
|
|
84
|
|
42335
|
use Sisimai::Message; |
|
|
84
|
|
|
|
|
350
|
|
|
|
84
|
|
|
|
|
3886
|
|
|
7
|
84
|
|
|
84
|
|
641
|
use Sisimai::RFC791; |
|
|
84
|
|
|
|
|
145
|
|
|
|
84
|
|
|
|
|
2001
|
|
|
8
|
84
|
|
|
84
|
|
405
|
use Sisimai::RFC1123; |
|
|
84
|
|
|
|
|
130
|
|
|
|
84
|
|
|
|
|
1588
|
|
|
9
|
84
|
|
|
84
|
|
378
|
use Sisimai::RFC1894; |
|
|
84
|
|
|
|
|
135
|
|
|
|
84
|
|
|
|
|
1424
|
|
|
10
|
84
|
|
|
84
|
|
327
|
use Sisimai::RFC5322; |
|
|
84
|
|
|
|
|
134
|
|
|
|
84
|
|
|
|
|
1440
|
|
|
11
|
84
|
|
|
84
|
|
48101
|
use Sisimai::Reason; |
|
|
84
|
|
|
|
|
255
|
|
|
|
84
|
|
|
|
|
3777
|
|
|
12
|
84
|
|
|
84
|
|
619
|
use Sisimai::Address; |
|
|
84
|
|
|
|
|
148
|
|
|
|
84
|
|
|
|
|
2364
|
|
|
13
|
84
|
|
|
84
|
|
45186
|
use Sisimai::DateTime; |
|
|
84
|
|
|
|
|
1515
|
|
|
|
84
|
|
|
|
|
4414
|
|
|
14
|
84
|
|
|
84
|
|
60878
|
use Sisimai::Time; |
|
|
84
|
|
|
|
|
324
|
|
|
|
84
|
|
|
|
|
1053
|
|
|
15
|
84
|
|
|
84
|
|
46814
|
use Sisimai::SMTP::Command; |
|
|
84
|
|
|
|
|
1366
|
|
|
|
84
|
|
|
|
|
4164
|
|
|
16
|
84
|
|
|
84
|
|
42440
|
use Sisimai::SMTP::Failure; |
|
|
84
|
|
|
|
|
296
|
|
|
|
84
|
|
|
|
|
3653
|
|
|
17
|
84
|
|
|
84
|
|
583
|
use Sisimai::String; |
|
|
84
|
|
|
|
|
146
|
|
|
|
84
|
|
|
|
|
2054
|
|
|
18
|
84
|
|
|
84
|
|
41523
|
use Sisimai::Rhost; |
|
|
84
|
|
|
|
|
244
|
|
|
|
84
|
|
|
|
|
3352
|
|
|
19
|
84
|
|
|
84
|
|
37109
|
use Sisimai::LDA; |
|
|
84
|
|
|
|
|
252
|
|
|
|
84
|
|
|
|
|
7588
|
|
|
20
|
84
|
|
|
|
|
1123
|
use Class::Accessor::Lite ('new' => 0, 'rw' => [ |
|
21
|
|
|
|
|
|
|
'action', # [String] The value of Action: header |
|
22
|
|
|
|
|
|
|
'addresser', # [Sisimai::Address] From address |
|
23
|
|
|
|
|
|
|
'alias', # [String] Alias of the recipient address |
|
24
|
|
|
|
|
|
|
'catch', # [?] Results generated by hook method |
|
25
|
|
|
|
|
|
|
'command', # [String] The last SMTP command |
|
26
|
|
|
|
|
|
|
'decodedby', # [String] MTA Module name since v5.2.0 |
|
27
|
|
|
|
|
|
|
'deliverystatus', # [String] Delivery Status(DSN) |
|
28
|
|
|
|
|
|
|
'destination', # [String] The domain part of the "recipient" |
|
29
|
|
|
|
|
|
|
'diagnosticcode', # [String] Diagnostic-Code: Header |
|
30
|
|
|
|
|
|
|
'diagnostictype', # [String] The 1st part of Diagnostic-Code: Header |
|
31
|
|
|
|
|
|
|
'feedbackid', # [String] The value of Feedback-ID: header of the original message |
|
32
|
|
|
|
|
|
|
'feedbacktype', # [String] Feedback Type |
|
33
|
|
|
|
|
|
|
'hardbounce', # [Integer] 1 = Hard bounce, 0 = Is not a hard bounce |
|
34
|
|
|
|
|
|
|
'lhost', # [String] local host name/Local MTA |
|
35
|
|
|
|
|
|
|
'listid', # [String] List-Id header of each ML |
|
36
|
|
|
|
|
|
|
'messageid', # [String] Message-Id: header |
|
37
|
|
|
|
|
|
|
'origin', # [String] Email path as a data source |
|
38
|
|
|
|
|
|
|
'reason', # [String] Bounce reason |
|
39
|
|
|
|
|
|
|
'recipient', # [Sisimai::Address] Recipient address which bounced |
|
40
|
|
|
|
|
|
|
'replycode', # [String] SMTP Reply Code |
|
41
|
|
|
|
|
|
|
'rhost', # [String] Remote host name/Remote MTA |
|
42
|
|
|
|
|
|
|
'senderdomain', # [String] The domain part of the "addresser" |
|
43
|
|
|
|
|
|
|
'subject', # [String] UTF-8 Subject text |
|
44
|
|
|
|
|
|
|
'timestamp', # [Sisimai::Time] Date: header in the original message |
|
45
|
|
|
|
|
|
|
'timezoneoffset', # [Integer] Time zone offset(seconds) |
|
46
|
|
|
|
|
|
|
'token', # [String] Message token/MD5 Hex digest value |
|
47
|
|
|
|
|
|
|
'toxic', # EXPERIMENTAL |
|
48
|
84
|
|
|
84
|
|
571
|
]); |
|
|
84
|
|
|
|
|
159
|
|
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
sub rise { |
|
51
|
|
|
|
|
|
|
# Constructor of Sisimai::Fact |
|
52
|
|
|
|
|
|
|
# @param [Hash] argvs |
|
53
|
|
|
|
|
|
|
# @options argvs [String] data Entire email message |
|
54
|
|
|
|
|
|
|
# @options argvs [Integer] delivered 1 if the result which has "delivered" reason is included |
|
55
|
|
|
|
|
|
|
# @options argvs [Integer] vacation 1 if the result which has "vacation" reason is included |
|
56
|
|
|
|
|
|
|
# @options argvs [Code] hook Code reference to callback method |
|
57
|
|
|
|
|
|
|
# @options argvs [String] origin Path to the original email file |
|
58
|
|
|
|
|
|
|
# @return [Array] Array of Sisimai::Fact objects |
|
59
|
3502
|
|
|
3502
|
1
|
381457
|
my $class = shift; |
|
60
|
3502
|
|
100
|
|
|
10923
|
my $argvs = shift || return undef; |
|
61
|
3501
|
50
|
|
|
|
14361
|
die ' ***error: Sisimai::Fact->rise receives only a HASH reference as an argument' unless ref $argvs eq 'HASH'; |
|
62
|
|
|
|
|
|
|
|
|
63
|
3501
|
|
100
|
|
|
12235
|
my $email = $argvs->{'data'} || return undef; |
|
64
|
3500
|
|
|
|
|
14353
|
my $args1 = {'data' => $email, 'hook' => $argvs->{'hook'}}; |
|
65
|
3500
|
|
100
|
|
|
28038
|
my $mesg1 = Sisimai::Message->rise($args1) || return undef; |
|
66
|
|
|
|
|
|
|
|
|
67
|
3456
|
50
|
|
|
|
12804
|
return undef unless $mesg1->{'ds'}; |
|
68
|
3456
|
50
|
|
|
|
9878
|
return undef unless $mesg1->{'rfc822'}; |
|
69
|
|
|
|
|
|
|
|
|
70
|
3456
|
|
|
|
|
9261
|
state $retryindex = Sisimai::Reason->retry; |
|
71
|
3456
|
|
|
|
|
6538
|
state $rfc822head = Sisimai::RFC5322::HEADERTABLE; |
|
72
|
3456
|
|
|
|
|
7259
|
state $actionlist = {'delayed' => 1, 'delivered' => 1, 'expanded' => 1, 'failed' => 1, 'relayed' => 1}; |
|
73
|
3456
|
|
|
|
|
6448
|
my $rfc822data = $mesg1->{'rfc822'}; |
|
74
|
3456
|
|
|
|
|
6736
|
my $listoffact = []; |
|
75
|
|
|
|
|
|
|
|
|
76
|
3456
|
|
|
|
|
10782
|
RISEOF: for my $e ( $mesg1->{'ds'}->@* ) { |
|
77
|
|
|
|
|
|
|
# Create parameters |
|
78
|
3638
|
50
|
|
|
|
12457
|
next if length $e->{'recipient'} < 5; |
|
79
|
3638
|
100
|
100
|
|
|
22183
|
next if ! $argvs->{'delivered'} && index($e->{'status'}, '2.') == 0; |
|
80
|
3626
|
100
|
100
|
|
|
17415
|
next if ! $argvs->{'vacation'} && $e->{'reason'} eq 'vacation'; |
|
81
|
|
|
|
|
|
|
|
|
82
|
3605
|
|
|
|
|
6750
|
my $thing = {}; # To be blessed and pushed into the array above at the end of the loop |
|
83
|
|
|
|
|
|
|
my $piece = { |
|
84
|
|
|
|
|
|
|
'action' => $e->{'action'} // '', |
|
85
|
|
|
|
|
|
|
'alias' => $e->{'alias'} // '', |
|
86
|
|
|
|
|
|
|
'catch' => $mesg1->{'catch'} // undef, |
|
87
|
|
|
|
|
|
|
'decodedby' => $e->{'agent'} // '', |
|
88
|
|
|
|
|
|
|
'deliverystatus' => $e->{'status'} // '', |
|
89
|
|
|
|
|
|
|
'diagnosticcode' => $e->{'diagnosis'} // '', |
|
90
|
|
|
|
|
|
|
'diagnostictype' => $e->{'spec'} // '', |
|
91
|
|
|
|
|
|
|
'feedbacktype' => $e->{'feedbacktype'} // '', |
|
92
|
|
|
|
|
|
|
'hardbounce' => 0, |
|
93
|
|
|
|
|
|
|
'lhost' => $e->{'lhost'} // '', |
|
94
|
|
|
|
|
|
|
'origin' => $argvs->{'origin'} // '', |
|
95
|
|
|
|
|
|
|
'reason' => $e->{'reason'} // '', |
|
96
|
|
|
|
|
|
|
'recipient' => $e->{'recipient'} // '', |
|
97
|
|
|
|
|
|
|
'replycode' => $e->{'replycode'} // '', |
|
98
|
|
|
|
|
|
|
'rhost' => $e->{'rhost'} // '', |
|
99
|
|
|
|
|
|
|
'command' => $e->{'command'} // '', |
|
100
|
3605
|
|
50
|
|
|
131602
|
'toxic' => $e->{'toxic'} // 0, |
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
101
|
|
|
|
|
|
|
}; |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
ADDRESSER: { |
|
104
|
|
|
|
|
|
|
# Detect an email address from message/rfc822 part |
|
105
|
3605
|
|
|
|
|
10293
|
my $j = []; |
|
|
3605
|
|
|
|
|
5864
|
|
|
106
|
3605
|
|
|
|
|
11269
|
for my $f ( $rfc822head->{'addresser'}->@* ) { |
|
107
|
|
|
|
|
|
|
# Check each header in message/rfc822 part |
|
108
|
5634
|
100
|
|
|
|
14819
|
next unless exists $rfc822data->{ $f }; |
|
109
|
3333
|
50
|
|
|
|
10687
|
next unless $rfc822data->{ $f }; |
|
110
|
|
|
|
|
|
|
|
|
111
|
3333
|
|
50
|
|
|
24607
|
$j = Sisimai::Address->find($rfc822data->{ $f }) || next; |
|
112
|
3333
|
|
|
|
|
15493
|
$piece->{'addresser'} = shift @$j; |
|
113
|
3333
|
|
|
|
|
9579
|
last ADDRESSER; |
|
114
|
|
|
|
|
|
|
} |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# Fallback: Get the sender address from the header of the bounced email if the address |
|
117
|
|
|
|
|
|
|
# is not set at the loop above. |
|
118
|
272
|
|
50
|
|
|
2444
|
$j = Sisimai::Address->find($mesg1->{'header'}->{'to'}) || []; |
|
119
|
272
|
|
|
|
|
1255
|
$piece->{'addresser'} = shift @$j; |
|
120
|
|
|
|
|
|
|
} |
|
121
|
3605
|
50
|
|
|
|
9998
|
next RISEOF unless $piece->{'addresser'}; |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
TIMESTAMP: { |
|
124
|
|
|
|
|
|
|
# Convert from a time stamp or a date string to a machine time. |
|
125
|
3605
|
|
|
|
|
8196
|
my $datestring = undef; |
|
|
3605
|
|
|
|
|
6654
|
|
|
126
|
3605
|
|
|
|
|
6651
|
my $zoneoffset = 0; |
|
127
|
3605
|
100
|
|
|
|
5471
|
my @datevalues; push @datevalues, $e->{'date'} if $e->{'date'}; |
|
|
3605
|
|
|
|
|
15007
|
|
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# Date information did not exist in message/delivery-status part,... |
|
130
|
3605
|
|
|
|
|
10505
|
for my $f ( $rfc822head->{'date'}->@* ) { |
|
131
|
|
|
|
|
|
|
# Get the value of Date header or other date related header. |
|
132
|
14420
|
100
|
|
|
|
34640
|
next unless $rfc822data->{ $f }; |
|
133
|
3139
|
|
|
|
|
8141
|
push @datevalues, $rfc822data->{ $f }; |
|
134
|
|
|
|
|
|
|
} |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
# Set "date" getting from the value of "Date" in the bounce message |
|
137
|
3605
|
100
|
|
|
|
16417
|
push @datevalues, $mesg1->{'header'}->{'date'} if scalar(@datevalues) < 2; |
|
138
|
|
|
|
|
|
|
|
|
139
|
3605
|
|
|
|
|
13350
|
while( my $v = shift @datevalues ) { |
|
140
|
|
|
|
|
|
|
# Decode each date value in the array |
|
141
|
3605
|
|
50
|
|
|
36312
|
$datestring = Sisimai::DateTime->parse($v) || next; |
|
142
|
|
|
|
|
|
|
|
|
143
|
3605
|
50
|
|
|
|
26080
|
if( $datestring =~ /\A(.+)[ ]+([-+]\d{4})\z/ ) { |
|
144
|
|
|
|
|
|
|
# Get the value of timezone offset from $datestring: Wed, 26 Feb 2014 06:05:48 -0500 |
|
145
|
3605
|
|
|
|
|
11180
|
$datestring = $1; |
|
146
|
3605
|
|
|
|
|
19660
|
$zoneoffset = Sisimai::DateTime->tz2second($2); |
|
147
|
3605
|
|
|
|
|
13857
|
$piece->{'timezoneoffset'} = $2; |
|
148
|
|
|
|
|
|
|
} |
|
149
|
3605
|
50
|
|
|
|
10822
|
last if $datestring; |
|
150
|
|
|
|
|
|
|
} |
|
151
|
|
|
|
|
|
|
|
|
152
|
3605
|
|
|
|
|
7925
|
eval { |
|
153
|
|
|
|
|
|
|
# Convert from the date string to an object then calculate time zone offset. |
|
154
|
3605
|
|
|
|
|
38773
|
my $t = Sisimai::Time->strptime($datestring, '%a, %d %b %Y %T'); |
|
155
|
3605
|
|
50
|
|
|
217611
|
$piece->{'timestamp'} = ($t->epoch - $zoneoffset) // undef; |
|
156
|
|
|
|
|
|
|
}; |
|
157
|
|
|
|
|
|
|
} |
|
158
|
3605
|
50
|
|
|
|
293613
|
next RISEOF unless defined $piece->{'timestamp'}; |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
RECEIVED: { |
|
161
|
|
|
|
|
|
|
# Scan "Received:" header of the original message |
|
162
|
3605
|
|
50
|
|
|
6902
|
my $recv = $mesg1->{'header'}->{'received'} || []; |
|
|
3605
|
|
|
|
|
15970
|
|
|
163
|
3605
|
100
|
|
|
|
11244
|
unless( $piece->{'rhost'} ) { |
|
164
|
|
|
|
|
|
|
# Try to pick a remote hostname from Received: headers of the bounce message |
|
165
|
1655
|
|
|
|
|
15884
|
my $ir = Sisimai::RFC1123->find($e->{'diagnosis'}); |
|
166
|
1655
|
100
|
|
|
|
6902
|
$piece->{'rhost'} = $ir if Sisimai::RFC1123->is_internethost($ir); |
|
167
|
|
|
|
|
|
|
|
|
168
|
1655
|
100
|
|
|
|
6019
|
unless( $piece->{'rhost'} ) { |
|
169
|
|
|
|
|
|
|
# The remote hostname in the error message did not exist or is not a valid |
|
170
|
|
|
|
|
|
|
# internet hostname |
|
171
|
1239
|
|
|
|
|
3180
|
for my $re ( reverse @$recv ) { |
|
172
|
|
|
|
|
|
|
# Check the Received: headers backwards and get a remote hostname |
|
173
|
1933
|
100
|
|
|
|
8459
|
last if $piece->{'rhost'}; |
|
174
|
1509
|
|
|
|
|
7041
|
my $cv = Sisimai::RFC5322->received($re)->[0]; |
|
175
|
1509
|
100
|
|
|
|
7250
|
next unless Sisimai::RFC1123->is_internethost($cv); |
|
176
|
1091
|
|
|
|
|
4495
|
$piece->{'rhost'} = $cv; |
|
177
|
|
|
|
|
|
|
} |
|
178
|
|
|
|
|
|
|
} |
|
179
|
|
|
|
|
|
|
} |
|
180
|
3605
|
100
|
|
|
|
15403
|
$piece->{'lhost'} = '' if $piece->{'lhost'} eq $piece->{'rhost'}; |
|
181
|
|
|
|
|
|
|
|
|
182
|
3605
|
100
|
|
|
|
11068
|
unless( $piece->{'lhost'} ) { |
|
183
|
|
|
|
|
|
|
# Try to pick a local hostname from Received: headers of the bounce message |
|
184
|
1568
|
|
|
|
|
4038
|
for my $le ( @$recv ) { |
|
185
|
|
|
|
|
|
|
# Check the Received: headers forwards and get a local hostname |
|
186
|
1761
|
|
|
|
|
7765
|
my $cv = Sisimai::RFC5322->received($le)->[0]; |
|
187
|
1761
|
100
|
|
|
|
10690
|
next unless Sisimai::RFC1123->is_internethost($cv); |
|
188
|
1167
|
|
|
|
|
7811
|
$piece->{'lhost'} = $cv; last; |
|
|
1167
|
|
|
|
|
2791
|
|
|
189
|
|
|
|
|
|
|
} |
|
190
|
|
|
|
|
|
|
} |
|
191
|
|
|
|
|
|
|
|
|
192
|
3605
|
|
|
|
|
8122
|
for my $v ('rhost', 'lhost') { |
|
193
|
|
|
|
|
|
|
# Check and rewrite each host name |
|
194
|
7210
|
100
|
|
|
|
19795
|
next unless length $piece->{ $v }; |
|
195
|
6661
|
50
|
|
|
|
21712
|
if( index($piece->{ $v }, '@') > -1 ) { |
|
196
|
|
|
|
|
|
|
# Use the domain part as a remote/local host when the value is an email address |
|
197
|
0
|
|
|
|
|
0
|
$piece->{ $v } = (split('@', $piece->{ $v }))[-1]; |
|
198
|
|
|
|
|
|
|
} |
|
199
|
6661
|
|
|
|
|
25158
|
y/[]()\r//d, s/\A.+=// for $piece->{ $v }; # Remove [], (), \r, and strings before "=" |
|
200
|
|
|
|
|
|
|
|
|
201
|
6661
|
100
|
|
|
|
18810
|
if( index($piece->{ $v }, ' ') > -1 ) { |
|
202
|
|
|
|
|
|
|
# Check a space character in each value and get the first hostname |
|
203
|
77
|
|
|
|
|
274
|
my @ee = split(' ', $piece->{ $v }); |
|
204
|
77
|
|
|
|
|
210
|
for my $w ( @ee ) { |
|
205
|
|
|
|
|
|
|
# get a hostname from the string like "127.0.0.1 x109-20.example.com 192.0.2.20" |
|
206
|
|
|
|
|
|
|
# or "mx.sp.example.jp 192.0.2.135" |
|
207
|
77
|
50
|
|
|
|
542
|
next if Sisimai::RFC791->is_ipv4address($w); |
|
208
|
77
|
|
|
|
|
171
|
$piece->{ $v } = $w; last; |
|
|
77
|
|
|
|
|
145
|
|
|
209
|
|
|
|
|
|
|
} |
|
210
|
77
|
50
|
|
|
|
300
|
$piece->{ $v } = $ee[0] if index($piece->{ $v }, ' ') > 0; |
|
211
|
|
|
|
|
|
|
} |
|
212
|
6661
|
100
|
|
|
|
21306
|
chop $piece->{ $v } if substr($piece->{ $v }, -1, 1) eq '.'; # Remove "." at the end of the value |
|
213
|
|
|
|
|
|
|
} |
|
214
|
|
|
|
|
|
|
} |
|
215
|
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
ID_HEADERS: { |
|
217
|
|
|
|
|
|
|
# Message-ID:, List-ID: headers of the original message |
|
218
|
3605
|
|
|
|
|
6161
|
my $p0 = 0; |
|
|
3605
|
|
|
|
|
5889
|
|
|
219
|
3605
|
|
|
|
|
6837
|
my $p1 = 0; |
|
220
|
3605
|
100
|
|
|
|
27172
|
if( Sisimai::String->aligned(\$rfc822data->{'message-id'}, ['<', '@', '>']) ) { |
|
221
|
|
|
|
|
|
|
# https://www.rfc-editor.org/rfc/rfc5322#section-3.6.4 |
|
222
|
|
|
|
|
|
|
# Leave only string inside of angle brackets(<>) |
|
223
|
3062
|
|
|
|
|
8349
|
$p0 = index($rfc822data->{'message-id'}, '<') + 1; |
|
224
|
3062
|
|
|
|
|
7154
|
$p1 = index($rfc822data->{'message-id'}, '>'); |
|
225
|
3062
|
|
|
|
|
13079
|
$piece->{'messageid'} = substr($rfc822data->{'message-id'}, $p0, $p1 - $p0); |
|
226
|
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
} else { |
|
228
|
|
|
|
|
|
|
# Invalid value of the Message-Id: field |
|
229
|
543
|
|
|
|
|
2013
|
$piece->{'messageid'} = ''; |
|
230
|
|
|
|
|
|
|
} |
|
231
|
|
|
|
|
|
|
|
|
232
|
3605
|
100
|
|
|
|
21533
|
if( Sisimai::String->aligned(\$rfc822data->{'list-id'}, ['<', '.', '>']) ) { |
|
233
|
|
|
|
|
|
|
# https://www.rfc-editor.org/rfc/rfc2919 |
|
234
|
|
|
|
|
|
|
# Get the value of List-Id header: "List name " |
|
235
|
27
|
|
|
|
|
93
|
$p0 = index($rfc822data->{'list-id'}, '<') + 1; |
|
236
|
27
|
|
|
|
|
115
|
$p1 = index($rfc822data->{'list-id'}, '>'); |
|
237
|
27
|
|
|
|
|
184
|
$piece->{'listid'} = substr($rfc822data->{'list-id'}, $p0, $p1 - $p0); |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
} else { |
|
240
|
|
|
|
|
|
|
# Invalid value of the List-Id: field |
|
241
|
3578
|
|
|
|
|
13967
|
$piece->{'listid'} = ''; |
|
242
|
|
|
|
|
|
|
} |
|
243
|
|
|
|
|
|
|
} |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
DIAGNOSTICCODE: { |
|
246
|
|
|
|
|
|
|
# Cleanup the value of "Diagnostic-Code:" header |
|
247
|
3605
|
100
|
|
|
|
7376
|
last unless length $piece->{'diagnosticcode'}; |
|
|
3605
|
|
|
|
|
9941
|
|
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
# Get an SMTP Reply Code and an SMTP Enhanced Status Code |
|
250
|
3595
|
50
|
|
|
|
13061
|
chop $piece->{'diagnosticcode'} if substr($piece->{'diagnosticcode'}, -1, 1) eq "\r"; |
|
251
|
|
|
|
|
|
|
|
|
252
|
3595
|
|
100
|
|
|
25338
|
my $cs = Sisimai::SMTP::Status->find($piece->{'diagnosticcode'}) || ''; |
|
253
|
3595
|
|
100
|
|
|
27834
|
my $cr = Sisimai::SMTP::Reply->find( $piece->{'diagnosticcode'}, $cs) || ''; |
|
254
|
3595
|
|
|
|
|
20355
|
$piece->{'deliverystatus'} = Sisimai::SMTP::Status->prefer($piece->{'deliverystatus'}, $cs, $cr); |
|
255
|
|
|
|
|
|
|
|
|
256
|
3595
|
100
|
|
|
|
12548
|
if( length $cr == 3 ) { |
|
257
|
|
|
|
|
|
|
# There is an SMTP reply code in the error message |
|
258
|
2585
|
|
66
|
|
|
12494
|
$piece->{'replycode'} ||= $cr; |
|
259
|
|
|
|
|
|
|
|
|
260
|
2585
|
100
|
|
|
|
10622
|
if( index($piece->{'diagnosticcode'}, $cr.'-') > -1 ) { |
|
261
|
|
|
|
|
|
|
# 550-5.7.1 [192.0.2.222] Our system has detected that this message is |
|
262
|
|
|
|
|
|
|
# 550-5.7.1 likely unsolicited mail. To reduce the amount of spam sent to Gmail, |
|
263
|
|
|
|
|
|
|
# 550-5.7.1 this message has been blocked. Please visit |
|
264
|
|
|
|
|
|
|
# 550 5.7.1 https://support.google.com/mail/answer/188131 for more information. |
|
265
|
|
|
|
|
|
|
# |
|
266
|
|
|
|
|
|
|
# kijitora@example.co.uk |
|
267
|
|
|
|
|
|
|
# host c.eu.example.com [192.0.2.3] |
|
268
|
|
|
|
|
|
|
# SMTP error from remote mail server after end of data: |
|
269
|
|
|
|
|
|
|
# 553-SPF (Sender Policy Framework) domain authentication |
|
270
|
|
|
|
|
|
|
# 553-fail. Refer to the Troubleshooting page at |
|
271
|
|
|
|
|
|
|
# 553-http://www.symanteccloud.com/troubleshooting for more |
|
272
|
|
|
|
|
|
|
# 553 information. (#5.7.1) |
|
273
|
207
|
|
|
|
|
666
|
for my $q ('-', ' ') { |
|
274
|
|
|
|
|
|
|
# Remove strings: "550-5.7.1", and "550 5.7.1" from the error message |
|
275
|
414
|
|
|
|
|
809
|
my $cx = sprintf("%s%s%s", $cr, $q, $cs); |
|
276
|
414
|
|
|
|
|
935
|
my $p0 = index($piece->{'diagnosticcode'}, $cx); |
|
277
|
414
|
|
|
|
|
1029
|
while( $p0 > -1 ) { |
|
278
|
|
|
|
|
|
|
# Remove strings like "550-5.7.1" |
|
279
|
801
|
|
|
|
|
1541
|
substr($piece->{'diagnosticcode'}, $p0, length $cx, ''); |
|
280
|
801
|
|
|
|
|
1799
|
$p0 = index($piece->{'diagnosticcode'}, $cx); |
|
281
|
|
|
|
|
|
|
} |
|
282
|
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
# Remove "553-" and "553 " (SMTP reply code only) from the error message |
|
284
|
414
|
|
|
|
|
719
|
$cx = sprintf("%s%s", $cr, $q); |
|
285
|
414
|
|
|
|
|
2645
|
$p0 = index($piece->{'diagnosticcode'}, $cx); |
|
286
|
414
|
|
|
|
|
1146
|
while( $p0 > -1 ) { |
|
287
|
|
|
|
|
|
|
# Remove strings like "553-" |
|
288
|
42
|
|
|
|
|
101
|
substr($piece->{'diagnosticcode'}, $p0, length $cx, ''); |
|
289
|
42
|
|
|
|
|
117
|
$p0 = index($piece->{'diagnosticcode'}, $cx); |
|
290
|
|
|
|
|
|
|
} |
|
291
|
|
|
|
|
|
|
} |
|
292
|
|
|
|
|
|
|
|
|
293
|
207
|
50
|
|
|
|
837
|
if( index($piece->{'diagnosticcode'}, $cr) > 1 ) { |
|
294
|
|
|
|
|
|
|
# Add "550 5.1.1" into the head of the error message when the error |
|
295
|
|
|
|
|
|
|
# message does not begin with "550" |
|
296
|
0
|
|
|
|
|
0
|
$piece->{'diagnosticcode'} = sprintf("%s %s %s", $cr, $cs, $piece->{'diagnosticcode'}); |
|
297
|
|
|
|
|
|
|
} |
|
298
|
|
|
|
|
|
|
} |
|
299
|
|
|
|
|
|
|
} |
|
300
|
|
|
|
|
|
|
|
|
301
|
3595
|
|
|
|
|
12823
|
my $dc = lc $piece->{'diagnosticcode'}; |
|
302
|
3595
|
|
|
|
|
9115
|
my $p1 = index($dc, ''); |
|
303
|
3595
|
|
|
|
|
9220
|
my $p2 = index($dc, ''); |
|
304
|
3595
|
100
|
66
|
|
|
13286
|
substr($piece->{'diagnosticcode'}, $p1, $p2 + 7 - $p1, '') if $p1 > 0 && $p2 > 0; |
|
305
|
3595
|
|
|
|
|
18571
|
$piece->{'diagnosticcode'} = Sisimai::String->sweep($piece->{'diagnosticcode'}); |
|
306
|
|
|
|
|
|
|
} |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
DIAGNOSTICTYPE: { |
|
309
|
|
|
|
|
|
|
# Set the value of "diagnostictype" if it is empty |
|
310
|
3605
|
100
|
100
|
|
|
6269
|
$piece->{'diagnostictype'} ||= 'X-UNIX' if $piece->{'reason'} eq 'mailererror'; |
|
|
3605
|
|
|
|
|
12294
|
|
|
311
|
3605
|
100
|
100
|
|
|
8889
|
$piece->{'diagnostictype'} ||= 'SMTP' unless grep { $piece->{'reason'} eq $_ } ('feedback', 'vacation'); |
|
|
7210
|
|
|
|
|
36350
|
|
|
312
|
|
|
|
|
|
|
} |
|
313
|
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
# Check the SMTP command, the Subject field of the original message |
|
315
|
3605
|
100
|
|
|
|
21581
|
$piece->{'command'} = '' unless Sisimai::SMTP::Command->test($piece->{'command'}); |
|
316
|
3605
|
|
50
|
|
|
15927
|
$piece->{'subject'} = $rfc822data->{'subject'} // ''; |
|
317
|
3605
|
50
|
|
|
|
13713
|
chop $piece->{'subject'} if substr($piece->{'subject'}, -1, 1) eq "\r"; |
|
318
|
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
CONSTRUCTOR: { |
|
320
|
|
|
|
|
|
|
# Create email address object |
|
321
|
3605
|
|
50
|
|
|
5669
|
my $as = Sisimai::Address->new($piece->{'addresser'}) || next RISEOF; |
|
|
3605
|
|
|
|
|
19639
|
|
|
322
|
3605
|
|
50
|
|
|
22385
|
my $ar = Sisimai::Address->new({'address' => $piece->{'recipient'}}) || next RISEOF; |
|
323
|
3605
|
|
|
|
|
27331
|
my @ea = (qw| |
|
324
|
|
|
|
|
|
|
action command decodedby deliverystatus diagnosticcode diagnostictype feedbacktype |
|
325
|
|
|
|
|
|
|
lhost listid messageid origin reason replycode rhost subject |
|
326
|
|
|
|
|
|
|
|); |
|
327
|
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
$thing = { |
|
329
|
|
|
|
|
|
|
'addresser' => $as, |
|
330
|
|
|
|
|
|
|
'recipient' => $ar, |
|
331
|
|
|
|
|
|
|
'senderdomain' => $as->host, |
|
332
|
|
|
|
|
|
|
'destination' => $ar->host, |
|
333
|
|
|
|
|
|
|
'alias' => $piece->{'alias'} || $ar->alias, |
|
334
|
3605
|
|
66
|
|
|
20680
|
'token' => __PACKAGE__->maketoken($as->address, $ar->address, $piece->{'timestamp'}), |
|
335
|
|
|
|
|
|
|
}; |
|
336
|
3605
|
|
50
|
|
|
180104
|
$thing->{ $_ } ||= $piece->{ $_ } // '' for @ea; |
|
|
|
|
66
|
|
|
|
|
|
337
|
3605
|
|
100
|
|
|
21617
|
$thing->{'catch'} = $piece->{'catch'} // undef; |
|
338
|
3605
|
|
|
|
|
20509
|
$thing->{"feedbackid"} = ""; |
|
339
|
3605
|
|
|
|
|
10664
|
$thing->{'hardbounce'} = int $piece->{'hardbounce'}; |
|
340
|
3605
|
|
|
|
|
10392
|
$thing->{'toxic'} = int $piece->{'toxic'}; |
|
341
|
3605
|
|
100
|
|
|
21230
|
$thing->{'replycode'} ||= Sisimai::SMTP::Reply->find($piece->{'diagnosticcode'}) || ''; |
|
|
|
|
100
|
|
|
|
|
|
342
|
3605
|
|
|
|
|
22174
|
$thing->{'timestamp'} = Sisimai::Time->new($piece->{'timestamp'}); |
|
343
|
3605
|
|
50
|
|
|
410735
|
$thing->{'timezoneoffset'} = $piece->{'timezoneoffset'} // '+0000'; |
|
344
|
|
|
|
|
|
|
} |
|
345
|
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
ALIAS: { |
|
347
|
|
|
|
|
|
|
# Look up the Envelope-To address from the Received: header in the original message |
|
348
|
|
|
|
|
|
|
# when the recipient address is same with the value of $o->{'alias'}. |
|
349
|
3605
|
100
|
100
|
|
|
6497
|
last if length $thing->{'alias'} == 0 || $thing->{'recipient'}->address ne $thing->{'alias'}; |
|
|
3605
|
|
|
|
|
20588
|
|
|
350
|
676
|
50
|
|
|
|
10910
|
last unless exists $rfc822data->{'received'}; |
|
351
|
676
|
100
|
|
|
|
2625
|
last unless scalar $rfc822data->{'received'}->@*; |
|
352
|
|
|
|
|
|
|
|
|
353
|
577
|
|
|
|
|
1803
|
for my $er ( reverse $rfc822data->{'received'}->@* ) { |
|
354
|
|
|
|
|
|
|
# Search for the string " for " from the Received: header |
|
355
|
1011
|
100
|
|
|
|
6836
|
next unless index($er, ' for ') > 1; |
|
356
|
761
|
|
|
|
|
4653
|
my $or = Sisimai::RFC5322->received($er); |
|
357
|
|
|
|
|
|
|
|
|
358
|
761
|
50
|
33
|
|
|
4434
|
next if scalar(@$or) == 0 || length($or->[5]) == 0; |
|
359
|
761
|
50
|
|
|
|
3297
|
next if Sisimai::Address->is_emailaddress($or->[5]) == 0; |
|
360
|
761
|
100
|
|
|
|
4684
|
next if $thing->{'recipient'}->address eq $or->[5]; |
|
361
|
|
|
|
|
|
|
|
|
362
|
33
|
|
|
|
|
387
|
$thing->{'alias'} = $or->[5]; |
|
363
|
33
|
|
|
|
|
150
|
last ALIAS; |
|
364
|
|
|
|
|
|
|
} |
|
365
|
|
|
|
|
|
|
} |
|
366
|
3605
|
100
|
|
|
|
22356
|
$thing->{'alias'} = '' if $thing->{'alias'} eq $thing->{'recipient'}->{'address'}; |
|
367
|
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
REASON: { |
|
369
|
|
|
|
|
|
|
# Decide the reason of the email bounce |
|
370
|
3605
|
100
|
100
|
|
|
5679
|
if( $thing->{'reason'} eq '' || exists $retryindex->{ $thing->{'reason'} } ) { |
|
|
3605
|
|
|
|
|
39070
|
|
|
371
|
|
|
|
|
|
|
# The value of "reason" is empty or is needed to check with other values again |
|
372
|
3262
|
|
100
|
|
|
15458
|
my $re = $thing->{'reason'} || 'undefined'; |
|
373
|
3262
|
|
|
|
|
6162
|
my $cr = "Sisimai::Reason"; |
|
374
|
3262
|
100
|
|
|
|
32479
|
my $or = Sisimai::LDA->find($thing); if( $cr->is_explicit($or) ){ $thing->{'reason'} = $or; last } |
|
|
3262
|
|
|
|
|
27094
|
|
|
|
32
|
|
|
|
|
72
|
|
|
|
32
|
|
|
|
|
81
|
|
|
375
|
3230
|
100
|
|
|
|
28817
|
$or = Sisimai::Rhost->find($thing); if( $cr->is_explicit($or) ){ $thing->{'reason'} = $or; last } |
|
|
3230
|
|
|
|
|
13309
|
|
|
|
833
|
|
|
|
|
2550
|
|
|
|
833
|
|
|
|
|
2441
|
|
|
376
|
2397
|
100
|
|
|
|
9594
|
$or = Sisimai::Reason->find($thing); if( $cr->is_explicit($or) ){ $thing->{'reason'} = $or; last } |
|
|
2397
|
|
|
|
|
9148
|
|
|
|
2360
|
|
|
|
|
7097
|
|
|
|
2360
|
|
|
|
|
6672
|
|
|
377
|
37
|
100
|
|
|
|
209
|
$thing->{'reason'} = $thing->{'diagnosticcode'} ? "onhold" : $re; |
|
378
|
|
|
|
|
|
|
} |
|
379
|
|
|
|
|
|
|
} |
|
380
|
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
HARDBOUNCE: { |
|
382
|
|
|
|
|
|
|
# Set the value of "hardbounce", default value of "bouncebounce" is 0 |
|
383
|
3605
|
100
|
100
|
|
|
6397
|
if( $thing->{'reason'} eq 'delivered' || $thing->{'reason'} eq 'feedback' || $thing->{'reason'} eq 'vacation' ) { |
|
|
3605
|
|
100
|
|
|
40070
|
|
|
384
|
|
|
|
|
|
|
# Delete the value of ReplyCode when the Reason is "feedback" or "vacation" |
|
385
|
120
|
100
|
|
|
|
616
|
$thing->{'replycode'} = '' unless $thing->{'reason'} eq 'delivered'; |
|
386
|
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
} else { |
|
388
|
|
|
|
|
|
|
# The reason is not "delivered", or "feedback", or "vacation" |
|
389
|
3485
|
|
|
|
|
13300
|
my $smtperrors = $piece->{'deliverystatus'}.' '.$piece->{'diagnosticcode'}; |
|
390
|
3485
|
100
|
|
|
|
9409
|
$smtperrors = '' if length $smtperrors < 4; |
|
391
|
3485
|
|
|
|
|
36639
|
$thing->{'hardbounce'} = Sisimai::SMTP::Failure->is_hardbounce($thing->{'reason'}, $smtperrors); |
|
392
|
|
|
|
|
|
|
} |
|
393
|
|
|
|
|
|
|
} |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
DELIVERYSTATUS: { |
|
396
|
|
|
|
|
|
|
# Set pseudo status code |
|
397
|
3605
|
100
|
|
|
|
6590
|
last DELIVERYSTATUS if $thing->{'deliverystatus'}; |
|
|
3605
|
|
|
|
|
12893
|
|
|
398
|
|
|
|
|
|
|
|
|
399
|
771
|
|
|
|
|
2622
|
my $smtperrors = $thing->{'replycode'}.' '.$piece->{'diagnosticcode'}; |
|
400
|
771
|
100
|
|
|
|
2658
|
$smtperrors = '' if length $smtperrors < 4; |
|
401
|
771
|
|
|
|
|
3922
|
my $permanent0 = Sisimai::SMTP::Failure->is_permanent($smtperrors); |
|
402
|
771
|
|
|
|
|
3450
|
my $temporary0 = Sisimai::SMTP::Failure->is_temporary($smtperrors); |
|
403
|
771
|
100
|
|
|
|
3670
|
my $temporary1 = $permanent0.$temporary0 eq "00" ? 0 : $temporary0; |
|
404
|
771
|
|
|
|
|
4912
|
$thing->{'deliverystatus'} = Sisimai::SMTP::Status->code($thing->{'reason'}, $temporary1); |
|
405
|
|
|
|
|
|
|
} |
|
406
|
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
REPLYCODE: { |
|
408
|
|
|
|
|
|
|
# Check both of the first digit of "deliverystatus" and "replycode" |
|
409
|
3605
|
|
|
|
|
6088
|
my $cx = [substr($thing->{'deliverystatus'}, 0, 1), substr($thing->{'replycode'}, 0, 1)]; |
|
|
3605
|
|
|
|
|
19859
|
|
|
410
|
3605
|
100
|
|
|
|
12316
|
if( $cx->[0] ne $cx->[1] ) { |
|
411
|
|
|
|
|
|
|
# The class of the "Status:" is defer with the first digit of the reply code |
|
412
|
935
|
|
100
|
|
|
4657
|
$cx->[1] = Sisimai::SMTP::Reply->find($piece->{'diagnosticcode'}, $cx->[0]) || ''; |
|
413
|
935
|
100
|
|
|
|
5314
|
$thing->{'replycode'} = index($cx->[1], $cx->[0]) == 0 ? $cx->[1] : ''; |
|
414
|
|
|
|
|
|
|
} |
|
415
|
|
|
|
|
|
|
|
|
416
|
3605
|
100
|
|
|
|
17203
|
unless( exists $actionlist->{ $thing->{'action'} } ) { |
|
417
|
|
|
|
|
|
|
# There is an action value which is not described at RFC1894 |
|
418
|
1665
|
50
|
|
|
|
17680
|
if( my $ox = Sisimai::RFC1894->field('Action: '.$thing->{'action'}) ) { |
|
419
|
|
|
|
|
|
|
# Rewrite the value of "Action:" field to the valid value |
|
420
|
|
|
|
|
|
|
# |
|
421
|
|
|
|
|
|
|
# The syntax for the action-field is: |
|
422
|
|
|
|
|
|
|
# action-field = "Action" ":" action-value |
|
423
|
|
|
|
|
|
|
# action-value = "failed" / "delayed" / "delivered" / "relayed" / "expanded" |
|
424
|
1665
|
|
|
|
|
4571
|
$thing->{'action'} = $ox->[2]; |
|
425
|
|
|
|
|
|
|
} |
|
426
|
|
|
|
|
|
|
} |
|
427
|
3605
|
100
|
|
|
|
12342
|
$thing->{'action'} = 'delivered' if $thing->{'reason'} eq 'delivered'; |
|
428
|
3605
|
100
|
100
|
|
|
12845
|
$thing->{'action'} ||= 'delayed' if $thing->{'reason'} eq 'expired'; |
|
429
|
3605
|
100
|
100
|
|
|
27868
|
$thing->{'action'} ||= 'failed' if $cx->[0] eq '4' || $cx->[0] eq '5'; |
|
|
|
|
100
|
|
|
|
|
|
430
|
3605
|
|
100
|
|
|
14266
|
$thing->{'action'} ||= ""; |
|
431
|
|
|
|
|
|
|
} |
|
432
|
|
|
|
|
|
|
|
|
433
|
3605
|
100
|
|
|
|
11843
|
if( $thing->{'replycode'} ne "" ) { |
|
434
|
|
|
|
|
|
|
# Fill empty values: ["SMTP Command", "DSN", "Reason"] |
|
435
|
2564
|
|
|
|
|
16148
|
my $cv = Sisimai::SMTP::Reply->associatedwith($thing->{'replycode'}); |
|
436
|
2564
|
100
|
|
|
|
8097
|
if( scalar @$cv > 0 ) { |
|
437
|
324
|
100
|
100
|
|
|
1518
|
$thing->{'command'} = $cv->[0] if $cv->[0] ne "" && $thing->{'command'} eq ""; |
|
438
|
324
|
50
|
66
|
|
|
1518
|
$thing->{'deliverystatus'} = $cv->[1] if $cv->[1] ne "" && Sisimai::SMTP::Status->is_explicit($thing->{'deliverystatus'}) == 0; |
|
439
|
324
|
100
|
66
|
|
|
2067
|
$thing->{'reason'} = $cv->[2] if $cv->[2] ne "" && Sisimai::Reason->is_explicit($thing->{'reason'}) == 0; |
|
440
|
|
|
|
|
|
|
} |
|
441
|
|
|
|
|
|
|
} |
|
442
|
|
|
|
|
|
|
# Feedback-ID: 1.us-west-2.QHuyeCQrGtIIMGKQfVdUhP9hCQR2LglVOrRamBc+Prk=:AmazonSES |
|
443
|
3605
|
|
100
|
|
|
19465
|
$thing->{'feedbackid'} = $rfc822data->{'feedback-id'} || ""; |
|
444
|
3605
|
|
100
|
|
|
26317
|
$thing->{'toxic'} ||= __PACKAGE__->is_toxic($thing); |
|
445
|
|
|
|
|
|
|
|
|
446
|
3605
|
|
|
|
|
47174
|
push @$listoffact, bless($thing, __PACKAGE__); |
|
447
|
|
|
|
|
|
|
} # End of for(RISEOF) |
|
448
|
|
|
|
|
|
|
|
|
449
|
3456
|
|
|
|
|
91127
|
return $listoffact; |
|
450
|
|
|
|
|
|
|
} |
|
451
|
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
sub is_toxic { |
|
453
|
|
|
|
|
|
|
# is_toxic checks if the recipient address should be permanently excluded from the list. |
|
454
|
|
|
|
|
|
|
# It returns true for addresses that pose a persistent delivery risk, making further resend |
|
455
|
|
|
|
|
|
|
# attempts unviable and detrimental to the sender's reputation. |
|
456
|
|
|
|
|
|
|
# @return [Bool] 1 if the recipient address should be removed from the list. |
|
457
|
3599
|
|
|
3599
|
1
|
7449
|
my $class = shift; |
|
458
|
3599
|
|
50
|
|
|
10535
|
my $thing = shift // return 0; |
|
459
|
3599
|
|
50
|
|
|
11464
|
my $cr = $thing->{'reason'} || 'undefined'; |
|
460
|
3599
|
|
100
|
|
|
12338
|
my $cv = $thing->{'replycode'} || ''; |
|
461
|
3599
|
|
100
|
|
|
13779
|
my $cw = $thing->{'deliverystatus'} || ''; |
|
462
|
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
# 1. Hard bounces or some soft bounces with a permanent error. |
|
464
|
|
|
|
|
|
|
# 1-1. Hard bounce: UserUnknown, HostUnknown, HasMoved, NotAccept |
|
465
|
|
|
|
|
|
|
# 1-2. Almost hard bounce: Suspend, Suppressed |
|
466
|
3599
|
100
|
100
|
|
|
20998
|
return 0 if index($cv, '4') == 0 || index($cw, '4') == 0; |
|
467
|
3138
|
100
|
|
|
|
7301
|
return 1 if grep { $cr eq $_ } qw[userunknown hostunknown hasmoved notaccept suspend suppressed]; |
|
|
18828
|
|
|
|
|
37993
|
|
|
468
|
|
|
|
|
|
|
|
|
469
|
1901
|
100
|
|
|
|
3895
|
if( grep { $cr eq $_ } qw[mailboxfull filtered norelaying] ) { |
|
|
5703
|
100
|
|
|
|
14751
|
|
|
470
|
|
|
|
|
|
|
# 2. Several softbounces: MailboxFull, Filtered, NoRelaying |
|
471
|
|
|
|
|
|
|
# 2-1. The SMTP command is "RCPT" except "MailboxFull". |
|
472
|
|
|
|
|
|
|
# 2-2. The SMTP reply code begins with "5" such as "550". |
|
473
|
|
|
|
|
|
|
# 2-3. The SMTP status code is explicit code (not empty, not 5.0.9XX). |
|
474
|
|
|
|
|
|
|
# 2-4. The SMTP status code begins with "5." such as "5.1.1". |
|
475
|
580
|
100
|
100
|
|
|
3285
|
return 1 if $cr ne 'mailboxfull' && $thing->{'command'} eq 'RCPT'; |
|
476
|
565
|
100
|
|
|
|
2990
|
return 1 if index($cv, '5') == 0; |
|
477
|
116
|
100
|
|
|
|
749
|
return 0 if Sisimai::SMTP::Status->is_explicit($cw) == 0; |
|
478
|
36
|
50
|
|
|
|
203
|
return 1 if index($cw, '5.') == 0; |
|
479
|
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
} elsif( $cr eq 'feedback' ) { |
|
481
|
|
|
|
|
|
|
# 3. Feedback Loop |
|
482
|
|
|
|
|
|
|
# 3-1. The Feedback Type is any of "abuse", "fraud", "opt-out" |
|
483
|
111
|
100
|
|
|
|
227
|
return 1 if grep { $thing->{'feedbacktype'} eq $_ } qw[abuse fraud opt-out]; |
|
|
333
|
|
|
|
|
1048
|
|
|
484
|
|
|
|
|
|
|
} |
|
485
|
1225
|
|
|
|
|
4289
|
return 0; |
|
486
|
|
|
|
|
|
|
} |
|
487
|
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
sub maketoken { |
|
489
|
|
|
|
|
|
|
# Create the message token from an addresser and a recipient |
|
490
|
|
|
|
|
|
|
# @param [String] addr1 A sender's email address |
|
491
|
|
|
|
|
|
|
# @param [String] addr2 A recipient's email address |
|
492
|
|
|
|
|
|
|
# @param [Integer] epoch Machine time of the email bounce |
|
493
|
|
|
|
|
|
|
# @return [String] Message token(MD5 hex digest) or empty string |
|
494
|
|
|
|
|
|
|
# if the any argument is missing |
|
495
|
|
|
|
|
|
|
# @see http://en.wikipedia.org/wiki/ASCII |
|
496
|
|
|
|
|
|
|
# @see https://metacpan.org/pod/Digest::MD5 |
|
497
|
3611
|
|
50
|
3611
|
0
|
126754
|
my $class = shift || return ''; |
|
498
|
3611
|
|
100
|
|
|
10412
|
my $addr1 = shift || return ''; |
|
499
|
3610
|
|
100
|
|
|
10199
|
my $addr2 = shift || return ''; |
|
500
|
3609
|
|
100
|
|
|
9674
|
my $epoch = shift // return ''; |
|
501
|
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
# Format: STX(0x02) Sender-Address RS(0x1e) Recipient-Address ETX(0x03) |
|
503
|
3608
|
|
|
|
|
85241
|
return Digest::SHA::sha1_hex(sprintf("\x02%s\x1e%s\x1e%d\x03", lc $addr1, lc $addr2, $epoch)); |
|
504
|
|
|
|
|
|
|
} |
|
505
|
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
sub damn { |
|
508
|
|
|
|
|
|
|
# Convert from object to hash reference |
|
509
|
|
|
|
|
|
|
# @return [Hash] Data in Hash reference |
|
510
|
2933
|
|
|
2933
|
1
|
9564273
|
my $self = shift; |
|
511
|
2933
|
|
|
|
|
9153
|
my $data = undef; |
|
512
|
2933
|
|
|
|
|
8222
|
state $stringdata = [qw| |
|
513
|
|
|
|
|
|
|
action alias catch command decodedby deliverystatus destination diagnosticcode diagnostictype |
|
514
|
|
|
|
|
|
|
feedbackid feedbacktype lhost listid messageid origin reason replycode rhost senderdomain |
|
515
|
|
|
|
|
|
|
subject timezoneoffset token |
|
516
|
|
|
|
|
|
|
|]; |
|
517
|
2933
|
|
|
|
|
6850
|
eval { |
|
518
|
2933
|
|
|
|
|
6657
|
my $v = {}; |
|
519
|
2933
|
|
100
|
|
|
24922
|
$v->{ $_ } = $self->$_ // '' for @$stringdata; |
|
520
|
2933
|
|
|
|
|
402388
|
$v->{'hardbounce'} = int $self->hardbounce; |
|
521
|
2933
|
|
|
|
|
21934
|
$v->{'toxic'} = int $self->toxic; |
|
522
|
2933
|
|
|
|
|
18347
|
$v->{'addresser'} = $self->addresser->address; |
|
523
|
2933
|
|
|
|
|
49475
|
$v->{'recipient'} = $self->recipient->address; |
|
524
|
2933
|
|
|
|
|
37865
|
$v->{'timestamp'} = $self->timestamp->epoch; |
|
525
|
2933
|
|
|
|
|
59156
|
$data = $v; |
|
526
|
|
|
|
|
|
|
}; |
|
527
|
2933
|
|
|
|
|
79795
|
return $data; |
|
528
|
|
|
|
|
|
|
} |
|
529
|
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
sub dump { |
|
531
|
|
|
|
|
|
|
# Data dumper |
|
532
|
|
|
|
|
|
|
# @param [String] type Data format: json, yaml |
|
533
|
|
|
|
|
|
|
# @return [String] Dumped data or an empty string when the argument is neither "json" nor "yaml" |
|
534
|
1523
|
|
|
1523
|
0
|
23598903
|
my $self = shift; |
|
535
|
1523
|
100
|
100
|
|
|
10816
|
my $type = shift || 'json'; return "" unless $type =~ /\A(?:json|yaml)\z/; |
|
|
1523
|
|
|
|
|
13184
|
|
|
536
|
|
|
|
|
|
|
|
|
537
|
1485
|
|
|
|
|
10371
|
my $referclass = 'Sisimai::Fact::'.uc($type); |
|
538
|
1485
|
|
|
|
|
4924
|
my $modulepath = 'Sisimai/Fact/'.uc($type).'.pm'; |
|
539
|
|
|
|
|
|
|
|
|
540
|
1485
|
|
|
|
|
65142
|
require $modulepath; |
|
541
|
1485
|
|
|
|
|
17677
|
return $referclass->dump($self); |
|
542
|
|
|
|
|
|
|
} |
|
543
|
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
1; |
|
545
|
|
|
|
|
|
|
__END__ |