2 # Copyright (c) 1995 - 2018 University of Cambridge.
3 # SPDX-License-Identifier: GPL-2.0-or-later
4 # See the file NOTICE for conditions of use and distribution.
7 # This variables should be set by the building process
8 my $spool = 'SPOOL_DIRECTORY'; # may be overridden later
9 my $exim = 'BIN_DIRECTORY/exim';
11 # Need to set this dynamically during build, but it's not used right now anyway.
12 my $charset = 'ISO-8859-1';
14 # use 'exipick --help' to view documentation for this program.
15 # Documentation also viewable online at
16 # http://www.exim.org/eximwiki/ToolExipickManPage
19 BEGIN { pop @INC if $INC[-1] eq '.' };
24 my $p_name = basename $0;
25 my $p_version = "20100323.0";
26 my $p_usage = "Usage: $p_name [--help|--man|--version] (see --help for details)";
28 Copyright (c) 2003-2010 John Jetmore <jj33\@pobox.com>
29 Copyright (c) 2019 The Exim Maintainers
31 This program is free software; you can redistribute it and/or modify
32 it under the terms of the GNU General Public License as published by
33 the Free Software Foundation; either version 2 of the License, or
34 (at your option) any later version.
36 This program is distributed in the hope that it will be useful,
37 but WITHOUT ANY WARRANTY; without even the implied warranty of
38 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39 GNU General Public License for more details.
41 You should have received a copy of the GNU General Public License
42 along with this program; if not, write to the Free Software
43 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
46 $| = 1; # unbuffer STDOUT
48 Getopt::Long::Configure("bundling_override");
50 'spool=s' => \$G::spool, # exim spool dir
51 'C|Config=s' => \$G::config, # use alternative Exim configuration file
52 'input-dir=s' => \$G::input_dir, # name of the "input" dir
53 'queue=s' => \$G::queue, # name of the queue
54 'finput' => \$G::finput, # same as "--input-dir Finput"
55 'bp' => \$G::mailq_bp, # List the queue (noop - default)
56 'bpa' => \$G::mailq_bpa, # ... with generated address as well
57 'bpc' => \$G::mailq_bpc, # ... but just show a count of messages
58 'bpr' => \$G::mailq_bpr, # ... do not sort
59 'bpra' => \$G::mailq_bpra, # ... with generated addresses, unsorted
60 'bpru' => \$G::mailq_bpru, # ... only undelivered addresses, unsorted
61 'bpu' => \$G::mailq_bpu, # ... only undelivered addresses
62 'and' => \$G::and, # 'and' the criteria (default)
63 'or' => \$G::or, # 'or' the criteria
64 'f=s' => \$G::qgrep_f, # from regexp
65 'r=s' => \$G::qgrep_r, # recipient regexp
66 's=s' => \$G::qgrep_s, # match against size field
67 'y=s' => \$G::qgrep_y, # message younger than (secs)
68 'o=s' => \$G::qgrep_o, # message older than (secs)
69 'z' => \$G::qgrep_z, # frozen only
70 'x' => \$G::qgrep_x, # non-frozen only
71 'c' => \$G::qgrep_c, # display match count
72 'l' => \$G::qgrep_l, # long format (default)
73 'i' => \$G::qgrep_i, # message ids only
74 'b' => \$G::qgrep_b, # brief format
75 'size' => \$G::size_only, # sum the size of the matching msgs
76 'not' => \$G::negate, # flip every test
77 'R|reverse' => \$G::reverse, # reverse output (-R is qgrep option)
78 'sort=s' => \@G::sort, # allow you to choose variables to sort by
79 'freeze=s' => \$G::freeze, # freeze data in this file
80 'thaw=s' => \$G::thaw, # thaw data from this file
81 'unsorted' => \$G::unsorted, # unsorted, regardless of output format
82 'random' => \$G::random, # (poorly) randomize evaluation order
83 'flatq' => \$G::flatq, # brief format
84 'caseful' => \$G::caseful, # in '=' criteria, respect case
85 'caseless' => \$G::caseless, # ...ignore case (default)
86 'charset=s' => \$charset, # charset for $bh and $h variables
87 'show-vars=s' => \$G::show_vars, # display the contents of these vars
88 'just-vars' => \$G::just_vars, # only display vars, no other info
89 'show-rules' => \$G::show_rules, # display compiled match rules
90 'show-tests' => \$G::show_tests, # display tests as applied to each message
91 'man' => sub { pod2usage(-verbose => 2, -exit => 0, -noperldoc => system('perldoc -V >/dev/null 2>&1')) },
92 'help' => sub { pod2usage(-verbose => 1, -exit => 0) },
94 print "$p_name: $0\n",
95 "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
96 "perl(runtime): $]\n";
101 # if both freeze and thaw specified, only thaw as it is less destructive
102 $G::freeze = undef if ($G::freeze && $G::thaw);
103 freeze_start() if ($G::freeze);
104 thaw_start() if ($G::thaw);
106 # massage sort options (make '$var,Var:' be 'var','var')
107 for (my $i = scalar(@G::sort)-1; $i >= 0; $i--) {
108 $G::sort[$i] = lc($G::sort[$i]);
109 $G::sort[$i] =~ s/[\$:\s]//g;
110 if ((my @vars = split(/,/, $G::sort[$i])) > 1) {
111 $G::sort[$i] = $vars[0]; shift(@vars); # replace current slot w/ first var
112 splice(@G::sort, $i+1, 0, @vars); # add other vars after current pos
115 push(@G::sort, "message_exim_id") if (@G::sort);
116 die "empty value provided to --sort not allowed, exiting\n"
117 if (grep /^\s*$/, @G::sort);
119 # massage the qgrep options into standard criteria
120 push(@ARGV, "\$sender_address =~ /$G::qgrep_f/") if ($G::qgrep_f);
121 push(@ARGV, "\$recipients =~ /$G::qgrep_r/") if ($G::qgrep_r);
122 push(@ARGV, "\$shown_message_size eq $G::qgrep_s") if ($G::qgrep_s);
123 push(@ARGV, "\$message_age < $G::qgrep_y") if ($G::qgrep_y);
124 push(@ARGV, "\$message_age > $G::qgrep_o") if ($G::qgrep_o);
125 push(@ARGV, "\$deliver_freeze") if ($G::qgrep_z);
126 push(@ARGV, "!\$deliver_freeze") if ($G::qgrep_x);
128 $G::mailq_bp = $G::mailq_bp; # shut up -w
129 $G::and = $G::and; # shut up -w
130 $G::msg_ids = {}; # short circuit when crit is only MID
131 $G::caseless = $G::caseful ? 0 : 1; # nocase by default, case if both
132 @G::recipients_crit = (); # holds per-recip criteria
133 $spool = defined $G::spool ? $G::spool
134 : do { chomp($_ = `$exim @{[defined $G::config ? "-C $G::config" : '']} -n -bP spool_directory`)
136 my $input_dir = (defined $G::queue ? "$G::queue/" : '')
137 . (defined $G::input_dir || ($G::finput ? "Finput" : "input"));
138 my $count_only = 1 if ($G::mailq_bpc || $G::qgrep_c);
139 my $unsorted = 1 if ($G::mailq_bpr || $G::mailq_bpra ||
140 $G::mailq_bpru || $G::unsorted);
141 my $msg = $G::thaw ? thaw_message_list()
142 : get_all_msgs($spool, $input_dir, $unsorted,
143 $G::reverse, $G::random);
144 die "Problem accessing thaw file\n" if ($G::thaw && !$msg);
145 my $crit = process_criteria(\@ARGV);
146 my $e = Exim::SpoolFile->new();
147 my $tcount = 0 if ($count_only); # holds count of all messages
148 my $mcount = 0 if ($count_only); # holds count of matching messages
149 my $total_size = 0 if ($G::size_only);
150 $e->set_undelivered_only(1) if ($G::mailq_bpru || $G::mailq_bpu);
151 $e->set_show_generated(1) if ($G::mailq_bpra || $G::mailq_bpa);
152 $e->output_long() if ($G::qgrep_l);
153 $e->output_idonly() if ($G::qgrep_i);
154 $e->output_brief() if ($G::qgrep_b);
155 $e->output_flatq() if ($G::flatq);
156 $e->output_vars_only() if ($G::just_vars && $G::show_vars);
157 $e->set_show_vars($G::show_vars) if ($G::show_vars);
158 $e->set_spool($spool, $input_dir);
161 foreach my $m (@$msg) {
162 next if (scalar(keys(%$G::msg_ids)) && !$G::or
163 && !$G::msg_ids->{$m->{message}});
165 my $data = thaw_data();
166 if (!$e->restore_state($data)) {
167 warn "Couldn't thaw $data->{_message}: ".$e->error()."\n";
171 if (!$e->parse_message($m->{message}, $m->{path})) {
172 warn "Couldn't parse $m->{message}: ".$e->error()."\n";
179 foreach my $c (@G::recipients_crit) { # handle each_recip* vars
180 foreach my $addr (split(/, /, $e->get_var($c->{var}))) {
181 my %t = ( 'cmp' => $c->{cmp}, 'var' => $c->{var} );
182 $t{cmp} =~ s/"?\$var"?/'$addr'/;
183 push(@local_crit, \%t);
186 if ($G::show_tests) { print $e->get_var('message_exim_id'), "\n"; }
188 foreach my $c (@$crit, @local_crit) {
189 my $var = $e->get_var($c->{var});
190 my $ret = eval($c->{cmp});
191 if ($G::show_tests) {
192 printf " %25s = '%s'\n %25s => $ret\n",$c->{var},$var,$c->{cmp},$ret;
195 print STDERR "Error in eval '$c->{cmp}': $@\n";
199 if ($G::or) { last CRITERIA; }
200 else { next CRITERIA; }
202 if ($G::or) { next CRITERIA; }
207 # skip this message if any criteria were supplied and it didn't match
208 next MSG if ((scalar(@$crit) || scalar(@local_crit)) && !$match);
210 if ($count_only || $G::size_only) {
212 $total_size += $e->get_var('message_size');
215 # if we are defining criteria to sort on, save the message here. If
216 # we don't save here and do the sort later, we have a chicken/egg
218 push(@G::to_print, { vars => {}, output => "" });
219 foreach my $var (@G::sort) {
220 # save any values we want to sort on. I don't like doing the internal
221 # struct access here, but calling get_var a bunch can be _slow_ =(
222 $G::sort_type{$var} ||= '<=>';
223 $G::to_print[-1]{vars}{$var} = $e->{_vars}{$var};
224 $G::sort_type{$var} = 'cmp' if ($G::to_print[-1]{vars}{$var} =~ /\D/);
226 $G::to_print[-1]{output} = $e->format_message();
228 print $e->format_message();
233 freeze_data($e->get_state());
234 push(@G::frozen_msgs, $m);
239 msg_sort(\@G::to_print, \@G::sort, $G::reverse);
240 foreach my $msg (@G::to_print) {
241 print $msg->{output};
246 print "$mcount matches out of $tcount messages" .
247 ($G::size_only ? " ($total_size)" : "") . "\n";
248 } elsif ($G::mailq_bpc) {
249 print "$mcount" . ($G::size_only ? " ($total_size)" : "") . "\n";
250 } elsif ($G::size_only) {
251 print "$total_size\n";
255 freeze_message_list(\@G::frozen_msgs);
263 # sender_address_domain,shown_message_size
270 foreach my $v (@G::sort) {
271 push(@pieces, "\$a->{vars}{\"$v\"} $G::sort_type{$v} \$b->{vars}{\"$v\"}");
273 my $sort_str = join(" || ", @pieces);
275 @$msgs = sort { eval $sort_str } (@$msgs);
276 @$msgs = reverse(@$msgs) if ($reverse);
286 # FREEZE FILE FORMAT:
292 # message_list_bytes <- 10 bytes, zero-packed, plus \n
295 eval("use Storable");
296 die "Storable module not found: $@\n" if ($@);
297 open(O, ">$G::freeze") || die "Can't open freeze file $G::freeze: $!\n";
298 $G::freeze_handle = \*O;
302 close($G::freeze_handle);
306 eval("use Storable");
307 die "Storable module not found: $@\n" if ($@);
308 open(I, "<$G::thaw") || die "Can't open freeze file $G::thaw: $!\n";
309 $G::freeze_handle = \*I;
313 close($G::freeze_handle);
317 my $h = Storable::freeze($_[0]);
318 print $G::freeze_handle length($h)+1, "\n$h\n";
321 sub freeze_message_list {
322 my $h = Storable::freeze($_[0]);
323 my $l = length($h) + 1;
324 printf $G::freeze_handle "EOM\n$l\n$h\n%010d\n", $l+11+length($l)+1;
327 sub thaw_message_list {
328 my $orig_pos = tell($G::freeze_handle);
329 seek($G::freeze_handle, -11, 2);
330 chomp(my $bytes = <$G::freeze_handle>);
331 seek($G::freeze_handle, $bytes * -1, 2);
332 my $obj = thaw_data();
333 seek($G::freeze_handle, 0, $orig_pos);
339 chomp(my $bytes = <$G::freeze_handle>);
340 return(undef) if (!$bytes || $bytes eq 'EOM');
341 my $read = read(I, $obj, $bytes);
342 die "Format error in thaw file (expected $bytes bytes, got $read)\n"
343 if ($bytes != $read);
345 return(Storable::thaw($obj));
348 sub process_criteria {
354 foreach my $t ('@') { s/$t/\\$t/g; }
355 if (/^(.*?)\s+(<=|>=|==|!=|<|>)\s+(.*)$/) {
356 #print STDERR "found as integer\n";
357 my $v = $1; my $o = $2; my $n = $3;
358 if ($n =~ /^(-?[\d\.]+)M$/) { $n = $1 * 1024 * 1024; }
359 elsif ($n =~ /^(-?[\d\.]+)K$/) { $n = $1 * 1024; }
360 elsif ($n =~ /^(-?[\d\.]+)B?$/) { $n = $1; }
361 elsif ($n =~ /^(-?[\d\.]+)d$/) { $n = $1 * 60 * 60 * 24; }
362 elsif ($n =~ /^(-?[\d\.]+)h$/) { $n = $1 * 60 * 60; }
363 elsif ($n =~ /^(-?[\d\.]+)m$/) { $n = $1 * 60; }
364 elsif ($n =~ /^(-?[\d\.]+)s?$/) { $n = $1; }
366 print STDERR "Expression $_ did not parse: numeric comparison with ",
371 push(@c, { var => lc($v), cmp => "(\$var $o $n)" });
372 } elsif (/^(.*?)\s+(=~|!~)\s+(.*)$/) {
373 #print STDERR "found as string regexp\n";
374 push(@c, { var => lc($1), cmp => "(\"\$var\" $2 $3)" });
375 } elsif (/^(.*?)\s+=\s+(.*)$/) {
376 #print STDERR "found as bare string regexp\n";
377 my $case = $G::caseful ? '' : 'i';
378 push(@c, { var => lc($1), cmp => "(\"\$var\" =~ /$2/$case)" });
379 # quote special characters in perl text string
380 #foreach my $t ('@') { $c[-1]{cmp} =~ s/$t/\\$t/g; }
381 } elsif (/^(.*?)\s+(eq|ne)\s+(.*)$/) {
382 #print STDERR "found as string cmp\n";
383 my $var = lc($1); my $op = $2; my $val = $3;
384 $val =~ s|^(['"])(.*)\1$|$2|;
385 push(@c, { var => $var, cmp => "(\"\$var\" $op \"$val\")" });
386 if (($var eq 'message_id' || $var eq 'message_exim_id') && $op eq "eq") {
387 #print STDERR "short circuit @c[-1]->{cmp} $val\n";
388 $G::msg_ids->{$val} = 1;
390 #foreach my $t ('@') { $c[-1]{cmp} =~ s/$t/\\$t/g; }
391 } elsif (/^(\S+)$/) {
392 #print STDERR "found as boolean\n";
393 push(@c, { var => lc($1), cmp => "(\$var)" });
395 print STDERR "Expression $_ did not parse\n";
399 # assign the results of the cmp test here (handle "!" negation)
400 # also handle global --not negation
401 if ($c[-1]{var} =~ s|^!||) {
402 $c[-1]{cmp} .= $G::negate ? " ? 1 : 0" : " ? 0 : 1";
404 $c[-1]{cmp} .= $G::negate ? " ? 0 : 1" : " ? 1 : 0";
406 # support the each_* pseudo variables. Steal the criteria off of the
407 # queue for special processing later
408 if ($c[-1]{var} =~ /^each_(recipients(_(un)?del)?)$/) {
410 push(@G::recipients_crit,pop(@c));
411 $G::recipients_crit[-1]{var} = $var; # remove each_ from the variable
417 if ($G::show_rules) { foreach (@c) { print "$_->{var}\t$_->{cmp}\n"; } }
425 my $u = shift; # don't sort
426 my $r = shift; # right before returning, reverse order
427 my $o = shift; # if true, randomize list order before returning
430 if ($i =~ m|^/|) { $d = $i; } else { $d = $d . '/' . $i; }
432 opendir(D, "$d") || die "Couldn't opendir $d: $!\n";
433 foreach my $e (grep !/^\./, readdir(D)) {
434 if ($e =~ /^[a-zA-Z0-9]$/) {
435 opendir(DD, "$d/$e") || next;
436 foreach my $f (grep !/^\./, readdir(DD)) {
437 push(@m, { message => $1, path => "$d/$e" }) if ($f =~ /^(.{16})-H$/);
440 } elsif ($e =~ /^(.{16})-H$/) {
441 push(@m, { message => $1, path => $d });
448 # loop twice to pretend we're doing a good job of mixing things up
449 for (my $i = 0; $i < 2 * $c; $i++) {
450 my $rand = int(rand($c));
451 ($m[$i % $c],$m[$rand]) = ($m[$rand],$m[$i % $c]);
454 @m = sort { $a->{message} cmp $b->{message} } @m;
456 @m = reverse(@m) if ($r);
463 package Exim::SpoolFile;
465 # versions 4.61 and higher will not need these variables anymore, but they
466 # are left for handling legacy installs
467 $Exim::SpoolFile::ACL_C_MAX_LEGACY = 10;
468 #$Exim::SpoolFile::ACL_M_MAX _LEGACY= 10;
473 bless($self, $class);
475 $self->{_spool_dir} = '';
476 $self->{_input_path} = '';
477 $self->{_undelivered_only} = 0;
478 $self->{_show_generated} = 0;
479 $self->{_output_long} = 1;
480 $self->{_output_idonly} = 0;
481 $self->{_output_brief} = 0;
482 $self->{_output_flatq} = 0;
483 $self->{_output_vars_only} = 0;
484 $self->{_show_vars} = [];
493 $self->{_output_long} = 1;
494 $self->{_output_idonly} = 0;
495 $self->{_output_brief} = 0;
496 $self->{_output_flatq} = 0;
497 $self->{_output_vars_only} = 0;
503 $self->{_output_long} = 0;
504 $self->{_output_idonly} = 1;
505 $self->{_output_brief} = 0;
506 $self->{_output_flatq} = 0;
507 $self->{_output_vars_only} = 0;
513 $self->{_output_long} = 0;
514 $self->{_output_idonly} = 0;
515 $self->{_output_brief} = 1;
516 $self->{_output_flatq} = 0;
517 $self->{_output_vars_only} = 0;
523 $self->{_output_long} = 0;
524 $self->{_output_idonly} = 0;
525 $self->{_output_brief} = 0;
526 $self->{_output_flatq} = 1;
527 $self->{_output_vars_only} = 0;
530 sub output_vars_only {
533 $self->{_output_long} = 0;
534 $self->{_output_idonly} = 0;
535 $self->{_output_brief} = 0;
536 $self->{_output_flatq} = 0;
537 $self->{_output_vars_only} = 1;
544 foreach my $v (split(/\s*,\s*/, $s)) {
545 push(@{$self->{_show_vars}}, $v);
549 sub set_show_generated {
551 $self->{_show_generated} = shift;
554 sub set_undelivered_only {
556 $self->{_undelivered_only} = shift;
561 return $self->{_error};
566 $self->{_error} = shift;
573 $self->{_error} = '';
574 $self->{_delivered} = 0;
575 $self->{_message} = '';
578 $self->{_vars_raw} = {};
580 $self->{_numrecips} = 0;
581 $self->{_udel_tree} = {};
582 $self->{_del_tree} = {};
583 $self->{_recips} = {};
592 $self->{_message} = shift || return(0);
593 $self->{_path} = shift; # optional path to message
594 return(0) if (!$self->{_input_path});
595 if (!$self->{_path} && !$self->_find_path()) {
596 # assume the message was delivered from under us and ignore
597 $self->{_delivered} = 1;
600 $self->_parse_header() || return(0);
605 # take the output of get_state() and set up a message internally like
606 # parse_message (except from a saved data struct, not by parsing the
612 return(1) if ($h->{_delivered});
614 $self->{_message} = $h->{_message} || return(0);
615 return(0) if (!$self->{_input_path});
617 $self->{_path} = $h->{_path};
618 $self->{_vars} = $h->{_vars};
619 $self->{_numrecips} = $h->{_numrecips};
620 $self->{_udel_tree} = $h->{_udel_tree};
621 $self->{_del_tree} = $h->{_del_tree};
622 $self->{_recips} = $h->{_recips};
624 $self->{_vars}{message_age} = time() - $self->{_vars}{received_time};
628 # This returns the state data for a specific message in a format that can
629 # be later frozen back in to regain state
631 # after calling this function, this specific state is not expect to be
632 # reused. That's because we're returning direct references to specific
633 # internal structures. We're also modifying the structure ourselves
634 # by deleting certain internal message variables.
637 my $h = {}; # this is the hash ref we'll be returning.
639 $h->{_delivered} = $self->{_delivered};
640 $h->{_message} = $self->{_message};
641 $h->{_path} = $self->{_path};
642 $h->{_vars} = $self->{_vars};
643 $h->{_numrecips} = $self->{_numrecips};
644 $h->{_udel_tree} = $self->{_udel_tree};
645 $h->{_del_tree} = $self->{_del_tree};
646 $h->{_recips} = $self->{_recips};
648 # delete some internal variables that we will rebuild later if needed
649 delete($h->{_vars}{message_body});
650 delete($h->{_vars}{message_age});
655 # keep this sub as a feature if we ever break this module out, but do away
656 # with its use in exipick (pass it in from caller instead)
660 return(0) if (!$self->{_message});
661 return(0) if (!$self->{_input_path});
663 # test split spool first on the theory that people concerned about
664 # performance will have split spool set =).
665 foreach my $f (substr($self->{_message}, 5, 1).'/', '') {
666 if (-f "$self->{_input_path}/$f$self->{_message}-H") {
667 $self->{_path} = "$self->{_input_path}}/$f";
676 $self->{_spool_dir} = shift;
677 $self->{_input_path} = shift;
678 if ($self->{_input_path} !~ m|^/|) {
679 $self->{_input_path} = $self->{_spool_dir} . '/' . $self->{_input_path};
683 sub get_matching_vars {
689 foreach my $v (keys %{$self->{_vars}}) { push(@r, $v) if ($v =~ /$e/); }
696 # accepts a variable with or without leading '$' or trailing ':'
699 my $var = lc(shift); $var =~ s/^\$//; $var =~ s/:$//;
701 if ($var eq 'message_body' && !defined($self->{_vars}{message_body})) {
703 } elsif ($var =~ s|^([rb]?h)(eader)?_|${1}eader_| &&
704 exists($self->{_vars}{$var}) && !defined($self->{_vars}{$var}))
706 if ((my $type = $1) eq 'rh') {
707 $self->{_vars}{$var} = join('', @{$self->{_vars_raw}{$var}{vals}});
709 # both bh_ and h_ build their strings from rh_. Do common work here
710 my $rh = $var; $rh =~ s|^b?|r|;
711 my $comma = 1 if ($self->{_vars_raw}{$rh}{type} =~ /^[BCFRST]$/);
712 foreach (@{$self->{_vars_raw}{$rh}{vals}}) {
713 my $x = $_; # editing $_ here would change the original, which is bad
716 if ($comma) { chomp($x); $self->{_vars}{$var} .= "$x,\n"; }
717 else { $self->{_vars}{$var} .= $x; }
719 $self->{_vars}{$var} =~ s|[\s\n]*$||;
720 $self->{_vars}{$var} =~ s|,$|| if ($comma);
721 # ok, that's the preprocessing, not do specific processing for h type
723 $self->{_vars}{$var} = $self->_decode_2047($self->{_vars}{$var});
725 $self->{_vars}{$var} =
726 $self->_decode_2047($self->{_vars}{$var}, $charset);
730 elsif ($var eq 'received_count' && !defined($self->{_vars}{received_count}))
732 $self->{_vars}{received_count} =
733 scalar(@{$self->{_vars_raw}{rheader_received}{vals}});
735 elsif ($var eq 'message_headers' && !defined($self->{_vars}{message_headers}))
737 $self->{_vars}{$var} =
738 $self->_decode_2047($self->{_vars}{message_headers_raw}, $charset);
739 chomp($self->{_vars}{$var});
741 elsif ($var eq 'reply_address' && !defined($self->{_vars}{reply_address}))
743 $self->{_vars}{reply_address} = exists($self->{_vars}{"header_reply-to"})
744 ? $self->get_var("header_reply-to") : $self->get_var("header_from");
747 #chomp($self->{_vars}{$var}); # I think this was only for headers, obsolete
748 return $self->{_vars}{$var};
753 my $s = shift; # string to decode
754 my $c = shift; # target charset. If empty, just decode, don't convert
755 my $t = ''; # the translated string
756 my $e = 0; # set to true if we get an error in here anywhere
758 return($s) if ($s !~ /=\?/); # don't even bother to look if there's no sign
761 foreach my $mw (split(/(=\?[^\?]{3,}\?[BQ]\?[^\?]{1,74}\?=)/i, $s)) {
763 if ($mw =~ /=\?([^\?]{3,})\?([BQ])\?([^\?]{1,74})\?=/i) {
764 push(@p, { data => $3, encoding => uc($2), charset => uc($1),
766 if ($p[-1]{encoding} eq 'Q') {
767 my @ow = split('', $p[-1]{data});
769 for (my $i = 0; $i < @ow; $i++) {
770 if ($ow[$i] eq '_') { push(@nw, ' '); }
771 elsif ($ow[$i] eq '=') {
772 if (scalar(@ow) - ($i+1) < 2) { # ran out of characters
774 } elsif ($ow[$i+1] !~ /[\dA-F]/i || $ow[$i+2] !~ /[\dA-F]/i) {
777 #push(@nw, chr('0x'.$ow[$i+1].$ow[$i+2]));
778 push(@nw, pack("C", hex($ow[$i+1].$ow[$i+2])));
782 elsif ($ow[$i] =~ /\s/) { # whitespace is illegal
786 else { push(@nw, $ow[$i]); }
788 $p[-1]{data} = join('', @nw);
789 } elsif ($p[-1]{encoding} eq 'B') {
790 my $x = $p[-1]{data};
791 $x =~ tr#A-Za-z0-9+/##cd;
793 $x =~ tr#A-Za-z0-9+/# -_#;
795 while ($x =~ s/(.{1,60})//s) {
796 $r .= unpack("u", chr(32 + int(length($1)*3/4)) . $1);
801 push(@p, { data => $mw, is_mime => 0,
802 is_ws => ($mw =~ m|^[\s\n]+|sm) ? 1 : 0 });
806 for (my $i = 0; $i < @p; $i++) {
807 # mark entities we want to skip (whitespace between consecutive mimewords)
808 if ($p[$i]{is_mime} && $p[$i+1]{is_ws} && $p[$i+2]{is_mime}) {
812 # if word is a mimeword and we have access to Encode and charset was
813 # specified, try to convert text
814 # XXX _cannot_ get consistent conversion results in perl, can't get them
815 # to return same conversions that exim performs. Until I can figure this
816 # out, don't attempt any conversions (header_ will return same value as
818 #if ($c && $p[$i]{is_mime} && $self->_try_load('Encode')) {
819 # # XXX not sure how to catch errors here
820 # Encode::from_to($p[$i]{data}, $p[$i]{charset}, $c);
823 # replace binary zeros w/ '?' in decoded text
824 if ($p[$i]{is_mime}) { $p[$i]{data} =~ s|\x00|?|g; }
830 return(join('', map { $_->{data} } grep { !$_->{skip} } @p));
834 # This isn't a class func but I'm tired
845 my $f = $self->{_path} . '/' . $self->{_message} . '-D';
846 $self->{_vars}{message_body} = ""; # define var so we only come here once
848 open(I, "<$f") || return($self->_error("Couldn't open $f: $!"));
850 return(0) if ($self->{_message}.'-D' ne $_);
852 $self->{_vars}{message_body} = join('', <I>);
854 $self->{_vars}{message_body} =~ s/\n/ /g;
855 $self->{_vars}{message_body} =~ s/\000/ /g;
861 my $f = $self->{_path} . '/' . $self->{_message} . '-H';
862 $self->{_vars}{header_path} = $f;
863 $self->{_vars}{data_path} = $self->{_path} . '/' . $self->{_message} . '-D';
865 if (!open(I, "<$f")) {
866 # assume message went away and silently ignore
867 $self->{_delivered} = 1;
871 # There are a few numeric variables that should explicitly be set to
872 # zero if they aren't found in the header. Technically an empty value
873 # works just as well, but might as well be pedantic
874 $self->{_vars}{body_zerocount} = 0;
875 $self->{_vars}{host_lookup_deferred} = 0;
876 $self->{_vars}{host_lookup_failed} = 0;
877 $self->{_vars}{tls_certificate_verified} = 0;
880 return(0) if ($self->{_message}.'-H' ne $_);
881 $self->{_vars}{message_id} = $self->{_message};
882 $self->{_vars}{message_exim_id} = $self->{_message};
886 return(0) if (!/^(.+)\s(\-?\d+)\s(\-?\d+)$/);
887 $self->{_vars}{originator_login} = $1;
888 $self->{_vars}{originator_uid} = $2;
889 $self->{_vars}{originator_gid} = $3;
893 return(0) if (!/^<(.*)>$/);
894 $self->{_vars}{sender_address} = $1;
895 $self->{_vars}{sender_address_domain} = $1;
896 $self->{_vars}{sender_address_local_part} = $1;
897 $self->{_vars}{sender_address_domain} =~ s/^.*\@//;
898 $self->{_vars}{sender_address_local_part} =~ s/^(.*)\@.*$/$1/;
902 return(0) if (!/^(\d+)\s(\d+)$/);
903 $self->{_vars}{received_time} = $1;
904 $self->{_vars}{warning_count} = $2;
905 $self->{_vars}{message_age} = time() - $self->{_vars}{received_time};
907 TAGGED: while (<I>) {
908 my ($tag, $arg) = /^-?(-\S+)(?:\s+(.*))?$/ or last TAGGED;
911 if ($tag eq '-acl') {
913 return(0) if ($arg !~ /^(\d+)\s(\d+)$/);
914 if ($1 < $Exim::SpoolFile::ACL_C_MAX_LEGACY) {
917 $t = "acl_m" . ($1 - $Exim::SpoolFile::ACL_C_MAX_LEGACY);
919 read(I, $self->{_vars}{$t}, $2+1) || return(0);
920 chomp($self->{_vars}{$t});
921 } elsif ($tag eq '-aclc') {
922 #return(0) if ($arg !~ /^(\d+)\s(\d+)$/);
923 return(0) if ($arg !~ /^(\S+)\s(\d+)$/);
925 read(I, $self->{_vars}{$t}, $2+1) || return(0);
926 chomp($self->{_vars}{$t});
927 } elsif ($tag eq '-aclm') {
928 #return(0) if ($arg !~ /^(\d+)\s(\d+)$/);
929 return(0) if ($arg !~ /^(\S+)\s(\d+)$/);
931 read(I, $self->{_vars}{$t}, $2+1) || return(0);
932 chomp($self->{_vars}{$t});
933 } elsif ($tag eq '-local') {
934 $self->{_vars}{sender_local} = 1;
935 } elsif ($tag eq '-localerror') {
936 $self->{_vars}{local_error_message} = 1;
937 } elsif ($tag eq '-local_scan') {
938 $self->{_vars}{local_scan_data} = $arg;
939 } elsif ($tag eq '-spam_score_int') {
940 $self->{_vars}{spam_score_int} = $arg;
941 $self->{_vars}{spam_score} = $arg / 10;
942 } elsif ($tag eq '-bmi_verdicts') {
943 $self->{_vars}{bmi_verdicts} = $arg;
944 } elsif ($tag eq '-host_lookup_deferred') {
945 $self->{_vars}{host_lookup_deferred} = 1;
946 } elsif ($tag eq '-host_lookup_failed') {
947 $self->{_vars}{host_lookup_failed} = 1;
948 } elsif ($tag eq '-body_linecount') {
949 $self->{_vars}{body_linecount} = $arg;
950 } elsif ($tag eq '-max_received_linelength') {
951 $self->{_vars}{max_received_linelength} = $arg;
952 } elsif ($tag eq '-body_zerocount') {
953 $self->{_vars}{body_zerocount} = $arg;
954 } elsif ($tag eq '-frozen') {
955 $self->{_vars}{deliver_freeze} = 1;
956 $self->{_vars}{deliver_frozen_at} = $arg;
957 } elsif ($tag eq '-allow_unqualified_recipient') {
958 $self->{_vars}{allow_unqualified_recipient} = 1;
959 } elsif ($tag eq '-allow_unqualified_sender') {
960 $self->{_vars}{allow_unqualified_sender} = 1;
961 } elsif ($tag eq '-deliver_firsttime') {
962 $self->{_vars}{deliver_firsttime} = 1;
963 $self->{_vars}{first_delivery} = 1;
964 } elsif ($tag eq '-manual_thaw') {
965 $self->{_vars}{deliver_manual_thaw} = 1;
966 $self->{_vars}{manually_thawed} = 1;
967 } elsif ($tag eq '-auth_id') {
968 $self->{_vars}{authenticated_id} = $arg;
969 } elsif ($tag eq '-auth_sender') {
970 $self->{_vars}{authenticated_sender} = $arg;
971 } elsif ($tag eq '-sender_set_untrusted') {
972 $self->{_vars}{sender_set_untrusted} = 1;
973 } elsif ($tag eq '-tls_certificate_verified') {
974 $self->{_vars}{tls_certificate_verified} = 1;
975 } elsif ($tag eq '-tls_cipher') {
976 $self->{_vars}{tls_cipher} = $arg;
977 } elsif ($tag eq '-tls_peerdn') {
978 $self->{_vars}{tls_peerdn} = $arg;
979 } elsif ($tag eq '-tls_sni') {
980 $self->{_vars}{tls_sni} = $arg;
981 } elsif ($tag eq '-host_address') {
982 $self->{_vars}{sender_host_port} = $self->_get_host_and_port(\$arg);
983 $self->{_vars}{sender_host_address} = $arg;
984 } elsif ($tag eq '-interface_address') {
985 $self->{_vars}{received_port} =
986 $self->{_vars}{interface_port} = $self->_get_host_and_port(\$arg);
987 $self->{_vars}{received_ip_address} =
988 $self->{_vars}{interface_address} = $arg;
989 } elsif ($tag eq '-active_hostname') {
990 $self->{_vars}{smtp_active_hostname} = $arg;
991 } elsif ($tag eq '-host_auth') {
992 $self->{_vars}{sender_host_authenticated} = $arg;
993 } elsif ($tag eq '-host_name') {
994 $self->{_vars}{sender_host_name} = $arg;
995 } elsif ($tag eq '-helo_name') {
996 $self->{_vars}{sender_helo_name} = $arg;
997 } elsif ($tag eq '-ident') {
998 $self->{_vars}{sender_ident} = $arg;
999 } elsif ($tag eq '-received_protocol') {
1000 $self->{_vars}{received_protocol} = $arg;
1001 } elsif ($tag eq '-N') {
1002 $self->{_vars}{dont_deliver} = 1;
1004 # unrecognized tag, save it for reference
1005 $self->{$tag} = $arg;
1009 # when we drop out of the while loop, we have the first line of the
1010 # delivered tree in $_
1015 } elsif ($_ =~ s/^[YN][YN]\s+//) {
1016 $self->{_del_tree}{$_} = 1;
1021 } while ($_ !~ /^\d+$/);
1023 $self->{_numrecips} = $_;
1024 $self->{_vars}{recipients_count} = $self->{_numrecips};
1025 for (my $i = 0; $i < $self->{_numrecips}; $i++) {
1027 return(0) if (/^$/);
1029 if (/^(.*)\s\d+,(\d+),\d+$/) {
1030 #print STDERR "exim3 type (untested): $_\n";
1031 $self->{_recips}{$1} = { pno => $2 };
1033 } elsif (/^(.*)\s(\d+)$/) {
1034 #print STDERR "exim4 original type (untested): $_\n";
1035 $self->{_recips}{$1} = { pno => $2 };
1037 } elsif (/^(.*)\s(.*)\s(\d+),(\d+)#1$/) {
1038 #print STDERR "exim4 new type #1 (untested): $_\n";
1039 return($self->_error("incorrect format: $_")) if (length($2) != $3);
1040 $self->{_recips}{$1} = { pno => $4, errors_to => $2 };
1042 } elsif (/^(\S*)\s(\S*)\s(\d+),(\d+)\s(\S*)\s(\d+),(-?\d+)#3$/) {
1043 #print STDERR "exim4 new type #3 DSN (untested): $_\n";
1044 return($self->_error("incorrect format: $_"))
1045 if ((length($2) != $3) || (length($5) != $6));
1046 $self->{_recips}{$1} = { pno => $7, errors_to => $5 };
1048 } elsif (/^.*#(\d+)$/) {
1049 #print STDERR "exim4 #$1 style (unimplemented): $_\n";
1050 $self->_error("exim4 #$1 style (unimplemented): $_");
1052 #print STDERR "default type: $_\n";
1053 $self->{_recips}{$_} = {};
1056 $self->{_udel_tree}{$addr} = 1 if (!$self->{_del_tree}{$addr});
1058 $self->{_vars}{recipients} = join(', ', keys(%{$self->{_recips}}));
1059 $self->{_vars}{recipients_del} = join(', ', keys(%{$self->{_del_tree}}));
1060 $self->{_vars}{recipients_undel} = join(', ', keys(%{$self->{_udel_tree}}));
1061 $self->{_vars}{recipients_undel_count} = scalar(keys(%{$self->{_udel_tree}}));
1062 $self->{_vars}{recipients_del_count} = 0;
1063 foreach my $r (keys %{$self->{_del_tree}}) {
1064 next if (!$self->{_recips}{$r});
1065 $self->{_vars}{recipients_del_count}++;
1070 return(0) if (!/^$/);
1072 # start reading headers
1073 while (read(I, $_, 3) == 3) {
1075 return(0) if (!length($t));
1076 while ($t =~ /^\d$/) {
1082 $t = getc(I); # strip the space out of the file
1083 return(0) if (read(I, $_, $hdr_bytes) != $hdr_bytes);
1084 if ($hdr_flag ne '*') {
1085 $self->{_vars}{message_linecount} += (tr/\n//);
1086 $self->{_vars}{message_size} += $hdr_bytes;
1089 # mark (rb)?header_ vars as existing and store raw value. They'll be
1090 # processed further in get_var() if needed
1091 my($v,$d) = split(/:/, $_, 2);
1092 $v = "header_" . lc($v);
1093 $self->{_vars}{$v} = $self->{_vars}{"b$v"} = $self->{_vars}{"r$v"} = undef;
1094 push(@{$self->{_vars_raw}{"r$v"}{vals}}, $d);
1095 $self->{_vars_raw}{"r$v"}{type} = $hdr_flag;
1096 $self->{_vars}{message_headers_raw} .= $_;
1100 $self->{_vars}{message_body_size} =
1101 (stat($self->{_path}.'/'.$self->{_message}.'-D'))[7] - 19;
1102 if ($self->{_vars}{message_body_size} < 0) {
1103 $self->{_vars}{message_size} = 0;
1104 $self->{_vars}{message_body_missing} = 1;
1106 $self->{_vars}{message_size} += $self->{_vars}{message_body_size} + 1;
1109 $self->{_vars}{message_linecount} += $self->{_vars}{body_linecount};
1111 my $i = $self->{_vars}{message_size};
1112 if ($i == 0) { $i = ""; }
1113 elsif ($i < 1024) { $i = sprintf("%d", $i); }
1114 elsif ($i < 10240) { $i = sprintf("%.1fK", $i / 1024); }
1115 elsif ($i < 1048576) { $i = sprintf("%dK", ($i+512)/1024); }
1116 elsif ($i < 10485760) { $i = sprintf("%.1fM", $i/1048576); }
1117 else { $i = sprintf("%dM", ($i + 524288)/1048576); }
1118 $self->{_vars}{shown_message_size} = $i;
1123 # mimic exim's host_extract_port function - receive a ref to a scalar,
1124 # strip it of port, return port
1125 sub _get_host_and_port {
1127 my $host = shift; # scalar ref, be careful
1129 if ($$host =~ /^\[([^\]]+)\](?:\:(\d+))?$/) {
1132 } elsif ($$host =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:\.(\d+))?$/) {
1135 } elsif ($$host =~ /^([\d\:]+)(?:\.(\d+))?$/) {
1143 # honoring all formatting preferences, return a scalar variable of the
1144 # information for the single message matching what exim -bp would show.
1145 # We can print later if we want.
1146 sub format_message {
1149 return if ($self->{_delivered});
1151 # define any vars we want to print out for this message. The requests
1152 # can be regexps, and the defined vars can change for each message, so we
1153 # have to build this list for each message
1155 if (@{$self->{_show_vars}}) {
1157 foreach my $e (@{$self->{_show_vars}}) {
1158 foreach my $v ($self->get_matching_vars($e)) {
1159 next if ($t{$v}); $t{$v}++; push(@vars, $v);
1164 if ($self->{_output_idonly}) {
1165 $o .= $self->{_message};
1166 foreach my $v (@vars) { $o .= " $v='" . $self->get_var($v) . "'"; }
1169 } elsif ($self->{_output_vars_only}) {
1170 foreach my $v (@vars) { $o .= $self->get_var($v) . "\n"; }
1174 if ($self->{_output_long} || $self->{_output_flatq}) {
1175 my $i = int($self->{_vars}{message_age} / 60);
1177 $i = int(($i+30)/60);
1178 if ($i > 72) { $o .= sprintf "%2dd ", int(($i+12)/24); }
1179 else { $o .= sprintf "%2dh ", $i; }
1180 } else { $o .= sprintf "%2dm ", $i; }
1182 if ($self->{_output_flatq} && @vars) {
1183 $o .= join(';', map { "$_='".$self->get_var($_)."'" } (@vars)
1186 $o .= sprintf "%5s", $self->{_vars}{shown_message_size};
1190 $o .= "$self->{_message} ";
1191 $o .= "From: " if ($self->{_output_brief});
1192 $o .= "<$self->{_vars}{sender_address}>";
1194 if ($self->{_output_long}) {
1195 $o .= " ($self->{_vars}{originator_login})"
1196 if ($self->{_vars}{sender_set_untrusted});
1198 # XXX exim contains code here to print spool format errors
1199 $o .= " *** frozen ***" if ($self->{_vars}{deliver_freeze});
1202 foreach my $v (@vars) {
1203 $o .= sprintf " %25s = '%s'\n", $v, $self->get_var($v);
1206 foreach my $r (keys %{$self->{_recips}}) {
1207 next if ($self->{_del_tree}{$r} && $self->{_undelivered_only});
1208 $o .= sprintf " %s %s\n", $self->{_del_tree}{$r} ? "D" : " ", $r;
1210 if ($self->{_show_generated}) {
1211 foreach my $r (keys %{$self->{_del_tree}}) {
1212 next if ($self->{_recips}{$r});
1213 $o .= sprintf " +D %s\n", $r;
1216 } elsif ($self->{_output_brief}) {
1218 foreach my $r (keys %{$self->{_recips}}) {
1219 next if ($self->{_del_tree}{$r});
1222 $o .= " To: " . join(';', @r);
1223 if (scalar(@vars)) {
1224 $o .= " Vars: ".join(';',map { "$_='".$self->get_var($_)."'" } (@vars));
1226 } elsif ($self->{_output_flatq}) {
1227 $o .= " *** frozen ***" if ($self->{_vars}{deliver_freeze});
1229 foreach my $r (keys %{$self->{_recips}}) {
1230 next if ($self->{_del_tree}{$r});
1233 $o .= " " . join(' ', @r);
1242 my $fh = shift || \*STDOUT;
1243 return if ($self->{_delivered});
1245 print $fh $self->format_message();
1251 foreach my $k (sort keys %$self) {
1252 my $r = ref($self->{$k});
1253 if ($r eq 'ARRAY') {
1254 printf "%20s <<EOM\n", $k;
1255 print @{$self->{$k}}, "EOM\n";
1256 } elsif ($r eq 'HASH') {
1257 printf "%20s <<EOM\n", $k;
1258 foreach (sort keys %{$self->{$k}}) {
1259 printf "%20s %s\n", $_, $self->{$k}{$_};
1263 printf "%20s %s\n", $k, $self->{$k};
1274 exipick - selectively display messages from an Exim queue
1278 exipick [<options>] [<criterion> [<criterion> ...]]
1279 exipick --help|--man
1283 B<exipick> 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. B<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.
1287 Delete every frozen message from queue:
1289 exipick -zi | xargs exim -Mrm
1291 Show only messages which have not yet been virus scanned:
1293 exipick '$received_protocol ne virus-scanned'
1295 Run the queue in a semi-random order:
1297 exipick -i --random | xargs exim -M
1299 Show the count and total size of all messages which either originated from localhost or have a received protocol of 'local':
1301 exipick --or --size --bpc \
1302 '$sender_host_address eq 127.0.0.1' \
1303 '$received_protocol eq local'
1305 Display all messages received on the MSA port, ordered first by the sender's email domain and then by the size of the emails:
1307 exipick --sort sender_address_domain,message_size \
1308 '$received_port == 587'
1310 Display only messages whose every recipient is in the example.com domain, also listing the IP address of the sending host:
1312 exipick --show-vars sender_host_address \
1313 '$each_recipients = example.com'
1315 Same as above, but show values for all defined variables starting with sender_ and the number of recipients:
1317 exipick --show-vars ^sender_,recipients_count \
1318 '$each_recipients = example.com'
1326 Display messages matching all criteria (default)
1330 Display messages in brief format (exiqgrep)
1332 =item B<-bp> | B<-l>
1334 Display messages in standard mailq format (default).
1339 Same as C<-bp>, show generated addresses also (exim)
1343 Show a count of matching messages (exim)
1347 Same as C<-bp --unsorted> (exim)
1351 Same as C<-bpa --unsorted> (exim)
1355 Same as C<-bpu --unsorted> (exim)
1359 Same as C<-bp>, but only show undelivered messages (exim)
1361 =item B<-C> | B<--config> I<config>
1363 Use I<config> to determine the proper spool directory. (See C<--spool>
1364 or C<--input> for alternative ways to specify the directories to operate on.)
1368 Show a count of matching messages (exiqgrep)
1372 Make operators involving C<=> honor case
1376 Override the default local character set for C<$header_> decoding
1378 =item B<-f> I<regexp>
1380 Same as C<< $sender_address =~ /<regexp>/ >> (exiqgrep). Note that this preserves the default case sensitivity of exiqgrep's interface.
1384 Same as C<--input-dir Finput>. F<Finput> is where exim copies frozen messages when compiled with SUPPORT_MOVE_FROZEN_MESSAGES.
1388 Use a single-line output format
1390 =item B<--freeze> I<cache file>
1392 Save queue information in an quickly retrievable format
1400 Display only the message IDs (exiqgrep)
1402 =item B<--input-dir> I<inputname>
1404 Set the name of the directory under the spool directory. By default this is F<input>. If this starts with F</>,
1405 the value of C<--spool> is ignored. See also C<--finput>.
1411 =item B<-o> I<seconds>
1413 Same as C<< $message_age > <seconds> >> (exiqgrep)
1417 Display messages matching any criteria
1419 =item B<--queue> I<name>
1421 Name of the queue (default: ''). See "named queues" in the spec.
1423 =item B<-r> I<regexp>
1425 Same as C<< $recipients =~ /<regexp>/ >> (exiqgrep). Note that this preserves the default case sensitivity of exiqgrep's interface.
1429 Display messages in random order
1431 =item B<--reverse> | B<-R>
1433 Display messages in reverse order (exiqgrep: C<-R>)
1435 =item B<-s> I<string>
1437 Same as C<< $shown_message_size eq <string> >> (exiqgrep)
1439 =item B<--spool> I<path>
1441 Set the path to the exim spool to use. This value will have the arguments to C<--queue>, and C<--input> or F<input> appended, or be ignored if C<--input> is a full path. If not specified, B<exipick> uses the value from C<exim [-C config] -n -bP spool_directory>, and if this call fails, the F</opt/exim/spool> from build time (F<Local/Makefile>) is used. See also C<--config>.
1443 =item B<--show-rules>
1445 Show the internal representation of each criterion specified
1447 =item B<--show-tests>
1449 Show the result of each criterion on each message
1451 =item B<--show-vars> I<variable>[,I<variable>...]
1453 Show the value for I<variable> for each displayed message. I<variable> will be a regular expression if it begins with a circumflex.
1457 Show the total bytes used by each displayed message
1459 =item B<--thaw> I<cache file>
1461 Read queue information cached from a previous C<--freeze> run
1463 =item B<--sort> I<variable>[,I<variable>...]
1465 Display matching messages sorted according to I<variable>
1469 Do not apply any sorting to output
1473 Display the version of this command
1477 Same as C<!$deliver_freeze> (exiqgrep)
1481 Same as C<< $message_age < <seconds> >> (exiqgrep)
1485 Same as C<$deliver_freeze> (exiqgrep)
1491 B<Exipick> decides which messages to display by applying a test against each message. The rules take the general form of "I<VARIABLE> I<OPERATOR> I<VALUE>". For example, C<< $message_age > 60 >>. When B<exipick> is deciding which messages to display, it checks the C<$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.
1493 Multiple 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 C<--or> option is used, a message is displayed as long as any of the criteria evaluate to true.
1495 See the VARIABLES and OPERATORS sections below for more details
1503 Boolean variables are checked simply by being true or false. There is no real operator except negation. Examples of valid boolean tests:
1510 Valid 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 multiplied by 86400, 3600, 60, 1, 1048576, 1024, or 1 respectively. Examples of valid numeric tests:
1513 $local_interface == 587
1518 The string operators are =, eq, ne, =~, and !~. With the exception of C<< = >>, 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 C<< = >> operator behaves just like =~ but you are not required to place // around the regular expression. Examples of valid string tests:
1520 $received_protocol eq esmtp
1521 $sender_address = example.com
1522 $each_recipients =~ /^a[a-z]{2,3}@example.com$/
1526 There 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, C<< !$acl_m1 =~ /^DENY$/ >> is the same as C<< $acl_m1 !~ /^DENY$/ >>. Finally, every test can be specified by using the command line argument C<--not>. This is functionally equivalent to adding a ! to the beginning of every test.
1532 With 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.
1534 Internally, all variables are represented as strings, meaning any operator will work on any variable. This means that C<< $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.
1537 B - Boolean variables
1538 S - String variables
1539 N - Numeric variables
1540 . - Standard variable matching Exim's content definition
1541 # - Standard variable, contents differ from Exim's definition
1542 + - Non-standard variable
1546 =item S . B<$acl_c0>-B<$acl_c9>, B<$acl_m0>-B<$acl_m9>
1548 User definable variables.
1550 =item B + B<$allow_unqualified_recipient>
1552 TRUE if unqualified recipient addresses are permitted in header lines.
1554 =item B + B<$allow_unqualified_sender>
1556 TRUE if unqualified sender addresses are permitted in header lines.
1558 =item S . B<$authenticated_id>
1560 Optional saved information from authenticators, or the login name of the calling process for locally submitted messages.
1562 =item S . B<$authenticated_sender>
1564 The value of AUTH= param for smtp messages, or a generated value from the calling processes login and qualify domain for locally submitted messages.
1566 =item S . B<$bheader_*>, B<$bh_*>
1568 Value of the header(s) with the same name with any RFC2047 words decoded if present. See section 11.5 of Exim's spec.txt for full details.
1570 =item S + B<$bmi_verdicts>
1572 The verdict string provided by a Brightmail content scan
1574 =item N . B<$body_linecount>
1576 The number of lines in the message's body.
1578 =item N . B<$body_zerocount>
1580 The number of binary zero bytes in the message's body.
1582 =item S + B<$data_path>
1584 The path to the body file's location in the filesystem.
1586 =item B + B<$deliver_freeze>
1588 TRUE if the message is currently frozen.
1590 =item N + B<$deliver_frozen_at>
1592 The epoch time at which message was frozen.
1594 =item B + B<$dont_deliver>
1596 TRUE if, under normal circumstances, Exim will not try to deliver the message.
1598 =item S + B<$each_recipients>
1600 This is a pseudo variable which allows you to apply a test against each address in $recipients individually. Whereas C<< $recipients =~ /@aol.com/ >> will match if any recipient address contains aol.com, C<< $each_recipients =~ /@aol.com$/ >> will only be true if every recipient matches that pattern. Note that this obeys C<--and> or C<--or> being set. Using it with C<--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.
1602 =item S + B<$each_recipients_del>
1604 Like $each_recipients, but for $recipients_del
1606 =item S + B<$each_recipients_undel>
1608 Like $each_recipients, but for $recipients_undel
1610 =item B . B<$first_delivery>
1612 TRUE if the message has never been deferred.
1614 =item S . B<$header_*>, B<$h_*>
1616 This will always match the contents of the corresponding $bheader_* variable currently (the same behaviour Exim displays when iconv is not installed).
1618 =item S + B<$header_path>
1620 The path to the header file's location in the filesystem.
1622 =item B . B<$host_lookup_deferred>
1624 TRUE if there was an attempt to look up the host's name from its IP address, but an error occurred that during the attempt.
1626 =item B . B<$host_lookup_failed>
1628 TRUE if there was an attempt to look up the host's name from its IP address, but the attempt returned a negative result.
1630 =item B + B<$local_error_message>
1632 TRUE if the message is a locally-generated error message.
1634 =item S . B<$local_scan_data>
1636 The text returned by the local_scan() function when a message is received.
1638 =item B . B<$manually_thawed>
1640 TRUE when the message has been manually thawed.
1642 =item N . B<$max_received_linelength>
1644 The number of bytes in the longest line that was received as part of the message, not counting line termination characters.
1646 =item N . B<$message_age>
1648 The number of seconds since the message was received.
1650 =item S # B<$message_body>
1652 The 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.
1654 =item B + B<$message_body_missing>
1656 TRUE is a message's spool data file (-D file) is missing or unreadable.
1658 =item N . B<$message_body_size>
1660 The size of the body in bytes.
1662 =item S . B<$message_exim_id>, B<$message_id>
1664 The unique message id that is used by Exim to identify the message. $message_id is deprecated as of Exim 4.53.
1666 =item S . B<$message_headers>
1668 A concatenation of all the header lines except for lines added by routers or transports. RFC2047 decoding is performed
1670 =item S . B<$message_headers_raw>
1672 A concatenation of all the header lines except for lines added by routers or transports. No decoding or translation is performed.
1674 =item N . B<$message_linecount>
1676 The number of lines in the entire message (body and headers).
1678 =item N . B<$message_size>
1680 The size of the message in bytes.
1682 =item N . B<$originator_gid>
1684 The group id under which the process that called Exim was running as when the message was received.
1686 =item S + B<$originator_login>
1688 The login of the process which called Exim.
1690 =item N . B<$originator_uid>
1692 The user id under which the process that called Exim was running as when the message was received.
1694 =item S . B<$received_ip_address>, B<$interface_address>
1696 The address of the local IP interface for network-originated messages. $interface_address is deprecated as of Exim 4.64
1698 =item N . B<$received_port>, B<$interface_port>
1700 The local port number if network-originated messages. $interface_port is deprecated as of Exim 4.64
1702 =item N . B<$received_count>
1704 The number of Received: header lines in the message.
1706 =item S . B<$received_protocol>
1708 The name of the protocol by which the message was received.
1710 =item N . B<$received_time>
1712 The epoch time at which the message was received.
1714 =item S # B<$recipients>
1716 The list of envelope recipients for a message. Unlike Exim's version, this variable always contains every recipient of the message. The recipients are separated by a comma and a space. See also $each_recipients.
1718 =item N . B<$recipients_count>
1720 The number of envelope recipients for the message.
1722 =item S + B<$recipients_del>
1724 The 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.
1726 =item N + B<$recipients_del_count>
1728 The 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.
1730 =item S + B<$recipients_undel>
1732 The 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.
1734 =item N + B<$recipients_undel_count>
1736 The number of envelope recipients for the message which have not yet been delivered.
1738 =item S . B<$reply_address>
1740 The contents of the Reply-To: header line if one exists and it is not empty, or otherwise the contents of the From: header line.
1742 =item S . B<$rheader_*>, B<$rh_*>
1744 The value of the message's header(s) with the same name. See section 11.5 of Exim's spec.txt for full description.
1746 =item S . B<$sender_address>
1748 The sender's address that was received in the message's envelope. For bounce messages, the value of this variable is the empty string.
1750 =item S . B<$sender_address_domain>
1752 The domain part of $sender_address.
1754 =item S . B<$sender_address_local_part>
1756 The local part of $sender_address.
1758 =item S . B<$sender_helo_name>
1760 The HELO or EHLO value supplied for smtp or bsmtp messages.
1762 =item S . B<$sender_host_address>
1764 The remote host's IP address.
1766 =item S . B<$sender_host_authenticated>
1768 The name of the authenticator driver which successfully authenticated the client from which the message was received.
1770 =item S . B<$sender_host_name>
1772 The remote host's name as obtained by looking up its IP address.
1774 =item N . B<$sender_host_port>
1776 The port number that was used on the remote host for network-originated messages.
1778 =item S . B<$sender_ident>
1780 The 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.
1782 =item B + B<$sender_local>
1784 TRUE if the message was locally generated.
1786 =item B + B<$sender_set_untrusted>
1788 TRUE if the envelope sender of this message was set by an untrusted local caller.
1790 =item S + B<$shown_message_size>
1792 This non-standard variable contains the formatted size string. That is, for a message whose $message_size is 66566 bytes, $shown_message_size is 65K.
1794 =item S . B<$smtp_active_hostname>
1796 The value of the active host name when the message was received, as specified by the "smtp_active_hostname" option.
1798 =item S . B<$spam_score>
1800 The spam score of the message, for example '3.4' or '30.5'. (Requires exiscan or WITH_CONTENT_SCAN)
1802 =item S . B<$spam_score_int>
1804 The spam score of the message, multiplied by ten, as an integer value. For instance '34' or '305'. (Requires exiscan or WITH_CONTENT_SCAN)
1806 =item B . B<$tls_certificate_verified>
1808 TRUE if a TLS certificate was verified when the message was received.
1810 =item S . B<$tls_cipher>
1812 The cipher suite that was negotiated for encrypted SMTP connections.
1814 =item S . B<$tls_peerdn>
1816 The value of the Distinguished Name of the certificate if Exim is configured to request one
1818 =item S . B<$tls_sni>
1820 The value of the Server Name Indication TLS extension sent by a client, if one was sent.
1822 =item N + B<$warning_count>
1824 The number of delay warnings which have been sent for this message.
1832 =item EMAIL: proj-exipick@jetmore.net
1834 =item HOME: L<https://jetmore.org/john/code/#exipick>
1836 This script was incorporated into the main Exim distribution some years ago.