Docs for "G" modifier on numbers in ${if comparisons.
[users/heiko/exim.git] / test / runtest
index 2eb714e8a17e1c7f120de86f84846af23ee10266..b88e71e60f05e5524b34a095e61ad077b5e89790 100755 (executable)
 # Placed in the Exim CVS: 06 February 2006                                    #
 ###############################################################################
 
 # Placed in the Exim CVS: 06 February 2006                                    #
 ###############################################################################
 
+#use strict;
 require Cwd;
 use Errno;
 use FileHandle;
 use Socket;
 require Cwd;
 use Errno;
 use FileHandle;
 use Socket;
+use Time::Local;
 
 
 # Start by initializing some global variables
 
 
 
 # Start by initializing some global variables
 
-$testversion = "4.78 (08-May-12)";
+$testversion = "4.80 (08-May-12)";
+
+# This gets embedded in the D-H params filename, and the value comes
+# from asking GnuTLS for "normal", but there appears to be no way to
+# use certtool/... to ask what that value currently is.  *sigh*
+# We also clamp it because of NSS interop, see addition of tls_dh_max_bits.
+# This value is correct as of GnuTLS 2.12.18 as clamped by tls_dh_max_bits.
+# normal = 2432   tls_dh_max_bits = 2236
+$gnutls_dh_bits_normal = 2236;
 
 $cf = "bin/cf -exact";
 $cr = "\r";
 
 $cf = "bin/cf -exact";
 $cr = "\r";
@@ -65,6 +75,9 @@ $parm_port_d2 = 1226;        # Additional for daemon
 $parm_port_d3 = 1227;        # Additional for daemon
 $parm_port_d4 = 1228;        # Additional for daemon
 
 $parm_port_d3 = 1227;        # Additional for daemon
 $parm_port_d4 = 1228;        # Additional for daemon
 
+# Manually set locale
+$ENV{'LC_ALL'} = 'C';
+
 
 
 ###############################################################################
 
 
 ###############################################################################
@@ -179,6 +192,8 @@ system("sudo /bin/rm -rf ./spool test-* ./dnszones/*")
   if ($rc == 0 && !$save_output);
 
 system("sudo /bin/rm -rf ./eximdir/*");
   if ($rc == 0 && !$save_output);
 
 system("sudo /bin/rm -rf ./eximdir/*");
+
+print "\nYou were in test $test at the end there.\n\n" if defined $test;
 exit $rc if ($rc >= 0);
 die "** runtest error: $_[1]\n";
 }
 exit $rc if ($rc >= 0);
 die "** runtest error: $_[1]\n";
 }
@@ -209,14 +224,26 @@ return $newid;
 }
 
 
 }
 
 
-# This is used while munging the output from exim_dumpdb. We cheat by assuming
-# that the  date always the same, and just return the number of seconds since
-# midnight.
+# This is used while munging the output from exim_dumpdb.
+# May go wrong across DST changes.
 
 sub date_seconds {
 my($day,$month,$year,$hour,$min,$sec) =
   $_[0] =~ /^(\d\d)-(\w\w\w)-(\d{4})\s(\d\d):(\d\d):(\d\d)/;
 
 sub date_seconds {
 my($day,$month,$year,$hour,$min,$sec) =
   $_[0] =~ /^(\d\d)-(\w\w\w)-(\d{4})\s(\d\d):(\d\d):(\d\d)/;
-return $hour * 60 * 60 + $min * 60 + $sec;
+my($mon);
+if   ($month =~ /Jan/) {$mon = 0;}
+elsif($month =~ /Feb/) {$mon = 1;}
+elsif($month =~ /Mar/) {$mon = 2;}
+elsif($month =~ /Apr/) {$mon = 3;}
+elsif($month =~ /May/) {$mon = 4;}
+elsif($month =~ /Jun/) {$mon = 5;}
+elsif($month =~ /Jul/) {$mon = 6;}
+elsif($month =~ /Aug/) {$mon = 7;}
+elsif($month =~ /Sep/) {$mon = 8;}
+elsif($month =~ /Oct/) {$mon = 9;}
+elsif($month =~ /Nov/) {$mon = 10;}
+elsif($month =~ /Dec/) {$mon = 11;}
+return timelocal($sec,$min,$hour,$day,$mon,$year);
 }
 
 
 }
 
 
@@ -320,6 +347,9 @@ RESET_AFTER_EXTRA_LINE_READ:
   # But convert "name=the.local.host address=127.0.0.1" to use "localhost"
   s/name=the\.local\.host address=127\.0\.0\.1/name=localhost address=127.0.0.1/g;
 
   # But convert "name=the.local.host address=127.0.0.1" to use "localhost"
   s/name=the\.local\.host address=127\.0\.0\.1/name=localhost address=127.0.0.1/g;
 
