#!/bin/sh
#The first lines added here prevent users from executing this program
#without reading the docs :-) Delete them after being sure you want
#to give it a try.
echo "Please read the documentation before executing $0";
echo;
echo "Beware that this program is destructive. Execute at your own risk!";
exit 0;
#THIS IS THE LAST LINE TO BE DELETED TO MAKE THE PROGRAM WORK
#!/usr/bin/perl 

# * WARNING *  This program can be very destructive!
# Beware that I accept no responsability about this program.
# Run it at your risk!

# This program checks in all the files in the spool directory
# for messages DELAGE days old and deletes them. The user is 
# also warned that he/she should retreive the messages WARNAGE
# days old, or they will be deleted when they reach DELAGE age.

# Detailed info is provided to the user about the messages deleted
# ant those that are older than WARNTIME. The admin is also informed
# about which users were warned and if they also had some mail 
# deleted.  

# Do not pay attention to warnings like "$foo used only once..."
#   as this is caused by the external variables of the messages files.

# Execute this program from the directory where the messages files are.
# Todo:                                                        #
#       - Activate the "per folder" switch (-f)                #
################################################################


# Definition of variables...

my (%confopt);
# Global variables. Overriden by the config file.
################################################
#Where to find the config file
$confopt{"CONFFILE"}="/etc/barrendero.conf";

#Debug level. The greater the noisier.
$confopt{"DEBUG"}=0;

#SPOOLDIR: The directory where the spool is.
$confopt{"SPOOLDIR"}="/var/spool/mail";

#WARNAGE: Maximum age a message can reach before warning.
$confopt{"WARNAGE"}=15;

#DELAGE: messages older than this are deleted.
$confopt{"DELAGE"}=90;

#USECONF: If this is 0, we dont process any config file.
$confopt{"USECONF"}=1;

#MAILREPORTS. If this is 0, we do not send any mail *just a debug option*
$confopt{"MAILREPORTS"}=1;

#ADMIN: The person who gets the global reports. Note the \@ !
$confopt{"ADMIN"}="root\@mysite.org";

#MAKEBACKUP: Should I make a backup of the mail folder (user.sav?)
# 0=No copy;
# 1=user.sav     -> Save the complete mail folder 
# 2=user.deleted -> Save only the deleted messages
$confopt{"MAKEBACKUP"}=2;

#SAVEDIR: Directory in which we will make the .sav and .deleted files.
$confopt{"SAVEDIR"}="\/tmp";

#TRIGGERSIZE: Mail folders smaller than this will not be inspected. 
#             Size un bytes
$confopt{"TRIGGERSIZE"}=300000;

#LANG: Languaje in which the users will receive the messages by default.
#en: english, es: espaol ... More to be contributed.
$confopt{"LANG"}="en";

#DO NOT MODIFY ANYTHING BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING

use Mail::Util qw(read_mbox); 
use Mail::Header;
use Date::Manip;    # We will use this two modules to resolve dates.
use Date::Parse;    # Both of them have their strengths and weakness.
use Fcntl ':flock'; # import LOCK_* constants


