line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Sisimai::Rhost::ExchangeOnline; |
2
|
7
|
|
|
7
|
|
1307
|
use feature ':5.10'; |
|
7
|
|
|
|
|
11
|
|
|
7
|
|
|
|
|
597
|
|
3
|
7
|
|
|
7
|
|
38
|
use strict; |
|
7
|
|
|
|
|
12
|
|
|
7
|
|
|
|
|
119
|
|
4
|
7
|
|
|
7
|
|
28
|
use warnings; |
|
7
|
|
|
|
|
9
|
|
|
7
|
|
|
|
|
5918
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
# https://technet.microsoft.com/en-us/library/bb232118 |
7
|
|
|
|
|
|
|
sub get { |
8
|
|
|
|
|
|
|
# Detect bounce reason from Exchange 2013 and Office 365 |
9
|
|
|
|
|
|
|
# @param [Sisimai::Data] argvs Parsed email object |
10
|
|
|
|
|
|
|
# @return [String] The bounce reason for Exchange Online |
11
|
|
|
|
|
|
|
# @see https://technet.microsoft.com/en-us/library/bb232118 |
12
|
30
|
|
|
30
|
0
|
839
|
my $class = shift; |
13
|
30
|
|
100
|
|
|
91
|
my $argvs = shift // return undef; |
14
|
29
|
100
|
|
|
|
90
|
return $argvs->reason if $argvs->reason; |
15
|
|
|
|
|
|
|
|
16
|
24
|
|
|
|
|
354
|
state $statuslist = { |
17
|
|
|
|
|
|
|
'4.3.1' => [{ 'reason' => 'systemfull', 'string' => 'Insufficient system resources' }], |
18
|
|
|
|
|
|
|
'4.3.2' => [{ 'reason' => 'notaccept', 'string' => 'System not accepting network messages' }], |
19
|
|
|
|
|
|
|
'4.4.2' => [{ 'reason' => 'blocked', 'string' => 'Connection dropped' }], |
20
|
|
|
|
|
|
|
'4.7.26' => [{ |
21
|
|
|
|
|
|
|
'reason' => 'securityerror', |
22
|
|
|
|
|
|
|
'string' => 'must pass either SPF or DKIM validation, this message is not signed' |
23
|
|
|
|
|
|
|
}], |
24
|
|
|
|
|
|
|
'5.0.0' => [{ 'reason' => 'blocked', 'string' => 'HELO / EHLO requires domain address' }], |
25
|
|
|
|
|
|
|
'5.1.4' => [{ 'reason' => 'systemerror', 'string' => 'Destination mailbox address ambiguous' }], |
26
|
|
|
|
|
|
|
'5.2.1' => [{ 'reason' => 'suspend', 'string' => 'Mailbox cannot be accessed' }], |
27
|
|
|
|
|
|
|
'5.2.2' => [{ 'reason' => 'mailboxfull', 'string' => 'Mailbox full' }], |
28
|
|
|
|
|
|
|
'5.2.3' => [{ 'reason' => 'exceedlimit', 'string' => 'Message too large' }], |
29
|
|
|
|
|
|
|
'5.2.4' => [{ 'reason' => 'systemerror', 'string' => 'Mailing list expansion problem' }], |
30
|
|
|
|
|
|
|
'5.2.14' => [{ 'reason' => 'systemerror', 'string' => 'misconfigured forwarding address' }], |
31
|
|
|
|
|
|
|
'5.2.122' => [{ 'reason' => 'toomanyconn', 'string' => 'The recipient has exceeded their limit for' }], |
32
|
|
|
|
|
|
|
'5.3.3' => [{ 'reason' => 'systemfull', 'string' => 'Unrecognized command' }], |
33
|
|
|
|
|
|
|
'5.3.4' => [{ 'reason' => 'mesgtoobig', 'string' => 'Message too big for system' }], |
34
|
|
|
|
|
|
|
'5.3.5' => [{ 'reason' => 'systemerror', 'string' => 'System incorrectly configured' }], |
35
|
|
|
|
|
|
|
'5.4.1' => [{ 'reason' => 'rejected', 'string' => 'Recipient address rejected: Access denied' }], |
36
|
|
|
|
|
|
|
'5.4.11' => [{ 'reason' => 'contenterror','string' => 'Agent generated message depth exceeded' }], |
37
|
|
|
|
|
|
|
'5.4.14' => [{ 'reason' => 'networkerror','string' => 'Hop count exceeded' }], |
38
|
|
|
|
|
|
|
'5.4.310' => [{ 'reason' => 'systemerror', 'string' => 'does not exist'}], # DNS domain * does not exist |
39
|
|
|
|
|
|
|
'5.5.2' => [{ 'reason' => 'syntaxerror', 'string' => 'Send hello first' }], |
40
|
|
|
|
|
|
|
'5.5.3' => [{ 'reason' => 'syntaxerror', 'string' => 'Too many recipients' }], |
41
|
|
|
|
|
|
|
'5.5.4' => [{ 'reason' => 'filtered', 'string' => 'Invalid domain name' }], |
42
|
|
|
|
|
|
|
'5.5.6' => [{ 'reason' => 'contenterror','string' => 'Invalid message content' }], |
43
|
|
|
|
|
|
|
'5.7.1' => [ |
44
|
|
|
|
|
|
|
{ 'reason' => 'securityerror', 'string' => 'Delivery not authorized' }, |
45
|
|
|
|
|
|
|
{ 'reason' => 'securityerror', 'string' => 'Client was not authenticated' }, |
46
|
|
|
|
|
|
|
{ 'reason' => 'norelaying', 'string' => 'Unable to relay' }, |
47
|
|
|
|
|
|
|
], |
48
|
|
|
|
|
|
|
'5.7.25' => [{ 'reason' => 'blocked', 'string' => 'must have a reverse DNS record' }], |
49
|
|
|
|
|
|
|
'5.7.51' => [{ 'reason' => 'blocked', 'string' => 'RestrictDomainsToIPAddresses or RestrictDomainsToCertificate' }], |
50
|
|
|
|
|
|
|
'5.7.506' => [{ 'reason' => 'blocked', 'string' => 'Bad HELO' }], |
51
|
|
|
|
|
|
|
'5.7.508' => [{ 'reason' => 'toomanyconn', 'string' => 'has exceeded permitted limits within ' }], |
52
|
|
|
|
|
|
|
'5.7.509' => [{ 'reason' => 'rejected', 'string' => 'does not pass DMARC verification' }], |
53
|
|
|
|
|
|
|
'5.7.510' => [{ 'reason' => 'notaccept', 'string' => 'does not accept email over IPv6' }], |
54
|
|
|
|
|
|
|
'5.7.511' => [{ 'reason' => 'blocked', 'string' => 'banned sender' }], |
55
|
|
|
|
|
|
|
'5.7.512' => [{ 'reason' => 'contenterror', 'string' => 'message must be RFC 5322' }], |
56
|
|
|
|
|
|
|
}; |
57
|
24
|
|
|
|
|
182
|
state $restatuses = { |
58
|
|
|
|
|
|
|
qr/\A4[.]4[.][17]\z/ => [ |
59
|
|
|
|
|
|
|
{ 'reason' => 'expired', 'string' => ['Connection timed out', 'Message expired'] } |
60
|
|
|
|
|
|
|
], |
61
|
|
|
|
|
|
|
qr/\A4[.]7[.][568]\d\d\z/ => [ |
62
|
|
|
|
|
|
|
{ 'reason' => 'securityerror', 'string' => ['Access denied, please try again later'] } |
63
|
|
|
|
|
|
|
], |
64
|
|
|
|
|
|
|
qr/\A5[.]1[.][07]\z/ => [ |
65
|
|
|
|
|
|
|
{ 'reason' => 'rejected', 'string' => ['Sender denied', 'Invalid address'] } |
66
|
|
|
|
|
|
|
], |
67
|
|
|
|
|
|
|
qr/\A5[.]1[.][123]\z/ => [{ |
68
|
|
|
|
|
|
|
'reason' => 'userunknown', |
69
|
|
|
|
|
|
|
'string' => [ |
70
|
|
|
|
|
|
|
'Bad destination mailbox address', |
71
|
|
|
|
|
|
|
'Invalid X.400 address', |
72
|
|
|
|
|
|
|
'Invalid recipient address', |
73
|
|
|
|
|
|
|
] |
74
|
|
|
|
|
|
|
}], |
75
|
|
|
|
|
|
|
qr/\A5[.]4[.][46]\z/ => [{ |
76
|
|
|
|
|
|
|
'reason' => 'networkerror', |
77
|
|
|
|
|
|
|
'string' => ['Invalid arguments', 'Routing loop detected'], |
78
|
|
|
|
|
|
|
}], |
79
|
|
|
|
|
|
|
qr/\A5[.]7[.][13]\z/ => [{ |
80
|
|
|
|
|
|
|
'reason' => 'securityerror', |
81
|
|
|
|
|
|
|
'string' => ['Delivery not authorized', 'Not Authorized'], |
82
|
|
|
|
|
|
|
}], |
83
|
|
|
|
|
|
|
qr/\A5[.]7[.]50[1-3]\z/ => [{ |
84
|
|
|
|
|
|
|
'reason' => 'spamdetected', |
85
|
|
|
|
|
|
|
'string' => [ |
86
|
|
|
|
|
|
|
'Access denied, spam abuse detected', |
87
|
|
|
|
|
|
|
'Access denied, banned sender' |
88
|
|
|
|
|
|
|
], |
89
|
|
|
|
|
|
|
}], |
90
|
|
|
|
|
|
|
qr/\A5[.]7[.]50[457]\z/ => [{ |
91
|
|
|
|
|
|
|
'reason' => 'filtered', |
92
|
|
|
|
|
|
|
'string' => [ |
93
|
|
|
|
|
|
|
'Recipient address rejected: Access denied', |
94
|
|
|
|
|
|
|
'Access denied, banned recipient', |
95
|
|
|
|
|
|
|
'Access denied, rejected by recipient' |
96
|
|
|
|
|
|
|
] |
97
|
|
|
|
|
|
|
}], |
98
|
|
|
|
|
|
|
qr/\A5[.]7[.]6\d\d\z/ => [ |
99
|
|
|
|
|
|
|
{ 'reason' => 'blocked', 'string' => ['Access denied, banned sending IP '] } |
100
|
|
|
|
|
|
|
], |
101
|
|
|
|
|
|
|
qr/\A5[.]7[.]7\d\d\z/ => [ |
102
|
|
|
|
|
|
|
{ 'reason' => 'toomanyconn', 'string' => ['Access denied, tenant has exceeded threshold'] } |
103
|
|
|
|
|
|
|
], |
104
|
|
|
|
|
|
|
}; |
105
|
24
|
|
|
|
|
109
|
state $messagesof = { |
106
|
|
|
|
|
|
|
# Copied and converted from Sisimai::Lhost::Exchange2007 |
107
|
|
|
|
|
|
|
'expired' => ['QUEUE.Expired'], |
108
|
|
|
|
|
|
|
'hostunknown' => ['SMTPSEND.DNS.NonExistentDomain'], |
109
|
|
|
|
|
|
|
'mesgtoobig' => ['RESOLVER.RST.RecipSizeLimit', 'RESOLVER.RST.RecipientSizeLimit'], |
110
|
|
|
|
|
|
|
'networkerror' => ['SMTPSEND.DNS.MxLoopback'], |
111
|
|
|
|
|
|
|
'rejected' => ['RESOLVER.RST.NotAuthorized'], |
112
|
|
|
|
|
|
|
'securityerror' => ['RESOLVER.RST.AuthRequired'], |
113
|
|
|
|
|
|
|
'systemerror' => [ |
114
|
|
|
|
|
|
|
'RESOLVER.ADR.Ambiguous', |
115
|
|
|
|
|
|
|
'RESOLVER.ADR.BadPrimary', |
116
|
|
|
|
|
|
|
'RESOLVER.ADR.InvalidInSmtp', |
117
|
|
|
|
|
|
|
'RESOLVER.FWD.NotFound', |
118
|
|
|
|
|
|
|
], |
119
|
|
|
|
|
|
|
'toomanyconn' => ['RESOLVER.ADR.RecipLimit', 'RESOLVER.ADR.RecipientLimit'], |
120
|
|
|
|
|
|
|
'userunknown' => [ |
121
|
|
|
|
|
|
|
'RESOLVER.ADR.RecipNotFound', |
122
|
|
|
|
|
|
|
'RESOLVER.ADR.RecipientNotFound', |
123
|
|
|
|
|
|
|
'RESOLVER.ADR.ExRecipNotFound', |
124
|
|
|
|
|
|
|
'RESOLVER.ADR.ExRecipientNotFound', |
125
|
|
|
|
|
|
|
], |
126
|
|
|
|
|
|
|
}; |
127
|
|
|
|
|
|
|
|
128
|
24
|
|
|
|
|
90
|
my $statuscode = $argvs->deliverystatus; |
129
|
24
|
|
|
|
|
156
|
my $statusmesg = $argvs->diagnosticcode; |
130
|
24
|
|
|
|
|
107
|
my $reasontext = ''; |
131
|
|
|
|
|
|
|
|
132
|
24
|
|
|
|
|
228
|
for my $e ( keys %$statuslist ) { |
133
|
|
|
|
|
|
|
# Try to compare with each status code as a key |
134
|
657
|
100
|
|
|
|
847
|
next unless $statuscode eq $e; |
135
|
11
|
|
|
|
|
18
|
for my $f ( @{ $statuslist->{ $e } } ) { |
|
11
|
|
|
|
|
34
|
|
136
|
|
|
|
|
|
|
# Try to compare with each string of error messages |
137
|
11
|
50
|
|
|
|
58
|
next if index($statusmesg, $f->{'string'}) == -1; |
138
|
11
|
|
|
|
|
22
|
$reasontext = $f->{'reason'}; |
139
|
11
|
|
|
|
|
21
|
last; |
140
|
|
|
|
|
|
|
} |
141
|
11
|
50
|
|
|
|
30
|
last if $reasontext; |
142
|
|
|
|
|
|
|
} |
143
|
24
|
100
|
|
|
|
111
|
return $reasontext if $reasontext; |
144
|
|
|
|
|
|
|
|
145
|
13
|
|
|
|
|
53
|
for my $e ( keys %$restatuses ) { |
146
|
|
|
|
|
|
|
# Try to compare with each string of delivery status codes |
147
|
84
|
100
|
|
|
|
1074
|
next unless $statuscode =~ $e; |
148
|
7
|
|
|
|
|
20
|
for my $f ( @{ $restatuses->{ $e } } ) { |
|
7
|
|
|
|
|
18
|
|
149
|
|
|
|
|
|
|
# Try to compare with each string of error messages |
150
|
7
|
|
|
|
|
12
|
for my $g ( @{ $f->{'string'} } ) { |
|
7
|
|
|
|
|
17
|
|
151
|
7
|
50
|
|
|
|
33
|
next if index($statusmesg, $g) == -1; |
152
|
7
|
|
|
|
|
13
|
$reasontext = $f->{'reason'}; |
153
|
7
|
|
|
|
|
13
|
last; |
154
|
|
|
|
|
|
|
} |
155
|
7
|
50
|
|
|
|
18
|
last if $reasontext; |
156
|
|
|
|
|
|
|
} |
157
|
7
|
50
|
|
|
|
26
|
last if $reasontext; |
158
|
|
|
|
|
|
|
} |
159
|
13
|
100
|
|
|
|
72
|
return $reasontext if $reasontext; |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# D.S.N. included in the error message did not matched with any key |
162
|
|
|
|
|
|
|
# in statuslist, restatuses |
163
|
6
|
|
|
|
|
28
|
for my $e ( keys %$messagesof ) { |
164
|
|
|
|
|
|
|
# Try to compare with error messages defined in MessagesOf |
165
|
35
|
|
|
|
|
36
|
for my $f ( @{ $messagesof->{ $e } } ) { |
|
35
|
|
|
|
|
68
|
|
166
|
59
|
100
|
|
|
|
131
|
next if index($statusmesg, $f) == -1; |
167
|
6
|
|
|
|
|
11
|
$reasontext = $e; |
168
|
6
|
|
|
|
|
10
|
last; |
169
|
|
|
|
|
|
|
} |
170
|
35
|
100
|
|
|
|
52
|
last if $reasontext; |
171
|
|
|
|
|
|
|
} |
172
|
6
|
|
|
|
|
22
|
return $reasontext; |
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
1; |
176
|
|
|
|
|
|
|
__END__ |