1package Sisimai::Lhost::X3;
2use parent 'Sisimai::Lhost';
3use feature ':5.10';
4use strict;
5use warnings;
6
7sub description { 'Unknown MTA #3' }
8sub make {
9    # Detect an error from Unknown MTA #3
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    # @since v4.1.9
15    my $class = shift;
16    my $mhead = shift // return undef;
17    my $mbody = shift // return undef;
18
19    return undef unless index($mhead->{'from'}, 'Mail Delivery System') == 0;
20    return undef unless index($mhead->{'subject'}, 'Delivery status notification') == 0;
21
22    state $indicators = __PACKAGE__->INDICATORS;
23    state $rebackbone = qr|^Content-Type:[ ]message/rfc822|m;
24    state $startingof = { 'message' => ['      This is an automatically generated Delivery Status Notification.'] };
25
26    my $dscontents = [__PACKAGE__->DELIVERYSTATUS];
27    my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone);
28    my $readcursor = 0;     # (Integer) Points the current cursor position
29    my $recipients = 0;     # (Integer) The number of 'Final-Recipient' header
30    my $v = undef;
31
32    for my $e ( split("\n", $emailsteak->[0]) ) {
33        # Read error messages and delivery status lines from the head of the email
34        # to the previous line of the beginning of the original message.
35        unless( $readcursor ) {
36            # Beginning of the bounce message or message/delivery-status part
37            $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0;
38            next;
39        }
40        next unless $readcursor & $indicators->{'deliverystatus'};
41        next unless length $e;
42
43        # ============================================================================
44        #      This is an automatically generated Delivery Status Notification.
45        #
46        # Delivery to the following recipients failed permanently:
47        #
48        #   * kijitora@example.com
49        #
50        #
51        # ============================================================================
52        #                             Technical details:
53        #
54        # SMTP:RCPT host 192.0.2.8: 553 5.3.0 <kijitora@example.com>... No such user here
55        #
56        #
57        # ============================================================================
58        $v = $dscontents->[-1];
59
60        if( $e =~ /\A[ \t]+[*][ \t]([^ ]+[@][^ ]+)\z/ ) {
61            #   * kijitora@example.com
62            if( $v->{'recipient'} ) {
63                # There are multiple recipient addresses in the message body.
64                push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
65                $v = $dscontents->[-1];
66            }
67            $v->{'recipient'} = $1;
68            $recipients++;
69
70        } else {
71            # Detect error message
72            if( $e =~ /\ASMTP:([^ ]+)[ \t](.+)\z/ ) {
73                # SMTP:RCPT host 192.0.2.8: 553 5.3.0 <kijitora@example.com>... No such user here
74                $v->{'command'} = uc $1;
75                $v->{'diagnosis'} = $2;
76
77            } elsif( $e =~ /\ARouting: (.+)/ ) {
78                # Routing: Could not find a gateway for kijitora@example.co.jp
79                $v->{'diagnosis'} = $1;
80
81            } elsif( $e =~ /\ADiagnostic-Code: smtp; (.+)/ ) {
82                # Diagnostic-Code: smtp; 552 5.2.2 Over quota
83                $v->{'diagnosis'} = $1;
84            }
85        }
86    }
87    return undef unless $recipients;
88
89    for my $e ( @$dscontents ) {
90        $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
91        $e->{'status'}    = Sisimai::SMTP::Status->find($e->{'diagnosis'}) || '';
92    }
93    return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] };
94}
95
961;
97__END__
98
99=encoding utf-8
100
101=head1 NAME
102
103Sisimai::Lhost::X3 - bounce mail parser class for C<X3>.
104
105=head1 SYNOPSIS
106
107    use Sisimai::Lhost::X3;
108
109=head1 DESCRIPTION
110
111Sisimai::Lhost::X3 parses a bounce email which created by Unknown MTA #3.
112Methods in the module are called from only Sisimai::Message.
113
114=head1 CLASS METHODS
115
116=head2 C<B<description()>>
117
118C<description()> returns description string of this module.
119
120    print Sisimai::Lhost::X3->description;
121
122=head2 C<B<make(I<header data>, I<reference to body string>)>>
123
124C<make()> method parses a bounced email and return results as a array reference.
125See Sisimai::Message for more details.
126
127=head1 AUTHOR
128
129azumakuniyuki
130
131=head1 COPYRIGHT
132
133Copyright (C) 2014-2020 azumakuniyuki, All rights reserved.
134
135=head1 LICENSE
136
137This software is distributed under The BSD 2-Clause License.
138
139=cut
140