9fee423e421c69581d2eaaf4abc073b614273fe4
[exim.git] / src / src / exiqgrep.src
1 #!PERL_COMMAND
2
3 # Utility for searching and displaying queue information.
4 # Written by Matt Hubbard 15 August 2002
5 #
6 # Copyright (c) The Exim Maintainers 2021
7
8 # Except when they appear in comments, the following placeholders in this
9 # source are replaced when it is turned into a runnable script:
10 #
11 # BIN_DIRECTORY
12 # PERL_COMMAND
13
14 # PROCESSED_FLAG
15
16
17 # Routine for extracting the UTC timestamp from message ID
18 # lifted from eximstat utility
19
20 # Version 1.2
21
22 use strict;
23 BEGIN { pop @INC if $INC[-1] eq '.' };
24
25 use Getopt::Std;
26 use File::Basename;
27
28 # Have this variable point to your exim binary.
29 my $exim = 'BIN_DIRECTORY/exim';
30 my $eargs = '-bpu';
31 my %id;
32 my %opt;
33 my $count = 0;
34 my $mcount = 0;
35 my @tab62 =
36   (0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,     # 0-9
37    0,10,11,12,13,14,15,16,17,18,19,20,  # A-K
38   21,22,23,24,25,26,27,28,29,30,31,32,  # L-W
39   33,34,35, 0, 0, 0, 0, 0,              # X-Z
40    0,36,37,38,39,40,41,42,43,44,45,46,  # a-k
41   47,48,49,50,51,52,53,54,55,56,57,58,  # l-w
42   59,60,61);                            # x-z
43
44 my $base;
45 if ($^O eq 'darwin') { # aka MacOS X
46   $base = 36;
47  } else {
48   $base = 62;
49 };
50
51 if ($ARGV[0] eq '--version') {
52     print basename($0) . ": $0\n",
53         "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
54         "perl(runtime): $]\n";
55         exit 0;
56 }
57
58 if (!getopts('hf:r:y:o:s:C:zxlibRcaG:',\%opt) { &help; exit;}
59 if ($ARGV[0]) { &help; exit;}
60 if ($opt{h}) { &help; exit;}
61 if ($opt{a}) { $eargs = '-bp'; }
62 if ($opt{C} && -e $opt{C} && -f $opt{C} && -R $opt{C}) { $eargs .= ' -C '.$opt{C}; }
63 if ($opt{G}) { $eargs .= ' -qG'.$opt{G}; }
64
65 # Read message queue output into hash
66 &collect();
67 # Identify which messages match selection criteria
68 &selection();
69 # Print matching data according to display option.
70 &display();
71 exit;
72
73
74 sub help() {
75         print <<'EOF'
76 Exim message queue display utility.
77
78         -h              This help message.
79         -C              Specify which exim.conf to use.
80
81 Selection criteria:
82         -f <regexp>     Match sender address sender (field is "< >" wrapped)
83         -r <regexp>     Match recipient address
84         -s <regexp>     Match against the size field from long output
85         -y <seconds>    Message younger than
86         -o <seconds>    Message older than
87         -z              Frozen messages only (exclude non-frozen)
88         -x              Non-frozen messages only (exclude frozen)
89         -G <queuename>  Match in given queue only
90
91 [ NB: for regexps, provided string sits in /<string>/ ]
92
93 Display options:
94         -c              Display match count
95         -l              Long Format [Default]
96         -i              Message IDs only
97         -b              Brief Format
98         -R              Reverse order
99         -a              All recipients (including delivered)
100 EOF
101 }
102
103 sub collect() {
104         open(QUEUE,"$exim $eargs |") or die("Error opening pipe: $!\n");
105         while(<QUEUE>) {
106                 chomp();
107                 my $line = $_;
108                 #Should be 1st line of record, if not error.
109                 if ($line =~ /^\s*(\w+)\s+((?:\d+(?:\.\d+)?[A-Z]?)?)\s*(\w{6}-\w{6}-\w{2})\s+(<.*?>)/) {
110                         my $msg = $3;
111                         $id{$msg}{age} = $1;
112                         $id{$msg}{size} = $2;
113                         $id{$msg}{from} = $4;
114                         $id{$msg}{birth} = &msg_utc($msg);
115                         $id{$msg}{ages} = time - $id{$msg}{birth};
116                         if ($line =~ /\*\*\* frozen \*\*\*$/) {
117                                 $id{$msg}{frozen} = 1;
118                         } else {
119                                 $id{$msg}{frozen} = 0;
120                         }
121                         while(<QUEUE> =~ /\s+(.*?\@.*)$/) {
122                                 push(@{$id{$msg}{rcpt}},$1);
123                         }
124                         # Increment message counter.
125                         $count++;
126                 } else {
127                         print STDERR "Line mismatch: $line\n"; exit 1;
128                 }
129         }
130         close(QUEUE) or die("Error closing pipe: $!\n");
131 }
132
133 sub selection() {
134         foreach my $msg (keys(%id)) {
135                 if ($opt{f}) {
136                         # Match sender address
137                         next unless ($id{$msg}{from} =~ /$opt{f}/i);
138                 }
139                 if ($opt{r}) {
140                         # Match any recipient address
141                         my $match = 0;
142                         foreach my $rcpt (@{$id{$msg}{rcpt}}) {
143                                 $match++ if ($rcpt =~ /$opt{r}/i);
144                         }
145                         next unless ($match);
146                 }
147                 if ($opt{s}) {
148                         # Match against the size string.
149                         next unless ($id{$msg}{size} =~ /$opt{s}/);
150                 }
151                 if ($opt{y}) {
152                         # Match younger than
153                         next unless ($id{$msg}{ages} < $opt{y});
154                 }
155                 if ($opt{o}) {
156                         # Match older than
157                         next unless ($id{$msg}{ages} > $opt{o});
158                 }
159                 if ($opt{z}) {
160                         # Exclude non frozen
161                         next unless ($id{$msg}{frozen});
162                 }
163                 if ($opt{x}) {
164                         # Exclude frozen
165                         next if ($id{$msg}{frozen});
166                 }
167                 # Here's what we do to select the record.
168                 # Should only get this far if the message passed all of
169                 # the active tests.
170                 $id{$msg}{d} = 1;
171                 # Increment match counter.
172                 $mcount++;
173         }
174 }
175
176 sub display() {
177         if ($opt{c}) {
178                 printf("%d matches out of %d messages\n",$mcount,$count);
179                 exit;
180         }
181         foreach my $msg (sort { $opt{R} ? $id{$b}{birth} <=> $id{$a}{birth} : $id{$a}{birth} <=> $id{$b}{birth} } keys(%id) ) {
182                 if (exists($id{$msg}{d})) {
183                         if ($opt{i}) {
184                                 # Just the msg ID
185                                 print $msg, "\n";
186                         } elsif ($opt{b}) {
187                                 # Brief format
188                                 printf("%s From: %s To: %s\n",$msg,$id{$msg}{from},join(';',@{$id{$msg}{rcpt}}))
189                         } else {
190                                 # Otherwise Long format attempted duplication of original format.
191                                 printf("%3s %5s %s %s%s\n",$id{$msg}{age},$id{$msg}{size},$msg,$id{$msg}{from},$id{$msg}{frozen} ? " *** frozen ***" : "");
192                                 foreach my $rcpt (@{$id{$msg}{rcpt}}) {
193                                         printf("          %s\n",$rcpt);
194                                 }
195                                 print "\n";
196                         }
197                 }
198         }
199 }
200
201 sub report() {
202         foreach my $msg (keys(%id)) {
203                 print "$id{$msg}{birth} $msg\tAge: $id{$msg}{age}\tSize: $id{$msg}{size}\tFrom: $id{$msg}{from}\tTo: " . join(" ",@{$id{$msg}{rcpt}}). "\n";
204         }
205 }
206
207 sub msg_utc() {
208         my $id = substr((pop @_), 0, 6);
209         my $s = 0;
210         my @c = split(//, $id);
211         while($#c >= 0) { $s = $s * $base + $tab62[ord(shift @c) - ord('0')] }
212         return $s;
213 }