File Coverage

lib/Sisimai/Lhost/OpenSMTPD.pm
Criterion Covered Total %
statement 42 42 100.0
branch 16 18 88.8
condition 6 7 85.7
subroutine 6 6 100.0
pod 2 2 100.0
total 72 75 96.0


line stmt bran cond sub pod time code
1             package Sisimai::Lhost::OpenSMTPD;
2 41     41   3382 use parent 'Sisimai::Lhost';
  41         71  
  41         247  
3 41     41   2687 use v5.26;
  41         1411  
4 41     41   146 use strict;
  41         54  
  41         863  
5 41     41   145 use warnings;
  41         69  
  41         18533  
6              
7 1     1 1 3 sub description { 'OpenSMTPD: https://www.opensmtpd.org/' }
8             sub inquire {
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 decode or the arguments are missing
14             # @since v4.0.0
15 1119     1119 1 3118 my $class = shift;
16 1119   100     2356 my $mhead = shift // return undef;
17 1118   100     1961 my $mbody = shift // return undef;
18              
19 1117 100       3794 return undef unless index($mhead->{'subject'}, 'Delivery status notification') > -1;
20 97 100       310 return undef unless index($mhead->{'from'}, 'Mailer Daemon <') > -1;
21 71 50       177 return undef unless grep { rindex($_, ' (OpenSMTPD) with ') > -1 } $mhead->{'received'}->@*;
  97         388  
22              
23 71         116 state $indicators = __PACKAGE__->INDICATORS;
24 71         83 state $boundaries = [' Below is a copy of the original message:'];
25 71         81 state $startingof = {
26             # http://www.openbsd.org/cgi-bin/man.cgi?query=smtpd&sektion=8
27             # opensmtpd-5.4.2p1/smtpd/
28             # bounce.c/317:#define NOTICE_INTRO \
29             # bounce.c/318: " Hi!\n\n" \
30             # bounce.c/319: " This is the MAILER-DAEMON, please DO NOT REPLY to this e-mail.\n"
31             # bounce.c/320:
32             # bounce.c/321:const char *notice_error =
33             # bounce.c/322: " An error has occurred while attempting to deliver a message for\n"
34             # bounce.c/323: " the following list of recipients:\n\n";
35             # bounce.c/324:
36             # bounce.c/325:const char *notice_warning =
37             # bounce.c/326: " A message is delayed for more than %s for the following\n"
38             # bounce.c/327: " list of recipients:\n\n";
39             # bounce.c/328:
40             # bounce.c/329:const char *notice_warning2 =
41             # bounce.c/330: " Please note that this is only a temporary failure report.\n"
42             # bounce.c/331: " The message is kept in the queue for up to %s.\n"
43             # bounce.c/332: " You DO NOT NEED to re-send the message to these recipients.\n\n";
44             # bounce.c/333:
45             # bounce.c/334:const char *notice_success =
46             # bounce.c/335: " Your message was successfully delivered to these recipients.\n\n";
47             # bounce.c/336:
48             # bounce.c/337:const char *notice_relay =
49             # bounce.c/338: " Your message was relayed to these recipients.\n\n";
50             # bounce.c/339:
51             'message' => [' This is the MAILER-DAEMON, please DO NOT REPLY to this'],
52             };
53              
54 71         234 my $dscontents = [__PACKAGE__->DELIVERYSTATUS]; my $v = undef;
  71         96  
55 71         261 my $emailparts = Sisimai::RFC5322->part($mbody, $boundaries);
56 71         711 my $readcursor = 0; # (Integer) Points the current cursor position
57 71         115 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
58              
59 71         374 for my $e ( split("\n", $emailparts->[0]) ) {
60             # Read error messages and delivery status lines from the head of the email to the previous
61             # line of the beginning of the original message.
62 788 100       894 unless( $readcursor ) {
63             # Beginning of the bounce message or message/delivery-status part
64 303 100       549 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0;
65 303         274 next;
66             }
67 485 100 66     1139 next if ($readcursor & $indicators->{'deliverystatus'}) == 0 || $e eq "";
68              
69             # Hi!
70             #
71             # This is the MAILER-DAEMON, please DO NOT REPLY to this e-mail.
72             #
73             # An error has occurred while attempting to deliver a message for
74             # the following list of recipients:
75             #
76             # kijitora@example.jp: 550 5.2.2 ... Mailbox Full
77             #
78             # Below is a copy of the original message:
79 313         279 $v = $dscontents->[-1];
80              
81 313 100       807 if( Sisimai::String->aligned(\$e, ['@', ' ']) ) {
82             # kijitora@example.jp: 550 5.2.2 ... Mailbox Full
83 81 100       145 if( $v->{'recipient'} ) {
84             # There are multiple recipient addresses in the message body.
85 10         30 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
86 10         13 $v = $dscontents->[-1];
87             }
88 81         184 $v->{'recipient'} = substr($e, 0, index($e, ':'));
89 81         208 $v->{'diagnosis'} = substr($e, index($e, ':') + 1, );
90 81         127 $recipients++;
91             }
92             }
93 71 50       188 return undef unless $recipients;
94 71         251 return {"ds" => $dscontents, "rfc822" => $emailparts->[1]};
95             }
96              
97             1;
98             __END__