File Coverage

blib/lib/POE/Component/SSLify/ServerHandle.pm
Criterion Covered Total %
statement 69 84 82.1
branch 33 50 66.0
condition 2 3 66.6
subroutine 11 13 84.6
pod n/a
total 115 150 76.6


line stmt bran cond sub pod time code
1             #
2             # This file is part of POE-Component-SSLify
3             #
4             # This software is copyright (c) 2014 by Apocalypse.
5             #
6             # This is free software; you can redistribute it and/or modify it under
7             # the same terms as the Perl 5 programming language system itself.
8             #
9 13     13   5384 use strict; use warnings;
  13     13   113  
  13         363  
  13         48  
  13         14  
  13         699  
10             package POE::Component::SSLify::ServerHandle;
11             $POE::Component::SSLify::ServerHandle::VERSION = '1.011';
12             our $AUTHORITY = 'cpan:APOCAL';
13              
14             # ABSTRACT: Server-side handle for SSLify
15              
16             # Import the SSL death routines
17 13     13   52 use Net::SSLeay 1.36 qw( die_now die_if_ssl_error ERROR_WANT_READ ERROR_WANT_WRITE );
  13         200  
  13         9984  
18              
19             # Ties the socket
20             sub TIEHANDLE {
21 29     29   58 my ( $class, $socket, $ctx, $connref ) = @_;
22              
23 29 50       490 my $ssl = Net::SSLeay::new( $ctx ) or die_now( "Failed to create SSL $!" );
24              
25 29         56 my $fileno = fileno( $socket );
26              
27 29         205 Net::SSLeay::set_fd( $ssl, $fileno );
28              
29             # Socket is in non-blocking mode, so accept() will return immediately.
30             # die_if_ssl_error won't die on non-blocking errors. We don't need to call accept()
31             # again, because OpenSSL I/O functions (read, write, ...) can handle that entirely
32             # by self (it's needed to accept() once to determine connection type).
33 29 50       2958 my $res = Net::SSLeay::accept( $ssl ) and die_if_ssl_error( 'ssl accept' );
34              
35 29         484 my $self = bless {
36             'ssl' => $ssl,
37             'ctx' => $ctx,
38             'socket' => $socket,
39             'fileno' => $fileno,
40             'status' => $res,
41             'on_connect' => $connref,
42             'ssl_started' => 0,
43             }, $class;
44              
45 29         184 return $self;
46             }
47              
48             # TODO should we make a convenience function to convert retval to string equivalents for easier debugging?
49             # From OpenSSL 1.0.0d
50             #define SSL_ERROR_NONE 0
51             #define SSL_ERROR_SSL 1
52             #define SSL_ERROR_WANT_READ 2
53             #define SSL_ERROR_WANT_WRITE 3
54             #define SSL_ERROR_WANT_X509_LOOKUP 4
55             #define SSL_ERROR_SYSCALL 5 /* look at error stack/return value/errno */
56             #define SSL_ERROR_ZERO_RETURN 6
57             #define SSL_ERROR_WANT_CONNECT 7
58             #define SSL_ERROR_WANT_ACCEPT 8
59              
60             sub _check_status {
61 169     169   203 my $self = shift;
62              
63             # Okay, is negotiation done?
64             # http://www.openssl.org/docs/ssl/SSL_connect.html#RETURN_VALUES
65 169 100       324 if ( exists $self->{'client'} ) {
66 129         11438 $self->{'status'} = Net::SSLeay::connect( $self->{'ssl'} );
67             } else {
68 40         103530 $self->{'status'} = Net::SSLeay::accept( $self->{'ssl'} );
69             }
70              
71 169 100       652 if ( $self->{'status'} <= 0 ) {
    50          
72             # http://www.openssl.org/docs/ssl/SSL_get_error.html
73 113         397 my $errval = Net::SSLeay::get_error( $self->{'ssl'}, $self->{'status'} );
74              
75             # Handle the case of ERROR_WANT_READ and ERROR_WANT_WRITE
76             # TODO should we skip ERROR_WANT_ACCEPT and ERROR_WANT_CONNECT ?
77             # also, ERROR_WANT_ACCEPT isn't exported by Net::SSLeay, huh?
78 113 100 66     2982 if ( $errval == ERROR_WANT_READ or $errval == ERROR_WANT_WRITE ) {
79             # continue reading/writing from the socket until we connect or not...
80 111         1765 return 1;
81             } else {
82             # call the hook function for error connect
83 2 50       388 if ( defined $self->{'on_connect'} ) {
84 2         9 $self->{'on_connect'}->( $self->{'orig_socket'}, 0, $errval );
85             }
86              
87             # don't try to read/write from the socket anymore!
88 2         1821 return 0;
89             }
90             } elsif ( $self->{'status'} == 1 ) {
91             # SSL handshake is done!
92 56         138 $self->{'ssl_started'} = 1;
93              
94             # call the hook function for successful connect
95 56 100       155 if ( defined $self->{'on_connect'} ) {
96 4         25 $self->{'on_connect'}->( $self->{'orig_socket'}, 1 );
97             }
98              
99             # we can now read/write from the socket!
100 56         1025 return 1;
101             }
102             }
103              
104             # Read something from the socket
105             sub READ {
106             # Get ourself!
107 2909     2909   436448 my $self = shift;
108              
109             # Get the pointers to buffer, length, and the offset
110 2909         4096 my( $buf, $len, $offset ) = \( @_ );
111              
112             # Check the status of the SSL handshake
113 2909 100       6942 if ( ! $self->{'ssl_started'} ) {
114 43 100       144 return if $self->_check_status == 0;
115             }
116              
117             # If we have no offset, replace the buffer with some input
118 2907 50       5091 if ( ! defined $$offset ) {
119 2907         650572 $$buf = Net::SSLeay::read( $self->{'ssl'}, $$len );
120              
121             # Are we done?
122 2907 100       6432 if ( defined $$buf ) {
123             # TODO do we need the same "flush is success" logic in WRITE?
124              
125 2860         9468 return length( $$buf );
126             } else {
127             # Nah, clear the buffer too...
128 47         78 $$buf = "";
129 47         188 return;
130             }
131             }
132              
133             # Now, actually read the data
134 0 0       0 defined( my $read = Net::SSLeay::read( $self->{'ssl'}, $$len ) ) or return;
135              
136             # TODO do we need the same "flush is success" logic in WRITE?
137              
138             # Figure out the buffer and offset
139 0         0 my $buf_len = length( $$buf );
140              
141             # If our offset is bigger, pad the buffer
142 0 0       0 if ( $$offset > $buf_len ) {
143 0         0 $$buf .= chr( 0 ) x ( $$offset - $buf_len );
144             }
145              
146             # Insert what we just read into the buffer
147 0         0 substr( $$buf, $$offset, 1, $read );
148              
149             # All done!
150 0         0 return length( $read );
151             }
152              
153             # Write some stuff to the socket
154             sub WRITE {
155             # Get ourself + buffer + length + offset to write
156 3203     3203   71085 my( $self, $buf, $len, $offset ) = @_;
157              
158             # Check the status of the SSL handshake
159 3203 100       5516 if ( ! $self->{'ssl_started'} ) {
160             # The normal syswrite() POE uses expects 0 here.
161 126 50       244 return 0 if $self->_check_status == 0;
162             }
163              
164             # If we have nothing to offset, then start from the beginning
165 3203 50       4989 if ( ! defined $offset ) {
166 0         0 $offset = 0;
167             }
168              
169             # Thanks to RT#95071 and RT#58243 we need to clamp the length to the TLS 16K limit
170             # seems like the same thing happened to https://www.mail-archive.com/openssl-users@openssl.org/msg28151.html
171 3203 100       4668 $len = 16_384 if $len > 16_384;
172              
173             # We count the number of characters written to the socket
174 3203         597578 my $wrote_len = Net::SSLeay::write( $self->{'ssl'}, substr( $buf, $offset, $len ) );
175              
176             # Did we get an error or number of bytes written?
177             # Net::SSLeay::write() returns the number of bytes written, or 0 on unsuccessful
178             # operation (probably connection closed), or -1 on error.
179 3203 100       7422 if ( $wrote_len < 0 ) {
180             # The normal syswrite() POE uses expects 0 here.
181 371         988 return 0;
182             } else {
183             # We flushed some data, which means we finished the handshake!
184             # This is IMPORTANT, as MIRE found out!
185             # Otherwise openssl will zonk out and give us SSL_ERROR_SSL and things randomly break :(
186             # this is because we tried to connect() or accept() and the handshake was done... or something like that hah
187 2832 50       5429 if ( ! $self->{'ssl_started'} ) {
188 0         0 $self->{'ssl_started'} = 1;
189 0         0 $self->{'status'} = 1;
190              
191             # call the hook function for successful connect
192 0 0       0 if ( defined $self->{'on_connect'} ) {
193 0         0 $self->{'on_connect'}->( $self->{'orig_socket'}, 1 );
194             }
195             }
196              
197             # All done!
198 2832         7766 return $wrote_len;
199             }
200             }
201              
202             # Sets binmode on the socket
203             # Thanks to RT #27117
204             sub BINMODE {
205 58     58   817 my $self = shift;
206 58 50       154 if (@_) {
207 0         0 my $mode = shift;
208 0         0 binmode $self->{'socket'}, $mode;
209             } else {
210 58         172 binmode $self->{'socket'};
211             }
212              
213 58         128 return;
214             }
215              
216             # Closes the socket
217             sub CLOSE {
218 58     58   68 my $self = shift;
219 58 50       137 if ( defined $self->{'socket'} ) {
220 58         2397 Net::SSLeay::free( $self->{'ssl'} );
221              
222             # TODO we ignore any close errors because there's no way to sanely propagate it up the stack...
223 58         2553 close( $self->{'socket'} ); ## no critic ( InputOutput::RequireCheckedClose )
224 58         133 undef $self->{'socket'};
225              
226             # do we need to do CTX_free?
227 58 100       293 if ( exists $self->{'client'} ) {
228 29         444 Net::SSLeay::CTX_free( $self->{'ctx'} );
229             }
230             }
231              
232 58         111 return 1;
233             }
234              
235             # Add DESTROY handler
236             sub DESTROY {
237 58     58   2375 my $self = shift;
238              
239             # Did we already CLOSE?
240 58 50       272 if ( defined $self->{'socket'} ) {
241             # Guess not...
242 58         168 $self->CLOSE();
243             }
244              
245 58         356 return;
246             }
247              
248             sub FILENO {
249 1234     1234   100620 my $self = shift;
250 1234         2590 return $self->{'fileno'};
251             }
252              
253             # Not implemented TIE's
254             sub READLINE {
255 0     0     die 'Not Implemented';
256             }
257              
258             sub PRINT {
259 0     0     die 'Not Implemented';
260             }
261              
262             1;
263              
264             __END__