Changed the test harness to attempt to gracefully shut down servers
authorDan Fandrich <dan@coneharvesters.com>
Sat, 24 Mar 2007 01:01:28 +0000 (01:01 +0000)
committerDan Fandrich <dan@coneharvesters.com>
Sat, 24 Mar 2007 01:01:28 +0000 (01:01 +0000)
before resorting to the kill -9 hammer.

Added test harness infrastructure to support scp/sftp tests, using
OpenSSH as the server.

CHANGES
tests/FILEFORMAT
tests/runtests.pl
tests/sshserver.pl [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
index dec9678..950ea5b 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -10,6 +10,12 @@ Dan F (23 March 2007)
 - Added --pubkey option to curl and made --key also work for SCP/SFTP,
   plus made --pass work on an SSH private key as well.
 
+- Changed the test harness to attempt to gracefully shut down servers
+  before resorting to the kill -9 hammer.
+
+- Added test harness infrastructure to support scp/sftp tests, using
+  OpenSSH as the server.
+
 Yang Tse (20 March 2007)
 - Fixed: When a signal was caught awaiting for an event using Curl_select()
   or Curl_poll() with a non-zero timeout both functions would restart the
index 56e2e3e..8e3f6ce 100644 (file)
@@ -126,6 +126,8 @@ http
 http-ipv6
 https
 none
+scp
+sftp
 
 Give only one per line.  This subsection is mandatory.
 </server>
@@ -212,9 +214,11 @@ Available substitute variables include:
 %FTP2PORT  - Port number of the FTP server 2
 %TFTPPORT  - Port number of the TFTP server
 %TFTP6PORT - IPv6 port number of the TFTP server
+%SSHPORT   - Port number of the SCP/SFTP server
 %SRCDIR    - Full path to the source dir
 %PWD       - Current directory
 %CURL      - Path to the curl executable
+%USER      - Login ID of the user running the test
 </command>
 
 <file name="log/filename">
index 56ed781..30d1254 100755 (executable)
@@ -48,6 +48,7 @@ my $FTPSPORT; # FTPS server port
 my $FTP6PORT; # FTP IPv6 server port
 my $TFTPPORT; # TFTP
 my $TFTP6PORT; # TFTP
+my $SSHPORT; # SCP/SFTP
 
 my $CURL="../src/curl"; # what curl executable to run on the tests
 my $DBGCURL=$CURL; #"../src/.libs/curl";  # alternative for debugging
@@ -79,6 +80,7 @@ my $FTP2PIDFILE=".ftp2.pid";
 my $FTPSPIDFILE=".ftps.pid";
 my $TFTPPIDFILE=".tftpd.pid";
 my $TFTP6PIDFILE=".tftp6.pid";
+my $SSHPIDFILE=".ssh.pid";
 
 # invoke perl like this:
 my $perl="perl -I$srcdir";
@@ -197,6 +199,15 @@ sub logmsg {
 
 chomp($pwd = `pwd`);
 
+# get the name of the current user
+my $USER = $ENV{USER}; # Linux
+if (!$USER) {
+    $USER = $ENV{USERNAME};    # Windows
+    if (!$USER) {
+        $USER = $ENV{LOGNAME}; # Some UNIX (I think)
+    }
+}
+
 # enable memory debugging if curl is compiled with it
 $ENV{'CURL_MEMDEBUG'} = $memdump;
 $ENV{'HOME'}=$pwd;
@@ -231,6 +242,16 @@ $ENV{'SSL_CERT_PATH'}=undef;
 $ENV{'CURL_CA_BUNDLE'}=undef;
 
 #######################################################################
+# Check if a given child process has just died. Reaps it if so.
+#
+sub checkdied {
+    use POSIX ":sys_wait_h";
+    my $pid = $_[0];
+    my $rc = waitpid($pid, &WNOHANG);
+    return $rc == $pid;
+}
+
+#######################################################################
 # Start a new thread/process and run the given command line in there.
 # Return the pids (yes plural) of the new child process to the parent.
 #
@@ -277,6 +298,15 @@ sub startnew {
                 last;
             }
         }
+        if (checkdied($child)) {
+            logmsg "startnew: Warning: child process has died\n" if($verbose);
+            # We can't just abort waiting for the server with a
+            # return (-1,-1);
+            # because the server might have forked and could still start
+            # up normally. Instead, just reduce the amount of time we remain
+            # waiting.
+            $count >>= 2;
+        }
         sleep(1);
     }
 
@@ -411,8 +441,10 @@ sub stopserver {
         return; # whad'da'ya wanna'da with no pid ?
     }
 
-    # it might be more than one pid
+    # It might be more than one pid
+    # Send each one a SIGTERM to gracefully kill it
 
+    my @killed;
     my @pids = split(/\s+/, $pid);
     for (@pids) {
         chomp($_);
@@ -421,10 +453,27 @@ sub stopserver {
                 if($verbose) {
                     logmsg "RUN: Test server pid $1 signalled to die\n";
                 }
-                kill(9, $1); # die!
+                kill(15, $1); # die!
+                push @killed, $1;
             }
         }
     }
