Modified the FTP server to use the new 'sockfilt' program to do all the socket
authorDaniel Stenberg <daniel@haxx.se>
Mon, 18 Apr 2005 06:57:44 +0000 (06:57 +0000)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 18 Apr 2005 06:57:44 +0000 (06:57 +0000)
level stuff. The FTP server communicates with sockfilt using perl's open2().
This enables easier IPv6 support and hopefully FTP-SSL support in the future.
Added four test cases for FTP-ipv6.

14 files changed:
tests/Makefile.am
tests/data/Makefile.am
tests/data/test103
tests/data/test252 [new file with mode: 0644]
tests/data/test253 [new file with mode: 0644]
tests/data/test254 [new file with mode: 0644]
tests/data/test255 [new file with mode: 0644]
tests/ftp.pm [new file with mode: 0644]
tests/ftpserver.pl
tests/runtests.pl
tests/server/Makefile.am
tests/server/getpart.c
tests/server/sockfilt.c [new file with mode: 0644]
tests/server/testpart.c [new file with mode: 0644]

index c1cfb73..9f02c14 100644 (file)
@@ -26,7 +26,8 @@ PDFPAGES = testcurl.pdf runtests.pdf
 
 EXTRA_DIST = ftpserver.pl httpserver.pl httpsserver.pl runtests.pl     \
  ftpsserver.pl getpart.pm FILEFORMAT README stunnel.pem memanalyze.pl  \
- testcurl.pl valgrind.pm testcurl.1 runtests.1 $(HTMLPAGES) $(PDFPAGES)
+ testcurl.pl valgrind.pm testcurl.1 runtests.1 $(HTMLPAGES) $(PDFPAGES) \
+ ftp.pm
 
 SUBDIRS = data server libtest
 
index 66c5f52..a1e455e 100644 (file)
@@ -34,7 +34,7 @@ EXTRA_DIST = test1 test108 test117 test127 test20 test27 test34 test46        \
  test199 test225 test226 test227 test230 test231 test232 test228       \
  test229 test233 test234 test235 test236 test520 test237 test238 \
  test239 test243 test245 test246 test247 test248 test249 test250 \
- test251
+ test251 test252 test253 test254 test255
 
 # The following tests have been removed from the dist since they no longer
 # work. We need to fix the test suite's FTPS server first, then bring them
index 87a640d..b7ae60b 100644 (file)
@@ -26,8 +26,8 @@ ftp://%HOSTIP:%FTPPORT/a/path/103 -P -
 # Verify data after the test has been "shot"
 <verify>
 <strippart>
-s/^LPRT.*[\n]//
-s/^EPRT.*[\n]//
+s/^LPRT.*/LPRT/
+s/^EPRT.*/EPRT/
 s/^(PORT 127,0,0,1,)([0-9,]+)/$1/
 </strippart>
 <protocol>
@@ -36,6 +36,8 @@ PASS curl_by_daniel@haxx.se
 PWD\r
 CWD a\r
 CWD path\r
+EPRT
+LPRT
 PORT 127,0,0,1,\r
 TYPE I\r
 SIZE 103\r
diff --git a/tests/data/test252 b/tests/data/test252
new file mode 100644 (file)
index 0000000..3e5ffa6
--- /dev/null
@@ -0,0 +1,57 @@
+<info>
+<keywords>
+FTP-ipv6
+EPSV
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data>
+total 20\r
+drwxr-xr-x   8 98       98           512 Oct 22 13:06 .\r
+drwxr-xr-x   8 98       98           512 Oct 22 13:06 ..\r
+drwxr-xr-x   2 98       98           512 May  2  1996 .NeXT\r
+-r--r--r--   1 0        1             35 Jul 16  1996 README\r
+lrwxrwxrwx   1 0        1              7 Dec  9  1999 bin -> usr/bin\r
+dr-xr-xr-x   2 0        1            512 Oct  1  1997 dev\r
+drwxrwxrwx   2 98       98           512 May 29 16:04 download.html\r
+dr-xr-xr-x   2 0        1            512 Nov 30  1995 etc\r
+drwxrwxrwx   2 98       1            512 Oct 30 14:33 pub\r
+dr-xr-xr-x   5 0        1            512 Oct  1  1997 usr\r
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+ipv6
+</features>
+<server>
+ftp-ipv6
+</server>
+ <name>
+FTP IPv6 dir list PASV
+ </name>
+ <command>
+-g "ftp://%HOST6IP:%FTP6PORT/"
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+filter off really nothing
+</strip>
+<protocol>
+USER anonymous\r
+PASS curl_by_daniel@haxx.se\r
+PWD\r
+EPSV\r
+TYPE A\r
+LIST\r
+QUIT\r
+</protocol>
+</verify>
diff --git a/tests/data/test253 b/tests/data/test253
new file mode 100644 (file)
index 0000000..7f66ae6
--- /dev/null
@@ -0,0 +1,57 @@
+<info>
+<keywords>
+FTP-ipv6
+EPRT
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data>
+total 20\r
+drwxr-xr-x   8 98       98           512 Oct 22 13:06 .\r
+drwxr-xr-x   8 98       98           512 Oct 22 13:06 ..\r
+drwxr-xr-x   2 98       98           512 May  2  1996 .NeXT\r
+-r--r--r--   1 0        1             35 Jul 16  1996 README\r
+lrwxrwxrwx   1 0        1              7 Dec  9  1999 bin -> usr/bin\r
+dr-xr-xr-x   2 0        1            512 Oct  1  1997 dev\r
+drwxrwxrwx   2 98       98           512 May 29 16:04 download.html\r
+dr-xr-xr-x   2 0        1            512 Nov 30  1995 etc\r
+drwxrwxrwx   2 98       1            512 Oct 30 14:33 pub\r
+dr-xr-xr-x   5 0        1            512 Oct  1  1997 usr\r
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+ipv6
+</features>
+<server>
+ftp-ipv6
+</server>
+ <name>
+FTP IPv6 dir list with EPRT
+ </name>
+ <command>
+-g "ftp://%HOST6IP:%FTP6PORT/" -P -
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strippart>
+s/^(EPRT \|2\|::1\|)(.*)/$1/
+</strippart>
+<protocol>
+USER anonymous\r
+PASS curl_by_daniel@haxx.se\r
+PWD\r
+EPRT |2|::1|
+TYPE A\r
+LIST\r
+QUIT\r
+</protocol>
+</verify>
diff --git a/tests/data/test254 b/tests/data/test254
new file mode 100644 (file)
index 0000000..8466ce2
--- /dev/null
@@ -0,0 +1,58 @@
+<info>
+<keywords>
+FTP-ipv6
+EPSV
+--disable-epsv
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data>
+total 20\r
+drwxr-xr-x   8 98       98           512 Oct 22 13:06 .\r
+drwxr-xr-x   8 98       98           512 Oct 22 13:06 ..\r
+drwxr-xr-x   2 98       98           512 May  2  1996 .NeXT\r
+-r--r--r--   1 0        1             35 Jul 16  1996 README\r
+lrwxrwxrwx   1 0        1              7 Dec  9  1999 bin -> usr/bin\r
+dr-xr-xr-x   2 0        1            512 Oct  1  1997 dev\r
+drwxrwxrwx   2 98       98           512 May 29 16:04 download.html\r
+dr-xr-xr-x   2 0        1            512 Nov 30  1995 etc\r
+drwxrwxrwx   2 98       1            512 Oct 30 14:33 pub\r
+dr-xr-xr-x   5 0        1            512 Oct  1  1997 usr\r
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+ipv6
+</features>
+<server>
+ftp-ipv6
+</server>
+ <name>
+FTP IPv6 dir list PASV and --disable-epsv
+ </name>
+ <command>
+-g "ftp://%HOST6IP:%FTP6PORT/" --disable-epsv
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+filter off really nothing
+</strip>
+<protocol>
+USER anonymous\r
+PASS curl_by_daniel@haxx.se\r
+PWD\r
+EPSV\r
+TYPE A\r
+LIST\r
+QUIT\r
+</protocol>
+</verify>
diff --git a/tests/data/test255 b/tests/data/test255
new file mode 100644 (file)
index 0000000..2128473
--- /dev/null
@@ -0,0 +1,58 @@
+<info>
+<keywords>
+FTP-ipv6
+EPRT
+--disable-eprt
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data>
+total 20\r
+drwxr-xr-x   8 98       98           512 Oct 22 13:06 .\r
+drwxr-xr-x   8 98       98           512 Oct 22 13:06 ..\r
+drwxr-xr-x   2 98       98           512 May  2  1996 .NeXT\r
+-r--r--r--   1 0        1             35 Jul 16  1996 README\r
+lrwxrwxrwx   1 0        1              7 Dec  9  1999 bin -> usr/bin\r
+dr-xr-xr-x   2 0        1            512 Oct  1  1997 dev\r
+drwxrwxrwx   2 98       98           512 May 29 16:04 download.html\r
+dr-xr-xr-x   2 0        1            512 Nov 30  1995 etc\r
+drwxrwxrwx   2 98       1            512 Oct 30 14:33 pub\r
+dr-xr-xr-x   5 0        1            512 Oct  1  1997 usr\r
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+ipv6
+</features>
+<server>
+ftp-ipv6
+</server>
+ <name>
+FTP IPv6 dir list with EPRT and --disable-eprt
+ </name>
+ <command>
+-g "ftp://%HOST6IP:%FTP6PORT/" -P - --disable-eprt
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strippart>
+s/^(EPRT \|2\|::1\|)(.*)/$1/
+</strippart>
+<protocol>
+USER anonymous\r
+PASS curl_by_daniel@haxx.se\r
+PWD\r
+EPRT |2|::1|
+TYPE A\r
+LIST\r
+QUIT\r
+</protocol>
+</verify>
diff --git a/tests/ftp.pm b/tests/ftp.pm
new file mode 100644 (file)
index 0000000..390c214
--- /dev/null
@@ -0,0 +1,17 @@
+# make sure no leftovers are still running
+sub ftpkillslaves {
+    for $ext (("", "ipv6")) {
+        for $id (("", "2")) {
+            for $base (('filt', 'data')) {
+                my $f = ".sock$base$id$ext.pid";
+                my $pid = checkserver($f);
+                if($pid > 0) {
+                    kill (9, $pid); # die!
+                }
+                unlink($f);
+            }
+        }
+    }
+}
+
+1;
index e51bfee..a47a54a 100644 (file)
 # You may optionally specify port on the command line, otherwise it'll
 # default to port 8921.
 #