if ($#ARGV >= 0 )
{
    $NUM=$#ARGV+1;
    if ($confopt{"DEBUG"}>=5) {print "** We're going to process $NUM arguments\n"};
    $INDEX=1;
    while( $INDEX <= $NUM )
    {
	$OPCION=substr($ARGV[$INDEX-1],0,2);
	if ($OPCION eq "-c") 
	{
	    $confopt{"CONFFILE"}=substr($ARGV[$INDEX-1],2); 
	    print "CONFFILE=$confopt{'CONFFILE'}\n" if ($confopt{'DEBUG'} >=5);
	}
	elsif ($OPCION eq "-f") 
	{
	    $confopt{"FOLDER"}=substr($ARGV[$INDEX-1],2);
	    print "FOLDER=$confopt{'FOLDER'}\n" if ($confopt{'DEBUG'}>=5);
	}
	elsif ($OPCION eq "-n")
	{
	    $confopt{"USECONF"}=0;
	}
	elsif ($OPCION eq "-u")
	{
	    $confopt{"PROCESS_NON_USER"}=1;
	}
	elsif (($OPCION eq "-V") || ($OPCION eq "--version"))
	{
	    $VERSION='$Id: barrendero,v 1.0 1999/08/13 20:15:41 miedu Exp miedu $ ';
	    @VERSION_CADS=split /\s/,$VERSION;
	    print "Barrendero v. $VERSION_CADS[2] $VERSION_CADS[3]".' by Eduardo Diaz Comellas (ediaz@tsc.uvigo.es)'."\n";
	    print "This program is distributed under the GNU license\n";
	    exit 0;
	}
	$INDEX++;
    }
}

#Now, if USECONF=True, we have to process the config file. Beware that the 
# parameters given in the command line have preference over those in the 
# config file.

if($confopt{"USECONF"})
{
    if($confopt{'DEBUG'}>=5){print "** We are going to read the config file $confopt{'CONFFILE'}\n"};
    open (CONFFILE,$confopt{'CONFFILE'}) or die "Unable to open $confopt{'CONFFILE'} : $! \n";
    $DEBUG=$confopt{'DEBUG'}; #Use this temporary $DEBUG while parsing 
                              #the config file. Not doing so is annoying, 
                              #as the debug messages behave abnormally.
    while(defined($line=<CONFFILE>))
    {
	if ($line=~/^[\s\t]*\#(.*)$/)  #Lines begining with # are comments
	{
	    print "Comment: $1 \n" if ($DEBUG >= 6);
	}
	elsif($line=~/(\w+)[\s\t]*=[\s\t]*(\w+)[\s\t]*;[\s\t]*$/i)
	{
	    print "Variable: $line\n" if ($DEBUG>=6);
	    $confopt{"$1"}=$2;
	    print $1."=".$confopt{"$1"}."\n" if($DEBUG>=4);
	}
	# Account number number : For exceptions
	elsif($line=~/(\w+)[\s\t]+(\d+)[\s\t]+(\d+)[\s\t]+(\w+)[\s\t]+(\d+)/i)
	{
	    $NAME=$1;
	    $exceptions{"$1"}->{"warnage"}=$2;
	    $exceptions{"$1"}->{"delage"}=$3;
	    $exceptions{"$1"}->{"lang"}=$4;
	    $exceptions{"$1"}->{"triggersize"}=$5;
	    print "$NAME: warntime=".$exceptions{"$1"}->{"warnage"}." deltime=".$exceptions{"$1"}->{"delage"}." languaje=".$exceptions{"$1"}->{"lang"}." triggersize=".$exceptions{"$1"}->{"triggersize"}."\n" if ($DEBUG>=3);
	}
    }
    close CONFFILE;
}


$totalsaved=0;
#Ok. Now we have to begin the real work!. 
#What time is it?
$now=gmtime;
print "Today is ".$now."\n" if($confopt{'DEBUG'}>=5);
$datetoday=&getsecs($now);

#If we can't save the mail (and saving is activated...) re refuse to go on.
if($confopt{"MAKEBACKUP"})
{
    die "Can't write to SAVEDIR directory: $confopt{'SAVEDIR'}\n" unless (-w $confopt{'SAVEDIR'});
}


#Initialize the messages variables
$root_messages_file="messages.root.$confopt{'LANG'}";
print "* Root's messages file $root_messages_file\n" if ($confopt{'DEBUG'}>=5);
$report_root_body="";

#Check we are running this in the correct directory.
die "Can\'t open $root_messages_file\nPlease check that you run this program from the directory where the file is\n" if (! -r $root_messages_file ); 

#Opening the spool directory
opendir(MAILDIR,$confopt{'SPOOLDIR'}) || die "Cannot access the spool directory $confopt{'SPOOLDIR'}: $!\n"; 

#Some checks about what can be done, permisions, etc.
if($confopt{'MAKEBACKUP'})
{
    die "$confopt{'SAVEDIR'} is not a directory\n" if(! -d $confopt{'SAVEDIR'});
    die "Can't write to directory $confopt{'SAVEDIR'}\n" if(! -w $confopt{'SAVEDIR'});
}

#file after file, the hard work has to be done.
NEWFOLDER: while(defined($mailfolder=readdir(MAILDIR)))
{
    # Check that the folder is a users folder, or if '-u' was used.
    if (getpwnam($mailfolder) || $confopt{'PROCESS_NON_USER'}) 
    {
	next NEWFOLDER if $mailfolder=~/^\./; 
        #Check if it is hidden or the '.' '..' directories
	if ($DEBUG>=4) {print "** Considering the folder $mailfolder\n"};
	#Initialize the WARNAGE, DELAGE, TRIGGER, etc... vars.
	if (defined($exceptions{"$mailfolder"}->{"warnage"}))
	{
	    $WARNAGE=$exceptions{"$mailfolder"}->{"warnage"};
	    $DELAGE=$exceptions{"$mailfolder"}->{"delage"};
	    $LANG=$exceptions{"$mailfolder"}->{"lang"};
	    $TRIGGERSIZE=$exceptions{"$mailfolder"}->{"triggersize"};
	    if ($DEBUG>=5) {print "*** Individual configuration for $mailfolder $WARNAGE $DELAGE $LANG $TRIGGERSIZE\n"};
	}
	else
	{
	    $WARNAGE=$confopt{"WARNAGE"};
	    $DELAGE=$confopt{"DELAGE"};
	    $LANG=$confopt{"LANG"};
	    $TRIGGERSIZE=$confopt{"TRIGGERSIZE"};
	}
	$user=$mailfolder;
	$smfolder="$confopt{'SPOOLDIR'}\/$mailfolder"; 
        #This is the complete path to the folder.

	#Check the triggersize
	if ( $TRIGGERSIZE > -s $smfolder )
	{
	    print "** Not processing $mailfolder becouse it is smaller than $TRIGGERSIZE\n" if ($confopt{'DEBUG'}>4);
	    $report_root_body.=$report_root_line_user_skiped;
	    next NEWFOLDER;
	}
	#OK. If we are here, we know that whe have to work.
	print "** Processing $mailfolder becouse it is bigger than $TRIGGERSIZE\n" if ($confopt{'DEBUG'}>4);
	
	if ($DEBUG>=5) {print "*** Calling check_mailfolder($mailfolder,$smfolder)\n"};
	$savedperuser=&check_mailfolder($mailfolder,$smfolder);
	$totalsaved+=$savedperuser;
    }
    else
    {
	print "* Skiping the folder $mailfolder \n" if ($confopt{'DEBUG'} >=4);
    }
}

# Now we have to send the report to the root.
do($root_messages_file);
$mailtoroot="$report_root_heading"."$report_root_body"."$report_root_tail";
$mailcommand="mail -s \"$mailsubject_root\" $confopt{'ADMIN'}";
open(MAIL,"|$mailcommand");
print MAIL $mailtoroot;
close MAIL;

#Sacab!! (It's finished!). Now just exit :-)
print "Work done. See you later\n" if ($confopt{'DEBUG'}>=3);
exit 0;



### The real work subrutine! This will process the user folder in this way:
#
#Lock mailfolder (with the lock system call and with the procmail semaphore).
#Copy the folder to the savedir if makebackups is activated, with a name
# that will save time later (when leaving a backup). If makebackups is not
# active, make it in the SPOOLDIR directory, with a hidden name.
#Truncate the mailfolder. This remakes the folder and saves the permisions and 
# the owner of the folder!
#Dump the < DELAGE messages into the new mailfolder.
#Unlock the new mailfolder and close it.
#If a hidden folder was created, delete it. If Makebackup=2, delete the
# saved mailfolder and leave just the deleted messages. If Makebackup=1
# the saved mailfolder is already there! ;-) 
#Create the user report and mail it.
#Update the root report.
#Return.
sub check_mailfolder
{
    my ($user,$file)=@_;
    my ($lockfile,$touchtime,$backupfolder,$line,@msgreferences,$msgref,@msgcontents,$msgheaders,@headertags,$report_user_body,$deletedfolder,$size1,$size2);

    $totaldel=0;
    $totalold=0;
    #Check if we can write in that file.
    if(! -w $file)
    {
	print "***** Can't write to $file. Please check permisions\n";
	sleep 5; # Make sure that the operator running this program 
	         # interactively can  read the message.
	return 0;# Continue.
    }

    print "**** Trying to get $file locked\n" if ($confopt{'DEBUG'}>=5);
    $lockfile="${file}.lock";
    open (MBOX,"+<$file");      # Requesting to read/write the file
    seek(MBOX,0,0);
    
    if (( -e  $lockfile ) || (! flock(MBOX,LOCK_EX|LOCK_NB))) 
    {
	# It is locked with mailfolder.lock semaphore or the EXCLUSIVE lock
	# can't be granted.
	print "**** $file  is already locked\nWaiting a moment...\n" if ($confopt{'DEBUG'}>=4);
	sleep 3;
	if (( -e  $lockfile ) || (! flock(MBOX,LOCK_EX|LOCK_NB)))
	{
	    print "**** $file still locked after 2 tries. Giving up\n" if ($confopt{'DEBUG'}>=4);
	    do($root_messages_file); #Update the messages
	    $report_root_body.="$report_root_line_user_skiped_locked"; 
            #Add line to root report
	    return 0;
	}
    }
    #OK. We now have the control over the mail folder.
    #An exclusive lock is doing its job. Lets make the procmail lock.
    $touchtime=time;
    utime $touchtime,$touchtime,$lockfile;

    #Where to move the original folder? Use the backup destination if possible.
    if($confopt{'MAKEBACKUP'} == 1) #Save the whole folder?
    {
	print "***** Saving $user in $confopt{'SAVEDIR'}\/${user}.sav\n" if ($confopt{'DEBUG'}>=5);
	$backupfolder="$confopt{'SAVEDIR'}\/${user}.sav";
    }
    elsif ($confopt{'MAKEBACKUP'} == 2) 
    {
	print "***** Saving $user in $confopt{'SAVEDIR'}\/.${user}.sav\n" if ($confopt{'DEBUG'}>=5);
	print "***** Deleted messages into $confopt{'SAVEDIR'}\/${user}.del\n" if ($confopt{'DEBUG'}>=5);
	$backupfolder="$confopt{'SAVEDIR'}\/.${user}.sav";
	$deletedfolder="$confopt{'SAVEDIR'}\/${user}.del";
    }
    else
    {
	print "***** Saving $user in $confopt{'SPOOLDIR'}\/.${user}.sav\n" if ($confopt{'DEBUG'}>=5);
	$backupfolder="$confopt{'SPOOLDIR'}\/.${user}.sav";
    }
    
    #Now we have to move it....
    open(MBOXBACKUP,">$backupfolder") || die ("Couldn't open $backupfolder for writing: $!\n");
    #Hey, we should have control over this file, shouldn't we? This lock 
    #should work without a flaw.
    flock(MBOXBACKUP,LOCK_EX); 
    seek(MBOX,0,0);
    $linea=<MBOX>;
    while (defined($linea))
    {
	print MBOXBACKUP $linea;
	$linea=<MBOX>;
    }
    flock(MBOXBACKUP,LOCK_UN);
    close MBOXBACKUP;
    seek(MBOX,0,0);
    truncate(MBOX,0); #And truncate it to 0 length! 
                      #note that we didn't loose the lock.
    
    #Now the real work... discriminate and generate reports.
    $user_messages_file="messages.user.$LANG";

    @msgreferences=read_mbox($backupfolder);    
    foreach $msgref  (@msgreferences)
    {
	 @msgcontents=@{$msgref};
	 print "This is the message:\n @msgcontents\n" if ($confopt{'DEBUG'}>=7);
	 $msgheaders=new Mail::Header($msgref);
	 @headertags=$msgheaders->tags();
	 print "This are the tags of the message @headertags\n" if ($confopt{'DEBUG'}>=6);
	 $msgdate=$msgheaders->get('Date');
	 $from=$msgheaders->get('From');
	 $subj=$msgheaders->get('Subject');
	 $msgage=&getage($msgdate);
	 print " ***> From: $from ***> Subject: $subj ***> Date:$msgdate ***> Days old: $msgage\n\n" if ($confopt{'DEBUG'}>=5) ; 
	 if ($msgage>=$WARNAGE)
	 {
	     $totalold+=1;
	     do($user_messages_file);
	     if ($msgage>=$DELAGE)
	     {
		 $totaldel+=1;
		 $report_user_body.="$report_user_line_deleted";
		 #Put it in the deleted messages folder if MAKEBACKUP=2
		 if ($confopt{'MAKEBACKUP'}==2)
		 {
		     open (DELFOLDER,">>$deletedfolder")|| die("Couldn't append to $deletedfolder: $!\n");
		     print DELFOLDER @msgcontents;
		     close DELFOLDER;
		 }
	     }
	     else
	     {
		 $report_user_body.="$report_user_line_warn";
		 #Put it back in the ordinary mail folder
		 print MBOX @msgcontents;
	     }
	 }
	 else
	 {
	     #Just put it in the mail folder.
	     print MBOX @msgcontents;
	 }	 
     }
    close MBOXBACKUP;
    flock (MBOX,LOCK_UN);
    close MBOX;
    #Calculate the total saved space.
    $size1= -s $backupfolder;
    $size2= -s $file;
    $savedperuser=$size1-$size2;
    do($user_messages_file);
    #Messages ready to mail the user report.
    if(($totalold >= 1) && ($confopt{'MAILREPORTS'}))#Only send message if there is somethin to say.
    {
	print "***** Sending mail report to ${user}: $totalold , $totaldel \n" if($confopt{'DEBUG'}>=5);
	$mailcommand="mail -s \"$mailsubject_user\" $user";
	open(MAIL,"|$mailcommand");
	print MAIL $report_user_heading;
	print MAIL $report_user_body;
	print MAIL $report_user_tail;
	close MAIL;
    }
    if($totalold >= 1)#Do we have to inform about this user?.
    {
	#Prepare root messages
	do($root_messages_file);
	$report_root_body.=$report_root_line_deleted_per_user;
	$report_root_body.=$report_root_line_savedspace_per_user;
    }
    

    #Unlink the backup folder unless the admin wants it to be saved.
    unlink $backupfolder unless($confopt{'MAKEBACKUP'}==1);
    
    return $savedperuser;    
}

sub getage
{
    my($datestring)=@_;
    my ($result);
    $result=&getsecs($datestring);
    $result=$result-$datetoday;
    $result=&secs2days($result);
    return $result;
}
   

sub secs2days #Returns the number of days  between two dates given in seconds
{
    my($date1,$date2)=@_;
    my($datediff,$days);
    $datediff=$date2 - $date1; #It dosen't matter the order. 
    $days=$datediff/86400;
    $days=int(abs($days)) ;
    return $days;
}

sub getsecs
{
    
    # Well... this is delicated... It seems that Date::Manip is not as
    # strong as it should be ;). There are so weird mailers out there :(

    # Several languajes are tried. This should be modified as Date::Manip
    # knows about more languajes. If this languajes are not enought for you
    # try to modify Date::Manip. It is *VERY* easy to add a new languaje.

    #If we can't resolve the date, we return $datetoday, and the mail will
    #not be deleted. 

    my ($date)=@_;
    my ($datetest);

    $datetest=str2time($date); #Simple parse....
    return $datetest if (defined($datetest)); #Return if it worked

    $date=~s/\(.*\)//g; #Take out the zone info. It confuses Date::Manip
    $date=~s/\+\-*\d+//g;  #This erases +002 like constructions

    print $date."\n" if ($confopt{"DEBUG"}>=5);
    &Date_Init("Language=English");
    $datetest=&UnixDate($date,"%s");
    if (!defined($datetest)) 
    {
	&Date_Init("Language=Spanish","DateFormat=non-US");
	$datetest=&UnixDate($date,"%s");
    }
    if (!defined($datetest))
    {
	&Date_Init("Language=French","DateFormat=non-US");
	$datetest=&UnixDate($date,"%s");
    }
    if (!defined($datetest))
    {
	&Date_Init("Language=German","DateFormat=non-US");
	$datetest=&UnixDate($date,"%s");
    }
    if (!defined($datetest))
    {
	$datetest=$datetoday;
    }


    return $datetest;
}




  




