# Declare our package
package POE::Component::SSLify::NonBlock::ServerHandle;
use strict; use warnings;

# Initialize our version
use vars qw( $VERSION );
$VERSION = (qw$LastChangedRevision: 7 $)[1];

# Import the SSL death routines
use Net::SSLeay qw( die_now die_if_ssl_error );

our $globalinfos;
our $getserial = 0;

# Ties the socket
sub TIEHANDLE {
	my ( $class, $socket, $ctx, $params ) = @_;

	my $ssl = Net::SSLeay::new( $ctx ) or die_now( "Failed to create SSL $!" );

	my $fileno = fileno( $socket );

	Net::SSLeay::set_fd( $ssl, $fileno );

   my $orfilter = &Net::SSLeay::VERIFY_PEER
                  | &Net::SSLeay::VERIFY_CLIENT_ONCE;
   $orfilter |=  &Net::SSLeay::VERIFY_FAIL_IF_NO_PEER_CERT unless $params->{noblockbadcert};
   Net::SSLeay::set_verify ($ssl, $orfilter, \&verify);
   $getserial = $params->{getserial};

   # BAD!
	#my $err = Net::SSLeay::accept( $ssl ) and die_if_ssl_error( 'ssl accept' );

	my $self = bless {
		'ssl'		     => $ssl,
		'ctx'		     => $ctx,
		'socket'	     => $socket,
		'fileno'	     => $fileno,
      'acceptstate' => 0,
      'debug'       => $params->{debug}
	}, $class;
   $globalinfos = [0, 0, []];

   return undef unless $self->HandleSSL();
	return $self;
}

sub verify {
   my ($ok, $x509_store_ctx) = @_;
   print "VERIFY!\n";
   $globalinfos->[0] = $ok ? 1 : 2 if ($globalinfos->[0] != 2);
   $globalinfos->[1]++;
   if (my $x = Net::SSLeay::X509_STORE_CTX_get_current_cert($x509_store_ctx)) {
      push(@{$globalinfos->[2]},[Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_subject_name($x)),
                                 Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_issuer_name($x)),
                                 Net::SSLeay::X509_get_serialNumber($x)]);
   }
   return 1; # $ok; # 1=accept cert, 0=reject
}

sub HandleSSL {
   my $self = shift;
   my $rv = Net::SSLeay::accept($self->{ssl});
   $self->{acceptstate} = 0;
   $rv == 0 ? $self->{acceptstate} = 0 : $rv > 0 ? $self->{acceptstate} = 3 : 1;
   if ($self->{acceptstate}) { 
      print "HANDLEACCEPT:SERVER:A:".$self->{acceptstate}.":\n"
         if ($self->{debug});
      if ($self->{acceptstate} > 2) {
         $self->{infos} = [((@$globalinfos)[0..2])];
         $globalinfos = [0, 0, []];
      }
      return $self->{acceptstate};
   }
   my $err = Net::SSLeay::get_error($self->{ssl},$rv);
   $self->{acceptstate} = $err == Net::SSLeay::ERROR_WANT_READ()  ? 1 : 
                          $err == Net::SSLeay::ERROR_WANT_WRITE() ? 2 : 3;
   print "HANDLEACCEPT:SERVER:B:".$self->{acceptstate}.":\n"
      if ($self->{debug});
   return $self->{acceptstate};
}

# Read something from the socket
sub READ {
	# Get ourself!
	my $self = shift;

	# Get the pointers to buffer, length, and the offset
	my( $buf, $len, $offset ) = \( @_ );

   return -1 unless exists($self->{'acceptstate'});

   if ($self->{'acceptstate'} < 3) {
      $self->{'acceptstate'} = $self->HandleSSL();
      if ($self->{'acceptstate'} < 3) {
         return -1 unless $self->{'acceptstate'};
         # Currently we can't read cause we're in handshake!
         print "Currently we can't read cause we're in handshake!\n"
            if ($self->{debug});
         $$buf = "";
         return -2;
      }
   }

	# If we have no offset, replace the buffer with some input
	if ( ! defined $$offset ) {
		$$buf = Net::SSLeay::read( $self->{'ssl'}, $$len );

		# Are we done?
		if ( defined $$buf ) {
			return length( $$buf );
		} else {
			# Nah, clear the buffer too...
			$$buf = "";
			return;
		}
	}

	# Now, actually read the data
	defined( my $read = Net::SSLeay::read( $self->{'ssl'}, $$len ) ) or return undef;

	# Figure out the buffer and offset
	my $buf_len = length( $$buf );

	# If our offset is bigger, pad the buffer
	if ( $$offset > $buf_len ) {
		$$buf .= chr( 0 ) x ( $$offset - $buf_len );
	}

	# Insert what we just read into the buffer
	substr( $$buf, $$offset ) = $read;

	# All done!
	return length( $read );
}

