package Danga::Socket::SSL; use strict; use Danga::Socket 1.54; use base 'Danga::Socket'; use Net::SSLeay; Net::SSLeay::load_error_strings(); Net::SSLeay::SSLeay_add_ssl_algorithms(); Net::SSLeay::randomize(); use constant SSL_ERROR_WANT_READ => 2; use constant SSL_ERROR_WANT_WRITE => 3; use constant POLLIN => 1; use constant POLLOUT => 4; use fields ( 'ssl_state', # our SSL state 'ssl_write_when_readable', ); # optionally override this sub get_ssl_context { my Danga::Socket::SSL $self = shift; my $ctx = Net::SSLeay::CTX_new() or die("Failed to create SSL_CTX $!"); #$Net::SSLeay::ssl_version = 10; # Insist on TLSv1 #$Net::SSLeay::ssl_version = 3; # Insist on SSLv3 Net::SSLeay::CTX_set_options($ctx, &Net::SSLeay::OP_ALL) and Net::SSLeay::die_if_ssl_error("ssl ctx set options"); Net::SSLeay::CTX_set_mode($ctx, 1) # enable partial writes and Net::SSLeay::die_if_ssl_error("ssl ctx set options"); # Following will ask password unless private key is not encrypted Net::SSLeay::CTX_use_RSAPrivateKey_file ($ctx, $self->ssl_private_key_file, &Net::SSLeay::FILETYPE_PEM); Net::SSLeay::die_if_ssl_error("private key"); Net::SSLeay::CTX_use_certificate_file ($ctx, $self->ssl_cert_file, &Net::SSLeay::FILETYPE_PEM); Net::SSLeay::die_if_ssl_error("certificate"); return $ctx; } # override this: sub ssl_private_key_file { my Danga::Socket::SSL $self = shift; return "/dev/null"; } # override this: sub ssl_cert_file { my Danga::Socket::SSL $self = shift; return "/dev/null"; } sub start_ssl { my Danga::Socket::SSL $self = shift; my $ctx = $self->get_ssl_context or die "No SSL context returned"; my $ssl = Net::SSLeay::new($ctx) or die_now("Failed to create SSL $!"); $self->{ssl_state} = $ssl; # Net::SSLeay::set_verify($ssl, Net::SSLeay::VERIFY_PEER(), 0); my $fileno = $self->{sock}->fileno; warn "setting ssl ($ssl) fileno to $fileno\n"; Net::SSLeay::set_fd($ssl, $fileno); #$Net::SSLeay::trace = 2; #Net::SSLeay::connect($ssl) or Net::SSLeay::die_now("Failed SSL connect ($!)"); my $rv = Net::SSLeay::accept($ssl); if (!$rv) { warn "SSL accept error on $self\n"; $self->close("ssl_accept_error"); return; } warn "$self: Cipher `" . Net::SSLeay::get_cipher($ssl) . "'\n"; $self->set_writer_func(_danga_socket_writerfunc($self)); } sub _danga_socket_writerfunc { my Danga::Socket::SSL $conn = shift; my $ssl = $conn->{ssl_state}; return sub { my ($bref, $to_write, $offset) = @_; # unless our event_read has been called, we don't want to try # to do any work now. and probably we should complain. if ($conn->{ssl_write_when_readable}) { warn "writer func called when we're waiting for readability first.\n"; return 0; } # we can't write a lot or we get some SSL non-blocking error. # NO LONGER RELEVANT? # $to_write = 4096 if $to_write > 4096; my $str = substr($$bref, $offset, $to_write); my $written = Net::SSLeay::write($ssl, $str); if ($written == -1) { my $err = Net::SSLeay::get_error($ssl, $written); if ($err == SSL_ERROR_WANT_READ) { $conn->write_when_readable; return 0; } if ($err == SSL_ERROR_WANT_WRITE) { # unclear here. it just wants to write some more? okay. # easy enough. do nothing? return 0; } my $errstr = Net::SSLeay::ERR_error_string($err); warn " SSL write err = $err, $errstr\n"; Net::SSLeay::print_errs("SSL_write"); $conn->close('ssl_write_error'); return 0; } return $written; }; } sub is_ssl { my Danga::Socket::SSL $self = shift; return $self->{ssl_state} ? 1 : 0; } # return SSL state object. more useful as a boolean if conn is in SSL mode. sub ssl_state { my Danga::Socket::SSL $self = shift; return $self->{ssl_state}; } # called by SSL machinery to let us know a write is stalled on readability. # so we need to (at least temporarily) go readable and then process writes. sub write_when_readable { my $self = shift; # enable readability, but remember old value so we can pop it back my $prev_readable = ($self->{event_watch} & POLLIN) ? 1 : 0; $self->watch_read(1); $self->{ssl_write_when_readable} = [ $prev_readable ]; # don't need to push/pop its state because Danga::Socket->write, called later, # will do the one final write, or if not all written, will turn on watch_write $self->watch_write(0); } # called by Danga::Socket when a write doesn't fully go through. by default it # enables writability. but we want to do nothing if we're waiting for a read for SSL sub on_incomplete_write { my $self = shift; return if $self->{ssl_write_when_readable}; $self->SUPER::on_incomplete_write; } # overloaded read... sub read { my Danga::Socket::SSL $self = $_[0]; my $amount = $_[1]; my $ssl = $self->{ssl_state}; return $self->SUPER::read($amount) unless $ssl; my $data = Net::SSLeay::read($ssl); my $errs = Net::SSLeay::print_errs('SSL_read'); if ($errs) { warn "SSL Read error: $errs\n"; $self->close; return; } return undef if defined $data && ! length $data; # Net::SSLeays buffers internally, so if we didn't read anything, it's # in its buffer $data = "" unless $data && length $data; return \$data; } # if returns true, continue to call event read, else skip event_read. sub cond_event_read { my Danga::Socket::SSL $self = shift; # for async SSL: if a session renegotation is in progress, # our previous write wants us to become readable first. # we then go back into the write path (by flushing the write # buffer) and it then does a read on this socket. if (my $ar = $self->{ssl_write_when_readable}) { $self->{ssl_write_when_readable} = 0; $self->watch_read($ar->[0]); # restore previous readability state $self->watch_write(1); return 0; } $self->event_read; } 1;