NAME
    POE::Component::SSLify::NonBlock - Nonblocking SSL for POE with client
    certificate verification.

SYNOPSIS
  Server-side usage
       # Import the modules
       use POE::Component::SSLify qw( SSLify_Options SSLify_GetCTX );
       use POE::Component::SSLify::NonBlock qw( Server_SSLify_NonBlock );

       # Set the key + certificate file, only one time needed.
       eval { SSLify_Options( 'server.key', 'server.crt' ) };
       if ( $@ ) {
          # Unable to load key or certificate file...
       }

       # Create a normal SocketFactory wheel or something
       my $factory = POE::Wheel::SocketFactory->new( ... );

       # Converts the socket into a SSL socket POE can communicate with, every time on new socket needed.
       eval { $socket = Server_SSLify_NonBlock( SSLify_GetCTX(), $socket, { } ) };
       if ( $@ ) {
          # Unable to SSLify it...
       }

       # Now, hand it off to ReadWrite
       my $rw = POE::Wheel::ReadWrite->new(
          Handle   =>   $socket,
          ...
       );

ABSTRACT
    Nonblocking SSL for POE with client certificate verification.

DESCRIPTION
    This component represents a common way of using ssl on a server, which
    needs to ensure that no client can block the whole server. Further it
    allows to verificate client certificates.

  Non-Blocking needed, especially on client certificate verification
    SSL is a protocol which interacts with the client during the handshake
    multiple times. If the socket is blocking, as on pure
    POE::Component::SSLify, a client can block the whole server. Especially
    if you want to do client certificate verification, the user has the
    abilty to choose a client certificate. In this situation the ssl
    handshake is waiting, and in blocked mode the whole server also stops
    responding.

  Client certificate verification
    You have three opportunities to do client certificate verification:

      Easiest way: 
        Verify the certificate and let OpenSSL reject the connection during ssl handshake if there is no certificate or it is unstrusted.

      Advanced way:
        Verify the certificate and poe handler determines if there is no certificate or it is unstrusted.

      Complicated way:
        Verify the certificate and poe handler determines if there is no certificate, it is unstrusted or if it is blocked by a CRL.

   Easiest way: Client certificat rejection in ssl handshake
    Generally you can use the "Server-side usage" example above, but you
    have to enable the client certification feature with the
    "clientcertrequest" parameter. The Server_SSLify_NonBlock function
    allows a hash for parameters:

       use POE::Component::SSLify qw( SSLify_Options SSLify_GetCTX );
       use POE::Component::SSLify::NonBlock qw( Server_SSLify_NonBlock SSLify_Options_NonBlock_ClientCert Server_SSLify_NonBlock_ClientCertVerifyAgainstCRL Server_SSLify_NonBlock_ClientCertificateExists Server_SSLify_NonBlock_ClientCertIsValid Server_SSLify_NonBlock_SSLDone );
       use POE qw( Wheel::SocketFactory Driver::SysRW Filter::Stream Wheel::ReadWrite );

       eval { SSLify_Options( 'server.key', 'server.crt' ) };
       die "SSLify_Options: ".$@ if ( $@ );

       eval { SSLify_Options_NonBlock_ClientCert(SSLify_GetCTX(), 'ca.crt') };
       die "SSLify_Options_NonBlock_ClientCert: ".$@ if ( $@ );

       POE::Session->create(
          inline_states => {
             _start => sub {
                my ( $heap, $kernel ) = @_[ HEAP, KERNEL ];
                $heap->{server_wheel} = POE::Wheel::SocketFactory->new(
                   BindAddress  => "0.0.0.0",
                   BindPort     => 443,
                   Reuse        => 'yes',
                   SuccessEvent => 'client_accept',
                   FailureEvent => 'accept_failure',
                );
             },
             client_accept => sub {
                my ( $heap, $kernel, $socket ) = @_[ HEAP, KERNEL, ARG0 ];
                eval { $socket = Server_SSLify_NonBlock( SSLify_GetCTX(), $socket, {
                   clientcertrequest => 1,
                   debug => 1
                } ) };
                if ( $@ ) {
                   print "SSL Failed: ".$@."\n";
                   delete $heap->{server}->{$wheel_id}->{wheel};
                }
                my $io_wheel = POE::Wheel::ReadWrite->new(
                   Handle     => $socket,
                   Driver     => POE::Driver::SysRW->new,
                   Filter     => POE::Filter::Stream->new,
                   InputEvent => 'client_input'
                );
                $heap->{server}->{$io_wheel->ID()}->{wheel} = $io_wheel;
                $heap->{server}->{$io_wheel->ID()}->{socket} = $socket;
             },
             client_input => sub {
                my ( $heap, $kernel, $input, $wheel_id ) = @_[ HEAP, KERNEL, ARG0, ARG1 ];
                $heap->{server}->{$wheel_id}->{wheel}->put("[".$wheel_id."] Yeah! You're authenticated!\n") if $canwrite;
                $kernel->yield("disconnect" => $wheel_id);
             },
             disconnect => sub {
                my ($heap, $kernel, $wheel_id) = @_[HEAP, KERNEL, ARG0];
                $kernel->delay(close_delayed => 1, $wheel_id)
                   unless ($heap->{server}->{$wheel_id}->{disconnecting}++);
             },
             close_delayed => sub {
                my ($kernel, $heap, $wheel_id) = @_[KERNEL, HEAP, ARG0];
                delete $heap->{server}->{$wheel_id}->{wheel};
                delete $heap->{server}->{$wheel_id}->{socket};
             }
          }
       );
        
   $poe_kernel->run();

    Now the server sends the request for a client certificate during SSL
    handshake. By default, POE::Component::SSLify::NonBlock aborts the
    connection if "clientcertrequest" is set and there is no client
    certificat or the certificate is not trusted.

   Advanced way: Client certificat reject in POE Handler
       use POE::Component::SSLify qw( SSLify_Options SSLify_GetCTX );
       use POE::Component::SSLify::NonBlock qw( Server_SSLify_NonBlock SSLify_Options_NonBlock_ClientCert Server_SSLify_NonBlock_ClientCertVerifyAgainstCRL Server_SSLify_NonBlock_ClientCertificateExists Server_SSLify_NonBlock_ClientCertIsValid Server_SSLify_NonBlock_SSLDone );
       use POE qw( Wheel::SocketFactory Driver::SysRW Filter::Stream Wheel::ReadWrite );

       eval { SSLify_Options( 'server.key', 'server.crt' ) };
       die "SSLify_Options: ".$@ if ( $@ );

       eval { SSLify_Options_NonBlock_ClientCert(SSLify_GetCTX(), 'ca.crt') };
       die "SSLify_Options_NonBlock_ClientCert: ".$@ if ( $@ );

       POE::Session->create(
          inline_states => {
             _start => sub {
                my ( $heap, $kernel ) = @_[ HEAP, KERNEL ];
                $heap->{server_wheel} = POE::Wheel::SocketFactory->new(
                   BindAddress  => "0.0.0.0",
                   BindPort     => 443,
                   Reuse        => 'yes',
                   SuccessEvent => 'client_accept',
                   FailureEvent => 'accept_failure',
                );
             },
             client_accept => sub {
                my ( $heap, $kernel, $socket ) = @_[ HEAP, KERNEL, ARG0 ];
                eval { $socket = Server_SSLify_NonBlock( SSLify_GetCTX(), $socket, {
                   clientcertrequest => 1,
                   noblockbadclientcert => 1,
                   debug => 1
                } ) };
                if ( $@ ) {
                   print "SSL Failed: ".$@."\n";
                   delete $heap->{server}->{$wheel_id}->{wheel};
                }
                my $io_wheel = POE::Wheel::ReadWrite->new(
                   Handle     => $socket,
                   Driver     => POE::Driver::SysRW->new,
                   Filter     => POE::Filter::Stream->new,
                   InputEvent => 'client_input'
                );
                $heap->{server}->{$io_wheel->ID()}->{wheel} = $io_wheel;
                $heap->{server}->{$io_wheel->ID()}->{socket} = $socket;
             },
             client_input => sub {
                my ( $heap, $kernel, $input, $wheel_id ) = @_[ HEAP, KERNEL, ARG0, ARG1 ];
                my $canwrite = exists $heap->{server}->{$wheel_id}->{wheel} &&
                                 (ref($heap->{server}->{$wheel_id}->{wheel}) eq "POE::Wheel::ReadWrite");
                my $socket = $heap->{server}->{$wheel_id}->{socket};
                return unless Server_SSLify_NonBlock_SSLDone($socket);
                if (!(Server_SSLify_NonBlock_ClientCertificateExists($socket))) {
                   $heap->{server}->{$wheel_id}->{wheel}->put("[".$wheel_id."] NoClientCertExists\n") if $canwrite;
                   return $kernel->yield("disconnect" => $wheel_id);
                } elsif(!(Server_SSLify_NonBlock_ClientCertIsValid($socket))) {
                  $heap->{server}->{$wheel_id}->{wheel}->put("[".$wheel_id."] ClientCertInvalid\n") if $canwrite;
                   return $kernel->yield("disconnect" => $wheel_id);
                }
                $heap->{server}->{$wheel_id}->{wheel}->put("[".$wheel_id."] Yeah! You're authenticated!\n") if $canwrite;
                $kernel->yield("disconnect" => $wheel_id);
             },
             disconnect => sub {
                my ($heap, $kernel, $wheel_id) = @_[HEAP, KERNEL, ARG0];
                $kernel->delay(close_delayed => 1, $wheel_id)
                   unless ($heap->{server}->{$wheel_id}->{disconnecting}++);
             },
             close_delayed => sub {
                my ($kernel, $heap, $wheel_id) = @_[KERNEL, HEAP, ARG0];
                delete $heap->{server}->{$wheel_id}->{wheel};
                delete $heap->{server}->{$wheel_id}->{socket};
             }
          }
       );
        
   $poe_kernel->run();

   Complicated way: Client certificate reject in POE Handler with CRL support
    WARNING: To use this you have to patch the lines from net-ssleay-patch
    fike into Net::SSLeay (you find the patch in the base path of the tar.gz
    packet). Then recompile and reinstall the Net::SSLeay package.

       use POE::Component::SSLify qw( SSLify_Options SSLify_GetCTX );
       use POE::Component::SSLify::NonBlock qw( Server_SSLify_NonBlock SSLify_Options_NonBlock_ClientCert Server_SSLify_NonBlock_ClientCertVerifyAgainstCRL Server_SSLify_NonBlock_ClientCertificateExists Server_SSLify_NonBlock_ClientCertIsValid Server_SSLify_NonBlock_SSLDone );
       use POE qw( Wheel::SocketFactory Driver::SysRW Filter::Stream Wheel::ReadWrite );

       eval { SSLify_Options( 'server.key', 'server.crt' ) };
       die "SSLify_Options: ".$@ if ( $@ );

       eval { SSLify_Options_NonBlock_ClientCert(SSLify_GetCTX(), 'ca.crt') };
       die "SSLify_Options_NonBlock_ClientCert: ".$@ if ( $@ );

       POE::Session->create(
          inline_states => {
             _start => sub {
                my ( $heap, $kernel ) = @_[ HEAP, KERNEL ];
                $heap->{server_wheel} = POE::Wheel::SocketFactory->new(
                   BindAddress  => "0.0.0.0",
                   BindPort     => 443,
                   Reuse        => 'yes',
                   SuccessEvent => 'client_accept',
                   FailureEvent => 'accept_failure',
                );
             },
             client_accept => sub {
                my ( $heap, $kernel, $socket ) = @_[ HEAP, KERNEL, ARG0 ];
                eval { $socket = Server_SSLify_NonBlock( SSLify_GetCTX(), $socket, {
                   clientcertrequest => 1,
                   noblockbadclientcert => 1,
                   getserial => 1,
                   debug => 1
                } ) };
                if ( $@ ) {
                   print "SSL Failed: ".$@."\n";
                   delete $heap->{server}->{$wheel_id}->{wheel};
                }
                my $io_wheel = POE::Wheel::ReadWrite->new(
                   Handle     => $socket,
                   Driver     => POE::Driver::SysRW->new,
                   Filter     => POE::Filter::Stream->new,
                   InputEvent => 'client_input'
                );
                $heap->{server}->{$io_wheel->ID()}->{wheel} = $io_wheel;
                $heap->{server}->{$io_wheel->ID()}->{socket} = $socket;
             },
             client_input => sub {
                my ( $heap, $kernel, $input, $wheel_id ) = @_[ HEAP, KERNEL, ARG0, ARG1 ];
                my $canwrite = exists $heap->{server}->{$wheel_id}->{wheel} &&
                                 (ref($heap->{server}->{$wheel_id}->{wheel}) eq "POE::Wheel::ReadWrite");
                my $socket = $heap->{server}->{$wheel_id}->{socket};
                return unless Server_SSLify_NonBlock_SSLDone($socket);
                if (!(Server_SSLify_NonBlock_ClientCertificateExists($socket))) {
                   $heap->{server}->{$wheel_id}->{wheel}->put("[".$wheel_id."] NoClientCertExists\n") if $canwrite;
                   return $kernel->yield("disconnect" => $wheel_id);
                } elsif(!(Server_SSLify_NonBlock_ClientCertIsValid($socket))) {
                  $heap->{server}->{$wheel_id}->{wheel}->put("[".$wheel_id."] ClientCertInvalid\n") if $canwrite;
                   return $kernel->yield("disconnect" => $wheel_id);
                } elsif(!(Server_SSLify_NonBlock_ClientCertVerifyAgainstCRL($socket, 'ca.crl'))) {
                   $heap->{server}->{$wheel_id}->{wheel}->put("[".$wheel_id."] CRL\n") if $canwrite;
                   return $kernel->yield("disconnect" => $wheel_id);
                }
                $heap->{server}->{$wheel_id}->{wheel}->put("[".$wheel_id."] Yeah! You're authenticated!\n") if $canwrite;
                $kernel->yield("disconnect" => $wheel_id);
             },
             disconnect => sub {
                my ($heap, $kernel, $wheel_id) = @_[HEAP, KERNEL, ARG0];
                $kernel->delay(close_delayed => 1, $wheel_id)
                   unless ($heap->{server}->{$wheel_id}->{disconnecting}++);
             },
             close_delayed => sub {
                my ($kernel, $heap, $wheel_id) = @_[KERNEL, HEAP, ARG0];
                delete $heap->{server}->{$wheel_id}->{wheel};
                delete $heap->{server}->{$wheel_id}->{socket};
             }
          }
       );
        
   $poe_kernel->run();

  STARRTTLS
    Starting version 0.40, you can do SSL after plain text. This is often
    called "STARTTLS", "AUTH TLS" or "AUTH SSL". Here an FTP example:

       use POE::Component::SSLify qw( SSLify_Options SSLify_GetCTX );
       use POE::Component::SSLify::NonBlock qw( Server_SSLify_NonBlock SSLify_Options_NonBlock_ClientCert Server_SSLify_NonBlock_ClientCertVerifyAgainstCRL Server_SSLify_NonBlock_ClientCertificateExists Server_SSLify_NonBlock_ClientCertIsValid Server_SSLify_NonBlock_SSLDone Server_SSLify_NonBlock_STARTTLS);
       use POE qw( Wheel::SocketFactory Driver::SysRW Filter::Stream Wheel::ReadWrite );

       eval { SSLify_Options( 'server.key', 'server.crt' ) };
       die "SSLify_Options: ".$@ if ( $@ );

       eval { SSLify_Options_NonBlock_ClientCert(SSLify_GetCTX(), 'ca.crt' ) };
       die "SSLify_Options_NonBlock_ClientCert: ".$@ if ( $@ );

       POE::Session->create(
          inline_states => {
             _start => sub {
                my ( $heap, $kernel ) = @_[ HEAP, KERNEL ];
                $heap->{server_wheel} = POE::Wheel::SocketFactory->new(
                   BindAddress  => "0.0.0.0",
                   BindPort     => 443,
                   Reuse        => 'yes',
                   SuccessEvent => 'client_accept',
                   FailureEvent => 'accept_failure',
                );
             },
             client_accept => sub {
                my ( $heap, $kernel, $socket ) = @_[ HEAP, KERNEL, ARG0 ];
                eval { $socket = Server_SSLify_NonBlock( SSLify_GetCTX(), $socket, {
                   debug => 1,
                   starttls => 1
                } ) };
                if ( $@ ) {
                   print "SSL Failed: ".$@."\n";
                   delete $heap->{server}->{$wheel_id}->{wheel};
                }
                my $io_wheel = POE::Wheel::ReadWrite->new(
                   Handle     => $socket,
                   Driver     => POE::Driver::SysRW->new,
                   Filter     => POE::Filter::Stream->new,
                   InputEvent => 'client_input'
                );
                $heap->{server}->{$io_wheel->ID()}->{wheel} = $io_wheel;
                $heap->{server}->{$io_wheel->ID()}->{socket} = $socket;
                $io_wheel->put("220 ProFTPD\r\n");
             },
             client_input => sub {
                my ( $heap, $kernel, $input, $wheel_id ) = @_[ HEAP, KERNEL, ARG0, ARG1 ];
                my $canwrite = exists $heap->{server}->{$wheel_id}->{wheel} &&
                                 (ref($heap->{server}->{$wheel_id}->{wheel}) eq "POE::Wheel::ReadWrite");
                my $socket = $heap->{server}->{$wheel_id}->{socket};
                if (($input =~ /TLS/i) ||
                    ($input =~ /SSL/i)) {
                   $heap->{server}->{$wheel_id}->{wheel}->put("220 starttls\r\n");
                   $heap->{server}->{$wheel_id}->{wheel}->flush();
                   Server_SSLify_NonBlock_STARTTLS($socket);
                }
                return unless Server_SSLify_NonBlock_SSLDone($socket);
                $heap->{server}->{$wheel_id}->{wheel}->put("220 Yeah! You're authenticated!\n") if ($canwrite && (!$heap->{server}->{$wheel_id}->{disconnecting}));
                $kernel->yield("disconnect" => $wheel_id);
             },
             disconnect => sub {
                my ($heap, $kernel, $wheel_id) = @_[HEAP, KERNEL, ARG0];
                $kernel->delay(close_delayed => 1, $wheel_id)
                   unless ($heap->{server}->{$wheel_id}->{disconnecting}++);
             },
             close_delayed => sub {
                my ($kernel, $heap, $wheel_id) = @_[KERNEL, HEAP, ARG0];
                delete $heap->{server}->{$wheel_id}->{wheel};
                delete $heap->{server}->{$wheel_id}->{socket};
             }
          }
       );
        
   $poe_kernel->run();

