#!/usr/bin/perl -w
##################
# pfHandle - Postfix mail queue handler wrapper program
# written by carl.thompson@rackspace.com
##################

use strict;
use Getopt::Std;
use vars qw/%OPTIONS $pfdir $Version/;
$pfdir   = "/var/spool/postfix";
$Version = "20090923:1744";

&Parse_Switches;

#####

sub Parse_Switches
{
    my $available_options;
    $available_options = "abcdD:fF:hlm:NP:sS:v";
    getopts($available_options, \%OPTIONS) || &Usage;
    &Usage()   if $OPTIONS{'h'};
    &Version() if $OPTIONS{'v'};
    &List_Queue('', $OPTIONS{'N'}) if $OPTIONS{'l'};
    &Process_Queue() if $OPTIONS{'f'};
    &List_Queue('active',   $OPTIONS{'N'}) if $OPTIONS{'a'};
    &List_Queue('bounce',   $OPTIONS{'N'}) if $OPTIONS{'b'};
    &List_Queue('corrupt',  $OPTIONS{'N'}) if $OPTIONS{'c'};
    &List_Queue('deferred', $OPTIONS{'N'}) if $OPTIONS{'d'};
    &List_Queue('incoming', $OPTIONS{'N'}) if $OPTIONS{'i'};
    &List_Queue('',         'stats')       if $OPTIONS{'s'};
    &Display_Message($OPTIONS{'m'})    if $OPTIONS{'m'};
    &Delete_Message($OPTIONS{'D'})     if $OPTIONS{'D'};
    &Delete_From_Sender($OPTIONS{'F'}) if $OPTIONS{'F'};
    &Delete_By_Subject($OPTIONS{'S'})  if $OPTIONS{'S'};
    &Purge_Queue($OPTIONS{'P'})        if $OPTIONS{'P'};
    if (!%OPTIONS) { &Usage; }
    exit 0;
}

sub Usage
{
    print qq(Usage: pfHandle [OPTION]
Example: pfHandle -v

Available options:
  -a						list the current active mail queue
  -b						list the current bounce mail queue
  -c						list the current corrupt mail queue
  -d						list the current deferred mail queue
  -D #						delete the email message
  -f						try to reprocess queued messages now 
  -F sender					delete all mail from this email address
  -h						show this help message
  -l						list all the current mail queues
  -m #						display the email message
  -N						display only the message IDs
  -P [hold|incoming|active|deferred]		purge all messages from the mail queue
  -s						display the mail queue statistics
  -S subject					delete all mail with this in the subject
  -v						version information
);
    exit 1;
}

sub Purge_Queue
{
    my $queue = shift;
    $queue =~ tr/A-Z/a-z/;
    if ($queue !~ /^(hold|incoming|active|deferred)$/)
    {
        print
          "Please specify a valid queue to purge, [hold|incoming|active|deferred|all]\n";
        exit;
    }
    print "Do you really want to purge the $queue queue?: [y,N] ";
    my $really = <STDIN>;
    chomp($really);
    if ($really eq "y" || $really eq "Y")
    {
        qx|postsuper -d ALL $queue|;
    }
    exit;
}

sub Version
{
    print "pfHandle version $Version\n";
    exit;
}

