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   5869 use parent 'Sisimai::Lhost';
  21         54  
  21         112  
3 21     21   1266 use feature ':5.10';
  21         44  
  21         1430  
4 21     21   110 use strict;
  21         41  
  21         433  
5 21     21   107 use warnings;
  21         32  
  21         14242  
6              
7 2     2 1 1169 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 1122 my $class = shift;
17 474   100     1093 my $mhead = shift // return undef;
18 473   50     1167 my $mbody = shift // return undef;
19              
20 473 100       1652 return undef unless index($mhead->{'subject'}, 'Delivery status notification') > -1;
21 57 100       336 return undef unless index($mhead->{'from'}, 'Mailer Daemon <') > -1;
22 31 50       66 return undef unless grep { rindex($_, ' (OpenSMTPD) with ') > -1 } @{ $mhead->{'received'} };
  57         224  
  31         116  
23              
24 31         82 state $indicators = __PACKAGE__->INDICATORS;
25 31         81 state $rebackbone = qr|^[ ]+Below is a copy of the original message:|m;
26 31         64 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         75 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         124 my $dscontents = [__PACKAGE__->DELIVERYSTATUS];
84 31         208 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone);
85 31         174 my $readcursor = 0; # (Integer) Points the current cursor position
86 31         72 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
87 31         73 my $v = undef;
88              
89 31         222 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       444 unless( $readcursor ) {
93             # Beginning of the bounce message or message/delivery-status part
94 103 100       356 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0;
95 103         133 next;
96             }
97 200 50       363 next unless $readcursor & $indicators->{'deliverystatus'};
98 200 100       327 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         185 $v = $dscontents->[-1];
111              
112 128 100       529 if( $e =~ /\A([^ ]+?[@][^ ]+?):?[ ](.+)\z/ ) {
113             # kijitora@example.jp: 550 5.2.2 ... Mailbox Full
114 36 100       155 if( $v->{'recipient'} ) {
115             # There are multiple recipient addresses in the message body.
116 5         44 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
117 5         26 $v = $dscontents->[-1];
118             }
119 36         128 $v->{'recipient'} = $1;
120 36         80 $v->{'diagnosis'} = $2;
121 36         81 $recipients++;
122             }
123             }
124 31 50       107 return undef unless $recipients;
125              
126 31         75 for my $e ( @$dscontents ) {
127 36         213 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
128              
129 36         197 SESSION: for my $r ( keys %$messagesof ) {
130             # Verify each regular expression of session errors
131 155 100       185 next unless grep { index($e->{'diagnosis'}, $_) > -1 } @{ $messagesof->{ $r } };
  501         1187  
  155         260  
132 20         79 $e->{'reason'} = $r;
133 20         50 last;
134             }
135             }
136 31         178 return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] };
137             }
138              
139             1;
140             __END__