Files
2024-10-14 00:08:40 +02:00

207 lines
6.3 KiB
Perl

package Sisimai::RFC3834;
use feature ':5.10';
use strict;
use warnings;
# http://tools.ietf.org/html/rfc3834
my $MarkingsOf = { 'boundary' => qr/\A__SISIMAI_PSEUDO_BOUNDARY__\z/ };
my $AutoReply1 = {
# http://www.iana.org/assignments/auto-submitted-keywords/auto-submitted-keywords.xhtml
'auto-submitted' => qr/\Aauto-(?:generated|replied|notified)/,
# https://msdn.microsoft.com/en-us/library/ee219609(v=exchg.80).aspx
'x-auto-response-suppress' => qr/(?:oof|autoreply)/,
'precedence' => qr/\Aauto_reply\z/,
'subject' => qr/\A(?>
auto:
|auto[ ]response:
|automatic[ ]reply:
|out[ ]of[ ](?:the[ ])*office:
)
/x,
};
my $Excludings = {
'subject' => qr{(?:
security[ ]information[ ]for # sudo
|mail[ ]failure[ ][-] # Exim
)
}x,
'from' => qr/(?:root|postmaster|mailer-daemon)[@]/,
'to' => qr/root[@]/,
};
my $SubjectSet = qr{\A(?>
(?:.+?)?re:
|auto(?:[ ]response):
|automatic[ ]reply:
|out[ ]of[ ]office:
)
[ ]*(.+)\z
}x;
sub description { 'Detector for auto replied message' }
sub smtpagent { 'RFC3834' }
sub headerlist { [qw|Auto-Submitted Precedence X-Auto-Response-Suppress|] }
sub scan {
# Detect auto reply message as RFC3834
# @param [Hash] mhead Message header of a bounce email
# @options mhead [String] from From header
# @options mhead [String] date Date header
# @options mhead [String] subject Subject header
# @options mhead [Array] received Received headers
# @options mhead [String] others Other required headers
# @param [String] mbody Message body of a bounce email
# @return [Hash, Undef] Bounce data list and message/rfc822 part
# or Undef if it failed to parse or the
# arguments are missing
# @since v4.1.28
my $class = shift;
my $mhead = shift // return undef;
my $mbody = shift // return undef;
my $leave = 0;
my $match = 0;
return undef unless keys %$mhead;
return undef unless ref $mbody eq 'SCALAR';
DETECT_EXCLUSION_MESSAGE: for my $e ( keys %$Excludings ) {
# Exclude message from root@
next unless exists $mhead->{ $e };
next unless defined $mhead->{ $e };
next unless lc($mhead->{ $e }) =~ $Excludings->{ $e };
$leave = 1;
last;
}
return undef if $leave;
DETECT_AUTO_REPLY_MESSAGE: for my $e ( keys %$AutoReply1 ) {
# RFC3834 Auto-Submitted and other headers
next unless exists $mhead->{ $e };
next unless defined $mhead->{ $e };
next unless lc($mhead->{ $e }) =~ $AutoReply1->{ $e };
$match++;
last;
}
return undef unless $match;
require Sisimai::Bite::Email;
my $dscontents = [Sisimai::Bite::Email->DELIVERYSTATUS];
my @hasdivided = split("\n", $$mbody);
my $rfc822part = ''; # (String) message/rfc822-headers part
my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
my $maxmsgline = 5; # (Integer) Max message length(lines)
my $haveloaded = 0; # (Integer) The number of lines loaded from message body
my $blanklines = 0; # (Integer) Counter for countinuous blank lines
my $countuntil = 1; # (Integer) Maximun value of blank lines in the body part
my $v = $dscontents->[-1];
RECIPIENT_ADDRESS: {
# Try to get the address of the recipient
for my $e ('from', 'return-path') {
# Get the recipient address
next unless exists $mhead->{ $e };
next unless defined $mhead->{ $e };
$v->{'recipient'} = $mhead->{ $e };
last;
}
if( $v->{'recipient'} ) {
# Clean-up the recipient address
$v->{'recipient'} = Sisimai::Address->s3s4($v->{'recipient'});
$recipients++;
}
}
return undef unless $recipients;
if( $mhead->{'content-type'} ) {
# Get the boundary string and set regular expression for matching with
# the boundary string.
my $b0 = Sisimai::MIME->boundary($mhead->{'content-type'}, 0);
$MarkingsOf->{'boundary'} = qr/\A\Q$b0\E\z/ if length $b0;
}
BODY_PARSER: {
# Get vacation message
for my $e ( @hasdivided ) {
# Read the first 5 lines except a blank line
$countuntil += 1 if $e =~ $MarkingsOf->{'boundary'};
unless( length $e ) {
# Check a blank line
$blanklines++;
last if $blanklines > $countuntil;
next;
}
next unless rindex($e, ' ') > -1;
next if index($e, 'Content-Type') == 0;
next if index($e, 'Content-Transfer') == 0;
$v->{'diagnosis'} .= $e.' ';
$haveloaded++;
last if $haveloaded >= $maxmsgline;
}
$v->{'diagnosis'} ||= $mhead->{'subject'};
}
$v->{'diagnosis'} = Sisimai::String->sweep($v->{'diagnosis'});
$v->{'reason'} = 'vacation';
$v->{'agent'} = __PACKAGE__->smtpagent;
$v->{'date'} = $mhead->{'date'};
$v->{'status'} = '';
# Get the Subject header from the original message
$rfc822part = 'Subject: '.$1."\n" if lc($mhead->{'subject'}) =~ $SubjectSet;
return { 'ds' => $dscontents, 'rfc822' => $rfc822part };
}
1;
__END__
=encoding utf-8
=head1 NAME
Sisimai::RFC3834 - RFC3834 auto reply message detector
=head1 SYNOPSIS
use Sisimai::RFC3834;
=head1 DESCRIPTION
Sisimai::RFC3834 is a class which called from called from only Sisimai::Message
when other Sisimai::Bite::Email::* modules did not detected a bounce reason.
=head1 CLASS METHODS
=head2 C<B<description()>>
C<description()> returns description string of this module.
print Sisimai::RFC3834->description;
=head2 C<B<smtpagent()>>
C<smtpagent()> returns MDA name or string 'RFC3834'.
print Sisimai::RFC3834->smtpagent;
=head2 C<B<scan(I<header data>, I<reference to body string>)>>
C<scan()> method parses an auto replied message and return results as an array
reference. See Sisimai::Message for more details.
=head1 AUTHOR
azumakuniyuki
=head1 COPYRIGHT
Copyright (C) 2015-2018 azumakuniyuki, All rights reserved.
=head1 LICENSE
This software is distributed under The BSD 2-Clause License.
=cut