#!/usr/bin/perl # # qmHandle # # Copyright(c) 1998 -> 2003 Michele Beltrame # # This program is distributed under the GNU GPL. # For more information have a look at http://www.gnu.org use strict; use warnings; use diagnostics; my $version = '1.3.0'; #################### USER CONFIGURATION BEGIN #################### ##### # Set this to your qmail queue directory (be sure to include the final slash!) my ($queue) = '/var/qmail/queue/'; ##### # If your system has got automated command to start/stop qmail, then # enter them here. # ### Be sure to uncomment only ONE of each variable declarations ### # For instance, this is if you have DJB's daemontools my ($stopqmail) = '/usr/local/bin/svc -d /service/qmail-deliver'; my ($startqmail) = '/usr/local/bin/svc -u /service/qmail-deliver'; # While this is if you have a Debian GNU/Linux with its qmail package #my ($stopqmail) = '/etc/init.d/qmail stop'; #my ($startqmail) = '/etc/init.d/qmail start'; # If you don't have scripts, leave $stopqmail blank (the process will # be hunted and killed by qmHandle): #my ($stopqmail) = ''; # However, you still need to launch qmail in a way or the other. So, # if you have a standard qmail 1.03 use this: #my ($startqmail) = "csh -cf '/var/qmail/rc &'"; # While, if you have a standard qmail < 1.03 you should use this: #my ($startqmail) = '/var/qmail/bin/qmail-start ./Mailbox splogger qmail &'; ##### # Enter here the system command which returns qmail PID. The following # should work on most Unixes: my ($pidcmd) = 'pidof qmail-send'; #################### USER CONFIGURATION END #################### # Print usage if no arguments if ($#ARGV == -1) { &Usage(); } # Get command line options my ($cmsg, $cstat, $cend) = ('', '', ''); my $summary = 0; my @actions = (); my $dactions = 0; foreach my $arg (@ARGV) { SWITCH: { $arg eq '-a' and do { push @actions, [\&SendMsgs]; last SWITCH; }; $arg eq '-l' and do { push @actions, [\&ListMsg, 'A']; last SWITCH; }; $arg eq '-L' and do { push @actions, [\&ListMsg, 'L']; last SWITCH; }; $arg eq '-R' and do { push @actions, [\&ListMsg, 'R']; last SWITCH; }; $arg eq '-N' and do { $summary = 1; last SWITCH; }; $arg eq '-c' and do { ($cmsg, $cstat, $cend) = ("\e[01;34m", "\e[01;31m", "\e[00m"); last SWITCH; }; $arg eq '-s' and do { push @actions, [\&Stats]; last SWITCH; }; $arg =~ /^-m(.+)/ and do { push @actions, [\&ViewMsg, $1]; last SWITCH; }; $arg =~ /^-f(.+)/ and do { push @actions, [\&DelMsgFromSender, $1]; $dactions++; last SWITCH; }; $arg =~ /^-d(.+)/ and do { push @actions, [\&DelMsg, $1]; $dactions++; last SWITCH; }; $arg =~ /^-S(.+)/ and do { push @actions, [\&DelMsgSubj, $1]; $dactions++; last SWITCH; }; #$arg =~ /^-H(.+)/ and do { push @actions, [\&DelMsgRegex, 'H', $1]; $dactions++; last SWITCH; }; #$arg =~ /^-X(.+)/ and do { push @actions, [\&DelMsgRegex, 'X', $1]; $dactions++; last SWITCH; }; $arg eq '-D' and do { push @actions, [\&DelAll]; $dactions++; last SWITCH; }; $arg eq '-V' and do { push @actions, [\&Version]; last SWITCH; }; Usage(); } } # Set "global" variables my ($norestart) = 0; # Create a hash of messages in queue and the type of recipients they have and whether they are bouncing. my (%msglist) = (); my (%type) = (); my ($dirno, $msgno); opendir(DIR,"${queue}mess"); my (@dirlist) = grep !/\./, readdir DIR; closedir DIR; foreach my $dir (@dirlist) { opendir (SUBDIR,"${queue}mess/$dir"); my (@files) = grep !/\./, map "$dir/$_", readdir SUBDIR; foreach my $file (@files) { open (MSG, "${queue}info/$file"); $msglist{$file}{'sender'} = ; substr($msglist{$file}{'sender'}, 0, 1) = ''; chop ($msglist{$file}{'sender'}); close (MSG); if (-e "${queue}local/$file") { $msglist{ $file }{ 'local' } = 'L'; $type{"$file"} = 'L'; } if (-e "${queue}remote/$file") { $msglist{ $file }{ 'remote' } = 'R'; if ($type{"$file"}) { $type{"$file"} = 'LR'; } else { $type{"$file"} = 'R'; } } ($dirno, $msgno) = split(/\//, $file); if (-e "${queue}bounce/$msgno") { $msglist{ $file }{ 'bounce' } = 'B'; } } closedir SUBDIR; } # In case of deletion actions, stop qmail if ($dactions) { stopQmail() or die "Could not stop qmail: $!"; } # Execute actions foreach my $action (@actions) { my $sub = shift @$action; # First element is the sub $sub->(@$action); # Others the arguments, if any } # In case of deletion actions, restart qmail if ($dactions) { startQmail() or die "Could not stop qmail: $!"; } # ##### SERVICE FUNCTIONS ##### # Stop qmail sub stopQmail { my ($qmpid) = qmailPid(); # If qmail is running, we stop it if ($qmpid != 0) { # If there is a system script available, we use it if ($stopqmail ne '') { print "Calling system script to terminate qmail...\n"; if (system($stopqmail) > 0) { return 0; } # sleep 1; while (qmailPid()){ sleep 1; } # Otherwise, we're killers! } else { print "Terminating qmail (pid $qmpid)... this might take a while if qmail is working.\n"; kill 'TERM', $qmpid; while (qmailPid()){ sleep 1; } } # If it isn't, we don't. We also set a flag which assures we don't # restart it later either (the user might not want this) } else { print "Qmail isn't running... no need to stop it.\n"; $norestart = 1; } return 1; } # Start qmail sub startQmail { my ($qmpid) = qmailPid(); # If qmail is running, why restart it? if ($qmpid != 0) { print "Qmail is already running again, so it won't be restarted.\n"; # If it wasn't running before qmHandle was launched, it's better leave is this way } elsif ($norestart == 1) { print "Qmail wasn't running when qmHandle was started, so it won't be restarted.\n"; # In any other case, we restart it } else { print "Restarting qmail... "; system($startqmail); print "done (hopefully).\n"; } return 1; } # Returns the subject of a message sub getSubject { my $msg = shift; my $msgsub; open (MSG, "${queue}mess/$msg") or die("cannot open message $msg"); while () { if ( $_ =~ /^Subject: /) { $msgsub = $'; chop ($msgsub); } elsif ( $_ eq "\n") { last; } } close (MSG); return $msgsub; } # ##### MAIN FUNCTIONS ##### # Tries to send all queued messages now # This is achieved by sending an ALRM signal to qmail-send sub SendMsgs { my ($qmpid) = qmailPid(); # If qmail is running, we force sending of messages if ($qmpid != 0) { kill 'ALRM', $qmpid; } else { print "Qmail isn't running, can't send messages!\n"; } } # Display message list # pass parameter of queue NOT to list! i.e. if you want remote only, pass L # if you want local, pass R if you want all pass anything else eg A sub ListMsg { my ($q) = shift; my (%ret, %date, %from, %subj, %to, %cc, %fsize); # if ($summary == 0) { # for my $msg(keys %msglist) { # } # } for my $msg (keys %msglist) { if ($type{$msg} =~ $q) { my ($dir, $rmsg) = split(/\//, $msg); print "$rmsg ($dir, $type{$msg})\n"; if ($summary == 0) { # Read return path # open (MSG, "${queue}info/$msg"); # $ret{$msg} = ; # substr($ret{$msg}, 0, 1) = ''; # chop ($ret{$msg}); # close (MSG); # Get message (file) size $fsize{$msg} = (stat("${queue}mess/$msg"))[7]; # Read something from message header (sender, receiver, subject, date) open (MSG, "${queue}mess/$msg"); while () { if ($_ =~ /^Date: /) { $date{$msg} = $'; chop ($date{$msg}); } elsif ( $_ =~ /^From: /) { $from{$msg} = $'; chop ($from{$msg}); } elsif ( $_ =~ /^Subject: /) { $subj{$msg} = $'; chop ($subj{$msg}); } elsif ( $_ =~ /^To: /) { $to{$msg} = $'; chop ($to{$msg}); } elsif ( $_ =~ /^Cc: /) { $cc{$msg} = $'; chop ($cc{$msg}); } elsif ( $_ eq "\n") { last; } } defined($msglist{$msg}{'sender'}) and print " ${cmsg}Return-path${cend}: $msglist{$msg}{'sender'}\n"; defined($from{$msg}) and print " ${cmsg}From${cend}: $from{$msg}\n"; defined($to{$msg}) and print " ${cmsg}To${cend}: $to{$msg}\n"; defined($cc{$msg}) and print " ${cmsg}Cc${cend}: $cc{$msg}\n"; defined($subj{$msg}) and print " ${cmsg}Subject${cend}: $subj{$msg}\n"; defined($date{$msg}) and print " ${cmsg}Date${cend}: $date{$msg}\n"; defined($fsize{$msg}) and print " ${cmsg}Size${cend}: $fsize{$msg} bytes\n\n"; } ## end if ($summary == 0) } ## end unless ($q eq $type{$msg}) } ## end foreach my $msg (@msglist) Stats(); } # View a message in the queue sub ViewMsg { my ($rmsg) = shift; unless ($rmsg =~ /^\d+$/) { print "$rmsg is not a valid message number!\n"; } else { # Search message my ($ok) = 0; for my $msg(keys %msglist) { if ($msg =~ /\/$rmsg$/) { $ok = 1; print "\n --------------\nMESSAGE NUMBER $rmsg \n --------------\n"; open (MSG, "${queue}mess/$msg"); while () { print $_; } close (MSG); last; } } # If the message isn't found, print a notice if ($ok == 0) { print "Message $rmsg not found in the queue!\n"; } } } # Delete a message in the queue sub DelMsg { my ($rmsg) = shift; unless ($rmsg =~ /^\d+$/) { print "$rmsg is not a valid message number!\n"; } else { # Search message my ($ok) = 0; for my $msg(keys %msglist) { if ($msg =~ /\/$rmsg$/) { $ok = 1; my @todelete = (); print "Deleting message $msg...\n"; if ($msglist{$msg}{'bounce'}) { push @todelete, "${queue}bounce/$rmsg"; } push @todelete, "${queue}mess/$msg"; push @todelete, "${queue}info/$msg"; if ($msglist{$msg}{"remote"}) { push @todelete, "${queue}remote/$msg"; } if ($msglist{$msg}{"local"}) { push @todelete, "${queue}local/$msg"; } unlink @todelete; last; } } # If the message isn't found, print a notice if ($ok == 0) { print "Message $rmsg not found in the queue!\n"; } } } sub DelMsgFromSender { my $badsender = shift; my $delnum = 0; my $msgno; my $dirno; my $groupdel = 0; my @todelete = (); print "Looking for messages from $badsender\n"; my ($ok) = 0; for my $msg (keys %msglist) { ($dirno, $msgno) = split(/\//, $msg); if ($msglist{$msg}{'sender'} eq $badsender) { $ok = 1; print "Deleting message: $msgno\n"; if ($msglist{$msg}{'bounce'}) { push @todelete, "${queue}bounce/$msgno"; } push @todelete, "${queue}mess/$msg"; push @todelete, "${queue}info/$msg"; if ($msglist{$msg}{'remote'}) { push @todelete, "${queue}remote/$msg"; } if ($msglist{$msg}{'local'}) { push @todelete, "${queue}local/$msg"; } if ($groupdel == 10) { unlink @todelete; $groupdel = 0; @todelete = (); } $delnum++; } } # If no messages are found, print a notice if ($ok == 0) { print "No messages from $badsender found in the queue!\n"; } else { unlink @todelete; print "$delnum messages deleted\n"; } } sub DelMsgSubj { my $subject = shift; my $msgsub; my $delnum = 0; my $msgno; my $dirno; my $groupdel = 0; my @todelete = (); print "Looking for messages with Subject: $subject\n"; # Search messages my ($ok) = 0; for my $msg (keys %msglist) { ($dirno, $msgno) = split(/\//, $msg); $msgsub = getSubject($msg); if ($msgsub and $msgsub =~ /$subject/) { $ok = 1; print "Deleting message: $msgno\n"; if ($msglist{$msg}{'bounce'}) { push @todelete, "${queue}bounce/$msgno"; } push @todelete, "${queue}mess/$msg"; push @todelete, "${queue}info/$msg"; if ($msglist{$msg}{'remote'}) { push @todelete, "${queue}remote/$msg"; } if ($msglist{$msg}{'local'}) { push @todelete, "${queue}local/$msg"; } if ($groupdel == 10) { unlink @todelete; $groupdel = 0; @todelete = (); } $delnum++; } } # If no messages are found, print a notice if ($ok == 0) { print "No messages matching Subject \"$subject\" found in the queue!\n"; } else { unlink @todelete; print "$delnum messages deleted\n"; } } # Delete all messages in the queue (thanks Kasper Holtze) sub DelAll { my ($rmsg) = shift; # Search messages my ($ok) = 0; my ($groupdel) = 1; my @todelete = (); my ($dirno, $msgno); for my $msg (keys %msglist) { $ok = 1; ($dirno, $msgno) = split(/\//, $msg); print "Deleting message: $msgno\n"; if ($msglist{$msg}{'bounce'}) { push @todelete, "${queue}bounce/$msgno"; } push @todelete, "${queue}mess/$msg"; push @todelete, "${queue}info/$msg"; if ($msglist{$msg}{'remote'}) { push @todelete, "${queue}remote/$msg"; } if ($msglist{$msg}{'local'}) { push @todelete, "${queue}local/$msg"; } $groupdel++; if ($groupdel == 10) { unlink @todelete; @todelete = (); $groupdel = 0; } } # If no messages are found, print a notice if ($ok == 0) { print "No messages found in the queue!\n"; } else { unlink @todelete; } } # Make statistics sub Stats { my ($total) = 0; my ($l) = 0; my ($r) = 0; foreach my $msg(keys %msglist) { $total++; if ($msglist{$msg}{'local'} ) { $l++; } if ($msglist{$msg}{'remote'} ) { $r++; } } print "${cstat}Total messages${cend}: $total\n"; print "${cstat}Messages with local recipients${cend}: $l\n"; print "${cstat}Messages with remote recipients${cend}: $r\n"; } # Retrieve pid of qmail-send sub qmailPid { my $qmpid = `$pidcmd`; chomp ($qmpid); $qmpid =~ s/\s*//g; if ($qmpid =~ /^\d+$/) { return $qmpid; } return 0; } # Print help sub Usage { print "qmHandle v$version\n"; print "Copyright 1998-2003 Michele Beltrame\n\n"; print "Available parameters:\n"; print " -a : try to send queued messages now (qmail must be running)\n"; print " -l : list message queues\n"; print " -L : list local message queue\n"; print " -R : list remote message queue\n"; print " -s : show some statistics\n"; print " -mN : display message number N\n"; print " -dN : delete message number N\n"; print " -fsender: delete message from sender\n"; print " -Stext : delete all messages that have/contain text as Subject\n"; print " -D : delete all messages in the queue (local and remote)\n"; print " -V : print program version\n"; print "\n"; print "Additional (optional) parameters:\n"; print " -c : display colored output\n"; print " -N : list message numbers only\n"; print " (to be used either with -l, -L or -R)\n"; print "\n"; print "You can view/delete multiple message i.e. -d123 -v456 -d567\n\n"; exit; } # Print help sub Version { print "qmHandle v$version\n"; }