Tweak the ACL variable name code to require either a digit or an
[exim.git] / src / src / exipick.src
CommitLineData
059ec3d9 1#!PERL_COMMAND
e22ca4ac 2# $Cambridge: exim/src/src/exipick.src,v 1.12 2006/07/21 16:48:43 jetmore Exp $
059ec3d9
PH
3
4# This variable should be set by the building process to Exim's spool directory.
5my $spool = 'SPOOL_DIRECTORY';
6
e22ca4ac
JJ
7# use 'exipick --help' to view documentation for this program.
8# Documentation also viewable online at
9# http://www.exim.org/eximwiki/ToolExipickManPage
10
059ec3d9
PH
11use strict;
12use Getopt::Long;
13
14my($p_name) = $0 =~ m|/?([^/]+)$|;
e22ca4ac 15my $p_version = "20060721.2";
059ec3d9
PH
16my $p_usage = "Usage: $p_name [--help|--version] (see --help for details)";
17my $p_cp = <<EOM;
11121d3d 18 Copyright (c) 2003-2006 John Jetmore <jj33\@pobox.com>
059ec3d9
PH
19
20 This program is free software; you can redistribute it and/or modify
21 it under the terms of the GNU General Public License as published by
22 the Free Software Foundation; either version 2 of the License, or
23 (at your option) any later version.
24
25 This program is distributed in the hope that it will be useful,
26 but WITHOUT ANY WARRANTY; without even the implied warranty of
27 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 GNU General Public License for more details.
29
30 You should have received a copy of the GNU General Public License
31 along with this program; if not, write to the Free Software
e22ca4ac 32 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
059ec3d9
PH
33EOM
34ext_usage(); # before we do anything else, check for --help
35
bf759a8b
PH
36$| = 1; # unbuffer STDOUT
37
059ec3d9
PH
38Getopt::Long::Configure("bundling_override");
39GetOptions(
bf759a8b
PH
40 'spool:s' => \$G::spool, # exim spool dir
41 'bp' => \$G::mailq_bp, # List the queue (noop - default)
42 'bpa' => \$G::mailq_bpa, # ... with generated address as well
43 'bpc' => \$G::mailq_bpc, # ... but just show a count of messages
44 'bpr' => \$G::mailq_bpr, # ... do not sort
45 'bpra' => \$G::mailq_bpra, # ... with generated addresses, unsorted
46 'bpru' => \$G::mailq_bpru, # ... only undelivered addresses, unsorted
47 'bpu' => \$G::mailq_bpu, # ... only undelivered addresses
48 'and' => \$G::and, # 'and' the criteria (default)
49 'or' => \$G::or, # 'or' the criteria
50 'f:s' => \$G::qgrep_f, # from regexp
51 'r:s' => \$G::qgrep_r, # recipient regexp
5f970846 52 's:s' => \$G::qgrep_s, # match against size field
bf759a8b
PH
53 'y:s' => \$G::qgrep_y, # message younger than (secs)
54 'o:s' => \$G::qgrep_o, # message older than (secs)
55 'z' => \$G::qgrep_z, # frozen only
56 'x' => \$G::qgrep_x, # non-frozen only
57 'c' => \$G::qgrep_c, # display match count
58 'l' => \$G::qgrep_l, # long format (default)
59 'i' => \$G::qgrep_i, # message ids only
60 'b' => \$G::qgrep_b, # brief format
e22ca4ac
JJ
61 'size' => \$G::size_only, # sum the size of the matching msgs
62 'not' => \$G::negate, # flip every test
63 'R|reverse' => \$G::reverse, # reverse output (-R is qgrep option)
64 'sort:s' => \@G::sort, # allow you to choose variables to sort by
9cf6b11a
JJ
65 'freeze:s' => \$G::freeze, # freeze data in this file
66 'thaw:s' => \$G::thaw, # thaw data from this file
67 'unsorted' => \$G::unsorted, # unsorted, regardless of output format
e22ca4ac 68 'random' => \$G::random, # (poorly) randomize evaluation order
bf759a8b
PH
69 'flatq' => \$G::flatq, # brief format
70 'caseful' => \$G::caseful, # in '=' criteria, respect case
71 'caseless' => \$G::caseless, # ...ignore case (default)
72 'show-vars:s' => \$G::show_vars, # display the contents of these vars
73 'show-rules' => \$G::show_rules, # display compiled match rules
74 'show-tests' => \$G::show_tests # display tests as applied to each message
059ec3d9
PH
75) || exit(1);
76
9cf6b11a
JJ
77# if both freeze and thaw specified, only thaw as it is less desctructive
78$G::freeze = undef if ($G::freeze && $G::thaw);
79freeze_start() if ($G::freeze);
80thaw_start() if ($G::thaw);
81
e22ca4ac
JJ
82# massage sort options (make '$var,Var:' be 'var','var')
83for (my $i = scalar(@G::sort)-1; $i >= 0; $i--) {
84 $G::sort[$i] = lc($G::sort[$i]);
85 $G::sort[$i] =~ s/[\$:\s]//g;
86 if ((my @vars = split(/,/, $G::sort[$i])) > 1) {
87 $G::sort[$i] = $vars[0]; shift(@vars); # replace current slot w/ first var
88 splice(@G::sort, $i+1, 0, @vars); # add other vars after current pos
89 }
90}
91push(@G::sort, "message_exim_id") if (@G::sort);
92die "empty value provided to --sort not allowed, exiting\n"
93 if (grep /^\s*$/, @G::sort);
94
95# massage the qgrep options into standard criteria
5f970846
PH
96push(@ARGV, "\$sender_address =~ /$G::qgrep_f/") if ($G::qgrep_f);
97push(@ARGV, "\$recipients =~ /$G::qgrep_r/") if ($G::qgrep_r);
98push(@ARGV, "\$shown_message_size eq $G::qgrep_s") if ($G::qgrep_s);
99push(@ARGV, "\$message_age < $G::qgrep_y") if ($G::qgrep_y);
100push(@ARGV, "\$message_age > $G::qgrep_o") if ($G::qgrep_o);
101push(@ARGV, "\$deliver_freeze") if ($G::qgrep_z);
102push(@ARGV, "!\$deliver_freeze") if ($G::qgrep_x);
e22ca4ac 103
bf759a8b
PH
104$G::mailq_bp = $G::mailq_bp; # shut up -w
105$G::and = $G::and; # shut up -w
b3f69ca8 106$G::msg_ids = {}; # short circuit when crit is only MID
bf759a8b 107$G::caseless = $G::caseful ? 0 : 1; # nocase by default, case if both
b3f69ca8 108@G::recipients_crit = (); # holds per-recip criteria
bf759a8b 109$spool = $G::spool if ($G::spool);
9cf6b11a
JJ
110my $count_only = 1 if ($G::mailq_bpc || $G::qgrep_c);
111my $unsorted = 1 if ($G::mailq_bpr || $G::mailq_bpra ||
112 $G::mailq_bpru || $G::unsorted);
113my $msg = $G::thaw ? thaw_message_list()
e22ca4ac
JJ
114 : get_all_msgs($spool, $unsorted,
115 $G::reverse, $G::random);
9cf6b11a 116die "Problem accessing thaw file\n" if ($G::thaw && !$msg);
bf759a8b
PH
117my $crit = process_criteria(\@ARGV);
118my $e = Exim::SpoolFile->new();
b3f69ca8
JJ
119my $tcount = 0 if ($count_only); # holds count of all messages
120my $mcount = 0 if ($count_only); # holds count of matching messages
e22ca4ac 121my $total_size = 0 if ($G::size_only);
bf759a8b
PH
122$e->set_undelivered_only(1) if ($G::mailq_bpru || $G::mailq_bpu);
123$e->set_show_generated(1) if ($G::mailq_bpra || $G::mailq_bpa);
124$e->output_long() if ($G::qgrep_l);
125$e->output_idonly() if ($G::qgrep_i);
126$e->output_brief() if ($G::qgrep_b);
127$e->output_flatq() if ($G::flatq);
059ec3d9 128$e->set_show_vars($G::show_vars) if ($G::show_vars);
bf759a8b 129$e->set_spool($spool);
059ec3d9
PH
130
131MSG:
132foreach my $m (@$msg) {
af66f652
PH
133 next if (scalar(keys(%$G::msg_ids)) && !$G::or
134 && !$G::msg_ids->{$m->{message}});
9cf6b11a
JJ
135 if ($G::thaw) {
136 my $data = thaw_data();
137 if (!$e->restore_state($data)) {
138 warn "Couldn't thaw $data->{_message}: ".$e->error()."\n";
139 next MSG;
140 }
141 } else {
142 if (!$e->parse_message($m->{message}, $m->{path})) {
143 warn "Couldn't parse $m->{message}: ".$e->error()."\n";
144 next MSG;
145 }
059ec3d9
PH
146 }
147 $tcount++;
148 my $match = 0;
bf759a8b
PH
149 my @local_crit = ();
150 foreach my $c (@G::recipients_crit) { # handle each_recip* vars
151 foreach my $addr (split(/, /, $e->get_var($c->{var}))) {
152 my %t = ( 'cmp' => $c->{cmp}, 'var' => $c->{var} );
153 $t{cmp} =~ s/"?\$var"?/'$addr'/;
154 push(@local_crit, \%t);
155 }
156 }
ee744174 157 if ($G::show_tests) { print $e->get_var('message_exim_id'), "\n"; }
059ec3d9 158 CRITERIA:
bf759a8b 159 foreach my $c (@$crit, @local_crit) {
059ec3d9
PH
160 my $var = $e->get_var($c->{var});
161 my $ret = eval($c->{cmp});
bf759a8b
PH
162 if ($G::show_tests) {
163 printf " %25s = '%s'\n %25s => $ret\n",$c->{var},$var,$c->{cmp},$ret;
164 }
059ec3d9
PH
165 if ($@) {
166 print STDERR "Error in eval '$c->{cmp}': $@\n";
11121d3d 167 next MSG;
059ec3d9
PH
168 } elsif ($ret) {
169 $match = 1;
11121d3d
JJ
170 if ($G::or) { last CRITERIA; }
171 else { next CRITERIA; }
059ec3d9 172 } else { # no match
11121d3d
JJ
173 if ($G::or) { next CRITERIA; }
174 else { next MSG; }
059ec3d9
PH
175 }
176 }
b3f69ca8
JJ
177
178 # skip this message if any criteria were supplied and it didn't match
11121d3d 179 next MSG if ((scalar(@$crit) || scalar(@local_crit)) && !$match);
059ec3d9 180
e22ca4ac 181 if ($count_only || $G::size_only) {
059ec3d9 182 $mcount++;
e22ca4ac 183 $total_size += $e->get_var('message_size');
059ec3d9 184 } else {
e22ca4ac
JJ
185 if (@G::sort) {
186 # if we are defining criteria to sort on, save the message here. If
187 # we don't save here and do the sort later, we have a chicken/egg
188 # problem
189 push(@G::to_print, { vars => {}, output => "" });
190 foreach my $var (@G::sort) {
191 # save any values we want to sort on. I don't like doing the internal
192 # struct access here, but calling get_var a bunch can be _slow_ =(
193 $G::sort_type{$var} ||= '<=>';
194 $G::to_print[-1]{vars}{$var} = $e->{_vars}{$var};
195 $G::sort_type{$var} = 'cmp' if ($G::to_print[-1]{vars}{$var} =~ /\D/);
196 }
197 $G::to_print[-1]{output} = $e->format_message();
198 } else {
199 print $e->format_message();
200 }
059ec3d9 201 }
9cf6b11a
JJ
202
203 if ($G::freeze) {
204 freeze_data($e->get_state());
205 push(@G::frozen_msgs, $m);
206 }
059ec3d9
PH
207}
208
e22ca4ac
JJ
209if (@G::to_print) {
210 msg_sort(\@G::to_print, \@G::sort, $G::reverse);
211 foreach my $msg (@G::to_print) {
212 print $msg->{output};
213 }
214}
215
216if ($G::qgrep_c) {
217 print "$mcount matches out of $tcount messages" .
218 ($G::size_only ? " ($total_size)" : "") . "\n";
219} elsif ($G::mailq_bpc) {
220 print "$mcount" . ($G::size_only ? " ($total_size)" : "") . "\n";
221} elsif ($G::size_only) {
222 print "$total_size\n";
059ec3d9
PH
223}
224
9cf6b11a
JJ
225if ($G::freeze) {
226 freeze_message_list(\@G::frozen_msgs);
227 freeze_end();
228} elsif ($G::thaw) {
229 thaw_end();
230}
231
059ec3d9
PH
232exit;
233
e22ca4ac
JJ
234# sender_address_domain,shown_message_size
235sub msg_sort {
236 my $msgs = shift;
237 my $vars = shift;
238 my $reverse = shift;
239
240 my @pieces = ();
241 foreach my $v (@G::sort) {
242 push(@pieces, "\$a->{vars}{\"$v\"} $G::sort_type{$v} \$b->{vars}{\"$v\"}");
243 }
244 my $sort_str = join(" || ", @pieces);
245
246 @$msgs = sort { eval $sort_str } (@$msgs);
247 @$msgs = reverse(@$msgs) if ($reverse);
248}
249
250sub try_load {
251 my $mod = shift;
252
253 eval("use $mod");
254 return $@ ? 0 : 1;
255}
256
9cf6b11a
JJ
257# FREEZE FILE FORMAT:
258# message_data_bytes
259# message_data
260# <...>
261# EOM
262# message_list
263# message_list_bytes <- 10 bytes, zero-packed, plus \n
264
265sub freeze_start {
266 eval("use Storable");
267 die "Storable module not found: $@\n" if ($@);
268 open(O, ">$G::freeze") || die "Can't open freeze file $G::freeze: $!\n";
269 $G::freeze_handle = \*O;
270}
271
272sub freeze_end {
273 close($G::freeze_handle);
274}
275
276sub thaw_start {
277 eval("use Storable");
278 die "Storable module not found: $@\n" if ($@);
279 open(I, "<$G::thaw") || die "Can't open freeze file $G::thaw: $!\n";
280 $G::freeze_handle = \*I;
281}
282
283sub thaw_end {
284 close($G::freeze_handle);
285}
286
287sub freeze_data {
288 my $h = Storable::freeze($_[0]);
289 print $G::freeze_handle length($h)+1, "\n$h\n";
290}
291
292sub freeze_message_list {
293 my $h = Storable::freeze($_[0]);
294 my $l = length($h) + 1;
295 printf $G::freeze_handle "EOM\n$l\n$h\n%010d\n", $l+11+length($l)+1;
296}
297
298sub thaw_message_list {
299 my $orig_pos = tell($G::freeze_handle);
300 seek($G::freeze_handle, -11, 2);
301 chomp(my $bytes = <$G::freeze_handle>);
302 seek($G::freeze_handle, $bytes * -1, 2);
303 my $obj = thaw_data();
304 seek($G::freeze_handle, 0, $orig_pos);
305 return($obj);
306}
307
308sub thaw_data {
309 my $obj;
310 chomp(my $bytes = <$G::freeze_handle>);
311 return(undef) if (!$bytes || $bytes eq 'EOM');
312 my $read = read(I, $obj, $bytes);
313 die "Format error in thaw file (expected $bytes bytes, got $read)\n"
314 if ($bytes != $read);
315 chomp($obj);
316 return(Storable::thaw($obj));
317}
318
059ec3d9
PH
319sub process_criteria {
320 my $a = shift;
321 my @c = ();
322 my $e = 0;
323
324 foreach (@$a) {
e22ca4ac 325 foreach my $t ('@') { s/$t/\\$t/g; }
059ec3d9
PH
326 if (/^(.*?)\s+(<=|>=|==|!=|<|>)\s+(.*)$/) {
327 #print STDERR "found as integer\n";
328 my $v = $1; my $o = $2; my $n = $3;
329 if ($n =~ /^([\d\.]+)M$/) { $n = $1 * 1024 * 1024; }
330 elsif ($n =~ /^([\d\.]+)K$/) { $n = $1 * 1024; }
331 elsif ($n =~ /^([\d\.]+)B?$/) { $n = $1; }
332 elsif ($n =~ /^([\d\.]+)d$/) { $n = $1 * 60 * 60 * 24; }
333 elsif ($n =~ /^([\d\.]+)h$/) { $n = $1 * 60 * 60; }
334 elsif ($n =~ /^([\d\.]+)m$/) { $n = $1 * 60; }
335 elsif ($n =~ /^([\d\.]+)s?$/) { $n = $1; }
336 else {
337 print STDERR "Expression $_ did not parse: numeric comparison with ",
338 "non-number\n";
339 $e = 1;
340 next;
341 }
9cf6b11a
JJ
342 #push(@c, { var => lc($v), cmp => "(\$var $o $n) ? 1 : 0" });
343 push(@c, { var => lc($v), cmp => "(\$var $o $n)" });
059ec3d9
PH
344 } elsif (/^(.*?)\s+(=~|!~)\s+(.*)$/) {
345 #print STDERR "found as string regexp\n";
9cf6b11a 346 push(@c, { var => lc($1), cmp => "(\"\$var\" $2 $3)" });
059ec3d9
PH
347 } elsif (/^(.*?)\s+=\s+(.*)$/) {
348 #print STDERR "found as bare string regexp\n";
af66f652 349 my $case = $G::caseful ? '' : 'i';
9cf6b11a 350 push(@c, { var => lc($1), cmp => "(\"\$var\" =~ /$2/$case)" });
e22ca4ac
JJ
351 # quote special characters in perl text string
352 #foreach my $t ('@') { $c[-1]{cmp} =~ s/$t/\\$t/g; }
059ec3d9
PH
353 } elsif (/^(.*?)\s+(eq|ne)\s+(.*)$/) {
354 #print STDERR "found as string cmp\n";
af66f652 355 my $var = lc($1); my $op = $2; my $val = $3;
5f970846 356 $val =~ s|^(['"])(.*)\1$|$2|;
9cf6b11a 357 push(@c, { var => $var, cmp => "(\"\$var\" $op \"$val\")" });
ee744174 358 if (($var eq 'message_id' || $var eq 'message_exim_id') && $op eq "eq") {
af66f652
PH
359 #print STDERR "short circuit @c[-1]->{cmp} $val\n";
360 $G::msg_ids->{$val} = 1;
361 }
e22ca4ac 362 #foreach my $t ('@') { $c[-1]{cmp} =~ s/$t/\\$t/g; }
9cf6b11a 363 } elsif (/^(\S+)$/) {
059ec3d9 364 #print STDERR "found as boolean\n";
9cf6b11a 365 push(@c, { var => lc($1), cmp => "(\$var)" });
059ec3d9
PH
366 } else {
367 print STDERR "Expression $_ did not parse\n";
368 $e = 1;
369 }
9cf6b11a 370 # assign the results of the cmp test here (handle "!" negation)
e22ca4ac 371 # also handle global --not negation
9cf6b11a 372 if ($c[-1]{var} =~ s|^!||) {
e22ca4ac 373 $c[-1]{cmp} .= $G::negate ? " ? 1 : 0" : " ? 0 : 1";
9cf6b11a 374 } else {
e22ca4ac 375 $c[-1]{cmp} .= $G::negate ? " ? 0 : 1" : " ? 1 : 0";
9cf6b11a 376 }
bf759a8b
PH
377 # support the each_* psuedo variables. Steal the criteria off of the
378 # queue for special processing later
379 if ($c[-1]{var} =~ /^each_(recipients(_(un)?del)?)$/) {
380 my $var = $1;
381 push(@G::recipients_crit,pop(@c));
382 $G::recipients_crit[-1]{var} = $var; # remove each_ from the variable
383 }
059ec3d9
PH
384 }
385
386 exit(1) if ($e);
387
388 if ($G::show_rules) { foreach (@c) { print "$_->{var}\t$_->{cmp}\n"; } }
389
390 return(\@c);
391}
392
393sub get_all_msgs {
394 my $d = shift() . '/input';
e22ca4ac
JJ
395 my $u = shift; # don't sort
396 my $r = shift; # right before returning, reverse order
397 my $o = shift; # if true, randomize list order before returning
059ec3d9
PH
398 my @m = ();
399
400 opendir(D, "$d") || die "Couldn't opendir $d: $!\n";
401 foreach my $e (grep !/^\./, readdir(D)) {
402 if ($e =~ /^[a-zA-Z0-9]$/) {
403 opendir(DD, "$d/$e") || next;
404 foreach my $f (grep !/^\./, readdir(DD)) {
9cf6b11a 405 push(@m, { message => $1, path => "$d/$e" }) if ($f =~ /^(.{16})-H$/);
059ec3d9
PH
406 }
407 closedir(DD);
408 } elsif ($e =~ /^(.{16})-H$/) {
9cf6b11a 409 push(@m, { message => $1, path => $d });
059ec3d9
PH
410 }
411 }
412 closedir(D);
413
e22ca4ac
JJ
414 if ($o) {
415 my $c = scalar(@m);
416 # loop twice to pretend we're doing a good job of mixing things up
417 for (my $i = 0; $i < 2 * $c; $i++) {
418 my $rand = int(rand($c));
419 ($m[$i % $c],$m[$rand]) = ($m[$rand],$m[$i % $c]);
420 }
421 } elsif (!$u) {
422 @m = sort { $a->{message} cmp $b->{message} } @m;
423 }
424 @m = reverse(@m) if ($r);
425
426 return(\@m);
059ec3d9
PH
427}
428
429BEGIN {
430
431package Exim::SpoolFile;
432
b3f69ca8
JJ
433# versions 4.61 and higher will not need these variables anymore, but they
434# are left for handling legacy installs
435$Exim::SpoolFile::ACL_C_MAX_LEGACY = 10;
436#$Exim::SpoolFile::ACL_M_MAX _LEGACY= 10;
059ec3d9
PH
437
438sub new {
439 my $class = shift;
440 my $self = {};
441 bless($self, $class);
442
443 $self->{_spool_dir} = '';
444 $self->{_undelivered_only} = 0;
445 $self->{_show_generated} = 0;
446 $self->{_output_long} = 1;
447 $self->{_output_idonly} = 0;
448 $self->{_output_brief} = 0;
449 $self->{_output_flatq} = 0;
5f970846 450 $self->{_show_vars} = [];
059ec3d9
PH
451
452 $self->_reset();
453 return($self);
454}
455
456sub output_long {
457 my $self = shift;
458
459 $self->{_output_long} = 1;
460 $self->{_output_idonly} = 0;
461 $self->{_output_brief} = 0;
462 $self->{_output_flatq} = 0;
463}
464
465sub output_idonly {
466 my $self = shift;
467
468 $self->{_output_long} = 0;
469 $self->{_output_idonly} = 1;
470 $self->{_output_brief} = 0;
471 $self->{_output_flatq} = 0;
472}
473
474sub output_brief {
475 my $self = shift;
476
477 $self->{_output_long} = 0;
478 $self->{_output_idonly} = 0;
479 $self->{_output_brief} = 1;
480 $self->{_output_flatq} = 0;
481}
482
483sub output_flatq {
484 my $self = shift;
485
486 $self->{_output_long} = 0;
487 $self->{_output_idonly} = 0;
488 $self->{_output_brief} = 0;
489 $self->{_output_flatq} = 1;
490}
491
492sub set_show_vars {
493 my $self = shift;
494 my $s = shift;
495
496 foreach my $v (split(/\s*,\s*/, $s)) {
5f970846 497 push(@{$self->{_show_vars}}, $v);
059ec3d9
PH
498 }
499}
500
501sub set_show_generated {
502 my $self = shift;
503 $self->{_show_generated} = shift;
504}
505
506sub set_undelivered_only {
507 my $self = shift;
508 $self->{_undelivered_only} = shift;
509}
510
511sub error {
512 my $self = shift;
513 return $self->{_error};
514}
515
516sub _error {
517 my $self = shift;
518 $self->{_error} = shift;
519 return(undef);
520}
521
522sub _reset {
523 my $self = shift;
524
525 $self->{_error} = '';
526 $self->{_delivered} = 0;
527 $self->{_message} = '';
528 $self->{_path} = '';
529 $self->{_vars} = {};
530
531 $self->{_numrecips} = 0;
532 $self->{_udel_tree} = {};
533 $self->{_del_tree} = {};
534 $self->{_recips} = {};
535
536 return($self);
537}
538
539sub parse_message {
540 my $self = shift;
8e669ac1 541
059ec3d9
PH
542 $self->_reset();
543 $self->{_message} = shift || return(0);
9cf6b11a 544 $self->{_path} = shift; # optional path to message
059ec3d9 545 return(0) if (!$self->{_spool_dir});
9cf6b11a 546 if (!$self->{_path} && !$self->_find_path()) {
059ec3d9
PH
547 # assume the message was delivered from under us and ignore
548 $self->{_delivered} = 1;
549 return(1);
550 }
551 $self->_parse_header() || return(0);
552
553 return(1);
554}
555
9cf6b11a
JJ
556# take the output of get_state() and set up a message internally like
557# parse_message (except from a saved data struct, not by parsing the
558# files on disk).
559sub restore_state {
560 my $self = shift;
561 my $h = shift;
562
563 return(1) if ($h->{_delivered});
564 $self->_reset();
565 $self->{_message} = $h->{_message} || return(0);
566 return(0) if (!$self->{_spool_dir});
567
568 $self->{_path} = $h->{_path};
569 $self->{_vars} = $h->{_vars};
570 $self->{_numrecips} = $h->{_numrecips};
571 $self->{_udel_tree} = $h->{_udel_tree};
572 $self->{_del_tree} = $h->{_del_tree};
573 $self->{_recips} = $h->{_recips};
574
575 $self->{_vars}{message_age} = time() - $self->{_vars}{received_time};
576 return(1);
577}
578
579# This returns the state data for a specific message in a format that can
580# be later frozen back in to regain state
581#
582# after calling this function, this specific state is not expect to be
583# reused. That's because we're returning direct references to specific
584# internal structures. We're also modifying the structure ourselves
585# by deleting certain internal message variables.
586sub get_state {
587 my $self = shift;
588 my $h = {}; # this is the hash ref we'll be returning.
589
590 $h->{_delivered} = $self->{_delivered};
591 $h->{_message} = $self->{_message};
592 $h->{_path} = $self->{_path};
593 $h->{_vars} = $self->{_vars};
594 $h->{_numrecips} = $self->{_numrecips};
595 $h->{_udel_tree} = $self->{_udel_tree};
596 $h->{_del_tree} = $self->{_del_tree};
597 $h->{_recips} = $self->{_recips};
598
599 # delete some internal variables that we will rebuild later if needed
600 delete($h->{_vars}{message_body});
601 delete($h->{_vars}{message_age});
602
603 return($h);
604}
605
606# keep this sub as a feature if we ever break this module out, but do away
607# with its use in exipick (pass it in from caller instead)
059ec3d9
PH
608sub _find_path {
609 my $self = shift;
610
611 return(0) if (!$self->{_message});
612 return(0) if (!$self->{_spool_dir});
613
9cf6b11a
JJ
614 # test split spool first on the theory that people concerned about
615 # performance will have split spool set =).
616 foreach my $f (substr($self->{_message}, 5, 1).'/', '') {
617 if (-f "$self->{_spool_dir}/input/$f$self->{_message}-H") {
059ec3d9
PH
618 $self->{_path} = $self->{_spool_dir} . "/input/$f";
619 return(1);
620 }
621 }
622 return(0);
623}
624
625sub set_spool {
626 my $self = shift;
627 $self->{_spool_dir} = shift;
628}
629
630# accepts a variable with or without leading '$' or trailing ':'
631sub get_var {
632 my $self = shift;
b3f69ca8 633 my $var = lc(shift);
059ec3d9
PH
634
635 $var =~ s/^\$//;
636 $var =~ s/:$//;
637
638 $self->_parse_body()
639 if ($var eq 'message_body' && !$self->{_vars}{message_body});
640
9cf6b11a 641 chomp($self->{_vars}{$var});
059ec3d9
PH
642 return $self->{_vars}{$var};
643}
644
645sub _parse_body {
646 my $self = shift;
647 my $f = $self->{_path} . '/' . $self->{_message} . '-D';
648
649 open(I, "<$f") || return($self->_error("Couldn't open $f: $!"));
650 chomp($_ = <I>);
651 return(0) if ($self->{_message}.'-D' ne $_);
652
653 $self->{_vars}{message_body} = join('', <I>);
654 close(I);
655 $self->{_vars}{message_body} =~ s/\n/ /g;
656 $self->{_vars}{message_body} =~ s/\000/ /g;
657 return(1);
658}
659
660sub _parse_header {
661 my $self = shift;
662 my $f = $self->{_path} . '/' . $self->{_message} . '-H';
663
9cf6b11a
JJ
664 if (!open(I, "<$f")) {
665 # assume message went away and silently ignore
666 $self->{_delivered} = 1;
667 return(1);
668 }
669
059ec3d9
PH
670 chomp($_ = <I>);
671 return(0) if ($self->{_message}.'-H' ne $_);
672 $self->{_vars}{message_id} = $self->{_message};
ee744174 673 $self->{_vars}{message_exim_id} = $self->{_message};
059ec3d9
PH
674
675 # line 2
676 chomp($_ = <I>);
5f970846 677 return(0) if (!/^(.+)\s(\-?\d+)\s(\-?\d+)$/);
059ec3d9
PH
678 $self->{_vars}{originator_login} = $1;
679 $self->{_vars}{originator_uid} = $2;
680 $self->{_vars}{originator_gid} = $3;
681
682 # line 3
683 chomp($_ = <I>);
684 return(0) if (!/^<(.*)>$/);
685 $self->{_vars}{sender_address} = $1;
686 $self->{_vars}{sender_address_domain} = $1;
687 $self->{_vars}{sender_address_local_part} = $1;
688 $self->{_vars}{sender_address_domain} =~ s/^.*\@//;
689 $self->{_vars}{sender_address_local_part} =~ s/^(.*)\@.*$/$1/;
690
691 # line 4
692 chomp($_ = <I>);
693 return(0) if (!/^(\d+)\s(\d+)$/);
694 $self->{_vars}{received_time} = $1;
695 $self->{_vars}{warning_count} = $2;
696 $self->{_vars}{message_age} = time() - $self->{_vars}{received_time};
697
698 while (<I>) {
699 chomp();
700 if (/^(-\S+)\s*(.*$)/) {
701 my $tag = $1;
702 my $arg = $2;
703 if ($tag eq '-acl') {
704 my $t;
705 return(0) if ($arg !~ /^(\d+)\s(\d+)$/);
b3f69ca8 706 if ($1 < $Exim::SpoolFile::ACL_C_MAX_LEGACY) {
059ec3d9
PH
707 $t = "acl_c$1";
708 } else {
b3f69ca8 709 $t = "acl_m" . ($1 - $Exim::SpoolFile::ACL_C_MAX_LEGACY);
059ec3d9
PH
710 }
711 read(I, $self->{_vars}{$t}, $2+1) || return(0);
712 chomp($self->{_vars}{$t});
b3f69ca8
JJ
713 } elsif ($tag eq '-aclc') {
714 return(0) if ($arg !~ /^(\d+)\s(\d+)$/);
715 my $t = "acl_c$1";
716 read(I, $self->{_vars}{$t}, $2+1) || return(0);
717 chomp($self->{_vars}{$t});
718 } elsif ($tag eq '-aclm') {
719 return(0) if ($arg !~ /^(\d+)\s(\d+)$/);
720 my $t = "acl_m$1";
721 read(I, $self->{_vars}{$t}, $2+1) || return(0);
722 chomp($self->{_vars}{$t});
059ec3d9
PH
723 } elsif ($tag eq '-local') {
724 $self->{_vars}{sender_local} = 1;
725 } elsif ($tag eq '-localerror') {
726 $self->{_vars}{local_error_message} = 1;
727 } elsif ($tag eq '-local_scan') {
728 $self->{_vars}{local_scan_data} = $arg;
bf759a8b
PH
729 } elsif ($tag eq '-spam_score_int') {
730 $self->{_vars}{spam_score_int} = $arg;
731 $self->{_vars}{spam_score} = $arg / 10;
732 } elsif ($tag eq '-bmi_verdicts') {
733 $self->{_vars}{bmi_verdicts} = $arg;
734 } elsif ($tag eq '-host_lookup_deferred') {
735 $self->{_vars}{host_lookup_deferred} = 1;
059ec3d9
PH
736 } elsif ($tag eq '-host_lookup_failed') {
737 $self->{_vars}{host_lookup_failed} = 1;
738 } elsif ($tag eq '-body_linecount') {
739 $self->{_vars}{body_linecount} = $arg;
bf759a8b
PH
740 } elsif ($tag eq '-body_zerocount') {
741 $self->{_vars}{body_zerocount} = $arg;
059ec3d9
PH
742 } elsif ($tag eq '-frozen') {
743 $self->{_vars}{deliver_freeze} = 1;
744 $self->{_vars}{deliver_frozen_at} = $arg;
bf759a8b
PH
745 } elsif ($tag eq '-allow_unqualified_recipient') {
746 $self->{_vars}{allow_unqualified_recipient} = 1;
747 } elsif ($tag eq '-allow_unqualified_sender') {
748 $self->{_vars}{allow_unqualified_sender} = 1;
059ec3d9
PH
749 } elsif ($tag eq '-deliver_firsttime') {
750 $self->{_vars}{deliver_firsttime} = 1;
751 $self->{_vars}{first_delivery} = 1;
752 } elsif ($tag eq '-manual_thaw') {
753 $self->{_vars}{deliver_manual_thaw} = 1;
754 $self->{_vars}{manually_thawed} = 1;
755 } elsif ($tag eq '-auth_id') {
756 $self->{_vars}{authenticated_id} = $arg;
757 } elsif ($tag eq '-auth_sender') {
758 $self->{_vars}{authenticated_sender} = $arg;
759 } elsif ($tag eq '-sender_set_untrusted') {
760 $self->{_vars}{sender_set_untrusted} = 1;
761 } elsif ($tag eq '-tls_certificate_verified') {
762 $self->{_vars}{tls_certificate_verified} = 1;
763 } elsif ($tag eq '-tls_cipher') {
764 $self->{_vars}{tls_cipher} = $arg;
765 } elsif ($tag eq '-tls_peerdn') {
766 $self->{_vars}{tls_peerdn} = $arg;
767 } elsif ($tag eq '-host_address') {
768 $self->{_vars}{sender_host_port} = $self->_get_host_and_port(\$arg);
769 $self->{_vars}{sender_host_address} = $arg;
770 } elsif ($tag eq '-interface_address') {
771 $self->{_vars}{interface_port} = $self->_get_host_and_port(\$arg);
772 $self->{_vars}{interface_address} = $arg;
bf759a8b
PH
773 } elsif ($tag eq '-active_hostname') {
774 $self->{_vars}{smtp_active_hostname} = $arg;
059ec3d9
PH
775 } elsif ($tag eq '-host_auth') {
776 $self->{_vars}{sender_host_authenticated} = $arg;
777 } elsif ($tag eq '-host_name') {
778 $self->{_vars}{sender_host_name} = $arg;
779 } elsif ($tag eq '-helo_name') {
780 $self->{_vars}{sender_helo_name} = $arg;
781 } elsif ($tag eq '-ident') {
782 $self->{_vars}{sender_ident} = $arg;
783 } elsif ($tag eq '-received_protocol') {
784 $self->{_vars}{received_protocol} = $arg;
785 } elsif ($tag eq '-N') {
786 $self->{_vars}{dont_deliver} = 1;
059ec3d9
PH
787 } else {
788 # unrecognized tag, save it for reference
789 $self->{$tag} = $arg;
790 }
791 } else {
792 last;
793 }
794 }
795
8e669ac1 796 # when we drop out of the while loop, we have the first line of the
059ec3d9
PH
797 # delivered tree in $_
798 do {
799 if ($_ eq 'XX') {
800 ; # noop
801 } elsif ($_ =~ s/^[YN][YN]\s+//) {
802 $self->{_del_tree}{$_} = 1;
803 } else {
804 return(0);
805 }
806 chomp($_ = <I>);
807 } while ($_ !~ /^\d+$/);
808
809 $self->{_numrecips} = $_;
810 $self->{_vars}{recipients_count} = $self->{_numrecips};
811 for (my $i = 0; $i < $self->{_numrecips}; $i++) {
812 chomp($_ = <I>);
813 return(0) if (/^$/);
814 my $addr = '';
815 if (/^(.*)\s\d+,(\d+),\d+$/) {
816 #print STDERR "exim3 type (untested): $_\n";
817 $self->{_recips}{$1} = { pno => $2 };
818 $addr = $1;
819 } elsif (/^(.*)\s(\d+)$/) {
820 #print STDERR "exim4 original type (untested): $_\n";
821 $self->{_recips}{$1} = { pno => $2 };
822 $addr = $1;
823 } elsif (/^(.*)\s(.*)\s(\d+),(\d+)#1$/) {
824 #print STDERR "exim4 new type #1 (untested): $_\n";
825 return($self->_error("incorrect format: $_")) if (length($2) != $3);
826 $self->{_recips}{$1} = { pno => $4, errors_to => $2 };
827 $addr = $1;
828 } elsif (/^.*#(\d+)$/) {
bf759a8b 829 #print STDERR "exim4 #$1 style (unimplemented): $_\n";
059ec3d9
PH
830 $self->_error("exim4 #$1 style (unimplemented): $_");
831 } else {
832 #print STDERR "default type: $_\n";
833 $self->{_recips}{$_} = {};
834 $addr = $_;
835 }
836 $self->{_udel_tree}{$addr} = 1 if (!$self->{_del_tree}{$addr});
837 }
af66f652
PH
838 $self->{_vars}{recipients} = join(', ', keys(%{$self->{_recips}}));
839 $self->{_vars}{recipients_del} = join(', ', keys(%{$self->{_del_tree}}));
840 $self->{_vars}{recipients_undel} = join(', ', keys(%{$self->{_udel_tree}}));
841 $self->{_vars}{recipients_undel_count} = scalar(keys(%{$self->{_udel_tree}}));
842 $self->{_vars}{recipients_del_count} = 0;
843 foreach my $r (keys %{$self->{_del_tree}}) {
844 next if (!$self->{_recips}{$r});
845 $self->{_vars}{recipients_del_count}++;
846 }
059ec3d9
PH
847
848 # blank line
849 $_ = <I>;
850 return(0) if (!/^$/);
851
852 # start reading headers
853 while (read(I, $_, 3) == 3) {
854 my $t = getc(I);
855 return(0) if (!length($t));
856 while ($t =~ /^\d$/) {
857 $_ .= $t;
858 $t = getc(I);
859 }
860 # ok, right here $t contains the header flag and $_ contains the number of
861 # bytes to read. If we ever use the header flag, grab it here.
862 $self->{_vars}{message_size} += $_ if ($t ne '*');
863 $t = getc(I); # strip the space out of the file
864 my $bytes = $_;
865 return(0) if (read(I, $_, $bytes) != $bytes);
9cf6b11a
JJ
866 $self->{_vars}{message_linecount} += (tr/\n//) if ($t ne '*');
867
059ec3d9 868 # build the $header_ variable, following exim's rules (sort of)
9cf6b11a
JJ
869 my($v,$d) = split(/:/, $_, 2);
870 $v = "header_" . lc($v);
871 $d =~ s/^\s+//;
872 $d =~ s/\s+$//;
873 $self->{_vars}{$v} .= "$d\n";
874 $self->{_vars}{received_count}++ if ($v eq 'header_received');
059ec3d9 875 # push header onto $message_headers var, following exim's rules
9cf6b11a 876 $self->{_vars}{message_headers} .= $_;
059ec3d9
PH
877 }
878 close(I);
9cf6b11a
JJ
879 # remove trailing newline from $message_headers
880 chomp($self->{_vars}{message_headers});
059ec3d9
PH
881
882 if (length($self->{_vars}{"header_reply-to"}) > 0) {
883 $self->{_vars}{reply_address} = $self->{_vars}{"header_reply-to"};
884 } else {
885 $self->{_vars}{reply_address} = $self->{_vars}{header_from};
886 }
887
888 $self->{_vars}{message_body_size} =
889 (stat($self->{_path}.'/'.$self->{_message}.'-D'))[7] - 19;
890 if ($self->{_vars}{message_body_size} < 0) {
891 $self->{_vars}{message_size} = 0;
892 } else {
893 $self->{_vars}{message_size} += $self->{_vars}{message_body_size} + 1;
894 }
895
5f970846
PH
896 $self->{_vars}{message_linecount} += $self->{_vars}{body_linecount};
897
898 my $i = $self->{_vars}{message_size};
9cf6b11a
JJ
899 if ($i == 0) { $i = ""; }
900 elsif ($i < 1024) { $i = sprintf("%d", $i); }
901 elsif ($i < 10240) { $i = sprintf("%.1fK", $i / 1024); }
902 elsif ($i < 1048576) { $i = sprintf("%dK", ($i+512)/1024); }
903 elsif ($i < 10485760) { $i = sprintf("%.1fM", $i/1048576); }
904 else { $i = sprintf("%dM", ($i + 524288)/1048576); }
5f970846
PH
905 $self->{_vars}{shown_message_size} = $i;
906
059ec3d9 907 return(1);
8e669ac1 908}
059ec3d9
PH
909
910# mimic exim's host_extract_port function - receive a ref to a scalar,
911# strip it of port, return port
912sub _get_host_and_port {
913 my $self = shift;
914 my $host = shift; # scalar ref, be careful
915
916 if ($$host =~ /^\[([^\]]+)\](?:\:(\d+))?$/) {
917 $$host = $1;
918 return($2 || 0);
919 } elsif ($$host =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:\.(\d+))?$/) {
920 $$host = $1;
921 return($2 || 0);
922 } elsif ($$host =~ /^([\d\:]+)(?:\.(\d+))?$/) {
923 $$host = $1;
924 return($2 || 0);
925 }
926 # implicit else
927 return(0);
928}
929
e22ca4ac
JJ
930# honoring all formatting preferences, return a scalar variable of the
931# information for the single message matching what exim -bp would show.
932# We can print later if we want.
933sub format_message {
059ec3d9 934 my $self = shift;
e22ca4ac 935 my $o = '';
059ec3d9
PH
936 return if ($self->{_delivered});
937
938 if ($self->{_output_idonly}) {
e22ca4ac 939 $o .= $self->{_message};
5f970846 940 foreach my $v (@{$self->{_show_vars}}) {
e22ca4ac 941 $o .= " $v='" . $self->get_var($v) . "'";
5f970846 942 }
e22ca4ac
JJ
943 $o .= "\n";
944 return $o;
059ec3d9 945 }
8e669ac1 946
059ec3d9
PH
947 if ($self->{_output_long} || $self->{_output_flatq}) {
948 my $i = int($self->{_vars}{message_age} / 60);
949 if ($i > 90) {
950 $i = int(($i+30)/60);
e22ca4ac
JJ
951 if ($i > 72) { $o .= sprintf "%2dd ", int(($i+12)/24); }
952 else { $o .= sprintf "%2dh ", $i; }
953 } else { $o .= sprintf "%2dm ", $i; }
059ec3d9 954
5f970846 955 if ($self->{_output_flatq} && $self->{_show_vars}) {
e22ca4ac
JJ
956 $o .= join(';', map { "$_='".$self->get_var($_)."'" }
957 (@{$self->{_show_vars}})
958 );
5f970846 959 } else {
e22ca4ac 960 $o .= sprintf "%5s", $self->{_vars}{shown_message_size};
5f970846 961 }
e22ca4ac 962 $o .= " ";
059ec3d9 963 }
e22ca4ac
JJ
964 $o .= "$self->{_message} ";
965 $o .= "From: " if ($self->{_output_brief});
966 $o .= "<$self->{_vars}{sender_address}>";
059ec3d9
PH
967
968 if ($self->{_output_long}) {
e22ca4ac 969 $o .= " ($self->{_vars}{originator_login})"
059ec3d9 970 if ($self->{_vars}{sender_set_untrusted});
8e669ac1 971
059ec3d9 972 # XXX exim contains code here to print spool format errors
e22ca4ac
JJ
973 $o .= " *** frozen ***" if ($self->{_vars}{deliver_freeze});
974 $o .= "\n";
059ec3d9 975
5f970846 976 foreach my $v (@{$self->{_show_vars}}) {
e22ca4ac 977 $o .= sprintf " %25s = '%s'\n", $v, $self->get_var($v);
059ec3d9 978 }
8e669ac1 979
059ec3d9
PH
980 foreach my $r (keys %{$self->{_recips}}) {
981 next if ($self->{_del_tree}{$r} && $self->{_undelivered_only});
e22ca4ac 982 $o .= sprintf " %s %s\n", $self->{_del_tree}{$r} ? "D" : " ", $r;
059ec3d9
PH
983 }
984 if ($self->{_show_generated}) {
985 foreach my $r (keys %{$self->{_del_tree}}) {
986 next if ($self->{_recips}{$r});
e22ca4ac 987 $o .= sprintf " +D %s\n", $r;
059ec3d9
PH
988 }
989 }
990 } elsif ($self->{_output_brief}) {
991 my @r = ();
992 foreach my $r (keys %{$self->{_recips}}) {
993 next if ($self->{_del_tree}{$r});
994 push(@r, $r);
995 }
e22ca4ac 996 $o .= " To: " . join(';', @r);
b3f69ca8 997 if ($self->{_show_vars} && scalar(@{$self->{_show_vars}})) {
e22ca4ac
JJ
998 $o .= " Vars: " . join(';', map { "$_='".$self->get_var($_)."'" }
999 (@{$self->{_show_vars}})
1000 );
5f970846 1001 }
059ec3d9 1002 } elsif ($self->{_output_flatq}) {
e22ca4ac 1003 $o .= " *** frozen ***" if ($self->{_vars}{deliver_freeze});
059ec3d9
PH
1004 my @r = ();
1005 foreach my $r (keys %{$self->{_recips}}) {
1006 next if ($self->{_del_tree}{$r});
1007 push(@r, $r);
1008 }
e22ca4ac 1009 $o .= " " . join(' ', @r);
059ec3d9
PH
1010 }
1011
e22ca4ac
JJ
1012 $o .= "\n";
1013 return($o);
1014}
1015
1016sub print_message {
1017 my $self = shift;
1018 my $fh = shift || \*STDOUT;
1019 return if ($self->{_delivered});
1020
1021 print $fh $self->format_message();
059ec3d9
PH
1022}
1023
1024sub dump {
1025 my $self = shift;
1026
1027 foreach my $k (sort keys %$self) {
1028 my $r = ref($self->{$k});
1029 if ($r eq 'ARRAY') {
1030 printf "%20s <<EOM\n", $k;
1031 print @{$self->{$k}}, "EOM\n";
1032 } elsif ($r eq 'HASH') {
1033 printf "%20s <<EOM\n", $k;
1034 foreach (sort keys %{$self->{$k}}) {
1035 printf "%20s %s\n", $_, $self->{$k}{$_};
1036 }
1037 print "EOM\n";
1038 } else {
1039 printf "%20s %s\n", $k, $self->{$k};
1040 }
1041 }
1042}
1043
1044} # BEGIN
1045
1046sub ext_usage {
1047 if ($ARGV[0] =~ /^--help$/i) {
1048 require Config;
1049 $ENV{PATH} .= ":" unless $ENV{PATH} eq "";
1050 $ENV{PATH} = "$ENV{PATH}$Config::Config{'installscript'}";
1051 #exec("perldoc", "-F", "-U", $0) || exit 1;
1052 $< = $> = 1 if ($> == 0 || $< == 0);
1053 exec("perldoc", $0) || exit 1;
1054 # make parser happy
1055 %Config::Config = ();
1056 } elsif ($ARGV[0] =~ /^--version$/i) {
1057 print "$p_name version $p_version\n\n$p_cp\n";
1058 } else {
1059 return;
1060 }
1061
1062 exit(0);
1063}
1064
1065__END__
1066
1067=head1 NAME
1068
e22ca4ac 1069exipick - selectively display messages from an Exim queue
059ec3d9 1070
e22ca4ac 1071=head1 SYNOPSIS
059ec3d9 1072
e22ca4ac 1073exipick [<options>] [<criterion> [<criterion> ...]]
059ec3d9
PH
1074
1075=head1 DESCRIPTION
1076
e22ca4ac
JJ
1077exipick is a tool to display messages in an Exim queue. It is very similar to exiqgrep and is, in fact, a drop in replacement for exiqgrep. exipick allows you to select messages to be displayed using any piece of data stored in an Exim spool file. Matching messages can be displayed in a variety of formats.
1078
1079=head1 QUICK START
1080
1081Delete every frozen message from queue:
1082 exipick -zi | xargs exim -Mrm
1083
1084Show only messages which have not yet been virus scanned:
1085 exipick '$received_protocol ne virus-scanned'
1086
1087Run the queue in a semi-random order:
1088 exipick -i --random | xargs exim -M
1089
1090Show the count and total size of all messages which either originated from localhost or have a received protocol of 'local':
1091 exipick --or --size --bpc \
1092 '$sender_host_address eq 127.0.0.1' \
1093 '$received_protocol eq local'
1094
1095Display all messages received on the MSA port, ordered first by the sender's email domain and then by the size of the emails:
1096 exipick --sort sender_address_domain,message_size \
1097 '$interface_port == 587'
1098
1099Display only messages whose every recipient is in the example.com domain, also listing the IP address of the sending host:
1100 exipick --show-vars sender_host_address \
1101 '$each_recipients = example.com'
059ec3d9
PH
1102
1103=head1 OPTIONS
1104
1105=over 4
1106
e22ca4ac 1107=item --and
059ec3d9 1108
e22ca4ac 1109Display messages matching all criteria (default)
059ec3d9 1110
e22ca4ac 1111=item -b
059ec3d9 1112
e22ca4ac 1113Display messages in brief format (exiqgrep)
059ec3d9 1114
e22ca4ac 1115=item -bp
059ec3d9 1116
e22ca4ac 1117Display messages in standard mailq format (default)
059ec3d9 1118
e22ca4ac 1119=item -bpa
af66f652 1120
e22ca4ac 1121Same as -bp, show generated addresses also (exim)
af66f652 1122
e22ca4ac 1123=item -bpc
5f970846 1124
e22ca4ac 1125Show a count of matching messages (exim)
5f970846 1126
e22ca4ac 1127=item -bpr
5f970846 1128
e22ca4ac 1129Same as '-bp --unsorted' (exim)
5f970846 1130
e22ca4ac 1131=item -bpra
5f970846 1132
e22ca4ac 1133Same as '-bpr --unsorted' (exim)
5f970846 1134
e22ca4ac 1135=item -bpru
5f970846 1136
e22ca4ac 1137Same as '-bpu --unsorted' (exim)
5f970846 1138
e22ca4ac 1139=item -bpu
9cf6b11a 1140
e22ca4ac 1141Same as -bp, but only show undelivered messages (exim)
9cf6b11a 1142
e22ca4ac 1143=item -c
059ec3d9 1144
e22ca4ac 1145Show a count of matching messages (exiqgrep)
059ec3d9 1146
e22ca4ac 1147=item --caseful
059ec3d9 1148
e22ca4ac 1149Make operators involving '=' honor case
059ec3d9 1150
e22ca4ac 1151=item -f <regexp>
059ec3d9 1152
e22ca4ac 1153Same as '$sender_address = <regexp>' (exiqgrep)
059ec3d9 1154
e22ca4ac 1155=item --flatq
059ec3d9 1156
e22ca4ac 1157Use a single-line output format
059ec3d9 1158
e22ca4ac 1159=item --freeze <cache file>
059ec3d9 1160
e22ca4ac 1161Save queue information in an quickly retrievable format
059ec3d9 1162
e22ca4ac 1163=item --help
059ec3d9 1164
e22ca4ac 1165Display this output
059ec3d9 1166
e22ca4ac 1167=item -i
5f970846 1168
e22ca4ac 1169Display only the message IDs (exiqgrep)
059ec3d9 1170
e22ca4ac 1171=item -l
059ec3d9 1172
e22ca4ac 1173Same as -bp (exiqgrep)
059ec3d9 1174
e22ca4ac 1175=item --not
059ec3d9 1176
e22ca4ac 1177Negate all tests.
059ec3d9 1178
e22ca4ac 1179=item -o <seconds>
059ec3d9 1180
e22ca4ac 1181Same as '$message_age > <seconds>' (exiqgrep)
059ec3d9 1182
e22ca4ac 1183=item --or
059ec3d9 1184
e22ca4ac 1185Display messages matching any criteria
059ec3d9 1186
e22ca4ac 1187=item -R
059ec3d9 1188
e22ca4ac 1189Same as --reverse (exiqgrep)
059ec3d9 1190
e22ca4ac 1191=item -r <regexp>
059ec3d9 1192
e22ca4ac 1193Same as '$recipients = <regexp>' (exiqgrep)
059ec3d9 1194
e22ca4ac 1195=item --random
9cf6b11a 1196
e22ca4ac 1197Display messages in random order
9cf6b11a 1198
e22ca4ac 1199=item --reverse
9cf6b11a 1200
e22ca4ac 1201Display messages in reverse order
9cf6b11a 1202
e22ca4ac 1203=item -s <string>
9cf6b11a 1204
e22ca4ac 1205Same as '$shown_message_size eq <string>' (exiqgrep)
9cf6b11a 1206
e22ca4ac 1207=item --spool <path>
059ec3d9 1208
e22ca4ac 1209Set the path to the exim spool to use
059ec3d9 1210
e22ca4ac 1211=item --show-rules
059ec3d9 1212
e22ca4ac 1213Show the internal representation of each criterion specified
059ec3d9 1214
e22ca4ac 1215=item --show-tests
059ec3d9 1216
e22ca4ac 1217Show the result of each criterion on each message
059ec3d9 1218
e22ca4ac 1219=item --show-vars <variable>[,<variable>...]
059ec3d9 1220
e22ca4ac 1221Show the value for <variable> for each displayed message
059ec3d9 1222
e22ca4ac 1223=item --size
059ec3d9 1224
e22ca4ac 1225Show the total bytes used by each displayed message
059ec3d9 1226
e22ca4ac 1227=item --thaw <cache file>
059ec3d9 1228
e22ca4ac 1229Read queue information cached from a previous --freeze run
059ec3d9 1230
e22ca4ac 1231=item --sort <variable>[,<variable>...]
059ec3d9 1232
e22ca4ac 1233Display matching messages sorted according to <variable>
059ec3d9 1234
e22ca4ac 1235=item --unsorted
059ec3d9 1236
e22ca4ac 1237Do not apply any sorting to output
059ec3d9 1238
e22ca4ac 1239=item --version
059ec3d9 1240
e22ca4ac 1241Display the version of this command
059ec3d9 1242
e22ca4ac
JJ
1243=item -x
1244
1245Same as '!$deliver_freeze' (exiqgrep)
1246
1247=item -y
9cf6b11a 1248
e22ca4ac
JJ
1249Same as '$message_age < <seconds>' (exiqgrep)
1250
1251=item -z
1252
1253Same as '$deliver_freeze' (exiqgrep)
9cf6b11a 1254
059ec3d9
PH
1255=back
1256
e22ca4ac 1257=head1 CRITERIA
059ec3d9 1258
e22ca4ac 1259Exipick decides which messages to display by applying a test against each message. The rules take the general form of 'VARIABLE OPERATOR VALUE'. For example, '$message_age > 60'. When exipick is deciding which messages to display, it checks the $message_age variable for each message. If a message's age is greater than 60, the message will be displayed. If the message's age is 60 or less seconds, it will not be displayed.
059ec3d9 1260
e22ca4ac 1261Multiple criteria can be used. The order they are specified does not matter. By default all criteria must evaluate to true for a message to be displayed. If the --or option is used, a message is displayed as long as any of the criteria evaluate to true.
059ec3d9 1262
e22ca4ac 1263See the VARIABLES and OPERATORS sections below for more details
059ec3d9 1264
e22ca4ac 1265=head1 OPERATORS
059ec3d9 1266
e22ca4ac 1267=over 4
059ec3d9 1268
e22ca4ac 1269=item BOOLEAN
059ec3d9 1270
e22ca4ac
JJ
1271Boolean variables are checked simply by being true or false. There is no real operator except negation. Examples of valid boolean tests:
1272 '$deliver_freeze'
1273 '!$deliver_freeze'
059ec3d9 1274
e22ca4ac 1275=item NUMERIC
059ec3d9 1276
e22ca4ac
JJ
1277Valid comparisons are <, <=, >, >=, ==, and !=. Numbers can be integers or floats. Any number in a test suffixed with d, h, m, s, M, K, or B will be mulitplied by 86400, 3600, 60, 1, 1048576, 1024, or 1 respectively. Examples of valid numeric tests:
1278 '$message_age >= 3d'
1279 '$local_interface == 587'
1280 '$message_size < 30K'
059ec3d9 1281
e22ca4ac 1282=item STRING
059ec3d9 1283
e22ca4ac
JJ
1284The string operators are =, eq, ne, =~, and !~. With the exception of '=', the operators all match the functionality of the like-named perl operators. eq and ne match a string exactly. !~, =~, and = apply a perl regular expression to a string. The '=' operator behaves just like =~ but you are not required to place // around the regular expression. Examples of valid string tests:
1285 '$received_protocol eq esmtp'
1286 '$sender_address = example.com'
1287 '$each_recipients =~ /^a[a-z]{2,3}@example.com$/'
059ec3d9 1288
e22ca4ac 1289=item NEGATION
059ec3d9 1290
e22ca4ac 1291There are many ways to negate tests, each having a reason for existing. Many tests can be negated using native operators. For instance, >1 is the opposite of <=1 and eq and ne are opposites. In addition, each individual test can be negated by adding a ! at the beginning of the test. For instance, '!$acl_m1 =~ /^DENY$/' is the same as '$acl_m1 !~ /^DENY$/'. Finally, every test can be specified by using the command line argument --not. This is functionally equivilant to adding a ! to the beginning of every test.
059ec3d9 1292
e22ca4ac 1293=back
059ec3d9 1294
e22ca4ac 1295=head1 VARIABLES
059ec3d9 1296
e22ca4ac 1297With a few exceptions the available variables match Exim's internal expansion variables in both name and exact contents. There are a few notable additions and format deviations which are noted below. Although a brief explanation is offered below, Exim's spec.txt should be consulted for full details. It is important to remember that not every variable will be defined for every message. For example, $sender_host_port is not defined for messages not received from a remote host.
059ec3d9 1298
e22ca4ac 1299Internally, all variables are represented as strings, meaning any operator will work on any variable. This means that '$sender_host_name > 4' is a legal criterion, even if it does not produce meaningful results. Variables in the list below are marked with a 'type' to help in choosing which types of operators make sense to use.
bf759a8b 1300
e22ca4ac
JJ
1301 Identifiers
1302 B - Boolean variables
1303 S - String variables
1304 N - Numeric variables
1305 . - Standard variable matching Exim's content definition
1306 # - Standard variable, contents differ from Exim's definition
1307 + - Non-standard variable
bf759a8b 1308
e22ca4ac 1309=over 4
059ec3d9 1310
e22ca4ac 1311=item S . $acl_c0-$acl_c9, $acl_m0-$acl_m9
059ec3d9 1312
e22ca4ac 1313User definable variables.
059ec3d9 1314
e22ca4ac 1315=item B + $allow_unqualified_recipient
059ec3d9 1316
e22ca4ac 1317TRUE if unqualified recipient addresses are permitted in header lines.
059ec3d9 1318
e22ca4ac 1319=item B + $allow_unqualified_sender
059ec3d9 1320
e22ca4ac 1321TRUE if unqualified sender addresses are permitted in header lines.
059ec3d9 1322
e22ca4ac 1323=item S . $authenticated_id
059ec3d9 1324
e22ca4ac 1325Optional saved information from authenticators, or the login name of the calling process for locally submitted messages.
059ec3d9 1326
e22ca4ac 1327=item S . $authenticated_sender
059ec3d9 1328
e22ca4ac 1329The value of AUTH= param for smtp messages, or a generated value from the calling processes login and qualify domain for locally submitted messages.
059ec3d9 1330
e22ca4ac 1331=item S + $bmi_verdicts
059ec3d9 1332
e22ca4ac 1333The verdict string provided by a Brightmail content scan
059ec3d9 1334
e22ca4ac 1335=item N . $body_linecount
059ec3d9
PH
1336
1337The number of lines in the message's body.
1338
e22ca4ac 1339=item N . $body_zerocount
059ec3d9
PH
1340
1341The number of binary zero bytes in the message's body.
1342
e22ca4ac 1343=item B + $deliver_freeze
059ec3d9 1344
e22ca4ac 1345TRUE if the message is currently frozen.
059ec3d9 1346
e22ca4ac 1347=item N + $deliver_frozen_at
059ec3d9 1348
e22ca4ac 1349The epoch time at which message was frozen.
059ec3d9 1350
e22ca4ac 1351=item B + $dont_deliver
059ec3d9 1352
e22ca4ac 1353TRUE if, under normal circumstances, Exim will not try to deliver the message.
059ec3d9 1354
e22ca4ac 1355=item S + $each_recipients
059ec3d9 1356
e22ca4ac 1357This is a psuedo variable which allows you to apply a test against each address in $recipients individually. Whereas '$recipients =~ /@aol.com/' will match if any recipient address contains aol.com, '$each_recipients =~ /@aol.com$/' will only be true if every recipient matches that pattern. Note that this obeys --and or --or being set. Using it with --or is very similar to just matching against $recipients, but with the added benefit of being able to use anchors at the beginning and end of each recipient address.
5f970846 1358
e22ca4ac 1359=item S + $each_recipients_del
5f970846 1360
e22ca4ac 1361Like $each_recipients, but for $recipients_del
059ec3d9 1362
e22ca4ac 1363=item S + $each_recipients_undel
059ec3d9 1364
e22ca4ac 1365Like $each_recipients, but for $recipients_undel
059ec3d9 1366
e22ca4ac 1367=item B . $first_delivery
059ec3d9 1368
e22ca4ac 1369TRUE if the message has never been deferred.
059ec3d9 1370
e22ca4ac 1371=item S # $header_*
059ec3d9 1372
e22ca4ac 1373The value of the same named message header. These variables are really closer to Exim's rheader_* variables, with the exception that leading and trailing space is removed.
059ec3d9 1374
e22ca4ac 1375=item B . $host_lookup_deferred
059ec3d9 1376
e22ca4ac 1377TRUE if there was an attempt to look up the host's name from its IP address, but an error occurred that during the attempt.
059ec3d9 1378
e22ca4ac 1379=item B . $host_lookup_failed
059ec3d9 1380
e22ca4ac 1381TRUE if there was an attempt to look up the host's name from its IP address, but the attempt returned a negative result.
059ec3d9 1382
e22ca4ac 1383=item S . $interface_address
af66f652 1384
e22ca4ac 1385The address of the local IP interface for network-originated messages.
af66f652 1386
e22ca4ac 1387=item N . $interface_port
af66f652 1388
e22ca4ac 1389The local port number if network-originated messages.
af66f652 1390
e22ca4ac 1391=item B + $local_error_message
059ec3d9 1392
e22ca4ac 1393TRUE if the message is a locally-generated error message.
059ec3d9 1394
e22ca4ac 1395=item S . $local_scan_data
059ec3d9 1396
e22ca4ac 1397The text returned by the local_scan() function when a message is received.
059ec3d9 1398
e22ca4ac 1399=item B . $manually_thawed
059ec3d9 1400
e22ca4ac 1401TRUE when the message has been manually thawed.
059ec3d9 1402
e22ca4ac 1403=item N . $message_age
059ec3d9 1404
e22ca4ac 1405The number of seconds since the message was received.
059ec3d9 1406
e22ca4ac 1407=item S # $message_body
059ec3d9 1408
e22ca4ac 1409The message's body. Unlike Exim's variable of the same name, this variable contains the entire message body. Newlines and nulls are replaced by spaces.
059ec3d9 1410
e22ca4ac 1411=item N . $message_body_size
059ec3d9 1412
e22ca4ac 1413The size of the body in bytes.
059ec3d9 1414
e22ca4ac 1415=item S . $message_exim_id, $message_id
059ec3d9 1416
e22ca4ac 1417The unique message id that is used by Exim to identify the message. $message_id is deprecated as of Exim 4.53.
059ec3d9 1418
e22ca4ac 1419=item S . $message_headers
bf759a8b 1420
e22ca4ac 1421A concatenation of all the header lines except for lines added by routers or transports.
bf759a8b 1422
e22ca4ac 1423=item N . $message_linecount
bf759a8b 1424
e22ca4ac 1425The number of lines in the entire message (body and headers).
bf759a8b 1426
e22ca4ac 1427=item N . $message_size
bf759a8b 1428
e22ca4ac 1429The size of the message in bytes.
bf759a8b 1430
e22ca4ac 1431=item N . $originator_gid
bf759a8b 1432
e22ca4ac 1433The group id under which the process that called Exim was running as when the message was received.
bf759a8b 1434
e22ca4ac 1435=item S + $originator_login
059ec3d9 1436
e22ca4ac 1437The login of the process which called Exim.
059ec3d9 1438
e22ca4ac 1439=item N . $originator_uid
059ec3d9 1440
e22ca4ac 1441The user id under which the process that called Exim was running as when the message was received.
059ec3d9 1442
e22ca4ac 1443=item N . $received_count
059ec3d9 1444
e22ca4ac 1445The number of Received: header lines in the message.
059ec3d9 1446
e22ca4ac 1447=item S . $received_protocol
059ec3d9 1448
e22ca4ac 1449The name of the protocol by which the message was received.
059ec3d9 1450
e22ca4ac 1451=item N . $received_time
059ec3d9 1452
e22ca4ac 1453The epoch time at which the message was received.
059ec3d9 1454
e22ca4ac 1455=item S # $recipients
059ec3d9 1456
e22ca4ac 1457The list of envelope recipients for a message. Unlike Exim's version, this variable always contains every recipient of the message. The recipients are seperated by a comma and a space. See also $each_recipients.
059ec3d9 1458
e22ca4ac 1459=item N . $recipients_count
059ec3d9 1460
e22ca4ac 1461The number of envelope recipients for the message.
059ec3d9 1462
e22ca4ac 1463=item S + $recipients_del
059ec3d9 1464
e22ca4ac 1465The list of delivered envelope recipients for a message. This non-standard variable is in the same format as $recipients and contains the list of already-delivered recipients including any generated addresses. See also $each_recipients_del.
059ec3d9 1466
e22ca4ac 1467=item N + $recipients_del_count
059ec3d9 1468
e22ca4ac 1469The number of envelope recipients for the message which have already been delivered. Note that this is the count of original recipients to which the message has been delivered. It does not include generated addresses so it is possible that this number will be less than the number of addresses in the $recipients_del string.
059ec3d9 1470
e22ca4ac 1471=item S + $recipients_undel
059ec3d9 1472
e22ca4ac 1473The list of undelivered envelope recipients for a message. This non-standard variable is in the same format as $recipients and contains the list of undelivered recipients. See also $each_recipients_undel.
059ec3d9 1474
e22ca4ac 1475=item N + $recipients_undel_count
059ec3d9 1476
e22ca4ac 1477The number of envelope recipients for the message which have not yet been delivered.
059ec3d9 1478
e22ca4ac 1479=item S . $reply_address
059ec3d9
PH
1480
1481The contents of the Reply-To: header line if one exists and it is not empty, or otherwise the contents of the From: header line.
1482
e22ca4ac 1483=item S . $sender_address
059ec3d9
PH
1484
1485The sender's address that was received in the message's envelope. For bounce messages, the value of this variable is the empty string.
1486
e22ca4ac 1487=item S . $sender_address_domain
059ec3d9 1488
bf759a8b 1489The domain part of $sender_address.
059ec3d9 1490
e22ca4ac 1491=item S . $sender_address_local_part
059ec3d9 1492
bf759a8b 1493The local part of $sender_address.
059ec3d9 1494
e22ca4ac 1495=item S . $sender_helo_name
059ec3d9
PH
1496
1497The HELO or EHLO value supplied for smtp or bsmtp messages.
1498
e22ca4ac 1499=item S . $sender_host_address
059ec3d9
PH
1500
1501The remote host's IP address.
1502
e22ca4ac 1503=item S . $sender_host_authenticated
059ec3d9
PH
1504
1505The name of the authenticator driver which successfully authenticated the client from which the message was received.
1506
e22ca4ac 1507=item S . $sender_host_name
059ec3d9
PH
1508
1509The remote host's name as obtained by looking up its IP address.
1510
e22ca4ac 1511=item N . $sender_host_port
059ec3d9 1512
e22ca4ac 1513The port number that was used on the remote host for network-originated messages.
5f970846 1514
e22ca4ac 1515=item S . $sender_ident
5f970846 1516
e22ca4ac 1517The identification received in response to an RFC 1413 request for remote messages, the login name of the user that called Exim for locally generated messages.
bf759a8b 1518
e22ca4ac 1519=item B + $sender_local
bf759a8b 1520
e22ca4ac 1521TRUE if the message was locally generated.
bf759a8b 1522
e22ca4ac 1523=item B + $sender_set_untrusted
bf759a8b 1524
e22ca4ac 1525TRUE if the envelope sender of this message was set by an untrusted local caller.
bf759a8b 1526
e22ca4ac 1527=item S + $shown_message_size
bf759a8b 1528
e22ca4ac 1529This non-standard variable contains the formatted size string. That is, for a message whose $message_size is 66566 bytes, $shown_message_size is 65K.
059ec3d9 1530
e22ca4ac 1531=item S . $smtp_active_hostname
059ec3d9 1532
e22ca4ac 1533The value of the active host name when the message was received, as specified by the "smtp_active_hostname" option.
059ec3d9 1534
e22ca4ac 1535=item S . $spam_score
059ec3d9 1536
e22ca4ac 1537The spam score of the message, for example '3.4' or '30.5'. (Requires exiscan or WITH_CONTENT_SCAN)
059ec3d9 1538
e22ca4ac 1539=item S . $spam_score_int
059ec3d9 1540
e22ca4ac 1541The spam score of the message, multiplied by ten, as an integer value. For instance '34' or '305'. (Requires exiscan or WITH_CONTENT_SCAN)
059ec3d9 1542
e22ca4ac 1543=item B . $tls_certificate_verified
059ec3d9 1544
e22ca4ac 1545TRUE if a TLS certificate was verified when the message was received.
059ec3d9 1546
e22ca4ac 1547=item S . $tls_cipher
059ec3d9 1548
e22ca4ac 1549The cipher suite that was negotiated for encrypted SMTP connections.
059ec3d9 1550
e22ca4ac 1551=item S . $tls_peerdn
059ec3d9 1552
e22ca4ac 1553The value of the Distinguished Name of the certificate if Exim is configured to request one
059ec3d9 1554
e22ca4ac 1555=item N + $warning_count
059ec3d9 1556
e22ca4ac 1557The number of delay warnings which have been sent for this message.
059ec3d9
PH
1558
1559=back
1560
059ec3d9
PH
1561=head1 CONTACT
1562
1563=over 4
1564
1565=item EMAIL: proj-exipick@jetmore.net
1566
1567=item HOME: jetmore.org/john/code/#exipick
1568
1569=back
1570
1571=cut