# Write some stuff to the socket
sub WRITE {
	# Get ourself + buffer + length + offset to write
	my( $self, $buf, $len, $offset ) = @_;

	# If we have nothing to offset, then start from the beginning
	if ( ! defined $offset ) {
		$offset = 0;
	}

   return -1 unless exists($self->{'acceptstate'});

   if ($self->{'acceptstate'} < 3) {
      $self->{'acceptstate'} = $self->HandleSSL();
      if ($self->{'acceptstate'} < 3) {
         return -1 unless $self->{'acceptstate'};
         # Currently we can't read cause we're in handshake!
         print "Currently we can't read cause we're in handshake!\n"
            if ($self->{debug});
         return -2;
      }
   }

	# We count the number of characters written to the socket
	my $wrote_len = Net::SSLeay::write( $self->{'ssl'}, substr( $buf, $offset, $len ) );

	# Did we get an error or number of bytes written?
	# Net::SSLeay::write() returns the number of bytes written, or -1 on error.
	if ( $wrote_len < 0 ) {
		# The normal syswrite() POE uses expects 0 here.
		return 0;
	} else {
		# All done!
		return $wrote_len;
	}
}

# Sets binmode on the socket
# Thanks to RT #27117
sub BINMODE {
	my $self = shift;
	if (@_) {
		my $mode = shift;
		binmode $self->{'socket'}, $mode;
	} else {
		binmode $self->{'socket'};
	}
}

# Closes the socket
sub CLOSE {
	my $self = shift;
	if ( defined $self->{'socket'} ) {
		Net::SSLeay::free( $self->{'ssl'} );
		close( $self->{'socket'} );
		undef $self->{'socket'};

		# do we need to do CTX_free?
		if ( exists $self->{'client'} ) {
			Net::SSLeay::CTX_free( $self->{'ctx'} );
		}
	}

	return 1;
}

# Add DESTROY handler
sub DESTROY {
	my $self = shift;

	# Did we already CLOSE?
	if ( defined $self->{'socket'} ) {
		# Guess not...
		$self->CLOSE();
	}
}

sub FILENO {
	my $self = shift;
	return $self->{'fileno'};
}

# Not implemented TIE's
sub READLINE {
	die 'Not Implemented';
}

sub PRINT {
	die 'Not Implemented';
}

# Returns our hash
sub _get_self {
   my $self = shift;
   return $self;
}

# End of module
1;

__END__

=head1 NAME

POE::Component::SSLify::ServerHandle - server object for POE::Component::SSLify

=head1 ABSTRACT

	See POE::Component::SSLify

=head1 DESCRIPTION

	This is a subclass of Net::SSLeay::Handle because their read() and sysread()
	does not cooperate well with POE. They block until length bytes are read from the
	socket, and that is BAD in the world of POE...

	This subclass behaves exactly the same, except that it doesn't block :)

=head2 DIFFERENCES

	This subclass doesn't know what to do with PRINT/READLINE, as they usually are not used in POE::Wheel operations...

=head1 SEE ALSO

L<POE::Component::SSLify>

=head1 AUTHOR

Apocalypse E<lt>apocal@cpan.orgE<gt>

=head1 PROPS

	Original code is entirely Rocco Caputo ( Creator of POE ) -> I simply
	packaged up the code into something everyone could use...

	From the PoCo::Client::HTTP code for blocking sockets =]
	# TODO - This code should probably become a POE::Kernel method,
    	# seeing as it's rather baroque and potentially useful in a number
    	# of places.

=head1 COPYRIGHT AND LICENSE

Copyright 2008 by Apocalypse/Rocco Caputo

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut
