#!/usr/bin/perl # shubes 11/02/06 - changed from #!/usr/local/bin/perl # qmqtool: Copyright 2003-2006 Jeremy Kister # Released under Perl's Artistic License. # Function: view and/or safely manipulate the contents of a qmail queue. # Author: Jeremy Kister (qmqtool-devel @t jeremykister.com) # :set tabstop=3 in vi use strict; use Getopt::Std; my (%tool,%opt); my $qmail = '/var/qmail'; $tool{'ps'} = '/bin/ps'; # full path to ps, or just ps if $PATH is sufficient $tool{'ps_arg'} = (`uname -s` =~ /SunOS/) ? '-ef' : 'aux'; # should statically code this if(-x '/usr/local/bin/grep'){ $tool{'grep'} = '/usr/local/bin/grep'; # GNU grep is 5 times faster than solaris grep }elsif(-x '/bin/grep'){ $tool{'grep'} = '/bin/grep'; } # must play with @ARGV directly because Getopt doesnt have a xx: (-x with or without an arg) my $n = 0; foreach my $arg (@ARGV){ foreach my $l ('d','e','u'){ if($arg eq "-${l}"){ splice @ARGV, $n, 1; # or Getopt will complain if($ARGV[$n] =~ /-[ofVQ]/){ # only valid flags to -{d,e,u} # this arg is for something else $opt{$l} = ''; # just set $opt{$l} }else{ # this arg is for me if($ARGV[$n] =~ /[^\d,]/ || $ARGV[$n] =~ /,,/){ die "invalid syntax to -${l}\n"; }else{ $opt{$l} = $ARGV[$n]; splice @ARGV, $n, 1; # Getopt, again } } last; # only one -d, -e, or -u per run } } $n++; } getopts('hlLRSTsQcrin:wv:Vf:o:E:U:B:', \%opt); if(defined($opt{h})){ syntax(); }elsif(defined($opt{l})){ listmsgs('all'); }elsif(defined($opt{L})){ listmsgs('local'); }elsif(defined($opt{R})){ listmsgs('remote'); }elsif(defined($opt{T})){ listtodomsgs('todo'); }elsif(defined($opt{B})){ check_qmailsend(); # make sure qmail-send is not runnning my $backup = $qmail . '/queue.backup' ; my @subdirs = getsplit(); if($opt{B} eq 'b'){ die "error: ${backup} already exists.\n" if(-d ${backup}); mkdir(${backup},0700) || die "cannot mkdir $backup: $!\n"; foreach my $dir ('mess','remote','local','info'){ mkdir("${backup}/${dir}",0700); foreach my $subdir (@subdirs){ mkdir("${backup}/${dir}/${subdir}",0700); opendir(D, "${qmail}/queue/${dir}/${subdir}/") || die "cannot open queue/$dir/$subdir/: $!\n"; foreach my $file (grep {!/^\./} readdir D){ open(F, "${qmail}/queue/${dir}/${subdir}/${file}") || die "cannot open queue/$dir/$subdir/$file: $!\n"; open(N, ">${backup}/${dir}/${subdir}/${file}") || die "cannot write to $backup/$dir/$subdir/$file: $!\n"; while(){ print N; } close N; close F; } closedir D; } } foreach my $dir ('todo','intd','bounce'){ mkdir("${backup}/${dir}",0700); opendir(D, "${qmail}/queue/${dir}/") || die "cannot open queue/$dir/: $!\n"; foreach my $file (grep {!/^\./} readdir D){ open(F, "${qmail}/queue/${dir}/${file}") || die "cannot open queue/$dir/$file: $!\n"; open(N, ">${backup}/${dir}/${file}") || die "cannot write to $backup/$dir/$file: $!\n"; while(){ print N; } close N; close F; } } }elsif($opt{B} eq 'r'){ my(%owner,%uid,%gid); $owner{'info'} = $owner{'local'} = $owner{'remote'} = $owner{'bounce'} = 'qmails'; $owner{'mess'} = $owner{'todo'} = $owner{'intd'} = 'qmailq'; foreach my $user ('qmailq','qmails'){ ($uid{$user},$gid{$user}) = (getpwnam($user))[2,3]; } my $split = @subdirs; # we assume backed up queue is split the same as queue foreach my $subdir (@subdirs){ opendir(D, "${backup}/mess/${subdir}/") || die "cannot open ${backup}/mess/$subdir/: $!\n"; foreach my $file (grep {!/^\./} readdir D){ my $new = $^T . '.' . rand(99); open(N, ">${qmail}/queue/pid/${new}") || die "cannot write to queue/pid/$new: $!\n"; close N; my $inode = (stat("${qmail}/queue/pid/${new}"))[1]; # pid and mess must be on same slice my $nsdir = ($inode % $split); rename("${qmail}/queue/pid/${new}","${qmail}/queue/mess/${nsdir}/${inode}") || die "cannot rename queue/pid/$new to queue/mess/$nsdir/$inode: $!\n"; chmod(0640,"${qmail}/queue/mess/${nsdir}/${inode}") || die "couldnt chmod queue/mess/$nsdir/$inode: $!\n"; chown($uid{$owner{'mess'}},$gid{$owner{'mess'}},"${qmail}/queue/mess/${nsdir}/${inode}") || die "couldnt chown queue/mess/$subdir/$inode: $!\n"; open(F, "${backup}/mess/${subdir}/${file}") || die "cannot open $backup/mess/$subdir/$file: $!\n"; open(N, ">>${qmail}/queue/mess/${nsdir}/${inode}") || die "cannot append to mess/$nsdir/$inode: $!\n"; while(){ print N; } close N; close F; foreach my $dir ('remote','local','info'){ if(-f "${backup}/${dir}/${subdir}/${file}"){ rename("${backup}/${dir}/${subdir}/${file}","${qmail}/queue/${dir}/${nsdir}/${inode}") || die "cannot rename $backup/$dir/$subdir/$file to queue/$dir/$nsdir/$inode: $!\n"; chmod(0600,"${qmail}/queue/${dir}/${nsdir}/${inode}") || die "could not chmod queue/$dir/$nsdir/$inode: $!\n"; chown($uid{$owner{$dir}},$gid{$owner{$dir}},"${qmail}/queue/${dir}/${nsdir}/${inode}") || die "could not chown queue/$dir/$nsdir/$inode: $!\n"; } } foreach my $dir ('todo','intd','bounce'){ if(-f "${backup}/${dir}/${file}"){ rename("${backup}/${dir}/${file}","${qmail}/queue/${dir}/${inode}") || die "could not rename $backup/$dir/$file to queue/$dir/$inode: $!\n"; chmod(0600,"${qmail}/queue/${dir}/${inode}") || die "could not chmod queue/$dir/$inode: $!\n"; chown($uid{$owner{$dir}},$gid{$owner{$dir}},"${qmail}/queue/${dir}/${inode}") || die "could not chown queue/$dir/$inode: $!\n"; } } } } unless($opt{Q}){ print "you must now start qmail-send: for a LWQ installation, run: svc -u /service/qmail-send\n"; } }else{ syntax(); } exit; }elsif(defined($opt{s})){ my %msgs; my @subdirs = getsplit(); foreach my $queue ('todo','remote','local'){ $msgs{$queue} = 0; foreach my $subdir (@subdirs){ my $dir = "${qmail}/queue/${queue}/"; $dir .= "${subdir}/" unless($queue eq 'todo'); # comment this line for big-todo opendir(S, $dir) || die "cannot open $dir: $!\n"; foreach my $file (grep {!/^\./} readdir S){ $msgs{$queue}++; } closedir S; last if($queue eq 'todo'); # comment this line for big-todo } } if(defined($opt{Q})){ print "$msgs{'local'}\n$msgs{'remote'}\n$msgs{'todo'}\n"; }else{ print "Messages in local queue: $msgs{'local'}\n", "Messages in remote queue: $msgs{'remote'}\n", "Messages in todo queue: $msgs{'todo'}\n"; } }elsif(defined($opt{v})){ if($opt{v} =~ /^\d+$/){ exit if($opt{v} == 0); # qmqtool -f 'unfound string' gives 0 my @subdirs = getsplit(); my $split = @subdirs; my $subdir = ($opt{v} % $split); if(open(F, "${qmail}/queue/mess/${subdir}/$opt{v}")){ my ($n,@msg); # we put the message into memory because it could be delivered while we're reading it while(){ unless(defined($opt{w})){ last if($n == 100); $n++; } # could chop $_ after a certain length push @msg, $_; } close F; if(@msg){ print "MESSAGE NUMBER $opt{v}:\n\n"; foreach(@msg){ print; } } if($n == 100){ print "----> qmqtool: remainder of message has been suppressed\n\n"; } }else{ print "Message number $opt{v} not found in queue\n"; } }else{ print "syntax error: string found where integer expected ($opt{v})\n"; } }elsif(exists($opt{'e'})){ if(defined($opt{f}) || defined($opt{o})){ my $msgs = build_msgs_list($opt{f},$opt{o}); if($msgs == 0){ print "0\n" if($opt{V}); }else{ changetime('expiring',$msgs); } }else{ changetime('expiring',$opt{e}); } exit; }elsif(exists($opt{u})){ if(defined($opt{f}) || defined($opt{o})){ my $msgs = build_msgs_list($opt{f},$opt{o}); if($msgs == 0){ print "0\n" if($opt{V}); }else{ changetime('resetting',$msgs); } }else{ changetime('resetting',$opt{u}); } exit; }elsif(exists($opt{d})){ if(defined($opt{f}) || defined($opt{o})){ my $msgs = build_msgs_list($opt{f},$opt{o}); if($msgs == 0){ print "0\n"; }else{ rm_files($msgs); } }else{ rm_files($opt{d}); } exit; }elsif(defined($opt{f}) && defined($opt{o})){ print build_msgs_list($opt{f},$opt{o}) . "\n"; }elsif(defined($opt{f})){ if($opt{Q}){ my $num = find_msgs_bystring($opt{f}); print "${num}\n"; }else{ print join(',', find_msgs_bystring($opt{f})) . "\n"; } }elsif(defined($opt{o})){ if($opt{Q}){ my $num = find_msgs_byage($opt{o}); print "${num}\n"; }else{ print join(',', find_msgs_byage($opt{o})) . "\n"; } }elsif(defined($opt{E})){ changetimebulk('expired',$opt{E}); }elsif(defined($opt{U})){ changetimebulk('reset',$opt{U}); }elsif(defined($opt{c}) || defined($opt{r})){ checkqueue($opt{r}); }elsif(defined($opt{i}) || defined($opt{S})){ my %hash; my @subdirs = getsplit(); foreach my $subdir (@subdirs){ opendir(D, "${qmail}/queue/mess/${subdir}/") || die "cannot open queue/mess/$subdir/: $!\n"; foreach my $file (grep {!/^\./} readdir D){ if(open(F, "${qmail}/queue/mess/${subdir}/${file}")){ my ($ip,$level); while(){ last if(/^$/); # we only want the header if(/^Received: from .+\(HELO.+\).*[^\d]+((\d{1,3}\.){3}\d{1,3})/){ # ehlo still shows HELO $ip = $1; if(defined($opt{n})){ $level++; if($level == $opt{n}){ if($opt{S}){ $hash{$ip} += getbytes($subdir,$file); }else{ push @{$hash{$ip}}, $file; } last; } }else{ if($opt{S}){ $hash{$ip} += getbytes($subdir,$file); }else{ push @{$hash{$ip}}, $file; } last; } } } close F; unless(defined($ip)){ # via qmail-inject or some such if($opt{S}){ $hash{'127.0.0.2'} += getbytes($subdir,$file); }else{ push(@{$hash{'127.0.0.2'}}, $file); } } } } closedir D; } if(scalar(%hash)){ if(defined($opt{Q})){ # just print the highest my $highest=0; if($opt{S}){ my $ip; foreach my $key (keys %hash){ if($hash{$key} > $highest){ $highest = $hash{$key}; $ip = $key; } } print "${ip}\n"; }else{ foreach my $key (keys %hash){ if(@{$hash{$key}} > $highest){ $highest = @{$hash{$key}}; } } print "${highest}\n"; } }else{ if($opt{S}){ for(map { $_->[0] } sort { $hash{$a->[0]} <=> $hash{$b->[0]} || $a->[1] <=> $b->[1] || $a->[2] <=> $b->[2] || $a->[3] <=> $b->[3] || $a->[4] <=> $b->[4] } map { [ $_, split /\./ ] } keys %hash){ print "$hash{$_} $_\n"; } }else{ for(map { $_->[0] } sort { @{$hash{$a->[0]}} <=> @{$hash{$b->[0]}} || $a->[1] <=> $b->[1] || $a->[2] <=> $b->[2] || $a->[3] <=> $b->[3] || $a->[4] <=> $b->[4] } map { [ $_, split /\./ ] } keys %hash){ my $num = @{$hash{$_}}; print "$num $_"; print ' ', join(',',@{$hash{$_}}) if($opt{V}); print "\n"; } } } }else{ print "0\n"; } }else{ syntax(); } sub getsplit { opendir(D, "${qmail}/queue/info/") || die "cannot open queue/info: $!\n"; my @subdirs = grep {!/^\./} readdir D; closedir D; return(@subdirs); } sub getbytes { my $subdir = shift; my $file = shift || die "getbytes syntax error\n"; my $bytes = (stat("${qmail}/queue/mess/${subdir}/${file}"))[7]; if(open(FILE, "${qmail}/queue/remote/${subdir}/${file}") || open(FILE, "${qmail}/queue/local/${subdir}/${file}") ){ my $i; foreach my $r (split /\0/, ){ $i++ if($r =~ /^T/); } close FILE; $bytes *= $i; } return($bytes); } sub check_qmailsend { die "cant fork ps\n" unless(-x $tool{ps}); # must make sure all queue processing agents are down (qmail-todo dies with qmail-send) if(open(P, "$tool{'ps'} $tool{'ps_arg'} |")){ while(

){ if(/^\s*qmails\s.+\sqmail-send/){ die "you must stop qmail-send before this program can continue\n", "for a LWQ installation, run: svc -d /service/qmail-send\n", "others may be able to run: kill -9 `$tool{'ps'} $tool{'ps_arg'} | awk '/qmails.*qmail-send/ { print \$2 }'`\n"; } } close P; }else{ die "cannot open process list (problem with ps?): $!\n"; } } sub checkqueue { my $mode = shift; my @subdirs = getsplit(); my $split = @subdirs; my $count = 2; my $hd = int($split/2); while($count <= $hd){ if(($split % $count) == 0){ die "queue layout is corrupt: the number of subdirectories in ${qmail}/queue/info is not prime.\n", "try 'make setup check' from the qmail-1.03 source directory.\n"; }else{ $count++; } } check_qmailsend(); #S3. +mess +intd -todo -info -local -remote -bounce #S4. +mess ?intd +todo ?info ?local ?remote -bounce (queued) #S5. +mess -intd -todo +info ?local ?remote ?bounce (preprocessed) # todo/ intd/ bounce/ (check in pid/ ?) remote/n mess/n local/n info/n my(%rogue,%remote,%mess); # find all messages in mess, make sure their inodes match their filename # and inode % conf-split = subdir # and has matching (intd, todo, or info); foreach my $subdir (@subdirs){ opendir(D, "${qmail}/queue/mess/$subdir/") || die "cannot open queue/mess/$subdir: $!\n"; foreach my $file (grep {!/^\./} readdir D){ my $inode = (stat("${qmail}/queue/mess/${subdir}/${file}"))[1]; if(($inode == $file) && (-s "${qmail}/queue/mess/${subdir}/${file}") && ($inode % $split == $subdir) && ((-s "${qmail}/queue/intd/${file}") || (-s "${qmail}/queue/todo/${file}") || (-s "${qmail}/queue/info/${subdir}/${file}")) ){ $mess{$file} = $subdir; }else{ $rogue{$file} = $subdir; } } closedir D; } # find messages in intd: make sure each has a matching mess/, and # never in bounce, and (if not in todo, no other match) opendir(D, "${qmail}/queue/intd/") || die "cannot open queue/intd/: $!\n"; foreach my $file (grep {!/^\./} readdir D){ if(-f "${qmail}/queue/bounce/$file"){ $rogue{$file} = -1; next; } if((exists($mess{$file})) && (-f "${qmail}/queue/todo/$file")){ #can be in info,local,remote (bounce already handled) }else{ # can not be in info, local, or remote (bounce already handled) foreach my $subdir (@subdirs){ foreach my $dir ('remote','local','info'){ if(-f "${qmail}/queue/${dir}/${subdir}/${file}"){ $rogue{$file} = $subdir; last; } } } } } closedir D; # find messages in todo: make sure each has a matching mess and no bounce opendir(D, "${qmail}/queue/todo/") || die "cannot open queue/todo/: $!\n"; foreach my $file (grep {!/^\./} readdir D){ unless(exists($rogue{$file})){ if(-f "${qmail}/queue/bounce/${file}"){ $rogue{$file} = -1; next; } unless(exists($mess{$file})){ $rogue{$file} = -1; next; } } } closedir D; # find messages in info, make sure each has a matching mess # if todo, no bounce # if no todo, no intd foreach my $subdir (@subdirs){ opendir(D, "${qmail}/queue/info/${subdir}/") || die "cannot open queue/info/$subdir/: $!\n"; foreach my $file (grep {!/^\./} readdir D){ unless(exists($mess{$file})){ $rogue{$file} = $subdir; next; } if(-f "${qmail}/queue/todo/${file}"){ if(-f "${qmail}/queue/bounce/${file}"){ $rogue{$file} = $subdir; next; } }else{ if(-f "${qmail}/queue/intd/${file}"){ $rogue{$file} = $subdir; next; } } } closedir D; } # find messages in remote and local: make sure each has a mess, # if it has a todo, cant have a bounce # if it has no todo, cant have an intd # cant have duplicate filenames in remote and local foreach my $dir ('remote','local'){ foreach my $subdir (@subdirs){ opendir(D, "${qmail}/queue/${dir}/${subdir}/") || die "cannot open queue/$dir/$subdir: $!\n"; foreach my $file (grep {!/^\./} readdir D){ if($dir eq 'remote'){ # will be built first $remote{$file} = 1; # same inode cant be used at the same time }else{ if(exists($remote{$file})){ $rogue{$file} = $subdir; next; } } if(exists($mess{$file})){ if(-f "${qmail}/queue/todo/${file}"){ if(-f "${qmail}/queue/bounce/${file}"){ $rogue{$file} = $subdir; next; } }else{ if(-f "${qmail}/queue/intd/${file}"){ $rogue{$file} = $subdir; next; } } }else{ $rogue{$file} = $subdir; next; } } closedir D; } } if(scalar(%rogue)){ while(my($file,$subdir) = each %rogue){ if($subdir == -1){ print "$file is rogue\n"; }else{ print "$subdir/$file is rogue\n"; } if($mode == 1){ print "removing $file shrapnel\n"; # always unlink non-split locations unlink("${qmail}/queue/todo/$file","${qmail}/queue/intd/$file","${qmail}/queue/bounce/$file"); unless($subdir == -1){ #unlink in all split locations unlink("${qmail}/queue/mess/$subdir/$file","${qmail}/queue/info/$subdir/$file", "${qmail}/queue/remote/$subdir/$file","${qmail}/queue/local/$subdir/$file"); } } } }else{ print "no rogue files found\n"; } print "you must now start qmail-send: for a LWQ installation, run: svc -u /service/qmail-send\n"; } sub rm_files { my $number = shift || die "rm_files syntax error\n"; exit if($number == 0); # qmqtool -f 'unfound string' gives 0 my @subdirs = getsplit(); my $split = @subdirs; my $restart; foreach my $file (split(/,/, $number)){ my %file; die "syntax error: $number\n" unless($file =~ /^\d+$/); print "removing message number $file from queue..\n" if($opt{V}); # - find messages in all possible locations, even "impossible" ones. $file{'todo'} = 1 if(unlink("${qmail}/queue/todo/$file")); $file{'intd'} = 1 if(unlink("${qmail}/queue/intd/$file")); $file{'bounce'} = 1 if(unlink("${qmail}/queue/bounce/$file")); my $subdir = ($file % $split); $file{"mess/$subdir"} = 1 if(unlink("${qmail}/queue/mess/$subdir/$file")); $file{"remote/$subdir"} = 1 if(unlink("${qmail}/queue/remote/$subdir/$file")); $file{"local/$subdir"} = 1 if(unlink("${qmail}/queue/local/$subdir/$file")); $file{"info/$subdir"} = 1 if(unlink("${qmail}/queue/info/$subdir/$file")); if(scalar(%file)){ if(defined($opt{V})){ while(my ($key,$value) = each %file){ print " removed ${key}/${file}\n"; $restart = 1; } } }else{ print " could not find message number $file\n"; } } if(defined($restart)){ print "you must now restart qmail-send: for a LWQ installation, run: svc -du /service/qmail-send\n"; } } sub changetime { my ($what,$number) = @_; die "changetime syntax error\n" unless($what && defined($number)); exit if($number == 0); # qmqtool -f 'unfound string' gives 0 my @subdirs = getsplit(); my $split = @subdirs; my $utime = $^T; if($what eq 'expiring'){ if(open(F, "${qmail}/control/queuelifetime")){ chomp(my $qlt=); close F; $utime -= $qlt; }else{ $utime -= 604800; } } my @files; foreach my $file (split(/,/, $number)){ die "syntax error: $number ($file)\n" unless($file =~ /^\d+$/); my ($found,$where); my $subdir = ($file % $split); if(-f "${qmail}/queue/todo/${file}"){ # message is in todo - can not expire print "can not expire messages in todo\n" unless($opt{Q}); }elsif(-f "${qmail}/queue/mess/${subdir}/${file}"){ print "$what message number $file\n" if(defined($opt{V})); push @files, "${qmail}/queue/info/${subdir}/${file}"; }else{ print "cannot find message number $file\n" unless($opt{Q}); } } utime $utime, $utime, @files if(@files); } sub changetimebulk { my ($what,$which) = @_; my $utime = $^T; if($what eq 'expired'){ if(open(F, "${qmail}/control/queuelifetime")){ chop(my $qlt=); close F; $utime -= $qlt; }else{ $utime -= 604800; } } my @queues; if($which eq 'A'){ @queues = qw /local remote/; }elsif($which eq 'R'){ push @queues, 'remote'; }elsif($which eq 'L'){ push @queues, 'local'; }else{ die "changetimebulk syntax error: use qmqtool -h for more information\n"; } my @subdirs = getsplit(); my $split = @subdirs; my (%num,$processed); foreach my $queue (@queues){ $num{$queue} = 0; my @files; foreach my $subdir (@subdirs){ opendir(S, "${qmail}/queue/${queue}/${subdir}/") || die "cannot open $qmail/queue/$queue/$subdir/: $!\n"; foreach my $file (grep {!/^\./} readdir S){ push @files, "${qmail}/queue/info/${subdir}/${file}"; $num{$queue}++; } closedir S; } utime $utime, $utime, @files if(@files); } if(defined($opt{V})){ foreach my $queue (@queues){ print "$what $num{$queue} files from the $queue queue\n"; } } } sub listtodomsgs { my @subdirs = getsplit(); my $split = @subdirs; opendir(T, "${qmail}/queue/todo/") || die "cannot open queue/todo: $!\n"; if(defined($opt{Q})){ my @msgs = (grep {!/^\./} readdir T); @msgs ? print join(',', @msgs) : print '0'; print "\n"; }else{ my $num = 0; foreach my $file (grep {!/^\./} readdir T){ $num++; my $subdir = ($file % $split); if(-f "${qmail}/queue/mess/${subdir}/${file}"){ msgprop('todo',${subdir},${file}); } } print "Messages in todo queue: ${num}\n"; } closedir T; } sub listmsgs { my $where = shift || die "listmsgs syntax error\n"; my (@queues,@msgs,%num); if($where eq 'all'){ @queues = qw /local remote/; }else{ push @queues, $where; } my @subdirs = getsplit(); foreach my $queue (@queues){ $num{$queue} = 0; foreach my $subdir (@subdirs){ opendir (S,"${qmail}/queue/${queue}/${subdir}/") || die "cannot open queue/$queue/$subdir/: $!\n"; foreach my $file (grep {!/^\./} readdir S){ if(defined($opt{Q})){ push @msgs, $file; }else{ $num{$queue}++; msgprop(${queue},${subdir},${file}); } } closedir S; } } if(defined($opt{Q})){ @msgs ? print join(',', @msgs) : print '0'; print "\n"; }else{ foreach my $queue (@queues){ print "Messages in $queue queue: $num{$queue}\n"; } } } sub msgprop { my ($queue,$subdir,$file) = @_; my $bytes = (stat("${qmail}/queue/mess/${subdir}/${file}"))[7] || warn "cannot stat mess/$subdir/$file: $!\n"; my $size; if($bytes > 1048576){ $size = sprintf("%.2f",($bytes/1048576)) . "MB ($bytes Bytes)"; }elsif($bytes > 1024){ $size = sprintf("%.2f",($bytes/1024)) . "KB ($bytes Bytes)"; }else{ $size = "$bytes Bytes"; } my (@recips,$es,$er); if($queue eq 'todo'){ if(open(I, "${qmail}/queue/intd/${file}")){ chop(my $data=); close I; foreach(split(/\0/, $data)){ if(/^F/){ $es=$_; substr($es,0,1) = ''; }elsif(/^T/){ push @recips, $_; } } }else{ warn "cannot open ${qmail}/queue/intd/${file}: $!\n"; } }else{ if(open(S, "${qmail}/queue/info/${subdir}/${file}")){ chop($es=); close S; substr($es,0,1) = ''; }else{ warn "cannot open ${qmail}/queue/info/$subdir/$file: $!\n"; } if(open(R, "${qmail}/queue/${queue}/${subdir}/${file}")){ chop(my $recipients=); close R; @recips = split(/\0/, $recipients); } } # find the longest recipient to make formatting prettier my $longest = 0; foreach my $recipient (@recips){ my $len = length($recipient); if($len > $longest){ $longest = $len; } } if(open(F, "${qmail}/queue/mess/${subdir}/${file}")){ print "${file} (${subdir}, ${queue})\n", " Envelope Sender: ${es}\n"; foreach (@recips){ my $l = substr($_,0,1,''); my $status = ($l eq 'D') ? '(Done)' : '(To Be Delivered)'; printf (" Envelope Recipient: %-${longest}s%s\n",$_,${status}); } my %fields = (From => 0, To => 0, Cc => 0, Subject => 0, Date => 0); while(){ chop; if(/^$/){ last; }elsif(/^([^\s:]+)\s*:\s?(.{1,50})(.*)/){ my $field = ucfirst(lc($1)); if(exists($fields{$field})){ my $value=$2; if(length($3) <= 3){ $value .= $3; }else{ $value .= '...'; } $fields{$field} = 1; print " ${field}: ${value}\n"; } } last unless ($fields{'From'} && $fields{'To'} && $fields{'Cc'} && $fields{'Subject'} && $fields{'Date'}); } close F; print "\n"; } } sub build_msgs_list { my($string,$age) = @_; die "build_msgs_list syntax error\n" unless(defined($string) || defined($age)); my $msgs; my @bystring = find_msgs_bystring($string) if(defined($string)); my @byage = find_msgs_byage($age) if(defined($age)); if(defined($string) && defined($age)){ # convert, and compair my %hash; foreach my $msg (@byage){ $hash{$msg} = 1; } foreach my $msg (@bystring){ $msgs .= "$msg," if(exists($hash{$msg})); } undef %hash; chop($msgs); }else{ $msgs = (defined($string)) ? join(',', @bystring) : join(',', @byage); } $msgs ? return($msgs) : return(0); } sub find_msgs_byage { my $age = shift; die "find_msgs_byage syntax error\n" unless($age =~ /^\d+(\.\d+)?$/); my @msgs; my @subdirs = getsplit(); foreach my $subdir (@subdirs){ opendir(S, "${qmail}/queue/info/${subdir}/") || die "cannot open queue/info/$subdir/: $!\n"; foreach my $file (grep {!/^\./} readdir S){ my $fileage = (stat("${qmail}/queue/info/${subdir}/${file}"))[10]; my $hours = (($^T - $fileage)/3600); push @msgs, $file if($hours > $age); } closedir S; } @msgs ? return(@msgs) : return(0); } sub find_msgs_bystring { my $string = shift || die "find_msgs_bystring syntax error\n"; my @subdirs = getsplit(); my $split = @subdirs; my $max = (5000/$split); # the point in which we use multiple greps my $queued = 0; my $thisdir = 0; # as long as 1 message is in queue, grep should work (so long as there's not too many) # if we have found 1 message, and # we go through one full directory that has less than 200 messages, we know we can just # grep like normal (assuming default split) foreach my $subdir (@subdirs){ opendir(S, "${qmail}/queue/mess/${subdir}/") || die "cannot open queue/mess/$subdir/: $!\n"; $thisdir=0; foreach my $file (grep {!/^\./} readdir S){ $queued++; $thisdir++; # we know messages are spread pretty evenly - # if there are 200 in 1 directory, we've got about 4600 msgs (assuming default split) last if(($thisdir > $max) || ($queued == 2000)); # we should use split grep # this will be innefective on abnormally large conf-splits } last if($thisdir > $max); # break out of outer loop - split grep last if(($queued > 0) && ($thisdir < $max)); # a single grep is prolly fine closedir S; } return(0) if($queued == 0); my @msgs; # we cant count on grep's exit code, because: # if a message is removed while grepping, exit code is 2 if(exists($tool{'grep'})){ my $last=0; $string =~ s#\|#\\|#g; @subdirs = qw/*/ unless(($thisdir > $max || $queued == 2000)); foreach my $subdir (@subdirs){ open(G, "$tool{'grep'} \"$string\" ${qmail}/queue/mess/${subdir}/* /dev/null 2>/dev/null |") || die "could not fork grep: $!\n"; while(){ # accomodate for: /var/qmail/queue/mess/N/X: # and: Binary file /var/qmail/queue/mess/N/X matches if(/\/(\d+)[:\s]/){ my $msg = $1; next if($last == $msg); # dont want dupes, GNU's grep -m is non portable :-/ push @msgs, $msg; $last=$msg; }else{ warn "$tool{'grep'} returned incompatilble output: $_\n"; } } close G; } }else{ foreach my $subdir (@subdirs){ opendir(S, "${qmail}/queue/mess/$subdir/") || die "cannot open queue/mess/$subdir/: $!\n"; foreach my $file (grep {!/^\./} readdir S){ if(open(F, "${qmail}/queue/mess/${subdir}/${file}")){ while(){ if(/$opt{f}/){ push @msgs, $file; last; } } close F; } } closedir S; } } @msgs ? return(@msgs) : return(0); } sub syntax { print <