File Coverage

lib/Sisimai/Lhost/DragonFly.pm
Criterion Covered Total %
statement 53 55 96.3
branch 18 22 81.8
condition 9 12 75.0
subroutine 6 6 100.0
pod 2 2 100.0
total 88 97 90.7


line stmt bran cond sub pod time code
1             package Sisimai::Lhost::DragonFly;
2 39     39   4455 use parent 'Sisimai::Lhost';
  39         83  
  39         335  
3 39     39   3795 use v5.26;
  39         187  
4 39     39   220 use strict;
  39         89  
  39         1399  
5 39     39   191 use warnings;
  39         74  
  39         33611  
6              
7 1     1 1 5 sub description { 'DragonFly: https://www.dragonflybsd.org/handbook/mta/' }
8             sub inquire {
9             # Detect an error from DMA: DragonFly Mail Agent
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 v5.0.4
15 1060     1060 1 4328 my $class = shift;
16 1060   100     2943 my $mhead = shift // return undef;
17 1059   100     4593 my $mbody = shift // return undef;
18              
19 1058 100       5542 return undef unless index($mhead->{'subject'}, 'Mail delivery failed') > -1;
20 188 100       646 return undef unless grep { rindex($_, ' (DragonFly Mail Agent') > -1 } $mhead->{'received'}->@*;
  207         857  
21              
22 150         236 state $indicators = __PACKAGE__->INDICATORS;
23 150         262 state $boundaries = ['Original message follows.', 'Message headers follow'];
24 150         224 state $startingof = {
25             # https://github.com/corecode/dma/blob/ffad280aa40c242aa9a2cb9ca5b1b6e8efedd17e/mail.c#L84
26             'message' => ['This is the DragonFly Mail Agent '],
27             };
28 150         216 state $messagesof = {
29             'expired' => [
30             # https://github.com/corecode/dma/blob/master/dma.c#L370C1-L374C19
31             # dma.c:370| if (gettimeofday(&now, NULL) == 0 &&
32             # dma.c:371| (now.tv_sec - st.st_mtim.tv_sec > MAX_TIMEOUT)) {
33             # dma.c:372| snprintf(errmsg, sizeof(errmsg),
34             # dma.c:373| "Could not deliver for the last %d seconds. Giving up.",
35             # dma.c:374| MAX_TIMEOUT);
36             # dma.c:375| goto bounce;
37             # dma.c:376| }
38             'Could not deliver for the last ',
39             ],
40             'hostunknown' => [
41             # net.c:663| snprintf(errmsg, sizeof(errmsg), "DNS lookup failure: host %s not found", host);
42             'DNS lookup failure: host ',
43             ],
44             };
45              
46 150         697 my $dscontents = [__PACKAGE__->DELIVERYSTATUS]; my $v = undef;
  150         313  
47 150         669 my $emailparts = Sisimai::RFC5322->part($mbody, $boundaries);
48 150         241 my $readcursor = 0; # (Integer) Points the current cursor position
49 150         275 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
50              
51 150         682 require Sisimai::Address;
52 150         435 require Sisimai::SMTP::Command;
53              
54 150         799 for my $e ( split("\n", $emailparts->[0]) ) {
55             # Read error messages and delivery status lines from the head of the email to the previous
56             # line of the beginning of the original message.
57 905 100       1612 unless( $readcursor ) {
58             # Beginning of the bounce message or message/delivery-status part
59 150 50       32312 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0;
60 150         253 next;
61             }
62 755 100 66     2580 next if ($readcursor & $indicators->{'deliverystatus'}) == 0 || $e eq "";
63              
64             # This is the DragonFly Mail Agent v0.13 at df.example.jp.
65             #
66             # There was an error delivering your mail to .
67             #
68             # email.example.jp [192.0.2.25] did not like our RCPT TO:
69             # 552 5.2.2 : Recipient address rejected: Mailbox full
70             #
71             # Original message follows.
72 455         697 $v = $dscontents->[-1];
73              
74 455 100       964 if( index($e, 'There was an error delivering your mail to <') == 0 ) {
75             # email.example.jp [192.0.2.25] did not like our RCPT TO:
76             # 552 5.2.2 : Recipient address rejected: Mailbox full
77 150 50       376 if( $v->{'recipient'} ) {
78             # There are multiple recipient addresses in the message body.
79 0         0 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
80 0         0 $v = $dscontents->[-1];
81             }
82 150         1145 $v->{'recipient'} = Sisimai::Address->s3s4(substr($e, index($e, '<'), -1));
83 150         485 $recipients++;
84              
85             } else {
86             # Pick the error message
87 305         742 $v->{'diagnosis'} .= $e;
88              
89             # Pick the remote hostname, and the SMTP command
90             # net.c:500| snprintf(errmsg, sizeof(errmsg), "%s [%s] did not like our %s:\n%s",
91 305 100 66     1324 next if index($e, ' did not like our ') < 0 || length $v->{'rhost'} > 0;
92              
93 135         586 my $p = [split(' ', $e, 3)];
94 135 50       489 $v->{'rhost'} = index($p->[0], '.') > 1 ? $p->[0] : $p->[1];
95 135   50     1084 $v->{'command'} = Sisimai::SMTP::Command->find($e) || '';
96             }
97             }
98 150 50       522 return undef unless $recipients;
99              
100 150         291 for my $e ( @$dscontents ) {
101 150         1217 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
102              
103 150         521 SESSION: for my $r ( keys %$messagesof ) {
104             # Verify each regular expression of session errors
105 290 100       613 next unless grep { index($e->{'diagnosis'}, $_) > -1 } $messagesof->{ $r }->@*;
  290         985  
106 15         35 $e->{'reason'} = $r;
107 15         42 last;
108             }
109             }
110 150         880 return {"ds" => $dscontents, "rfc822" => $emailparts->[1]};
111             }
112              
113             1;
114             __END__