Fix Invalid Argument return code from IP_Factory in connect_to_server().
[openssl.git] / util / perl / TLSProxy / Proxy.pm
index 8f7f983ebc9bf0b5d8b8ea099713e7f5cc5c5013..89c8c1d6f26fe3803481e11175ab846702946e5a 100644 (file)
@@ -23,9 +23,50 @@ use TLSProxy::CertificateVerify;
 use TLSProxy::ServerKeyExchange;
 use TLSProxy::NewSessionTicket;
 
-my $have_IPv6 = 0;
+my $have_IPv6;
 my $IP_factory;
 
+BEGIN
+{
+    # IO::Socket::IP is on the core module list, IO::Socket::INET6 isn't.
+    # However, IO::Socket::INET6 is older and is said to be more widely
+    # deployed for the moment, and may have less bugs, so we try the latter
+    # first, then fall back on the core modules.  Worst case scenario, we
+    # fall back to IO::Socket::INET, only supports IPv4.
+    eval {
+        require IO::Socket::INET6;
+        my $s = IO::Socket::INET6->new(
+            LocalAddr => "::1",
+            LocalPort => 0,
+            Listen=>1,
+            );
+        $s or die "\n";
+        $s->close();
+    };
+    if ($@ eq "") {
+        $IP_factory = sub { IO::Socket::INET6->new(Domain => AF_INET6, @_); };
+        $have_IPv6 = 1;
+    } else {
+        eval {
+            require IO::Socket::IP;
+            my $s = IO::Socket::IP->new(
+                LocalAddr => "::1",
+                LocalPort => 0,
+                Listen=>1,
+                );
+            $s or die "\n";
+            $s->close();
+        };
+        if ($@ eq "") {
+            $IP_factory = sub { IO::Socket::IP->new(@_); };
+            $have_IPv6 = 1;
+        } else {
+            $IP_factory = sub { IO::Socket::INET->new(@_); };
+            $have_IPv6 = 0;
+        }
+    }
+}
+
 my $is_tls13 = 0;
 my $ciphersuite = undef;
 
@@ -39,8 +80,7 @@ sub new
 
     my $self = {
         #Public read/write
-        proxy_addr => "localhost",
-        server_addr => "localhost",
+        proxy_addr => $have_IPv6 ? "[::1]" : "127.0.0.1",
         filter => $filter,
         serverflags => "",
         clientflags => "",
@@ -67,43 +107,6 @@ sub new
         message_list => [],
     };
 
-    # IO::Socket::IP is on the core module list, IO::Socket::INET6 isn't.
-    # However, IO::Socket::INET6 is older and is said to be more widely
-    # deployed for the moment, and may have less bugs, so we try the latter
-    # first, then fall back on the code modules.  Worst case scenario, we
-    # fall back to IO::Socket::INET, only supports IPv4.
-    eval {
-        require IO::Socket::INET6;
-        my $s = IO::Socket::INET6->new(
-            LocalAddr => "::1",
-            LocalPort => 0,
-            Listen=>1,
-            );
-        $s or die "\n";
-        $s->close();
-    };
-    if ($@ eq "") {
-        $IP_factory = sub { IO::Socket::INET6->new(@_); };
-        $have_IPv6 = 1;
-    } else {
-        eval {
-            require IO::Socket::IP;
-            my $s = IO::Socket::IP->new(
-                LocalAddr => "::1",
-                LocalPort => 0,
-                Listen=>1,
-                );
-            $s or die "\n";
-            $s->close();
-        };
-        if ($@ eq "") {
-            $IP_factory = sub { IO::Socket::IP->new(@_); };
-            $have_IPv6 = 1;
-        } else {
-            $IP_factory = sub { IO::Socket::INET->new(@_); };
-        }
-    }
-
     # Create the Proxy socket
     my $proxaddr = $self->{proxy_addr};
     $proxaddr =~ s/[\[\]]//g; # Remove [ and ]
@@ -113,11 +116,16 @@ sub new
         Proto       => "tcp",
         Listen      => SOMAXCONN,
        );
-    $self->{proxy_sock} = $IP_factory->(@proxyargs);
 
