Article 3738 of comp.lang.perl:
Xref: feenix.metronet.com comp.lang.perl:3738
Path: feenix.metronet.com!news.utdallas.edu!hermes.chpc.utexas.edu!cs.utexas.edu!uunet!pipex!uknet!cardamom.compnews.co.uk!phil
From: [email protected] (Phil Male)
Newsgroups: comp.lang.perl
Subject: Re: Script to manage a mailbox (SunOS)
Date: 27 Jun 1993 10:26:26 GMT
Organization: Computer Newspaper Services, Howden, UK.
Lines: 525
Message-ID: <[email protected]>
References: <[email protected]>
Reply-To: [email protected]
NNTP-Posting-Host: cumin.compnews.co.uk
X-Newsreader: Tin 1.1 PL4

[email protected] (Rob) writes:
:
: I have the need for a utility, preferably perl, which will sort
: through a user's mailbox, on a distributed sun system, and remove
: messages which are older than a certain number of days.  Has anyone
: written such a utility?  Do you know where I would be able to find
: such a thing written in anything?  Thank you.
:
:                                -Rob

This is what we use, I knocked this up a while ago from expire_mail by Steve
Mitchell ([email protected]). We install it in /usr/local/sbin on
all our Suns then get cron to tidy up everyone's mailbox, removing messages
older than 2 months. It leaves a message in the users mailbox detailing the
subject lines of the messages expired. Options, as far as possible, are
compatible with Steve's expire_mail software.

Change the definitions at the start to say who your postmaster is etc.
Run like this in cron:

0 23 * * 3 /usr/local/sbin/expire_mail -l -M -a 60 /var/spool/mail/*

The options select the policy for the expiry, in our case 60 day old messages
whatever their status.

I havn't sent this out before, but it's been working here for a year or so.
I guess if anyone has any changes they want to fold in, mail me with them.


#!/usr/local/bin/perl --                                             -*-perl-*-
#
# Copyright (c) Information Systems, The Press Association Limited 1993
# Portions Copyright (c) Computer Newspaper Services Limited 1993
# All rights reserved.
#
# License to use, copy, modify, and distribute this work and its
# documentation for any purpose and without fee is hereby granted,
# provided that you also ensure modified files carry prominent notices
# stating that you changed the files and the date of any change, ensure
# that the above copyright notice appear in all copies, that both the
# copyright notice and this permission notice appear in supporting
# documentation, and that the name of Computer Newspaper Services not
# be used in advertising or publicity pertaining to distribution or use
# of the work without specific, written prior permission from Computer
# Newspaper Services.
#
# By copying, distributing or modifying this work (or any derived work)
# you indicate your acceptance of this license and all its terms and
# conditions.
#
# THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO ANY IMPLIED
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
# NONINFRINGEMENT OF THIRD PARTY RIGHTS.  THE ENTIRE RISK AS TO THE QUALITY
# AND PERFORMANCE OF THE SOFTWARE, INCLUDING ANY DUTY TO SUPPORT OR
# MAINTAIN, BELONGS TO THE LICENSEE.  SHOULD ANY PORTION OF THE SOFTWARE
# PROVE DEFECTIVE, THE LICENSEE (NOT THE COPYRIGHT OWNER) ASSUMES THE
# ENTIRE COST OF ALL SERVICING, REPAIR AND CORRECTION.  IN NO EVENT SHALL
# THE COPYRIGHT OWNER BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
# PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
# THIS SOFTWARE.
#
#
# $Id: expire_mail,v 1.1 1993/06/03 10:43:26 phil Exp $
#

#
# Information Systems Engineering Group
# Phil Male
#

local($_rcsid) = '$Id: expire_mail,v 1.1 1993/06/03 10:43:26 phil Exp $';
local($_copyright) = 'Copyright (c) Information Systems, The Press Association Limited 1993';

require "getopts.pl";                   # option handling
require "timelocal.pl";                 # time conversion
require "ctime.pl";                     # ctime for pseudo-mailing
require "stat.pl";                      # file status

# Perl mail expire.
# This program removes old messages from system mailboxes.
# It assumes the format of mailboxes to be standard
# sendmail format mail with a blank line followed by a `From ' line
# starting each and every message. Mailbox locking is via flock.
# Works under SunOS.
#
# Options as follows:
# -v                    verbose output
# -V                    display version information and quit
# -d                    debug mode (no change to mailbox)
# -l                    display messages for crontab output
# -z                    do not delete zero length mailboxes
# -t                    do not reset access and modification times on mailbox
# -o                    always open mailbox, never just test modification date
# -M                    append a message detailing deleted messages for the user
# -T                    do not record delivery of mail summary on mailbox date
# -a days               messages whose age is greater than days are expired
# -O days               messages whose age is greater than days are expired
# -u user               only consider messages from user (regexp)
# -S read|old           only consider messages with status `old' or `read'
# -s subject            only consider messages with subject (regexp)
#
# Based on expire_mail by Steve Mitchell ([email protected])
#

#####
#
# Definitions
#
#####

# site postmaster - XXX change this as required
$postmaster = "[email protected]";

# current user
$me = getlogin || (getpwuid($<))[0] || "unknown";
$home = $ENV{'HOME'};

# default mailbox for a user - XXX change this as required
$default_mailbox = $ENV{'MAILBOX'} || "/var/spool/mail/$me";

# notice to append to list of deleted messages
$notice = "
Please read your mail on a regular basis. Old mail should be deleted,
or be filed in your personal mail folders. If you do not know how to
use mail folders, please refer to the `Guide to EMail@compnews'
available from Information Systems department, or Administration.

If you have any other queries regarding the mail system, please send
mail to $postmaster.

Processed by $_expire_mail_rcsid";

# set the umask for temp files
umask( 0700 );

# make stdout unbuffered
select(STDOUT); $| = 1;

$LOCK_EX = 2;                           # lock
$LOCK_UN = 8;                           # unlock
$START_TIME = time;                     # time right now
$SEC_PER_DAY = 24 * 60 * 60;            # seconds in a day
$line_buffer = "";                      # empty line buffer

# month numbers
$mon_num{'Jan'} = 0;
$mon_num{'Feb'} = 1;
$mon_num{'Mar'} = 2;
$mon_num{'Apr'} = 3;
$mon_num{'May'} = 4;
$mon_num{'Jun'} = 5;
$mon_num{'Jul'} = 6;
$mon_num{'Aug'} = 7;
$mon_num{'Sep'} = 8;
$mon_num{'Oct'} = 9;
$mon_num{'Nov'} = 10;
$mon_num{'Dec'} = 11;

#####
#
# Support
#
#####

# line buffer for look-ahead

sub get_line
{
 local( $line ) = "";                  # line to return

 if( ! ($line_buffer eq "") ) {
   $line = $line_buffer;
   $line_buffer = "";
 } else {
   $line = <MBOX>;
 }
 return $line;
}

# read message from mailbox

sub read_message
{
 local( $msg ) = "";                   # message to send back
 local( $prev_blank ) = 1;             # assume previous line blank
 local( $seen_from ) = 0;              # seen a from line
 local( $line ) = "";                  # current line

 # reset some globals
 $msg_status = "";
 $msg_subject = "";
 $msg_date = "";

 while( $line = &get_line ) {

   if( $line =~ /^From\s+([^\s]+)\s+(.*)$/ ) {
     # if previous line was blank, then legal from line
     if( $prev_blank ) {
       # if already seen a legal from line, then this is next message
       if( $seen_from ) {
         # pushback this from line
         $line_buffer = $line;
         return $msg;
       }
       $seen_from++;
       # From line found, extract information
       ( $msg_from, $msg_date ) = ( $1, $2 );
       $msg_stamp = &rctime( $msg_date );
       $msg_age = &days_old( $msg_stamp );
     }
   } elsif( $line =~ /^[Ss]tatus: ([A-Za-z]+)/ ) {
     ( $msg_status ) = ( $1 );
   } elsif( $line =~ /^[Ss]ubject: (.*)$/ ) {
     ( $msg_subject ) = ( $1 );
   }

   # set previous line
   if( $line =~ /^$/ ) {
     $prev_blank = 1;
   } else {
     $prev_blank = 0;
   }

   $msg .= $line;
 }

 return $msg;
}

# write a message into a mailbox

sub write_message
{
 print TMPF "@_";
}

# parse the ctime string into a time value
# From line contains local time

sub rctime
{
 local( $pt ) = @_;                    # time to convert
 local( $ct ) = -1;                    # converted time

 if( $pt =~ /^([A-Za-z]+)\s+([A-Za-z]+)\s+([0-9]+)\s+([0-9:]+)\s+([0-9]+)/ ) {
   ( $day, $mon, $mday, $time, $year ) = ( $1, $2, $3, $4, $5 );
   ( $hour, $min, $sec ) = split( ':', $time );
   if( $year > 1900 ) { $year -= 1900; }
   $ct = &timelocal($sec,$min,$hour,$mday,$mon_num{$mon},$year);
 }
 return $ct;
}

# age in days

sub days_old
{
 local( $agev ) = @_;                  # time to convert

 return( ( $START_TIME - $agev ) / $SEC_PER_DAY );
}

# basename

sub basename
{
 local( $path ) = @_;                  # path to find the base of
 local( $base ) = rindex( $path, "/" );

 if( $base < 0 ) {
       $base = $path;
 } else {
       $base = substr($path, $base + 1);
 }

 return $base;
}

# usage message

sub usage
{
 print STDERR "usage: expire_mail [-vlV] [-zotTM] [-d] { [-O days] [-u user] [-S read|old] [-s subject] } mailbox...\n";
 exit 0;
}

#####
#
# Main
#
#####

&Getopts( 'VvO:a:ou:zdS:s:MtTl' ) || &usage;

# compat
$opt_a = $opt_O if ($opt_O && !$opt_a);

# check version
if( $opt_V ) {
 print "expire_mail: mail expiry agent\n";
 print "expire_mail: $_expire_mail_rcsid\n";
 &usage;
}

# use default mailbox if non supplied
if( $#ARGV < $[ ) {
 $ARGV[0] = "$default_mailbox";
}

# decode status option
if( $opt_S ) {
 if( $opt_S eq "old" ) {
   $opt_S = "O";
 } elsif( $opt_S eq "read" ) {
   $opt_S = "R";
 } else {
   print STDERR "expire_mail: status may only be one of `old' or `unread'\n";
   &usage;
 }
}

# check we are actually doing some processing
if( !$opt_a && !$opt_u && !$opt_S && !$opt_s ) {
 print STDERR "expire_mail: must specify at least one of -O, -u, -S or -s\n";
 &usage;
}

# debug mode implies verbose mode
if( $opt_d ) { $opt_v = 1; }

# foreach mailbox...
while( $mailbox = shift ) {

 if( $opt_v ) { print STDOUT "Checking mailbox $mailbox\n"; }

 # does mailbox exist
 if( ! -f $mailbox ) { next; }

 # stat the mailbox
 @sb = &Stat($mailbox);

 # can it be deleted now?
 if( !$opt_o && $opt_a ) {
   # check the modification date
   $age = &days_old(@sb[$ST_MTIME]);
   if( $age > $opt_a ) {
     if( $opt_v ) { print STDOUT "Expiring mailbox $mailbox\n"; }
     if( !$opt_d ) {
       if( $opt_z ) {
         open( MBOX, ">$mailbox" ) ||
           print STDERR "expire_mail: failed to truncate $mailbox\n";
         close( MBOX );
       } else {
         unlink( $mailbox ) ||
           print STDERR "expire_mail: failed to remove $mailbox\n";
       }
     }
     next;
   }
 }

 # open the mailbox
 if( !open( MBOX, "+<$mailbox" ) ) {
   print STDERR "expire_mail: unable to open $mailbox\n";
   next;
 }

 # lock the mailbox
 if( !flock( MBOX, $LOCK_EX ) ) {
   print STDERR "expire_mail: unable to lock $mailbox\n";
   close( MBOX );
   next;
 }

 # open the temporary file
 $tmpname = "$mailbox.exp$$";
 if( !open( TMPF, "+>$tmpname" ) ) {
   print STDERR "expire_mail: unable to create temporary file for $mailbox\n";
   close( MBOX );
   next;
 }
 unlink( $tmpname );

 # init counters
 $count = 0;
 $exp = 0;

 # read each message in turn
 while( $msg = &read_message ) {

   $count++;

   # looking for specific from users
   if( $opt_u ) {
     if( ! ($msg_from =~ /$opt_u/) ) {
       if( $opt_v ) {
         print STDOUT "\tMsg #$count: from   \r";
       }
       &write_message( $msg );
       next;
     }
   }

   # check message status
   if( $opt_S ) {
     if( !($msg_status =~ /$opt_S/) ) {
       if( $opt_v ) {
         print STDOUT "\tMsg #$count: status   \r";
       }
       &write_message( $msg );
       next;
     }
   }

   # check message subject
   if( $opt_s ) {
     if( ! ($msg_subject =~ /$opt_s/) ) {
       if( $opt_v ) {
         print STDOUT "\tMsg #$count: subject   \r";
       }
       &write_message( $msg );
       next;
     }
   }

   # only other thing to check is message age
   if( $opt_a ) {
     if( $msg_age <= $opt_a ) {
       if( $opt_v ) {
         print STDOUT "\tMsg #$count: newer   \r";
       }
       &write_message( $msg );
       next;
     }
   }

   # log the expiry
   if( $opt_v ) {
     print STDOUT "\tMsg #$count: expired   \r";
   }

   # copy message accross if in debug
   if( $opt_d ) {
     &write_message( $msg );
   } else {
     # record the mail message from and subject line
     $pad = ' ' x (25 - length($msg_from) );
     $npad = ' ' x ( 4 - length($count) );
     $subjects[$exp] = "$npad$count $msg_from$pad $msg_date\n     $msg_subject\n";
   }

   # increment the expired message count
   $exp++;
 }

 if( !$opt_d ) {

   # if sending mail to the owner of the mailbox, append message on the end

   if( $opt_M && $exp > 0 ) {
     chop( $ct = &ctime(time) );
     $to = &basename( $mailbox );
     print TMPF "From mail_expire $ct\n";
     print TMPF "From: mail_expire (Mail Expiry Agent)\n";
     print TMPF "Reply-To: $postmaster\n";
     print TMPF "To: $to\n";
     print TMPF "Subject: Expired Mail Summary\n\n";
     print TMPF "The following messages have been automatically removed from your\n";
     print TMPF "mailbox by the mail expiry agent.\n\n";
     # fitted to $subjects layout
     print TMPF " Msg From & Subject            Dated\n\n";
     foreach $msg ( @subjects ) {
       print TMPF "$msg\n";
     }
     print TMPF "$notice\n\n";

     if( !$opt_T ) {
       # set the modification time for the mailbox to be now
       @sb[$ST_MTIME] = time;
     }
   }

   # copy data back into mailbox to preserve permissions, creation time
   # and user and group id

   # zero length the mailbox
   truncate( MBOX, 0 );
   # *** START Critical
   # any data to copy?
   if( $exp < $count ) {
     # restart both files
     seek(MBOX, 0, 0);
     seek(TMPF, 0, 0);
     # copy file into mailbox, better with sysread/syswrite?
     while( <TMPF> ) {
       print MBOX $_;
     }
   } elsif( !$opt_z ) {
     unlink( $mailbox );
   }
   # *** END Critical

 }

 # unlock mailbox
 flock( MBOX, $LOCK_UN );

 # close files
 close( MBOX );
 close( TMPF );

 # reset access and modification dates
 # if we have sent mail, then the modification time is the time of the mail
 if( !$opt_t ) {
   utime( @sb[$ST_ATIME], @sb[$ST_MTIME], $mailbox );
 }

 # show counters
 if( $opt_v || ( $opt_l && $exp ) ) {
   print "$mailbox contained $count messages, expired $exp messages\n";
 }
}