+
+    # Give each process killed up to a few seconds to die, then send
+    # a SIGKILL to finish it off for good.
+    for (@killed) {
+        my $count = 5; # wait for this many seconds for server to die
+       while($count--) {
+            if (!kill(0, $_) || checkdied($_)) {
+                last;
+            }
+            sleep(1);
+        }
+        if ($count < 0) {
+            logmsg "RUN: forcing pid $_ to die with SIGKILL\n";
+            kill(9, $_); # die!
+        }
+    }
 }
 
 #######################################################################
@@ -521,6 +570,17 @@ sub verifyftp {
 }
 
 #######################################################################
+# STUB for verifying scp/sftp
+
+sub verifyssh {
+    my ($proto, $ip, $port) = @_;
+    open(FILE, "<" . $SSHPIDFILE);
+    my $pid=0+<FILE>;
+    close(FILE);
+    return $pid;
+}
+
+#######################################################################
 # Verify that the server that runs on $ip, $port is our server.
 # Retry during 5 seconds before giving up.
 #
@@ -529,7 +589,8 @@ my %protofunc = ('http' => \&verifyhttp,
                  'https' => \&verifyhttp,
                  'ftp' => \&verifyftp,
                  'ftps' => \&verifyftp,
-                 'tftp' => \&verifyftp);
+                 'tftp' => \&verifyftp,
+                 'ssh' => \&verifyssh);
 
 sub verifyserver {
     my ($proto, $ip, $port) = @_;
@@ -853,6 +914,44 @@ sub runtftpserver {
 
 
 #######################################################################
+# Start the scp/sftp server
+#
+sub runsshserver {
+    my ($id, $verbose, $ipv6) = @_;
+    my $ip=$HOSTIP;
+    my $port = $SSHPORT;
+    my $pidfile = $SSHPIDFILE;
+
+    my $pid = checkserver($pidfile);
+    if($pid > 0) {
+        stopserver($pid);
+    }
+
+    my $flag=$debugprotocol?"-v ":"";
+    my $cmd="$perl $srcdir/sshserver.pl $flag-u $USER -d $srcdir $port";
+    my ($sshpid, $pid2) =
+        startnew($cmd, $pidfile); # start the server in a new process
+
+    if(!$sshpid || !kill(0, $sshpid)) {
+        # it is NOT alive
+        logmsg "RUN: failed to start the SSH server!\n";
+        # failed to talk to it properly. Kill the server and return failure
+        stopserver("$sshpid $pid2");
+        return -1;
+    }
+
+    if (!verifyserver('ssh',$ip,$port)) {
+        logmsg "RUN: SSH server failed verification\n";
+        return (0,0);
+    }
+    if($verbose) {
+        logmsg "RUN: SSH server is now running PID $sshpid\n";
+    }
+
+    return ($pid2, $sshpid);
+}
+
+#######################################################################
 # Remove all files in the specified directory
 #
 sub cleardir {
@@ -1167,9 +1266,10 @@ sub checksystem {
     if($tftp_ipv6) {
         logmsg sprintf("* TFTP IPv6 port: %d\n", $TFTP6PORT);
     }
+    logmsg sprintf("* SCP/SFTP port:  %d\n", $SSHPORT);
 
     if($ssl_version) {
-        logmsg sprintf("* SSL library:    %s\n", $ssllib?"yassl":"<unknown>");
+        logmsg sprintf("* SSL library:    %s\n", $ssllib);
     }
 
     $has_textaware = ($^O eq 'MSWin32') || ($^O eq 'msys');
@@ -1197,7 +1297,9 @@ sub subVariables {
   $$thing =~ s/%PWD/$pwd/g;
   $$thing =~ s/%TFTPPORT/$TFTPPORT/g;
   $$thing =~ s/%TFTP6PORT/$TFTP6PORT/g;
+  $$thing =~ s/%SSHPORT/$SSHPORT/g;
   $$thing =~ s/%CURL/$CURL/g;
+  $$thing =~ s/%USER/$USER/g;
 
   # The purpose of FTPTIME2 and FTPTIME3 is to provide times that can be
   # used for time-out tests and that whould work on most hosts as these
@@ -2058,6 +2160,16 @@ sub startservers {
                 $run{'tftp-ipv6'}="$pid $pid2";
             }
         }
+        elsif($what eq "sftp" || $what eq "scp") {
+            if(!$run{'ssh'}) {
+                ($pid, $pid2) = runsshserver("", $verbose);
+                if($pid <= 0) {
+                    return "failed starting SSH server";
+                }
+                printf ("* pid ssh => %d %d\n", $pid, $pid2) if($verbose);
+                $run{'ssh'}="$pid $pid2";
+            }
+        }
         elsif($what eq "none") {
             logmsg "* starts no server\n" if ($verbose);
         }
@@ -2244,6 +2356,7 @@ $FTP2PORT =  $base + 5; # FTP server 2 port
 $FTP6PORT =  $base + 6; # FTP IPv6 port
 $TFTPPORT =  $base + 7; # TFTP (UDP) port
 $TFTP6PORT =  $base + 8; # TFTP IPv6 (UDP) port
+$SSHPORT =   $base + 9; # SSH (SCP/SFTP) port
 
 #######################################################################
 # clear and create logging directory:
diff --git a/tests/sshserver.pl b/tests/sshserver.pl
new file mode 100644 (file)
index 0000000..07762c2
--- /dev/null
@@ -0,0 +1,138 @@
+#/usr/bin/env perl
+# $Id$
+# Start sshd for use in the SCP and SFTP curl test harness tests
+
+# Options:
+# -u user
+# -v
+# target_port
+
+use strict;
+use File::Spec;
+
+my $verbose=0; # set to 1 for debugging
+
+my $port = 8999;        # just our default, weird enough
+
+my $path = `pwd`;
+chomp $path;
+
+my $exeext;
+if ($^O eq 'MSWin32' || $^O eq 'cygwin' || $^O eq 'msys' || $^O eq 'dos' || $^O eq 'os2') {
+    $exeext = '.exe';
+}
+
+# Where to look for sftp-server
+my @sftppath=qw(/usr/lib/openssh /usr/libexec/openssh /usr/libexec /usr/local/libexec /opt/local/libexec /usr/lib/ssh /usr/libexec/ssh /usr/sbin /usr/lib /usr/lib/ssh/openssh /usr/lib64/ssh);
+
+my $username = $ENV{USER};
+
+# Find a file somewhere in the given path
+sub searchpath {
+  my $fn = $_[0] . $exeext;
+  shift;
+  my @path = @_;
+  foreach (@path) {
+       my $file = File::Spec->catfile($_, $fn);
+       if (-e $file) {
+               return $file;
+       }
+  }
+}
+
+# Parse options
+do {
+    if($ARGV[0] eq "-v") {
+        $verbose=1;
+    }
+    elsif($ARGV[0] eq "-u") {
+        $username=$ARGV[1];
+        shift @ARGV;
+    }
+    elsif($ARGV[0] =~ /^(\d+)$/) {
+        $port = $1;
+    }
+} while(shift @ARGV);
+
+my $conffile="curl_sshd_config";       # sshd configuration data
+
+# Search the PATH for sshd.  sshd insists on being called with an absolute
+# path for some reason.
+my $sshd = searchpath("sshd", File::Spec->path());
+if (!$sshd) {
+       print "sshd is not available\n";
+       exit 1;
+}
+if ($verbose) {
+       print STDERR "SSH server found at $sshd\n";
+}
+
+my $sftp = searchpath("sftp-server", @sftppath);
+if (!$sftp) {
+       print "Could not find sftp-server plugin\n";
+       exit 1;
+}
+if ($verbose) {
+       print STDERR "SFTP server plugin found at $sftp\n";
+}
+
+if (! -e "curl_client_key.pub") {
+       if ($verbose) {
+               print STDERR "Generating host and client keys...\n";
+       }
+       # Make sure all files are gone so ssh-keygen doesn't complain
+       unlink("curl_host_dsa_key", "curl_client_key","curl_host_dsa_key.pub", "curl_client_key.pub"); 
+       system "ssh-keygen -q -t dsa -f curl_host_dsa_key -C 'curl test server' -N ''" and die "Could not generate key";
+        system "ssh-keygen -q -t dsa -f curl_client_key -C 'curl test client' -N ''" and die "Could not generate key";
+}
+
+open(FILE, ">$conffile") || die "Could not write $conffile";
+print FILE <<EOF
+# This is a generated file!  Do not edit!
+# OpenSSH sshd configuration file for curl testing
+AllowUsers $username
+AuthorizedKeysFile $path/curl_client_key.pub
+HostKey $path/curl_host_dsa_key
+PidFile $path/.ssh.pid
+Port $port
+ListenAddress localhost
+Protocol 2
+AllowTcpForwarding no
+HostbasedAuthentication no
+IgnoreRhosts yes
+IgnoreUserKnownHosts yes
+KeepAlive no
+PasswordAuthentication no
+PermitEmptyPasswords no
+PermitRootLogin no
+PrintLastLog no
+PrintMotd no
+StrictModes no
+Subsystem sftp $sftp
+UseLogin no
+X11Forwarding no
+# Newer OpenSSH options
+UsePam no
+UseDNS no
+ChallengeResponseAuthentication no
+EOF
+;
+close FILE;
+
+if (system "$sshd -t -q -f $conffile") {
+       # This is likely due to missing support for UsePam
+       print "$sshd is too old and is not supported\n";
+       unlink $conffile;
+       exit 1;
+}
+
+# Start the server
+my $rc = system "$sshd -e -f $conffile > log/ssh.log 2>&1";
+$rc >>= 8;
+if($rc) {
+    print STDERR "$sshd exited with $rc!\n";
+}
+
+unlink $conffile;
+
+exit $rc;