-    if ($self->{proxy_sock}) {
-        $self->{proxy_port} = $self->{proxy_sock}->sockport();
-        print "Proxy started on port ".$self->{proxy_port}."\n";
+    if (my $sock = $IP_factory->(@proxyargs)) {
+        $self->{proxy_sock} = $sock;
+        $self->{proxy_port} = $sock->sockport();
+        $self->{proxy_addr} = $sock->sockhost();
+        $self->{proxy_addr} =~ s/(.*:.*)/[$1]/;
+        print "Proxy started on port ",
+              "$self->{proxy_addr}:$self->{proxy_port}\n";
+        # use same address for s_server
+        $self->{server_addr} = $self->{proxy_addr};
     } else {
         warn "Failed creating proxy socket (".$proxaddr.",0): $!\n";
     }
@@ -189,10 +197,16 @@ sub connect_to_server
 
     $servaddr =~ s/[\[\]]//g; # Remove [ and ]
 
-    $self->{server_sock} = $IP_factory->(PeerAddr => $servaddr,
-                                         PeerPort => $self->{server_port},
-                                         Proto => 'tcp')
-                           or die "unable to connect: $!\n";
+    my $sock = $IP_factory->(PeerAddr => $servaddr,
+                             PeerPort => $self->{server_port},
+                             Proto => 'tcp');
+    if (!defined($sock)) {
+        my $err = $!;
+        kill(3, $self->{real_serverpid});
+        die "unable to connect: $err\n";
+    }
+
+    $self->{server_sock} = $sock;
 }
 
 sub start
@@ -206,11 +220,15 @@ sub start
 
     my $execcmd = $self->execute
         ." s_server -max_protocol TLSv1.3 -no_comp -rev -engine ossltest"
-        ." -accept 0 -cert ".$self->cert." -cert2 ".$self->cert
+        #In TLSv1.3 we issue two session tickets. The default session id
+        #callback gets confused because the ossltest engine causes the same
+        #session id to be created twice due to the changed random number
+        #generation. Using "-ext_cache" replaces the default callback with a
+        #different one that doesn't get confused.
+        ." -ext_cache"
+        ." -accept $self->{server_addr}:0"
+        ." -cert ".$self->cert." -cert2 ".$self->cert
         ." -naccept ".$self->serverconnects;
-    unless ($self->supports_IPv6) {
-        $execcmd .= " -4";
-    }
     if ($self->ciphers ne "") {
         $execcmd .= " -cipher ".$self->ciphers;
     }
@@ -227,7 +245,7 @@ sub start
     open(my $savedin, "<&STDIN");
 
     # Temporarily replace STDIN so that sink process can inherit it...
-    $pid = open(STDIN, "$execcmd |") or die "Failed to $execcmd: $!\n";
+    $pid = open(STDIN, "$execcmd 2>&1 |") or die "Failed to $execcmd: $!\n";
     $self->{real_serverpid} = $pid;
 
     # Process the output from s_server until we find the ACCEPT line, which
@@ -243,7 +261,8 @@ sub start
     if ($self->{server_port} == 0) {
         # This actually means that s_server exited, because otherwise
         # we would still searching for ACCEPT...
-        die "no ACCEPT detected in '$execcmd' output\n";
+        waitpid($pid, 0);
+        die "no ACCEPT detected in '$execcmd' output: $?\n";
     }
 
     # Just make sure everything else is simply printed [as separate lines].