+  # The name of the shell may vary
+  s/\s\Q$parm_shell\E\b/ ENV_SHELL/;
+
   # Replace the path to the testsuite directory
   s?\Q$parm_cwd\E?TESTSUITE?g;
 
   # Replace the path to the testsuite directory
   s?\Q$parm_cwd\E?TESTSUITE?g;
 
@@ -367,9 +397,6 @@ RESET_AFTER_EXTRA_LINE_READ:
   # The message for a non-listening FIFO varies
   s/:[^:]+: while opening named pipe/: Error: while opening named pipe/;
 
   # The message for a non-listening FIFO varies
   s/:[^:]+: while opening named pipe/: Error: while opening named pipe/;
 
-  # The name of the shell may vary
-  s/\s\Q$parm_shell\E\b/ SHELL/;
-
   # Debugging output of lists of hosts may have different sort keys
   s/sort=\S+/sort=xx/ if /^\S+ (?:\d+\.){3}\d+ mx=\S+ sort=\S+/;
 
   # Debugging output of lists of hosts may have different sort keys
   s/sort=\S+/sort=xx/ if /^\S+ (?:\d+\.){3}\d+ mx=\S+ sort=\S+/;
 
@@ -454,6 +481,42 @@ RESET_AFTER_EXTRA_LINE_READ:
     \d{4}-\d\d-\d\d\s\d\d:\d\d:\d\d/Exim statistics from <time> to <time>/x;
 
 
     \d{4}-\d\d-\d\d\s\d\d:\d\d:\d\d/Exim statistics from <time> to <time>/x;
 
 
