1package Sisimai::Lhost::MessagingServer; 2use parent 'Sisimai::Lhost'; 3use feature ':5.10'; 4use strict; 5use warnings; 6 7sub description { 'Oracle Communications Messaging Server' } 8sub make { 9 # Detect an error from Oracle Communications Messaging Server 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.3 15 my $class = shift; 16 my $mhead = shift // return undef; 17 my $mbody = shift // return undef; 18 my $match = 0; 19 20 # 'received' => qr/[ ][(]MessagingServer[)][ ]with[ ]/, 21 $match ||= 1 if rindex($mhead->{'content-type'}, 'Boundary_(ID_') > -1; 22 $match ||= 1 if index($mhead->{'subject'}, 'Delivery Notification: ') == 0; 23 return undef unless $match; 24 25 state $indicators = __PACKAGE__->INDICATORS; 26 state $rebackbone = qr<^(?:Content-type:[ \t]*message/rfc822|Return-path:[ \t]*)>m; 27 state $startingof = { 'message' => ['This report relates to a message you sent with the following header fields:'] }; 28 state $messagesof = { 'hostunknown' => ['Illegal host/domain name found'] }; 29 30 my $dscontents = [__PACKAGE__->DELIVERYSTATUS]; 31 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone); 32 my $readcursor = 0; # (Integer) Points the current cursor position 33 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header 34 my $v = undef; 35 36 for my $e ( split("\n", $emailsteak->[0]) ) { 37 # Read error messages and delivery status lines from the head of the email 38 # to the previous line of the beginning of the original message. 39 unless( $readcursor ) { 40 # Beginning of the bounce message or message/delivery-status part 41 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0; 42 next; 43 } 44 next unless $readcursor & $indicators->{'deliverystatus'}; 45 next unless length $e; 46 47 # --Boundary_(ID_0000000000000000000000) 48 # Content-type: text/plain; charset=us-ascii 49 # Content-language: en-US 50 # 51 # This report relates to a message you sent with the following header fields: 52 # 53 # Message-id: <CD8C6134-C312-41D5-B083-366F7FA1D752@me.example.com> 54 # Date: Fri, 21 Nov 2014 23:34:45 +0900 55 # From: Shironeko <shironeko@me.example.com> 56 # To: kijitora@example.jp 57 # Subject: Nyaaaaaaaaaaaaaaaaaaaaaan 58 # 59 # Your message cannot be delivered to the following recipients: 60 # 61 # Recipient address: kijitora@example.jp 62 # Reason: Remote SMTP server has rejected address 63 # Diagnostic code: smtp;550 5.1.1 <kijitora@example.jp>... User Unknown 64 # Remote system: dns;mx.example.jp (TCP|17.111.174.67|47323|192.0.2.225|25) (6jo.example.jp ESMTP SENDMAIL-VM) 65 $v = $dscontents->[-1]; 66 67 if( $e =~ /\A[ \t]+Recipient address:[ \t]*([^ ]+[@][^ ]+)\z/ ) { 68 # Recipient address: kijitora@example.jp 69 if( $v->{'recipient'} ) { 70 # There are multiple recipient addresses in the message body. 71 push @$dscontents, __PACKAGE__->DELIVERYSTATUS; 72 $v = $dscontents->[-1]; 73 } 74 $v->{'recipient'} = Sisimai::Address->s3s4($1); 75 $recipients++; 76 77 } elsif( $e =~ /\A[ \t]+Original address:[ \t]*([^ ]+[@][^ ]+)\z/ ) { 78 # Original address: kijitora@example.jp 79 $v->{'recipient'} = Sisimai::Address->s3s4($1); 80 81 } elsif( $e =~ /\A[ \t]+Date:[ \t]*(.+)\z/ ) { 82 # Date: Fri, 21 Nov 2014 23:34:45 +0900 83 $v->{'date'} = $1; 84 85 } elsif( $e =~ /\A[ \t]+Reason:[ \t]*(.+)\z/ ) { 86 # Reason: Remote SMTP server has rejected address 87 $v->{'diagnosis'} = $1; 88 89 } elsif( $e =~ /\A[ \t]+Diagnostic code:[ \t]*([^ ]+);(.+)\z/ ) { 90 # Diagnostic code: smtp;550 5.1.1 <kijitora@example.jp>... User Unknown 91 $v->{'spec'} = uc $1; 92 $v->{'diagnosis'} = $2; 93 94 } elsif( $e =~ /\A[ \t]+Remote system:[ \t]*dns;([^ ]+)[ \t]*([^ ]+)[ \t]*.+\z/ ) { 95 # Remote system: dns;mx.example.jp (TCP|17.111.174.67|47323|192.0.2.225|25) 96 # (6jo.example.jp ESMTP SENDMAIL-VM) 97 my $remotehost = $1; # remote host 98 my $sessionlog = $2; # smtp session 99 $v->{'rhost'} = $remotehost; 100 101 # The value does not include ".", use IP address instead. 102 # (TCP|17.111.174.67|47323|192.0.2.225|25) 103 next unless $sessionlog =~ /\A[(]TCP|(.+)|\d+|(.+)|\d+[)]/; 104 $v->{'lhost'} = $1; 105 $v->{'rhost'} = $2 unless $remotehost =~ /[^.]+[.][^.]+/; 106 107 } else { 108 # Original-envelope-id: 0NFC009FLKOUVMA0@mr21p30im-asmtp004.me.com 109 # Reporting-MTA: dns;mr21p30im-asmtp004.me.com (tcp-daemon) 110 # Arrival-date: Thu, 29 Apr 2014 23:34:45 +0000 (GMT) 111 # 112 # Original-recipient: rfc822;kijitora@example.jp 113 # Final-recipient: rfc822;kijitora@example.jp 114 # Action: failed 115 # Status: 5.1.1 (Remote SMTP server has rejected address) 116 # Remote-MTA: dns;mx.example.jp (TCP|17.111.174.67|47323|192.0.2.225|25) 117 # (6jo.example.jp ESMTP SENDMAIL-VM) 118 # Diagnostic-code: smtp;550 5.1.1 <kijitora@example.jp>... User Unknown 119 # 120 if( $e =~ /\AStatus:[ \t]*(\d[.]\d[.]\d)[ \t]*[(](.+)[)]\z/ ) { 121 # Status: 5.1.1 (Remote SMTP server has rejected address) 122 $v->{'status'} = $1; 123 $v->{'diagnosis'} ||= $2; 124 125 } elsif( $e =~ /\AArrival-Date:[ ]*(.+)\z/ ) { 126 # Arrival-date: Thu, 29 Apr 2014 23:34:45 +0000 (GMT) 127 $v->{'date'} ||= $1; 128 129 } elsif( $e =~ /\AReporting-MTA:[ ]*(?:DNS|dns);[ ]*(.+)\z/ ) { 130 # Reporting-MTA: dns;mr21p30im-asmtp004.me.com (tcp-daemon) 131 my $localhost = $1; 132 $v->{'lhost'} ||= $localhost; 133 $v->{'lhost'} = $localhost unless $v->{'lhost'} =~ /[^.]+[.][^ ]+/; 134 } 135 } # End of error message part 136 } 137 return undef unless $recipients; 138 139 for my $e ( @$dscontents ) { 140 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'}); 141 142 SESSION: for my $r ( keys %$messagesof ) { 143 # Verify each regular expression of session errors 144 next unless grep { index($e->{'diagnosis'}, $_) > -1 } @{ $messagesof->{ $r } }; 145 $e->{'reason'} = $r; 146 last; 147 } 148 } 149 return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] }; 150} 151 1521; 153__END__ 154 155=encoding utf-8 156 157=head1 NAME 158 159Sisimai::Lhost::MessagingServer - bounce mail parser class for 160C<Sun Java System Messaging Server> and C<Oracle Communications Messaging Server>. 161 162=head1 SYNOPSIS 163 164 use Sisimai::Lhost::MessagingServer; 165 166=head1 DESCRIPTION 167 168Sisimai::Lhost::MessagingServer parses a bounce email which created by 169C<Oracle Communications Messaging Server> and C<Sun Java System Messaging Server>. 170Methods in the module are called from only Sisimai::Message. 171 172=head1 CLASS METHODS 173 174=head2 C<B<description()>> 175 176C<description()> returns description string of this module. 177 178 print Sisimai::Lhost::MessagingServer->description; 179 180=head2 C<B<make(I<header data>, I<reference to body string>)>> 181 182C<make()> method parses a bounced email and return results as a array reference. 183See Sisimai::Message for more details. 184 185=head1 AUTHOR 186 187azumakuniyuki 188 189=head1 COPYRIGHT 190 191Copyright (C) 2014-2020 azumakuniyuki, All rights reserved. 192 193=head1 LICENSE 194 195This software is distributed under The BSD 2-Clause License. 196 197=cut 198 199