@@ -255,6 +274,7 @@ sub start
     if (eval { require Win32::Process; 1; }) {
         if (Win32::Process::Create(my $h, $^X, "perl -ne print", 0, 0, ".")) {
             $pid = $h->GetProcessID();
+            $self->{proc_handle} = $h;  # hold handle till next round [or exit]
         } else {
             $error = Win32::FormatMessage(Win32::GetLastError());
         }
@@ -278,7 +298,7 @@ sub start
     $self->{serverpid} = $pid;
 
     print STDERR "Server responds on ",
-        $self->{server_addr}, ":", $self->{server_port}, "\n";
+                 "$self->{server_addr}:$self->{server_port}\n";
 
     # Connect right away...
     $self->connect_to_server();
@@ -293,11 +313,8 @@ sub clientstart
     if ($self->execute) {
         my $pid;
         my $execcmd = $self->execute
-             ." s_client -max_protocol TLSv1.3 -engine ossltest -connect "
-             .($self->proxy_addr).":".($self->proxy_port);
-        unless ($self->supports_IPv6) {
-            $execcmd .= " -4";
-        }
+             ." s_client -max_protocol TLSv1.3 -engine ossltest"
+             ." -connect $self->{proxy_addr}:$self->{proxy_port}";
         if ($self->cipherc ne "") {
             $execcmd .= " -cipher ".$self->cipherc;
         }
@@ -307,6 +324,9 @@ sub clientstart
         if ($self->clientflags ne "") {
             $execcmd .= " ".$self->clientflags;
         }
+        if ($self->clientflags !~ m/-(no)?servername/) {
+            $execcmd .= " -servername localhost";
+        }
         if (defined $self->sessionfile) {
             $execcmd .= " -ign_eof";
         }
@@ -335,7 +355,7 @@ sub clientstart
 
     # Wait for incoming connection from client
     my $fdset = IO::Select->new($self->{proxy_sock});
-    if (!$fdset->can_read(1)) {
+    if (!$fdset->can_read(60)) {
         kill(3, $self->{real_serverpid});
         die "s_client didn't try to connect\n";
     }
@@ -356,11 +376,14 @@ sub clientstart
     my @ready;
     my $ctr = 0;
     local $SIG{PIPE} = "IGNORE";
-    while($fdset->count
-            && (!(TLSProxy::Message->end)
-                || (defined $self->sessionfile()
-                    && (-s $self->sessionfile()) == 0))
-            && $ctr < 10) {
+    $self->{saw_session_ticket} = undef;
+    while($fdset->count && $ctr < 10) {
+        if (defined($self->{sessionfile})) {
+            # s_client got -ign_eof and won't be exiting voluntarily, so we
+            # look for data *and* session ticket...
+            last if TLSProxy::Message->success()
+                    && $self->{saw_session_ticket};
+        }
         if (!(@ready = $fdset->can_read(1))) {
             $ctr++;
             next;
@@ -412,11 +435,19 @@ sub clientstart
     my $pid;
     if (--$self->{serverconnects} == 0) {
         $pid = $self->{serverpid};
-        die "serverpid is zero\n" if $pid == 0;
-        print "Waiting for server process to close: $pid...\n";
-        # recall that we wait on process that buffers server's output
+        print "Waiting for 'perl -ne print' process to close: $pid...\n";
+        $pid = waitpid($pid, 0);
+        if ($pid > 0) {
+            die "exit code $? from 'perl -ne print' process\n" if $? != 0;
+        } elsif ($pid == 0) {
+            kill(3, $self->{real_serverpid});
+            die "lost control over $self->{serverpid}?";
+        }
+        $pid = $self->{real_serverpid};
+        print "Waiting for s_server process to close: $pid...\n";
+        # it's done already, just collect the exit code [and reap]...
         waitpid($pid, 0);
-        die "exit code $? from server process\n" if $? != 0;
+        die "exit code $? from s_server process\n" if $? != 0;
     } else {
         # It's a bit counter-intuitive spot to make next connection to
         # the s_server. Rationale is that established connection works
@@ -425,8 +456,7 @@ sub clientstart
         $self->connect_to_server();
     }
     $pid = $self->{clientpid};
-    die "clientpid is zero\n" if $pid == 0;
-    print "Waiting for client process to close: $pid...\n";
+    print "Waiting for s_client process to close: $pid...\n";
     waitpid($pid, 0);
 
     return 1;
@@ -456,7 +486,8 @@ sub process_packet
 
     #Return contains the list of record found in the packet followed by the
     #list of messages in those records and any partial message
-    my @ret = TLSProxy::Record->get_records($server, $self->flight, $self->{partial}[$server].$packet);
+    my @ret = TLSProxy::Record->get_records($server, $self->flight,
+                                            $self->{partial}[$server].$packet);
     $self->{partial}[$server] = $ret[2];
     push @{$self->{record_list}}, @{$ret[0]};
     push @{$self->{message_list}}, @{$ret[1]};
@@ -472,6 +503,14 @@ sub process_packet
         $self->filter->($self);
     }
 
+    #Take a note on NewSessionTicket
+    foreach my $message (reverse @{$self->{message_list}}) {
+        if ($message->{mt} == TLSProxy::Message::MT_NEW_SESSION_TICKET) {
+            $self->{saw_session_ticket} = 1;
+            last;
+        }
+    }
+
     #Reconstruct the packet
     $packet = "";
     foreach my $record (@{$self->record_list}) {