--- autoresponder-0.8.pl 2025-09-12 10:36:21.235780551 +0200 +++ autoresponder-0.8.pl 2025-09-12 10:38:44.924798079 +0200 @@ -1,8 +1,7 @@ #!/usr/bin/perl # -# Copyright (C) 2008-2016 Manuel Mausz (manuel@mausz.at) +# Copyright (C) 2008-2025 Manuel Mausz (manuel@mausz.at) # http://manuel.mausz.at/coding/autoresponder/ -# Origin code copyright (c) 2004-2008 Parallels GmbH (http://www.parallels.com) # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -30,9 +29,10 @@ use strict; use warnings; use utf8; +use v5.38; binmode(STDOUT, ':utf8'); -my $VERSION = '0.7'; +my $VERSION = '0.8'; #------------------------------------------------------------------------------- # DATABASE TABLE LAYOUT @@ -88,7 +88,7 @@ # this/these header/s will be added to every generated reply # and checked to detect some loops more easily -my %myheader = ('X-Mailer' => 'Confixx Autoresponder'); +my %myheader = ('X-Mailer' => 'UD::Autoresponder'); # this can be enabled if its important to exit with exitcode 0 (=success) # on all errors. this means "mail sent successfully" although an error occured. @@ -107,7 +107,7 @@ #------------------------------------------------------------------------------- my %args = (); -my $getopt = new Getopt::Long::Parser(); +my $getopt = Getopt::Long::Parser->new(); $getopt->configure('prefix_pattern=--|-'); $getopt->getoptions( \%args, @@ -130,7 +130,7 @@ # create logger $args{'d'} = $args{'d'} || $debuglvl || 0; $args{'l'} = $args{'l'} || $logfile || undef; -our $log = new Logger(($args{'l'}) ? (file => $args{'l'}) : (handle => \*STDERR), level => $args{'d'}) +our $log = Logger->new(($args{'l'}) ? (file => $args{'l'}) : (handle => \*STDERR), level => $args{'d'}) or die("Couldn't create logger: $!"); # fetch ident @@ -169,7 +169,7 @@ ) or die("Unable to execute database query: ", DBI->errstr); if (scalar($cnt[0]) > 0) { - $log->info("Loop detected.", $/); + $log->warning("Loop detected.", $/); exit(0); } @@ -201,11 +201,11 @@ # send mail via mailer executable $log->debug("Sending reply...", $/); my @args = ($mailer, '-ti', '-f', $reply->{'from'}->address()); -open(MAIL, '|-') || exec { $args[0] } @args +open(my $mailfh, '|-') || exec { $args[0] } @args or die("Unable to open mailer: $!"); -binmode(MAIL, ':utf8'); -$reply->{'reply'}->print(\*MAIL); -close(MAIL); +$mailfh->binmode(':utf8'); +$reply->{'reply'}->print($mailfh); +$mailfh->close; # insert sender into database $log->debug("Inserting sender into database...", $/); @@ -246,11 +246,11 @@ if ($hvalue = $headers->get($hname)) { chomp($hvalue); - die("Message is either invalid, bounced or from a mailer daemon.") + die("Message is either invalid, bounced or from a mailer daemon.\n") if ($hvalue eq '<>' || $hvalue eq "#@[]" || $hvalue =~ /MAILER-DAEMON$/i); - die("Unable to parse mail address.") + die("Unable to parse mail address.\n") if (!(@addrs = Mail::Address->parse($hvalue))); - die("Value isn't exactly one mail address.") + die("Value isn't exactly one mail address.\n") if (scalar(@addrs) != 1); push(@sender, $addrs[0]); } @@ -258,14 +258,14 @@ # check From $hname = 'From'; $headers->unfold($hname); - die("No value.") + die("No value.\n") if (!($hvalue = $headers->get($hname))); chomp($hvalue); - die("Message is either invalid or from a mailer daemon.") + die("Message is either invalid or from a mailer daemon.\n") if ($hvalue =~ /MAILER-DAEMON$/i || $hvalue =~ /Mail Delivery (?:Sub)?(?:system|service)/i); - die("Unable to parse mail address.") + die("Unable to parse mail address.\n") if (!(@addrs = Mail::Address->parse($hvalue))); - die("Value isn't exactly one mail address.") + die("Value isn't exactly one mail address.\n") if (scalar(@addrs) != 1); push(@sender, $addrs[0]); @@ -275,17 +275,27 @@ if ($hvalue = $headers->get($hname)) { chomp($hvalue); - die("Message is either junk, bulk or from mailing list.") + die("Message is either junk, bulk or from mailing list.\n") if ($hvalue =~ /(?:junk|list|bulk|autoreply)/i); } + # check Auto-Submitted + $hname = 'Auto-Submitted'; + $headers->unfold($hname); + if ($hvalue = $headers->get($hname)) + { + chomp($hvalue); + die("Message is flagged as auto submitted.\n") + if ($hvalue !~ /no/i); + } + # check X-Auto-Response-Suppress $hname = 'X-Auto-Response-Suppress'; $headers->unfold($hname); if ($hvalue = $headers->get($hname)) { chomp($hvalue); - die("Message is has auto response suppress header.") + die("Message has active uto response suppression.\n") if ($hvalue =~ /(?:all|oof|autoreply)/i); } @@ -295,7 +305,7 @@ if ($hvalue = $headers->get($hname)) { chomp($hvalue); - die("Message is junk.") + die("Message is junk.\n") if ($hvalue =~ /yes/i); } @@ -305,7 +315,7 @@ 'X-Mailing-List', 'X-ML-Name', 'X-List') { $hname = $_; # explicit assignment needed - die("Message is from mailing list.") + die("Message is from mailing list.\n") if (($hvalue = $headers->get($hname)) && chomp($hvalue)); } @@ -315,16 +325,16 @@ if ($hvalue = $headers->get($hname)) { chomp($hvalue); - die("Message is bounced.") + die("Message is bounced.\n") if ($hvalue =~ /^Delivery Status Notification \(Failure\)$/i || $hvalue =~ /Automated reply/i); $subject = $hvalue; } # check my own headers - foreach (keys(%$myheader)) + foreach (keys($myheader->%*)) { $hname = $_; # explicit assignment needed - die("Message is signed by myself. Probably looping.") + die("Message is signed by myself. Probably looping.\n") if (($hvalue = $headers->get($hname)) && chomp($hvalue) && $hvalue eq $myheader->{$hname}); } @@ -342,7 +352,7 @@ if ($@) { my $error = $@; - $log->info("Invalid header '", $hname, "': ", $error); + $log->warning("Unexpected header value '", $hname, "': ", $error); $log->debug("Header value: '", $hvalue, "'.", $/); exit(0); }; @@ -403,6 +413,9 @@ Type => 'text/plain; charset=UTF-8', ) or die("Unable to create reply object."); + # remove MIME::Lite added header + $reply->delete('X-Mailer'); + # create message-id + threading my ($seconds, $microseconds) = gettimeofday(); my $messageid = sprintf("<%x.%06d.%x@%s>", $seconds, $microseconds, @@ -414,10 +427,8 @@ if (length($hdata->{'references'}) || length($hdata->{'messageid'})); # add some other headers - foreach (keys(%$myheader)) - { - $reply->add($_ = $myheader->{$_}); - } + $reply->add($_, $myheader->{$_}) + foreach (keys($myheader->%*)); $reply->add('Precedence', 'bulk'); $reply->add('Auto-Submitted', 'auto-replied'); $reply->add('X-Auto-Response-Suppress', 'All'); @@ -440,6 +451,7 @@ use Carp; use strict; use warnings; +use v5.38; use constant ERROR => 0; use constant WARNING => 1; use constant INFO => 2; @@ -455,26 +467,26 @@ bless($self, $class); - @{$self->{'handles'}} = (); + $self->{'handles'}->@* = (); if (defined($args{'handle'})) { - my $fh = new IO::Handle(); + my $fh = IO::Handle->new(); $fh->fdopen($args{'handle'}, 'w') or return undef; binmode($fh, ':utf8'); - push(@{$self->{'handles'}}, $fh); + push($self->{'handles'}->@*, $fh); } if (defined($args{'file'})) { - my $fh = new IO::File($args{'file'}, "a") + my $fh = IO::File->new($args{'file'}, "a") or return undef; # this is needed for standard confixx qmail installation chmod(0660, $args{'file'}); binmode($fh, ':utf8'); - push(@{$self->{'handles'}}, $fh); + push($self->{'handles'}->@*, $fh); } croak("No logging destination defined") - if (scalar(@{$self->{'handles'}}) <= 0); + if (scalar($self->{'handles'}->@*) <= 0); $self->{'level'} = (defined($args{'level'})) ? $args{'level'} : WARNING; @@ -484,10 +496,8 @@ sub print { my ($self) = shift; - foreach my $fh (@{$self->{'handles'}}) - { - $fh->print(POSIX::strftime("[%Y.%m.%d-%H:%M:%S] ", localtime(time)), @_); - } + $_->print(POSIX::strftime("[%Y.%m.%d-%H:%M:%S] ", localtime(time)), @_) + foreach ($self->{'handles'}->@*); return 1; } @@ -507,10 +517,8 @@ sub close { my ($self) = shift; - foreach my $fh (@{$self->{'handles'}}) - { - $fh->close(); - } + $_->close() + foreach ($self->{'handles'}->@*); } #-------------------------------------------------------------------------------