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