root/trunk/lib/Danga/Socket/SSL.pm @ 1

Revision 1, 6.2 kB (checked in by bradfitz, 3 years ago)

first cut at abstracting djabberd's SSL code into something
generically usable by danga::socket servers.

Line 
1package Danga::Socket::SSL;
2use strict;
3use Danga::Socket 1.54;
4use base 'Danga::Socket';
5use Net::SSLeay;
6
7Net::SSLeay::load_error_strings();
8Net::SSLeay::SSLeay_add_ssl_algorithms();
9Net::SSLeay::randomize();
10
11use constant SSL_ERROR_WANT_READ     => 2;
12use constant SSL_ERROR_WANT_WRITE    => 3;
13
14use constant POLLIN        => 1;
15use constant POLLOUT       => 4;
16
17use fields (
18            '_ssl',  # our SSL state
19            '_write_when_readable',
20            );
21
22# optionally override this
23sub 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:
50sub ssl_private_key_file {
51    my Danga::Socket::SSL $self = shift;
52    return "/dev/null";
53}
54
55# override this:
56sub ssl_cert_file {
57    my Danga::Socket::SSL $self = shift;
58    return "/dev/null";
59}
60
61sub 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} = $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
91sub _danga_socket_writerfunc {
92    my Danga::Socket::SSL $conn = shift;
93    my $ssl = $conn->{_ssl};
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->{_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# return SSL state object.  more useful as a boolean if conn is in SSL mode.
136sub ssl_state {
137    my Danga::Socket::SSL $self = shift;
138    return $self->{_ssl};
139}
140
141# called by SSL machinery to let us know a write is stalled on readability.
142# so we need to (at least temporarily) go readable and then process writes.
143sub write_when_readable {
144    my $self = shift;
145
146    # enable readability, but remember old value so we can pop it back
147    my $prev_readable = ($self->{event_watch} & POLLIN)  ? 1 : 0;
148    $self->watch_read(1);
149    $self->{_write_when_readable} = [ $prev_readable ];
150
151    # don't need to push/pop its state because Danga::Socket->write, called later,
152    # will do the one final write, or if not all written, will turn on watch_write
153    $self->watch_write(0);
154}
155
156# called by Danga::Socket when a write doesn't fully go through.  by default it
157# enables writability.  but we want to do nothing if we're waiting for a read for SSL
158sub on_incomplete_write {
159    my $self = shift;
160    return if $self->{_write_when_readable};
161    $self->SUPER::on_incomplete_write;
162}
163
164# overloaded read...
165sub read {
166    my Danga::Socket::SSL $self = $_[0];
167    my $amount = $_[1];
168
169    my $ssl = $self->{_ssl};
170    return $self->SUPER::read($amount) unless $ssl;
171
172    my $data = Net::SSLeay::read($ssl);
173    my $errs = Net::SSLeay::print_errs('SSL_read');
174    if ($errs) {
175        warn "SSL Read error: $errs\n";
176        $self->close;
177        return;
178    }
179
180    use Data::Dumper;
181    print Dumper([$data, $errs]);
182
183    return undef if defined $data && ! length $data;
184
185    # Net::SSLeays buffers internally, so if we didn't read anything, it's
186    # in its buffer
187    $data = "" unless $data && length $data;
188    return \$data;
189}
190
191# if returns true, continue to call event read, else skip event_read.
192sub cond_event_read {
193    my Danga::Socket::SSL $self = shift;
194
195    # for async SSL:  if a session renegotation is in progress,
196    # our previous write wants us to become readable first.
197    # we then go back into the write path (by flushing the write
198    # buffer) and it then does a read on this socket.
199    if (my $ar = $self->{_write_when_readable}) {
200        $self->{_write_when_readable} = 0;
201        $self->watch_read($ar->[0]);  # restore previous readability state
202        $self->watch_write(1);
203        return 0;
204    }
205
206    $self->event_read;
207}
208
209
2101;
Note: See TracBrowser for help on using the browser.