Si vacation
Aus Si:Wiki von Siegrist SystemLösungen - Informatik und Rezepte
#!/usr/bin/perl # Copyright (c) 2002 by Siegrist(SystemLoesungen) (PSS @ ZweierNet.ch) # http://pss.ZweierNet.ch # # All Rights reserved. # 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. # # 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. # # NAME # si_vacation - Email Vacation Auto-Responder. # # SYNOPSIS # si_vacation -i <FQDA-mailaddr,FQDA-mailaddr,...> # # DESCRIPTION # This is a replacement to the BSD vacation program. # As BSD vacation does only deal with login resp. alias names # si_vacation was made to deal with FQDA Mailaddresses. # This may be usefull in relation to sendmails virtualtable # mechanism. With other words it processes all Mailaddrs supported # by the MTA. # # To enable si_vacation put a line of the following form into # the .forwarw file of the users home directory: # # \username, "|/usr/local/bin/si_vacation [-i <FQDA-mailaddr,FQDA-mailaddr,...>]" # # Options: # With no arguments the hole mailaccount will be handled. # -i <comma seperated list> . processes only the given mailaddresses (aliases). FQDA-mailaddr must be rfc822 conform email address. # # This sends one copy of an incoming message to username while # another copy is piped into si_vacation. # # si_acation will not respond to automatically generated mails # (like mails generated by vacation progs). si_vacation will also not # respond to mails from either postmaster or mailer-daemon. # Further all messages with a precedence:(bulk|list|junk) header field # will not be processed. # # To avoid multiple replies to the same sender the program uses a # database file .vacation.bdb in the users home directory. You can # simply delete this file to reset this mechanism. # The '$span' variable in the program settings defines the interval # in days we send such a reply. # The database file holds an entry of each sender address with the appropriate # timestamp to calculate the interval. # # FILES # ~/.forward Contains e-mail adresses or programs # to which mail must be forwarded. # # ~/.vacation.msg The reply message. The tag "$SUBJECT" in this message # can be used tu substitute the subject of received mail. # # ~/.vacation.bdb Berkeley Database file. # # AUTHOR # Peter Siegrist <PSS at ZweierNet.ch> # # NOTES # This script has been tested with Gentoo Linux. Note that # for other systems it can be necessary to modify some things. # # The script needs the BerkeleyDB Module vom CPAN. # # To be sure the program can be processed out of the .forward file # consult the 'smrsh' manpage. # $VERSION = "0.9"; use strict; use BerkeleyDB; ##--- Program Settings ----------------------------- # path to sendmail my $sendmail = "/usr/sbin/sendmail"; # name of logfile my $logfile = "/var/log/si_vacation.log"; # the vacation message file my $vacation_msg = ".vacation.msg"; # the database file my $vacation_db = ".vacation.bdb"; # file contains all domains handled by the mailsystem my $local_hostnames = "/etc/mail/local-host-names"; # (days): send a vacation message every $span days. 0 = never send more than one message. my $span = 1; ## -------------------------------------------------- $span *= 86400; # convert days to secs sub TRUE { 1; } sub FALSE { 0; } my $fwall = FALSE; # RFC822 check. Regular expression von Jeffrey Friedl's Beispiel in # "Reguleare Ausdruecke" (http://www.ora.com/catalog/regexp/). my $RFC822PAT = <<'EOF'; [\040\t]*(?:\([^\\\x80-\xff\n\015()]*(?:(?:\\[^\x80-\xff]|\([^\\\x80-\ xff\n\015()]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015()]*)*\))[^\\\x80-\xf f\n\015()]*)*\)[\040\t]*)*(?:(?:[^(\040)<>@,;:".\\\[\]\000-\037\x80-\x ff]+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff])|"[^\\\x80-\xff\n\015 "]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015"]*)*")[\040\t]*(?:\([^\\\x80-\ xff\n\015()]*(?:(?:\\[^\x80-\xff]|\([^\\\x80-\xff\n\015()]*(?:\\[^\x80 -\xff][^\\\x80-\xff\n\015()]*)*\))[^\\\x80-\xff\n\015()]*)*\)[\040\t]* )*(?:\.[\040\t]*(?:\([^\\\x80-\xff\n\015()]*(?:(?:\\[^\x80-\xff]|\([^\ \\x80-\xff\n\015()]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015()]*)*\))[^\\\ x80-\xff\n\015()]*)*\)[\040\t]*)*(?:[^(\040)<>@,;:".\\\[\]\000-\037\x8 0-\xff]+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff])|"[^\\\x80-\xff\n \015"]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015"]*)*")[\040\t]*(?:\([^\\\x 80-\xff\n\015()]*(?:(?:\\[^\x80-\xff]|\([^\\\x80-\xff\n\015()]*(?:\\[^ \x80-\xff][^\\\x80-\xff\n\015()]*)*\))[^\\\x80-\xff\n\015()]*)*\)[\040 \t]*)*)*@[\040\t]*(?:\([^\\\x80-\xff\n\015()]*(?:(?:\\[^\x80-\xff]|\([ ^\\\x80-\xff\n\015()]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015()]*)*\))[^\ \\x80-\xff\n\015()]*)*\)[\040\t]*)*(?:[^(\040)<>@,;:".\\\[\]\000-\037\ x80-\xff]+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff])|\[(?:[^\\\x80- \xff\n\015\[\]]|\\[^\x80-\xff])*\])[\040\t]*(?:\([^\\\x80-\xff\n\015() ]*(?:(?:\\[^\x80-\xff]|\([^\\\x80-\xff\n\015()]*(?:\\[^\x80-\xff][^\\\ x80-\xff\n\015()]*)*\))[^\\\x80-\xff\n\015()]*)*\)[\040\t]*)*(?:\.[\04 0\t]*(?:\([^\\\x80-\xff\n\015()]*(?:(?:\\[^\x80-\xff]|\([^\\\x80-\xff\ n\015()]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015()]*)*\))[^\\\x80-\xff\n\ 015()]*)*\)[\040\t]*)*(?:[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+(?! [^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff])|\[(?:[^\\\x80-\xff\n\015\[\ ]]|\\[^\x80-\xff])*\])[\040\t]*(?:\([^\\\x80-\xff\n\015()]*(?:(?:\\[^\ x80-\xff]|\([^\\\x80-\xff\n\015()]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\01 5()]*)*\))[^\\\x80-\xff\n\015()]*)*\)[\040\t]*)*)*|(?:[^(\040)<>@,;:". \\\[\]\000-\037\x80-\xff]+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff] )|"[^\\\x80-\xff\n\015"]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015"]*)*")[^ ()<>@,;:".\\\[\]\x80-\xff\000-\010\012-\037]*(?:(?:\([^\\\x80-\xff\n\0 15()]*(?:(?:\\[^\x80-\xff]|\([^\\\x80-\xff\n\015()]*(?:\\[^\x80-\xff][ ^\\\x80-\xff\n\015()]*)*\))[^\\\x80-\xff\n\015()]*)*\)|"[^\\\x80-\xff\ n\015"]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015"]*)*")[^()<>@,;:".\\\[\]\ x80-\xff\000-\010\012-\037]*)*<[\040\t]*(?:\([^\\\x80-\xff\n\015()]*(? :(?:\\[^\x80-\xff]|\([^\\\x80-\xff\n\015()]*(?:\\[^\x80-\xff][^\\\x80- \xff\n\015()]*)*\))[^\\\x80-\xff\n\015()]*)*\)[\040\t]*)*(?:@[\040\t]* (?:\([^\\\x80-\xff\n\015()]*(?:(?:\\[^\x80-\xff]|\([^\\\x80-\xff\n\015 ()]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015()]*)*\))[^\\\x80-\xff\n\015() ]*)*\)[\040\t]*)*(?:[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+(?![^(\0 40)<>@,;:".\\\[\]\000-\037\x80-\xff])|\[(?:[^\\\x80-\xff\n\015\[\]]|\\ [^\x80-\xff])*\])[\040\t]*(?:\([^\\\x80-\xff\n\015()]*(?:(?:\\[^\x80-\ xff]|\([^\\\x80-\xff\n\015()]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015()]* )*\))[^\\\x80-\xff\n\015()]*)*\)[\040\t]*)*(?:\.[\040\t]*(?:\([^\\\x80 -\xff\n\015()]*(?:(?:\\[^\x80-\xff]|\([^\\\x80-\xff\n\015()]*(?:\\[^\x 80-\xff][^\\\x80-\xff\n\015()]*)*\))[^\\\x80-\xff\n\015()]*)*\)[\040\t ]*)*(?:[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+(?![^(\040)<>@,;:".\\ \[\]\000-\037\x80-\xff])|\[(?:[^\\\x80-\xff\n\015\[\]]|\\[^\x80-\xff]) *\])[\040\t]*(?:\([^\\\x80-\xff\n\015()]*(?:(?:\\[^\x80-\xff]|\([^\\\x 80-\xff\n\015()]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015()]*)*\))[^\\\x80 -\xff\n\015()]*)*\)[\040\t]*)*)*(?:,[\040\t]*(?:\([^\\\x80-\xff\n\015( )]*(?:(?:\\[^\x80-\xff]|\([^\\\x80-\xff\n\015()]*(?:\\[^\x80-\xff][^\\ \x80-\xff\n\015()]*)*\))[^\\\x80-\xff\n\015()]*)*\)[\040\t]*)*@[\040\t ]*(?:\([^\\\x80-\xff\n\015()]*(?:(?:\\[^\x80-\xff]|\([^\\\x80-\xff\n\0 15()]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015()]*)*\))[^\\\x80-\xff\n\015 ()]*)*\)[\040\t]*)*(?:[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+(?![^( \040)<>@,;:".\\\[\]\000-\037\x80-\xff])|\[(?:[^\\\x80-\xff\n\015\[\]]| \\[^\x80-\xff])*\])[\040\t]*(?:\([^\\\x80-\xff\n\015()]*(?:(?:\\[^\x80 -\xff]|\([^\\\x80-\xff\n\015()]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015() ]*)*\))[^\\\x80-\xff\n\015()]*)*\)[\040\t]*)*(?:\.[\040\t]*(?:\([^\\\x 80-\xff\n\015()]*(?:(?:\\[^\x80-\xff]|\([^\\\x80-\xff\n\015()]*(?:\\[^ \x80-\xff][^\\\x80-\xff\n\015()]*)*\))[^\\\x80-\xff\n\015()]*)*\)[\040 \t]*)*(?:[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+(?![^(\040)<>@,;:". \\\[\]\000-\037\x80-\xff])|\[(?:[^\\\x80-\xff\n\015\[\]]|\\[^\x80-\xff ])*\])[\040\t]*(?:\([^\\\x80-\xff\n\015()]*(?:(?:\\[^\x80-\xff]|\([^\\ \x80-\xff\n\015()]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015()]*)*\))[^\\\x 80-\xff\n\015()]*)*\)[\040\t]*)*)*)*:[\040\t]*(?:\([^\\\x80-\xff\n\015 ()]*(?:(?:\\[^\x80-\xff]|\([^\\\x80-\xff\n\015()]*(?:\\[^\x80-\xff][^\ \\x80-\xff\n\015()]*)*\))[^\\\x80-\xff\n\015()]*)*\)[\040\t]*)*)?(?:[^ (\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+(?![^(\040)<>@,;:".\\\[\]\000- \037\x80-\xff])|"[^\\\x80-\xff\n\015"]*(?:\\[^\x80-\xff][^\\\x80-\xff\ n\015"]*)*")[\040\t]*(?:\([^\\\x80-\xff\n\015()]*(?:(?:\\[^\x80-\xff]| \([^\\\x80-\xff\n\015()]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015()]*)*\)) [^\\\x80-\xff\n\015()]*)*\)[\040\t]*)*(?:\.[\040\t]*(?:\([^\\\x80-\xff \n\015()]*(?:(?:\\[^\x80-\xff]|\([^\\\x80-\xff\n\015()]*(?:\\[^\x80-\x ff][^\\\x80-\xff\n\015()]*)*\))[^\\\x80-\xff\n\015()]*)*\)[\040\t]*)*( ?:[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+(?![^(\040)<>@,;:".\\\[\]\ 000-\037\x80-\xff])|"[^\\\x80-\xff\n\015"]*(?:\\[^\x80-\xff][^\\\x80-\ xff\n\015"]*)*")[\040\t]*(?:\([^\\\x80-\xff\n\015()]*(?:(?:\\[^\x80-\x ff]|\([^\\\x80-\xff\n\015()]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015()]*) *\))[^\\\x80-\xff\n\015()]*)*\)[\040\t]*)*)*@[\040\t]*(?:\([^\\\x80-\x ff\n\015()]*(?:(?:\\[^\x80-\xff]|\([^\\\x80-\xff\n\015()]*(?:\\[^\x80- \xff][^\\\x80-\xff\n\015()]*)*\))[^\\\x80-\xff\n\015()]*)*\)[\040\t]*) *(?:[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+(?![^(\040)<>@,;:".\\\[\ ]\000-\037\x80-\xff])|\[(?:[^\\\x80-\xff\n\015\[\]]|\\[^\x80-\xff])*\] )[\040\t]*(?:\([^\\\x80-\xff\n\015()]*(?:(?:\\[^\x80-\xff]|\([^\\\x80- \xff\n\015()]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015()]*)*\))[^\\\x80-\x ff\n\015()]*)*\)[\040\t]*)*(?:\.[\040\t]*(?:\([^\\\x80-\xff\n\015()]*( ?:(?:\\[^\x80-\xff]|\([^\\\x80-\xff\n\015()]*(?:\\[^\x80-\xff][^\\\x80 -\xff\n\015()]*)*\))[^\\\x80-\xff\n\015()]*)*\)[\040\t]*)*(?:[^(\040)< >@,;:".\\\[\]\000-\037\x80-\xff]+(?![^(\040)<>@,;:".\\\[\]\000-\037\x8 0-\xff])|\[(?:[^\\\x80-\xff\n\015\[\]]|\\[^\x80-\xff])*\])[\040\t]*(?: \([^\\\x80-\xff\n\015()]*(?:(?:\\[^\x80-\xff]|\([^\\\x80-\xff\n\015()] *(?:\\[^\x80-\xff][^\\\x80-\xff\n\015()]*)*\))[^\\\x80-\xff\n\015()]*) *\)[\040\t]*)*)*>) EOF #--------------------------------------------------------------------- open(LOG, ">>$logfile"); #my $user = $ENV{"USER"} || $ENV{"LOGNAME"} || getlogin || (getpwuid($>))[0]; my $user = (getpwuid($>))[0]; print LOG "\n$user -- @{[scalar localtime()]} ------------------------------\n"; print LOG "$user -- ARGV: @ARGV\n"; print LOG ">>>>>$ENV{'USER'} || $ENV{'LOGNAME'} || ", getlogin, " || ", (getpwuid($>))[0], "\n"; #-- Options # Usage in .forward: "\mailaccount, |si_vacation -i <mail@addr.tld,mail@addr.tld,...>" # ... or "\mailaccount, |si_vacation" # # With no arguments the hole mailaccount will be handled. # -i <comma seperated list> processes only the given mailaddresses (aliases). mail@addr.tld must be rfc822 conform email address. my %i_args = (); if ( $ARGV[0] eq "-i" ) { shift (@ARGV); map { $i_args{lc($_)} = "1" } split(",", shift(@ARGV)); print LOG "$user -- Handle addrs: ", join(",", keys %i_args), "\n"; } else { $fwall = TRUE; } #-- read the local host names open(LH, "<$local_hostnames") or die "error open $local_hostnames: $!\n"; my @lhn = <LH>; close LH; # # Process the message my ($from, $to, $cc, $subject, $messageid, $lastheader, $email, $delivery_to, $header); $subject=""; while (<>) { last if (/^$/); $header .= $_; if (/^\s+(.*)/ and $lastheader) { $$lastheader .= " $1"; } elsif (/^from:\s+(.*)\n$/i) { $from = $1; $lastheader = \$from; } elsif (/^to:\s+(.*)\n$/i) { $to = $1; $lastheader = \$to; } elsif (/^cc:\s+(.*)\n$/i) { $cc = $1; $lastheader = \$cc; } elsif (/^subject:\s+(.*)\n$/i) { $subject = $1; $lastheader = \$subject; } elsif (/^message-id:\s+(.*)\n$/i) { $messageid = $1; $lastheader = \$messageid; } elsif (/^x-spam-(flag|status):\s+yes/i) { exit; } elsif (/^precedence:\s+(bulk|list|junk)/i) { exit; } elsif (/^Auto-Submitted: auto\-.+/i) { exit; } else {$lastheader = "" ; } } close ARGV; if (!$from || !$messageid) { exit; } exit if $from =~ /mailer-daemon/i; exit if $from =~ /postmaster/i; #print LOG "HEADER:\n$header\n"; $RFC822PAT =~ s/\n//g; if ( $from !~ /^${RFC822PAT}$/o ) { print LOG "$user -- Exit! Address check failed (not rfc822 conform): $from\n"; exit; } my ($db_from) = ( $from =~ /([\w\-.%]+\@[\w.-]+)/ ); chomp($db_from); chomp($from); print LOG "$user -- MAIL-TO: $to\n"; # exit if no need to send a mail if ( check_vdb($db_from) == FALSE ) { print LOG "$user -- Already sent a vacation message.\n"; exit; } # check for allowed domains my ($dom) = ( $db_from =~ /.+\@(.+)$/ ); if ( !grep /^$dom$/i, @lhn ) { #print LOG "$user -- Exit! Unallowed domainname ($dom). Won't reply.\n"; #exit; } # senseless mailaddr ? ($to) = ( $to =~ /([\w\-.%]+\@[\w.-]+)/ ); exit if $to eq ""; chomp($to); lc $to; #-- exit if not in argument list if ( ! $fwall ) { if ( ! exists $i_args{$to} ) { print LOG "$user -- Not handled address.($to / $i_args{$to}\n"; exit; } } my $msg = ""; if (open(MSG,"$vacation_msg")) { undef $/; $msg = <MSG>; close MSG; } else { print LOG "$user -- Can't open $vacation_msg: $!\n"; exit; } # Replace $SUBJECT with real subject text. $msg =~ s/\$SUBJECT/$subject/gm; print LOG "$user -- REPLAY-TO: $from\n"; # Send reply using sendmail. if ( open(MAIL, "|$sendmail -t -f $to") ) { print MAIL <<EOF_MAIL_HEADER; To: $from Auto-Submitted: auto-replied Precedence: junk EOF_MAIL_HEADER print MAIL $msg; close MAIL; upd_vdb($db_from); } else { print LOG "$user -- si_vacation: Can't run sendmail\n"; exit; } close LOG; exit; #-- check_vdb() # returns false if mailaddr is found and timestamp is in span (don't send a mail) # returns true if mailaddr is not found or timestamp is beyond span (send a mail) #-- sub check_vdb { my $mailaddr = shift; my $ts_span; my $db = BerkeleyDB::Hash->new( -Filename => "$vacation_db", -Flags => DB_CREATE ); if ( ! $db ) { print LOG "$user -- Cannot open vacation DB: $!"; exit FALSE; } my $lt = localtime(); if ( $db->db_get( "$mailaddr", $ts_span ) == 0 ) { return FALSE if $span == 0; # infinite span. send only one message. return TRUE if $lt - $ts_span > $span; return FALSE; } else { return TRUE; } $db->db_close(); } #-- upd_vdb() # update/write vacation db with mailaddr and timestamp #-- sub upd_vdb { my $mailaddr = shift; my $db = BerkeleyDB::Hash->new( -Filename => "$vacation_db", -Flags => DB_CREATE ); if ( ! $db ) { print LOG "$user -- Cannot open vacation DB: $!"; exit FALSE; } my $lt = localtime(); $db->db_put( "$mailaddr", $lt ); $db->db_close(); }