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