diff --git a/lib/POE/Component/SSLify/ServerHandle.pm b/lib/POE/Component/SSLify/ServerHandle.pm index 463a54b..cfc7056 100644 --- a/lib/POE/Component/SSLify/ServerHandle.pm +++ b/lib/POE/Component/SSLify/ServerHandle.pm @@ -3,7 +3,7 @@ # ABSTRACT: Server-side handle for SSLify # Import the SSL death routines -use Net::SSLeay 1.36 qw( die_now die_if_ssl_error ); +use Net::SSLeay 1.36 qw( die_now die_if_ssl_error ERROR_WANT_READ ERROR_WANT_WRITE ); # Ties the socket sub TIEHANDLE { @@ -44,11 +44,22 @@ $self->{'status'} = Net::SSLeay::accept( $self->{'ssl'} ); } - if ( $self->{'status'} == 0 ) { - # TODO error? + # Only process the stuff if we actually have a callback! + return unless defined $self->{'on_connect'}; + + if ( $self->{'status'} <= 0 ) { + # http://www.openssl.org/docs/ssl/SSL_get_error.html + my $errval = Net::SSLeay::get_error( $self->{'ssl'}, $self->{'status'} ); + + # TODO should we skip ERROR_WANT_ACCEPT and ERROR_WANT_CONNECT ? + # also, ERROR_WANT_ACCEPT isn't exported by Net::SSLeay, huh? + if ( $errval != ERROR_WANT_READ and $errval != ERROR_WANT_WRITE ) { + # call the hook function for error connect + $self->{'on_connect'}->( $self->{'orig_socket'}, 'ERR', $errval ); + } } elsif ( $self->{'status'} == 1 ) { # call the hook function for successful connect - $self->{'on_connect'}->( $self->{'orig_socket'} ) if defined $self->{'on_connect'}; + $self->{'on_connect'}->( $self->{'orig_socket'}, 'OK' ); } } @@ -61,7 +72,7 @@ my( $buf, $len, $offset ) = \( @_ ); # Check connection status - $self->_check_status if $self->{'status'} == -1; + $self->_check_status if $self->{'status'} <= 0; # If we have no offset, replace the buffer with some input if ( ! defined $$offset ) { @@ -101,7 +112,7 @@ my( $self, $buf, $len, $offset ) = @_; # Check connection status - $self->_check_status if $self->{'status'} == -1; + $self->_check_status if $self->{'status'} <= 0; # If we have nothing to offset, then start from the beginning if ( ! defined $offset ) { diff --git a/t/1_simple.t b/t/1_simple.t index 0d435e7..b5def40 100644 --- a/t/1_simple.t +++ b/t/1_simple.t @@ -12,7 +12,6 @@ if ( ! $@ ) { # increment by one $numtests++; - } } diff --git a/t/2_renegotiate.t b/t/2_renegotiate.t index fb2d789..f2bf556 100644 --- a/t/2_renegotiate.t +++ b/t/2_renegotiate.t @@ -16,7 +16,6 @@ if ( ! $@ ) { # increment by one $numtests++; - } } diff --git a/t/3_insitu.t b/t/3_insitu.t index 32c756d..b3c6f17 100644 --- a/t/3_insitu.t +++ b/t/3_insitu.t @@ -12,7 +12,6 @@ if ( ! $@ ) { # increment by one $numtests++; - } } diff --git a/t/4_connref.t b/t/4_connref.t index 5ecc1a9..ff7e73c 100644 --- a/t/4_connref.t +++ b/t/4_connref.t @@ -6,13 +6,12 @@ my $numtests; BEGIN { - $numtests = 20; + $numtests = 17; eval "use Test::NoWarnings"; if ( ! $@ ) { # increment by one $numtests++; - } } @@ -21,7 +20,7 @@ use POE 1.267; use POE::Component::Client::TCP; use POE::Component::Server::TCP; -use POE::Component::SSLify qw/Client_SSLify Server_SSLify SSLify_Options SSLify_GetCipher SSLify_ContextCreate SSLify_GetSocket SSLify_GetStatus/; +use POE::Component::SSLify qw/Client_SSLify Server_SSLify SSLify_Options SSLify_GetCipher SSLify_GetSocket SSLify_GetStatus/; # TODO rewrite this to use Test::POE::Server::TCP and stuff :) @@ -49,13 +48,15 @@ }, ClientPreConnect => sub { - eval { SSLify_Options('mylib/example.key', 'mylib/example.crt', 'sslv3') }; - eval { SSLify_Options('../mylib/example.key', '../mylib/example.crt', 'sslv3') } if ($@); + eval { SSLify_Options('mylib/example.key', 'mylib/example.crt') }; + eval { SSLify_Options('../mylib/example.key', '../mylib/example.crt') } if ($@); ok(!$@, "SERVER: SSLify_Options $@"); my $socket = eval { Server_SSLify( $_[ARG0], sub { - my $socket = shift; - pass( "Got connect hook for server" ); + my( $socket, $status, $errval ) = @_; + + pass( "SERVER: Got connect hook" ); + is( $status, 'OK', "SERVER: Status received from callback is OK" ); ## At this point, connection MUST be encrypted. my $cipher = SSLify_GetCipher($socket); @@ -63,11 +64,7 @@ ok( SSLify_GetStatus($socket) == 1, "SERVER: SSLify_GetStatus is done" ); } ) }; ok(!$@, "SERVER: Server_SSLify $@"); - ok(1, 'SERVER: SSLify_GetCipher: '. SSLify_GetCipher($socket)); ok( SSLify_GetStatus($socket) == -1, "SERVER: SSLify_GetStatus is pending" ); - - # We pray that IO::Handle is sane... - ok( SSLify_GetSocket( $socket )->blocking == 0, 'SERVER: SSLified socket is non-blocking?'); return ($socket); }, @@ -105,12 +102,11 @@ }, PreConnect => sub { - my $ctx = eval { SSLify_ContextCreate(undef, undef, 'sslv3') }; - ok(!$@, "CLIENT: SSLify_ContextCreate $@"); - my $socket = eval { Client_SSLify($_[ARG0], undef, undef, $ctx, sub { - my $socket = shift; + my $socket = eval { Client_SSLify($_[ARG0], sub { + my( $socket, $status, $errval ) = @_; - pass( "Got connect hook for client" ); + pass( "CLIENT: Got connect hook" ); + is( $status, 'OK', "CLIENT: Status received from callback is OK" ); ## At this point, connection MUST be encrypted. my $cipher = SSLify_GetCipher($socket); @@ -120,11 +116,7 @@ $poe_kernel->post( 'myclient' => 'shutdown' ); }) }; ok(!$@, "CLIENT: Client_SSLify $@"); - ok(1, 'CLIENT: SSLify_GetCipher: '. SSLify_GetCipher($socket)); ok( SSLify_GetStatus($socket) == -1, "CLIENT: SSLify_GetStatus is pending" ); - - # We pray that IO::Handle is sane... - ok( SSLify_GetSocket( $socket )->blocking == 0, 'CLIENT: SSLified socket is non-blocking?'); return ($socket); }, @@ -132,7 +124,7 @@ { my ($kernel, $heap, $line) = @_[KERNEL, HEAP, ARG0]; - $kernel->yield('shutdown'); + die "Should have never got any input from the server!"; }, ServerError => sub { diff --git a/t/5_connfail_client.t b/t/5_connfail_client.t new file mode 100644 index 0000000..f74ccd7 --- /dev/null +++ b/t/5_connfail_client.t @@ -0,0 +1,127 @@ +#!/usr/bin/perl + +# Thanks to ASCENT for this test! + +use strict; use warnings; + +my $numtests; +BEGIN { + $numtests = 8; + + eval "use Test::NoWarnings"; + if ( ! $@ ) { + # increment by one + $numtests++; + } +} + +use Test::More tests => $numtests; + +use POE 1.267; +use POE::Component::Client::TCP; +use POE::Component::Server::TCP; +use POE::Component::SSLify qw/Client_SSLify SSLify_GetSocket SSLify_GetStatus/; + +# TODO rewrite this to use Test::POE::Server::TCP and stuff :) + +my $port; + +POE::Component::Server::TCP->new +( + Alias => 'myserver', + Address => '127.0.0.1', + Port => 0, + + Started => sub + { + use Socket qw/sockaddr_in/; + $port = (sockaddr_in($_[HEAP]->{listener}->getsockname))[0]; + }, + ClientConnected => sub + { + ok(1, 'SERVER: accepted'); + }, + ClientDisconnected => sub + { + ok(1, 'SERVER: client disconnected'); + $_[KERNEL]->post( 'myserver' => 'shutdown'); + }, + ClientInput => sub + { + my ($kernel, $heap, $line) = @_[KERNEL, HEAP, ARG0]; + + # purposefully send garbage so we screw up the ssl connect on the client-side + $heap->{client}->put( 'garbage in, garbage out' ); + }, + ClientError => sub + { + # Thanks to H. Merijn Brand for spotting this FAIL in 5.12.0! + # The default PoCo::Server::TCP handler will throw a warning, which causes Test::NoWarnings to FAIL :( + my ($syscall, $errno, $error) = @_[ ARG0..ARG2 ]; + + # Since this test purposefully sends garbage, we expect a connection reset by peer + # not ok 7 - Got SERVER read error 104: Connection reset by peer + + # TODO are there other "errors" that is harmless? + $error = "Normal disconnection" unless $error; + my $msg = "Got SERVER $syscall error $errno: $error"; + unless ( $syscall eq 'read' and $errno == 104 ) { + fail( $msg ); + } else { + diag( $msg ) if $ENV{TEST_VERBOSE}; + } + }, +); + +POE::Component::Client::TCP->new +( + Alias => 'myclient', + RemoteAddress => '127.0.0.1', + RemotePort => $port, + Connected => sub + { + ok(1, 'CLIENT: connected'); + }, + PreConnect => sub + { + my $socket = eval { Client_SSLify($_[ARG0], sub { + my( $socket, $status, $errval ) = @_; + + pass( "CLIENT: Got connect hook" ); + is( $status, 'ERR', "CLIENT: Status received from callback is ERR - $errval" ); + + $poe_kernel->post( 'myclient' => 'shutdown' ); + }) }; + ok(!$@, "CLIENT: Client_SSLify $@"); + ok( SSLify_GetStatus($socket) == -1, "CLIENT: SSLify_GetStatus is pending" ); + + return ($socket); + }, + ServerInput => sub + { + my ($kernel, $heap, $line) = @_[KERNEL, HEAP, ARG0]; + + die "Should have never got any input from the server!"; + }, + ServerError => sub + { + # Thanks to H. Merijn Brand for spotting this FAIL in 5.12.0! + # The default PoCo::Client::TCP handler will throw a warning, which causes Test::NoWarnings to FAIL :( + my ($syscall, $errno, $error) = @_[ ARG0..ARG2 ]; + + # TODO are there other "errors" that is harmless? + $error = "Normal disconnection" unless $error; + my $msg = "Got CLIENT $syscall error $errno: $error"; + unless ( $syscall eq 'read' and $errno == 0 ) { + fail( $msg ); + } else { + diag( $msg ) if $ENV{TEST_VERBOSE}; + } + }, +); + +$poe_kernel->run(); + +pass( 'shut down sanely' ); + +exit 0;