File Coverage

lib/Sisimai/MDA.pm
Criterion Covered Total %
statement 46 46 100.0
branch 22 26 84.6
condition 6 11 54.5
subroutine 4 4 100.0
pod 1 1 100.0
total 79 88 89.7


line stmt bran cond sub pod time code
1             package Sisimai::MDA;
2 8     8   70890 use feature ':5.10';
  8         32  
  8         622  
3 8     8   43 use strict;
  8         13  
  8         156  
4 8     8   40 use warnings;
  8         13  
  8         5421  
5              
6             sub make {
7             # Parse message body and return reason and text
8             # @param [Hash] mhead Message headers of a bounce email
9             # @param [String] mbody Message body of a bounce email
10             # @return [Hash] Bounce data list and message/rfc822 part
11             # @return [Undef] failed to parse or the arguments are missing
12 137     137 1 7704 my $class = shift;
13 137   50     413 my $mhead = shift // return undef;
14 137   50     378 my $mbody = shift // return undef;
15              
16 137 50       431 return undef unless ref($mhead) eq 'HASH';
17 137 100       879 return undef unless lc($mhead->{'from'}) =~ /\A(?:mail delivery subsystem|mailer-daemon|postmaster)/;
18 86 50       291 return undef unless ref($mbody) eq 'SCALAR';
19 86 50       253 return undef unless $$mbody;
20              
21 86         202 state $agentnames = {
22             # dovecot/src/deliver/deliver.c
23             # 11: #define DEFAULT_MAIL_REJECTION_HUMAN_REASON \
24             # 12: "Your message to <%t> was automatically rejected:%n%r"
25             'dovecot' => qr/\AYour message to [^ ]+ was automatically rejected:\z/,
26             'mail.local' => qr/\Amail[.]local: /,
27             'procmail' => qr/\Aprocmail: /,
28             'maildrop' => qr/\Amaildrop: /,
29             'vpopmail' => qr/\Avdelivermail: /,
30             'vmailmgr' => qr/\Avdeliver: /,
31             };
32 86         126 state $markingsof = {
33             'message' => qr{\A(?>
34             Your[ ]message[ ]to[ ][^ ]+[ ]was[ ]automatically[ ]rejected:\z
35             |(?:mail[.]local|procmail|maildrop|vdelivermail|vdeliver):[ ]
36             )
37             }x,
38             };
39              
40             # dovecot/src/deliver/mail-send.c:94
41 86         240 state $messagesof = {
42             'dovecot' => {
43             'userunknown' => ["mailbox doesn't exist: "],
44             'mailboxfull' => [
45             'quota exceeded', # Dovecot 1.2 dovecot/src/plugins/quota/quota.c
46             'quota exceeded (mailbox for user is full)', # dovecot/src/plugins/quota/quota.c
47             'not enough disk space',
48             ],
49             },
50             'mail.local' => {
51             'userunknown' => [
52             ': unknown user:',
53             ': user unknown',
54             ': invalid mailbox path',
55             ': user missing home directory',
56             ],
57             'mailboxfull' => [
58             'disc quota exceeded',
59             'mailbox full or quota exceeded',
60             ],
61             'systemerror' => ['temporary file write error'],
62             },
63             'procmail' => {
64             'mailboxfull' => ['quota exceeded while writing'],
65             'systemfull' => ['no space left to finish writing'],
66             },
67             'maildrop' => {
68             'userunknown' => [
69             'invalid user specified.',
70             'cannot find system user',
71             ],
72             'mailboxfull' => ['maildir over quota.'],
73             },
74             'vpopmail' => {
75             'userunknown' => ['sorry, no mailbox here by that name.'],
76             'filtered' => [
77             'account is locked email bounced',
78             'user does not exist, but will deliver to '
79             ],
80             'mailboxfull' => [
81             'domain is over quota',
82             'user is over quota',
83             ],
84             },
85             'vmailmgr' => {
86             'userunknown' => [
87             'invalid or unknown base user or domain',
88             'invalid or unknown virtual user',
89             'user name does not refer to a virtual user'
90             ],
91             'mailboxfull' => ['delivery failed due to system quota violation'],
92             },
93             };
94              
95 86         163 my $agentname0 = ''; # [String] MDA name
96 86         135 my $reasonname = ''; # [String] Error reason
97 86         146 my $bouncemesg = ''; # [String] Error message
98 86         144 my @linebuffer;
99              
100 86         1041 for my $e ( split("\n", $$mbody) ) {
101             # Check each line with each MDA's symbol regular expression.
102 3259 100       4683 if( $agentname0 eq '' ) {
103             # Try to match with each regular expression
104 3234 100       4227 next unless $e;
105 2585 100       6955 next unless $e =~ $markingsof->{'message'};
106              
107 18         82 for my $f ( keys %$agentnames ) {
108             # Detect the agent name from the line
109 51 100       196 next unless $e =~ $agentnames->{ $f };
110 18         28 $agentname0 = $f;
111 18         28 last;
112             }
113             }
114              
115             # Append error message lines to @linebuffer
116 43         107 push @linebuffer, $e;
117 43 100       78 last unless length $e;
118             }
119 86 100       612 return undef unless $agentname0;
120 18 50       51 return undef unless scalar @linebuffer;
121              
122 18         30 for my $e ( keys %{ $messagesof->{ $agentname0 } } ) {
  18         68  
123             # Detect an error reason from message patterns of the MDA.
124 25         58 for my $f ( @linebuffer ) {
125             # Whether the error message include each message defined in $messagesof
126 45 100       58 next unless grep { index(lc($f), $_) > -1 } @{ $messagesof->{ $agentname0 }->{ $e } };
  95         260  
  45         76  
127 18         31 $reasonname = $e;
128 18         22 $bouncemesg = $f;
129 18         24 last;
130             }
131 25 100 66     102 last if $bouncemesg && $reasonname;
132             }
133              
134             return {
135 18   50     120 'mda' => $agentname0,
      50        
136             'reason' => $reasonname // '',
137             'message' => $bouncemesg // '',
138             };
139             }
140              
141             1;
142             __END__