1package Sisimai::Lhost::ApacheJames; 2use parent 'Sisimai::Lhost'; 3use feature ':5.10'; 4use strict; 5use warnings; 6 7sub description { 'Java Apache Mail Enterprise Server' } 8sub make { 9 # Detect an error from ApacheJames 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.26 15 my $class = shift; 16 my $mhead = shift // return undef; 17 my $mbody = shift // return undef; 18 my $match = 0; 19 20 # 'subject' => qr/\A\[BOUNCE\]\z/, 21 # 'received' => qr/JAMES SMTP Server/, 22 # 'message-id' => qr/\d+[.]JavaMail[.].+[@]/, 23 $match ||= 1 if $mhead->{'subject'} eq '[BOUNCE]'; 24 $match ||= 1 if defined $mhead->{'message-id'} && rindex($mhead->{'message-id'}, '.JavaMail.') > -1; 25 $match ||= 1 if grep { rindex($_, 'JAMES SMTP Server') > -1 } @{ $mhead->{'received'} }; 26 return undef unless $match; 27 28 state $indicators = __PACKAGE__->INDICATORS; 29 state $rebackbone = qr|^Content-Type:[ ]message/rfc822|m; 30 state $startingof = { 31 # apache-james-2.3.2/src/java/org/apache/james/transport/mailets/ 32 # AbstractNotify.java|124: out.println("Error message below:"); 33 # AbstractNotify.java|128: out.println("Message details:"); 34 'message' => [''], 35 'error' => ['Error message below:'], 36 }; 37 38 my $dscontents = [__PACKAGE__->DELIVERYSTATUS]; 39 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone); 40 my $readcursor = 0; # (Integer) Points the current cursor position 41 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header 42 my $diagnostic = ''; # (String) Alternative diagnostic message 43 my $subjecttxt = undef; # (String) Alternative Subject text 44 my $gotmessage = 0; # (Integer) Flag for error message 45 my $v = undef; 46 47 for my $e ( split("\n", $emailsteak->[0]) ) { 48 # Read error messages and delivery status lines from the head of the email 49 # to the previous line of the beginning of the original message. 50 unless( $readcursor ) { 51 # Beginning of the bounce message or message/delivery-status part 52 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0; 53 next; 54 } 55 next unless $readcursor & $indicators->{'deliverystatus'}; 56 next unless length $e; 57 58 # Message details: 59 # Subject: Nyaaan 60 # Sent date: Thu Apr 29 01:20:50 JST 2015 61 # MAIL FROM: shironeko@example.jp 62 # RCPT TO: kijitora@example.org 63 # From: Neko <shironeko@example.jp> 64 # To: kijitora@example.org 65 # Size (in bytes): 1024 66 # Number of lines: 64 67 $v = $dscontents->[-1]; 68 69 if( $e =~ /\A[ ][ ]RCPT[ ]TO:[ ]([^ ]+[@][^ ]+)\z/ ) { 70 # RCPT TO: kijitora@example.org 71 if( $v->{'recipient'} ) { 72 # There are multiple recipient addresses in the message body. 73 push @$dscontents, __PACKAGE__->DELIVERYSTATUS; 74 $v = $dscontents->[-1]; 75 } 76 $v->{'recipient'} = $1; 77 $recipients++; 78 79 } elsif( $e =~ /\A[ ][ ]Sent[ ]date:[ ](.+)\z/ ) { 80 # Sent date: Thu Apr 29 01:20:50 JST 2015 81 $v->{'date'} = $1; 82 83 } elsif( $e =~ /\A[ ][ ]Subject:[ ](.+)\z/ ) { 84 # Subject: Nyaaan 85 $subjecttxt = $1; 86 87 } else { 88 next if $gotmessage == 1; 89 90 if( $v->{'diagnosis'} ) { 91 # Get an error message text 92 if( $e eq 'Message details:' ) { 93 # Message details: 94 # Subject: nyaan 95 # ... 96 $gotmessage = 1; 97 98 } else { 99 # Append error message text like the followng: 100 # Error message below: 101 # 550 - Requested action not taken: no such user here 102 $v->{'diagnosis'} .= ' '.$e; 103 } 104 } else { 105 # Error message below: 106 # 550 - Requested action not taken: no such user here 107 $v->{'diagnosis'} = $e if $e eq $startingof->{'error'}->[0]; 108 $v->{'diagnosis'} .= ' '.$e unless $gotmessage; 109 } 110 } 111 } 112 return undef unless $recipients; 113 114 # Set the value of $subjecttxt as a Subject if there is no original message 115 # in the bounce mail. 116 $emailsteak->[1] .= sprintf("Subject: %s\n", $subjecttxt) unless $emailsteak->[1] =~ /^Subject:/m; 117 $_->{'diagnosis'} = Sisimai::String->sweep($_->{'diagnosis'} || $diagnostic) for @$dscontents; 118 return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] }; 119} 120 1211; 122__END__ 123 124=encoding utf-8 125 126=head1 NAME 127 128Sisimai::Lhost::ApacheJames - bounce mail parser class for C<ApacheJames>. 129 130=head1 SYNOPSIS 131 132 use Sisimai::Lhost::ApacheJames; 133 134=head1 DESCRIPTION 135 136Sisimai::Lhost::ApacheJames parses a bounce email which created by C<ApacheJames>. 137Methods in the module are called from only Sisimai::Message. 138 139=head1 CLASS METHODS 140 141=head2 C<B<description()>> 142 143C<description()> returns description string of this module. 144 145 print Sisimai::Lhost::ApacheJames->description; 146 147=head2 C<B<make(I<header data>, I<reference to body string>)>> 148 149C<make()> method parses a bounced email and return results as a array reference. 150See Sisimai::Message for more details. 151 152=head1 AUTHOR 153 154azumakuniyuki 155 156=head1 COPYRIGHT 157 158Copyright (C) 2015-2020 azumakuniyuki, All rights reserved. 159 160=head1 LICENSE 161 162This software is distributed under The BSD 2-Clause License. 163 164=cut 165 166