+  # ======== TLS certificate algorithms ========
+  # Test machines might have various different TLS library versions supporting
+  # different protocols; can't rely upon TLS 1.2's AES256-GCM-SHA384, so we
+  # treat the standard algorithms the same.
+  # So far, have seen:
+  #   TLSv1:AES256-SHA:256
+  #   TLSv1.2:AES256-GCM-SHA384:256
+  #   TLSv1.2:DHE-RSA-AES256-SHA:256
+  #   TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128
+  # We also need to handle the ciphersuite without the TLS part present, for
+  # client-ssl's output.  We also see some older forced ciphersuites, but
+  # negotiating TLS 1.2 instead of 1.0.
+  # Mail headers (...), log-lines X=..., client-ssl output ...
+  # (and \b doesn't match between ' ' and '(' )
+
+  s/( (?: (?:\b|\s) [\(=] ) | \s )TLSv1\.2:/$1TLSv1:/xg;
+  s/\bAES256-GCM-SHA384\b/AES256-SHA/g;
+  s/\bDHE-RSA-AES256-SHA\b/AES256-SHA/g;
+
+  # GnuTLS have seen:
+  #   TLS1.2:RSA_AES_256_CBC_SHA1:256 (canonical)
+  #   TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128
+  #
+  #   X=TLS1.2:DHE_RSA_AES_256_CBC_SHA256:256
+  #   X=TLS1.2:RSA_AES_256_CBC_SHA1:256
+  #   X=TLS1.1:RSA_AES_256_CBC_SHA1:256
+  #   X=TLS1.0:DHE_RSA_AES_256_CBC_SHA1:256
+  # and as stand-alone cipher:
+  #   DHE-RSA-AES256-SHA256
+  #   DHE-RSA-AES256-SHA
+  # picking latter as canonical simply because regex easier that way.
+  s/\bDHE_RSA_AES_128_CBC_SHA1:128/RSA_AES_256_CBC_SHA1:256/g;
+  s/TLS1.[012]:(DHE_)?RSA_AES_256_CBC_SHA(1|256):256/TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256/g;
+  s/\bDHE-RSA-AES256-SHA256\b/DHE-RSA-AES256-SHA/g;
+
+
   # ======== Caller's login, uid, gid, home, gecos ========
 
   s/\Q$parm_caller_home\E/CALLER_HOME/g;   # NOTE: these must be done
   # ======== Caller's login, uid, gid, home, gecos ========
 
   s/\Q$parm_caller_home\E/CALLER_HOME/g;   # NOTE: these must be done
@@ -659,7 +722,6 @@ RESET_AFTER_EXTRA_LINE_READ:
 
   s/(TLS error on connection (?:from|to) .*? \(SSL_\w+\): error:)(.*)/$1 <<detail omitted>>/;
 
 
   s/(TLS error on connection (?:from|to) .*? \(SSL_\w+\): error:)(.*)/$1 <<detail omitted>>/;
 
-
   # ======== Maildir things ========
   # timestamp output in maildir processing
   s/(timestamp=|\(timestamp_only\): )\d+/$1ddddddd/g;
   # ======== Maildir things ========
   # timestamp output in maildir processing
   s/(timestamp=|\(timestamp_only\): )\d+/$1ddddddd/g;
@@ -792,6 +854,15 @@ RESET_AFTER_EXTRA_LINE_READ:
     # be the case
     next if /^changing group to \d+ failed: Operation not permitted/;
 
     # be the case
     next if /^changing group to \d+ failed: Operation not permitted/;
 
+    # We might not keep this check; rather than change all the tests, just
+    # ignore it as long as it succeeds; then we only need to change the
+    # TLS tests where tls_require_ciphers has been set.
+    if (m{^changed uid/gid: calling tls_validate_require_cipher}) {
+      my $discard = <IN>;
+      next;
+    }
+    next if /^tls_validate_require_cipher child \d+ ended: status=0x0/;
+
     # We invoke Exim with -D, so we hit this new messag as of Exim 4.73:
     next if /^macros_trusted overridden to true by whitelisting/;
 
     # We invoke Exim with -D, so we hit this new messag as of Exim 4.73:
     next if /^macros_trusted overridden to true by whitelisting/;
 
@@ -1545,9 +1616,10 @@ if (/^eximstats\s+(.*)/)
 
 if (/^gnutls/)
   {
 
 if (/^gnutls/)
   {
-  run_system "sudo cp -p aux-fixed/gnutls-params spool/gnutls-params;" .
-         "sudo chown $parm_eximuser:$parm_eximgroup spool/gnutls-params;" .
-         "sudo chmod 0400 spool/gnutls-params";
+  my $gen_fn = "spool/gnutls-params-$gnutls_dh_bits_normal";
+  run_system "sudo cp -p aux-fixed/gnutls-params $gen_fn;" .
+         "sudo chown $parm_eximuser:$parm_eximgroup $gen_fn;" .
+         "sudo chmod 0400 $gen_fn";
   return 1;
   }
 
   return 1;
   }
 
@@ -1564,12 +1636,20 @@ if (/^killdaemon/)
     $pid = $aux_info->{exim_pid};
     $return_extra->{exim_pid} = undef;
     print ">> killdaemon: recovered pid $pid\n" if $debug;
     $pid = $aux_info->{exim_pid};
     $return_extra->{exim_pid} = undef;
     print ">> killdaemon: recovered pid $pid\n" if $debug;
+    if ($pid)
+      {
+      run_system("sudo /bin/kill -SIGINT $pid");
+      wait;
+      }
     } else {
     $pid = `cat $parm_cwd/spool/exim-daemon.*`;
     } else {
     $pid = `cat $parm_cwd/spool/exim-daemon.*`;
+    if ($pid)
+      {
+      run_system("sudo /bin/kill -SIGINT $pid");
+      close DAEMONCMD;                                   # Waits for process
+      }
     }
     }
-  run_system("sudo /bin/kill -SIGINT $pid");
-  close DAEMONCMD;                                   # Waits for process
-  run_system("sudo /bin/rm -f spool/exim-daemon.*");
+    run_system("sudo /bin/rm -f spool/exim-daemon.*");
   return (1, $return_extra);
   }
 
   return (1, $return_extra);
   }
 
@@ -1798,6 +1878,10 @@ elsif (/^([A-Z_]+=\S+\s+)?(\d+)?\s*(sudo\s+)?exim(_\S+)?\s+(.*)$/)
 
     my($i);
     for ($i = @msglist; $i > 0; $i--) { $args =~ s/\$msg$i/$msglist[$i-1]/g; }
 
     my($i);
     for ($i = @msglist; $i > 0; $i--) { $args =~ s/\$msg$i/$msglist[$i-1]/g; }
+    if ( $args =~ /\$msg\d/ )
+      {
+      tests_exit(-1, "Not enough messages in spool, for test $testno line $lineno\n");
+      }
     }
 
   # If -d is specified in $optargs, remove it from $args; i.e. let
     }
 
   # If -d is specified in $optargs, remove it from $args; i.e. let
