Download | Plain Text | No Line Numbers
- #!/usr/bin/perl
- #
- # Copyright (C) 2008-2016 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
- # as published by the Free Software Foundation; either
- # version 2 of the License, or (at your option) any later
- # version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software Foundation,
- # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-
- use Getopt::Long;
- use Pod::Usage;
- use Mail::Internet;
- use Mail::Address;
- use MIME::Lite;
- use DBI;
- use strict;
- use warnings;
- use utf8;
-
- my $VERSION = '0.7';
-
- #-------------------------------------------------------------------------------
- # DATABASE TABLE LAYOUT
- #-------------------------------------------------------------------------------
-
- # this table is required for running this software and is used for detecing
- # loops. create this table inside the confixx database.
- #
- # CREATE TABLE `autoresponder_seen` (
- # `ident` int(11) NOT NULL,
- # `sender` varchar(255) NOT NULL,
- # `time` datetime NOT NULL,
- # `server_id` varchar(32) NOT NULL,
- # KEY `ident` (`ident`),
- # KEY `server_id` (`server_id`)
- # );
- #
- # ALTER TABLE `autoresponder` CHANGE `emailtext` `emailtext` TEXT NOT NULL;
-
- #-------------------------------------------------------------------------------
- # SENDMAIL INFORMATION
- #-------------------------------------------------------------------------------
-
- # if you're using sendmail be sure to modify sendmail.cf so it will pass the
- # header "Return-Path" to the executable. this can usually be done by adding
- # the char "P" to the field F in line "prog".
- # e.g. from:
- # Mprog, P=/usr/sbin/smrsh, F=lsDFMoqeu9, S=EnvFromL/HdrFromL, ....
- # to:
- # Mprog, P=/usr/sbin/smrsh, F=lsDFMoqeu9P, S=EnvFromL/HdrFromL, ....
-
- #-------------------------------------------------------------------------------
- # START OF CONFIGURATION
- #-------------------------------------------------------------------------------
-
- # confixx database configuration
- # grep from confixx_main.conf
- my %db = (
- dsn => 'INSERT VALUE OF $db_address FROM confixx_main.conf',
- user => 'INSERT VALUE OF $dbUser FROM confixx_main.conf',
- pass => 'INSERT VALUE OF $dbPw FROM confixx_main.conf'
- );
-
- # confixx installationid/serverid
- my $cfx_serverid = 'INSERT VALUE OF $ServerID FROM confixx_main.conf';
-
- # path to mailer for sending the reply
- my $mailer = 'INSERT VALUE OF $bin_sendmail FROM confixx_main.conf';
-
- # used to specify the period in which addresses are kept and are not responded
- # to, specified in seconds. should be at least set to 1 day (=86400).
- my $respond_period = 86400;
-
- # 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');
-
- # 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.
- # useful for not keeping "error-generating" mails in the mailserver queue.
- #$SIG{'__DIE__'} = sub { our $log; (defined($log) && $log->can('error')) ? $log->error("Error: ", @_) : print STDERR "Error: ", @_; exit(0); };
-
- # default debugging options. may be useful to watch for errors without
- # passing commandline options. see --help for description
- my $debuglvl = 0;
- #my $debuglvl = 3;
- #my $logfile = "/tmp/autoresponder.log";
-
- #-------------------------------------------------------------------------------
- # END OF CONFIGURATION
- #-------------------------------------------------------------------------------
-
- my %args = ();
- my $getopt = new Getopt::Long::Parser();
- $getopt->configure('prefix_pattern=--|-');
- $getopt->getoptions(
- \%args,
- 'i|ident=i',
- 'd|debug=i',
- 'h|help',
- 'l|log=s',
- 'v|version',
- ) or pod2usage(2);
-
- pod2usage(1)
- if $args{'h'};
-
- if ($args{'v'})
- {
- }
-
- # create logger
- $args{'d'} = $args{'d'} || $debuglvl || 0;
- our $log = new Logger(($args{'l'}) ? (file => $args{'l'}) : (handle => \*STDERR), level => $args{'d'})
-
- # fetch ident
- my $ident = $args{'i'} || $ARGV[0];
- pod2usage(-msg => "Error: No ident given.")
- $log->debug("Got ident value: ", $ident, $/);
-
- # parse mail
- $log->debug("Parsing mail...", $/);
- my $mail = Mail::Internet->new(\*STDIN)
-
- # parse headers
- $log->debug("Parsing headers...", $/);
- my $hdata = check_headers($mail->head(), \%myheader);
-
- # connect to database
- $log->debug("Connecting to database...", $/);
- $dbh->{'mysql_enable_utf8'} = 1;
-
- # remove old entries in database
- $log->debug("Removing outdated entries from database...", $/);
- $dbh->do(
- q(DELETE FROM `autoresponder_seen` WHERE `time` < UTC_TIMESTAMP() - INTERVAL ? SECOND AND `server_id`=?),
-
- # check database for loops
- $log->debug("Checking if sender is already known...", $/);
- my @cnt = $dbh->selectrow_array(
- {
- $log->info("Loop detected.", $/);
- }
-
- # fetch reply data from database
- $log->debug("Fetching autoresponder data...", $/);
- my $ardata = $dbh->selectrow_hashref(
- NOW() between `valid_from` and `valid_till` as `active`
- FROM `autoresponder` WHERE `ident`=? AND `server_id`=?),
- );
- {
- $log->warning("Unable to fetch autoresponder data from database. Maybe wrong ident.", $/);
- }
-
- if (!$ardata->{'active'})
- {
- $log->info("Autoresponder is not active.", $/);
- }
-
- $ardata->{'emailtext'} =~ s/\r//g;
-
- # create reply
- my $reply = generate_reply($ardata, $hdata, \%myheader);
-
- # send mail via mailer executable
- $log->debug("Sending reply...", $/);
- my @args = ($mailer, '-ti', '-f', $reply->{'from'}->address());
-
- # insert sender into database
- $log->debug("Inserting sender into database...", $/);
- $dbh->do(
- q(INSERT INTO `autoresponder_seen` SET `ident`=?, `sender`=?, `time`=UTC_TIMESTAMP(), `server_id`=?),
-
- # cleanup
- $log->info("Reply sent successfully.", $/);
- $dbh->disconnect();
-
- #-------------------------------------------------------------------------------
-
- sub check_headers
- {
- my ($headers, $myheader) = @_;
- my (@sender, @addrs) = ();
- my $subject = "";
- our $log;
-
- {
-
- # qmail delivers return-path via environment
- $headers->replace($hname, $ENV{'RPLINE'})
-
- # debug stuff
- $log->debug("Headers of incoming mail:", $/, $mail->head()->as_string());
-
- # check Return-Path
- $hname = 'Return-Path';
- $headers->unfold($hname);
- if ($hvalue = $headers->get($hname))
- {
- if ($hvalue eq '<>' || $hvalue eq "#@[]" || $hvalue =~ /MAILER-DAEMON$/i);
- if (!(@addrs = Mail::Address->parse($hvalue)));
- }
-
- # check From
- $hname = 'From';
- $headers->unfold($hname);
- if (!($hvalue = $headers->get($hname)));
- if ($hvalue =~ /MAILER-DAEMON$/i || $hvalue =~ /Mail Delivery (?:Sub)?(?:system|service)/i);
- if (!(@addrs = Mail::Address->parse($hvalue)));
-
- # check Precedence
- $hname = 'Precedence';
- $headers->unfold($hname);
- if ($hvalue = $headers->get($hname))
- {
- if ($hvalue =~ /(?:junk|list|bulk|autoreply)/i);
- }
-
- # check X-Auto-Response-Suppress
- $hname = 'X-Auto-Response-Suppress';
- $headers->unfold($hname);
- if ($hvalue = $headers->get($hname))
- {
- if ($hvalue =~ /(?:all|oof|autoreply)/i);
- }
-
- # check X-Spam-Status
- $hname = 'X-Spam-Status';
- $headers->unfold($hname);
- if ($hvalue = $headers->get($hname))
- {
- if ($hvalue =~ /yes/i);
- }
-
- # check for mailing list
- foreach ('List-Id', 'List-Help', 'List-Subscribe', 'List-Unsubscribe',
- 'List-Post', 'List-Owner', 'List-Archive', 'Mailing-List',
- 'X-Mailing-List', 'X-ML-Name', 'X-List')
- {
- $hname = $_; # explicit assignment needed
- }
-
- # check Subject
- $hname = 'Subject';
- #$headers->unfold($hname);
- if ($hvalue = $headers->get($hname))
- {
- if ($hvalue =~ /^Delivery Status Notification \(Failure\)$/i || $hvalue =~ /Automated reply/i);
- $subject = $hvalue;
- }
-
- # check my own headers
- {
- $hname = $_; # explicit assignment needed
- }
-
- # check Reply-To
- $hname = 'Reply-To';
- $headers->unfold($hname);
- if ($hvalue = $headers->get($hname))
- {
- # we'll only respond to the first mail address
- if ($hvalue !~ /MAILER-DAEMON$/i && (@addrs = Mail::Address->parse($hvalue)));
- }
- };
- if ($@)
- {
- my $error = $@;
- $log->info("Invalid header '", $hname, "': ", $error);
- $log->debug("Header value: '", $hvalue, "'.", $/);
- };
-
-
- # fetch some additional headers
- my $references = $headers->get('References') || "";
- my $messageid = $headers->get('Message-ID') || "";
-
- # debug stuff
- $log->debug("Subject: ", $subject, $/);
- $log->debug("References: ", $references, $/);
- $log->debug("Message-ID: ", $messageid, $/);
-
- return {
- sender => $sender,
- subject => $subject,
- references => $references,
- messageid => $messageid
- };
- }
-
- #-------------------------------------------------------------------------------
-
- sub generate_reply
- {
- my ($ardata, $hdata, $myheader) = @_;
-
- # generate from
- my $from = Mail::Address->new($ardata->{'absendername'}, $ardata->{'absenderemail'})
-
- # generate subject
- my $subject = $ardata->{'emailbetreff'};
- $subject = "Auto: " . $hdata->{'subject'}
- $subject = "Automated reply"
-
- my $addr_encode = sub
- {
- };
-
- # create reply
- $log->debug("Generating reply...", $/);
- my $reply = MIME::Lite->new(
- From => $addr_encode->($from),
- To => $addr_encode->($hdata->{'sender'}),
- Subject => encode('MIME-Header', $subject),
- Data => $ardata->{'emailtext'},
- Type => 'text/plain; charset=UTF-8',
-
- # create message-id + threading
- my ($seconds, $microseconds) = gettimeofday();
- $reply->add('Message-ID', $messageid);
- $reply->add('In-Reply-To', $hdata->{'messageid'})
-
- # add some other headers
- {
- $reply->add($_ = $myheader->{$_});
- }
- $reply->add('Precedence', 'bulk');
- $reply->add('Auto-Submitted', 'auto-replied');
- $reply->add('X-Auto-Response-Suppress', 'All');
-
- # debug stuff
- $log->debug("Created reply:", $/, $reply->as_string(), $/);
-
- return {
- reply => $reply,
- from => $from
- };
- }
-
- #-------------------------------------------------------------------------------
-
- package Logger;
- use IO::Handle;
- use IO::File;
- use POSIX ();
- use Carp;
- use strict;
- use warnings;
- use constant ERROR => 0;
- use constant WARNING => 1;
- use constant INFO => 2;
- use constant DEBUG => 3;
-
- $VERSION = "0.1";
-
- sub new
- {
- my $self = {};
- my %args = @_;
-
-
- @{$self->{'handles'}} = ();
- {
- my $fh = new IO::Handle();
- $fh->fdopen($args{'handle'}, 'w')
- }
- {
- my $fh = new IO::File($args{'file'}, "a")
- # this is needed for standard confixx qmail installation
- }
- croak("No logging destination defined")
-
-
- $self;
- }
-
- sub print
- {
- foreach my $fh (@{$self->{'handles'}})
- {
- }
- }
-
- sub printlvl
- {
- if ($self->{'level'} < $level);
- }
-
- sub debug { shift->printlvl(DEBUG, @_); }
- sub info { shift->printlvl(INFO, @_); }
- sub warning { shift->printlvl(WARNING, @_); }
- sub error { shift->printlvl(ERROR, @_); }
-
- sub close
- {
- foreach my $fh (@{$self->{'handles'}})
- {
- }
- }
-
- #-------------------------------------------------------------------------------
-
- __END__
-
- =head1 NAME
-
- Autoresponder - Enhanced autoresponder for Parallels Confixx 3.0.0 and above
-
- =head1 SYNOPSIS
-
- B<autoresponder.pl>
- I<E<lt>identE<gt>>
- [ B<-l> I<logfile> ]
- [ B<-d> I<debuglevel> ]
- [ B<-v> ]
-
- =head1 OPTIONS
-
- =over 4
-
- =item I<E<lt>identE<gt>>
-
- =item B<-i> I<number>, B<--ident=>I<number>
-
- The autoresponders ident number.
-
- =item B<-l> I<logfile>, B<--log=>I<logfile>
-
- Path to logfile. Default is stderr
-
- =item B<-d> I<debuglevel>, B<--debug=>I<debuglevel>
-
- Specify debug level. Default is 0=off
-
- =item B<-v>, B<--version>
-
- Print version number and exit.
-
- =item B<-h>, B<--help>
-
- Print this usage message.
-
- =back
-
- =head1 USAGE
-
- in F<aliases>:
-
- confixx-du-<ident>: "|/usr/bin/autoresponder.pl <ident>"
-
- =head1 AUTHOR
-
- Manuel Mausz E<lt>manuel@mausz.atE<gt>
- L<http://manuel.mausz.at/coding/autoresponder/>
-
- Origin code from Parallels GmbH (L<http://www.parallels.com>)
-
- =cut
-