File Coverage

lib/Sisimai/Lhost/OpenSMTPD.pm
Criterion Covered Total %
statement 54 54 100.0
branch 19 22 86.3
condition 3 4 75.0
subroutine 6 6 100.0
pod 2 2 100.0
total 84 88 95.4


line stmt bran cond sub pod time code
1             package Sisimai::Lhost::OpenSMTPD;
2 21     21   5310 use parent 'Sisimai::Lhost';
  21         37  
  21         97  
3 21     21   1108 use feature ':5.10';
  21         40  
  21         1219  
4 21     21   245 use strict;
  21         35  
  21         514  
5 21     21   90 use warnings;
  21         36  
  21         12304  
6              
7 2     2 1 1002 sub description { 'OpenSMTPD' }
8             sub make {
9             # Detect an error from OpenSMTPD
10             # @param [Hash] mhead Message headers of a bounce email
11             # @param [String] mbody Message body of a bounce email
12             # @return [Hash] Bounce data list and message/rfc822 part
13             # @return [Undef] failed to parse or the arguments are missing
14              
15             # @since v4.0.0
16 474     474 1 1163 my $class = shift;
17 474   100     1153 my $mhead = shift // return undef;
18 473   50     1106 my $mbody = shift // return undef;
19              
20 473 100       1541 return undef unless index($mhead->{'subject'}, 'Delivery status notification') > -1;
21 57 100       217 return undef unless index($mhead->{'from'}, 'Mailer Daemon <') > -1;
22 31 50       51 return undef unless grep { rindex($_, ' (OpenSMTPD) with ') > -1 } @{ $mhead->{'received'} };
  57         179  
  31         73  
23              
24 31         67 state $indicators = __PACKAGE__->INDICATORS;
25 31         50 state $rebackbone = qr|^[ ]+Below is a copy of the original message:|m;
26 31         48 state $startingof = {
27             # http://www.openbsd.org/cgi-bin/man.cgi?query=smtpd&sektion=8
28             # opensmtpd-5.4.2p1/smtpd/
29             # bounce.c/317:#define NOTICE_INTRO \
30             # bounce.c/318: " Hi!\n\n" \
31             # bounce.c/319: " This is the MAILER-DAEMON, please DO NOT REPLY to this e-mail.\n"
32             # bounce.c/320:
33             # bounce.c/321:const char *notice_error =
34             # bounce.c/322: " An error has occurred while attempting to deliver a message for\n"
35             # bounce.c/323: " the following list of recipients:\n\n";
36             # bounce.c/324:
37             # bounce.c/325:const char *notice_warning =
38             # bounce.c/326: " A message is delayed for more than %s for the following\n"
39             # bounce.c/327: " list of recipients:\n\n";
40             # bounce.c/328:
41             # bounce.c/329:const char *notice_warning2 =
42             # bounce.c/330: " Please note that this is only a temporary failure report.\n"
43             # bounce.c/331: " The message is kept in the queue for up to %s.\n"
44             # bounce.c/332: " You DO NOT NEED to re-send the message to these recipients.\n\n";
45             # bounce.c/333:
46             # bounce.c/334:const char *notice_success =
47             # bounce.c/335: " Your message was successfully delivered to these recipients.\n\n";
48             # bounce.c/336:
49             # bounce.c/337:const char *notice_relay =
50             # bounce.c/338: " Your message was relayed to these recipients.\n\n";
51             # bounce.c/339:
52             'message' => [' This is the MAILER-DAEMON, please DO NOT REPLY to this'],
53             };
54 31         69 state $messagesof = {
55             # smtpd/queue.c:221| envelope_set_errormsg(&evp, "Envelope expired");
56             'expired' => ['Envelope expired'],
57             'hostunknown' => [
58             # smtpd/mta.c:976| relay->failstr = "Invalid domain name";
59             # smtpd/mta.c:980| relay->failstr = "Domain does not exist";
60             'Invalid domain name',
61             'Domain does not exist',
62             ],
63             # smtp/mta.c:1085| relay->failstr = "Destination seem to reject all mails";
64             'notaccept' => ['Destination seem to reject all mails'],
65             'networkerror'=> [
66             # smtpd/mta.c:972| relay->failstr = "Temporary failure in MX lookup";
67             'Address family mismatch on destination MXs',
68             'All routes to destination blocked',
69             'bad DNS lookup error code',
70             'Could not retrieve source address',
71             'Loop detected',
72             'Network error on destination MXs',
73             'No MX found for domain',
74             'No MX found for destination',
75             'No valid route to remote MX',
76             'No valid route to destination',
77             'Temporary failure in MX lookup',
78             ],
79             # smtpd/mta.c:1013| relay->failstr = "Could not retrieve credentials";
80             'securityerror' => ['Could not retrieve credentials'],
81             };
82              
83 31         119 my $dscontents = [__PACKAGE__->DELIVERYSTATUS];
84 31         164 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone);
85 31         49 my $readcursor = 0; # (Integer) Points the current cursor position
86 31         70 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
87 31         62 my $v = undef;
88              
89 31         162 for my $e ( split("\n", $emailsteak->[0]) ) {
90             # Read error messages and delivery status lines from the head of the email
91             # to the previous line of the beginning of the original message.
92 303 100       470 unless( $readcursor ) {
93             # Beginning of the bounce message or message/delivery-status part
94 103 100       288 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0;
95 103         137 next;
96             }
97 200 50       289 next unless $readcursor & $indicators->{'deliverystatus'};
98 200 100       276 next unless length $e;
99              
100             # Hi!
101             #
102             # This is the MAILER-DAEMON, please DO NOT REPLY to this e-mail.
103             #
104             # An error has occurred while attempting to deliver a message for
105             # the following list of recipients:
106             #
107             # kijitora@example.jp: 550 5.2.2 ... Mailbox Full
108             #
109             # Below is a copy of the original message:
110 128         132 $v = $dscontents->[-1];
111              
112 128 100       347 if( $e =~ /\A([^ ]+?[@][^ ]+?):?[ ](.+)\z/ ) {
113             # kijitora@example.jp: 550 5.2.2 ... Mailbox Full
114 36 100       110 if( $v->{'recipient'} ) {
115             # There are multiple recipient addresses in the message body.
116 5         19 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
117 5         10 $v = $dscontents->[-1];
118             }
119 36         95 $v->{'recipient'} = $1;
120 36         70 $v->{'diagnosis'} = $2;
121 36         54 $recipients++;
122             }
123             }
124 31 50       92 return undef unless $recipients;
125              
126 31         70 for my $e ( @$dscontents ) {
127 36         184 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
128              
129 36         123 SESSION: for my $r ( keys %$messagesof ) {
130             # Verify each regular expression of session errors
131 135 100       153 next unless grep { index($e->{'diagnosis'}, $_) > -1 } @{ $messagesof->{ $r } };
  402         736  
  135         183  
132 20         46 $e->{'reason'} = $r;
133 20         55 last;
134             }
135             }
136 31         139 return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] };
137             }
138              
139             1;
140             __END__