1package Sisimai::Lhost::FML; 2use parent 'Sisimai::Lhost'; 3use feature ':5.10'; 4use strict; 5use warnings; 6 7sub description { 'fml mailing list server/manager' }; 8sub make { 9 # Detect an error from fml mailing list server/manager 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.22.3 15 my $class = shift; 16 my $mhead = shift // return undef; 17 my $mbody = shift // return undef; 18 19 return undef unless defined $mhead->{'x-mlserver'}; 20 return undef unless $mhead->{'from'} =~ /.+[-]admin[@].+/; 21 return undef unless $mhead->{'message-id'} =~ /\A[<]\d+[.]FML.+[@].+[>]\z/; 22 23 state $rebackbone = qr|^Original[ ]mail[ ]as[ ]follows:|m; 24 state $errortitle = { 25 'rejected' => qr{(?> 26 (?:Ignored[ ])*NOT[ ]MEMBER[ ]article[ ]from[ ] 27 |reject[ ]mail[ ](?:.+:|from)[ ], 28 |Spam[ ]mail[ ]from[ ]a[ ]spammer[ ]is[ ]rejected 29 |You[ ].+[ ]are[ ]not[ ]member 30 ) 31 }x, 32 'systemerror' => qr{(?: 33 fml[ ]system[ ]error[ ]message 34 |Loop[ ]Alert:[ ] 35 |Loop[ ]Back[ ]Warning:[ ] 36 |WARNING:[ ]UNIX[ ]FROM[ ]Loop 37 ) 38 }x, 39 'securityerror' => qr/Security Alert/, 40 }; 41 state $errortable = { 42 'rejected' => qr{(?> 43 (?:Ignored[ ])*NOT[ ]MEMBER[ ]article[ ]from[ ] 44 |reject[ ](?: 45 mail[ ]from[ ].+[@].+ 46 |since[ ].+[ ]header[ ]may[ ]cause[ ]mail[ ]loop 47 |spammers: 48 ) 49 |You[ ]are[ ]not[ ]a[ ]member[ ]of[ ]this[ ]mailing[ ]list 50 ) 51 }x, 52 'systemerror' => qr{(?: 53 Duplicated[ ]Message-ID 54 |fml[ ].+[ ]has[ ]detected[ ]a[ ]loop[ ]condition[ ]so[ ]that 55 |Loop[ ]Back[ ]Warning: 56 ) 57 }x, 58 'securityerror' => qr/Security alert:/, 59 }; 60 61 my $dscontents = [__PACKAGE__->DELIVERYSTATUS]; 62 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone); 63 my $readcursor = 0; # (Integer) Points the current cursor position 64 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header 65 my $v = undef; 66 67 for my $e ( split("\n", $emailsteak->[0]) ) { 68 # Read error messages and delivery status lines from the head of the email 69 # to the previous line of the beginning of the original message. 70 next unless length $e; 71 72 # Duplicated Message-ID in <2ndml@example.com>. 73 # Original mail as follows: 74 $v = $dscontents->[-1]; 75 76 if( $e =~ /[<]([^ ]+?[@][^ ]+?)[>][.]\z/ ) { 77 # Duplicated Message-ID in <2ndml@example.com>. 78 if( $v->{'recipient'} ) { 79 # There are multiple recipient addresses in the message body. 80 push @$dscontents, __PACKAGE__->DELIVERYSTATUS; 81 $v = $dscontents->[-1]; 82 } 83 $v->{'recipient'} = $1; 84 $v->{'diagnosis'} = $e; 85 $recipients++; 86 87 } else { 88 # If you know the general guide of this list, please send mail with 89 # the mail body 90 $v->{'diagnosis'} .= $e; 91 } 92 } 93 return undef unless $recipients; 94 95 for my $e ( @$dscontents ) { 96 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'}); 97 98 for my $f ( keys %$errortable ) { 99 # Try to match with error messages defined in $errortable 100 next unless $e->{'diagnosis'} =~ $errortable->{ $f }; 101 $e->{'reason'} = $f; 102 last; 103 } 104 next if $e->{'reason'}; 105 106 # Error messages in the message body did not matched 107 for my $f ( keys %$errortitle ) { 108 # Try to match with the Subject string 109 next unless $mhead->{'subject'} =~ $errortitle->{ $f }; 110 $e->{'reason'} = $f; 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::FML - bounce mail parser class for FML (fml.org). 125 126=head1 SYNOPSIS 127 128 use Sisimai::Lhost::FML; 129 130=head1 DESCRIPTION 131 132Sisimai::Lhost::FML parses a bounce email which created by C<fml mailing 133list server/manager>. Methods in the module are called from only Sisimai::Message. 134 135=head1 CLASS METHODS 136 137=head2 C<B<description()>> 138 139C<description()> returns description string of this module. 140 141 print Sisimai::Lhost::FML->description; 142 143=head2 C<B<make(I<header data>, I<reference to body string>)>> 144 145C<make()> method parses a bounced email and return results as a array reference. 146See Sisimai::Message for more details. 147 148=head1 AUTHOR 149 150azumakuniyuki 151 152=head1 COPYRIGHT 153 154Copyright (C) 2017-2020 azumakuniyuki, All rights reserved. 155 156=head1 LICENSE 157 158This software is distributed under The BSD 2-Clause License. 159 160=cut 161 162