Fix $regex<n> use-after-free. Bug 2915
[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 - 2022
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:E:',\%opt)) { &help; exit; }
59 if ($opt{h}) { &help; exit; }
60 if ($ARGV[0] || !($opt{f} || $opt{r} || $opt{s} || $opt{y} || $opt{o} || $opt{z} || $opt{x} || $opt{c}))
61    { &help; exit(1); }
62 if ($opt{a}) { $eargs = '-bp'; }
63 if ($opt{C} && -e $opt{C} && -f $opt{C} && -R $opt{C}) { $eargs .= ' -C '.$opt{C}; }
64 if ($opt{G}) { $eargs .= ' -qG'.$opt{G}; }
65 if ($opt{E}) { $exim = $opt{E}; }
66
67 # Read message queue output into hash
68 &collect();
69 # Identify which messages match selection criteria
70 &selection();
71 # Print matching data according to display option.
72 &display();
73 exit;
74
75
76 sub help() {
77         print <<'EOF'
78 Exim message queue display utility.
79
80         -h              This help message.
81         -C              Specify which exim.conf to use.
82         -E              Specify exim binary to use.
83
84 Selection criteria:
85         -f <regexp>     Match sender address sender (field is "< >" wrapped)
86         -r <regexp>     Match recipient address
87         -s <regexp>     Match against the size field from long output
88         -y <seconds>    Message younger than
89         -o <seconds>    Message older than
90         -z              Frozen messages only (exclude non-frozen)
91         -x              Non-frozen messages only (exclude frozen)
92         -G <queuename>  Match in given queue only
93
94 [ NB: for regexps, provided string sits in /<string>/ ]
95
96 Display options:
97         -c              Display match count
98         -l              Long Format [Default]
99         -i              Message IDs only
100         -b              Brief Format
101         -R              Reverse order
102         -a              All recipients (including delivered)
103 EOF
104 }
105
106 sub collect() {
107         open(QUEUE,"$exim $eargs |") or die("Error opening pipe: $!\n");
108         while(<QUEUE>) {
109                 chomp();
110                 my $line = $_;
111                 #Should be 1st line of record, if not error.
112                 if ($line =~ /^\s*(\w+)\s+((?:\d+(?:\.\d+)?[A-Z]?)?)\s*(\w{6}-\w{6}-\w{2})\s+(<.*?>)/) {
113                         my $msg = $3;
114                         $id{$msg}{age} = $1;
115                         $id{$msg}{size} = $2;
116                         $id{$msg}{from} = $4;
117                         $id{$msg}{birth} = &msg_utc($msg);
118                         $id{$msg}{ages} = time - $id{$msg}{birth};
119                         if ($line =~ /\*\*\* frozen \*\*\*$/) {
120                                 $id{$msg}{frozen} = 1;
121                         } else {
122                                 $id{$msg}{frozen} = 0;
123                         }
124                         while(<QUEUE> =~ /\s+(.*?\@.*)$/) {
125                                 push(@{$id{$msg}{rcpt}},$1);
126                         }
127                         # Increment message counter.
128                         $count++;
129                 } else {
130                         print STDERR "Line mismatch: $line\n"; exit 1;
131                 }
132         }
133         close(QUEUE) or die("Error closing pipe: $!\n");
134 }
135
136 sub selection() {
137         foreach my $msg (keys(%id)) {
138                 if ($opt{f}) {
139                         # Match sender address
140                         next unless ($id{$msg}{from} =~ /$opt{f}/i);
141                 }
142                 if ($opt{r}) {
143                         # Match any recipient address
144                         my $match = 0;
145                         foreach my $rcpt (@{$id{$msg}{rcpt}}) {
146                                 $match++ if ($rcpt =~ /$opt{r}/i);
147                         }
148                         next unless ($match);
149                 }
150                 if ($opt{s}) {
151                         # Match against the size string.
152                         next unless ($id{$msg}{size} =~ /$opt{s}/);
153                 }
154                 if ($opt{y}) {
155                         # Match younger than
156                         next unless ($id{$msg}{ages} < $opt{y});
157                 }
158                 if ($opt{o}) {
159                         # Match older than
160                         next unless ($id{$msg}{ages} > $opt{o});
161                 }
162                 if ($opt{z}) {
163                         # Exclude non frozen
164                         next unless ($id{$msg}{frozen});
165                 }
166                 if ($opt{x}) {
167                         # Exclude frozen
168                         next if ($id{$msg}{frozen});
169                 }
170                 # Here's what we do to select the record.
171                 # Should only get this far if the message passed all of
172                 # the active tests.
173                 $id{$msg}{d} = 1;
174                 # Increment match counter.
175                 $mcount++;
176         }
177 }
178
179 sub display() {
180         if ($opt{c}) {
181                 printf("%d matches out of %d messages\n",$mcount,$count);
182                 exit;
183         }
184         foreach my $msg (sort { $opt{R} ? $id{$b}{birth} <=> $id{$a}{birth} : $id{$a}{birth} <=> $id{$b}{birth} } keys(%id) ) {
185                 if (exists($id{$msg}{d})) {
186                         if ($opt{i}) {
187                                 # Just the msg ID
188                                 print $msg, "\n";
189                         } elsif ($opt{b}) {
190                                 # Brief format
191                                 printf("%s From: %s To: %s\n",$msg,$id{$msg}{from},join(';',@{$id{$msg}{rcpt}}))
192                         } else {
193                                 # Otherwise Long format attempted duplication of original format.
194                                 printf("%3s %5s %s %s%s\n",$id{$msg}{age},$id{$msg}{size},$msg,$id{$msg}{from},$id{$msg}{frozen} ? " *** frozen ***" : "");
195                                 foreach my $rcpt (@{$id{$msg}{rcpt}}) {
196                                         printf("          %s\n",$rcpt);
197                                 }
198                                 print "\n";
199                         }
200                 }
201         }
202 }
203
204 sub report() {
205         foreach my $msg (keys(%id)) {
206                 print "$id{$msg}{birth} $msg\tAge: $id{$msg}{age}\tSize: $id{$msg}{size}\tFrom: $id{$msg}{from}\tTo: " . join(" ",@{$id{$msg}{rcpt}}). "\n";
207         }
208 }
209
210 sub msg_utc() {
211         my $id = substr((pop @_), 0, 6);
212         my $s = 0;
213         my @c = split(//, $id);
214         while($#c >= 0) { $s = $s * $base + $tab62[ord(shift @c) - ord('0')] }
215         return $s;
216 }