1package Sisimai::Lhost::McAfee; 2use parent 'Sisimai::Lhost'; 3use feature ':5.10'; 4use strict; 5use warnings; 6 7sub description { 'McAfee Email Appliance' } 8sub make { 9 # Detect an error from McAfee 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.1 15 my $class = shift; 16 my $mhead = shift // return undef; 17 my $mbody = shift // return undef; 18 19 # X-NAI-Header: Modified by McAfee Email and Web Security Virtual Appliance 20 return undef unless defined $mhead->{'x-nai-header'}; 21 return undef unless index($mhead->{'x-nai-header'}, 'Modified by McAfee') > -1; 22 return undef unless $mhead->{'subject'} eq 'Delivery Status'; 23 24 state $indicators = __PACKAGE__->INDICATORS; 25 state $rebackbone = qr|^Content-Type:[ ]message/rfc822|m; 26 state $startingof = { 'message' => ['--- The following addresses had delivery problems ---'] }; 27 state $refailures = { 28 'userunknown' => qr{(?: 29 [ ]User[ ][(].+[@].+[)][ ]unknown[.][ ] 30 |550[ ]Unknown[ ]user[ ][^ ]+[@][^ ]+ 31 |550[ ][<].+?[@].+?[>][.]+[ ]User[ ]not[ ]exist 32 |No[ ]such[ ]user 33 ) 34 }x, 35 }; 36 37 require Sisimai::RFC1894; 38 my $fieldtable = Sisimai::RFC1894->FIELDTABLE; 39 my $dscontents = [__PACKAGE__->DELIVERYSTATUS]; 40 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone); 41 my $readcursor = 0; # (Integer) Points the current cursor position 42 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header 43 my $diagnostic = ''; # (String) Alternative diagnostic message 44 my $v = undef; 45 my $p = ''; 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]) > -1; 53 next; 54 } 55 next unless $readcursor & $indicators->{'deliverystatus'}; 56 next unless length $e; 57 58 # Content-Type: text/plain; name="deliveryproblems.txt" 59 # 60 # --- The following addresses had delivery problems --- 61 # 62 # <user@example.com> (User unknown user@example.com) 63 # 64 # --------------Boundary-00=_00000000000000000000 65 # Content-Type: message/delivery-status; name="deliverystatus.txt" 66 # 67 $v = $dscontents->[-1]; 68 69 if( $e =~ /\A[<]([^ ]+[@][^ ]+)[>][ \t]+[(](.+)[)]\z/ ) { 70 # <kijitora@example.co.jp> (Unknown user kijitora@example.co.jp) 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 $diagnostic = $2; 78 $recipients++; 79 80 } elsif( my $f = Sisimai::RFC1894->match($e) ) { 81 # $e matched with any field defined in RFC3464 82 my $o = Sisimai::RFC1894->field($e); 83 unless( $o ) { 84 # Fallback code for empty value or invalid formatted value 85 # - Original-Recipient: <kijitora@example.co.jp> 86 $v->{'alias'} = Sisimai::Address->s3s4($1) if $e =~ /\AOriginal-Recipient:[ ]*([^ ]+)\z/; 87 next; 88 } 89 next unless exists $fieldtable->{ $o->[0] }; 90 $v->{ $fieldtable->{ $o->[0] } } = $o->[2]; 91 92 } else { 93 # Continued line of the value of Diagnostic-Code field 94 next unless index($p, 'Diagnostic-Code:') == 0; 95 next unless $e =~ /\A[ \t]+(.+)\z/; 96 $v->{'diagnosis'} .= ' '.$1; 97 } # End of error message part 98 } continue { 99 # Save the current line for the next loop 100 $p = $e; 101 } 102 return undef unless $recipients; 103 104 for my $e ( @$dscontents ) { 105 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'} || $diagnostic); 106 107 SESSION: for my $r ( keys %$refailures ) { 108 # Verify each regular expression of session errors 109 next unless $e->{'diagnosis'} =~ $refailures->{ $r }; 110 $e->{'reason'} = $r; 111 last; 112 } 113 } 114 return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] }; 115} 116 1171; 118__END__ 119 120=encoding utf-8 121 122=head1 NAME 123 124Sisimai::Lhost::McAfee - bounce mail parser class for C<McAfee Email Appliance>. 125 126=head1 SYNOPSIS 127 128 use Sisimai::Lhost::McAfee; 129 130=head1 DESCRIPTION 131 132Sisimai::Lhost::McAfee parses a bounce email which created by 133C<McAfee Email Appliance>. 134Methods in the module are called from only Sisimai::Message. 135 136=head1 CLASS METHODS 137 138=head2 C<B<description()>> 139 140C<description()> returns description string of this module. 141 142 print Sisimai::Lhost::McAfee->description; 143 144=head2 C<B<make(I<header data>, I<reference to body string>)>> 145 146C<make()> method parses a bounced email and return results as a array reference. 147See Sisimai::Message for more details. 148 149=head1 AUTHOR 150 151azumakuniyuki 152 153=head1 COPYRIGHT 154 155Copyright (C) 2014-2020 azumakuniyuki, All rights reserved. 156 157=head1 LICENSE 158 159This software is distributed under The BSD 2-Clause License. 160 161=cut 162 163