@@ -1844,6 +1928,7 @@ elsif (/^([A-Z_]+=\S+\s+)?(\d+)?\s*(sudo\s+)?exim(_\S+)?\s+(.*)$/)
   elsif ($cmd =~ /\s-DSERVER=wait:(\d+)\s/)
     {
     my $listen_port = $1;
   elsif ($cmd =~ /\s-DSERVER=wait:(\d+)\s/)
     {
     my $listen_port = $1;
+    my $waitmode_sock = new FileHandle;
     if ($debug) { printf ">> wait-mode daemon: $cmd\n"; }
     run_system("sudo mkdir spool/log 2>/dev/null");
     run_system("sudo chown $parm_eximuser:$parm_eximgroup spool/log");
     if ($debug) { printf ">> wait-mode daemon: $cmd\n"; }
     run_system("sudo mkdir spool/log 2>/dev/null");
     run_system("sudo chown $parm_eximuser:$parm_eximgroup spool/log");
@@ -1851,18 +1936,19 @@ elsif (/^([A-Z_]+=\S+\s+)?(\d+)?\s*(sudo\s+)?exim(_\S+)?\s+(.*)$/)
     my ($s_ip,$s_port) = ('127.0.0.1', $listen_port);
     my $sin = sockaddr_in($s_port, inet_aton($s_ip))
         or die "** Failed packing $s_ip:$s_port\n";
     my ($s_ip,$s_port) = ('127.0.0.1', $listen_port);
     my $sin = sockaddr_in($s_port, inet_aton($s_ip))
         or die "** Failed packing $s_ip:$s_port\n";
-    socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp'))
+    socket($waitmode_sock, PF_INET, SOCK_STREAM, getprotobyname('tcp'))
         or die "** Unable to open socket $s_ip:$s_port: $!\n";
         or die "** Unable to open socket $s_ip:$s_port: $!\n";
-    setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, 1)
+    setsockopt($waitmode_sock, SOL_SOCKET, SO_REUSEADDR, 1)
         or die "** Unable to setsockopt(SO_REUSEADDR): $!\n";
         or die "** Unable to setsockopt(SO_REUSEADDR): $!\n";
-    bind(SOCK, $sin)
+    bind($waitmode_sock, $sin)
         or die "** Unable to bind socket ($s_port): $!\n";
         or die "** Unable to bind socket ($s_port): $!\n";
-    listen(SOCK, 5);
+    listen($waitmode_sock, 5);
     my $pid = fork();
     if (not defined $pid) { die "** fork failed: $!\n" }
     if (not $pid) {
       close(STDIN);
     my $pid = fork();
     if (not defined $pid) { die "** fork failed: $!\n" }
     if (not $pid) {
       close(STDIN);
-      open(STDIN, "<&", SOCK) or die "** dup sock to stdin failed: $!\n";
+      open(STDIN, "<&", $waitmode_sock) or die "** dup sock to stdin failed: $!\n";
+      close($waitmode_sock);
       print "[$$]>> ${cmd}-server\n" if ($debug);
       exec "exec ${cmd}-server";
       exit(1);
       print "[$$]>> ${cmd}-server\n" if ($debug);
       exec "exec ${cmd}-server";
       exit(1);
@@ -2234,6 +2320,8 @@ unlink("$parm_cwd/test-config");
 
 if (defined $parm_support{'Content_Scanning'})
   {
 
 if (defined $parm_support{'Content_Scanning'})
   {
+  my $sock = new FileHandle;
+
   if (system("spamc -h 2>/dev/null >/dev/null") == 0)
     {
     print "The spamc command works:\n";
   if (system("spamc -h 2>/dev/null >/dev/null") == 0)
     {
     print "The spamc command works:\n";
@@ -2250,23 +2338,23 @@ if (defined $parm_support{'Content_Scanning'})
       {
       my $sin = sockaddr_in($sport, inet_aton($sint))
           or die "** Failed packing $sint:$sport\n";
       {
       my $sin = sockaddr_in($sport, inet_aton($sint))
           or die "** Failed packing $sint:$sport\n";
-      socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp'))
+      socket($sock, PF_INET, SOCK_STREAM, getprotobyname('tcp'))
           or die "** Unable to open socket $sint:$sport\n";
 
       local $SIG{ALRM} =
           sub { die "** Timeout while connecting to socket $sint:$sport\n"; };
       alarm(5);
           or die "** Unable to open socket $sint:$sport\n";
 
       local $SIG{ALRM} =
           sub { die "** Timeout while connecting to socket $sint:$sport\n"; };
       alarm(5);
-      connect(SOCK, $sin)
+      connect($sock, $sin)
           or die "** Unable to connect to socket $sint:$sport\n";
       alarm(0);
 
           or die "** Unable to connect to socket $sint:$sport\n";
       alarm(0);
 
-      select((select(SOCK), $| = 1)[0]);
-      print SOCK "bad command\r\n";
+      select((select($sock), $| = 1)[0]);
+      print $sock "bad command\r\n";
 
       $SIG{ALRM} =
           sub { die "** Timeout while reading from socket $sint:$sport\n"; };
       alarm(10);
 
       $SIG{ALRM} =
           sub { die "** Timeout while reading from socket $sint:$sport\n"; };
       alarm(10);
-      my $res = <SOCK>;
+      my $res = <$sock>;
       alarm(0);
 
       $res =~ m|^SPAMD/|
       alarm(0);
 
       $res =~ m|^SPAMD/|
@@ -2377,18 +2465,18 @@ if (defined $parm_support{'Content_Scanning'})
             {
             die "** Unknown socket domain '$socket_domain' (should not happen)\n";
             }
             {
             die "** Unknown socket domain '$socket_domain' (should not happen)\n";
             }
-          socket(SOCK, $socket_domain, SOCK_STREAM, 0) or die "** Unable to open socket '$parm_clamsocket'\n";
+          socket($sock, $socket_domain, SOCK_STREAM, 0) or die "** Unable to open socket '$parm_clamsocket'\n";
           local $SIG{ALRM} = sub { die "** Timeout while connecting to socket '$parm_clamsocket'\n"; };
           alarm(5);
           local $SIG{ALRM} = sub { die "** Timeout while connecting to socket '$parm_clamsocket'\n"; };
           alarm(5);
-          connect(SOCK, $socket) or die "** Unable to connect to socket '$parm_clamsocket'\n";
+          connect($sock, $socket) or die "** Unable to connect to socket '$parm_clamsocket'\n";
           alarm(0);
 
           alarm(0);
 
-          my $ofh = select SOCK; $| = 1; select $ofh;
-          print SOCK "PING\n";
+          my $ofh = select $sock; $| = 1; select $ofh;
+          print $sock "PING\n";
 
           $SIG{ALRM} = sub { die "** Timeout while reading from socket '$parm_clamsocket'\n"; };
           alarm(10);
 
           $SIG{ALRM} = sub { die "** Timeout while reading from socket '$parm_clamsocket'\n"; };
           alarm(10);
-          my $res = <SOCK>;
+          my $res = <$sock>;
           alarm(0);
 
           $res =~ /PONG/ or die "** Did not get PONG from socket '$parm_clamsocket'. It said: $res\n";
           alarm(0);
 
           $res =~ /PONG/ or die "** Did not get PONG from socket '$parm_clamsocket'. It said: $res\n";
@@ -2640,9 +2728,11 @@ if ($parm_hostname !~ /\./)
   print "\n*** Host name is not fully qualified: this may cause problems ***\n\n";
   }
 
   print "\n*** Host name is not fully qualified: this may cause problems ***\n\n";
   }
 
-# Find the user's shell
+if ($parm_hostname =~ /[[:upper:]]/)
+  {
+  print "\n*** Host name has upper case characters: this may cause problems ***\n\n";
+  }
 
 
-$parm_shell = $ENV{'SHELL'};
 
 
 ##################################################
 
 
 ##################################################
@@ -2963,6 +3053,10 @@ foreach $basedir ("aux-var", "dnszones")
     }
   }
 
     }
   }
 
+# Set a user's shell, distinguishable from /bin/sh
+
+symlink("/bin/sh","aux-var/sh");
+$ENV{'SHELL'} = $parm_shell = $parm_cwd . "/aux-var/sh";
 
 ##################################################
 #     Create fake DNS zones for this host        #
 
 ##################################################
 #     Create fake DNS zones for this host        #
@@ -3010,6 +3104,8 @@ if ($have_ipv6 && $parm_ipv6 ne "::1")
     $exp_v6 = $1 . ':0' x (8-length($exp_v6)) . ':' . $2;
   } elsif ( $parm_ipv6 =~ /^::(.+[^:])$/ ) {
     $exp_v6 = '0:' x (9-length($exp_v6)) . $1;
     $exp_v6 = $1 . ':0' x (8-length($exp_v6)) . ':' . $2;
   } elsif ( $parm_ipv6 =~ /^::(.+[^:])$/ ) {
     $exp_v6 = '0:' x (9-length($exp_v6)) . $1;
+  } else {
+    $exp_v6 = $parm_ipv6;
   }
   my(@components) = split /:/, $exp_v6;
   my(@nibbles) = reverse (split /\s*/, shift @components);
   }
   my(@components) = split /:/, $exp_v6;
   my(@nibbles) = reverse (split /\s*/, shift @components);