| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Sisimai::Fact; |
|
2
|
86
|
|
|
86
|
|
362563
|
use v5.26; |
|
|
86
|
|
|
|
|
224
|
|
|
3
|
86
|
|
|
86
|
|
380
|
use strict; |
|
|
86
|
|
|
|
|
98
|
|
|
|
86
|
|
|
|
|
1397
|
|
|
4
|
86
|
|
|
86
|
|
222
|
use warnings; |
|
|
86
|
|
|
|
|
95
|
|
|
|
86
|
|
|
|
|
3089
|
|
|
5
|
86
|
|
|
86
|
|
39792
|
use Digest::SHA; |
|
|
86
|
|
|
|
|
243523
|
|
|
|
86
|
|
|
|
|
5234
|
|
|
6
|
86
|
|
|
86
|
|
34646
|
use Sisimai::Message; |
|
|
86
|
|
|
|
|
235
|
|
|
|
86
|
|
|
|
|
2899
|
|
|
7
|
86
|
|
|
86
|
|
448
|
use Sisimai::RFC791; |
|
|
86
|
|
|
|
|
118
|
|
|
|
86
|
|
|
|
|
1553
|
|
|
8
|
86
|
|
|
86
|
|
247
|
use Sisimai::RFC1123; |
|
|
86
|
|
|
|
|
94
|
|
|
|
86
|
|
|
|
|
983
|
|
|
9
|
86
|
|
|
86
|
|
232
|
use Sisimai::RFC1894; |
|
|
86
|
|
|
|
|
91
|
|
|
|
86
|
|
|
|
|
864
|
|
|
10
|
86
|
|
|
86
|
|
209
|
use Sisimai::RFC5322; |
|
|
86
|
|
|
|
|
84
|
|
|
|
86
|
|
|
|
|
842
|
|
|
11
|
86
|
|
|
86
|
|
29696
|
use Sisimai::Reason; |
|
|
86
|
|
|
|
|
168
|
|
|
|
86
|
|
|
|
|
2586
|
|
|
12
|
86
|
|
|
86
|
|
421
|
use Sisimai::Address; |
|
|
86
|
|
|
|
|
97
|
|
|
|
86
|
|
|
|
|
1577
|
|
|
13
|
86
|
|
|
86
|
|
36216
|
use Sisimai::DateTime; |
|
|
86
|
|
|
|
|
352
|
|
|
|
86
|
|
|
|
|
3135
|
|
|
14
|
86
|
|
|
86
|
|
29616
|
use Sisimai::Time; |
|
|
86
|
|
|
|
|
208
|
|
|
|
86
|
|
|
|
|
681
|
|
|
15
|
86
|
|
|
86
|
|
35337
|
use Sisimai::SMTP::Command; |
|
|
86
|
|
|
|
|
311
|
|
|
|
86
|
|
|
|
|
2717
|
|
|
16
|
86
|
|
|
86
|
|
31851
|
use Sisimai::SMTP::Failure; |
|
|
86
|
|
|
|
|
218
|
|
|
|
86
|
|
|
|
|
2720
|
|
|
17
|
86
|
|
|
86
|
|
472
|
use Sisimai::String; |
|
|
86
|
|
|
|
|
105
|
|
|
|
86
|
|
|
|
|
1575
|
|
|
18
|
86
|
|
|
86
|
|
29123
|
use Sisimai::Rhost; |
|
|
86
|
|
|
|
|
228
|
|
|
|
86
|
|
|
|
|
2644
|
|
|
19
|
86
|
|
|
86
|
|
29547
|
use Sisimai::LDA; |
|
|
86
|
|
|
|
|
187
|
|
|
|
86
|
|
|
|
|
5957
|
|
|
20
|
86
|
|
|
|
|
878
|
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
|
|
|
|
|
|
|
'bogus', # [Integer] EXPERIMENTAL |
|
25
|
|
|
|
|
|
|
'catch', # [?] Results generated by hook method |
|
26
|
|
|
|
|
|
|
'command', # [String] The last SMTP command |
|
27
|
|
|
|
|
|
|
'decodedby', # [String] MTA Module name since v5.2.0 |
|
28
|
|
|
|
|
|
|
'deliverystatus', # [String] Delivery Status(DSN) |
|
29
|
|
|
|
|
|
|
'destination', # [String] The domain part of the "recipient" |
|
30
|
|
|
|
|
|
|
'diagnosticcode', # [String] Diagnostic-Code: Header |
|
31
|
|
|
|
|
|
|
'diagnostictype', # [String] The 1st part of Diagnostic-Code: Header |
|
32
|
|
|
|
|
|
|
'feedbackid', # [String] The value of Feedback-ID: header of the original message |
|
33
|
|
|
|
|
|
|
'feedbacktype', # [String] Feedback Type |
|
34
|
|
|
|
|
|
|
'hardbounce', # [Integer] 1 = Hard bounce, 0 = Is not a hard bounce |
|
35
|
|
|
|
|
|
|
'lhost', # [String] local host name/Local MTA |
|
36
|
|
|
|
|
|
|
'listid', # [String] List-Id header of each ML |
|
37
|
|
|
|
|
|
|
'messageid', # [String] Message-Id: header |
|
38
|
|
|
|
|
|
|
'origin', # [String] Email path as a data source |
|
39
|
|
|
|
|
|
|
'reason', # [String] Bounce reason |
|
40
|
|
|
|
|
|
|
'recipient', # [Sisimai::Address] Recipient address which bounced |
|
41
|
|
|
|
|
|
|
'replycode', # [String] SMTP Reply Code |
|
42
|
|
|
|
|
|
|
'rhost', # [String] Remote host name/Remote MTA |
|
43
|
|
|
|
|
|
|
'senderdomain', # [String] The domain part of the "addresser" |
|
44
|
|
|
|
|
|
|
'subject', # [String] UTF-8 Subject text |
|
45
|
|
|
|
|
|
|
'timestamp', # [Sisimai::Time] Date: header in the original message |
|
46
|
|
|
|
|
|
|
'timezoneoffset', # [Integer] Time zone offset(seconds) |
|
47
|
|
|
|
|
|
|
'token', # [String] Message token/MD5 Hex digest value |
|
48
|
|
|
|
|
|
|
'toxic', # [Integer] EXPERIMENTAL |
|
49
|
86
|
|
|
86
|
|
445
|
]); |
|
|
86
|
|
|
|
|
127
|
|
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
sub rise { |
|
52
|
|
|
|
|
|
|
# Constructor of Sisimai::Fact |
|
53
|
|
|
|
|
|
|
# @param [Hash] argvs |
|
54
|
|
|
|
|
|
|
# @options argvs [String] data Entire email message |
|
55
|
|
|
|
|
|
|
# @options argvs [Integer] delivered 1 if the result which has "delivered" reason is included |
|
56
|
|
|
|
|
|
|
# @options argvs [Integer] vacation 1 if the result which has "vacation" reason is included |
|
57
|
|
|
|
|
|
|
# @options argvs [Code] hook Code reference to callback method |
|
58
|
|
|
|
|
|
|
# @options argvs [String] origin Path to the original email file |
|
59
|
|
|
|
|
|
|
# @return [Array] Array of Sisimai::Fact objects |
|
60
|
3514
|
|
|
3514
|
1
|
222185
|
my $class = shift; |
|
61
|
3514
|
|
100
|
|
|
7625
|
my $argvs = shift || return undef; |
|
62
|
3513
|
50
|
|
|
|
8422
|
die ' ***error: Sisimai::Fact->rise receives only a HASH reference as an argument' unless ref $argvs eq 'HASH'; |
|
63
|
|
|
|
|
|
|
|
|
64
|
3513
|
|
100
|
|
|
7609
|
my $email = $argvs->{'data'} || return undef; |
|
65
|
3512
|
|
|
|
|
8822
|
my $args1 = {'data' => $email, 'hook' => $argvs->{'hook'}}; |
|
66
|
3512
|
|
100
|
|
|
17258
|
my $mesg1 = Sisimai::Message->rise($args1) || return undef; |
|
67
|
|
|
|
|
|
|
|
|
68
|
3471
|
50
|
|
|
|
7760
|
return undef unless $mesg1->{'ds'}; |
|
69
|
3471
|
50
|
|
|
|
7187
|
return undef unless $mesg1->{'rfc822'}; |
|
70
|
|
|
|
|
|
|
|
|
71
|
3471
|
|
|
|
|
4636
|
state $retryindex = Sisimai::Reason->retry; |
|
72
|
3471
|
|
|
|
|
3712
|
state $rfc822head = Sisimai::RFC5322::HEADERTABLE; |
|
73
|
3471
|
|
|
|
|
4062
|
state $actionlist = {'delayed' => 1, 'delivered' => 1, 'expanded' => 1, 'failed' => 1, 'relayed' => 1}; |
|
74
|
3471
|
|
|
|
|
5402
|
my $rfc822data = $mesg1->{'rfc822'}; |
|
75
|
3471
|
|
|
|
|
3767
|
my $listoffact = []; |
|
76
|
|
|
|
|
|
|
|
|
77
|
3471
|
|
|
|
|
6865
|
RISEOF: for my $e ( $mesg1->{'ds'}->@* ) { |
|
78
|
|
|
|
|
|
|
# Create parameters |
|
79
|
3658
|
50
|
|
|
|
7692
|
next if length $e->{'recipient'} < 5; |
|
80
|
3658
|
100
|
100
|
|
|
12943
|
next if ! $argvs->{'delivered'} && index($e->{'status'}, '2.') == 0; |
|
81
|
3646
|
100
|
100
|
|
|
11038
|
next if ! $argvs->{'vacation'} && $e->{'reason'} eq 'vacation'; |
|
82
|
|
|
|
|
|
|
|
|
83
|
3625
|
|
|
|
|
4353
|
my $thing = {}; # To be blessed and pushed into the array above at the end of the loop |
|
84
|
|
|
|
|
|
|
my $piece = { |
|
85
|
|
|
|
|
|
|
'action' => $e->{'action'} // '', |
|
86
|
|
|
|
|
|
|
'alias' => $e->{'alias'} // '', |
|
87
|
|
|
|
|
|
|
'catch' => $mesg1->{'catch'} // undef, |
|
88
|
|
|
|
|
|
|
'decodedby' => $e->{'agent'} // '', |
|
89
|
|
|
|
|
|
|
'deliverystatus' => $e->{'status'} // '', |
|
90
|
|
|
|
|
|
|
'diagnosticcode' => $e->{'diagnosis'} // '', |
|
91
|
|
|
|
|
|
|
'diagnostictype' => $e->{'spec'} // '', |
|
92
|
|
|
|
|
|
|
'feedbacktype' => $e->{'feedbacktype'} // '', |
|
93
|
|
|
|
|
|
|
'hardbounce' => 0, |
|
94
|
|
|
|
|
|
|
'lhost' => $e->{'lhost'} // '', |
|
95
|
|
|
|
|
|
|
'origin' => $argvs->{'origin'} // '', |
|
96
|
|
|
|
|
|
|
'reason' => $e->{'reason'} // '', |
|
97
|
|
|
|
|
|
|
'recipient' => $e->{'recipient'} // '', |
|
98
|
|
|
|
|
|
|
'replycode' => $e->{'replycode'} // '', |
|
99
|
|
|
|
|
|
|
'rhost' => $e->{'rhost'} // '', |
|
100
|
3625
|
|
50
|
|
|
72997
|
'command' => $e->{'command'} // '', |
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
101
|
|
|
|
|
|
|
}; |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
ADDRESSER: { |
|
104
|
|
|
|
|
|
|
# Detect an email address from message/rfc822 part |
|
105
|
3625
|
|
|
|
|
4429
|
my $j = []; |
|
|
3625
|
|
|
|
|
3613
|
|
|
106
|
3625
|
|
|
|
|
6858
|
for my $f ( $rfc822head->{'addresser'}->@* ) { |
|
107
|
|
|
|
|
|
|
# Check each header in message/rfc822 part |
|
108
|
5472
|
100
|
|
|
|
11325
|
next unless exists $rfc822data->{ $f }; |
|
109
|
3379
|
50
|
|
|
|
5918
|
next unless $rfc822data->{ $f }; |
|
110
|
|
|
|
|
|
|
|
|
111
|
3379
|
|
50
|
|
|
13026
|
$j = Sisimai::Address->find($rfc822data->{ $f }) || next; |
|
112
|
3379
|
|
|
|
|
6350
|
$piece->{'addresser'} = shift @$j; |
|
113
|
3379
|
|
|
|
|
6039
|
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
|
246
|
|
50
|
|
|
1327
|
$j = Sisimai::Address->find($mesg1->{'header'}->{'to'}) || []; |
|
119
|
246
|
|
|
|
|
598
|
$piece->{'addresser'} = shift @$j; |
|
120
|
|
|
|
|
|
|
} |
|
121
|
3625
|
50
|
|
|
|
5975
|
next RISEOF unless $piece->{'addresser'}; |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
TIMESTAMP: { |
|
124
|
|
|
|
|
|
|
# Convert from a time stamp or a date string to a machine time. |
|
125
|
3625
|
|
|
|
|
4231
|
my $datestring = undef; |
|
|
3625
|
|
|
|
|
4634
|
|
|
126
|
3625
|
|
|
|
|
3501
|
my $zoneoffset = 0; |
|
127
|
3625
|
100
|
|
|
|
3588
|
my @datevalues; push @datevalues, $e->{'date'} if $e->{'date'}; |
|
|
3625
|
|
|
|
|
8311
|
|
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# Date information did not exist in message/delivery-status part,... |
|
130
|
3625
|
|
|
|
|
6849
|
for my $f ( $rfc822head->{'date'}->@* ) { |
|
131
|
|
|
|
|
|
|
# Get the value of Date header or other date related header. |
|
132
|
14500
|
100
|
|
|
|
21523
|
next unless $rfc822data->{ $f }; |
|
133
|
3185
|
|
|
|
|
6121
|
push @datevalues, $rfc822data->{ $f }; |
|
134
|
|
|
|
|
|
|
} |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
# Set "date" getting from the value of "Date" in the bounce message |
|
137
|
3625
|
100
|
|
|
|
8008
|
push @datevalues, $mesg1->{'header'}->{'date'} if scalar(@datevalues) < 2; |
|
138
|
|
|
|
|
|
|
|
|
139
|
3625
|
|
|
|
|
8904
|
while( my $v = shift @datevalues ) { |
|
140
|
|
|
|
|
|
|
# Decode each date value in the array |
|
141
|
3625
|
|
50
|
|
|
22408
|
$datestring = Sisimai::DateTime->parse($v) || next; |
|
142
|
|
|
|
|
|
|
|
|
143
|
3625
|
50
|
|
|
|
16432
|
if( $datestring =~ /\A(.+)[ ]+([-+]\d{4})\z/ ) { |
|
144
|
|
|
|
|
|
|
# Get the value of timezone offset from $datestring: Wed, 26 Feb 2014 06:05:48 -0500 |
|
145
|
3625
|
|
|
|
|
6416
|
$datestring = $1; |
|
146
|
3625
|
|
|
|
|
10240
|
$zoneoffset = Sisimai::DateTime->tz2second($2); |
|
147
|
3625
|
|
|
|
|
7763
|
$piece->{'timezoneoffset'} = $2; |
|
148
|
|
|
|
|
|
|
} |
|
149
|
3625
|
50
|
|
|
|
7884
|
last if $datestring; |
|
150
|
|
|
|
|
|
|
} |
|
151
|
|
|
|
|
|
|
|
|
152
|
3625
|
|
|
|
|
4914
|
eval { |
|
153
|
|
|
|
|
|
|
# Convert from the date string to an object then calculate time zone offset. |
|
154
|
3625
|
|
|
|
|
22367
|
my $t = Sisimai::Time->strptime($datestring, '%a, %d %b %Y %T'); |
|
155
|
3625
|
|
50
|
|
|
136397
|
$piece->{'timestamp'} = ($t->epoch - $zoneoffset) // undef; |
|
156
|
|
|
|
|
|
|
}; |
|
157
|
|
|
|
|
|
|
} |
|
158
|
3625
|
50
|
|
|
|
176746
|
next RISEOF unless defined $piece->{'timestamp'}; |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
RECEIVED: { |
|
161
|
|
|
|
|
|
|
# Scan "Received:" header of the original message |
|
162
|
3625
|
|
50
|
|
|
5540
|
my $recv = $mesg1->{'header'}->{'received'} || []; |
|
|
3625
|
|
|
|
|
10540
|
|
|
163
|
3625
|
100
|
|
|
|
7215
|
unless( $piece->{'rhost'} ) { |
|
164
|
|
|
|
|
|
|
# Try to pick a remote hostname from Received: headers of the bounce message |
|
165
|
1675
|
|
|
|
|
8901
|
my $ir = Sisimai::RFC1123->find($e->{'diagnosis'}); |
|
166
|
1675
|
100
|
|
|
|
3558
|
$piece->{'rhost'} = $ir if Sisimai::RFC1123->is_internethost($ir); |
|
167
|
|
|
|
|
|
|
|
|
168
|
1675
|
100
|
|
|
|
3919
|
unless( $piece->{'rhost'} ) { |
|
169
|
|
|
|
|
|
|
# The remote hostname in the error message did not exist or is not a valid |
|
170
|
|
|
|
|
|
|
# internet hostname |
|
171
|
1259
|
|
|
|
|
2411
|
for my $re ( reverse @$recv ) { |
|
172
|
|
|
|
|
|
|
# Check the Received: headers backwards and get a remote hostname |
|
173
|
1973
|
100
|
|
|
|
4011
|
last if $piece->{'rhost'}; |
|
174
|
1529
|
|
|
|
|
5003
|
my $cv = Sisimai::RFC5322->received($re)->[0]; |
|
175
|
1529
|
100
|
|
|
|
4852
|
next unless Sisimai::RFC1123->is_internethost($cv); |
|
176
|
1111
|
|
|
|
|
2692
|
$piece->{'rhost'} = $cv; |
|
177
|
|
|
|
|
|
|
} |
|
178
|
|
|
|
|
|
|
} |
|
179
|
|
|
|
|
|
|
} |
|
180
|
3625
|
100
|
|
|
|
8274
|
$piece->{'lhost'} = '' if $piece->{'lhost'} eq $piece->{'rhost'}; |
|
181
|
|
|
|
|
|
|
|
|
182
|
3625
|
100
|
|
|
|
6022
|
unless( $piece->{'lhost'} ) { |
|
183
|
|
|
|
|
|
|
# Try to pick a local hostname from Received: headers of the bounce message |
|
184
|
1588
|
|
|
|
|
2733
|
for my $le ( @$recv ) { |
|
185
|
|
|
|
|
|
|
# Check the Received: headers forwards and get a local hostname |
|
186
|
1781
|
|
|
|
|
4489
|
my $cv = Sisimai::RFC5322->received($le)->[0]; |
|
187
|
1781
|
100
|
|
|
|
6100
|
next unless Sisimai::RFC1123->is_internethost($cv); |
|
188
|
1187
|
|
|
|
|
2185
|
$piece->{'lhost'} = $cv; last; |
|
|
1187
|
|
|
|
|
1973
|
|
|
189
|
|
|
|
|
|
|
} |
|
190
|
|
|
|
|
|
|
} |
|
191
|
|
|
|
|
|
|
|
|
192
|
3625
|
|
|
|
|
5135
|
for my $v ('rhost', 'lhost') { |
|
193
|
|
|
|
|
|
|
# Check and rewrite each host name |
|
194
|
7250
|
100
|
|
|
|
11348
|
next unless length $piece->{ $v }; |
|
195
|
6701
|
50
|
|
|
|
10283
|
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
|
6701
|
|
|
|
|
15736
|
y/[]()\r//d, s/\A.+=// for $piece->{ $v }; # Remove [], (), \r, and strings before "=" |
|
200
|
|
|
|
|
|
|
|
|
201
|
6701
|
100
|
|
|
|
9554
|
if( index($piece->{ $v }, ' ') > -1 ) { |
|
202
|
|
|
|
|
|
|
# Check a space character in each value and get the first hostname |
|
203
|
77
|
|
|
|
|
191
|
my @ee = split(' ', $piece->{ $v }); |
|
204
|
77
|
|
|
|
|
118
|
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
|
|
|
|
320
|
next if Sisimai::RFC791->is_ipv4address($w); |
|
208
|
77
|
|
|
|
|
138
|
$piece->{ $v } = $w; last; |
|
|
77
|
|
|
|
|
79
|
|
|
209
|
|
|
|
|
|
|
} |
|
210
|
77
|
50
|
|
|
|
187
|
$piece->{ $v } = $ee[0] if index($piece->{ $v }, ' ') > 0; |
|
211
|
|
|
|
|
|
|
} |
|
212
|
6701
|
100
|
|
|
|
14173
|
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
|
3625
|
|
|
|
|
4542
|
my $p0 = 0; |
|
|
3625
|
|
|
|
|
3742
|
|
|
219
|
3625
|
|
|
|
|
3945
|
my $p1 = 0; |
|
220
|
3625
|
100
|
|
|
|
16197
|
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
|
3093
|
|
|
|
|
5553
|
$p0 = index($rfc822data->{'message-id'}, '<') + 1; |
|
224
|
3093
|
|
|
|
|
3930
|
$p1 = index($rfc822data->{'message-id'}, '>'); |
|
225
|
3093
|
|
|
|
|
7727
|
$piece->{'messageid'} = substr($rfc822data->{'message-id'}, $p0, $p1 - $p0); |
|
226
|
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
} else { |
|
228
|
|
|
|
|
|
|
# Invalid value of the Message-Id: field |
|
229
|
532
|
|
|
|
|
974
|
$piece->{'messageid'} = ''; |
|
230
|
|
|
|
|
|
|
} |
|
231
|
|
|
|
|
|
|
|
|
232
|
3625
|
100
|
|
|
|
12840
|
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
|
|
|
|
|
59
|
$p0 = index($rfc822data->{'list-id'}, '<') + 1; |
|
236
|
27
|
|
|
|
|
57
|
$p1 = index($rfc822data->{'list-id'}, '>'); |
|
237
|
27
|
|
|
|
|
85
|
$piece->{'listid'} = substr($rfc822data->{'list-id'}, $p0, $p1 - $p0); |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
} else { |
|
240
|
|
|
|
|
|
|
# Invalid value of the List-Id: field |
|
241
|
3598
|
|
|
|
|
6815
|
$piece->{'listid'} = ''; |
|
242
|
|
|
|
|
|
|
} |
|
243
|
|
|
|
|
|
|
} |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
DIAGNOSTICCODE: { |
|
246
|
|
|
|
|
|
|
# Cleanup the value of "Diagnostic-Code:" header |
|
247
|
3625
|
100
|
|
|
|
4433
|
last unless length $piece->{'diagnosticcode'}; |
|
|
3625
|
|
|
|
|
6577
|
|
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
# Get an SMTP Reply Code and an SMTP Enhanced Status Code |
|
250
|
3615
|
50
|
|
|
|
7296
|
chop $piece->{'diagnosticcode'} if substr($piece->{'diagnosticcode'}, -1, 1) eq "\r"; |
|
251
|
|
|
|
|
|
|
|
|
252
|
3615
|
|
100
|
|
|
17555
|
my $cs = Sisimai::SMTP::Status->find($piece->{'diagnosticcode'}) || ''; |
|
253
|
3615
|
|
100
|
|
|
18355
|
my $cr = Sisimai::SMTP::Reply->find( $piece->{'diagnosticcode'}, $cs) || ''; |
|
254
|
3615
|
|
|
|
|
12313
|
$piece->{'deliverystatus'} = Sisimai::SMTP::Status->prefer($piece->{'deliverystatus'}, $cs, $cr); |
|
255
|
|
|
|
|
|
|
|
|
256
|
3615
|
100
|
|
|
|
8914
|
if( length $cr == 3 ) { |
|
257
|
|
|
|
|
|
|
# There is an SMTP reply code in the error message |
|
258
|
2600
|
|
66
|
|
|
7154
|
$piece->{'replycode'} ||= $cr; |
|
259
|
|
|
|
|
|
|
|
|
260
|
2600
|
100
|
|
|
|
6824
|
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
|
|
|
|
|
391
|
for my $q ('-', ' ') { |
|
274
|
|
|
|
|
|
|
# Remove strings: "550-5.7.1", and "550 5.7.1" from the error message |
|
275
|
414
|
|
|
|
|
574
|
my $cx = sprintf("%s%s%s", $cr, $q, $cs); |
|
276
|
414
|
|
|
|
|
758
|
my $p0 = index($piece->{'diagnosticcode'}, $cx); |
|
277
|
414
|
|
|
|
|
889
|
while( $p0 > -1 ) { |
|
278
|
|
|
|
|
|
|
# Remove strings like "550-5.7.1" |
|
279
|
801
|
|
|
|
|
1180
|
substr($piece->{'diagnosticcode'}, $p0, length $cx, ''); |
|
280
|
801
|
|
|
|
|
1335
|
$p0 = index($piece->{'diagnosticcode'}, $cx); |
|
281
|
|
|
|
|
|
|
} |
|
282
|
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
# Remove "553-" and "553 " (SMTP reply code only) from the error message |
|
284
|
414
|
|
|
|
|
462
|
$cx = sprintf("%s%s", $cr, $q); |
|
285
|
414
|
|
|
|
|
561
|
$p0 = index($piece->{'diagnosticcode'}, $cx); |
|
286
|
414
|
|
|
|
|
762
|
while( $p0 > -1 ) { |
|
287
|
|
|
|
|
|
|
# Remove strings like "553-" |
|
288
|
42
|
|
|
|
|
98
|
substr($piece->{'diagnosticcode'}, $p0, length $cx, ''); |
|
289
|
42
|
|
|
|
|
84
|
$p0 = index($piece->{'diagnosticcode'}, $cx); |
|
290
|
|
|
|
|
|
|
} |
|
291
|
|
|
|
|
|
|
} |
|
292
|
|
|
|
|
|
|
|
|
293
|
207
|
50
|
|
|
|
617
|
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
|
3615
|
|
|
|
|
8288
|
my $dc = lc $piece->{'diagnosticcode'}; |
|
302
|
3615
|
|
|
|
|
5851
|
my $p1 = index($dc, ''); |
|
303
|
3615
|
|
|
|
|
4542
|
my $p2 = index($dc, ''); |
|
304
|
3615
|
100
|
66
|
|
|
7785
|
substr($piece->{'diagnosticcode'}, $p1, $p2 + 7 - $p1, '') if $p1 > 0 && $p2 > 0; |
|
305
|
3615
|
|
|
|
|
10788
|
$piece->{'diagnosticcode'} = Sisimai::String->sweep($piece->{'diagnosticcode'}); |
|
306
|
|
|
|
|
|
|
} |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
DIAGNOSTICTYPE: { |
|
309
|
|
|
|
|
|
|
# Set the value of "diagnostictype" if it is empty |
|
310
|
3625
|
50
|
0
|
|
|
3955
|
$piece->{'diagnostictype'} ||= 'X-UNIX' if $piece->{'reason'} eq 'mailererror'; |
|
|
3625
|
|
|
|
|
7624
|
|
|
311
|
3625
|
100
|
100
|
|
|
5084
|
$piece->{'diagnostictype'} ||= 'SMTP' unless grep { $piece->{'reason'} eq $_ } ('feedback', 'vacation'); |
|
|
7250
|
|
|
|
|
18692
|
|
|
312
|
|
|
|
|
|
|
} |
|
313
|
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
# Check the Subject field of the original message |
|
315
|
3625
|
|
50
|
|
|
10195
|
$piece->{'subject'} = $rfc822data->{'subject'} // ''; |
|
316
|
3625
|
50
|
|
|
|
8808
|
chop $piece->{'subject'} if substr($piece->{'subject'}, -1, 1) eq "\r"; |
|
317
|
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
# When "RCPT first" in the error message, set "RCPT" as the last command. |
|
319
|
|
|
|
|
|
|
# - <<< 503 RCPT first (#5.5.1) |
|
320
|
|
|
|
|
|
|
# - <<< 503-5.5.1 RCPT first. A mail transaction protocol command was issued ... |
|
321
|
|
|
|
|
|
|
# - RCPT first (in reply to DATA command) |
|
322
|
3625
|
100
|
|
|
|
15775
|
$piece->{'command'} = '' unless Sisimai::SMTP::Command->test($piece->{'command'}); |
|
323
|
3625
|
100
|
|
|
|
9377
|
$piece->{'command'} = 'RCPT' if index($piece->{'diagnosticcode'}, 'RCPT first') > -1; |
|
324
|
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
CONSTRUCTOR: { |
|
326
|
|
|
|
|
|
|
# Create email address object |
|
327
|
3625
|
|
50
|
|
|
3425
|
my $as = Sisimai::Address->new($piece->{'addresser'}) || next RISEOF; |
|
|
3625
|
|
|
|
|
11528
|
|
|
328
|
3625
|
|
50
|
|
|
14098
|
my $ar = Sisimai::Address->new({'address' => $piece->{'recipient'}}) || next RISEOF; |
|
329
|
3625
|
|
|
|
|
16807
|
my @ea = (qw| |
|
330
|
|
|
|
|
|
|
action command decodedby deliverystatus diagnosticcode diagnostictype feedbacktype |
|
331
|
|
|
|
|
|
|
lhost listid messageid origin reason replycode rhost subject |
|
332
|
|
|
|
|
|
|
|); |
|
333
|
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
$thing = { |
|
335
|
|
|
|
|
|
|
'addresser' => $as, |
|
336
|
|
|
|
|
|
|
'recipient' => $ar, |
|
337
|
|
|
|
|
|
|
'senderdomain' => $as->host, |
|
338
|
|
|
|
|
|
|
'destination' => $ar->host, |
|
339
|
|
|
|
|
|
|
'alias' => $piece->{'alias'} || $ar->alias, |
|
340
|
3625
|
|
66
|
|
|
12865
|
'token' => __PACKAGE__->maketoken($as->address, $ar->address, $piece->{'timestamp'}), |
|
341
|
|
|
|
|
|
|
}; |
|
342
|
3625
|
|
50
|
|
|
112315
|
$thing->{ $_ } ||= $piece->{ $_ } // '' for @ea; |
|
|
|
|
66
|
|
|
|
|
|
343
|
3625
|
|
|
|
|
7417
|
$thing->{'bogus'} = 0; |
|
344
|
3625
|
|
100
|
|
|
15253
|
$thing->{'catch'} = $piece->{'catch'} // undef; |
|
345
|
3625
|
|
|
|
|
6411
|
$thing->{"feedbackid"} = ""; |
|
346
|
3625
|
|
|
|
|
6721
|
$thing->{'hardbounce'} = int $piece->{'hardbounce'}; |
|
347
|
3625
|
|
100
|
|
|
9130
|
$thing->{'replycode'} ||= Sisimai::SMTP::Reply->find($piece->{'diagnosticcode'}) || ''; |
|
|
|
|
100
|
|
|
|
|
|
348
|
3625
|
|
|
|
|
13702
|
$thing->{'timestamp'} = Sisimai::Time->new($piece->{'timestamp'}); |
|
349
|
3625
|
|
50
|
|
|
268403
|
$thing->{'timezoneoffset'} = $piece->{'timezoneoffset'} // '+0000'; |
|
350
|
3625
|
|
|
|
|
11821
|
$thing->{'toxic'} = 0; |
|
351
|
|
|
|
|
|
|
} |
|
352
|
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
ALIAS: { |
|
354
|
|
|
|
|
|
|
# Look up the Envelope-To address from the Received: header in the original message |
|
355
|
|
|
|
|
|
|
# when the recipient address is same with the value of $o->{'alias'}. |
|
356
|
3625
|
100
|
100
|
|
|
4049
|
last if length $thing->{'alias'} == 0 || $thing->{'recipient'}->address ne $thing->{'alias'}; |
|
|
3625
|
|
|
|
|
11919
|
|
|
357
|
676
|
50
|
|
|
|
6051
|
last unless exists $rfc822data->{'received'}; |
|
358
|
676
|
100
|
|
|
|
1644
|
last unless scalar $rfc822data->{'received'}->@*; |
|
359
|
|
|
|
|
|
|
|
|
360
|
603
|
|
|
|
|
1304
|
for my $er ( reverse $rfc822data->{'received'}->@* ) { |
|
361
|
|
|
|
|
|
|
# Search for the string " for " from the Received: header |
|
362
|
1093
|
100
|
|
|
|
5114
|
next unless index($er, ' for ') > 1; |
|
363
|
791
|
|
|
|
|
2835
|
my $or = Sisimai::RFC5322->received($er); |
|
364
|
|
|
|
|
|
|
|
|
365
|
791
|
50
|
33
|
|
|
3052
|
next if scalar(@$or) == 0 || length($or->[5]) == 0; |
|
366
|
791
|
50
|
|
|
|
2933
|
next if Sisimai::Address->is_emailaddress($or->[5]) == 0; |
|
367
|
791
|
100
|
|
|
|
3165
|
next if $thing->{'recipient'}->address eq $or->[5]; |
|
368
|
|
|
|
|
|
|
|
|
369
|
33
|
|
|
|
|
305
|
$thing->{'alias'} = $or->[5]; |
|
370
|
33
|
|
|
|
|
99
|
last ALIAS; |
|
371
|
|
|
|
|
|
|
} |
|
372
|
|
|
|
|
|
|
} |
|
373
|
3625
|
100
|
|
|
|
13747
|
$thing->{'alias'} = '' if $thing->{'alias'} eq $thing->{'recipient'}->{'address'}; |
|
374
|
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
REASON: { |
|
376
|
|
|
|
|
|
|
# Decide the reason of the email bounce |
|
377
|
3625
|
100
|
100
|
|
|
4831
|
if( $thing->{'reason'} eq '' || exists $retryindex->{ $thing->{'reason'} } ) { |
|
|
3625
|
|
|
|
|
11296
|
|
|
378
|
|
|
|
|
|
|
# The value of "reason" is empty or is needed to check with other values again |
|
379
|
3360
|
|
100
|
|
|
9023
|
my $re = $thing->{'reason'} || 'undefined'; |
|
380
|
3360
|
|
|
|
|
3773
|
my $cr = "Sisimai::Reason"; |
|
381
|
3360
|
100
|
|
|
|
19429
|
my $or = Sisimai::LDA->find($thing); if( $cr->is_explicit($or) ){ $thing->{'reason'} = $or; last } |
|
|
3360
|
|
|
|
|
16375
|
|
|
|
32
|
|
|
|
|
50
|
|
|
|
32
|
|
|
|
|
60
|
|
|
382
|
3328
|
100
|
|
|
|
14535
|
$or = Sisimai::Rhost->find($thing); if( $cr->is_explicit($or) ){ $thing->{'reason'} = $or; last } |
|
|
3328
|
|
|
|
|
9246
|
|
|
|
833
|
|
|
|
|
1345
|
|
|
|
833
|
|
|
|
|
1533
|
|
|
383
|
2495
|
100
|
|
|
|
7978
|
$or = Sisimai::Reason->find($thing); if( $cr->is_explicit($or) ){ $thing->{'reason'} = $or; last } |
|
|
2495
|
|
|
|
|
6644
|
|
|
|
2453
|
|
|
|
|
4136
|
|
|
|
2453
|
|
|
|
|
4112
|
|
|
384
|
42
|
100
|
|
|
|
173
|
$thing->{'reason'} = $thing->{'diagnosticcode'} ? "onhold" : $re; |
|
385
|
|
|
|
|
|
|
} |
|
386
|
|
|
|
|
|
|
} |
|
387
|
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
HARDBOUNCE: { |
|
389
|
|
|
|
|
|
|
# Set the value of "hardbounce", default value of "bouncebounce" is 0 |
|
390
|
3625
|
100
|
100
|
|
|
3933
|
if( $thing->{'reason'} eq 'delivered' || $thing->{'reason'} eq 'feedback' || $thing->{'reason'} eq 'vacation' ) { |
|
|
3625
|
|
100
|
|
|
16850
|
|
|
391
|
|
|
|
|
|
|
# Delete the value of ReplyCode when the Reason is "feedback" or "vacation" |
|
392
|
120
|
100
|
|
|
|
323
|
$thing->{'replycode'} = '' unless $thing->{'reason'} eq 'delivered'; |
|
393
|
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
} else { |
|
395
|
|
|
|
|
|
|
# The reason is not "delivered", or "feedback", or "vacation" |
|
396
|
3505
|
|
|
|
|
7684
|
my $smtperrors = $piece->{'deliverystatus'}.' '.$piece->{'diagnosticcode'}; |
|
397
|
3505
|
100
|
|
|
|
6658
|
$smtperrors = '' if length $smtperrors < 4; |
|
398
|
3505
|
|
|
|
|
18994
|
$thing->{'hardbounce'} = Sisimai::SMTP::Failure->is_hardbounce($thing->{'reason'}, $smtperrors); |
|
399
|
|
|
|
|
|
|
} |
|
400
|
|
|
|
|
|
|
} |
|
401
|
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
DELIVERYSTATUS: { |
|
403
|
|
|
|
|
|
|
# Set pseudo status code |
|
404
|
3625
|
100
|
|
|
|
3957
|
last DELIVERYSTATUS if $thing->{'deliverystatus'}; |
|
|
3625
|
|
|
|
|
7722
|
|
|
405
|
|
|
|
|
|
|
|
|
406
|
796
|
|
|
|
|
1850
|
my $smtperrors = $thing->{'replycode'}.' '.$piece->{'diagnosticcode'}; |
|
407
|
796
|
100
|
|
|
|
1397
|
$smtperrors = '' if length $smtperrors < 4; |
|
408
|
796
|
|
|
|
|
2296
|
my $permanent0 = Sisimai::SMTP::Failure->is_permanent($smtperrors); |
|
409
|
796
|
|
|
|
|
1973
|
my $temporary0 = Sisimai::SMTP::Failure->is_temporary($smtperrors); |
|
410
|
796
|
100
|
|
|
|
2229
|
my $temporary1 = $permanent0.$temporary0 eq "00" ? 0 : $temporary0; |
|
411
|
796
|
|
|
|
|
3049
|
$thing->{'deliverystatus'} = Sisimai::SMTP::Status->code($thing->{'reason'}, $temporary1); |
|
412
|
|
|
|
|
|
|
} |
|
413
|
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
REPLYCODE: { |
|
415
|
|
|
|
|
|
|
# Check both of the first digit of "deliverystatus" and "replycode" |
|
416
|
3625
|
|
|
|
|
3464
|
my $cx = [substr($thing->{'deliverystatus'}, 0, 1), substr($thing->{'replycode'}, 0, 1)]; |
|
|
3625
|
|
|
|
|
11294
|
|
|
417
|
3625
|
100
|
|
|
|
7678
|
if( $cx->[0] ne $cx->[1] ) { |
|
418
|
|
|
|
|
|
|
# The class of the "Status:" is defer with the first digit of the reply code |
|
419
|
940
|
|
100
|
|
|
3036
|
$cx->[1] = Sisimai::SMTP::Reply->find($piece->{'diagnosticcode'}, $cx->[0]) || ''; |
|
420
|
940
|
100
|
|
|
|
2811
|
$thing->{'replycode'} = index($cx->[1], $cx->[0]) == 0 ? $cx->[1] : ''; |
|
421
|
|
|
|
|
|
|
} |
|
422
|
|
|
|
|
|
|
|
|
423
|
3625
|
100
|
|
|
|
11204
|
unless( exists $actionlist->{ $thing->{'action'} } ) { |
|
424
|
|
|
|
|
|
|
# There is an action value which is not described at RFC1894 |
|
425
|
1685
|
50
|
|
|
|
10372
|
if( my $ox = Sisimai::RFC1894->field('Action: '.$thing->{'action'}) ) { |
|
426
|
|
|
|
|
|
|
# Rewrite the value of "Action:" field to the valid value |
|
427
|
|
|
|
|
|
|
# |
|
428
|
|
|
|
|
|
|
# The syntax for the action-field is: |
|
429
|
|
|
|
|
|
|
# action-field = "Action" ":" action-value |
|
430
|
|
|
|
|
|
|
# action-value = "failed" / "delayed" / "delivered" / "relayed" / "expanded" |
|
431
|
1685
|
|
|
|
|
2888
|
$thing->{'action'} = $ox->[2]; |
|
432
|
|
|
|
|
|
|
} |
|
433
|
|
|
|
|
|
|
} |
|
434
|
3625
|
100
|
|
|
|
7811
|
$thing->{'action'} = 'delivered' if $thing->{'reason'} eq 'delivered'; |
|
435
|
3625
|
100
|
100
|
|
|
6595
|
$thing->{'action'} ||= 'delayed' if $thing->{'reason'} eq 'expired'; |
|
436
|
3625
|
100
|
100
|
|
|
17137
|
$thing->{'action'} ||= 'failed' if $cx->[0] eq '4' || $cx->[0] eq '5'; |
|
|
|
|
100
|
|
|
|
|
|
437
|
3625
|
|
100
|
|
|
8217
|
$thing->{'action'} ||= ""; |
|
438
|
|
|
|
|
|
|
} |
|
439
|
|
|
|
|
|
|
|
|
440
|
3625
|
100
|
|
|
|
7118
|
if( $thing->{'replycode'} ne "" ) { |
|
441
|
|
|
|
|
|
|
# Fill empty values: ["SMTP Command", "DSN", "Reason"] |
|
442
|
2579
|
|
|
|
|
9535
|
my $cv = Sisimai::SMTP::Reply->associatedwith($thing->{'replycode'}); |
|
443
|
2579
|
100
|
|
|
|
5562
|
if( scalar @$cv > 0 ) { |
|
444
|
334
|
100
|
100
|
|
|
1237
|
$thing->{'command'} = $cv->[0] if $cv->[0] ne "" && $thing->{'command'} eq ""; |
|
445
|
334
|
50
|
66
|
|
|
918
|
$thing->{'deliverystatus'} = $cv->[1] if $cv->[1] ne "" && Sisimai::SMTP::Status->is_explicit($thing->{'deliverystatus'}) == 0; |
|
446
|
334
|
100
|
66
|
|
|
1442
|
$thing->{'reason'} = $cv->[2] if $cv->[2] ne "" && Sisimai::Reason->is_explicit($thing->{'reason'}) == 0; |
|
447
|
|
|
|
|
|
|
} |
|
448
|
|
|
|
|
|
|
} |
|
449
|
|
|
|
|
|
|
# Feedback-ID: 1.us-west-2.QHuyeCQrGtIIMGKQfVdUhP9hCQR2LglVOrRamBc+Prk=:AmazonSES |
|
450
|
3625
|
|
100
|
|
|
12457
|
$thing->{'feedbackid'} = $rfc822data->{'feedback-id'} || ""; |
|
451
|
|
|
|
|
|
|
|
|
452
|
3625
|
|
|
|
|
31455
|
push @$listoffact, bless($thing, __PACKAGE__); |
|
453
|
|
|
|
|
|
|
} # End of for(RISEOF) |
|
454
|
|
|
|
|
|
|
|
|
455
|
3471
|
|
|
|
|
56504
|
return $listoffact; |
|
456
|
|
|
|
|
|
|
} |
|
457
|
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
sub maketoken { |
|
459
|
|
|
|
|
|
|
# Create the message token from an addresser and a recipient |
|
460
|
|
|
|
|
|
|
# @param [String] addr1 A sender's email address |
|
461
|
|
|
|
|
|
|
# @param [String] addr2 A recipient's email address |
|
462
|
|
|
|
|
|
|
# @param [Integer] epoch Machine time of the email bounce |
|
463
|
|
|
|
|
|
|
# @return [String] Message token(MD5 hex digest) or empty string |
|
464
|
|
|
|
|
|
|
# if the any argument is missing |
|
465
|
|
|
|
|
|
|
# @see http://en.wikipedia.org/wiki/ASCII |
|
466
|
|
|
|
|
|
|
# @see https://metacpan.org/pod/Digest::MD5 |
|
467
|
3631
|
|
50
|
3631
|
0
|
82855
|
my $class = shift || return ''; |
|
468
|
3631
|
|
100
|
|
|
5627
|
my $addr1 = shift || return ''; |
|
469
|
3630
|
|
100
|
|
|
5217
|
my $addr2 = shift || return ''; |
|
470
|
3629
|
|
100
|
|
|
5502
|
my $epoch = shift // return ''; |
|
471
|
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
# Format: STX(0x02) Sender-Address RS(0x1e) Recipient-Address ETX(0x03) |
|
473
|
3628
|
|
|
|
|
53409
|
return Digest::SHA::sha1_hex(sprintf("\x02%s\x1e%s\x1e%d\x03", lc $addr1, lc $addr2, $epoch)); |
|
474
|
|
|
|
|
|
|
} |
|
475
|
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
sub damn { |
|
478
|
|
|
|
|
|
|
# Convert from object to hash reference |
|
479
|
|
|
|
|
|
|
# @return [Hash] Data in Hash reference |
|
480
|
2949
|
|
|
2949
|
1
|
7679220
|
my $self = shift; |
|
481
|
2949
|
|
|
|
|
6619
|
my $data = undef; |
|
482
|
2949
|
|
|
|
|
5071
|
state $stringdata = [qw| |
|
483
|
|
|
|
|
|
|
action alias catch command decodedby deliverystatus destination diagnosticcode diagnostictype |
|
484
|
|
|
|
|
|
|
feedbackid feedbacktype lhost listid messageid origin reason replycode rhost senderdomain |
|
485
|
|
|
|
|
|
|
subject timezoneoffset token |
|
486
|
|
|
|
|
|
|
|]; |
|
487
|
2949
|
|
|
|
|
4483
|
eval { |
|
488
|
2949
|
|
|
|
|
4763
|
my $v = {}; |
|
489
|
2949
|
|
100
|
|
|
15251
|
$v->{ $_ } = $self->$_ // '' for @$stringdata; |
|
490
|
2949
|
|
|
|
|
266259
|
$v->{'hardbounce'} = int $self->hardbounce; |
|
491
|
2949
|
|
|
|
|
15007
|
$v->{'bogus'} = int $self->bogus; |
|
492
|
2949
|
|
|
|
|
11926
|
$v->{'toxic'} = int $self->toxic; |
|
493
|
2949
|
|
|
|
|
12417
|
$v->{'addresser'} = $self->addresser->address; |
|
494
|
2949
|
|
|
|
|
30803
|
$v->{'recipient'} = $self->recipient->address; |
|
495
|
2949
|
|
|
|
|
20736
|
$v->{'timestamp'} = $self->timestamp->epoch; |
|
496
|
2949
|
|
|
|
|
34631
|
$data = $v; |
|
497
|
|
|
|
|
|
|
}; |
|
498
|
2949
|
|
|
|
|
54573
|
return $data; |
|
499
|
|
|
|
|
|
|
} |
|
500
|
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
sub dump { |
|
502
|
|
|
|
|
|
|
# Data dumper |
|
503
|
|
|
|
|
|
|
# @param [String] type Data format: json, yaml |
|
504
|
|
|
|
|
|
|
# @return [String] Dumped data or an empty string when the argument is neither "json" nor "yaml" |
|
505
|
1531
|
|
|
1531
|
0
|
16194006
|
my $self = shift; |
|
506
|
1531
|
100
|
100
|
|
|
7205
|
my $type = shift || 'json'; return "" unless $type =~ /\A(?:json|yaml)\z/; |
|
|
1531
|
|
|
|
|
9870
|
|
|
507
|
|
|
|
|
|
|
|
|
508
|
1493
|
|
|
|
|
4254
|
my $referclass = 'Sisimai::Fact::'.uc($type); |
|
509
|
1493
|
|
|
|
|
3305
|
my $modulepath = 'Sisimai/Fact/'.uc($type).'.pm'; |
|
510
|
|
|
|
|
|
|
|
|
511
|
1493
|
|
|
|
|
49312
|
require $modulepath; |
|
512
|
1493
|
|
|
|
|
10278
|
return $referclass->dump($self); |
|
513
|
|
|
|
|
|
|
} |
|
514
|
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
1; |
|
516
|
|
|
|
|
|
|
__END__ |