###############################################################################
#use strict;
-require Cwd;
use Errno;
use FileHandle;
use Socket;
use Time::Local;
+use Cwd;
+use File::Basename;
+use if $ENV{DEBUG} && $ENV{DEBUG} =~ /\bruntest\b/ => ('Smart::Comments' => '####');
# Start by initializing some global variables
$optargs = "";
$save_output = 0;
$server_opts = "";
+$flavour = 'FOO';
$have_ipv4 = 1;
$have_ipv6 = 1;
# Manually set locale
$ENV{'LC_ALL'} = 'C';
+# In some environments USER does not exists, but we
+# need it for some test(s)
+$ENV{USER} = getpwuid($>)
+ if not exists $ENV{USER};
###############################################################################
my($yield) = 0;
my(@saved) = ();
+local $_;
+
open(IN, "$file") || tests_exit(-1, "Failed to open $file: $!");
my($is_log) = $file =~ /log/;
# that are specific to certain file types, though there are also some of those
# inline too.
-while(<IN>)
+LINE: while(<IN>)
{
RESET_AFTER_EXTRA_LINE_READ:
# Custom munges
# signature algorithm names
s/RSA-SHA1/RSA-SHA/;
+ # -d produces a list of environement variables as they are checked if they exist in the
+ # in the environment. Unfortunately this list isn't always in the same order. For now we
+ # just remove this list
+ #
+ if (/^\w+ in keep_environment/)
+ {
+ my @lines = $_;
+ while (<IN>)
+ {
+ if (/^\w+ in keep_environment/)
+ {
+ push @lines, $_;
+ next;
+ }
+ print MUNGED sort grep { !/^(SHLVL|_) / } @lines;
+ redo LINE;
+ }
+ }
+
# ======== Caller's login, uid, gid, home, gecos ========
s/^\s+host\s(\S+)\s+(\S+)/ host $1 $2/;
s/^\s+(host\s\S+\s\S+)\s+(port=.*)/ host $1 $2/;
s/^\s+(host\s\S+\s\S+)\s+(?=MX=)/ $1 /;
+ s/^\s+host\s.*?\K\s+(ad=\S+)/ $1/;
s/host\s\Q$parm_ipv4\E\s\[\Q$parm_ipv4\E\]/host ipv4.ipv4.ipv4.ipv4 [ipv4.ipv4.ipv4.ipv4]/;
s/host\s\Q$parm_ipv6\E\s\[\Q$parm_ipv6\E\]/host ip6:ip6:ip6:ip6:ip6:ip6:ip6:ip6 [ip6:ip6:ip6:ip6:ip6:ip6:ip6:ip6]/;
s/\b\Q$parm_ipv4\E\b/ip4.ip4.ip4.ip4/g;
# IP address lookups use gethostbyname() when IPv6 is not supported,
# and gethostbyname2() or getipnodebyname() when it is.
- s/\bgethostbyname2?|\bgetipnodebyname/get[host|ipnode]byname[2]/;
+ s/\b(gethostbyname2?|\bgetipnodebyname)(\(af=inet\))?/get[host|ipnode]byname[2]/;
# drop gnutls version strings
next if /GnuTLS compile-time version: \d+[\.\d]+$/;
@saved = ();
}
+ # remote port numbers vary
+ s/(Connection request from 127.0.0.1 port) \d{1,5}/$1 sssss/;
+
# Skip hosts_require_dane checks when the options
# are unset, because dane ain't always there.
# Arguments: [0] the prompt string
# [1] if there is a U in the prompt and $force_update is true
# [2] if there is a C in the prompt and $force_continue is true
-# Returns: nothing (it sets $_)
+# Returns: returns the answer
sub interact{
print $_[0];
# If there is no saved file, the raw files must either not exist, or be
# empty. The test ! -s is TRUE if the file does not exist or is empty.
-if (! -e $sf)
+# we check if there is a flavour specific file, but we remember
+# the original file name as "generic"
+$sf_generic = $sf;
+$sf_flavour = "$sf_generic.$flavour";
+$sf_current = -e $sf_flavour ? $sf_flavour : $sf_generic;
+
+if (! -e $sf_current)
{
return 0 if (! -s $rf && (! defined $rsf || ! -s $rsf));
}
}
+#### $_
+
# Control reaches here if either (a) there is a saved file ($sf), or (b) there
# was a request to create a saved file. First, create the munged file from any
# data that does exist.
# a result of parallel deliveries. We load the munged file and sort sequences
# of delivery lines.
-if (-e $sf)
+if (-e $sf_current)
{
# Deal with truncated text items
open(MUNGED, "$mf") || tests_exit(-1, "Failed to open $mf: $!");
@munged = <MUNGED>;
close(MUNGED);
- open(SAVED, "$sf") || tests_exit(-1, "Failed to open $sf: $!");
+ open(SAVED, $sf_current) || tests_exit(-1, "Failed to open $sf_current: $!");
@saved = <SAVED>;
close(SAVED);
# Do the comparison
- return 0 if (system("$cf '$mf' '$sf' >test-cf") == 0);
+ return 0 if (system("$cf '$mf' '$sf_current' >test-cf") == 0);
# Handle comparison failure
- print "** Comparison of $mf with $sf failed";
+ print "** Comparison of $mf with $sf_current failed";
system("$more test-cf");
print "\n";
for (;;)
{
- interact("Continue, Retry, Update & retry, Quit? [Q] ", $force_update, $force_continue);
+ interact("Continue, Retry, Update current"
+ . ($sf_current ne $sf_flavour ? "/Save for flavour '$flavour'" : "")
+ . " & retry, Quit? [Q] ", $force_update, $force_continue);
tests_exit(1) if /^q?$/i;
- log_failure($log_failed_filename, $testno, $sf) if (/^c$/i && $force_continue);
+ log_failure($log_failed_filename, $testno, $sf_current) if (/^c$/i && $force_continue);
return 0 if /^c$/i;
return 1 if /^r$/i;
- last if (/^u$/i);
+ last if (/^[us]$/i);
}
}
# Update or delete the saved file, and give the appropriate return code.
if (-s $mf)
- { tests_exit(-1, "Failed to cp $mf $sf") if system("cp '$mf' '$sf'") != 0; }
+ {
+ my $sf = /^u/i ? $sf_current : $sf_flavour;
+ tests_exit(-1, "Failed to cp $mf $sf") if system("cp '$mf' '$sf'") != 0;
+ }
else
- { tests_exit(-1, "Failed to unlink $sf") if !unlink($sf); }
+ {
+ # if we deal with a flavour file, we can't delete it, because next time the generic
+ # file would be used again
+ if ($sf_current eq $sf_flavour) {
+ open(FOO, ">$sf_current");
+ close(FOO);
+ }
+ else {
+ tests_exit(-1, "Failed to unlink $sf_current") if !unlink($sf_current);
+ }
+ }
return 1;
}
'scanfile_size' =>
{ 'stdout' => 's/(Content-length:) \d\d\d/$1 ddd/' },
+ 'delay_1500' =>
+ { 'stderr' => 's/(1[5-9]|23\d)\d\d msec/ssss msec/' },
+
+ 'tls_anycipher' =>
+ { 'mainlog' => 's/ X=TLS\S+ / X=TLS_proto_and_cipher /' },
+
+ 'debug_pid' =>
+ { 'stderr' => 's/(^\s{0,4}|(?<=Process )|(?<=child ))\d{1,5}/ppppp/g' },
+
};
my($aux_info) = $_[4];
my($yield) = 1;
+our %ENV = map { $_ => $ENV{$_} } grep { /^(?:USER|SHELL|PATH|TERM|EXIM_TEST_.*)$/ } keys %ENV;
+
if (/^(\d+)\s*$/) # Handle unusual return code
{
my($r) = $_[2];
if (/^server\s+(.*)$/)
{
- $cmd = "./bin/server $server_opts $1 >>test-stdout-server";
+ $pidfile = "$parm_cwd/aux-var/server-daemon.pid";
+ $cmd = "./bin/server $server_opts -oP $pidfile $1 >>test-stdout-server";
print ">> $cmd\n" if ($debug);
$server_pid = open SERVERCMD, "|$cmd" || tests_exit(-1, "Failed to run $cmd");
SERVERCMD->autoflush(1);
print SERVERCMD "++++\n"; # Send end to server; can't send EOF yet
# because close() waits for the process.
- # This gives the server time to get started; otherwise the next
+ # Interlock the server startup; otherwise the next
# process may not find it there when it expects it.
-
- select(undef, undef, undef, 0.5);
+ while (! stat("$pidfile") ) { select(undef, undef, undef, 0.3); }
return 3;
}
# not drop privilege when -C and -D options are present. To run the exim
# command as root, we use sudo.
-elsif (/^([A-Z_]+=\S+\s+)?(\d+)?\s*(sudo\s+)?exim(_\S+)?\s+(.*)$/)
+elsif (/^((?i:[A-Z\d_]+=\S+\s+)+)?(\d+)?\s*(sudo(?:\s+-u\s+(\w+))?\s+)?exim(_\S+)?\s+(.*)$/)
{
- $args = $5;
+ $args = $6;
my($envset) = (defined $1)? $1 : "";
- my($sudo) = (defined $3)? "sudo " : "";
- my($special)= (defined $4)? $4 : "";
+ my($sudo) = (defined $3)? "sudo " . (defined $4 ? "-u $4 ":"") : "";
+ my($special)= (defined $5)? $5 : "";
$wait_time = (defined $2)? $2 : 0;
# Return 2 rather than 1 afterwards
if ($cmd =~ /\s-DSERVER=server\s/ && $cmd !~ /\s-DNOTDAEMON\s/)
{
+ $pidfile = "$parm_cwd/spool/exim-daemon.pid";
if ($debug) { printf ">> daemon: $cmd\n"; }
run_system("sudo mkdir spool/log 2>/dev/null");
run_system("sudo chown $parm_eximuser:$parm_eximgroup spool/log");
# file is written to the spool directory, in case the Exim binary was
# built with PID_FILE_PATH pointing somewhere else.
- $cmd =~ s!\s-bd\s! -bdf -oP $parm_cwd/spool/exim-daemon.pid !;
+ if ($cmd =~ /\s-oP\s/)
+ {
+ ($pidfile = $cmd) =~ s/^.*-oP ([^ ]+).*$/$1/;
+ $cmd =~ s!\s-bd\s! -bdf !;
+ }
+ else
+ {
+ $pidfile = "$parm_cwd/spool/exim-daemon.pid";
+ $cmd =~ s!\s-bd\s! -bdf -oP $pidfile !;
+ }
print ">> |${cmd}-server\n" if ($debug);
open DAEMONCMD, "|${cmd}-server" || tests_exit(-1, "Failed to run $cmd");
DAEMONCMD->autoflush(1);
while (<SCRIPT>) { $lineno++; last if /^\*{4}\s*$/; } # Ignore any input
- select(undef, undef, undef, 0.3); # Let the daemon get going
+
+ # Interlock with daemon startup
+ while (! stat("$pidfile") ) { select(undef, undef, undef, 0.3); }
return 3; # Don't wait
}
elsif ($cmd =~ /\s-DSERVER=wait:(\d+)\s/)
##################################################
# If the first character of the first argument is '/', the argument is taken
-# as the path to the binary.
+# as the path to the binary. If the first argument does not start with a
+# '/' but exists in the file system, it's assumed to be the Exim binary.
-$parm_exim = (@ARGV > 0 && $ARGV[0] =~ m?^/?)? shift @ARGV : "";
+$parm_exim = (@ARGV > 0 && (-x $ARGV[0] or $ARGV[0] =~ m?^/?))? Cwd::abs_path(shift @ARGV) : "";
print "Exim binary is $parm_exim\n" if $parm_exim ne "";
if ($arg eq "-NOIPV4") { $have_ipv4 = 0; next; }
if ($arg eq "-NOIPV6") { $have_ipv6 = 0; next; }
if ($arg eq "-KEEP") { $save_output = 1; next; }
+ if ($arg =~ /^-FLAVOU?R$/) { $flavour = shift; next; }
}
$optargs .= " $arg";
}
print("Probing with config file: $parm_cwd/test-config\n");
open(EXIMINFO, "$parm_exim -d -C $parm_cwd/test-config -DDIR=$parm_cwd " .
- "-bP exim_user exim_group|") ||
+ "-bP exim_user exim_group 2>&1|") ||
die "** Cannot run $parm_exim: $!\n";
while(<EXIMINFO>)
{
$parm_eximuser = $1 if /^exim_user = (.*)$/;
$parm_eximgroup = $1 if /^exim_group = (.*)$/;
+ $parm_trusted_config_list = $1 if /^TRUSTED_CONFIG_LIST:.*?"(.*?)"$/;
}
close(EXIMINFO);
else { $parm_exim_gid = getgrnam($parm_eximgroup); }
}
+# check the permissions on the TRUSTED_CONFIG_LIST
+if (defined $parm_trusted_config_list)
+ {
+ die "TRUSTED_CONFIG_LIST: $parm_trusted_config_list: $!\n"
+ if not -f $parm_trusted_config_list;
+
+ die "TRUSTED_CONFIG_LIST $parm_trusted_config_list must not be world writable!\n"
+ if 02 & (stat _)[2];
+
+ die sprintf "TRUSTED_CONFIG_LIST: $parm_trusted_config_list %d is group writable, but not owned by group '%s' or '%s'.\n",
+ (stat _)[1],
+ scalar(getgrgid 0), scalar(getgrgid $>)
+ if (020 & (stat _)[2]) and not ((stat _)[5] == $> or (stat _)[5] == 0);
+
+ die sprintf "TRUSTED_CONFIG_LIST: $parm_trusted_config_list is not owned by user '%s' or '%s'.\n",
+ scalar(getpwuid 0), scalar(getpwuid $>)
+ if (not (-o _ or (stat _)[4] == 0));
+
+ open(TCL, $parm_trusted_config_list) or die "Can't open $parm_trusted_config_list: $!\n";
+ my $test_config = getcwd() . '/test-config';
+ die "Can't find '$test_config' in TRUSTED_CONFIG_LIST $parm_trusted_config_list."
+ if not grep { /^$test_config$/ } <TCL>;
+ }
+else
+ {
+ die "Unable to check the TRUSTED_CONFIG_LIST, seems to be empty?\n";
+ }
+
open(EXIMINFO, "$parm_exim -bV -C $parm_cwd/test-config -DDIR=$parm_cwd |") ||
die "** Cannot run $parm_exim: $!\n";
$_ =~ /^\s*inet(?:\saddr)?:?\s?(\d+\.\d+\.\d+\.\d+)\s/i)
{
$ip = $1;
- next if ($ip =~ /^127\./);
+ next if ($ip =~ /^127\./ || $ip =~ /^10\./);
$parm_ipv4 = $ip;
}
# because the current binary does not support the right facilities, and also
# those that are outside the numerical range selected.
-print "\nTest range is $test_start to $test_end\n";
+print "\nTest range is $test_start to $test_end (flavour $flavour)\n";
print "Omitting \${dlfunc expansion tests (loadable module not present)\n"
if $dlfunc_deleted;
print "Omitting dbm tests (unable to copy exim_dbmbuild)\n"
foreach $test (@testlist)
{
- next if $test !~ /^\d{4}$/;
+ next if $test !~ /^\d{4}(?:\.\d+)?$/;
next if $test < $test_start || $test > $test_end;
push @test_list, "$testdir/$test";
}
local($lineno) = 0;
local($commandno) = 0;
local($subtestno) = 0;
- local($testno) = substr($test, -4);
+ (local $testno = $test) =~ s|.*/||;
local($sortlog) = 0;
my($gnutls) = 0;
my($rc, $run_extra) = run_command($testno, \$subtestno, \$expectrc, \$commandname, $TEST_STATE);
my($cmdrc) = $?;
+$0 = "[runtest $testno]";
+
if ($debug) {
print ">> rc=$rc cmdrc=$cmdrc\n";
if (defined $run_extra) {