Exipick: handle tainted options in spool files
[exim.git] / src / src / exipick.src
CommitLineData
059ec3d9 1#!PERL_COMMAND
f9ba5e22 2# Copyright (c) 1995 - 2018 University of Cambridge.
9242a7e8
JH
3# See the file NOTICE for conditions of use and distribution.
4
059ec3d9 5
57397119
HSHR
6# This variables should be set by the building process
7my $spool = 'SPOOL_DIRECTORY'; # may be overridden later
8my $exim = 'BIN_DIRECTORY/exim';
9
0ea2a468
JJ
10# Need to set this dynamically during build, but it's not used right now anyway.
11my $charset = 'ISO-8859-1';
059ec3d9 12
e22ca4ac
JJ
13# use 'exipick --help' to view documentation for this program.
14# Documentation also viewable online at
15# http://www.exim.org/eximwiki/ToolExipickManPage
16
059ec3d9 17use strict;
4d3d955f 18BEGIN { pop @INC if $INC[-1] eq '.' };
059ec3d9 19use Getopt::Long;
983da878 20use File::Basename;
cc05007f 21use Pod::Usage;
059ec3d9 22
cc05007f 23my $p_name = basename $0;
1a41defa 24my $p_version = "20100323.0";
cc05007f 25my $p_usage = "Usage: $p_name [--help|--man|--version] (see --help for details)";
059ec3d9 26my $p_cp = <<EOM;
465e92cf 27 Copyright (c) 2003-2010 John Jetmore <jj33\@pobox.com>
059ec3d9
PH
28
29 This program is free software; you can redistribute it and/or modify
30 it under the terms of the GNU General Public License as published by
31 the Free Software Foundation; either version 2 of the License, or
32 (at your option) any later version.
33
34 This program is distributed in the hope that it will be useful,
35 but WITHOUT ANY WARRANTY; without even the implied warranty of
36 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37 GNU General Public License for more details.
38
39 You should have received a copy of the GNU General Public License
40 along with this program; if not, write to the Free Software
e22ca4ac 41 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
059ec3d9 42EOM
059ec3d9 43
bf759a8b
PH
44$| = 1; # unbuffer STDOUT
45
059ec3d9
PH
46Getopt::Long::Configure("bundling_override");
47GetOptions(
a2405d83 48 'spool=s' => \$G::spool, # exim spool dir
c4d5e329 49 'C|Config=s' => \$G::config, # use alternative Exim configuration file
edae0343 50 'input-dir=s' => \$G::input_dir, # name of the "input" dir
a6d70503 51 'queue=s' => \$G::queue, # name of the queue
edae0343 52 'finput' => \$G::finput, # same as "--input-dir Finput"
bf759a8b
PH
53 'bp' => \$G::mailq_bp, # List the queue (noop - default)
54 'bpa' => \$G::mailq_bpa, # ... with generated address as well
55 'bpc' => \$G::mailq_bpc, # ... but just show a count of messages
56 'bpr' => \$G::mailq_bpr, # ... do not sort
57 'bpra' => \$G::mailq_bpra, # ... with generated addresses, unsorted
58 'bpru' => \$G::mailq_bpru, # ... only undelivered addresses, unsorted
59 'bpu' => \$G::mailq_bpu, # ... only undelivered addresses
60 'and' => \$G::and, # 'and' the criteria (default)
61 'or' => \$G::or, # 'or' the criteria
a2405d83
JJ
62 'f=s' => \$G::qgrep_f, # from regexp
63 'r=s' => \$G::qgrep_r, # recipient regexp
64 's=s' => \$G::qgrep_s, # match against size field
65 'y=s' => \$G::qgrep_y, # message younger than (secs)
66 'o=s' => \$G::qgrep_o, # message older than (secs)
bf759a8b
PH
67 'z' => \$G::qgrep_z, # frozen only
68 'x' => \$G::qgrep_x, # non-frozen only
69 'c' => \$G::qgrep_c, # display match count
70 'l' => \$G::qgrep_l, # long format (default)
71 'i' => \$G::qgrep_i, # message ids only
72 'b' => \$G::qgrep_b, # brief format
e22ca4ac
JJ
73 'size' => \$G::size_only, # sum the size of the matching msgs
74 'not' => \$G::negate, # flip every test
75 'R|reverse' => \$G::reverse, # reverse output (-R is qgrep option)
a2405d83
JJ
76 'sort=s' => \@G::sort, # allow you to choose variables to sort by
77 'freeze=s' => \$G::freeze, # freeze data in this file
78 'thaw=s' => \$G::thaw, # thaw data from this file
9cf6b11a 79 'unsorted' => \$G::unsorted, # unsorted, regardless of output format
e22ca4ac 80 'random' => \$G::random, # (poorly) randomize evaluation order
bf759a8b
PH
81 'flatq' => \$G::flatq, # brief format
82 'caseful' => \$G::caseful, # in '=' criteria, respect case
83 'caseless' => \$G::caseless, # ...ignore case (default)
0ea2a468 84 'charset=s' => \$charset, # charset for $bh and $h variables
a2405d83 85 'show-vars=s' => \$G::show_vars, # display the contents of these vars
0ea2a468 86 'just-vars' => \$G::just_vars, # only display vars, no other info
bf759a8b 87 'show-rules' => \$G::show_rules, # display compiled match rules
983da878 88 'show-tests' => \$G::show_tests, # display tests as applied to each message
cc05007f
HSHR
89 'man' => sub { pod2usage(-verbose => 2, -exit => 0, -noperldoc => system('perldoc -V >/dev/null 2>&1')) },
90 'help' => sub { pod2usage(-verbose => 1, -exit => 0) },
983da878 91 'version' => sub {
cc05007f 92 print "$p_name: $0\n",
983da878 93 "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
02721dcd 94 "perl(runtime): $]\n";
983da878
HSHR
95 exit 0;
96 },
cc05007f 97) or pod2usage;
059ec3d9 98
4c04137d 99# if both freeze and thaw specified, only thaw as it is less destructive
9cf6b11a
JJ
100$G::freeze = undef if ($G::freeze && $G::thaw);
101freeze_start() if ($G::freeze);
102thaw_start() if ($G::thaw);
103
e22ca4ac
JJ
104# massage sort options (make '$var,Var:' be 'var','var')
105for (my $i = scalar(@G::sort)-1; $i >= 0; $i--) {
106 $G::sort[$i] = lc($G::sort[$i]);
107 $G::sort[$i] =~ s/[\$:\s]//g;
108 if ((my @vars = split(/,/, $G::sort[$i])) > 1) {
109 $G::sort[$i] = $vars[0]; shift(@vars); # replace current slot w/ first var
110 splice(@G::sort, $i+1, 0, @vars); # add other vars after current pos
111 }
112}
113push(@G::sort, "message_exim_id") if (@G::sort);
114die "empty value provided to --sort not allowed, exiting\n"
115 if (grep /^\s*$/, @G::sort);
116
117# massage the qgrep options into standard criteria
5f970846
PH
118push(@ARGV, "\$sender_address =~ /$G::qgrep_f/") if ($G::qgrep_f);
119push(@ARGV, "\$recipients =~ /$G::qgrep_r/") if ($G::qgrep_r);
120push(@ARGV, "\$shown_message_size eq $G::qgrep_s") if ($G::qgrep_s);
121push(@ARGV, "\$message_age < $G::qgrep_y") if ($G::qgrep_y);
122push(@ARGV, "\$message_age > $G::qgrep_o") if ($G::qgrep_o);
123push(@ARGV, "\$deliver_freeze") if ($G::qgrep_z);
124push(@ARGV, "!\$deliver_freeze") if ($G::qgrep_x);
e22ca4ac 125
bf759a8b
PH
126$G::mailq_bp = $G::mailq_bp; # shut up -w
127$G::and = $G::and; # shut up -w
b3f69ca8 128$G::msg_ids = {}; # short circuit when crit is only MID
bf759a8b 129$G::caseless = $G::caseful ? 0 : 1; # nocase by default, case if both
b3f69ca8 130@G::recipients_crit = (); # holds per-recip criteria
57397119 131$spool = defined $G::spool ? $G::spool
c4d5e329
HSHR
132 : do { chomp($_ = `$exim @{[defined $G::config ? "-C $G::config" : '']} -n -bP spool_directory`)
133 and $_ or $spool };
a6d70503
HSHR
134my $input_dir = (defined $G::queue ? "$G::queue/" : '')
135 . (defined $G::input_dir || ($G::finput ? "Finput" : "input"));
9cf6b11a
JJ
136my $count_only = 1 if ($G::mailq_bpc || $G::qgrep_c);
137my $unsorted = 1 if ($G::mailq_bpr || $G::mailq_bpra ||
138 $G::mailq_bpru || $G::unsorted);
139my $msg = $G::thaw ? thaw_message_list()
edae0343 140 : get_all_msgs($spool, $input_dir, $unsorted,
e22ca4ac 141 $G::reverse, $G::random);
9cf6b11a 142die "Problem accessing thaw file\n" if ($G::thaw && !$msg);
bf759a8b
PH
143my $crit = process_criteria(\@ARGV);
144my $e = Exim::SpoolFile->new();
b3f69ca8
JJ
145my $tcount = 0 if ($count_only); # holds count of all messages
146my $mcount = 0 if ($count_only); # holds count of matching messages
e22ca4ac 147my $total_size = 0 if ($G::size_only);
bf759a8b
PH
148$e->set_undelivered_only(1) if ($G::mailq_bpru || $G::mailq_bpu);
149$e->set_show_generated(1) if ($G::mailq_bpra || $G::mailq_bpa);
150$e->output_long() if ($G::qgrep_l);
151$e->output_idonly() if ($G::qgrep_i);
152$e->output_brief() if ($G::qgrep_b);
153$e->output_flatq() if ($G::flatq);
0ea2a468 154$e->output_vars_only() if ($G::just_vars && $G::show_vars);
059ec3d9 155$e->set_show_vars($G::show_vars) if ($G::show_vars);
edae0343 156$e->set_spool($spool, $input_dir);
059ec3d9
PH
157
158MSG:
159foreach my $m (@$msg) {
af66f652
PH
160 next if (scalar(keys(%$G::msg_ids)) && !$G::or
161 && !$G::msg_ids->{$m->{message}});
9cf6b11a
JJ
162 if ($G::thaw) {
163 my $data = thaw_data();
164 if (!$e->restore_state($data)) {
165 warn "Couldn't thaw $data->{_message}: ".$e->error()."\n";
166 next MSG;
167 }
168 } else {
169 if (!$e->parse_message($m->{message}, $m->{path})) {
170 warn "Couldn't parse $m->{message}: ".$e->error()."\n";
171 next MSG;
172 }
059ec3d9
PH
173 }
174 $tcount++;
175 my $match = 0;
bf759a8b
PH
176 my @local_crit = ();
177 foreach my $c (@G::recipients_crit) { # handle each_recip* vars
178 foreach my $addr (split(/, /, $e->get_var($c->{var}))) {
179 my %t = ( 'cmp' => $c->{cmp}, 'var' => $c->{var} );
180 $t{cmp} =~ s/"?\$var"?/'$addr'/;
181 push(@local_crit, \%t);
182 }
183 }
ee744174 184 if ($G::show_tests) { print $e->get_var('message_exim_id'), "\n"; }
059ec3d9 185 CRITERIA:
bf759a8b 186 foreach my $c (@$crit, @local_crit) {
059ec3d9
PH
187 my $var = $e->get_var($c->{var});
188 my $ret = eval($c->{cmp});
bf759a8b
PH
189 if ($G::show_tests) {
190 printf " %25s = '%s'\n %25s => $ret\n",$c->{var},$var,$c->{cmp},$ret;
191 }
059ec3d9
PH
192 if ($@) {
193 print STDERR "Error in eval '$c->{cmp}': $@\n";
11121d3d 194 next MSG;
059ec3d9
PH
195 } elsif ($ret) {
196 $match = 1;
11121d3d
JJ
197 if ($G::or) { last CRITERIA; }
198 else { next CRITERIA; }
059ec3d9 199 } else { # no match
11121d3d
JJ
200 if ($G::or) { next CRITERIA; }
201 else { next MSG; }
059ec3d9
PH
202 }
203 }
b3f69ca8
JJ
204
205 # skip this message if any criteria were supplied and it didn't match
11121d3d 206 next MSG if ((scalar(@$crit) || scalar(@local_crit)) && !$match);
059ec3d9 207
e22ca4ac 208 if ($count_only || $G::size_only) {
059ec3d9 209 $mcount++;
e22ca4ac 210 $total_size += $e->get_var('message_size');
059ec3d9 211 } else {
e22ca4ac
JJ
212 if (@G::sort) {
213 # if we are defining criteria to sort on, save the message here. If
214 # we don't save here and do the sort later, we have a chicken/egg
215 # problem
216 push(@G::to_print, { vars => {}, output => "" });
217 foreach my $var (@G::sort) {
218 # save any values we want to sort on. I don't like doing the internal
219 # struct access here, but calling get_var a bunch can be _slow_ =(
220 $G::sort_type{$var} ||= '<=>';
221 $G::to_print[-1]{vars}{$var} = $e->{_vars}{$var};
222 $G::sort_type{$var} = 'cmp' if ($G::to_print[-1]{vars}{$var} =~ /\D/);
223 }
224 $G::to_print[-1]{output} = $e->format_message();
225 } else {
226 print $e->format_message();
227 }
059ec3d9 228 }
9cf6b11a
JJ
229
230 if ($G::freeze) {
231 freeze_data($e->get_state());
232 push(@G::frozen_msgs, $m);
233 }
059ec3d9
PH
234}
235
e22ca4ac
JJ
236if (@G::to_print) {
237 msg_sort(\@G::to_print, \@G::sort, $G::reverse);
238 foreach my $msg (@G::to_print) {
239 print $msg->{output};
240 }
241}
242
243if ($G::qgrep_c) {
244 print "$mcount matches out of $tcount messages" .
245 ($G::size_only ? " ($total_size)" : "") . "\n";
246} elsif ($G::mailq_bpc) {
247 print "$mcount" . ($G::size_only ? " ($total_size)" : "") . "\n";
248} elsif ($G::size_only) {
249 print "$total_size\n";
059ec3d9
PH
250}
251
9cf6b11a
JJ
252if ($G::freeze) {
253 freeze_message_list(\@G::frozen_msgs);
254 freeze_end();
255} elsif ($G::thaw) {
256 thaw_end();
257}
258
059ec3d9
PH
259exit;
260
e22ca4ac
JJ
261# sender_address_domain,shown_message_size
262sub msg_sort {
263 my $msgs = shift;
264 my $vars = shift;
265 my $reverse = shift;
266
267 my @pieces = ();
268 foreach my $v (@G::sort) {
269 push(@pieces, "\$a->{vars}{\"$v\"} $G::sort_type{$v} \$b->{vars}{\"$v\"}");
270 }
271 my $sort_str = join(" || ", @pieces);
272
273 @$msgs = sort { eval $sort_str } (@$msgs);
274 @$msgs = reverse(@$msgs) if ($reverse);
275}
276
277sub try_load {
278 my $mod = shift;
279
280 eval("use $mod");
281 return $@ ? 0 : 1;
282}
283
9cf6b11a
JJ
284# FREEZE FILE FORMAT:
285# message_data_bytes
286# message_data
287# <...>
288# EOM
289# message_list
290# message_list_bytes <- 10 bytes, zero-packed, plus \n
291
292sub freeze_start {
293 eval("use Storable");
294 die "Storable module not found: $@\n" if ($@);
295 open(O, ">$G::freeze") || die "Can't open freeze file $G::freeze: $!\n";
296 $G::freeze_handle = \*O;
297}
298
299sub freeze_end {
300 close($G::freeze_handle);
301}
302
303sub thaw_start {
304 eval("use Storable");
305 die "Storable module not found: $@\n" if ($@);
306 open(I, "<$G::thaw") || die "Can't open freeze file $G::thaw: $!\n";
307 $G::freeze_handle = \*I;
308}
309
310sub thaw_end {
311 close($G::freeze_handle);
312}
313
314sub freeze_data {
315 my $h = Storable::freeze($_[0]);
316 print $G::freeze_handle length($h)+1, "\n$h\n";
317}
318
319sub freeze_message_list {
320 my $h = Storable::freeze($_[0]);
321 my $l = length($h) + 1;
322 printf $G::freeze_handle "EOM\n$l\n$h\n%010d\n", $l+11+length($l)+1;
323}
324
325sub thaw_message_list {
326 my $orig_pos = tell($G::freeze_handle);
327 seek($G::freeze_handle, -11, 2);
328 chomp(my $bytes = <$G::freeze_handle>);
329 seek($G::freeze_handle, $bytes * -1, 2);
330 my $obj = thaw_data();
331 seek($G::freeze_handle, 0, $orig_pos);
332 return($obj);
333}
334
335sub thaw_data {
336 my $obj;
337 chomp(my $bytes = <$G::freeze_handle>);
338 return(undef) if (!$bytes || $bytes eq 'EOM');
339 my $read = read(I, $obj, $bytes);
340 die "Format error in thaw file (expected $bytes bytes, got $read)\n"
341 if ($bytes != $read);
342 chomp($obj);
343 return(Storable::thaw($obj));
344}
345
059ec3d9
PH
346sub process_criteria {
347 my $a = shift;
348 my @c = ();
349 my $e = 0;
350
351 foreach (@$a) {
e22ca4ac 352 foreach my $t ('@') { s/$t/\\$t/g; }
059ec3d9
PH
353 if (/^(.*?)\s+(<=|>=|==|!=|<|>)\s+(.*)$/) {
354 #print STDERR "found as integer\n";
355 my $v = $1; my $o = $2; my $n = $3;
0ea2a468
JJ
356 if ($n =~ /^(-?[\d\.]+)M$/) { $n = $1 * 1024 * 1024; }
357 elsif ($n =~ /^(-?[\d\.]+)K$/) { $n = $1 * 1024; }
358 elsif ($n =~ /^(-?[\d\.]+)B?$/) { $n = $1; }
359 elsif ($n =~ /^(-?[\d\.]+)d$/) { $n = $1 * 60 * 60 * 24; }
360 elsif ($n =~ /^(-?[\d\.]+)h$/) { $n = $1 * 60 * 60; }
361 elsif ($n =~ /^(-?[\d\.]+)m$/) { $n = $1 * 60; }
362 elsif ($n =~ /^(-?[\d\.]+)s?$/) { $n = $1; }
059ec3d9
PH
363 else {
364 print STDERR "Expression $_ did not parse: numeric comparison with ",
365 "non-number\n";
366 $e = 1;
367 next;
368 }
9cf6b11a 369 push(@c, { var => lc($v), cmp => "(\$var $o $n)" });
059ec3d9
PH
370 } elsif (/^(.*?)\s+(=~|!~)\s+(.*)$/) {
371 #print STDERR "found as string regexp\n";
9cf6b11a 372 push(@c, { var => lc($1), cmp => "(\"\$var\" $2 $3)" });
059ec3d9
PH
373 } elsif (/^(.*?)\s+=\s+(.*)$/) {
374 #print STDERR "found as bare string regexp\n";
af66f652 375 my $case = $G::caseful ? '' : 'i';
9cf6b11a 376 push(@c, { var => lc($1), cmp => "(\"\$var\" =~ /$2/$case)" });
e22ca4ac
JJ
377 # quote special characters in perl text string
378 #foreach my $t ('@') { $c[-1]{cmp} =~ s/$t/\\$t/g; }
059ec3d9
PH
379 } elsif (/^(.*?)\s+(eq|ne)\s+(.*)$/) {
380 #print STDERR "found as string cmp\n";
af66f652 381 my $var = lc($1); my $op = $2; my $val = $3;
5f970846 382 $val =~ s|^(['"])(.*)\1$|$2|;
9cf6b11a 383 push(@c, { var => $var, cmp => "(\"\$var\" $op \"$val\")" });
ee744174 384 if (($var eq 'message_id' || $var eq 'message_exim_id') && $op eq "eq") {
af66f652
PH
385 #print STDERR "short circuit @c[-1]->{cmp} $val\n";
386 $G::msg_ids->{$val} = 1;
387 }
e22ca4ac 388 #foreach my $t ('@') { $c[-1]{cmp} =~ s/$t/\\$t/g; }
9cf6b11a 389 } elsif (/^(\S+)$/) {
059ec3d9 390 #print STDERR "found as boolean\n";
9cf6b11a 391 push(@c, { var => lc($1), cmp => "(\$var)" });
059ec3d9
PH
392 } else {
393 print STDERR "Expression $_ did not parse\n";
394 $e = 1;
0ea2a468 395 next;
059ec3d9 396 }
9cf6b11a 397 # assign the results of the cmp test here (handle "!" negation)
e22ca4ac 398 # also handle global --not negation
9cf6b11a 399 if ($c[-1]{var} =~ s|^!||) {
e22ca4ac 400 $c[-1]{cmp} .= $G::negate ? " ? 1 : 0" : " ? 0 : 1";
9cf6b11a 401 } else {
e22ca4ac 402 $c[-1]{cmp} .= $G::negate ? " ? 0 : 1" : " ? 1 : 0";
9cf6b11a 403 }
5dda37a2 404 # support the each_* pseudo variables. Steal the criteria off of the
bf759a8b
PH
405 # queue for special processing later
406 if ($c[-1]{var} =~ /^each_(recipients(_(un)?del)?)$/) {
407 my $var = $1;
408 push(@G::recipients_crit,pop(@c));
409 $G::recipients_crit[-1]{var} = $var; # remove each_ from the variable
410 }
059ec3d9
PH
411 }
412
413 exit(1) if ($e);
414
415 if ($G::show_rules) { foreach (@c) { print "$_->{var}\t$_->{cmp}\n"; } }
416
417 return(\@c);
418}
419
420sub get_all_msgs {
edae0343
JJ
421 my $d = shift();
422 my $i = shift();
e22ca4ac
JJ
423 my $u = shift; # don't sort
424 my $r = shift; # right before returning, reverse order
425 my $o = shift; # if true, randomize list order before returning
059ec3d9
PH
426 my @m = ();
427
edae0343
JJ
428 if ($i =~ m|^/|) { $d = $i; } else { $d = $d . '/' . $i; }
429
059ec3d9
PH
430 opendir(D, "$d") || die "Couldn't opendir $d: $!\n";
431 foreach my $e (grep !/^\./, readdir(D)) {
432 if ($e =~ /^[a-zA-Z0-9]$/) {
433 opendir(DD, "$d/$e") || next;
434 foreach my $f (grep !/^\./, readdir(DD)) {
9cf6b11a 435 push(@m, { message => $1, path => "$d/$e" }) if ($f =~ /^(.{16})-H$/);
059ec3d9
PH
436 }
437 closedir(DD);
438 } elsif ($e =~ /^(.{16})-H$/) {
9cf6b11a 439 push(@m, { message => $1, path => $d });
059ec3d9
PH
440 }
441 }
442 closedir(D);
443
e22ca4ac
JJ
444 if ($o) {
445 my $c = scalar(@m);
446 # loop twice to pretend we're doing a good job of mixing things up
447 for (my $i = 0; $i < 2 * $c; $i++) {
448 my $rand = int(rand($c));
449 ($m[$i % $c],$m[$rand]) = ($m[$rand],$m[$i % $c]);
450 }
451 } elsif (!$u) {
452 @m = sort { $a->{message} cmp $b->{message} } @m;
453 }
454 @m = reverse(@m) if ($r);
455
456 return(\@m);
059ec3d9
PH
457}
458
459BEGIN {
460
461package Exim::SpoolFile;
462
b3f69ca8
JJ
463# versions 4.61 and higher will not need these variables anymore, but they
464# are left for handling legacy installs
465$Exim::SpoolFile::ACL_C_MAX_LEGACY = 10;
466#$Exim::SpoolFile::ACL_M_MAX _LEGACY= 10;
059ec3d9
PH
467
468sub new {
469 my $class = shift;
470 my $self = {};
471 bless($self, $class);
472
473 $self->{_spool_dir} = '';
edae0343 474 $self->{_input_path} = '';
059ec3d9
PH
475 $self->{_undelivered_only} = 0;
476 $self->{_show_generated} = 0;
477 $self->{_output_long} = 1;
478 $self->{_output_idonly} = 0;
479 $self->{_output_brief} = 0;
480 $self->{_output_flatq} = 0;
0ea2a468 481 $self->{_output_vars_only} = 0;
5f970846 482 $self->{_show_vars} = [];
059ec3d9
PH
483
484 $self->_reset();
485 return($self);
486}
487
488sub output_long {
489 my $self = shift;
490
491 $self->{_output_long} = 1;
492 $self->{_output_idonly} = 0;
493 $self->{_output_brief} = 0;
494 $self->{_output_flatq} = 0;
0ea2a468 495 $self->{_output_vars_only} = 0;
059ec3d9
PH
496}
497
498sub output_idonly {
499 my $self = shift;
500
501 $self->{_output_long} = 0;
502 $self->{_output_idonly} = 1;
503 $self->{_output_brief} = 0;
504 $self->{_output_flatq} = 0;
0ea2a468 505 $self->{_output_vars_only} = 0;
059ec3d9
PH
506}
507
508sub output_brief {
509 my $self = shift;
510
511 $self->{_output_long} = 0;
512 $self->{_output_idonly} = 0;
513 $self->{_output_brief} = 1;
514 $self->{_output_flatq} = 0;
0ea2a468 515 $self->{_output_vars_only} = 0;
059ec3d9
PH
516}
517
518sub output_flatq {
519 my $self = shift;
520
521 $self->{_output_long} = 0;
522 $self->{_output_idonly} = 0;
523 $self->{_output_brief} = 0;
524 $self->{_output_flatq} = 1;
0ea2a468
JJ
525 $self->{_output_vars_only} = 0;
526}
527
528sub output_vars_only {
529 my $self = shift;
530
531 $self->{_output_long} = 0;
532 $self->{_output_idonly} = 0;
533 $self->{_output_brief} = 0;
534 $self->{_output_flatq} = 0;
535 $self->{_output_vars_only} = 1;
059ec3d9
PH
536}
537
538sub set_show_vars {
539 my $self = shift;
540 my $s = shift;
541
542 foreach my $v (split(/\s*,\s*/, $s)) {
5f970846 543 push(@{$self->{_show_vars}}, $v);
059ec3d9
PH
544 }
545}
546
547sub set_show_generated {
548 my $self = shift;
549 $self->{_show_generated} = shift;
550}
551
552sub set_undelivered_only {
553 my $self = shift;
554 $self->{_undelivered_only} = shift;
555}
556
557sub error {
558 my $self = shift;
559 return $self->{_error};
560}
561
562sub _error {
563 my $self = shift;
564 $self->{_error} = shift;
565 return(undef);
566}
567
568sub _reset {
569 my $self = shift;
570
571 $self->{_error} = '';
572 $self->{_delivered} = 0;
573 $self->{_message} = '';
574 $self->{_path} = '';
575 $self->{_vars} = {};
0ea2a468 576 $self->{_vars_raw} = {};
059ec3d9
PH
577
578 $self->{_numrecips} = 0;
579 $self->{_udel_tree} = {};
580 $self->{_del_tree} = {};
581 $self->{_recips} = {};
582
583 return($self);
584}
585
586sub parse_message {
587 my $self = shift;
8e669ac1 588
059ec3d9
PH
589 $self->_reset();
590 $self->{_message} = shift || return(0);
9cf6b11a 591 $self->{_path} = shift; # optional path to message
edae0343 592 return(0) if (!$self->{_input_path});
9cf6b11a 593 if (!$self->{_path} && !$self->_find_path()) {
059ec3d9
PH
594 # assume the message was delivered from under us and ignore
595 $self->{_delivered} = 1;
596 return(1);
597 }
598 $self->_parse_header() || return(0);
599
600 return(1);
601}
602
9cf6b11a
JJ
603# take the output of get_state() and set up a message internally like
604# parse_message (except from a saved data struct, not by parsing the
605# files on disk).
606sub restore_state {
607 my $self = shift;
608 my $h = shift;
609
610 return(1) if ($h->{_delivered});
611 $self->_reset();
612 $self->{_message} = $h->{_message} || return(0);
edae0343 613 return(0) if (!$self->{_input_path});
9cf6b11a
JJ
614
615 $self->{_path} = $h->{_path};
616 $self->{_vars} = $h->{_vars};
617 $self->{_numrecips} = $h->{_numrecips};
618 $self->{_udel_tree} = $h->{_udel_tree};
619 $self->{_del_tree} = $h->{_del_tree};
620 $self->{_recips} = $h->{_recips};
621
622 $self->{_vars}{message_age} = time() - $self->{_vars}{received_time};
623 return(1);
624}
625
626# This returns the state data for a specific message in a format that can
627# be later frozen back in to regain state
628#
629# after calling this function, this specific state is not expect to be
630# reused. That's because we're returning direct references to specific
631# internal structures. We're also modifying the structure ourselves
632# by deleting certain internal message variables.
633sub get_state {
634 my $self = shift;
635 my $h = {}; # this is the hash ref we'll be returning.
636
637 $h->{_delivered} = $self->{_delivered};
638 $h->{_message} = $self->{_message};
639 $h->{_path} = $self->{_path};
640 $h->{_vars} = $self->{_vars};
641 $h->{_numrecips} = $self->{_numrecips};
642 $h->{_udel_tree} = $self->{_udel_tree};
643 $h->{_del_tree} = $self->{_del_tree};
644 $h->{_recips} = $self->{_recips};
645
646 # delete some internal variables that we will rebuild later if needed
647 delete($h->{_vars}{message_body});
648 delete($h->{_vars}{message_age});
649
650 return($h);
651}
652
653# keep this sub as a feature if we ever break this module out, but do away
654# with its use in exipick (pass it in from caller instead)
059ec3d9
PH
655sub _find_path {
656 my $self = shift;
657
658 return(0) if (!$self->{_message});
edae0343 659 return(0) if (!$self->{_input_path});
059ec3d9 660
9cf6b11a
JJ
661 # test split spool first on the theory that people concerned about
662 # performance will have split spool set =).
663 foreach my $f (substr($self->{_message}, 5, 1).'/', '') {
edae0343
JJ
664 if (-f "$self->{_input_path}/$f$self->{_message}-H") {
665 $self->{_path} = "$self->{_input_path}}/$f";
059ec3d9
PH
666 return(1);
667 }
668 }
669 return(0);
670}
671
672sub set_spool {
673 my $self = shift;
674 $self->{_spool_dir} = shift;
edae0343
JJ
675 $self->{_input_path} = shift;
676 if ($self->{_input_path} !~ m|^/|) {
677 $self->{_input_path} = $self->{_spool_dir} . '/' . $self->{_input_path};
678 }
059ec3d9
PH
679}
680
a2405d83
JJ
681sub get_matching_vars {
682 my $self = shift;
683 my $e = shift;
684
685 if ($e =~ /^\^/) {
686 my @r = ();
687 foreach my $v (keys %{$self->{_vars}}) { push(@r, $v) if ($v =~ /$e/); }
688 return(@r);
689 } else {
690 return($e);
691 }
692}
693
059ec3d9
PH
694# accepts a variable with or without leading '$' or trailing ':'
695sub get_var {
696 my $self = shift;
0ea2a468
JJ
697 my $var = lc(shift); $var =~ s/^\$//; $var =~ s/:$//;
698
699 if ($var eq 'message_body' && !defined($self->{_vars}{message_body})) {
700 $self->_parse_body()
701 } elsif ($var =~ s|^([rb]?h)(eader)?_|${1}eader_| &&
702 exists($self->{_vars}{$var}) && !defined($self->{_vars}{$var}))
703 {
704 if ((my $type = $1) eq 'rh') {
705 $self->{_vars}{$var} = join('', @{$self->{_vars_raw}{$var}{vals}});
706 } else {
707 # both bh_ and h_ build their strings from rh_. Do common work here
708 my $rh = $var; $rh =~ s|^b?|r|;
709 my $comma = 1 if ($self->{_vars_raw}{$rh}{type} =~ /^[BCFRST]$/);
710 foreach (@{$self->{_vars_raw}{$rh}{vals}}) {
711 my $x = $_; # editing $_ here would change the original, which is bad
712 $x =~ s|^\s+||;
713 $x =~ s|\s+$||;
714 if ($comma) { chomp($x); $self->{_vars}{$var} .= "$x,\n"; }
715 else { $self->{_vars}{$var} .= $x; }
716 }
717 $self->{_vars}{$var} =~ s|[\s\n]*$||;
718 $self->{_vars}{$var} =~ s|,$|| if ($comma);
719 # ok, that's the preprocessing, not do specific processing for h type
720 if ($type eq 'bh') {
721 $self->{_vars}{$var} = $self->_decode_2047($self->{_vars}{$var});
722 } else {
723 $self->{_vars}{$var} =
724 $self->_decode_2047($self->{_vars}{$var}, $charset);
725 }
726 }
727 }
728 elsif ($var eq 'received_count' && !defined($self->{_vars}{received_count}))
729 {
730 $self->{_vars}{received_count} =
731 scalar(@{$self->{_vars_raw}{rheader_received}{vals}});
732 }
733 elsif ($var eq 'message_headers' && !defined($self->{_vars}{message_headers}))
734 {
735 $self->{_vars}{$var} =
736 $self->_decode_2047($self->{_vars}{message_headers_raw}, $charset);
737 chomp($self->{_vars}{$var});
738 }
739 elsif ($var eq 'reply_address' && !defined($self->{_vars}{reply_address}))
740 {
741 $self->{_vars}{reply_address} = exists($self->{_vars}{"header_reply-to"})
742 ? $self->get_var("header_reply-to") : $self->get_var("header_from");
743 }
059ec3d9 744
0ea2a468
JJ
745 #chomp($self->{_vars}{$var}); # I think this was only for headers, obsolete
746 return $self->{_vars}{$var};
747}
748
749sub _decode_2047 {
750 my $self = shift;
751 my $s = shift; # string to decode
752 my $c = shift; # target charset. If empty, just decode, don't convert
753 my $t = ''; # the translated string
754 my $e = 0; # set to true if we get an error in here anywhere
755
756 return($s) if ($s !~ /=\?/); # don't even bother to look if there's no sign
757
758 my @p = ();
759 foreach my $mw (split(/(=\?[^\?]{3,}\?[BQ]\?[^\?]{1,74}\?=)/i, $s)) {
760 next if ($mw eq '');
761 if ($mw =~ /=\?([^\?]{3,})\?([BQ])\?([^\?]{1,74})\?=/i) {
762 push(@p, { data => $3, encoding => uc($2), charset => uc($1),
763 is_mime => 1 });
764 if ($p[-1]{encoding} eq 'Q') {
765 my @ow = split('', $p[-1]{data});
766 my @nw = ();
767 for (my $i = 0; $i < @ow; $i++) {
768 if ($ow[$i] eq '_') { push(@nw, ' '); }
769 elsif ($ow[$i] eq '=') {
770 if (scalar(@ow) - ($i+1) < 2) { # ran out of characters
771 $e = 1; last;
772 } elsif ($ow[$i+1] !~ /[\dA-F]/i || $ow[$i+2] !~ /[\dA-F]/i) {
773 $e = 1; last;
774 } else {
775 #push(@nw, chr('0x'.$ow[$i+1].$ow[$i+2]));
776 push(@nw, pack("C", hex($ow[$i+1].$ow[$i+2])));
777 $i += 2;
778 }
779 }
4c04137d 780 elsif ($ow[$i] =~ /\s/) { # whitespace is illegal
0ea2a468
JJ
781 $e = 1;
782 last;
783 }
784 else { push(@nw, $ow[$i]); }
785 }
786 $p[-1]{data} = join('', @nw);
787 } elsif ($p[-1]{encoding} eq 'B') {
788 my $x = $p[-1]{data};
789 $x =~ tr#A-Za-z0-9+/##cd;
790 $x =~ s|=+$||;
791 $x =~ tr#A-Za-z0-9+/# -_#;
792 my $r = '';
793 while ($x =~ s/(.{1,60})//s) {
794 $r .= unpack("u", chr(32 + int(length($1)*3/4)) . $1);
795 }
796 $p[-1]{data} = $r;
797 }
798 } else {
799 push(@p, { data => $mw, is_mime => 0,
800 is_ws => ($mw =~ m|^[\s\n]+|sm) ? 1 : 0 });
801 }
802 }
059ec3d9 803
0ea2a468
JJ
804 for (my $i = 0; $i < @p; $i++) {
805 # mark entities we want to skip (whitespace between consecutive mimewords)
806 if ($p[$i]{is_mime} && $p[$i+1]{is_ws} && $p[$i+2]{is_mime}) {
807 $p[$i+1]{skip} = 1;
808 }
059ec3d9 809
0ea2a468
JJ
810 # if word is a mimeword and we have access to Encode and charset was
811 # specified, try to convert text
812 # XXX _cannot_ get consistent conversion results in perl, can't get them
813 # to return same conversions that exim performs. Until I can figure this
814 # out, don't attempt any conversions (header_ will return same value as
815 # bheader_).
816 #if ($c && $p[$i]{is_mime} && $self->_try_load('Encode')) {
817 # # XXX not sure how to catch errors here
818 # Encode::from_to($p[$i]{data}, $p[$i]{charset}, $c);
819 #}
820
821 # replace binary zeros w/ '?' in decoded text
822 if ($p[$i]{is_mime}) { $p[$i]{data} =~ s|\x00|?|g; }
823 }
824
825 if ($e) {
826 return($s);
827 } else {
828 return(join('', map { $_->{data} } grep { !$_->{skip} } @p));
829 }
830}
831
832# This isn't a class func but I'm tired
833sub _try_load {
834 my $self = shift;
835 my $mod = shift;
836
837 eval("use $mod");
838 return $@ ? 0 : 1;
059ec3d9
PH
839}
840
841sub _parse_body {
842 my $self = shift;
843 my $f = $self->{_path} . '/' . $self->{_message} . '-D';
0ea2a468 844 $self->{_vars}{message_body} = ""; # define var so we only come here once
059ec3d9
PH
845
846 open(I, "<$f") || return($self->_error("Couldn't open $f: $!"));
847 chomp($_ = <I>);
848 return(0) if ($self->{_message}.'-D' ne $_);
849
850 $self->{_vars}{message_body} = join('', <I>);
851 close(I);
852 $self->{_vars}{message_body} =~ s/\n/ /g;
853 $self->{_vars}{message_body} =~ s/\000/ /g;
854 return(1);
855}
856
857sub _parse_header {
858 my $self = shift;
859 my $f = $self->{_path} . '/' . $self->{_message} . '-H';
465e92cf
JJ
860 $self->{_vars}{header_path} = $f;
861 $self->{_vars}{data_path} = $self->{_path} . '/' . $self->{_message} . '-D';
059ec3d9 862
9cf6b11a
JJ
863 if (!open(I, "<$f")) {
864 # assume message went away and silently ignore
865 $self->{_delivered} = 1;
866 return(1);
867 }
868
0ea2a468
JJ
869 # There are a few numeric variables that should explicitly be set to
870 # zero if they aren't found in the header. Technically an empty value
871 # works just as well, but might as well be pedantic
872 $self->{_vars}{body_zerocount} = 0;
873 $self->{_vars}{host_lookup_deferred} = 0;
874 $self->{_vars}{host_lookup_failed} = 0;
875 $self->{_vars}{tls_certificate_verified} = 0;
876
059ec3d9
PH
877 chomp($_ = <I>);
878 return(0) if ($self->{_message}.'-H' ne $_);
879 $self->{_vars}{message_id} = $self->{_message};
ee744174 880 $self->{_vars}{message_exim_id} = $self->{_message};
059ec3d9
PH
881
882 # line 2
883 chomp($_ = <I>);
5f970846 884 return(0) if (!/^(.+)\s(\-?\d+)\s(\-?\d+)$/);
059ec3d9
PH
885 $self->{_vars}{originator_login} = $1;
886 $self->{_vars}{originator_uid} = $2;
887 $self->{_vars}{originator_gid} = $3;
888
889 # line 3
890 chomp($_ = <I>);
891 return(0) if (!/^<(.*)>$/);
892 $self->{_vars}{sender_address} = $1;
893 $self->{_vars}{sender_address_domain} = $1;
894 $self->{_vars}{sender_address_local_part} = $1;
895 $self->{_vars}{sender_address_domain} =~ s/^.*\@//;
896 $self->{_vars}{sender_address_local_part} =~ s/^(.*)\@.*$/$1/;
897
898 # line 4
899 chomp($_ = <I>);
900 return(0) if (!/^(\d+)\s(\d+)$/);
901 $self->{_vars}{received_time} = $1;
902 $self->{_vars}{warning_count} = $2;
903 $self->{_vars}{message_age} = time() - $self->{_vars}{received_time};
904
b4ab1dc8
HSHR
905 TAGGED: while (<I>) {
906 chomp;
907 my ($tag, $arg) = /^-?(-\S+)(?:\s+(.*))?$/ or last TAGGED;
908
909 if ($tag eq '-acl') {
059ec3d9
PH
910 my $t;
911 return(0) if ($arg !~ /^(\d+)\s(\d+)$/);
b3f69ca8 912 if ($1 < $Exim::SpoolFile::ACL_C_MAX_LEGACY) {
059ec3d9
PH
913 $t = "acl_c$1";
914 } else {
b3f69ca8 915 $t = "acl_m" . ($1 - $Exim::SpoolFile::ACL_C_MAX_LEGACY);
059ec3d9
PH
916 }
917 read(I, $self->{_vars}{$t}, $2+1) || return(0);
918 chomp($self->{_vars}{$t});
b4ab1dc8 919 } elsif ($tag eq '-aclc') {
a2405d83
JJ
920 #return(0) if ($arg !~ /^(\d+)\s(\d+)$/);
921 return(0) if ($arg !~ /^(\S+)\s(\d+)$/);
b3f69ca8
JJ
922 my $t = "acl_c$1";
923 read(I, $self->{_vars}{$t}, $2+1) || return(0);
924 chomp($self->{_vars}{$t});
b4ab1dc8 925 } elsif ($tag eq '-aclm') {
a2405d83
JJ
926 #return(0) if ($arg !~ /^(\d+)\s(\d+)$/);
927 return(0) if ($arg !~ /^(\S+)\s(\d+)$/);
b3f69ca8
JJ
928 my $t = "acl_m$1";
929 read(I, $self->{_vars}{$t}, $2+1) || return(0);
930 chomp($self->{_vars}{$t});
b4ab1dc8 931 } elsif ($tag eq '-local') {
059ec3d9 932 $self->{_vars}{sender_local} = 1;
b4ab1dc8 933 } elsif ($tag eq '-localerror') {
059ec3d9 934 $self->{_vars}{local_error_message} = 1;
b4ab1dc8 935 } elsif ($tag eq '-local_scan') {
059ec3d9 936 $self->{_vars}{local_scan_data} = $arg;
b4ab1dc8 937 } elsif ($tag eq '-spam_score_int') {
bf759a8b
PH
938 $self->{_vars}{spam_score_int} = $arg;
939 $self->{_vars}{spam_score} = $arg / 10;
b4ab1dc8 940 } elsif ($tag eq '-bmi_verdicts') {
bf759a8b 941 $self->{_vars}{bmi_verdicts} = $arg;
b4ab1dc8 942 } elsif ($tag eq '-host_lookup_deferred') {
bf759a8b 943 $self->{_vars}{host_lookup_deferred} = 1;
b4ab1dc8 944 } elsif ($tag eq '-host_lookup_failed') {
059ec3d9 945 $self->{_vars}{host_lookup_failed} = 1;
b4ab1dc8 946 } elsif ($tag eq '-body_linecount') {
059ec3d9 947 $self->{_vars}{body_linecount} = $arg;
b4ab1dc8 948 } elsif ($tag eq '-max_received_linelength') {
465e92cf 949 $self->{_vars}{max_received_linelength} = $arg;
b4ab1dc8 950 } elsif ($tag eq '-body_zerocount') {
bf759a8b 951 $self->{_vars}{body_zerocount} = $arg;
b4ab1dc8 952 } elsif ($tag eq '-frozen') {
059ec3d9
PH
953 $self->{_vars}{deliver_freeze} = 1;
954 $self->{_vars}{deliver_frozen_at} = $arg;
b4ab1dc8 955 } elsif ($tag eq '-allow_unqualified_recipient') {
bf759a8b 956 $self->{_vars}{allow_unqualified_recipient} = 1;
b4ab1dc8 957 } elsif ($tag eq '-allow_unqualified_sender') {
bf759a8b 958 $self->{_vars}{allow_unqualified_sender} = 1;
b4ab1dc8 959 } elsif ($tag eq '-deliver_firsttime') {
059ec3d9
PH
960 $self->{_vars}{deliver_firsttime} = 1;
961 $self->{_vars}{first_delivery} = 1;
b4ab1dc8 962 } elsif ($tag eq '-manual_thaw') {
059ec3d9
PH
963 $self->{_vars}{deliver_manual_thaw} = 1;
964 $self->{_vars}{manually_thawed} = 1;
b4ab1dc8 965 } elsif ($tag eq '-auth_id') {
059ec3d9 966 $self->{_vars}{authenticated_id} = $arg;
b4ab1dc8 967 } elsif ($tag eq '-auth_sender') {
059ec3d9 968 $self->{_vars}{authenticated_sender} = $arg;
b4ab1dc8 969 } elsif ($tag eq '-sender_set_untrusted') {
059ec3d9 970 $self->{_vars}{sender_set_untrusted} = 1;
b4ab1dc8 971 } elsif ($tag eq '-tls_certificate_verified') {
059ec3d9 972 $self->{_vars}{tls_certificate_verified} = 1;
b4ab1dc8 973 } elsif ($tag eq '-tls_cipher') {
059ec3d9 974 $self->{_vars}{tls_cipher} = $arg;
b4ab1dc8 975 } elsif ($tag eq '-tls_peerdn') {
059ec3d9 976 $self->{_vars}{tls_peerdn} = $arg;
b4ab1dc8 977 } elsif ($tag eq '-tls_sni') {
3f0945ff 978 $self->{_vars}{tls_sni} = $arg;
b4ab1dc8 979 } elsif ($tag eq '-host_address') {
059ec3d9
PH
980 $self->{_vars}{sender_host_port} = $self->_get_host_and_port(\$arg);
981 $self->{_vars}{sender_host_address} = $arg;
b4ab1dc8 982 } elsif ($tag eq '-interface_address') {
0ea2a468
JJ
983 $self->{_vars}{received_port} =
984 $self->{_vars}{interface_port} = $self->_get_host_and_port(\$arg);
985 $self->{_vars}{received_ip_address} =
986 $self->{_vars}{interface_address} = $arg;
b4ab1dc8 987 } elsif ($tag eq '-active_hostname') {
bf759a8b 988 $self->{_vars}{smtp_active_hostname} = $arg;
b4ab1dc8 989 } elsif ($tag eq '-host_auth') {
059ec3d9 990 $self->{_vars}{sender_host_authenticated} = $arg;
b4ab1dc8 991 } elsif ($tag eq '-host_name') {
059ec3d9 992 $self->{_vars}{sender_host_name} = $arg;
b4ab1dc8 993 } elsif ($tag eq '-helo_name') {
059ec3d9 994 $self->{_vars}{sender_helo_name} = $arg;
b4ab1dc8 995 } elsif ($tag eq '-ident') {
059ec3d9 996 $self->{_vars}{sender_ident} = $arg;
b4ab1dc8 997 } elsif ($tag eq '-received_protocol') {
059ec3d9 998 $self->{_vars}{received_protocol} = $arg;
b4ab1dc8 999 } elsif ($tag eq '-N') {
059ec3d9 1000 $self->{_vars}{dont_deliver} = 1;
b4ab1dc8 1001 } else {
059ec3d9
PH
1002 # unrecognized tag, save it for reference
1003 $self->{$tag} = $arg;
059ec3d9
PH
1004 }
1005 }
1006
8e669ac1 1007 # when we drop out of the while loop, we have the first line of the
059ec3d9
PH
1008 # delivered tree in $_
1009 do {
1010 if ($_ eq 'XX') {
1011 ; # noop
1012 } elsif ($_ =~ s/^[YN][YN]\s+//) {
1013 $self->{_del_tree}{$_} = 1;
1014 } else {
1015 return(0);
1016 }
1017 chomp($_ = <I>);
1018 } while ($_ !~ /^\d+$/);
1019
1020 $self->{_numrecips} = $_;
1021 $self->{_vars}{recipients_count} = $self->{_numrecips};
1022 for (my $i = 0; $i < $self->{_numrecips}; $i++) {
1023 chomp($_ = <I>);
1024 return(0) if (/^$/);
1025 my $addr = '';
1026 if (/^(.*)\s\d+,(\d+),\d+$/) {
1027 #print STDERR "exim3 type (untested): $_\n";
1028 $self->{_recips}{$1} = { pno => $2 };
1029 $addr = $1;
1030 } elsif (/^(.*)\s(\d+)$/) {
1031 #print STDERR "exim4 original type (untested): $_\n";
1032 $self->{_recips}{$1} = { pno => $2 };
1033 $addr = $1;
1034 } elsif (/^(.*)\s(.*)\s(\d+),(\d+)#1$/) {
1035 #print STDERR "exim4 new type #1 (untested): $_\n";
1036 return($self->_error("incorrect format: $_")) if (length($2) != $3);
1037 $self->{_recips}{$1} = { pno => $4, errors_to => $2 };
1038 $addr = $1;
bad059db
WB
1039 } elsif (/^(\S*)\s(\S*)\s(\d+),(\d+)\s(\S*)\s(\d+),(-?\d+)#3$/) {
1040 #print STDERR "exim4 new type #3 DSN (untested): $_\n";
1041 return($self->_error("incorrect format: $_"))
1042 if ((length($2) != $3) || (length($5) != $6));
1043 $self->{_recips}{$1} = { pno => $7, errors_to => $5 };
1044 $addr = $1;
059ec3d9 1045 } elsif (/^.*#(\d+)$/) {
bf759a8b 1046 #print STDERR "exim4 #$1 style (unimplemented): $_\n";
059ec3d9
PH
1047 $self->_error("exim4 #$1 style (unimplemented): $_");
1048 } else {
1049 #print STDERR "default type: $_\n";
1050 $self->{_recips}{$_} = {};
1051 $addr = $_;
1052 }
1053 $self->{_udel_tree}{$addr} = 1 if (!$self->{_del_tree}{$addr});
1054 }
af66f652
PH
1055 $self->{_vars}{recipients} = join(', ', keys(%{$self->{_recips}}));
1056 $self->{_vars}{recipients_del} = join(', ', keys(%{$self->{_del_tree}}));
1057 $self->{_vars}{recipients_undel} = join(', ', keys(%{$self->{_udel_tree}}));
1058 $self->{_vars}{recipients_undel_count} = scalar(keys(%{$self->{_udel_tree}}));
1059 $self->{_vars}{recipients_del_count} = 0;
1060 foreach my $r (keys %{$self->{_del_tree}}) {
1061 next if (!$self->{_recips}{$r});
1062 $self->{_vars}{recipients_del_count}++;
1063 }
059ec3d9
PH
1064
1065 # blank line
1066 $_ = <I>;
1067 return(0) if (!/^$/);
1068
1069 # start reading headers
1070 while (read(I, $_, 3) == 3) {
1071 my $t = getc(I);
1072 return(0) if (!length($t));
1073 while ($t =~ /^\d$/) {
1074 $_ .= $t;
1075 $t = getc(I);
1076 }
0ea2a468
JJ
1077 my $hdr_flag = $t;
1078 my $hdr_bytes = $_;
1079 $t = getc(I); # strip the space out of the file
1080 return(0) if (read(I, $_, $hdr_bytes) != $hdr_bytes);
1081 if ($hdr_flag ne '*') {
1082 $self->{_vars}{message_linecount} += (tr/\n//);
1083 $self->{_vars}{message_size} += $hdr_bytes;
1084 }
1085
1086 # mark (rb)?header_ vars as existing and store raw value. They'll be
1087 # processed further in get_var() if needed
9cf6b11a
JJ
1088 my($v,$d) = split(/:/, $_, 2);
1089 $v = "header_" . lc($v);
0ea2a468
JJ
1090 $self->{_vars}{$v} = $self->{_vars}{"b$v"} = $self->{_vars}{"r$v"} = undef;
1091 push(@{$self->{_vars_raw}{"r$v"}{vals}}, $d);
1092 $self->{_vars_raw}{"r$v"}{type} = $hdr_flag;
1093 $self->{_vars}{message_headers_raw} .= $_;
059ec3d9
PH
1094 }
1095 close(I);
059ec3d9
PH
1096
1097 $self->{_vars}{message_body_size} =
1098 (stat($self->{_path}.'/'.$self->{_message}.'-D'))[7] - 19;
1099 if ($self->{_vars}{message_body_size} < 0) {
1100 $self->{_vars}{message_size} = 0;
0ea2a468 1101 $self->{_vars}{message_body_missing} = 1;
059ec3d9
PH
1102 } else {
1103 $self->{_vars}{message_size} += $self->{_vars}{message_body_size} + 1;
1104 }
1105
5f970846
PH
1106 $self->{_vars}{message_linecount} += $self->{_vars}{body_linecount};
1107
1108 my $i = $self->{_vars}{message_size};
9cf6b11a
JJ
1109 if ($i == 0) { $i = ""; }
1110 elsif ($i < 1024) { $i = sprintf("%d", $i); }
1111 elsif ($i < 10240) { $i = sprintf("%.1fK", $i / 1024); }
1112 elsif ($i < 1048576) { $i = sprintf("%dK", ($i+512)/1024); }
1113 elsif ($i < 10485760) { $i = sprintf("%.1fM", $i/1048576); }
1114 else { $i = sprintf("%dM", ($i + 524288)/1048576); }
5f970846
PH
1115 $self->{_vars}{shown_message_size} = $i;
1116
059ec3d9 1117 return(1);
8e669ac1 1118}
059ec3d9
PH
1119
1120# mimic exim's host_extract_port function - receive a ref to a scalar,
1121# strip it of port, return port
1122sub _get_host_and_port {
1123 my $self = shift;
1124 my $host = shift; # scalar ref, be careful
1125
1126 if ($$host =~ /^\[([^\]]+)\](?:\:(\d+))?$/) {
1127 $$host = $1;
1128 return($2 || 0);
1129 } elsif ($$host =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:\.(\d+))?$/) {
1130 $$host = $1;
1131 return($2 || 0);
1132 } elsif ($$host =~ /^([\d\:]+)(?:\.(\d+))?$/) {
1133 $$host = $1;
1134 return($2 || 0);
1135 }
1136 # implicit else
1137 return(0);
1138}
1139
e22ca4ac
JJ
1140# honoring all formatting preferences, return a scalar variable of the
1141# information for the single message matching what exim -bp would show.
1142# We can print later if we want.
1143sub format_message {
059ec3d9 1144 my $self = shift;
e22ca4ac 1145 my $o = '';
059ec3d9
PH
1146 return if ($self->{_delivered});
1147
a2405d83
JJ
1148 # define any vars we want to print out for this message. The requests
1149 # can be regexps, and the defined vars can change for each message, so we
1150 # have to build this list for each message
1151 my @vars = ();
1152 if (@{$self->{_show_vars}}) {
1153 my %t = ();
1154 foreach my $e (@{$self->{_show_vars}}) {
1155 foreach my $v ($self->get_matching_vars($e)) {
1156 next if ($t{$v}); $t{$v}++; push(@vars, $v);
1157 }
1158 }
1159 }
1160
059ec3d9 1161 if ($self->{_output_idonly}) {
e22ca4ac 1162 $o .= $self->{_message};
0ea2a468 1163 foreach my $v (@vars) { $o .= " $v='" . $self->get_var($v) . "'"; }
e22ca4ac
JJ
1164 $o .= "\n";
1165 return $o;
0ea2a468
JJ
1166 } elsif ($self->{_output_vars_only}) {
1167 foreach my $v (@vars) { $o .= $self->get_var($v) . "\n"; }
1168 return $o;
059ec3d9 1169 }
8e669ac1 1170
059ec3d9
PH
1171 if ($self->{_output_long} || $self->{_output_flatq}) {
1172 my $i = int($self->{_vars}{message_age} / 60);
1173 if ($i > 90) {
1174 $i = int(($i+30)/60);
e22ca4ac
JJ
1175 if ($i > 72) { $o .= sprintf "%2dd ", int(($i+12)/24); }
1176 else { $o .= sprintf "%2dh ", $i; }
1177 } else { $o .= sprintf "%2dm ", $i; }
059ec3d9 1178
a2405d83
JJ
1179 if ($self->{_output_flatq} && @vars) {
1180 $o .= join(';', map { "$_='".$self->get_var($_)."'" } (@vars)
e22ca4ac 1181 );
5f970846 1182 } else {
e22ca4ac 1183 $o .= sprintf "%5s", $self->{_vars}{shown_message_size};
5f970846 1184 }
e22ca4ac 1185 $o .= " ";
059ec3d9 1186 }
e22ca4ac
JJ
1187 $o .= "$self->{_message} ";
1188 $o .= "From: " if ($self->{_output_brief});
1189 $o .= "<$self->{_vars}{sender_address}>";
059ec3d9
PH
1190
1191 if ($self->{_output_long}) {
e22ca4ac 1192 $o .= " ($self->{_vars}{originator_login})"
059ec3d9 1193 if ($self->{_vars}{sender_set_untrusted});
8e669ac1 1194
059ec3d9 1195 # XXX exim contains code here to print spool format errors
e22ca4ac
JJ
1196 $o .= " *** frozen ***" if ($self->{_vars}{deliver_freeze});
1197 $o .= "\n";
059ec3d9 1198
a2405d83 1199 foreach my $v (@vars) {
e22ca4ac 1200 $o .= sprintf " %25s = '%s'\n", $v, $self->get_var($v);
059ec3d9 1201 }
8e669ac1 1202
059ec3d9
PH
1203 foreach my $r (keys %{$self->{_recips}}) {
1204 next if ($self->{_del_tree}{$r} && $self->{_undelivered_only});
e22ca4ac 1205 $o .= sprintf " %s %s\n", $self->{_del_tree}{$r} ? "D" : " ", $r;
059ec3d9
PH
1206 }
1207 if ($self->{_show_generated}) {
1208 foreach my $r (keys %{$self->{_del_tree}}) {
1209 next if ($self->{_recips}{$r});
e22ca4ac 1210 $o .= sprintf " +D %s\n", $r;
059ec3d9
PH
1211 }
1212 }
1213 } elsif ($self->{_output_brief}) {
1214 my @r = ();
1215 foreach my $r (keys %{$self->{_recips}}) {
1216 next if ($self->{_del_tree}{$r});
1217 push(@r, $r);
1218 }
e22ca4ac 1219 $o .= " To: " . join(';', @r);
a2405d83
JJ
1220 if (scalar(@vars)) {
1221 $o .= " Vars: ".join(';',map { "$_='".$self->get_var($_)."'" } (@vars));
5f970846 1222 }
059ec3d9 1223 } elsif ($self->{_output_flatq}) {
e22ca4ac 1224 $o .= " *** frozen ***" if ($self->{_vars}{deliver_freeze});
059ec3d9
PH
1225 my @r = ();
1226 foreach my $r (keys %{$self->{_recips}}) {
1227 next if ($self->{_del_tree}{$r});
1228 push(@r, $r);
1229 }
e22ca4ac 1230 $o .= " " . join(' ', @r);
059ec3d9
PH
1231 }
1232
e22ca4ac
JJ
1233 $o .= "\n";
1234 return($o);
1235}
1236
1237sub print_message {
1238 my $self = shift;
1239 my $fh = shift || \*STDOUT;
1240 return if ($self->{_delivered});
1241
1242 print $fh $self->format_message();
059ec3d9
PH
1243}
1244
1245sub dump {
1246 my $self = shift;
1247
1248 foreach my $k (sort keys %$self) {
1249 my $r = ref($self->{$k});
1250 if ($r eq 'ARRAY') {
1251 printf "%20s <<EOM\n", $k;
1252 print @{$self->{$k}}, "EOM\n";
1253 } elsif ($r eq 'HASH') {
1254 printf "%20s <<EOM\n", $k;
1255 foreach (sort keys %{$self->{$k}}) {
1256 printf "%20s %s\n", $_, $self->{$k}{$_};
1257 }
1258 print "EOM\n";
1259 } else {
1260 printf "%20s %s\n", $k, $self->{$k};
1261 }
1262 }
1263}
1264
1265} # BEGIN
1266
059ec3d9
PH
1267__END__
1268
1269=head1 NAME
1270
cc05007f 1271 exipick - selectively display messages from an Exim queue
059ec3d9 1272
e22ca4ac 1273=head1 SYNOPSIS
059ec3d9 1274
cc05007f
HSHR
1275 exipick [<options>] [<criterion> [<criterion> ...]]
1276 exipick --help|--man
059ec3d9
PH
1277
1278=head1 DESCRIPTION
1279
48ab0b3c 1280B<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.
e22ca4ac
JJ
1281
1282=head1 QUICK START
1283
1284Delete every frozen message from queue:
48ab0b3c 1285
e22ca4ac
JJ
1286 exipick -zi | xargs exim -Mrm
1287
1288Show only messages which have not yet been virus scanned:
48ab0b3c 1289
e22ca4ac
JJ
1290 exipick '$received_protocol ne virus-scanned'
1291
1292Run the queue in a semi-random order:
48ab0b3c 1293
e22ca4ac
JJ
1294 exipick -i --random | xargs exim -M
1295
1296Show the count and total size of all messages which either originated from localhost or have a received protocol of 'local':
48ab0b3c 1297
e22ca4ac
JJ
1298 exipick --or --size --bpc \
1299 '$sender_host_address eq 127.0.0.1' \
1300 '$received_protocol eq local'
1301
1302Display all messages received on the MSA port, ordered first by the sender's email domain and then by the size of the emails:
48ab0b3c 1303
e22ca4ac 1304 exipick --sort sender_address_domain,message_size \
0ea2a468 1305 '$received_port == 587'
e22ca4ac
JJ
1306
1307Display only messages whose every recipient is in the example.com domain, also listing the IP address of the sending host:
48ab0b3c 1308
e22ca4ac
JJ
1309 exipick --show-vars sender_host_address \
1310 '$each_recipients = example.com'
059ec3d9 1311
a2405d83 1312Same as above, but show values for all defined variables starting with sender_ and the number of recipients:
48ab0b3c 1313
a2405d83
JJ
1314 exipick --show-vars ^sender_,recipients_count \
1315 '$each_recipients = example.com'
1316
059ec3d9
PH
1317=head1 OPTIONS
1318
1319=over 4
1320
48ab0b3c 1321=item B<--and>
059ec3d9 1322
e22ca4ac 1323Display messages matching all criteria (default)
059ec3d9 1324
48ab0b3c 1325=item B<-b>
059ec3d9 1326
e22ca4ac 1327Display messages in brief format (exiqgrep)
059ec3d9 1328
48ab0b3c 1329=item B<-bp> | B<-l>
059ec3d9 1330
48ab0b3c
HSHR
1331Display messages in standard mailq format (default).
1332(exiqgrep: C<-l>)
059ec3d9 1333
48ab0b3c 1334=item B<-bpa>
af66f652 1335
48ab0b3c 1336Same as C<-bp>, show generated addresses also (exim)
af66f652 1337
48ab0b3c 1338=item B<-bpc>
5f970846 1339
e22ca4ac 1340Show a count of matching messages (exim)
5f970846 1341
48ab0b3c 1342=item B<-bpr>
5f970846 1343
48ab0b3c 1344Same as C<-bp --unsorted> (exim)
5f970846 1345
48ab0b3c 1346=item B<-bpra>
5f970846 1347
48ab0b3c 1348Same as C<-bpa --unsorted> (exim)
5f970846 1349
48ab0b3c 1350=item B<-bpru>
5f970846 1351
48ab0b3c 1352Same as C<-bpu --unsorted> (exim)
5f970846 1353
48ab0b3c 1354=item B<-bpu>
9cf6b11a 1355
48ab0b3c 1356Same as C<-bp>, but only show undelivered messages (exim)
9cf6b11a 1357
48ab0b3c 1358=item B<-C> | B<--config> I<config>
c4d5e329 1359
48ab0b3c 1360Use I<config> to determine the proper spool directory. (See C<--spool>
c4d5e329
HSHR
1361or C<--input> for alternative ways to specify the directories to operate on.)
1362
48ab0b3c 1363=item B<-c>
059ec3d9 1364
e22ca4ac 1365Show a count of matching messages (exiqgrep)
059ec3d9 1366
48ab0b3c 1367=item B<--caseful>
059ec3d9 1368
48ab0b3c 1369Make operators involving C<=> honor case
059ec3d9 1370
48ab0b3c 1371=item B<--charset>
0ea2a468 1372
48ab0b3c 1373Override the default local character set for C<$header_> decoding
0ea2a468 1374
48ab0b3c 1375=item B<-f> I<regexp>
059ec3d9 1376
48ab0b3c 1377Same as C<< $sender_address =~ /<regexp>/ >> (exiqgrep). Note that this preserves the default case sensitivity of exiqgrep's interface.
059ec3d9 1378
48ab0b3c 1379=item B<--finput>
edae0343 1380
48ab0b3c 1381Same as C<--input-dir Finput>. F<Finput> is where exim copies frozen messages when compiled with SUPPORT_MOVE_FROZEN_MESSAGES.
edae0343 1382
48ab0b3c 1383=item B<--flatq>
059ec3d9 1384
e22ca4ac 1385Use a single-line output format
059ec3d9 1386
48ab0b3c 1387=item B<--freeze> I<cache file>
059ec3d9 1388
e22ca4ac 1389Save queue information in an quickly retrievable format
059ec3d9 1390
48ab0b3c 1391=item B<--help>
059ec3d9 1392
e22ca4ac 1393Display this output
059ec3d9 1394
48ab0b3c 1395=item B<-i>
5f970846 1396
e22ca4ac 1397Display only the message IDs (exiqgrep)
059ec3d9 1398
48ab0b3c 1399=item B<--input-dir> I<inputname>
edae0343 1400
48ab0b3c
HSHR
1401Set the name of the directory under the spool directory. By default this is F<input>. If this starts with F</>,
1402the value of C<--spool> is ignored. See also C<--finput>.
059ec3d9 1403
48ab0b3c 1404=item B<--not>
059ec3d9 1405
e22ca4ac 1406Negate all tests.
059ec3d9 1407
48ab0b3c 1408=item B<-o> I<seconds>
059ec3d9 1409
48ab0b3c 1410Same as C<< $message_age > <seconds> >> (exiqgrep)
059ec3d9 1411
48ab0b3c 1412=item B<--or>
059ec3d9 1413
e22ca4ac 1414Display messages matching any criteria
059ec3d9 1415
48ab0b3c 1416=item B<--queue> I<name>
a6d70503
HSHR
1417
1418Name of the queue (default: ''). See "named queues" in the spec.
1419
48ab0b3c 1420=item B<-r> I<regexp>
059ec3d9 1421
48ab0b3c 1422Same as C<< $recipients =~ /<regexp>/ >> (exiqgrep). Note that this preserves the default case sensitivity of exiqgrep's interface.
059ec3d9 1423
48ab0b3c 1424=item B<--random>
9cf6b11a 1425
e22ca4ac 1426Display messages in random order
9cf6b11a 1427
48ab0b3c 1428=item B<--reverse> | B<-R>
9cf6b11a 1429
48ab0b3c 1430Display messages in reverse order (exiqgrep: C<-R>)
9cf6b11a 1431
48ab0b3c 1432=item B<-s> I<string>
9cf6b11a 1433
48ab0b3c 1434Same as C<< $shown_message_size eq <string> >> (exiqgrep)
9cf6b11a 1435
48ab0b3c 1436=item B<--spool> I<path>
059ec3d9 1437
48ab0b3c 1438Set 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>.
059ec3d9 1439
48ab0b3c 1440=item B<--show-rules>
059ec3d9 1441
e22ca4ac 1442Show the internal representation of each criterion specified
059ec3d9 1443
48ab0b3c 1444=item B<--show-tests>
059ec3d9 1445
e22ca4ac 1446Show the result of each criterion on each message
059ec3d9 1447
48ab0b3c 1448=item B<--show-vars> I<variable>[,I<variable>...]
059ec3d9 1449
48ab0b3c 1450Show the value for I<variable> for each displayed message. I<variable> will be a regular expression if it begins with a circumflex.
059ec3d9 1451
48ab0b3c 1452=item B<--size>
059ec3d9 1453
e22ca4ac 1454Show the total bytes used by each displayed message
059ec3d9 1455
48ab0b3c 1456=item B<--thaw> I<cache file>
059ec3d9 1457
48ab0b3c 1458Read queue information cached from a previous C<--freeze> run
059ec3d9 1459
48ab0b3c 1460=item B<--sort> I<variable>[,I<variable>...]
059ec3d9 1461
48ab0b3c 1462Display matching messages sorted according to I<variable>
059ec3d9 1463
48ab0b3c 1464=item B<--unsorted>
059ec3d9 1465
e22ca4ac 1466Do not apply any sorting to output
059ec3d9 1467
48ab0b3c 1468=item B<--version>
059ec3d9 1469
e22ca4ac 1470Display the version of this command
059ec3d9 1471
48ab0b3c 1472=item B<-x>
e22ca4ac 1473
48ab0b3c 1474Same as C<!$deliver_freeze> (exiqgrep)
e22ca4ac 1475
48ab0b3c 1476=item B<-y>
9cf6b11a 1477
48ab0b3c 1478Same as C<< $message_age < <seconds> >> (exiqgrep)
e22ca4ac 1479
48ab0b3c 1480=item B<-z>
e22ca4ac 1481
48ab0b3c 1482Same as C<$deliver_freeze> (exiqgrep)
9cf6b11a 1483
059ec3d9
PH
1484=back
1485
e22ca4ac 1486=head1 CRITERIA
059ec3d9 1487
48ab0b3c 1488B<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.
059ec3d9 1489
48ab0b3c 1490Multiple 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.
059ec3d9 1491
e22ca4ac 1492See the VARIABLES and OPERATORS sections below for more details
059ec3d9 1493
e22ca4ac 1494=head1 OPERATORS
059ec3d9 1495
e22ca4ac 1496=over 4
059ec3d9 1497
e22ca4ac 1498=item BOOLEAN
059ec3d9 1499
e22ca4ac 1500Boolean variables are checked simply by being true or false. There is no real operator except negation. Examples of valid boolean tests:
48ab0b3c
HSHR
1501
1502 $deliver_freeze
1503 !$deliver_freeze
059ec3d9 1504
e22ca4ac 1505=item NUMERIC
059ec3d9 1506
43236f35 1507Valid 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:
48ab0b3c
HSHR
1508
1509 $message_age >= 3d
1510 $local_interface == 587
1511 $message_size < 30K
059ec3d9 1512
e22ca4ac 1513=item STRING
059ec3d9 1514
48ab0b3c
HSHR
1515The 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:
1516
1517 $received_protocol eq esmtp
1518 $sender_address = example.com
1519 $each_recipients =~ /^a[a-z]{2,3}@example.com$/
059ec3d9 1520
e22ca4ac 1521=item NEGATION
059ec3d9 1522
48ab0b3c 1523There 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.
059ec3d9 1524
e22ca4ac 1525=back
059ec3d9 1526
e22ca4ac 1527=head1 VARIABLES
059ec3d9 1528
e22ca4ac 1529With 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 1530
48ab0b3c 1531Internally, 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.
bf759a8b 1532
e22ca4ac
JJ
1533 Identifiers
1534 B - Boolean variables
1535 S - String variables
1536 N - Numeric variables
1537 . - Standard variable matching Exim's content definition
1538 # - Standard variable, contents differ from Exim's definition
1539 + - Non-standard variable
bf759a8b 1540
e22ca4ac 1541=over 4
059ec3d9 1542
48ab0b3c 1543=item S . B<$acl_c0>-B<$acl_c9>, B<$acl_m0>-B<$acl_m9>
059ec3d9 1544
e22ca4ac 1545User definable variables.
059ec3d9 1546
48ab0b3c 1547=item B + B<$allow_unqualified_recipient>
059ec3d9 1548
e22ca4ac 1549TRUE if unqualified recipient addresses are permitted in header lines.
059ec3d9 1550
48ab0b3c 1551=item B + B<$allow_unqualified_sender>
059ec3d9 1552
e22ca4ac 1553TRUE if unqualified sender addresses are permitted in header lines.
059ec3d9 1554
48ab0b3c 1555=item S . B<$authenticated_id>
059ec3d9 1556
e22ca4ac 1557Optional saved information from authenticators, or the login name of the calling process for locally submitted messages.
059ec3d9 1558
48ab0b3c 1559=item S . B<$authenticated_sender>
059ec3d9 1560
e22ca4ac 1561The value of AUTH= param for smtp messages, or a generated value from the calling processes login and qualify domain for locally submitted messages.
059ec3d9 1562
48ab0b3c 1563=item S . B<$bheader_*>, B<$bh_*>
0ea2a468
JJ
1564
1565Value 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.
1566
48ab0b3c 1567=item S + B<$bmi_verdicts>
059ec3d9 1568
e22ca4ac 1569The verdict string provided by a Brightmail content scan
059ec3d9 1570
48ab0b3c 1571=item N . B<$body_linecount>
059ec3d9
PH
1572
1573The number of lines in the message's body.
1574
48ab0b3c 1575=item N . B<$body_zerocount>
059ec3d9
PH
1576
1577The number of binary zero bytes in the message's body.
1578
48ab0b3c 1579=item S + B<$data_path>
465e92cf
JJ
1580
1581The path to the body file's location in the filesystem.
1582
48ab0b3c 1583=item B + B<$deliver_freeze>
059ec3d9 1584
e22ca4ac 1585TRUE if the message is currently frozen.
059ec3d9 1586
48ab0b3c 1587=item N + B<$deliver_frozen_at>
059ec3d9 1588
e22ca4ac 1589The epoch time at which message was frozen.
059ec3d9 1590
48ab0b3c 1591=item B + B<$dont_deliver>
059ec3d9 1592
e22ca4ac 1593TRUE if, under normal circumstances, Exim will not try to deliver the message.
059ec3d9 1594
48ab0b3c 1595=item S + B<$each_recipients>
059ec3d9 1596
48ab0b3c 1597This 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.
5f970846 1598
48ab0b3c 1599=item S + B<$each_recipients_del>
5f970846 1600
e22ca4ac 1601Like $each_recipients, but for $recipients_del
059ec3d9 1602
48ab0b3c 1603=item S + B<$each_recipients_undel>
059ec3d9 1604
e22ca4ac 1605Like $each_recipients, but for $recipients_undel
059ec3d9 1606
48ab0b3c 1607=item B . B<$first_delivery>
059ec3d9 1608
e22ca4ac 1609TRUE if the message has never been deferred.
059ec3d9 1610
48ab0b3c 1611=item S . B<$header_*>, B<$h_*>
059ec3d9 1612
0ea2a468 1613This will always match the contents of the corresponding $bheader_* variable currently (the same behaviour Exim displays when iconv is not installed).
059ec3d9 1614
48ab0b3c 1615=item S + B<$header_path>
465e92cf
JJ
1616
1617The path to the header file's location in the filesystem.
1618
48ab0b3c 1619=item B . B<$host_lookup_deferred>
059ec3d9 1620
e22ca4ac 1621TRUE 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 1622
48ab0b3c 1623=item B . B<$host_lookup_failed>
059ec3d9 1624
e22ca4ac 1625TRUE if there was an attempt to look up the host's name from its IP address, but the attempt returned a negative result.
059ec3d9 1626
48ab0b3c 1627=item B + B<$local_error_message>
059ec3d9 1628
e22ca4ac 1629TRUE if the message is a locally-generated error message.
059ec3d9 1630
48ab0b3c 1631=item S . B<$local_scan_data>
059ec3d9 1632
e22ca4ac 1633The text returned by the local_scan() function when a message is received.
059ec3d9 1634
48ab0b3c 1635=item B . B<$manually_thawed>
059ec3d9 1636
e22ca4ac 1637TRUE when the message has been manually thawed.
059ec3d9 1638
48ab0b3c 1639=item N . B<$max_received_linelength>
465e92cf
JJ
1640
1641The number of bytes in the longest line that was received as part of the message, not counting line termination characters.
1642
48ab0b3c 1643=item N . B<$message_age>
059ec3d9 1644
e22ca4ac 1645The number of seconds since the message was received.
059ec3d9 1646
48ab0b3c 1647=item S # B<$message_body>
059ec3d9 1648
e22ca4ac 1649The 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 1650
48ab0b3c 1651=item B + B<$message_body_missing>
0ea2a468
JJ
1652
1653TRUE is a message's spool data file (-D file) is missing or unreadable.
1654
48ab0b3c 1655=item N . B<$message_body_size>
059ec3d9 1656
e22ca4ac 1657The size of the body in bytes.
059ec3d9 1658
48ab0b3c 1659=item S . B<$message_exim_id>, B<$message_id>
059ec3d9 1660
e22ca4ac 1661The unique message id that is used by Exim to identify the message. $message_id is deprecated as of Exim 4.53.
059ec3d9 1662
48ab0b3c 1663=item S . B<$message_headers>
bf759a8b 1664
0ea2a468
JJ
1665A concatenation of all the header lines except for lines added by routers or transports. RFC2047 decoding is performed
1666
48ab0b3c 1667=item S . B<$message_headers_raw>
0ea2a468
JJ
1668
1669A concatenation of all the header lines except for lines added by routers or transports. No decoding or translation is performed.
bf759a8b 1670
48ab0b3c 1671=item N . B<$message_linecount>
bf759a8b 1672
e22ca4ac 1673The number of lines in the entire message (body and headers).
bf759a8b 1674
48ab0b3c 1675=item N . B<$message_size>
bf759a8b 1676
e22ca4ac 1677The size of the message in bytes.
bf759a8b 1678
48ab0b3c 1679=item N . B<$originator_gid>
bf759a8b 1680
e22ca4ac 1681The group id under which the process that called Exim was running as when the message was received.
bf759a8b 1682
48ab0b3c 1683=item S + B<$originator_login>
059ec3d9 1684
e22ca4ac 1685The login of the process which called Exim.
059ec3d9 1686
48ab0b3c 1687=item N . B<$originator_uid>
059ec3d9 1688
e22ca4ac 1689The user id under which the process that called Exim was running as when the message was received.
059ec3d9 1690
48ab0b3c 1691=item S . B<$received_ip_address>, B<$interface_address>
0ea2a468
JJ
1692
1693The address of the local IP interface for network-originated messages. $interface_address is deprecated as of Exim 4.64
1694
48ab0b3c 1695=item N . B<$received_port>, B<$interface_port>
0ea2a468
JJ
1696
1697The local port number if network-originated messages. $interface_port is deprecated as of Exim 4.64
1698
48ab0b3c 1699=item N . B<$received_count>
059ec3d9 1700
e22ca4ac 1701The number of Received: header lines in the message.
059ec3d9 1702
48ab0b3c 1703=item S . B<$received_protocol>
059ec3d9 1704
e22ca4ac 1705The name of the protocol by which the message was received.
059ec3d9 1706
48ab0b3c 1707=item N . B<$received_time>
059ec3d9 1708
e22ca4ac 1709The epoch time at which the message was received.
059ec3d9 1710
48ab0b3c 1711=item S # B<$recipients>
059ec3d9 1712
465e92cf 1713The 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.
059ec3d9 1714
48ab0b3c 1715=item N . B<$recipients_count>
059ec3d9 1716
e22ca4ac 1717The number of envelope recipients for the message.
059ec3d9 1718
48ab0b3c 1719=item S + B<$recipients_del>
059ec3d9 1720
e22ca4ac 1721The 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 1722
48ab0b3c 1723=item N + B<$recipients_del_count>
059ec3d9 1724
e22ca4ac 1725The 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 1726
48ab0b3c 1727=item S + B<$recipients_undel>
059ec3d9 1728
e22ca4ac 1729The 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 1730
48ab0b3c 1731=item N + B<$recipients_undel_count>
059ec3d9 1732
e22ca4ac 1733The number of envelope recipients for the message which have not yet been delivered.
059ec3d9 1734
48ab0b3c 1735=item S . B<$reply_address>
059ec3d9
PH
1736
1737The contents of the Reply-To: header line if one exists and it is not empty, or otherwise the contents of the From: header line.
1738
48ab0b3c 1739=item S . B<$rheader_*>, B<$rh_*>
0ea2a468
JJ
1740
1741The value of the message's header(s) with the same name. See section 11.5 of Exim's spec.txt for full description.
1742
48ab0b3c 1743=item S . B<$sender_address>
059ec3d9
PH
1744
1745The sender's address that was received in the message's envelope. For bounce messages, the value of this variable is the empty string.
1746
48ab0b3c 1747=item S . B<$sender_address_domain>
059ec3d9 1748
bf759a8b 1749The domain part of $sender_address.
059ec3d9 1750
48ab0b3c 1751=item S . B<$sender_address_local_part>
059ec3d9 1752
bf759a8b 1753The local part of $sender_address.
059ec3d9 1754
48ab0b3c 1755=item S . B<$sender_helo_name>
059ec3d9
PH
1756
1757The HELO or EHLO value supplied for smtp or bsmtp messages.
1758
48ab0b3c 1759=item S . B<$sender_host_address>
059ec3d9
PH
1760
1761The remote host's IP address.
1762
48ab0b3c 1763=item S . B<$sender_host_authenticated>
059ec3d9
PH
1764
1765The name of the authenticator driver which successfully authenticated the client from which the message was received.
1766
48ab0b3c 1767=item S . B<$sender_host_name>
059ec3d9
PH
1768
1769The remote host's name as obtained by looking up its IP address.
1770
48ab0b3c 1771=item N . B<$sender_host_port>
059ec3d9 1772
e22ca4ac 1773The port number that was used on the remote host for network-originated messages.
5f970846 1774
48ab0b3c 1775=item S . B<$sender_ident>
5f970846 1776
e22ca4ac 1777The 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 1778
48ab0b3c 1779=item B + B<$sender_local>
bf759a8b 1780
e22ca4ac 1781TRUE if the message was locally generated.
bf759a8b 1782
48ab0b3c 1783=item B + B<$sender_set_untrusted>
bf759a8b 1784
e22ca4ac 1785TRUE if the envelope sender of this message was set by an untrusted local caller.
bf759a8b 1786
48ab0b3c 1787=item S + B<$shown_message_size>
bf759a8b 1788
e22ca4ac 1789This 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 1790
48ab0b3c 1791=item S . B<$smtp_active_hostname>
059ec3d9 1792
e22ca4ac 1793The value of the active host name when the message was received, as specified by the "smtp_active_hostname" option.
059ec3d9 1794
48ab0b3c 1795=item S . B<$spam_score>
059ec3d9 1796
e22ca4ac 1797The spam score of the message, for example '3.4' or '30.5'. (Requires exiscan or WITH_CONTENT_SCAN)
059ec3d9 1798
48ab0b3c 1799=item S . B<$spam_score_int>
059ec3d9 1800
e22ca4ac 1801The spam score of the message, multiplied by ten, as an integer value. For instance '34' or '305'. (Requires exiscan or WITH_CONTENT_SCAN)
059ec3d9 1802
48ab0b3c 1803=item B . B<$tls_certificate_verified>
059ec3d9 1804
e22ca4ac 1805TRUE if a TLS certificate was verified when the message was received.
059ec3d9 1806
48ab0b3c 1807=item S . B<$tls_cipher>
059ec3d9 1808
e22ca4ac 1809The cipher suite that was negotiated for encrypted SMTP connections.
059ec3d9 1810
48ab0b3c 1811=item S . B<$tls_peerdn>
059ec3d9 1812
e22ca4ac 1813The value of the Distinguished Name of the certificate if Exim is configured to request one
059ec3d9 1814
48ab0b3c 1815=item S . B<$tls_sni>
3f0945ff
PP
1816
1817The value of the Server Name Indication TLS extension sent by a client, if one was sent.
1818
48ab0b3c 1819=item N + B<$warning_count>
059ec3d9 1820
e22ca4ac 1821The number of delay warnings which have been sent for this message.
059ec3d9
PH
1822
1823=back
1824
059ec3d9
PH
1825=head1 CONTACT
1826
1827=over 4
1828
1829=item EMAIL: proj-exipick@jetmore.net
1830
48ab0b3c
HSHR
1831=item HOME: L<https://jetmore.org/john/code/#exipick>
1832
1833This script was incorporated into the main Exim distribution some years ago.
059ec3d9
PH
1834
1835=back
1836
1837=cut
48ab0b3c
HSHR
1838
1839# vim:ft=perl