my $me= $0 or "vacation"; my $true= (1==1);
my $false= (1==0);
my $forwardFile= ".forward"; my $dataFilePrefix= ".vacation"; my $messageFileSuffix= ".msg"; my $logFileSuffix= ".log"; my $oldSuffix= ".old"; my $editor= $ENV{'VISUAL'} || $ENV{'EDITOR'} || 'vi';
my $pager= $ENV{'PAGER'} || 'more';
my $directoryDelimiter= "/"; my $myTag= "# automatically created by the Vacation program";
my $versionString= "vacation 1.1.9\n"
. "<http://www.ayradyss.org/programs/current.html#vacation>";
use strict;
use Getopt::Long; use Sys::Syslog qw(:DEFAULT setlogsock);
my $interactive= $false; my $quiet= $false; my $ignoreRecipient= $false; my $ignoreFile= undef; my $only= $false; my @aliases= (); my $silenceInterval= undef; my $senderAlias= undef; my $fromName= undef; my $senderMailer= undef; my $spamTag= undef; my $log= $false; my $debug= $false; my $pretend= $false; my $syslogSocketUnix= $false; my $version= $false; my $help= $false;
if (@ARGV)
{ GetOptions("i|I|interactive" => \$interactive,
"q|quiet" => \$quiet,
"j" => \$ignoreRecipient,
"f|file|ignore=s" => \$ignoreFile,
"a|alias=s" => \@aliases,
"o|only" => \$only,
"t|time=s" =>\$silenceInterval,
"s|sender=s" => \$senderAlias,
"n|name=s" => \$fromName,
"m|mailer=s" => \$senderMailer,
"spam=s" => \$spamTag,
"l|log" => \$log,
"d|debug" => \$debug,
"p|pretend" => \$pretend,
"u|unix" => \$syslogSocketUnix,
"v|V|version" => \$version,
"h|help" => \$help
);
$interactive= $true if $quiet;
}
else
{ $interactive= $true;
$quiet= $false;
}
unless ($interactive) { setlogsock('unix') if $syslogSocketUnix;
openlog('vacation', 'pid', 'mail');
}
if ($debug)
{ if ($interactive)
{
print "Initialized: ready for interactive mode\n";
}
elsif ($help)
{
print "Initialized: ready for help mode.\n";
}
elsif ($version)
{
print "Initialized: ready for version mode.\n";
}
else
{
syslog('debug', "Initialized: entering automatic mode.");
syslog('debug', "Will not send the message due to set pretend flag.")
if $pretend;
}
if (@ARGV and ($interactive or $help or $version))
{ die "\nI do not know what to do with the extraneous arguments:\n",
"\t", join(" ", @ARGV), "\n\n",
"Usage: vacation [interactive mode]\n",
"Usage: vacation -h [terse help mode]\n",
"Usage: vacation -t 3d -a alias1 -a alias2 user [automatic mode]\n";
}
}
if ($interactive)
{ Interact();
}
elsif ($help)
{ Help();
}
elsif ($version)
{ Version();
}
else
{ Log(AutoReply());
}
closelog unless $interactive; exit;
sub Interact
{
my $home= $ENV{'HOME'}; my $user= $ENV{'USER'} || $ENV{'LOGNAME'} || getlogin || (getpwuid($>))[0];
print "\nWithin Interact...\n" if $debug;
print "\tInvoker's name= $user\n",
"\tEditor= $editor\n",
"\tPager= $pager\n",
"\tHome= $home\n",
"\tDefault message:\n",
DefaultMessage(),
"\n\n"
if $debug;
system("clear") unless $debug;
print "\tWelcome to Vacation Interactive Setup\n",
"\n",
"Please answer the questions and follow the prompts to configure\n",
"automatic mail responses to your correspondents.\n" unless $quiet;
unless ($home)
{ chdir or die "Could not cd to $user\'s home directory: $!\n";
chop($home= `pwd`);
print "Changed to $user\'s home directory <$home>\n" if $debug;
}
$home.= $directoryDelimiter unless $home=~ /$directoryDelimiter$/;
unless ((-T $home.$forwardFile) and Disable($home))
{
SetMessage($home);
SetForwarding($home, $user);
}
}
sub Help
{
print "\nWithin Help...\n" if $debug;
system("perldoc $me")
and die "Problems accessing perldoc... Try the following line at the prompt:\n",
"perldoc $me";
}
sub Version
{
print "\nWithin Version...\n" if $debug;
print "\n$versionString\n\n";
}
sub AutoReply
{
my $header= undef; my $from= undef; my $subject= undef; my $replyTo= undef; my $to= undef; my $cc= undef; my $sender= undef; my $when= undef; my $username= pop(@ARGV); my $replyResult= undef; my $value= undef; my $interval= 0; my @ignoreSenders= ();
die "I cannot proceed in automatic mode without a user's name!"
unless $username;
my %scale= ( 's', 1,
'm', 60,
'h', 60 * 60,
'd', 24 * 60 * 60,
'w', 7 * 24 * 60 * 60,
);
$silenceInterval= "1w" unless $silenceInterval;
while ($silenceInterval=~ s/^(\d+)([smhdw])?//)
{ $value= $1;
$value*= $scale{$2} if $2; $interval+= $value;
}
$silenceInterval= $interval;
@ignoreSenders= ( 'daemon',
'postmaster',
'mailer-daemon',
'mailer',
'root',
'-request@',
)
unless $only; push(@ignoreSenders, $username."\@".$senderMailer) if ($senderMailer and !$only);
push(@ignoreSenders, $username."\@localhost") unless $only;
push(@ignoreSenders, ReadIgnoredSenders())
if ($ignoreFile);
push(@aliases, $username);
$fromName= "via the Vacation auto-responder" unless $fromName;
if ($debug)
{ syslog('debug', "Within AutoReply");
syslog('debug', "User's name: $username");
syslog('debug', "Custom name: $fromName");
syslog('debug', "Silence period: $silenceInterval seconds");
syslog('debug', join(" ", "Ignored senders:", @ignoreSenders)) unless $only;
syslog('debug', join(" ", "Included senders:", @ignoreSenders)) if $only;
syslog('debug', join(" ", "Aliases:", @aliases));
syslog('debug', "I will not check for a recipient")
if $ignoreRecipient;
syslog('debug', join(" ", "Ignoring extraneous options:", @ARGV))
if @ARGV;
}
$/= ''; $header= <STDIN>; $header=~ s/\n\s+/ /g; while(<STDIN>) {};
return ("Precedence: $1", $header)
if $header=~ /^Precedence:[ \t]*(bulk|junk|list)/im;
return ("Suspected spam: X-Spam-Flag")
if $header=~ /^X-Spam-Flag: YES$/im;
($from)= ($header=~ /^From:[ \t]*(.*)/m);
$from=~ s/".*"//; $from= $1 if $from=~ /(\S+\@\S+)/;
while ($from=~ /<(.+)>/)
{ $from= $1;
}
syslog('debug', "From: <$from>") if $debug;
unless ($from)
{ syslog('err', "Could not find a \"From:\" line");
return ("No \"From:\" line", $header);
}
($replyTo)= ($header=~ /^Reply-To:[ \t]*(.*)/m);
syslog('debug', "Reply-To: <$replyTo>") if $debug;
($to)= ($header=~ /^To:[ \t]*(.*)/m);
syslog('debug', "To: <$to>") if $debug;
($cc)= ($header=~ /^Cc:[ \t]*(.*)/m);
syslog('debug', "Cc: <$cc>") if $debug;
($subject)= ($header=~ /Subject:[ \t]*(.*)/m);
syslog('debug', "Subject: <$subject>") if $debug;
return ("Suspected spam: $subject") if ($spamTag and $subject=~ /$spamTag/i);
$subject= "(No subject)" unless $subject;
$to.= ", " . $cc if $cc; unless ($to)
{ if ($ignoreRecipient)
{ syslog('debug', "Could not find any recipients, but we don't care")
if $debug;
}
else
{ syslog('err', "Could not find any recipients");
return ("Could not find any recipients", $header);
}
}
if ($only)
{ my $matched= $false; foreach $sender (@ignoreSenders)
{
$matched= $true, last if ($from=~ /$sender/i);
}
return ("Did not match any of the explicitly listed senders", $header) unless $matched;
}
else
{ foreach $sender (@ignoreSenders)
{
if ($sender=~ s/\@$//i)
{ if ($from=~ /$sender\@/i)
{
return ("Matched ignored sender: $sender\@", $header);
}
}
else
{
if ($from=~ /\b$sender\b/i)
{
return ("Matched ignored sender: $sender", $header);
}
}
}
}
unless ($ignoreRecipient)
{
my $matched= $false;
foreach my $alias (@aliases)
{ if (($to=~ /$alias$/i) or ($to=~ /$alias[\s\@\"\'\+>,]/i))
{ syslog('debug', "Matched a recipient: $alias") if $debug;
$matched= $true;
last;
}
}
return ("You are not the specific recipient", $header) unless $matched;
}
$replyTo= $from unless $replyTo;
if ($when= RecentSender($replyTo))
{ return ("We've already notified <$from> $when", $header);
}
else
{ $replyResult= SendReply($replyTo, $subject, $username, $fromName);
}
return ($replyResult, $header);
}
sub YesOrNo
{
my $question= shift; my $answer;
while ($true)
{ print "\n$question ";
$answer= <STDIN>;
last if $answer=~ /^[yn]/i;
print "Please answer \"yes\" or \"no\" ('y' or 'n')\n";
}
print "\n";
return ($answer=~ /^y/i); }
sub Disable
{
my $home= shift; my %senders; my @keys; my $key; my $when; my $mine; my $readDelimiter; my $forward;
if ($quiet)
{ rename($home.$forwardFile, $home.$forwardFile.$oldSuffix)
or die "Failed to preserve <$home$forwardFile> " .
"as <$home$forwardFile$oldSuffix>: $!\n";
return $false; }
print "\n--\n\n",
"You have a \"$forwardFile\" file in your home directory containing:\n\n";
if(open(Forward, $home.$forwardFile))
{ $readDelimiter= $/;
undef $/; $forward= <Forward>;
close(Forward);
$/= $readDelimiter;
print $forward, "\n\n"; $mine= $forward=~ /$myTag/m; }
else
{ print "Could not open <$home$forwardFile>: $!\n";
$mine= $false; }
if ($mine)
{
if (YesOrNo("Would you like to disable the vacation feature?"))
{ unlink($home.$forwardFile)
or die "Failed to remove <$home$forwardFile>: $!\n";
use DB_File; use Fcntl; if (tie(%senders, 'DB_File', $home.$dataFilePrefix,
O_RDONLY, 0600, $DB_HASH))
{
if (@keys= sort { $senders{$a} <=> $senders{$b}; } keys %senders)
{
require 'ctime.pl';
open(Pager, "| $pager")
or die "Could not open a pipe to $pager: $!\n";
print Pager "While you were away, I responded to:\n\n";
foreach $key (@keys)
{
($when)= unpack("L", $senders{$key});
printf Pager "%40s on %s", $key, ctime($when);
}
print Pager "\n\n";
close(Pager);
untie(%senders);
}
}
else
{
print "Could not open the past senders database: $!\n\n" if $debug;
}
print "Back to normal mail reception.\n\n";
return $true; }
else
{
print "Ok, the vacation feature remains enabled.\n";
}
}
else
{ print "\n",
"If you continue, I will replace this \"$forwardFile\" file\n",
"with my own settings. I will keep a copy of the original\n",
"as \"$forwardFile$oldSuffix\".\n";
unless (YesOrNo("Continue and replace the \"$forwardFile\" file?"))
{
print "Ok, I will leave it alone.\n\n";
return $true; }
else
{ rename($home.$forwardFile, $home.$forwardFile.$oldSuffix)
or die "Failed to preserve <$home$forwardFile> " .
"as <$home$forwardFile$oldSuffix>: $!\n";
}
}
return $false; }
sub SetMessage
{
my $home= shift; my $see; my $edit;
if (-T $home.$dataFilePrefix.$messageFileSuffix)
{ if ($quiet)
{ $edit= $true;
}
else
{
print "\n--\n\n",
"You have a message file in ",
"$home$dataFilePrefix$messageFileSuffix\n";
$see= YesOrNo("Would you like to see it?");
if ($see)
{
print "\n";
print "\tInvoking pager:\n",
"\t\t$pager $home$dataFilePrefix$messageFileSuffix\n"
if $debug;
if (system($pager, $home.$dataFilePrefix.$messageFileSuffix))
{
print
"\tFailed to hand off <$home$dataFilePrefix$messageFileSuffix>\n",
"\tto a pager ($pager): $!\n" if $debug;
}
print "\n--\n\n";
}
$edit= YesOrNo("Would you like to edit it?");
}
}
else
{
MakeDefaultMessage($home.$dataFilePrefix.$messageFileSuffix);
unless ($quiet)
{ print "\n--\n\n",
"I've created a default vacation message in ",
"$home$dataFilePrefix$messageFileSuffix\n",
"While you are away, I will return this message to anyone who sends\n",
"you mail.\n",
"\n",
"Press 'return' or 'enter' when you are ready to continue, and you\n",
"will enter your favorite editor ($editor) to adjust the message\n",
"to your own tastes.\n\n";
$|= 1; print "Press 'return' or 'enter' to continue... ";
<STDIN>;
}
$edit= $true;
}
if ($edit)
{ print "\tInvoking editor:\n",
"\t\t$editor $home$dataFilePrefix$messageFileSuffix\n"
if $debug;
if (system($editor, $home.$dataFilePrefix.$messageFileSuffix))
{
print "\tFailed to hand off <$home$dataFilePrefix$messageFileSuffix>\n",
"\tto an editor ($editor): $!\n" if $debug;
}
}
}
sub MakeDefaultMessage
{
my $messageFile= shift;
open(Message, ">$messageFile")
or die "Could not create <$messageFile>: $!\n";
print Message DefaultMessage();
close(Message);
}
sub DefaultMessage
{
return "Precedence: junk\n" .
"X-No-Archive: yes\n" .
"Subject: Away for a while\n" .
"\n" .
"Hello,\n" .
"\n" .
"I am gone for a while and will read your message concerning\n" .
"\"\$SUBJECT\" upon my return.\n";
}
sub SetForwarding
{
my $home= shift; my $user= shift; my $vacation= $0;
if ((-T $home.$forwardFile) and !$quiet)
{ print "\n--\n\n",
"Keeping previous \"$forwardFile\" file.\n\n",
"The vacation feature remains enabled.\n\n",
"Please remember to turn it off when you return.\n",
"You may accomplish this by running this program again.\n",
"\n",
"Enjoy!\n\n";
return; }
print "\tMy path= $vacation\n" if $debug;
unless ($vacation=~ m/^\//)
{
$vacation= '/usr/local/bin/vacation';
print "\tChanged my path to <$vacation>\n" if $debug;
}
print "\n--\n\n",
"I will create a \"$forwardFile\" file in your home directory\n",
"to enable the vacation feature.\n" unless $quiet;
if ($quiet or YesOrNo("Would you like to enable the vacation feature now?"))
{ my $alias; my $arguments= "";
$arguments.= " -f $ignoreFile" if $ignoreFile;
$arguments.= " -j $ignoreRecipient" if $ignoreRecipient;
$arguments.= " -t $silenceInterval" if $silenceInterval;
$arguments.= " -s $senderAlias" if $senderAlias;
$arguments.= " -m $senderMailer" if $senderMailer;
$arguments.= " -n '$fromName\'" if $fromName;
$arguments.= " -o" if $only;
$arguments.= " -l" if $log;
$arguments.= " -d" if $debug;
$arguments.= " -u" if $syslogSocketUnix;
if ($spamTag)
{ $spamTag=~ s/\\/\\\\/g; $arguments.= " --spam \\\"$spamTag\\\"";
}
foreach $alias (@aliases)
{
$arguments.= " -a $alias";
}
ClearDatabase($home.$dataFilePrefix);
open(Forward, ">$home$forwardFile")
or die "Could not create <$home$forwardFile>: $!\n";
chmod(0644, $home.$forwardFile) or die "Could not change permissions for <$home$forwardFile>: $!\n";
print Forward "\\$user, \"|$vacation$arguments $user\"\n";
print Forward "$myTag\n";
close(Forward);
MakeDefaultMessage($home.$dataFilePrefix.$messageFileSuffix)
unless -T $home.$dataFilePrefix.$messageFileSuffix;
print "\n--\n\n",
"Ok, I enabled the vacation feature for you.\n\n",
"Please remember to turn it off when you return.\n",
"You may accomplish this by running this program again.\n",
"\n",
"Enjoy!\n\n" unless $quiet;
}
else
{ print "Ok, the vacation feature remains disabled.\n\n";
}
}
sub ClearDatabase
{
my $databasePath= shift; my %database; use DB_File; use Fcntl;
print "\nWithin ClearDatabase...\n" if $debug;
if (tie(%database, 'DB_File', $databasePath, O_RDWR|O_CREAT, 0600, $DB_HASH))
{
undef(%database); untie(%database);
}
else
{ print "Could not clear the database <$databasePath>: $!\n"
if $debug;
}
}
sub ReadIgnoredSenders
{
my @ignoreSenders= ();
syslog('debug', "Within ReadIgnoredSenders") if $debug;
if (-e $ignoreFile)
{ if ((-T $ignoreFile) and (-r $ignoreFile))
{ if (open(Ignore, $ignoreFile))
{ while(<Ignore>)
{ push(@ignoreSenders, split());
}
close(Ignore);
}
else
{ syslog('err', "Failed to open <$ignoreFile>: %m");
}
}
else
{ syslog('debug', "Cannot read <$ignoreFile>") if $debug;
}
}
else
{ syslog('debug', "<$ignoreFile> does not exist") if $debug;
}
return @ignoreSenders;
}
sub RecentSender
{
my $sender= shift; my %senders; my $lastTime; my $ago;
my $now= time; my $home= $ENV{'HOME'}; use DB_File; use Fcntl;
chop($home= `pwd`) unless $home;
$home.= $directoryDelimiter unless $home=~ /$directoryDelimiter$/;
if (tie(%senders, 'DB_File', $home.$dataFilePrefix,
O_RDONLY, 0600, $DB_HASH))
{ if ($lastTime= $senders{$sender})
{
($lastTime)= unpack('L', $lastTime); if ($lastTime)
{ syslog('debug', "now= $now, last= $lastTime, " .
"timeout= $silenceInterval") if $debug;
if (($ago= $now - $lastTime) < $silenceInterval)
{ untie(%senders);
syslog('debug', "ago= $ago") if $debug;
return ("$ago seconds ago") if ($ago > 1);
return ("a second ago");
}
}
}
untie(%senders);
syslog('debug', "No time stamp available for $sender") if $debug;
return $false; }
else
{ syslog('debug', "Could not check the previous senders database " .
"<$home$dataFilePrefix>: %m") if $debug;
return $false;
}
}
sub SendReply
{
use Net::SMTP; my $to= shift; my $subject= shift; my $from= shift; my $fromName= shift; my $response; my $readDelimiter; my $mailer; my %senders; my $now= time; my $home= $ENV{'HOME'};
chop($home= `pwd`) unless $home;
$home.= $directoryDelimiter unless $home=~ /$directoryDelimiter$/;
if (open(Response, $home.$dataFilePrefix.$messageFileSuffix))
{
$readDelimiter= $/;
undef $/; $response= <Response>;
close(Response);
$/= $readDelimiter; }
else
{ syslog('debug', "Could not open the preset response file " .
"<$home$dataFilePrefix$messageFileSuffix>: %m") if $debug;
$response= DefaultMessage(); }
$response=~ s/\$SUBJECT/$subject/g; $response=~ s/\$SENDER/$to/g; $response=~ s/\$RECIPIENT/$from/g;
unless ($mailer= new Net::SMTP($senderMailer))
{ my $error= $@;
if ($senderMailer)
{ syslog('err', "Could not create a custom mailer through $senderMailer: $error");
return ("Could not create a custom mailer through $senderMailer ($error)");
}
else
{ if ($debug)
{
syslog('debug', "Could not create a mailer ($error); did you configure libnet?");
syslog('debug', "Will try \'localhost\' as a work-around...");
}
unless ($mailer= new Net::SMTP("localhost"))
{ my $error= $@;
syslog('err', "Could not create a local mailer: $error");
return ("Could not create a local mailer ($error)");
}
}
}
if ($senderAlias)
{ $from= $senderAlias;
syslog('debug', "Using alternate sender address <$from>") if $debug;
}
unless ($from=~ /\@/)
{ $from.= "\@" . $mailer->domain();
syslog('debug', "Fully qualified user's email address as <$from>")
if $debug;
}
if ($pretend)
{ syslog('debug', "Dumping message to STDOUT")
if $debug;
print("To: $to\n" .
"From: $from ($fromName)\n" .
"Date: " . MailDate() . "\n" .
$response);
}
else
{ if ($mailer->mail($from) and $mailer->to($to))
{ $mailer->data();
$mailer->datasend("To: $to\n" .
"From: $from ($fromName)\n" .
"Date: " . MailDate() . "\n" .
$response);
$mailer->dataend();
$mailer->quit();
use DB_File; use Fcntl; if (tie(%senders, 'DB_File', $home.$dataFilePrefix,
O_WRONLY|O_CREAT, 0600, $DB_HASH))
{ $senders{$to}= pack('L', $now);
untie(%senders);
}
else
{ syslog('debug', "Could not update the previous senders database " .
"<$home$dataFilePrefix>: %m") if $debug;
}
}
else
{ my $message= $mailer->message();
syslog('err', "Could not send the response to $to from $from: $message");
$mailer->quit;
return ("Could not send response to $to ($message)");
}
}
return ("Attempted to notify <$to>");
}
sub MailDate
{
use Time::Local;
syslog('debug', "MailDate: constructing the date header") if $debug;
my $time= time(); my @months= qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
my @weekDays= qw(Sun Mon Tue Wed Thu Fri Sat);
my ($second, $minute, $hour, $day, $month, $year, $weekDay)=
localtime($time);
my $offset= (timegm(localtime($time)) - $time) / 3600;
my $timezone= sprintf("%+03d", int($offset)) .
sprintf "%02d", abs($offset - int($offset)) * 60;
return join(" ", $weekDays[$weekDay] . ",",
$day, $months[$month], $year+1900,
sprintf("%02d:%02d:%02d", $hour, $minute, $second), $timezone
);
}
sub Log
{
return unless $log;
my $decision= shift; my $header= shift; my $home= $ENV{'HOME'};
chop($home= `pwd`) unless $home;
$home.= $directoryDelimiter unless $home=~ /$directoryDelimiter$/;
if (open(Log, ">>$home$dataFilePrefix$logFileSuffix"))
{
print Log "--\n",
$decision,
"\n--\n",
$header,
"\n--\n";
close(Log);
}
else
{ syslog('debug', "Cannot append to " .
"<$home$dataFilePrefix$logFileSuffix>") if $debug;
}
}
__END__