-
-use Socket;
-use FileHandle;
+# All socket/network/TCP related stuff is done by the 'sockfilt' program.
+#
 
 use strict;
+use IPC::Open2;
 
 require "getpart.pm";
+require "ftp.pm";
 
 my $ftpdnum="";
 
@@ -70,7 +71,10 @@ my $srcdir=".";
 my $nosave=0;
 my $controldelay=0; # set to 1 to delay the control connect data sending to
  # test that curl deals with that nicely
-
+my $slavepid; # for the DATA connection sockfilt slave process
+my $ipv6;
+my $ext; # append to log/pid file names
+my $grok_eprt;
 my $port = 8921; # just a default
 do {
     if($ARGV[0] eq "-v") {
@@ -84,41 +88,68 @@ do {
         $ftpdnum=$ARGV[1];
         shift @ARGV;
     }
-    elsif($ARGV[0] =~ /^(\d+)$/) {
-        $port = $1;
+    elsif($ARGV[0] eq "--ipv6") {
+        $ipv6="--ipv6";
+        $ext="ipv6";
+        $grok_eprt = 1;
+    }
+    elsif($ARGV[0] eq "--port") {
+        $port = $ARGV[1];
+        shift @ARGV;
     }
 } while(shift @ARGV);
 
-my $proto = getprotobyname('tcp') || 6;
+my $sfpid;
 
-socket(Server, PF_INET, SOCK_STREAM, $proto)|| die "socket: $!";
-setsockopt(Server, SOL_SOCKET, SO_REUSEADDR,
-           pack("l", 1)) || die "setsockopt: $!";
-bind(Server, sockaddr_in($port, INADDR_ANY))|| die "bind: $!";
-listen(Server,SOMAXCONN) || die "listen: $!";
+sub startsf {
+    my $cmd="./server/sockfilt --port $port --logfile log/sockctrl$ftpdnum$ext.log --pidfile .sockfilt$ftpdnum$ext.pid $ipv6";
+    $sfpid = open2(\*SFREAD, \*SFWRITE, $cmd);
 
+    print STDERR "$cmd\n" if($verbose);
 
-logmsg "FTP server started on port $port\n";
+    print SFWRITE "PING\n";
+    my $pong = <SFREAD>;
+
+    if($pong !~ /^PONG/) {
+        die "Failed to start sockfilt!";
+    }
+    open(STDIN,  "<&SFREAD")   || die "can't dup client to stdin";
+    open(STDOUT, ">&SFWRITE")   || die "can't dup client to stdout";
+}
+
+startsf();
+
+logmsg sprintf("FTP server started on port IPv%d/$port\n",
+               $ipv6?6:4);
 
 open(PID, ">.ftp$ftpdnum.pid");
 print PID $$;
 close(PID);
 
+sub sockfilt {
+    my $l;
+    foreach $l (@_) {
+        printf "DATA\n%04x\n", length($l);
+        print $l;
+    }
+}
+
+
 # Send data to the client on the control stream, which happens to be plain
 # stdout.
 
 sub sendcontrol {
     if(!$controldelay) {
         # spit it all out at once
-        print @_;
+        sockfilt @_;
     }
     else {
         my $a = join("", @_);
         my @a = split("", $a);
 
         for(@a) {
-            print $_;
-            select(undef, undef, undef, 0.02);
+            sockfilt $_;
+            select(undef, undef, undef, 0.01);
         }
     }
 
@@ -127,16 +158,11 @@ sub sendcontrol {
 # Send data to the client on the data stream
 
 sub senddata {
-    print SOCK @_;
-}
-
-my $waitedpid = 0;
-my $paddr;
-
-sub REAPER {
-    $waitedpid = wait;
-    $SIG{CHLD} = \&REAPER;  # loathe sysV
-    logmsg "reaped $waitedpid" . ($? ? " with exit $?\n" : "\n");
+    my $l;
+    foreach $l (@_) {
+        printf DWRITE "DATA\n%04x\n", length($l);
+        print DWRITE $l;
+    }
 }
 
 # USER is ok in fresh state
@@ -146,6 +172,7 @@ my %commandok = (
                  'PASV' => 'loggedin|twosock',
                  'EPSV' => 'loggedin|twosock',
                  'PORT' => 'loggedin|twosock',
+                 'EPRT' => 'loggedin|twosock',
                  'TYPE' => 'loggedin|twosock',
                  'LIST' => 'twosock',
                  'NLST' => 'twosock',
@@ -171,6 +198,7 @@ my %commandok = (
 my %statechange = ( 'USER' => 'passwd',    # USER goes to passwd state
                     'PASS' => 'loggedin',  # PASS goes to loggedin state
                     'PORT' => 'twosock',   # PORT goes to twosock
+                    'EPRT' => 'twosock',   # EPRT goes to twosock
                     'PASV' => 'twosock',   # PASV goes to twosock
                     'EPSV' => 'twosock',   # EPSV goes to twosock
                     );
@@ -196,6 +224,7 @@ my %displaytext = ('USER' => '331 We are happy you popped in!',
 
 # callback functions for certain commands
 my %commandfunc = ( 'PORT' => \&PORT_command,
+                    'EPRT' => \&PORT_command,
                     'LIST' => \&LIST_command,
                     'NLST' => \&NLST_command,
                     'PASV' => \&PASV_command,
@@ -210,8 +239,25 @@ my %commandfunc = ( 'PORT' => \&PORT_command,
 
 
 sub close_dataconn {
-    close(SOCK);
-    logmsg "Closed data connection\n";
+    my ($closed)=@_; # non-zero if already disconnected
+
+    if(!$closed) {
+        logmsg "time to kill the data connection\n";
+        print DWRITE "DISC\n";
+        my $i;
+        sysread DREAD, $i, 5;
+    }
+    else {
+        logmsg "data connection already disconnected\n";
+    }
+
+    logmsg "time to quit sockfilt for data\n";
+    print DWRITE "QUIT\n";
+    logmsg "told data slave to die (pid $slavepid)\n";
+    waitpid $slavepid, 0;
+    $slavepid=0;
+    logmsg "=====> Closed data connection\n";
+
 }
 
 my $rest=0;
@@ -240,9 +286,8 @@ my @ftpdir=("total 20\r\n",
     for(@ftpdir) {
         senddata $_;
     }
-    close_dataconn();
+    close_dataconn(0);
     logmsg "done passing data\n";
-
     sendcontrol "226 ASCII transfer complete\r\n";
     return 0;
 }
@@ -253,7 +298,7 @@ sub NLST_command {
     for(@ftpdir) {
         senddata "$_\r\n";
     }
-    close_dataconn();
+    close_dataconn(0);
     sendcontrol "226 ASCII transfer complete\r\n";
     return 0;
 }
@@ -292,6 +337,14 @@ sub SIZE_command {
 
     logmsg "SIZE file \"$testno\"\n";
 
+    if($testno eq "verifiedserver") {
+        my $response = "WE ROOLZ: $$\r\n";
+        my $size = length($response);
+        sendcontrol "213 $size\r\n";
+        logmsg "SIZE $testno returned $size\n";
+        return 0;
+    }
+
     my @data = getpart("reply", "size");
 
     my $size = $data[0];
@@ -337,7 +390,8 @@ sub RETR_command {
         sendcontrol "150 Binary junk ($len bytes).\r\n";
         logmsg "pass our pid on the data connection\n";
         senddata "WE ROOLZ: $$\r\n";
-        close_dataconn();
+        close_dataconn(0);
+        logmsg "Data sent, sending a 226-reponse now\n";
         sendcontrol "226 File transfer complete\r\n";
         if($verbose) {
             print STDERR "FTPD: We returned proof we are the test server\n";
@@ -377,7 +431,7 @@ sub RETR_command {
                 my $send = $_;
                 senddata $send;
             }
-            close_dataconn();
+            close_dataconn(0);
             $retrweirdo=0; # switch off the weirdo again!
         }
         else {
@@ -394,7 +448,7 @@ sub RETR_command {
                 my $send = $_;
                 senddata $send;
             }
-            close_dataconn();
+            close_dataconn(0);
             sendcontrol "226 File transfer complete\r\n";
         }
     }
@@ -421,121 +475,184 @@ sub STOR_command {
 
     my $line;
     my $ulsize=0;
-    while (defined($line = <SOCK>)) {
-        $ulsize += length($line);
-        print FILE $line if(!$nosave);
+    my $disc=0;
+    while (5 == (sysread DREAD, $line, 5)) {
+        logmsg "command from sockfilt: $line";
+        if($line eq "DATA\n") {
+            my $i;
+            sysread DREAD, $i, 5;
+
+            #print STDERR "  GOT: $i";
+
+            my $size = hex($i);
+            sysread DREAD, $line, $size;
+            
+            #print STDERR "  GOT: $size bytes\n";
+
+            $ulsize += $size;
+            print FILE $line if(!$nosave);
+            logmsg "> Appending $size bytes to file\n";
+        }
+        elsif($line eq "DISC\n") {
+            # disconnect!
+            logmsg "DISC means disconnect!\n";
+            $disc=1;
+            last;
+        }
+        else {
+            logmsg "No support for: $line";
+            last;
+        }
     }
     if($nosave) {
         print FILE "$ulsize bytes would've been stored here\n";
     }
     close(FILE);
-    close_dataconn();
-
+    close_dataconn($disc);
     logmsg "received $ulsize bytes upload\n";
-
     sendcontrol "226 File transfer complete\r\n";
     return 0;
 }
 
-my $pasvport=9000;
 sub PASV_command {
     my ($arg, $cmd)=@_;
+    my $pasvport;
 
-    socket(Server2, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
-    setsockopt(Server2, SOL_SOCKET, SO_REUSEADDR,
-               pack("l", 1)) || die "setsockopt: $!";
+    # We fire up a new sockfilt to do the data tranfer for us.
+    $slavepid = open2(\*DREAD, \*DWRITE,
+                      "./server/sockfilt --port 0 --logfile log/sockdata$ftpdnum$ext.log --pidfile .sockdata$ftpdnum$ext.pid $ipv6");
 
-    my $ok=0;
+    print DWRITE "PING\n";
+    my $pong = <DREAD>;
 
-    $pasvport++; # don't reuse the previous
-    for(1 .. 10) {
-        if($pasvport > 65535) {
-            $pasvport = 1025;
-        }
-        if(bind(Server2, sockaddr_in($pasvport, INADDR_ANY))) {
-            $ok=1;
-            last;
-        }
-        $pasvport+= 3; # try another port please
-    }
-    if(!$ok) {
+    if($pong !~ /^PONG/) {
         sendcontrol "500 no free ports!\r\n";
         logmsg "couldn't find free port\n";
         return 0;
     }
-    listen(Server2,SOMAXCONN) || die "listen: $!";
+
+    logmsg "sockfilt for data on pid $slavepid\n";
+
+    # Find out what port we listen on
+    my $i;
+    print DWRITE "PORT\n";
+        
+    # READ the response code
+    sysread(DREAD, $i, 5) || die;
+
+    # READ the response size
+    sysread(DREAD, $i, 5) || die;
+
+    my $size = hex($i);
+        
+    # READ the response data
+    sysread(DREAD, $i, $size) || die;
+        
+    # The data is in the format
+    # IPvX/NNN
+
+    if($i =~ /IPv(\d)\/(\d+)/) {
+        # FIX: deal with IP protocol version
+        $pasvport = $2;
+    }
 
     if($cmd ne "EPSV") {
         # PASV reply
-        logmsg "replying to a $cmd command\n";
-        printf("227 Entering Passive Mode (127,0,0,1,%d,%d)\n",
-               ($pasvport/256), ($pasvport%256));
+        logmsg "replying to a $cmd command, waiting on port $pasvport\n";
+        sendcontrol sprintf("227 Entering Passive Mode (127,0,0,1,%d,%d)\n",
+                            ($pasvport/256), ($pasvport%256));
     }
     else {
         # EPSV reply
-        logmsg "replying to a $cmd command\n";
-        printf("229 Entering Passive Mode (|||%d|)\n", $pasvport);
+        logmsg "replying to a $cmd command, waiting on port $pasvport\n";
+        sendcontrol sprintf("229 Entering Passive Mode (|||%d|)\n", $pasvport);
     }
 
 
-    my $paddr;
     eval {
         local $SIG{ALRM} = sub { die "alarm\n" };
-        alarm 2; # assume swift operations!
-        $paddr = accept(SOCK, Server2);
+
+        alarm 2; # assume swift operations
+
+        # Wait for 'CNCT'
+        my $input = <DREAD>;
+
+        if($input !~ /^CNCT/) {
+            # we wait for a connected client
+            next;
+        }
+        logmsg "====> Client DATA connect\n";
+
         alarm 0;
     };
     if ($@) {
         # timed out
-        
-        close(Server2);
+
+        print DWRITE "QUIT\n";
+        waitpid $slavepid, 0;
         logmsg "accept failed\n";
+        $slavepid=0;
         return;
     }
     else {
-        logmsg "accept worked\n";
-
-        my($iport,$iaddr) = sockaddr_in($paddr);
-        my $name = gethostbyaddr($iaddr,AF_INET);
-
-        close(Server2); # close the listener when its served its purpose!
-
-        logmsg "data connection from $name [", inet_ntoa($iaddr),
-        "] at port $iport\n";
+        logmsg "data connection setup on port $pasvport\n";
     }
 
     return;
 }
 
+# Support both PORT and EPRT here. Consider LPRT too.
 
 sub PORT_command {
-    my $arg = $_[0];
+    my ($arg, $cmd) = @_;
+    my $port;
+
+    # We always ignore the given IP and use localhost.
 
-    if($arg !~ /(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)/) {
-        logmsg "bad PORT-line: $arg\n";
-        sendcontrol "500 silly you, go away\r\n";
+    if($cmd eq "PORT") {
+        if($arg !~ /(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)/) {
+            logmsg "bad PORT-line: $arg\n";
+            sendcontrol "500 silly you, go away\r\n";
+            return 0;
+        }
+        $port = ($5<<8)+$6;
+    }
+    # EPRT |2|::1|49706|
+    elsif(($cmd eq "EPRT") && ($grok_eprt)) {
+        if($arg !~ /(\d+)\|([^\|]+)\|(\d+)/) {
+            logmsg "bad EPRT-line: $arg\n";
+            sendcontrol "500 silly you, go away\r\n";
+            return 0;
+        }
+        sendcontrol "200 Thanks for dropping by. We contact you later\r\n";
+        $port = $3;
+    }
+    else {
+        logmsg "got a $cmd line we don't like\n";
+        sendcontrol "500 we don't like $cmd now\r\n";
         return 0;
     }
-    #my $iaddr = inet_aton("$1.$2.$3.$4");
-    my $iaddr = inet_aton("127.0.0.1"); # always use localhost
-
-    my $port = ($5<<8)+$6;
 
     if(!$port || $port > 65535) {
         print STDERR "very illegal PORT number: $port\n";
         return 1;
     }
 
-    my $paddr = sockaddr_in($port, $iaddr);
-    my $proto   = getprotobyname('tcp') || 6;
+    # We fire up a new sockfilt to do the data tranfer for us.
+    # FIX: make it use IPv6 if need be
+    $slavepid = open2(\*DREAD, \*DWRITE,
+                      "./server/sockfilt --connect $port --logfile log/sockdata$ftpdnum$ext.log --pidfile .sockdata$ftpdnum$ext.pid $ipv6");
 
-    socket(SOCK, PF_INET, SOCK_STREAM, $proto) || die "major failure";
-    connect(SOCK, $paddr)    || return 1;
+    print DWRITE "PING\n";
+    my $pong = <DREAD>;
 
-    return \&SOCK;
-}
+    if($pong !~ /^PONG/) {
+        logmsg "sockfilt failed!\n";
+    }
+    logmsg "====> Client DATA connect to port $port\n";
 
-$SIG{CHLD} = \&REAPER;
+    return;
+}
 
 my %customreply;
 my %customcount;
@@ -595,22 +712,45 @@ my @welcome=(
             '220-  | (__| |_| |  _ <| |___ '."\r\n",
             '220    \___|\___/|_| \_\_____|'."\r\n");
 
-for ( $waitedpid = 0;
-      ($paddr = accept(Client,Server)) || $waitedpid;
-        $waitedpid = 0, close Client)
-{
-    next if $waitedpid and not $paddr;
-    my($port,$iaddr) = sockaddr_in($paddr);
-    my $name = gethostbyaddr($iaddr,AF_INET);
+
+while(1) {
+    #
+    # We read 'sockfilt' commands.
+    # 
+    my $input;
+    eval {
+        local $SIG{ALRM} = sub { die "alarm\n" };
+        alarm 5; # just in case things go bad
+        $input = <STDIN>;
+        alarm 0;
+    };
+    if ($@) {
+        # timed out
+        logmsg "reading stdin timed out\n";
+    }
+
+    if($input !~ /^CNCT/) {
+        # we wait for a connected client
+        if(!length($input)) {
+            # it probably died, restart it
+            kill(9, $sfpid);
+            waitpid $sfpid, 0;
+            startsf();
+            logmsg "restarted sockfilt\n";
+        }
+        else {
+            logmsg "sockfilt said: $input";
+        }
+        next;
+    }
+    logmsg "====> Client connect\n";
 
     # flush data:
     $| = 1;
+
+    kill(9, $slavepid) if($slavepid);
+    $slavepid=0;
         
-    logmsg "connection from $name [", inet_ntoa($iaddr), "] at port $port\n";
-    
-    open(STDIN,  "<&Client")   || die "can't dup client to stdin";
-    open(STDOUT, ">&Client")   || die "can't dup client to stdout";
-    
     &customize(); # read test control instructions
 
     sendcontrol @welcome;
@@ -622,8 +762,29 @@ for ( $waitedpid = 0;
     my $state="fresh";
 
     while(1) {
+        my $i;
+
+        # Now we expect to read DATA\n[hex size]\n[prot], where the [prot]
+        # part only is FTP lingo.
+
+        # COMMAND
+        sysread(STDIN, $i, 5) || die;
 
-        last unless defined ($_ = <STDIN>);
+        if($i !~ /^DATA/) {
+            logmsg "sockfilt said $i";
+            if($i =~ /^DISC/) {
+                # disconnect
+                last;
+            }
+            next;
+        }
+
+        # SIZE of data
+        sysread(STDIN, $i, 5) || die;
+        my $size = hex($i);
+
+        # data
+        sysread STDIN, $_, $size;
         
         ftpmsg $_;
         
@@ -632,7 +793,7 @@ for ( $waitedpid = 0;
 
         unless (m/^([A-Z]{3,4})\s?(.*)/i) {
             sendcontrol "500 '$_': command not understood.\r\n";
-            logmsg "unknown crap received, bailing out hard\n";
+            logmsg "unknown crap received: $_, bailing out hard\n";
             last;
         }
         my $FTPCMD=$1;
@@ -692,11 +853,14 @@ for ( $waitedpid = 0;
             my $func = $commandfunc{$FTPCMD};
             if($func) {
                 # it is!
-                \&$func($FTPARG, $FTPCMD);
+                &$func($FTPARG, $FTPCMD);
             }
         }
             
     } # while(1)
-    logmsg "client disconnected\n";
-    close(Client);
+    logmsg "====> Client disconnected\n";
 }
+
+print SFWRITE "QUIT\n";
+waitpid $sfpid, 0;
+exit;
index 2210134..04ef999 100755 (executable)
@@ -30,6 +30,7 @@ use strict;
 
 require "getpart.pm"; # array functions
 require "valgrind.pm"; # valgrind report parser
+require "ftp.pm";
 
 my $srcdir = $ENV{'srcdir'} || '.';
 my $HOSTIP="127.0.0.1";
@@ -43,6 +44,7 @@ my $HTTPSPORT; # HTTPS server port
 my $FTPPORT; # FTP server port
 my $FTP2PORT; # FTP server 2 port
 my $FTPSPORT; # FTPS server port
+my $FTP6PORT; # FTP IPv6 server port
 
 my $CURL="../src/curl"; # what curl executable to run on the tests
 my $DBGCURL=$CURL; #"../src/.libs/curl";  # alternative for debugging
@@ -69,6 +71,7 @@ my $HTTPPIDFILE=".http.pid";
 my $HTTP6PIDFILE=".http6.pid";
 my $HTTPSPIDFILE=".https.pid";
 my $FTPPIDFILE=".ftp.pid";
+my $FTP6PIDFILE=".ftp6.pid";
 my $FTP2PIDFILE=".ftp2.pid";
 my $FTPSPIDFILE=".ftps.pid";
 
@@ -112,6 +115,7 @@ my $ssl_version; # set if libcurl is built with SSL support
 my $large_file;  # set if libcurl is built with large file support
 my $has_idn;     # set if libcurl is built with IDN support
 my $http_ipv6;   # set if HTTP server has IPv6 support
+my $ftp_ipv6;    # set if FTP server has IPv6 support
 my $has_ipv6;    # set if libcurl is built with IPv6 support
 my $has_libz;    # set if libcurl is built with libz support
 my $has_getrlimit;  # set if system has getrlimit()
@@ -365,7 +369,7 @@ sub runhttpserver {
     $pid = checkserver($pidfile);
 
     # verify if our/any server is running on this port
-    my $cmd = "$CURL -o log/verifiedserver -g \"http://$ip:$port/verifiedserver\" 2>log/verifystderr";
+    my $cmd = "$CURL -o log/verifiedserver -g \"http://$ip:$port/verifiedserver\" 2>log/verifyhttp";
     print "CMD; $cmd\n" if ($verbose);
     my $res = system($cmd);
 
@@ -425,7 +429,7 @@ sub runhttpserver {
     my $verified;
     for(1 .. 30) {
         # verify that our server is up and running:
-        my $data=`$CURL --silent -g \"$ip:$port/verifiedserver\" 2>/dev/null`;
+        my $data=`$CURL --silent -g \"$ip:$port/verifiedserver\" 2>>log/verifyhttp`;
 
         if ( $data =~ /WE ROOLZ: (\d+)/ ) {
             $pid = 0+$1;
@@ -503,24 +507,39 @@ sub runhttpsserver {
     return $pid;
 }
 
+
 #######################################################################
 # start the ftp server if needed
 #
 sub runftpserver {
-    my ($id, $verbose) = @_;
+    my ($id, $verbose, $ipv6) = @_;
     my $STATUS;
     my $RUNNING;
     my $port = $id?$FTP2PORT:$FTPPORT;
     # check for pidfile
-    my $pid = checkserver ($id?$FTP2PIDFILE:$FTPPIDFILE);
+    my $pidfile = $id?$FTP2PIDFILE:$FTPPIDFILE;
+    my $ip=$HOSTIP;
+    my $nameext;
+
+    ftpkillslaves();
+
+    if($ipv6) {
+        # if IPv6, use a different setup
+        $pidfile = $FTP6PIDFILE;
+        $port = $FTP6PORT;
+        $ip = $HOST6IP;
+        $nameext="-ipv6";
+    }
+
+    my $pid = checkserver ();
 
     if ($pid <= 0) {
-        print "RUN: Check port $port for the FTP$id server\n"
+        print "RUN: Check port $port for the FTP$id$nameext server\n"
             if ($verbose);
 
         my $time=time();
         # check if this is our server running on this port:
-        my @data=`$CURL -m4 --silent ftp://$HOSTIP:$port/verifiedserver 2>/dev/null`;
+        my @data=`$CURL -m4 --silent -vg \"ftp://$ip:$port/verifiedserver\" 2>log/verifyftp`;
         my $line;
 
         # if this took more than 2 secs, we assume it "hung" on a weird server
@@ -534,7 +553,7 @@ sub runftpserver {
         }
         if(!$pid && $data[0]) {
             # this is not a known server
-            print "RUN: Unknown server on our favourite FTP port: $port\n";
+            print "RUN: Unknown server on our favourite FTP$nameext port: $port\n";
             return -1;
         }
     }
@@ -543,7 +562,7 @@ sub runftpserver {
         print "RUN: Killing a previous server using pid $pid\n" if($verbose);
         my $res = kill (9, $pid); # die!
         if(!$res) {
-            print "RUN: Failed to kill FTP$id test server, do it manually and",
+            print "RUN: Failed to kill FTP$id$nameext test server, do it manually and",
             " restart the tests.\n";
             return -1;
         }
@@ -556,7 +575,10 @@ sub runftpserver {
     if($id) {
         $flag .="--id $id ";
     }
-    my $cmd="$perl $srcdir/ftpserver.pl $flag $port &";
+    if($ipv6) {
+        $flag .="--ipv6 ";
+    }
+    my $cmd="$perl $srcdir/ftpserver.pl $flag --port $port &";
     if($verbose) {
         print "CMD: $cmd\n";
     }
@@ -567,7 +589,7 @@ sub runftpserver {
     for(1 .. 30) {
         # verify that our server is up and running:
         my $line;
-        my $cmd="$CURL --silent ftp://$HOSTIP:$port/verifiedserver 2>/dev/null";
+        my $cmd="$CURL --silent -g \"ftp://$ip:$port/verifiedserver\" 2>>log/verifyftp";
         print "$cmd\n" if($verbose);
         my @data = `$cmd`;
         foreach $line (@data) {
@@ -579,7 +601,7 @@ sub runftpserver {
         }
         if(!$pid) {
             if($verbose) {
-                print STDERR "RUN: Retrying FTP$id server existence in a sec\n";
+                print STDERR "RUN: Retrying FTP$id$nameext server existence in a sec\n";
             }
             sleep(1);
             next;
@@ -594,7 +616,7 @@ sub runftpserver {
     }
 
     if($verbose) {
-        print "RUN: FTP$id server is now verified to be our server\n";
+        print "RUN: FTP$id$nameext server is now verified to be our server\n";
     }
 
     return $pid;
@@ -850,12 +872,21 @@ sub checkcurl {
     }
 
     if($has_ipv6) {
-        # client has ipv6 support, check that the HTTP server has it!
+        # client has ipv6 support
+
+        # check if the HTTP server has it!
         my @sws = `server/sws --version`;
         if($sws[0] =~ /IPv6/) {
             # HTTP server has ipv6 support!
             $http_ipv6 = 1;
         }
+
+        # check if the FTP server has it!
+        my @sws = `server/sockfilt --version`;
+        if($sws[0] =~ /IPv6/) {
+            # FTP server has ipv6 support!
+            $ftp_ipv6 = 1;
+        }
     }
 
     if(!$curl_debug && $torture) {
@@ -877,6 +908,7 @@ sub checkcurl {
     printf("* libcurl debug:  %s\n", $curl_debug?"ON":"OFF");
     printf("* valgrind:       %s\n", $valgrind?"ON":"OFF");
     printf("* HTTP IPv6       %s\n", $http_ipv6?"ON":"OFF");
+    printf("* FTP IPv6        %s\n", $ftp_ipv6?"ON":"OFF");
 
     printf("* HTTP port:      %d\n", $HTTPPORT);
     printf("* FTP port:       %d\n", $FTPPORT);
@@ -888,6 +920,9 @@ sub checkcurl {
     if($http_ipv6) {
         printf("* HTTP IPv6 port: %d\n", $HTTP6PORT);
     }
+    if($ftp_ipv6) {
+        printf("* FTP IPv6 port:  %d\n", $FTP6PORT);
+    }
     print "***************************************** \n";
 }
 
@@ -903,6 +938,7 @@ sub subVariables {
   $$thing =~ s/%HTTP6PORT/$HTTP6PORT/g;
   $$thing =~ s/%HTTPSPORT/$HTTPSPORT/g;
   $$thing =~ s/%FTPPORT/$FTPPORT/g;
+  $$thing =~ s/%FTP6PORT/$FTP6PORT/g;
   $$thing =~ s/%FTP2PORT/$FTP2PORT/g;
   $$thing =~ s/%FTPSPORT/$FTPSPORT/g;
   $$thing =~ s/%SRCDIR/$srcdir/g;
@@ -1533,6 +1569,7 @@ sub stopservers {
         printf ("* kill pid for %-5s => %-5d\n", $_, $run{$_}) if($verbose);
         stopserver($run{$_}); # the pid file is in the hash table
     }
+    ftpkillslaves();
 }
 
 #######################################################################
@@ -1566,6 +1603,16 @@ sub startservers {
                 $run{'ftp2'}=$pid;
             }
         }
+        elsif($what eq "ftp-ipv6") {
+            if(!$run{'ftp-ipv6'}) {
+                $pid = runftpserver("", $verbose, "ipv6");
+                if($pid <= 0) {
+                    return "failed starting FTP-ipv6 server";
+                }
+                printf ("* pid ftp-ipv6 => %-5d\n", $pid) if($verbose);
+                $run{'ftp-ipv6'}=$pid;
+            }
+        }
         elsif($what eq "http") {
             if(!$run{'http'}) {
                 $pid = runhttpserver($verbose);
@@ -1803,6 +1850,7 @@ $FTPSPORT =  $base + 3; # FTPS server port
 $HTTP6PORT = $base + 4; # HTTP IPv6 server port (different IP protocol
                         # but we follow the same port scheme anyway)
 $FTP2PORT =  $base + 5; # FTP server 2 port
+$FTP6PORT =  $base + 6; # FTP IPv6 port
 
 #######################################################################
 # Output curl version and host info being tested
index 565441c..0772710 100644 (file)
@@ -25,16 +25,14 @@ AUTOMAKE_OPTIONS = foreign
 
 INCLUDES = -I$(top_srcdir)/lib -I$(top_srcdir)/include
 
-noinst_PROGRAMS = sws getpart
+noinst_PROGRAMS = sws getpart sockfilt
 
-sws_SOURCES= sws.c getpart.c getpart.h $(top_srcdir)/lib/strequal.c    \
- $(top_srcdir)/lib/base64.c $(top_srcdir)/lib/mprintf.c                        \
+useful = getpart.c getpart.h $(top_srcdir)/lib/strequal.c      \
+ $(top_srcdir)/lib/base64.c $(top_srcdir)/lib/mprintf.c                \
  $(top_srcdir)/lib/memdebug.c
 
-extra_DIST = base64.pl
-
-getpart_CPPFLAGS = -DGETPART_TEST
+sws_SOURCES= sws.c $(useful)
+sockfilt_SOURCES = sockfilt.c $(useful) $(top_srcdir)/lib/inet_pton.c
+getpart_SOURCES= testpart.c $(useful)
 
-getpart_SOURCES= getpart.c getpart.h $(top_srcdir)/lib/strequal.c      \
- $(top_srcdir)/lib/base64.c $(top_srcdir)/lib/mprintf.c                        \
- $(top_srcdir)/lib/memdebug.c
+extra_DIST = base64.pl
index c493f25..85bf3ed 100644 (file)
@@ -218,19 +218,3 @@ const char *spitout(FILE *stream,
   return string;
 }
 
-#ifdef GETPART_TEST
-int main(int argc, char **argv)
-{
-  if(argc< 3) {
-    printf("./moo main sub\n");
-  }
-  else {
-    size_t size;
-    unsigned int i;
-    const char *buffer = spitout(stdin, argv[1], argv[2], &size);
-    for(i=0; i< size; i++)
-      printf("%c", buffer[i]);
-  }
-  return 0;
-}
-#endif
diff --git a/tests/server/sockfilt.c b/tests/server/sockfilt.c
new file mode 100644 (file)
index 0000000..07c81b1
--- /dev/null
@@ -0,0 +1,668 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * $Id$
+ ***************************************************************************/
+
+/* Purpose
+ *
+ * 1. Accept a TCP connection on a custom port (ipv4 or ipv6).
+ *
+ * 2. Get commands on STDIN. Pass data on to the TCP stream.
+ *    Get data from TCP stream and pass on to STDOUT.
+ *
+ * This program is made to perform all the socket/stream/connection stuff for
+ * the test suite's (perl) FTP server. Previously the perl code did all of
+ * this by its own, but I decided to let this program do the socket layer
+ * because of several things:
+ *
+ * o We want the perl code to work with rather old perl installations, thus
+ *   we cannot use recent perl modules or features.
+ *
+ * o We want IPv6 support for systems that provide it, and doing optional IPv6
+ *   support in perl seems if not impossible so at least awkward.
+ *
+ * o We want FTP-SSL support, which means that a connection that starts with
+ *   plain sockets needs to be able to "go SSL" in the midst. This would also
+ *   require some nasty perl stuff I'd rather avoid.
+ *
+ * (Source originally based on sws.c)
+ */
+#include "setup.h" /* portability help from the lib directory */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <time.h>
+#include <ctype.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef _XOPEN_SOURCE_EXTENDED
+/* This define is "almost" required to build on HPUX 11 */
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+
+#include "curlx.h" /* from the private lib dir */
+#include "getpart.h"
+#include "inet_pton.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#if defined(WIN32) && !defined(__CYGWIN__)
+#include <windows.h>
+#include <winsock2.h>
+#include <process.h>
+
+#define sleep(sec)   Sleep ((sec)*1000)
+
+#define EINPROGRESS  WSAEINPROGRESS
+#define EWOULDBLOCK  WSAEWOULDBLOCK
+#define EISCONN      WSAEISCONN
+#define ENOTSOCK     WSAENOTSOCK
+#define ECONNREFUSED WSAECONNREFUSED
+
+static void win32_cleanup(void);
+
+#if defined(ENABLE_IPV6) && defined(__MINGW32__)
+const struct in6_addr in6addr_any = {{ IN6ADDR_ANY_INIT }};
+#endif
+#endif
+
+/* include memdebug.h last */
+#include "memdebug.h"
+
+#define DEFAULT_PORT 8999
+
+#ifndef DEFAULT_LOGFILE
+#define DEFAULT_LOGFILE "log/sockfilt.log"
+#endif
+
+#ifdef SIGPIPE
+static volatile int sigpipe;  /* Why? It's not used */
+#endif
+
+char *socklogfile = (char *)DEFAULT_LOGFILE;
+
+/*
+ * ourerrno() returns the errno (or equivalent) on this platform to
+ * hide platform specific for the function that calls this.
+ */
+static int ourerrno(void)
+{
+#ifdef WIN32
+  return (int)GetLastError();
+#else
+  return errno;
+#endif
+}
+
+static void logmsg(const char *msg, ...)
+{
+  time_t t = time(NULL);
+  va_list ap;
+  struct tm *curr_time = localtime(&t);
+  char buffer[256]; /* possible overflow if you pass in a huge string */
+  FILE *logfp;
+
+  va_start(ap, msg);
+  vsprintf(buffer, msg, ap);
+  va_end(ap);
+
+  logfp = fopen(socklogfile, "a");
+
+  fprintf(logfp?logfp:stderr, /* write to stderr if the logfile doesn't open */
+          "%02d:%02d:%02d %s\n",
+          curr_time->tm_hour,
+          curr_time->tm_min,
+          curr_time->tm_sec, buffer);
+  if(logfp)
+    fclose(logfp);
+}
+
+static void lograw(unsigned char *buffer, int len)
+{
+  char data[120];
+  int i;
+  unsigned char *ptr = buffer;
+  char *optr = data;
+  int width=0;
+
+  for(i=0; i<len; i++) {
+    sprintf(optr, "%c",
+            (isgraph(ptr[i]) || ptr[i]==0x20) ?ptr[i]:'.');
+    optr += 1;
+    width += 1;
+
+    if(width>60) {
+      logmsg("RAW: '%s'", data);
+      width = 0;
+      optr = data;
+    }
+  }
+  if(width)
+    logmsg("RAW: '%s'", data);
+}
+
+#ifdef SIGPIPE
+static void sigpipe_handler(int sig)
+{
+  (void)sig; /* prevent warning */
+  sigpipe = 1;
+}
+#endif
+
+#if defined(WIN32) && !defined(__CYGWIN__)
+#undef perror
+#define perror(m) win32_perror(m)
+
+static void win32_perror(const char *msg)
+{
+  char buf[256];
+  DWORD err = WSAGetLastError();
+
+  if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err,
+                     LANG_NEUTRAL, buf, sizeof(buf), NULL))
+     snprintf(buf, sizeof(buf), "Unknown error %lu (%#lx)", err, err);
+  if (msg)
+     fprintf(stderr, "%s: ", msg);
+  fprintf(stderr, "%s\n", buf);
+}
+#endif
+
+#if defined(WIN32) && !defined(__CYGWIN__)
+static void win32_init(void)
+{
+  WORD wVersionRequested;
+  WSADATA wsaData;
+  int err;
+  wVersionRequested = MAKEWORD(2, 0);
+
+  err = WSAStartup(wVersionRequested, &wsaData);
+
+  if (err != 0) {
+    perror("Winsock init failed");
+    logmsg("Error initialising winsock -- aborting\n");
+    exit(1);
+  }
+
+  if ( LOBYTE( wsaData.wVersion ) != 2 ||
+       HIBYTE( wsaData.wVersion ) != 0 ) {
+
+    WSACleanup();
+    perror("Winsock init failed");
+    logmsg("No suitable winsock.dll found -- aborting\n");
+    exit(1);
+  }
+}
+static void win32_cleanup(void)
+{
+  WSACleanup();
+}
+#endif
+
+char use_ipv6=FALSE;
+unsigned short port = DEFAULT_PORT;
+unsigned short connectport = 0; /* if non-zero, we activate this mode */
+
+enum sockmode {
+  PASSIVE_LISTEN, /* as a server waiting for connections */
+  PASSIVE_CONNECT, /* as a server, connected to a client */
+  ACTIVE    /* as a client, connected to a server */
+};
+
+/*
+  sockfdp is a pointer to an established stream or CURL_SOCKET_BAD
+
+  if sockfd is CURL_SOCKET_BAD, listendfd is a listening socket we must
+  accept()
+*/
+static int juggle(curl_socket_t *sockfdp,
+                  curl_socket_t listenfd,
+                  enum sockmode *mode)
+{
+  struct timeval timeout;
+  fd_set fds_read;
+  fd_set fds_write;
+  fd_set fds_err;
+  curl_socket_t maxfd;
+  int r;
+  unsigned char buffer[256]; /* FIX: bigger buffer */
+  char data[256];
+  int sockfd;
+
+  timeout.tv_sec = 120;
+  timeout.tv_usec = 0;
+
+  FD_ZERO(&fds_read);
+  FD_ZERO(&fds_write);
+  FD_ZERO(&fds_err);
+
+  FD_SET(fileno(stdin), &fds_read);
+
+  switch(*mode) {
+  case PASSIVE_LISTEN:
+    /* server mode */
+    sockfd = listenfd;
+    logmsg("waiting for a client to connect on socket %d", (int)sockfd);
+    /* there's always a socket to wait for */
+    FD_SET(sockfd, &fds_read);
+    maxfd = sockfd;
+    break;
+
+  case PASSIVE_CONNECT:
+    sockfd = *sockfdp;
+    logmsg("waiting for data from client on socket %d", (int)sockfd);
+    /* there's always a socket to wait for */
+    FD_SET(sockfd, &fds_read);
+    maxfd = sockfd;
+    break;
+
+  case ACTIVE:
+    sockfd = *sockfdp;
+
+    /* sockfd turns CURL_SOCKET_BAD when our connection has been closed */
+    if(sockfd != CURL_SOCKET_BAD) {
+      FD_SET(sockfd, &fds_read);
+      maxfd = sockfd;
+      logmsg("waiting for data from client on socket %d", (int)sockfd);
+    }
+    else {
+      logmsg("No socket to read on");
+      maxfd = 0;
+    }
+    break;
+  }
+
+  do {
+    r = select(maxfd + 1, &fds_read, &fds_write, &fds_err, &timeout);
+  } while((r == -1) && (ourerrno() == EINTR));
+
+  logmsg("select() returned %d", r);
+
+  switch(r) {
+  case -1:
+    return FALSE;
+
+  case 0: /* timeout! */
+    return TRUE;
+  }
+
+
+  if(FD_ISSET(fileno(stdin), &fds_read)) {
+    size_t nread;
+    logmsg("data on stdin");
+    /* read from stdin, commands/data to be dealt with and possibly passed on
+       to the socket
+
+       protocol:
+
+       4 letter command + LF [mandatory]
+
+       4-digit hexadecimal data length + LF [if the command takes data]
+       data                       [the data being as long as set above]
+
+       Commands:
+
+       DATA - plain pass-thru data
+    */
+    nread = read(fileno(stdin), buffer, 5);
+    if(5 == nread) {
+
+      logmsg("Received command %c%c%c%c",
+             buffer[0], buffer[1], buffer[2], buffer[3] );
+
+      if(!memcmp("PING", buffer, 4)) {
+        /* send reply on stdout, just proving we are alive */
+        write(fileno(stdout), "PONG\n", 5);
+      }
+
+      else if(!memcmp("PORT", buffer, 4)) {
+        /* question asking us what PORT number we are listening to.
+           Replies with PORT with "IPv[num]/[port]" */
+        sprintf((char *)buffer, "IPv%d/%d\n", use_ipv6?6:4, port);
+        r = strlen((char *)buffer);
+        sprintf(data, "PORT\n%04x\n", r);
+        write(fileno(stdout), data, 10);
+        write(fileno(stdout), buffer, r);
+      }
+      else if(!memcmp("QUIT", buffer, 4)) {
+        /* just die */
+        logmsg("quits");
+        exit(0);
+      }
+      else if(!memcmp("DATA", buffer, 4)) {
+        /* data IN => data OUT */
+        long len;
+
+        if(5 != read(fileno(stdin), buffer, 5))
+          return FALSE;
+
+        len = strtol((char *)buffer, NULL, 16);
+        if(len != read(fileno(stdin), buffer, len))
+          return FALSE;
+
+        logmsg("> %d bytes data, server => client", len);
+        lograw(buffer, len);
+
+        if(*mode == PASSIVE_LISTEN) {
+          logmsg(".., but we are disconnected!");
+          write(fileno(stdout), "DISC\n", 5);
+        }
+        else
+          /* send away on the socket */
+          swrite(sockfd, buffer, len);
+      }
+      else if(!memcmp("DISC", buffer, 4)) {
+        /* disconnect! */
+        write(fileno(stdout), "DISC\n", 5);
+        if(sockfd != CURL_SOCKET_BAD) {
+          logmsg("====> Client forcibly disconnected");
+          sclose(sockfd);
+          *sockfdp = CURL_SOCKET_BAD;
+        }
+        else
+          logmsg("attempt to close already dead connection");
+        return TRUE;
+      }
+    }
+    else {
+      logmsg("read %d from stdin, exiting", (int)nread);
+      exit(0);
+    }
+  }
+
+  if((sockfd != CURL_SOCKET_BAD) && (FD_ISSET(sockfd, &fds_read)) ) {
+    logmsg("data on socket");
+
+    if(*mode == PASSIVE_LISTEN) {
+      /* there's no stream set up yet, this is an indication that there's a
+         client connecting. */
+      sockfd = accept(sockfd, NULL, NULL);
+      if(-1 == sockfd)
+        logmsg("accept() failed\n");
+      else {
+        logmsg("====> Client connect");
+        write(fileno(stdout), "CNCT\n", 5);
+        *sockfdp = sockfd; /* store the new socket */
+        *mode = PASSIVE_CONNECT; /* we have connected */
+      }
+      return TRUE;
+    }
+
+    /* read from socket, pass on data to stdout */
+    r = sread(sockfd, buffer, sizeof(buffer));
+
+    if(r <= 0) {
+      logmsg("====> Client disconnect");
+      write(fileno(stdout), "DISC\n", 5);
+      sclose(sockfd);
+      *sockfdp = CURL_SOCKET_BAD;
+      if(*mode == PASSIVE_CONNECT)
+        *mode = PASSIVE_LISTEN;
+      return TRUE;
+    }
+
+    sprintf(data, "DATA\n%04x\n", r);
+    write(fileno(stdout), data, 10);
+    write(fileno(stdout), buffer, r);
+
+    logmsg("< %d bytes data, client => server", r);
+    lograw(buffer, r);
+  }
+
+  return TRUE;
+}
+
+int main(int argc, char *argv[])
+{
+  struct sockaddr_in me;
+#ifdef ENABLE_IPV6
+  struct sockaddr_in6 me6;
+#endif /* ENABLE_IPV6 */
+  int sock;
+  int msgsock = CURL_SOCKET_BAD; /* no stream socket yet */
+  int flag;
+  FILE *pidfile;
+  char *pidname= (char *)".sockfilt.pid";
+  int rc;
+  int arg=1;
+  bool ok = FALSE;
+  enum sockmode mode = PASSIVE_LISTEN; /* default */
+
+  while(argc>arg) {
+    if(!strcmp("--version", argv[arg])) {
+      printf("sockfilt IPv4%s\n",
+#ifdef ENABLE_IPV6
+             "/IPv6"
+#else
+             ""
+#endif
+             );
+      return 0;
+    }
+    else if(!strcmp("--pidfile", argv[arg])) {
+      arg++;
+      if(argc>arg)
+        pidname = argv[arg++];
+    }
+    else if(!strcmp("--logfile", argv[arg])) {
+      arg++;
+      if(argc>arg)
+        socklogfile = argv[arg++];
+    }
+    else if(!strcmp("--ipv6", argv[arg])) {
+#ifdef ENABLE_IPV6
+      use_ipv6=TRUE;
+#endif
+      arg++;
+    }
+    else if(!strcmp("--ipv4", argv[arg])) {
+      /* for completeness, we support this option as well */
+      use_ipv6=FALSE;
+      arg++;
+    }
+    else if(!strcmp("--port", argv[arg])) {
+      arg++;
+      if(argc>arg) {
+        port = (unsigned short)atoi(argv[arg]);
+        arg++;
+      }
+    }
+    else if(!strcmp("--connect", argv[arg])) {
+      /* Asked to actively connect to the specified local port instead of
+         doing a passive server-style listening. */
+      arg++;
+      if(argc>arg) {
+        connectport = (unsigned short)atoi(argv[arg]);
+        arg++;
+      }
+    }
+    else {
+      puts("Usage: sockfilt [option]\n"
+           " --version\n"
+           " --logfile [file]\n"
+           " --pidfile [file]\n"
+           " --ipv4\n"
+           " --ipv6\n"
+           " --port [port]");
+      exit(0);
+    }
+  }
+
+#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__)
+  win32_init();
+  atexit(win32_cleanup);
+#else
+
+#ifdef SIGPIPE
+#ifdef HAVE_SIGNAL
+  signal(SIGPIPE, sigpipe_handler);
+#endif
+#ifdef HAVE_SIGINTERRUPT
+  siginterrupt(SIGPIPE, 1);
+#endif
+#endif
+#endif
+
+#ifdef ENABLE_IPV6
+  if(!use_ipv6)
+#endif
+    sock = socket(AF_INET, SOCK_STREAM, 0);
+#ifdef ENABLE_IPV6
+  else
+    sock = socket(AF_INET6, SOCK_STREAM, 0);
+#endif
+
+  if (sock < 0) {
+    perror("opening stream socket");
+    logmsg("Error opening socket");
+    exit(1);
+  }
+
+  if(connectport) {
+    /* Active mode, we should connect to the given port number */
+    mode = ACTIVE;
+#ifdef ENABLE_IPV6
+    if(!use_ipv6) {
+#endif
+      memset(&me, 0, sizeof(me));
+      me.sin_family = AF_INET;
+      me.sin_port = htons(connectport);
+      me.sin_addr.s_addr = INADDR_ANY;
+      Curl_inet_pton(AF_INET, "127.0.0.1", &me.sin_addr);
+
+      rc = connect(sock, (struct sockaddr *) &me, sizeof(me));
+#ifdef ENABLE_IPV6
+    }
+    else {
+      memset(&me6, 0, sizeof(me6));
+      me6.sin6_family = AF_INET6;
+      me6.sin6_port = htons(connectport);
+      Curl_inet_pton(AF_INET, "::1", &me6.sin6_addr);
+
+      rc = connect(sock, (struct sockaddr *) &me6, sizeof(me6));
+    }
+#endif /* ENABLE_IPV6 */
+    if(rc) {
+      perror("connecting stream socket");
+      logmsg("Error connecting to port %d", port);
+      exit(1);
+    }
+    logmsg("====> Client connect");
+    msgsock = sock; /* use this as stream */
+  }
+  else {
+    /* passive daemon style */
+
+    flag = 1;
+    if (setsockopt
+        (sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &flag,
+         sizeof(int)) < 0) {
+      perror("setsockopt(SO_REUSEADDR)");
+    }
+
+#ifdef ENABLE_IPV6
+    if(!use_ipv6) {
+#endif
+      me.sin_family = AF_INET;
+      me.sin_addr.s_addr = INADDR_ANY;
+      me.sin_port = htons(port);
+      rc = bind(sock, (struct sockaddr *) &me, sizeof(me));
+#ifdef ENABLE_IPV6
+    }
+    else {
+      memset(&me6, 0, sizeof(struct sockaddr_in6));
+      me6.sin6_family = AF_INET6;
+      me6.sin6_addr = in6addr_any;
+      me6.sin6_port = htons(port);
+      rc = bind(sock, (struct sockaddr *) &me6, sizeof(me6));
+    }
+#endif /* ENABLE_IPV6 */
+    if(rc < 0) {
+      perror("binding stream socket");
+      logmsg("Error binding socket");
+      exit(1);
+    }
+
+    if(!port) {
+      /* The system picked a port number, now figure out which port we actually
+         got */
+      /* we succeeded to bind */
+      struct sockaddr_in add;
+      socklen_t socksize = sizeof(add);
+
+      if(getsockname(sock, (struct sockaddr *) &add,
+                     &socksize)<0) {
+        fprintf(stderr, "getsockname() failed");
+        return 1;
+      }
+      port = ntohs(add.sin_port);
+    }
+
+    /* start accepting connections */
+    listen(sock, 0);
+
+  }
+
+  pidfile = fopen(pidname, "w");
+  if(pidfile) {
+    fprintf(pidfile, "%d\n", (int)getpid());
+    fclose(pidfile);
+  }
+  else
+    fprintf(stderr, "Couldn't write pid file\n");
+
+  logmsg("Running IPv%d version",
+         (use_ipv6?6:4));
+
+  if(connectport)
+    logmsg("Connected to port %d", connectport);
+  else
+    logmsg("Listening on port %d", port);
+
+  do {
+    ok = juggle(&msgsock, sock, &mode);
+  } while(ok);
+
+  sclose(sock);
+
+  return 0;
+}
+
diff --git a/tests/server/testpart.c b/tests/server/testpart.c
new file mode 100644 (file)
index 0000000..c1ebdd4
--- /dev/null
@@ -0,0 +1,52 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * $Id$
+ ***************************************************************************/
+
+#include "setup.h"
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include "getpart.h"
+
+#define _MPRINTF_REPLACE /* use our functions only */
+#include <curl/mprintf.h>
+
+/* include memdebug.h last */
+#include "memdebug.h"
+
+int main(int argc, char **argv)
+{
+  if(argc< 3) {
+    printf("./testpart main sub\n");
+  }
+  else {
+    size_t size;
+    unsigned int i;
+    const char *buffer = spitout(stdin, argv[1], argv[2], &size);
+    for(i=0; i< size; i++)
+      printf("%c", buffer[i]);
+  }
+  return 0;
+}
+