FUNCTIONS
  SSLify_Options_NonBlock_ClientCert($ctx, $cacrt)
    Configures ssl ctx(context) to request from the client a certificate for
    authentication, which is verificated against the configured CA in the
    file $cacrt.

       SSLify_Options_NonBlock_ClientCert(SSLify_GetCTX(), 'ca.crt');

    Note:

       SSLify_Options from POE::Component::SSLify must be called first!

  Server_SSLify_NonBlock($ctx, $socket, %$options)
    Similar to Server_SSLify from POE::Component::SSLify. It needs further
    the CTX of POE::Component::SSLify and a hash for special options:

       my $socket = shift;   # get the socket from somewhere
       $socket = Server_SSLify_NonBlock(SSLify_GetCTX(), $socket, { option1 => 1, option1 => 2,... });

    Options are:

       clientcertrequest
          The client gets requested for a client certificat during 
          ssl handshake

       noblockbadclientcert
          If the client does not provide a client certificate or the
          client certificate is untrusted, the connection will not
          be aborted. You can check for the errors via the functions
          Server_SSLify_NonBlock_ClientCertificateExists and
          Server_SSLify_NonBlock_ClientCertIsValid.

       debug
          Get debug messages during ssl handshake. Especially usefull
          for Server_SSLify_NonBlock_ClientCertVerifyAgainstCRL.

       getserial
          Request the serial of the client certificate during
          ssl handshake.
          
      WARNING: You have to patch Net::SSLeay to provide the
                   Net::SSLeay::X509_get_serialNumber function
                   before you can set the getserial option! See the
                   file net-ssleay-patch in the base path of the
                   tar.gz of the packet.

       starttls
          Don't actually do SSL but later. It is initiated if you
          call Server_SSLify_NonBlock_STARTTLS.

    Note:

       SSLify_Options from POE::Component::SSLify must be set first!

  Server_SSLify_NonBlock_SSLDone
    Checks if the SSL handshake has been completed.

       Server_SSLify_NonBlock_SSLDone($socket);

  Server_SSLify_NonBlock_ClientCertificateExists($socket)
    Verify if the client commited a valid client certificate.

      Server_SSLify_NonBlock_ClientCertificateExists($socket);

  Server_SSLify_NonBlock_ClientCertIsValid($socket)
    Verify if the client certificate is trusted by a loaded CA (see
    SSLify_Options_NonBlock_ClientCert).

      Server_SSLify_NonBlock_ClientCertIsValid($socket);

  Server_SSLify_NonBlock_STARTTLS($socket)
    Initiates SSL after plain text conversation. You have to use the
    starttls option in Server_SSLify_NonBlock.

      Server_SSLify_NonBlock_STARTTLS($socket)

    See STARTTLS example above.

  Server_SSLify_NonBlock_ClientCertVerifyAgainstCRL($socket, $crlfile)
    Opens a CRL file, and verify if the serial of the client certificate is
    not contained in the CRL file. No file caching is done, each call opens
    the file again.

    Note: If your CRL file is missing, can not be opened is empty, or has no
    blocked certificate at all, every call will get blocked.

      Server_SSLify_NonBlock_ClientCertVerifyAgainstCRL($socket, 'ca.crl');
      
   WARNING: You have to patch Net::SSLeay to provide the
                Net::SSLeay::verify_serial_against_crl_file function
                before you can set the getserial option! See the
                file net-ssleay-patch in the base path of the tar.gz
                of the packet.

  Server_SSLify_NonBlock_GetClientCertificateIDs($socket)
    Fetches the IDs as array of the clients certifcate and its signees.

    Retruns empty list if you did not patch Net::SSLeay.

  hexdump($string)
    Returns string data in hex format.

    For example:

      perl -e 'use POE::Component::SSLify::NonBlock; print POE::Component::SSLify::NonBlock::hexdump("test")."\n";'
      74:65:73:74

  Futher functions...
    You can use all functions from POE::Component::SSLify !

NOTES
  Based on POE::Component::SSLify
    This module is based on POE::Component::SSLify, so
    POE::Component::SSLify::NonBlock has the same issues.

EXPORT
    Puts all of the above functions in @EXPORT_OK so you have to request
    them directly

BUGS
  Server_SSLify_NonBlock_ClientCertVerifyAgainstCRL: certificate serials
    Server_SSLify_NonBlock_ClientCertVerifyAgainstCRL also verifies against
    the serial of the CA! Make sure that you never use the serial of the CA
    for client certificates!

  Win32
    I did not test POE::Component::SSLify::NonBlock on Win32 platforms at
    all!

SEE ALSO
    POE::Component::SSLify

    Net::SSLeay

AUTHOR
    pRiVi <pRiVi@cpan.org>

PROPS
    This code is based on Apocalypse module POE::Component::SSLify, improved
    by client certification code and non-blocking sockets.

    Copyright 2010 by Markus Mueller/Apocalypse/Rocco Caputo/Dariusz
    Jackowski.

COPYRIGHT AND LICENSE
    Copyright 2010 by Markus Mueller

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