| 1 | package Danga::Socket::SSL; |
|---|
| 2 | use strict; |
|---|
| 3 | use Danga::Socket 1.54; |
|---|
| 4 | use base 'Danga::Socket'; |
|---|
| 5 | use Net::SSLeay; |
|---|
| 6 | |
|---|
| 7 | Net::SSLeay::load_error_strings(); |
|---|
| 8 | Net::SSLeay::SSLeay_add_ssl_algorithms(); |
|---|
| 9 | Net::SSLeay::randomize(); |
|---|
| 10 | |
|---|
| 11 | use constant SSL_ERROR_WANT_READ => 2; |
|---|
| 12 | use constant SSL_ERROR_WANT_WRITE => 3; |
|---|
| 13 | |
|---|
| 14 | use constant POLLIN => 1; |
|---|
| 15 | use constant POLLOUT => 4; |
|---|
| 16 | |
|---|
| 17 | use fields ( |
|---|
| 18 | 'ssl_state', # our SSL state |
|---|
| 19 | 'ssl_write_when_readable', |
|---|
| 20 | ); |
|---|
| 21 | |
|---|
| 22 | # optionally override this |
|---|
| 23 | sub get_ssl_context { |
|---|
| 24 | my Danga::Socket::SSL $self = shift; |
|---|
| 25 | |
|---|
| 26 | my $ctx = Net::SSLeay::CTX_new() |
|---|
| 27 | or die("Failed to create SSL_CTX $!"); |
|---|
| 28 | |
|---|
| 29 | #$Net::SSLeay::ssl_version = 10; # Insist on TLSv1 |
|---|
| 30 | #$Net::SSLeay::ssl_version = 3; # Insist on SSLv3 |
|---|
| 31 | |
|---|
| 32 | Net::SSLeay::CTX_set_options($ctx, &Net::SSLeay::OP_ALL) |
|---|
| 33 | and Net::SSLeay::die_if_ssl_error("ssl ctx set options"); |
|---|
| 34 | |
|---|
| 35 | Net::SSLeay::CTX_set_mode($ctx, 1) # enable partial writes |
|---|
| 36 | and Net::SSLeay::die_if_ssl_error("ssl ctx set options"); |
|---|
| 37 | |
|---|
| 38 | # Following will ask password unless private key is not encrypted |
|---|
| 39 | Net::SSLeay::CTX_use_RSAPrivateKey_file ($ctx, $self->ssl_private_key_file, |
|---|
| 40 | &Net::SSLeay::FILETYPE_PEM); |
|---|
| 41 | Net::SSLeay::die_if_ssl_error("private key"); |
|---|
| 42 | |
|---|
| 43 | Net::SSLeay::CTX_use_certificate_file ($ctx, $self->ssl_cert_file, |
|---|
| 44 | &Net::SSLeay::FILETYPE_PEM); |
|---|
| 45 | Net::SSLeay::die_if_ssl_error("certificate"); |
|---|
| 46 | return $ctx; |
|---|
| 47 | } |
|---|
| 48 | |
|---|
| 49 | # override this: |
|---|
| 50 | sub ssl_private_key_file { |
|---|
| 51 | my Danga::Socket::SSL $self = shift; |
|---|
| 52 | return "/dev/null"; |
|---|
| 53 | } |
|---|
| 54 | |
|---|
| 55 | # override this: |
|---|
| 56 | sub ssl_cert_file { |
|---|
| 57 | my Danga::Socket::SSL $self = shift; |
|---|
| 58 | return "/dev/null"; |
|---|
| 59 | } |
|---|
| 60 | |
|---|
| 61 | sub start_ssl { |
|---|
| 62 | my Danga::Socket::SSL $self = shift; |
|---|
| 63 | |
|---|
| 64 | my $ctx = $self->get_ssl_context |
|---|
| 65 | or die "No SSL context returned"; |
|---|
| 66 | |
|---|
| 67 | my $ssl = Net::SSLeay::new($ctx) or die_now("Failed to create SSL $!"); |
|---|
| 68 | $self->{ssl_state} = $ssl; |
|---|
| 69 | |
|---|
| 70 | # Net::SSLeay::set_verify($ssl, Net::SSLeay::VERIFY_PEER(), 0); |
|---|
| 71 | |
|---|
| 72 | my $fileno = $self->{sock}->fileno; |
|---|
| 73 | warn "setting ssl ($ssl) fileno to $fileno\n"; |
|---|
| 74 | Net::SSLeay::set_fd($ssl, $fileno); |
|---|
| 75 | |
|---|
| 76 | #$Net::SSLeay::trace = 2; |
|---|
| 77 | #Net::SSLeay::connect($ssl) or Net::SSLeay::die_now("Failed SSL connect ($!)"); |
|---|
| 78 | |
|---|
| 79 | my $rv = Net::SSLeay::accept($ssl); |
|---|
| 80 | if (!$rv) { |
|---|
| 81 | warn "SSL accept error on $self\n"; |
|---|
| 82 | $self->close("ssl_accept_error"); |
|---|
| 83 | return; |
|---|
| 84 | } |
|---|
| 85 | |
|---|
| 86 | warn "$self: Cipher `" . Net::SSLeay::get_cipher($ssl) . "'\n"; |
|---|
| 87 | |
|---|
| 88 | $self->set_writer_func(_danga_socket_writerfunc($self)); |
|---|
| 89 | } |
|---|
| 90 | |
|---|
| 91 | sub _danga_socket_writerfunc { |
|---|
| 92 | my Danga::Socket::SSL $conn = shift; |
|---|
| 93 | my $ssl = $conn->{ssl_state}; |
|---|
| 94 | return sub { |
|---|
| 95 | my ($bref, $to_write, $offset) = @_; |
|---|
| 96 | |
|---|
| 97 | # unless our event_read has been called, we don't want to try |
|---|
| 98 | # to do any work now. and probably we should complain. |
|---|
| 99 | if ($conn->{ssl_write_when_readable}) { |
|---|
| 100 | warn "writer func called when we're waiting for readability first.\n"; |
|---|
| 101 | return 0; |
|---|
| 102 | } |
|---|
| 103 | |
|---|
| 104 | # we can't write a lot or we get some SSL non-blocking error. |
|---|
| 105 | # NO LONGER RELEVANT? |
|---|
| 106 | # $to_write = 4096 if $to_write > 4096; |
|---|
| 107 | |
|---|
| 108 | my $str = substr($$bref, $offset, $to_write); |
|---|
| 109 | my $written = Net::SSLeay::write($ssl, $str); |
|---|
| 110 | |
|---|
| 111 | if ($written == -1) { |
|---|
| 112 | my $err = Net::SSLeay::get_error($ssl, $written); |
|---|
| 113 | |
|---|
| 114 | if ($err == SSL_ERROR_WANT_READ) { |
|---|
| 115 | $conn->write_when_readable; |
|---|
| 116 | return 0; |
|---|
| 117 | } |
|---|
| 118 | if ($err == SSL_ERROR_WANT_WRITE) { |
|---|
| 119 | # unclear here. it just wants to write some more? okay. |
|---|
| 120 | # easy enough. do nothing? |
|---|
| 121 | return 0; |
|---|
| 122 | } |
|---|
| 123 | |
|---|
| 124 | my $errstr = Net::SSLeay::ERR_error_string($err); |
|---|
| 125 | warn " SSL write err = $err, $errstr\n"; |
|---|
| 126 | Net::SSLeay::print_errs("SSL_write"); |
|---|
| 127 | $conn->close('ssl_write_error'); |
|---|
| 128 | return 0; |
|---|
| 129 | } |
|---|
| 130 | |
|---|
| 131 | return $written; |
|---|
| 132 | }; |
|---|
| 133 | } |
|---|
| 134 | |
|---|
| 135 | sub is_ssl { |
|---|
| 136 | my Danga::Socket::SSL $self = shift; |
|---|
| 137 | return $self->{ssl_state} ? 1 : 0; |
|---|
| 138 | } |
|---|
| 139 | |
|---|
| 140 | # return SSL state object. more useful as a boolean if conn is in SSL mode. |
|---|
| 141 | sub ssl_state { |
|---|
| 142 | my Danga::Socket::SSL $self = shift; |
|---|
| 143 | return $self->{ssl_state}; |
|---|
| 144 | } |
|---|
| 145 | |
|---|
| 146 | # called by SSL machinery to let us know a write is stalled on readability. |
|---|
| 147 | # so we need to (at least temporarily) go readable and then process writes. |
|---|
| 148 | sub write_when_readable { |
|---|
| 149 | my $self = shift; |
|---|
| 150 | |
|---|
| 151 | # enable readability, but remember old value so we can pop it back |
|---|
| 152 | my $prev_readable = ($self->{event_watch} & POLLIN) ? 1 : 0; |
|---|
| 153 | $self->watch_read(1); |
|---|
| 154 | $self->{ssl_write_when_readable} = [ $prev_readable ]; |
|---|
| 155 | |
|---|
| 156 | # don't need to push/pop its state because Danga::Socket->write, called later, |
|---|
| 157 | # will do the one final write, or if not all written, will turn on watch_write |
|---|
| 158 | $self->watch_write(0); |
|---|
| 159 | } |
|---|
| 160 | |
|---|
| 161 | # called by Danga::Socket when a write doesn't fully go through. by default it |
|---|
| 162 | # enables writability. but we want to do nothing if we're waiting for a read for SSL |
|---|
| 163 | sub on_incomplete_write { |
|---|
| 164 | my $self = shift; |
|---|
| 165 | return if $self->{ssl_write_when_readable}; |
|---|
| 166 | $self->SUPER::on_incomplete_write; |
|---|
| 167 | } |
|---|
| 168 | |
|---|
| 169 | # overloaded read... |
|---|
| 170 | sub read { |
|---|
| 171 | my Danga::Socket::SSL $self = $_[0]; |
|---|
| 172 | my $amount = $_[1]; |
|---|
| 173 | |
|---|
| 174 | my $ssl = $self->{ssl_state}; |
|---|
| 175 | return $self->SUPER::read($amount) unless $ssl; |
|---|
| 176 | |
|---|
| 177 | my $data = Net::SSLeay::read($ssl); |
|---|
| 178 | my $errs = Net::SSLeay::print_errs('SSL_read'); |
|---|
| 179 | if ($errs) { |
|---|
| 180 | warn "SSL Read error: $errs\n"; |
|---|
| 181 | $self->close; |
|---|
| 182 | return; |
|---|
| 183 | } |
|---|
| 184 | |
|---|
| 185 | return undef if defined $data && ! length $data; |
|---|
| 186 | |
|---|
| 187 | # Net::SSLeays buffers internally, so if we didn't read anything, it's |
|---|
| 188 | # in its buffer |
|---|
| 189 | $data = "" unless $data && length $data; |
|---|
| 190 | return \$data; |
|---|
| 191 | } |
|---|
| 192 | |
|---|
| 193 | # if returns true, continue to call event read, else skip event_read. |
|---|
| 194 | sub cond_event_read { |
|---|
| 195 | my Danga::Socket::SSL $self = shift; |
|---|
| 196 | |
|---|
| 197 | # for async SSL: if a session renegotation is in progress, |
|---|
| 198 | # our previous write wants us to become readable first. |
|---|
| 199 | # we then go back into the write path (by flushing the write |
|---|
| 200 | # buffer) and it then does a read on this socket. |
|---|
| 201 | if (my $ar = $self->{ssl_write_when_readable}) { |
|---|
| 202 | $self->{ssl_write_when_readable} = 0; |
|---|
| 203 | $self->watch_read($ar->[0]); # restore previous readability state |
|---|
| 204 | $self->watch_write(1); |
|---|
| 205 | return 0; |
|---|
| 206 | } |
|---|
| 207 | |
|---|
| 208 | $self->event_read; |
|---|
| 209 | } |
|---|
| 210 | |
|---|
| 211 | |
|---|
| 212 | 1; |
|---|