sub Get_Queue
{
    my $queue = shift;
    my $do    = shift;
    my ($entity, @qlist, %from, %sender, %to, %subject, %date, %size, $count);
    if ($queue ne "deferred")
    {
        my (@qlist1);
        opendir(QUEUE, "$pfdir/$queue");
        @qlist1 = grep(!/^\.$/, grep(!/^\.\.$/, readdir(QUEUE)));
        closedir(QUEUE);
        foreach (@qlist1)
        {
            my ($test) = qx|file $pfdir/$queue|;
            if ($_ !~ /directory/) { push(@qlist, $_); }
        }
    } else
    {
        my (@subdirs);
        opendir(QUEUE, "$pfdir/$queue");
        push(@subdirs, grep(!/^\.$/, grep(!/^\.\.$/, readdir(QUEUE))));
        closedir(QUEUE);
        foreach (@subdirs)
        {
            opendir(QUEUE, "$pfdir/$queue/$_");
            push(@qlist, grep(!/^\.$/, grep(!/^\.\.$/, readdir(QUEUE))));
            closedir(QUEUE);
        }
    }
    foreach $entity (@qlist)
    {
        my @lines = qx|postcat -q $entity|;
        foreach (@lines)
        {
            chomp($_);
            if ((/^sender:/) && (!$sender{$entity}))
            {
                $_ =~ s/^sender:\s+//;
                $sender{$entity} = $_;
            }
            if ((/^From:/) && (!$from{$entity}))
            {
                $_ =~ s/^From:\s+//;
                $from{$entity} = $_;
            }
            if ((/^To:/) && (!$to{$entity}))
            {
                $_ =~ s/To:\s+//;
                $to{$entity} = $_;
            }
            if ((/^Subject:/) && (!$subject{$entity}))
            {
                $_ =~ s/^Subject:\s+//;
                $subject{$entity} = $_;
            }
            if ((/^Date:/) && (!$date{$entity}))
            {
                $_ =~ s/^Date:\s+//;
                $date{$entity} = $_;
            }
            if ((/^message_size:/) && (!$size{$entity}))
            {
                $_ =~ s/^[^\d]+(\d+).*/$1/;
                $size{$entity} = $1;
            }
        }
    }
    if ($do ne "stats")
    {
        foreach (@qlist)
        {
            print "$_\n";
            if (!$do)
            {
                print "\treturn_path: $sender{$_}\n";
                print "\tFrom: $from{$_}\n";
                print "\tTo: $to{$_}\n";
                print "\tSubject: $subject{$_}\n";
                print "\tDate: $date{$_}\n";
                print "\tSize: $size{$_} bytes\n";
            }
        }
    }
    return ($#qlist + 1);
}

sub List_Queue
{
    my $queue = shift;
    my $do    = shift;
    if (!$do) { $do = 0; }
    my ($count, %queue);
    if ($queue)
    {
        $count = &Get_Queue($queue, $do);
        $queue{$queue} = $count;
    } else
    {
        my @queues = ('active', 'bounce', 'corrupt', 'deferred');
        foreach (@queues)
        {
            if ($do ne "stats")
            {
                print "####################\n";
                print "##### \U$_\n";
                print "####################\n";
            }
            my $c = &Get_Queue($_, $do);
            $count += $c;
            $queue{$_} = $c;
        }
    }
    print "Total Messages: $count\n";
    foreach (keys %queue)
    {
        print "$_ Queue Messages: $queue{$_}\n";
    }
}

sub Process_Queue
{
    qx|postqueue -f|;
}

sub Display_Message
{
    my $mid   = shift;
    my @lines = qx|postcat -q $mid|;
    print @lines;
}

sub Delete_Message
{
    my $mid = shift;
    qx|postsuper -d $mid|;
    return;
}

sub Delete_From_Sender
{
    my $sender = shift;
    &Delete("From", $sender);
}

sub Delete_By_Subject
{
    my $subject = shift;
    &Delete("Subject", $subject);
}

sub Delete
{
    my $field  = shift;
    my $data   = shift;
    my @queues = ('active', 'bounce', 'corrupt', 'deferred');
    my $queue;
    foreach $queue (@queues)
    {
        my ($entity, @qlist);
        if ($queue ne "deferred")
        {
            opendir(QUEUE, "$pfdir/$queue");
            @qlist = grep(!/^\.$/, grep(!/^\.\.$/, readdir(QUEUE)));
            closedir(QUEUE);
        } else
        {
            my (@subdirs);
            opendir(QUEUE, "$pfdir/$queue");
            push(@subdirs, grep(!/^\.$/, grep(!/^\.\.$/, readdir(QUEUE))));
            closedir(QUEUE);
            foreach (@subdirs)
            {
                opendir(QUEUE, "$pfdir/$queue/$_");
                push(@qlist, grep(!/^\.$/, grep(!/^\.\.$/, readdir(QUEUE))));
                closedir(QUEUE);
            }
        }
        foreach $entity (@qlist)
        {
            my $test  = 0;
            my @lines = qx|postcat -q $entity|;
            foreach (@lines)
            {
                chomp($_);
                if (/^$field:/)
                {
                    if (/^$field:.*$data.*/)
                    {
                        &Delete_Message($entity);
                    }
                    last;
                }
            }
        }
    }
}

################################################################################
################################  DOCUMENTATION  ###############################
################################################################################

=head1 NAME

pfHandle - perl script to manage the postfix mail queue

=head1 SYNOPSIS

pfHandle [OPTION]

=head1 DESCRIPTION

This is a program that encorporates all the various built in tools of Postfix in a
single program and extends this functionality with some additional options.

=head1 OPTIONS

=over 8

=item -a						list the current active mail queue

=item -b						list the current bounce mail queue

=item -c						list the current corrupt mail queue

=item -d						list the current deferred mail queue

=item -D #						delete the email message

=item -f						try to reprocess queued messages now 

=item -F sender					delete all mail from this email address

=item -h						show this help message

=item -l						list all the current mail queues

=item -m #						display the email message

=item -N						display only the message IDs

=item -P [hold|incoming|active|deferred]	purge all messages from the mail queue

=item -s						display the mail queue statistics

=item -S subject					delete all mail with this in the subject

=item -v						version information

=head1 AUTHORS

=item Carl Thompson carl.thompson@rackspace.com

=cut

