line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Sisimai::Lhost::Amavis; |
2
|
16
|
|
|
16
|
|
4887
|
use parent 'Sisimai::Lhost'; |
|
16
|
|
|
|
|
30
|
|
|
16
|
|
|
|
|
90
|
|
3
|
16
|
|
|
16
|
|
850
|
use feature ':5.10'; |
|
16
|
|
|
|
|
31
|
|
|
16
|
|
|
|
|
951
|
|
4
|
16
|
|
|
16
|
|
75
|
use strict; |
|
16
|
|
|
|
|
22
|
|
|
16
|
|
|
|
|
248
|
|
5
|
16
|
|
|
16
|
|
54
|
use warnings; |
|
16
|
|
|
|
|
22
|
|
|
16
|
|
|
|
|
10897
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
# https://www.amavis.org |
8
|
2
|
|
|
2
|
1
|
1020
|
sub description { 'amavisd-new: https://www.amavis.org/' } |
9
|
|
|
|
|
|
|
sub make { |
10
|
|
|
|
|
|
|
# Detect an error from amavisd-new |
11
|
|
|
|
|
|
|
# @param [Hash] mhead Message headers of a bounce email |
12
|
|
|
|
|
|
|
# @param [String] mbody Message body of a bounce email |
13
|
|
|
|
|
|
|
# @return [Hash] Bounce data list and message/rfc822 part |
14
|
|
|
|
|
|
|
# @return [Undef] failed to parse or the arguments are missing |
15
|
|
|
|
|
|
|
# @since v4.25.0 |
16
|
229
|
|
|
229
|
1
|
625
|
my $class = shift; |
17
|
229
|
|
100
|
|
|
499
|
my $mhead = shift // return undef; |
18
|
228
|
|
50
|
|
|
510
|
my $mbody = shift // return undef; |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# From: "Content-filter at neko1.example.jp" |
21
|
|
|
|
|
|
|
# Subject: Undeliverable mail, MTA-BLOCKED |
22
|
228
|
100
|
|
|
|
800
|
return undef unless index($mhead->{'from'}, '"Content-filter at ') == 0; |
23
|
|
|
|
|
|
|
|
24
|
16
|
|
|
|
|
52
|
state $indicators = __PACKAGE__->INDICATORS; |
25
|
16
|
|
|
|
|
33
|
state $rebackbone = qr|^Content-Type:[ ]text/rfc822-headers|m; |
26
|
16
|
|
|
|
|
30
|
state $startingof = { 'message' => ['The message '] }; |
27
|
16
|
|
|
|
|
37
|
state $messagesof = { |
28
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840|%smtp_reason_by_ccat = ( |
29
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| # currently only used for blocked messages only, status 5xx |
30
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| # a multiline message will produce a valid multiline SMTP response |
31
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_VIRUS, 'id=%n - INFECTED: %V', |
32
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_BANNED, 'id=%n - BANNED: %F', |
33
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_UNCHECKED.',1', 'id=%n - UNCHECKED: encrypted', |
34
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_UNCHECKED.',2', 'id=%n - UNCHECKED: over limits', |
35
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_UNCHECKED, 'id=%n - UNCHECKED', |
36
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_SPAM, 'id=%n - spam', |
37
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_SPAMMY.',1', 'id=%n - spammy (tag3)', |
38
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_SPAMMY, 'id=%n - spammy', |
39
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_BADH.',1', 'id=%n - BAD HEADER: MIME error', |
40
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_BADH.',2', 'id=%n - BAD HEADER: nonencoded 8-bit character', |
41
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_BADH.',3', 'id=%n - BAD HEADER: contains invalid control character', |
42
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_BADH.',4', 'id=%n - BAD HEADER: line made up entirely of whitespace', |
43
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_BADH.',5', 'id=%n - BAD HEADER: line longer than RFC 5322 limit', |
44
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_BADH.',6', 'id=%n - BAD HEADER: syntax error', |
45
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_BADH.',7', 'id=%n - BAD HEADER: missing required header field', |
46
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_BADH.',8', 'id=%n - BAD HEADER: duplicate header field', |
47
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_BADH, 'id=%n - BAD HEADER', |
48
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_OVERSIZED, 'id=%n - Message size exceeds recipient\'s size limit', |
49
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_MTA.',1', 'id=%n - Temporary MTA failure on relaying', |
50
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_MTA.',2', 'id=%n - Rejected by next-hop MTA on relaying', |
51
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_MTA, 'id=%n - Unable to relay message back to MTA', |
52
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_CLEAN, 'id=%n - CLEAN', |
53
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:1840| CC_CATCHALL, 'id=%n - OTHER', # should not happen |
54
|
|
|
|
|
|
|
# ... |
55
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15289|my $status = setting_by_given_contents_category( |
56
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15289| $blocking_ccat, |
57
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15289| { CC_VIRUS, "554 5.7.0", |
58
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15289| CC_BANNED, "554 5.7.0", |
59
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15289| CC_UNCHECKED, "554 5.7.0", |
60
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15289| CC_SPAM, "554 5.7.0", |
61
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15289| CC_SPAMMY, "554 5.7.0", |
62
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15289| CC_BADH.",2", "554 5.6.3", # nonencoded 8-bit character |
63
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15289| CC_BADH, "554 5.6.0", |
64
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15289| CC_OVERSIZED, "552 5.3.4", |
65
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15289| CC_MTA, "550 5.3.5", |
66
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15289| CC_CATCHALL, "554 5.7.0", |
67
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15289| }); |
68
|
|
|
|
|
|
|
# ... |
69
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15332|my $response = sprintf("%s %s%s%s", $status, |
70
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15333| ($final_destiny == D_PASS ? "Ok" : |
71
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15334| $final_destiny == D_DISCARD ? "Ok, discarded" : |
72
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15335| $final_destiny == D_REJECT ? "Reject" : |
73
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15336| $final_destiny == D_BOUNCE ? "Bounce" : |
74
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15337| $final_destiny == D_TEMPFAIL ? "Temporary failure" : |
75
|
|
|
|
|
|
|
# amavisd-new-2.11.1/amavisd:15338| "Not ok ($final_destiny)" ), |
76
|
|
|
|
|
|
|
'spamdetected' => [' - spam'], |
77
|
|
|
|
|
|
|
'virusdetected' => [' - infected'], |
78
|
|
|
|
|
|
|
'contenterror' => [' - bad header:'], |
79
|
|
|
|
|
|
|
'exceedlimit' => [' - message size exceeds recipient'], |
80
|
|
|
|
|
|
|
'systemerror' => [ |
81
|
|
|
|
|
|
|
' - temporary mta failure on relaying', |
82
|
|
|
|
|
|
|
' - rejected by next-hop mta on relaying', |
83
|
|
|
|
|
|
|
' - unable to relay message back to mta', |
84
|
|
|
|
|
|
|
], |
85
|
|
|
|
|
|
|
}; |
86
|
|
|
|
|
|
|
|
87
|
16
|
|
|
|
|
401
|
require Sisimai::RFC1894; |
88
|
16
|
|
|
|
|
89
|
my $fieldtable = Sisimai::RFC1894->FIELDTABLE; |
89
|
16
|
|
|
|
|
42
|
my $permessage = {}; # (Hash) Store values of each Per-Message field |
90
|
|
|
|
|
|
|
|
91
|
16
|
|
|
|
|
60
|
my $dscontents = [__PACKAGE__->DELIVERYSTATUS]; |
92
|
16
|
|
|
|
|
86
|
my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone); |
93
|
16
|
|
|
|
|
39
|
my $readcursor = 0; # (Integer) Points the current cursor position |
94
|
16
|
|
|
|
|
30
|
my $recipients = 0; # (Integer) The number of 'Final-Recipient' header |
95
|
16
|
|
|
|
|
23
|
my $v = undef; |
96
|
|
|
|
|
|
|
|
97
|
16
|
|
|
|
|
152
|
for my $e ( split("\n", $emailsteak->[0]) ) { |
98
|
|
|
|
|
|
|
# Read error messages and delivery status lines from the head of the email |
99
|
|
|
|
|
|
|
# to the previous line of the beginning of the original message. |
100
|
524
|
100
|
|
|
|
595
|
unless( $readcursor ) { |
101
|
|
|
|
|
|
|
# Beginning of the bounce message or message/delivery-status part |
102
|
83
|
100
|
|
|
|
190
|
$readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0; |
103
|
83
|
|
|
|
|
89
|
next; |
104
|
|
|
|
|
|
|
} |
105
|
441
|
50
|
|
|
|
592
|
next unless $readcursor & $indicators->{'deliverystatus'}; |
106
|
441
|
100
|
|
|
|
526
|
next unless length $e; |
107
|
362
|
100
|
|
|
|
501
|
next unless my $f = Sisimai::RFC1894->match($e); |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
# $e matched with any field defined in RFC3464 |
110
|
150
|
50
|
|
|
|
241
|
next unless my $o = Sisimai::RFC1894->field($e); |
111
|
150
|
|
|
|
|
173
|
$v = $dscontents->[-1]; |
112
|
|
|
|
|
|
|
|
113
|
150
|
100
|
|
|
|
254
|
if( $o->[-1] eq 'addr' ) { |
|
|
100
|
|
|
|
|
|
114
|
|
|
|
|
|
|
# Final-Recipient: rfc822; kijitora@example.jp |
115
|
|
|
|
|
|
|
# X-Actual-Recipient: rfc822; kijitora@example.co.jp |
116
|
27
|
100
|
|
|
|
68
|
if( $o->[0] eq 'final-recipient' ) { |
117
|
|
|
|
|
|
|
# Final-Recipient: rfc822; kijitora@example.jp |
118
|
16
|
50
|
|
|
|
62
|
if( $v->{'recipient'} ) { |
119
|
|
|
|
|
|
|
# There are multiple recipient addresses in the message body. |
120
|
0
|
|
|
|
|
0
|
push @$dscontents, __PACKAGE__->DELIVERYSTATUS; |
121
|
0
|
|
|
|
|
0
|
$v = $dscontents->[-1]; |
122
|
|
|
|
|
|
|
} |
123
|
16
|
|
|
|
|
28
|
$v->{'recipient'} = $o->[2]; |
124
|
16
|
|
|
|
|
32
|
$recipients++; |
125
|
|
|
|
|
|
|
} else { |
126
|
|
|
|
|
|
|
# X-Actual-Recipient: rfc822; kijitora@example.co.jp |
127
|
11
|
|
|
|
|
45
|
$v->{'alias'} = $o->[2]; |
128
|
|
|
|
|
|
|
} |
129
|
|
|
|
|
|
|
} elsif( $o->[-1] eq 'code' ) { |
130
|
|
|
|
|
|
|
# Diagnostic-Code: SMTP; 550 5.1.1 ... User Unknown |
131
|
16
|
|
|
|
|
42
|
$v->{'spec'} = $o->[1]; |
132
|
16
|
50
|
|
|
|
54
|
$v->{'spec'} = 'SMTP' if $v->{'spec'} eq 'X-POSTFIX'; |
133
|
16
|
|
|
|
|
37
|
$v->{'diagnosis'} = $o->[2]; |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
} else { |
136
|
|
|
|
|
|
|
# Other DSN fields defined in RFC3464 |
137
|
107
|
50
|
|
|
|
184
|
next unless exists $fieldtable->{ $o->[0] }; |
138
|
107
|
|
|
|
|
161
|
$v->{ $fieldtable->{ $o->[0] } } = $o->[2]; |
139
|
|
|
|
|
|
|
|
140
|
107
|
100
|
|
|
|
204
|
next unless $f == 1; |
141
|
48
|
|
|
|
|
123
|
$permessage->{ $fieldtable->{ $o->[0] } } = $o->[2]; |
142
|
|
|
|
|
|
|
} |
143
|
|
|
|
|
|
|
} |
144
|
16
|
50
|
|
|
|
69
|
return undef unless $recipients; |
145
|
|
|
|
|
|
|
|
146
|
16
|
|
|
|
|
31
|
for my $e ( @$dscontents ) { |
147
|
|
|
|
|
|
|
# Set default values if each value is empty. |
148
|
16
|
|
0
|
|
|
113
|
$e->{ $_ } ||= $permessage->{ $_ } || '' for keys %$permessage; |
|
|
|
33
|
|
|
|
|
149
|
16
|
|
33
|
|
|
57
|
$e->{'diagnosis'} ||= Sisimai::String->sweep($e->{'diagnosis'}); |
150
|
16
|
|
|
|
|
38
|
my $q = lc $e->{'diagnosis'}; |
151
|
16
|
|
|
|
|
53
|
DETECT_REASON: for my $p ( keys %$messagesof ) { |
152
|
|
|
|
|
|
|
# Try to detect an error reason |
153
|
74
|
|
|
|
|
63
|
for my $r ( @{ $messagesof->{ $p } } ) { |
|
74
|
|
|
|
|
97
|
|
154
|
|
|
|
|
|
|
# Try to find an error message including lower-cased string |
155
|
|
|
|
|
|
|
# defined in $messagesof |
156
|
98
|
100
|
|
|
|
210
|
next unless index($q, $r) > -1; |
157
|
5
|
|
|
|
|
7
|
$e->{'reason'} = $p; |
158
|
5
|
|
|
|
|
19
|
last(DETECT_REASON) |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
} |
162
|
16
|
|
|
|
|
114
|
return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] }; |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
1; |
166
|
|
|
|
|
|
|
__END__ |