138032Speter#!/usr/bin/perl
238032Speter#
338032Speter# re-mqueue -- requeue messages from queueA to queueB based on age.
438032Speter#
538032Speter#	Contributed by Paul Pomes <ppomes@Qualcomm.COM>.
638032Speter#		http://www.qualcomm.com/~ppomes/
738032Speter#
838032Speter# Usage: re-mqueue [-d] queueA queueB seconds
938032Speter#
1038032Speter#  -d		enable debugging
1138032Speter#  queueA	source directory
1238032Speter#  queueB	destination directory
1338032Speter#  seconds	select files older than this number of seconds
1438032Speter#
1538032Speter# Example: re-mqueue /var/spool/mqueue /var/spool/mqueue2 2700
1638032Speter#
1738032Speter# Moves the qf* and df* files for a message from /var/spool/mqueue to
1838032Speter# /var/spool/mqueue2 if the df* file is over 2700 seconds old.
1938032Speter#
2038032Speter# The qf* file can't be used for age checking as it's partially re-written
2138032Speter# with the results of the last queue run.
2238032Speter#
2338032Speter# Rationale: With a limited number of sendmail processes allowed to run,
2438032Speter# messages that can't be delivered immediately slow down the ones that can.
2538032Speter# This becomes especially important when messages are being queued instead
2638032Speter# of delivered right away, or when the queue becomes excessively deep.
2738032Speter# By putting messages that have already failed one or more delivery attempts
2838032Speter# into another queue, the primary queue can be kept small and fast.
2938032Speter#
3038032Speter# On postoffice.cso.uiuc.edu, the primary sendmail daemon runs the queue
3138032Speter# every thirty minutes.  Messages over 45 minutues old are moved to
3238032Speter# /var/spool/mqueue2 where sendmail runs every hour.  Messages more than
3338032Speter# 3.25 hours old are moved to /var/spool/mqueue3 where sendmail runs every
3438032Speter# four hours.  Messages more than a day old are moved to /var/spool/mqueue4
3538032Speter# where sendmail runs three times a day.  The idea is that a message is
3638032Speter# tried at least twice in the first three queues before being moved to the
3738032Speter# old-age ghetto.
3838032Speter#
3938032Speter# (Each must be re-formed into a single line before using in crontab)
4038032Speter#
4138032Speter# 08 * * * *	/usr/local/libexec/re-mqueue /var/spool/mqueue ##						/var/spool/mqueue2 2700
4238032Speter# 11 * * * *	/usr/lib/sendmail -oQ/var/spool/mqueue2 -q > ##							> /var/log/mqueue2 2>&1
4338032Speter# 38 * * * *	/usr/local/libexec/re-mqueue /var/spool/mqueue2
4438032Speter#					/var/spool/mqueue3 11700
4538032Speter# 41 1,5,9,13,17,21 * * * /usr/lib/sendmail -oQ/var/spool/mqueue3 -q ##							> /var/log/mqueue3 2>&1
4638032Speter# 48 * * * *	/usr/local/libexec/re-mqueue /var/spool/mqueue3
4738032Speter#					/var/spool/mqueue4 100000
4838032Speter#53 3,11,19 * * * /usr/lib/sendmail -oQ/var/spool/mqueue4 -q > ##							> /var/log/mqueue4 2>&1
4938032Speter#
5038032Speter#
5138032Speter# N.B., the moves are done with link().  This has two effects: 1) the mqueue*
5238032Speter# directories must all be on the same filesystem, and 2) the file modification
5338032Speter# times are not changed.  All times must be cumulative from when the df*
5438032Speter# file was created.
5538032Speter#
5638032Speter# Copyright (c) 1995 University of Illinois Board of Trustees and Paul Pomes
5738032Speter# All rights reserved.
5838032Speter#
5938032Speter# Redistribution and use in source and binary forms, with or without
6038032Speter# modification, are permitted provided that the following conditions
6138032Speter# are met:
6238032Speter# 1. Redistributions of source code must retain the above copyright
6338032Speter#    notice, this list of conditions and the following disclaimer.
6438032Speter# 2. Redistributions in binary form must reproduce the above copyright
6538032Speter#    notice, this list of conditions and the following disclaimer in the
6638032Speter#    documentation and/or other materials provided with the distribution.
6738032Speter# 3. All advertising materials mentioning features or use of this software
6838032Speter#    must display the following acknowledgement:
6938032Speter#       This product includes software developed by the University of
7038032Speter#       Illinois at Urbana and their contributors.
7138032Speter# 4. Neither the name of the University nor the names of their contributors
7238032Speter#    may be used to endorse or promote products derived from this software
7338032Speter#    without specific prior written permission.
7438032Speter#
7538032Speter# THIS SOFTWARE IS PROVIDED BY THE TRUSTEES AND CONTRIBUTORS ``AS IS'' AND
7638032Speter# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
7738032Speter# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
7838032Speter# ARE DISCLAIMED.  IN NO EVENT SHALL THE TRUSTEES OR CONTRIBUTORS BE LIABLE
7938032Speter# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
8038032Speter# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
8138032Speter# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
8238032Speter# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
8338032Speter# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
8438032Speter# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
8538032Speter# SUCH DAMAGE.
8638032Speter#
8764562Sgshapiro# @(#)$OrigId: re-mqueue,v 1.3 1995/05/25 18:14:53 p-pomes Exp $
8864562Sgshapiro#
8964562Sgshapiro# Updated by Graeme Hewson <ghewson@uk.oracle.com> May 1999
9064562Sgshapiro#
9164562Sgshapiro#	'use Sys::Syslog' for Perl 5
9264562Sgshapiro#	Move transcript (xf) files if they exist
9364562Sgshapiro#	Allow zero-length df files (empty message body)
9464562Sgshapiro#	Preserve $! for error messages
9564562Sgshapiro#
9664562Sgshapiro# Updated by Graeme Hewson <ghewson@uk.oracle.com> April 2000
9764562Sgshapiro#
9864562Sgshapiro#	Improve handling of race between re-mqueue and sendmail
9964562Sgshapiro#
10064562Sgshapiro# Updated by Graeme Hewson <graeme.hewson@oracle.com> June 2000
10164562Sgshapiro#
10264562Sgshapiro#	Don't exit(0) at end so can be called as subroutine
10364562Sgshapiro#
10464562Sgshapiro# NB This program can't handle separate qf/df/xf subdirectories
10564562Sgshapiro# as introduced in sendmail 8.10.0.
10664562Sgshapiro#
10738032Speter
10864562Sgshapirouse Sys::Syslog;
10938032Speter
11038032Speter$LOCK_EX = 2;
11138032Speter$LOCK_NB = 4;
11238032Speter$LOCK_UN = 8;
11338032Speter
11438032Speter# Count arguments, exit if wrong in any way.
11538032Speterdie "Usage: $0 [-d] queueA queueB seconds\n" if ($#ARGV < 2);
11638032Speter
11738032Speterwhile ($_ = $ARGV[0], /^-/) {
11838032Speter    shift;
11938032Speter    last if /^--$/;
12038032Speter    /^-d/ && $debug++;
12138032Speter}
12238032Speter
12338032Speter$queueA = shift;
12438032Speter$queueB = shift;
12538032Speter$age = shift;
12638032Speter
12738032Speterdie "$0: $queueA not a directory\n" if (! -d $queueA);
12838032Speterdie "$0: $queueB not a directory\n" if (! -d $queueB);
12938032Speterdie "$0: $age isn't a valid number of seconds for age\n" if ($age =~ /\D/);
13038032Speter
13138032Speter# chdir to $queueA and read the directory.  When a df* file is found, stat it.
13238032Speter# If it's older than $age, lock the corresponding qf* file.  If the lock
13338032Speter# fails, give up and move on.  Once the lock is obtained, verify that files
13438032Speter# of the same name *don't* already exist in $queueB and move on if they do.
13538032Speter# Otherwise re-link the qf* and df* files into $queueB then release the lock.
13638032Speter
13738032Speterchdir "$queueA" || die "$0: can't cd to $queueA: $!\n";
13838032Speteropendir (QA, ".") || die "$0: can't open directory $queueA for reading: $!\n";
13938032Speter@dfiles = grep(/^df/, readdir(QA));
14038032Speter$now = time();
14138032Speter($program = $0) =~ s,.*/,,;
14238032Speter&openlog($program, 'pid', 'mail');
14338032Speter
14438032Speter# Loop through the dfiles
14538032Speterwhile ($dfile = pop(@dfiles)) {
14638032Speter    print "Checking $dfile\n" if ($debug);
14738032Speter    ($qfile = $dfile) =~ s/^d/q/;
14864562Sgshapiro    ($xfile = $dfile) =~ s/^d/x/;
14938032Speter    ($mfile = $dfile) =~ s/^df//;
15038032Speter    if (! -e $qfile || -z $qfile) {
15138032Speter	print "$qfile is gone or zero bytes - skipping\n" if ($debug);
15238032Speter	next;
15338032Speter    }
15438032Speter
15538032Speter    ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
15638032Speter     $atime,$mtime,$ctime,$blksize,$blocks) = stat($dfile);
15764562Sgshapiro    if (! defined $mtime) {
15864562Sgshapiro	print "$dfile is gone - skipping\n" if ($debug);
15964562Sgshapiro	next;
16064562Sgshapiro    }
16138032Speter
16238032Speter    # Compare timestamps
16338032Speter    if (($mtime + $age) > $now) {
16438032Speter	printf ("%s is %d seconds old - skipping\n", $dfile, $now-$mtime) if ($debug);
16538032Speter	next;
16638032Speter    }
16738032Speter
16838032Speter    # See if files of the same name already exist in $queueB
16938032Speter    if (-e "$queueB/$dfile") {
17038032Speter	print "$queueb/$dfile already exists - skipping\n" if ($debug);
17138032Speter	next;
17238032Speter    }
17338032Speter    if (-e "$queueB/$qfile") {
17438032Speter	print "$queueb/$qfile already exists - skipping\n" if ($debug);
17538032Speter	next;
17638032Speter    }
17764562Sgshapiro    if (-e "$queueB/$xfile") {
17864562Sgshapiro	print "$queueb/$xfile already exists - skipping\n" if ($debug);
17964562Sgshapiro	next;
18064562Sgshapiro    }
18138032Speter
18238032Speter    # Try and lock qf* file
18338032Speter    unless (open(QF, ">>$qfile")) {
18438032Speter	print "$qfile: $!\n" if ($debug);
18538032Speter	next;
18638032Speter    }
18738032Speter    $retval = flock(QF, $LOCK_EX|$LOCK_NB) || ($retval = -1);
18838032Speter    if ($retval == -1) {
18938032Speter	print "$qfile already flock()ed - skipping\n" if ($debug);
19038032Speter	close(QF);
19138032Speter	next;
19238032Speter    }
19338032Speter    print "$qfile now flock()ed\n" if ($debug);
19438032Speter
19564562Sgshapiro    # Check df* file again in case sendmail got in
19664562Sgshapiro    if (! -e $dfile) {
19764562Sgshapiro	print "$mfile sent - skipping\n" if ($debug);
19864562Sgshapiro	# qf* file created by ourselves at open? (Almost certainly)
19964562Sgshapiro	if (-z $qfile) {
20064562Sgshapiro	   unlink($qfile);
20164562Sgshapiro	}
20264562Sgshapiro	close(QF);
20364562Sgshapiro	next;
20464562Sgshapiro    }
20564562Sgshapiro
20638032Speter    # Show time!  Do the link()s
20738032Speter    if (link("$dfile", "$queueB/$dfile") == 0) {
20864562Sgshapiro	$bang = $!;
20964562Sgshapiro	&syslog('err', 'link(%s, %s/%s): %s', $dfile, $queueB, $dfile, $bang);
21064562Sgshapiro	print STDERR "$0: link($dfile, $queueB/$dfile): $bang\n";
21138032Speter	exit (1);
21238032Speter    }
21338032Speter    if (link("$qfile", "$queueB/$qfile") == 0) {
21464562Sgshapiro	$bang = $!;
21564562Sgshapiro	&syslog('err', 'link(%s, %s/%s): %s', $qfile, $queueB, $qfile, $bang);
21664562Sgshapiro	print STDERR "$0: link($qfile, $queueB/$qfile): $bang\n";
21738032Speter	unlink("$queueB/$dfile");
21838032Speter	exit (1);
21938032Speter    }
22064562Sgshapiro    if (-e "$xfile") {
22164562Sgshapiro	if (link("$xfile", "$queueB/$xfile") == 0) {
22264562Sgshapiro	    $bang = $!;
22364562Sgshapiro	    &syslog('err', 'link(%s, %s/%s): %s', $xfile, $queueB, $xfile, $bang);
22464562Sgshapiro	    print STDERR "$0: link($xfile, $queueB/$xfile): $bang\n";
22564562Sgshapiro	    unlink("$queueB/$dfile");
22664562Sgshapiro	    unlink("$queueB/$qfile");
22764562Sgshapiro	    exit (1);
22864562Sgshapiro	}
22964562Sgshapiro    }
23038032Speter
23138032Speter    # Links created successfully.  Unlink the original files, release the
23238032Speter    # lock, and close the file.
23338032Speter    print "links ok\n" if ($debug);
23438032Speter    if (unlink($qfile) == 0) {
23564562Sgshapiro	$bang = $!;
23664562Sgshapiro	&syslog('err', 'unlink(%s): %s', $qfile, $bang);
23764562Sgshapiro	print STDERR "$0: unlink($qfile): $bang\n";
23838032Speter	exit (1);
23938032Speter    }
24038032Speter    if (unlink($dfile) == 0) {
24164562Sgshapiro	$bang = $!;
24264562Sgshapiro	&syslog('err', 'unlink(%s): %s', $dfile, $bang);
24364562Sgshapiro	print STDERR "$0: unlink($dfile): $bang\n";
24438032Speter	exit (1);
24538032Speter    }
24664562Sgshapiro    if (-e "$xfile") {
24764562Sgshapiro	if (unlink($xfile) == 0) {
24864562Sgshapiro	    $bang = $!;
24964562Sgshapiro	    &syslog('err', 'unlink(%s): %s', $xfile, $bang);
25064562Sgshapiro	    print STDERR "$0: unlink($xfile): $bang\n";
25164562Sgshapiro	    exit (1);
25264562Sgshapiro	}
25364562Sgshapiro    }
25438032Speter    flock(QF, $LOCK_UN);
25538032Speter    close(QF);
25638032Speter    &syslog('info', '%s moved to %s', $mfile, $queueB);
25738032Speter    print "Done with $dfile $qfile\n\n" if ($debug);
25838032Speter}
259