File Coverage

blib/lib/Net/Server/Proto/SSLEAY.pm
Criterion Covered Total %
statement 220 309 71.2
branch 76 154 49.3
condition 18 64 28.1
subroutine 32 42 76.1
pod 6 32 18.7
total 352 601 58.5


line stmt bran cond sub pod time code
1             # -*- perl -*-
2             #
3             # Net::Server::Proto::SSLEAY - Net::Server Protocol module
4             #
5             # Copyright (C) 2010-2017
6             #
7             # Paul Seamons
8             #
9             # This package may be distributed under the terms of either the
10             # GNU General Public License
11             # or the
12             # Perl Artistic License
13             #
14             # All rights reserved.
15             #
16             ################################################################
17              
18             package Net::Server::Proto::SSLEAY;
19              
20 3     3   69081 use strict;
  3         27  
  3         136  
21 3     3   19 use warnings;
  3         6  
  3         161  
22 3     3   19 use IO::Socket::INET;
  3         7  
  3         143  
23 3     3   2398 use Fcntl ();
  3         44  
  3         101  
24 3     3   21 use Errno ();
  3         5  
  3         47  
25 3     3   28 use Socket ();
  3         7  
  3         494  
26              
27             BEGIN {
28 3 50   3   14 eval { require Net::SSLeay; 1 }
  3         15  
  3         15  
29             or warn "Module Net::SSLeay is required for SSLeay.";
30 3         9 for my $sub (qw(load_error_strings SSLeay_add_ssl_algorithms ENGINE_load_builtin_engines ENGINE_register_all_complete randomize)) {
31 15         5764 Net::SSLeay->can($sub)->();
32             }
33 3 50       3399 eval { [Fcntl::F_GETFL(), Fcntl::F_SETFL(), Fcntl::O_NONBLOCK()] } || die "Could not access Fcntl constant while loading ".__PACKAGE__.": $@";
  3         11397  
34             }
35              
36             our @ISA = qw(IO::Socket::INET);
37             our $AUTOLOAD;
38              
39             my @ssl_args = qw(
40             SSL_use_cert
41             SSL_verify_mode
42             SSL_key_file
43             SSL_cert_file
44             SSL_ca_path
45             SSL_ca_file
46             SSL_cipher_list
47             SSL_passwd_cb
48             SSL_max_getline_length
49             SSL_error_callback
50             );
51              
52 18     18 0 92 sub NS_proto { 'SSLEAY' }
53 21 100   21 0 49 sub NS_port { my $sock = shift; ${*$sock}{'NS_port'} = shift if @_; return ${*$sock}{'NS_port'} }
  21         56  
  7         21  
  21         33  
  21         66  
54 19 100   19 0 40 sub NS_host { my $sock = shift; ${*$sock}{'NS_host'} = shift if @_; return ${*$sock}{'NS_host'} }
  19         44  
  7         27  
  19         34  
  19         61  
55 19 100   19 0 416 sub NS_ipv { my $sock = shift; ${*$sock}{'NS_ipv'} = shift if @_; return ${*$sock}{'NS_ipv'} }
  19         39  
  7         18  
  19         30  
  19         52  
56 10 100   10 0 22 sub NS_listen { my $sock = shift; ${*$sock}{'NS_listen'} = shift if @_; return ${*$sock}{'NS_listen'} }
  10         23  
  5         11  
  10         16  
  10         23  
57              
58             sub object {
59 5     5 0 16 my ($class, $info, $server) = @_;
60              
61 5   66     60 my $ssl = $server->{'server'}->{'ssl_args'} ||= do {
62 4         18 my %temp = map {$_ => undef} @ssl_args;
  40         217  
63 4         15 $server->configure({map {$_ => \$temp{$_}} @ssl_args});
  40         109  
64 4         22 \%temp;
65             };
66              
67             # we cannot do this at compile time because we have not yet read the configuration then
68 5 50 33     47 @ISA = qw(IO::Socket::INET6) if $ISA[0] eq 'IO::Socket::INET' && Net::Server::Proto->requires_ipv6($server);
69              
70 5         111 my @sock = $class->SUPER::new();
71 5         926 foreach my $sock (@sock) {
72 5         27 $sock->NS_host($info->{'host'});
73 5         18 $sock->NS_port($info->{'port'});
74 5         31 $sock->NS_ipv( $info->{'ipv'} );
75             $sock->NS_listen(defined($info->{'listen'}) ? $info->{'listen'}
76 5 100       39 : defined($server->{'server'}->{'listen'}) ? $server->{'server'}->{'listen'}
    100          
77             : Socket::SOMAXCONN());
78 5 50       14 ${*$sock}{'NS_orig_port'} = $info->{'orig_port'} if defined $info->{'orig_port'};
  0         0  
79              
80 5         13 for my $key (@ssl_args) {
81             my $val = defined($info->{$key}) ? $info->{$key}
82             : defined($ssl->{$key}) ? $ssl->{$key}
83 50 100       267 : $server->can($key) ? $server->$key($info->{'host'}, $info->{'port'}, 'SSLEAY')
    100          
    100          
84             : undef;
85 50 100       116 next if ! defined $val;
86 9 50       88 $sock->$key($val) if defined $val;
87             }
88             }
89 5 50       34 return wantarray ? @sock : $sock[0];
90             }
91              
92             sub log_connect {
93 2     2 0 6 my ($sock, $server) = @_;
94 2         7 $server->log(2, "Binding to ".$sock->NS_proto." port ".$sock->NS_port." on host ".$sock->NS_host." with IPv".$sock->NS_ipv);
95             }
96              
97             sub connect { # connect the first time
98 2     2 0 5 my ($sock, $server) = @_;
99 2         13 my $host = $sock->NS_host;
100 2         6 my $port = $sock->NS_port;
101 2         5 my $ipv = $sock->NS_ipv;
102 2         6 my $lstn = $sock->NS_listen;
103              
104 2 50       84 $sock->SUPER::configure({
    0          
    0          
    50          
    50          
105             LocalPort => $port,
106             Proto => 'tcp',
107             Listen => $lstn,
108             ReuseAddr => 1,
109             Reuse => 1,
110             (($host ne '*') ? (LocalAddr => $host) : ()), # * is all
111             ($sock->isa("IO::Socket::INET6") ? (Domain => ($ipv eq '6') ? Socket6::AF_INET6() : ($ipv eq '4') ? Socket::AF_INET() : Socket::AF_UNSPEC()) : ()),
112             }) || $server->fatal("Can't connect to SSLEAY port $port on $host [$!]");
113              
114 2 50 33     741 if ($port eq '0' and $port = $sock->sockport) {
    50 33        
115 0         0 $server->log(2, " Bound to auto-assigned port $port");
116 0         0 ${*$sock}{'NS_orig_port'} = $sock->NS_port;
  0         0  
117 0         0 $sock->NS_port($port);
118             } elsif ($port =~ /\D/ and $port = $sock->sockport) {
119 0         0 $server->log(2, " Bound to service port ".$sock->NS_port()."($port)");
120 0         0 ${*$sock}{'NS_orig_port'} = $sock->NS_port;
  0         0  
121 0         0 $sock->NS_port($port);
122             }
123              
124 2         17 $sock->bind_SSL($server);
125             }
126              
127             sub reconnect { # connect on a sig -HUP
128 0     0 0 0 my ($sock, $fd, $server, $port) = @_;
129 0         0 $server->log(3,"Reassociating file descriptor $fd with ".$sock->NS_proto." on [".$sock->NS_host."]:".$sock->NS_port.", using IPv".$sock->NS_ipv);
130 0 0       0 my $resp = $sock->fdopen( $fd, 'w' ) or $server->fatal("Error opening to file descriptor ($fd) [$!]");
131              
132 0 0       0 if ($sock->isa("IO::Socket::INET6")) {
133 0         0 my $ipv = $sock->NS_ipv;
134 0 0       0 ${*$sock}{'io_socket_domain'} = ($ipv eq '6') ? Socket6::AF_INET6() : ($ipv eq '4') ? Socket::AF_INET() : Socket::AF_UNSPEC();
  0 0       0  
135             }
136              
137 0         0 $sock->bind_SSL($server);
138              
139 0 0       0 if ($port ne $sock->NS_port) {
140 0         0 $server->log(2, " Re-bound to previously assigned port $port");
141 0         0 ${*$sock}{'NS_orig_port'} = $sock->NS_port;
  0         0  
142 0         0 $sock->NS_port($port);
143             }
144              
145 0         0 return $resp;
146             }
147              
148             sub bind_SSL {
149 2     2 0 7 my ($sock, $server) = @_;
150 2         467 my $ctx = Net::SSLeay::CTX_new(); $sock->SSLeay_check_fatal("SSLeay bind_SSL CTX_new");
  2         57  
151              
152 2         63 Net::SSLeay::CTX_set_options($ctx, Net::SSLeay::OP_ALL()); $sock->SSLeay_check_fatal("SSLeay bind_SSL CTX_set_options");
  2         370  
153              
154             # 0x1: SSL_MODE_ENABLE_PARTIAL_WRITE
155             # 0x10: SSL_MODE_RELEASE_BUFFERS (ignored before OpenSSL v1.0.0)
156 2         8 Net::SSLeay::CTX_set_mode($ctx, 0x11); $sock->SSLeay_check_fatal("SSLeay bind_SSL CTX_set_mode");
  2         5  
157              
158             # Load certificate. This will prompt for a password if necessary.
159 2   50     5 my $file_key = $sock->SSL_key_file || die "SSLeay missing SSL_key_file on ".$sock->hup_string.".\n";
160 2   50     6 my $file_cert = $sock->SSL_cert_file || die "SSLeay missing SSL_cert_file on ".$sock->hup_string>".\n";
161 2         27 Net::SSLeay::CTX_use_RSAPrivateKey_file($ctx, $file_key, Net::SSLeay::FILETYPE_PEM()); $sock->SSLeay_check_fatal("SSLeay bind_SSL CTX_use_RSAPrivateKey_file");
  2         393  
162 2         53 Net::SSLeay::CTX_use_certificate_file( $ctx, $file_cert, Net::SSLeay::FILETYPE_PEM()); $sock->SSLeay_check_fatal("SSLeay bind_SSL CTX_use_certificate_file");
  2         236  
163 2         14 $sock->SSLeay_context($ctx);
164             }
165              
166             sub close {
167 1     1 0 3 my $sock = shift;
168 1 50       6 if ($sock->SSLeay_is_client) {
169 1         4 Net::SSLeay::free($sock->SSLeay);
170             } else {
171 0         0 Net::SSLeay::CTX_free($sock->SSLeay_context);
172             }
173 1         6 $sock->SSLeay_check_fatal("SSLeay close free");
174 1         247 return $sock->SUPER::close(@_);
175             }
176              
177             sub accept {
178 2     2 1 8 my ($sock, $class) = (@_);
179 2         4 my ($client, $peername);
180 2 50       6 if (wantarray) {
181 0         0 ($client, $peername) = $sock->SUPER::accept($class);
182             } else {
183 2         22 $client = $sock->SUPER::accept($class);
184             }
185 2 50       395 if (defined $client) {
186 2         10 $client->NS_proto($sock->NS_proto);
187 2         7 $client->NS_ipv( $sock->NS_ipv);
188 2         8 $client->NS_host( $sock->NS_host);
189 2         25 $client->NS_port( $sock->NS_port);
190 2         10 $client->SSLeay_context($sock->SSLeay_context);
191 2         16 $client->SSLeay_is_client(1);
192             }
193              
194 2 50       38 return wantarray ? ($client, $peername) : $client;
195             }
196              
197             sub post_accept {
198 2     2 0 4 my $client = shift;
199 2         7 $client->SSLeay;
200             }
201              
202             sub SSLeay {
203 9     9 0 17 my $client = shift;
204              
205 9 100       13 if (! exists ${*$client}{'SSLeay'}) {
  9         35  
206 2 50       5 die "SSLeay refusing to accept on non-client socket" if !$client->SSLeay_is_client;
207              
208 2         10 $client->autoflush(1);
209              
210 2   50     122 my $f = fcntl($client, Fcntl::F_GETFL(), 0) || die "SSLeay - fcntl get: $!\n";
211 2 50       20 fcntl($client, Fcntl::F_SETFL(), $f | Fcntl::O_NONBLOCK()) || die "SSLeay - fcntl set: $!\n";
212              
213 2         9 my $ssl = Net::SSLeay::new($client->SSLeay_context); $client->SSLeay_check_fatal("SSLeay new");
  2         15  
214 2         21 Net::SSLeay::set_fd($ssl, $client->fileno); $client->SSLeay_check_fatal("SSLeay set_fd");
  2         33  
215 2         610 Net::SSLeay::accept($ssl); $client->SSLeay_check_fatal("SSLeay accept");
  2         14  
216 2         14 ${*$client}{'SSLeay'} = $ssl;
  2         25  
217             }
218              
219 9 100       34 return if ! defined wantarray;
220 7         14 return ${*$client}{'SSLeay'};
  7         47  
221             }
222              
223             sub SSLeay_check_fatal {
224 17     17 0 88 my ($client, $msg) = @_;
225 17 50       50 if (my $err = $client->SSLeay_check_error($msg, 1)) {
226 0         0 my ($file, $pkg, $line) = caller;
227 0         0 die "$msg at $file line $line\n ".join(' ', @$err);
228             }
229             }
230              
231             sub SSLeay_check_error {
232 21     21 0 65 my ($client, $msg, $fatal) = @_;
233 21         36 my @err;
234 21         133 while (my $n = Net::SSLeay::ERR_get_error()) {
235 0         0 push @err, "$n. ". Net::SSLeay::ERR_error_string($n) ."\n";
236             }
237 21 50       51 if (@err) {
238 0         0 my $cb = $client->SSL_error_callback;
239 0 0       0 $cb->($client, $msg, \@err, ($fatal ? 'is_fatal' : ())) if $cb;
    0          
240 0         0 return \@err;
241             }
242 21         76 return;
243             }
244              
245              
246             ###----------------------------------------------------------------###
247              
248             sub read_until {
249 1     1 1 6 my ($client, $bytes, $end_qr, $non_greedy) = @_;
250              
251 1         3 my $ssl = $client->SSLeay;
252 1         3 my $content = ${*$client}{'SSLeay_buffer'};
  1         3  
253 1 50       14 $content = '' if ! defined $content;
254 1         5 my $ok = 0;
255              
256             # the rough outline for this loop came from http://devpit.org/wiki/OpenSSL_with_nonblocking_sockets_%28in_Perl%29
257 1         3 OUTER: while (1) {
258 2 100 33     98 if (!length($content)) {
    50 33        
    50          
259             }
260             elsif (defined($bytes) && length($content) >= $bytes) {
261 0         0 ${*$client}{'SSLeay_buffer'} = substr($content, $bytes, length($content), '');
  0         0  
262 0         0 $ok = 2;
263 0         0 last;
264             }
265             elsif (defined($end_qr) && $content =~ m/$end_qr/g) {
266 1         5 my $n = pos($content);
267 1         6 ${*$client}{'SSLeay_buffer'} = substr($content, $n, length($content), '');
  1         9  
268 1         3 $ok = 1;
269 1         4 last;
270             }
271              
272             # 'select' prevents spinloops waiting for new data on the socket, and are necessary for non-blocking filehandles.
273 1         14 vec(my $vec = '', $client->fileno, 1) = 1;
274 1         38724 select($vec, undef, undef, undef);
275              
276 1         12 my $n_empty = 0;
277 1         5 while (1) {
278             # 16384 is the maximum amount read() can return
279 2         4 my $n = 16384;
280 2 50 33     10 $n -= ($bytes - length($content)) if $non_greedy && ($bytes - length($content)) < $n;
281 2         95 my ($buf, $rv) = Net::SSLeay::read($ssl, 16384); # read the most we can - continue reading until the buffer won't read any more
282 2 50       18 if ($client->SSLeay_check_error('SSLeay read_until read')) {
283 0         0 last OUTER;
284             }
285              
286 2 100       9 if (! defined($buf)) {
287             # Preserved from Net/Server/Proto/SSLEAY's version
288 1 0 33     62 last if $!{'EAGAIN'} || $!{'EINTR'} || $!{'ENOBUFS'};
      0        
289              
290             # Treat these renegotiation errors like EAGAIN - select will handle it and the next SSL_read will resolve it.
291 0 0 0     0 last if $rv && ($rv == Net::SSLeay::ERROR_WANT_READ() || $rv == Net::SSLeay::ERROR_WANT_WRITE());
      0        
292              
293 0         0 die "SSLeay read_until: $!\n";
294             }
295              
296 1 50       6 if (!length($buf)) {
297 0 0 0     0 last OUTER if !length($buf) && $n_empty++;
298             }
299             else {
300 1         4 $content .= $buf;
301 1 50 33     7 if ($non_greedy && length($content) == $bytes) {
302 0         0 $ok = 3;
303 0         0 last;
304             }
305             }
306             }
307             }
308 1 50       10 return wantarray ? ($ok, $content) : $content;
309             }
310              
311             sub read {
312 0     0 0 0 my ($client, $buf, $size, $offset) = @_;
313 0         0 my ($ok, $read) = $client->read_until($size, undef, 1);
314 0 0 0     0 substr($_[1], $offset || 0, defined($buf) ? length($buf) : 0, $read);
315 0         0 return length $read;
316             }
317              
318             sub sysread {
319 2     2 0 395 my ($client, $buf, $length, $offset) = @_;
320 2 50       9 $length = length $buf unless defined $length;
321 2 50       7 $offset = 0 unless defined $offset;
322 2         9 my $ssl = $client->SSLeay;
323 2         3028 my $data = Net::SSLeay::read($ssl, $length);
324              
325 2 100 66     23 return if $!{EAGAIN} || $!{EINTR};
326              
327 1 50       56 die "SSLeay print: $!\n" unless defined $data;
328              
329 1         3 $length = length($data);
330 1 50       4 $$buf = '' if !defined $buf;
331              
332 1 50       16 if ($offset > length($$buf)) {
333 0         0 $$buf .= "\0" x ($offset - length($buf));
334             }
335              
336 1         11 substr($$buf, $offset, length($$buf), $data);
337 1         4 return $length;
338             }
339              
340 0     0 1 0 sub error { my $client = shift; return ${*$client}{'_error'} }
  0         0  
  0         0  
341              
342             sub syswrite {
343 1     1 0 20 my ($client, $buf, $length, $offset) = @_;
344 1         4 delete ${*$client}{'_error'};
  1         5  
345              
346 1 50       5 $length = length $buf unless defined $length;
347 1 50       5 $offset = 0 unless defined $offset;
348 1         4 my $ssl = $client->SSLeay;
349              
350 1         66 my $write = Net::SSLeay::write_partial($ssl, $offset, $length, $buf);
351              
352 1 50 33     10 return if $!{EAGAIN} || $!{EINTR};
353 1 50       30 if ($write < 0) {
354 0         0 ${*$client}{'_error'} = "SSLeay print: $!\n";
  0         0  
355 0         0 return;
356             }
357              
358 1         4 return $write;
359             }
360              
361             sub getline {
362 1     1 1 3 my $client = shift;
363 1         14 my ($ok, $line) = $client->read_until($client->SSL_max_getline_length, $/);
364 1         13 return $line;
365             }
366              
367             sub getlines {
368 0     0 1 0 my $client = shift;
369 0         0 my @lines;
370 0         0 while (1) {
371 0         0 my ($ok, $line) = $client->read_until($client->SSL_max_getline_length, $/);
372 0         0 push @lines, $line;
373 0 0       0 last if $ok != 1;
374             }
375 0         0 return @lines;
376             }
377              
378             sub print {
379 2     2 0 5 my $client = shift;
380 2         3 delete ${*$client}{'_error'};
  2         10  
381 2 100       11 my $buf = @_ == 1 ? $_[0] : join('', @_);
382 2         9 my $ssl = $client->SSLeay;
383 2         8 while (length $buf) {
384 2         25 vec(my $vec = '', $client->fileno, 1) = 1;
385 2         63 select(undef, $vec, undef, undef);
386              
387 2         3208 my $write = Net::SSLeay::write($ssl, $buf);
388 2 50       15 return 0 if $client->SSLeay_check_error('SSLeay write');
389 2 0 33     10 if ($write == -1 && !$!{EAGAIN} && !$!{EINTR} && !$!{ENOBUFS}) {
      33        
      0        
390 0         0 ${*$client}{'_error'} = "SSLeay print: $!\n";
  0         0  
391 0         0 return;
392             }
393 2 50       13 substr($buf, 0, $write, "") if $write > 0;
394             }
395 2         11 return 1;
396             }
397              
398             sub printf {
399 0     0 0 0 my $client = shift;
400 0         0 $client->print(sprintf(shift, @_));
401             }
402              
403             sub say {
404 0     0 0 0 my $client = shift;
405 0         0 $client->print(@_, "\n");
406             }
407              
408             sub write {
409 0     0 1 0 my $client = shift;
410 0         0 my $buf = shift;
411 0 0 0     0 $buf = substr($buf, $_[1] || 0, $_[0]) if @_;
412 0         0 $client->print($buf);
413             }
414              
415             sub seek {
416 0     0 0 0 my $client = shift;
417 0         0 my ($pos, $whence) = @_;
418 0 0       0 if ($whence) {
419 0         0 $! = "Seek from $whence of non-zero is not supported.";
420 0         0 return 0;
421             }
422 0         0 my $n = $client->read(my $buf, $pos);
423 0 0       0 if ($n != $pos) {
424 0         0 $| = "Couldn't seek to $pos ($n)\n";
425 0         0 return 0;
426             }
427 0         0 return 1;
428             }
429              
430             sub poll_cb { # implemented for psgi compatibility - TODO - should poll appropriately for Multipex
431 0     0 0 0 my ($self, $cb) = @_;
432 0         0 return $cb->($self);
433             }
434              
435             ###----------------------------------------------------------------###
436              
437             sub hup_string {
438 3     3 0 965 my $sock = shift;
439 3 50       10 return join "|", $sock->NS_host, $sock->NS_port, $sock->NS_proto, "ipv".$sock->NS_ipv, (defined(${*$sock}{'NS_orig_port'}) ? ${*$sock}{'NS_orig_port'} : ());
  3         22  
  0         0  
440             }
441              
442             sub show {
443 0     0 0 0 my $sock = shift;
444 0         0 my $t = "Ref = \"".ref($sock). "\" (".$sock->hup_string.")\n";
445 0         0 foreach my $prop (qw(SSLeay_context SSLeay_is_client)) {
446 0         0 $t .= " $prop = \"" .$sock->$prop()."\"\n";
447             }
448 0         0 return $t;
449             }
450              
451             sub AUTOLOAD {
452 7     7   16 my $sock = shift;
453 7 50       97 my $prop = $AUTOLOAD =~ /::([^:]+)$/ ? $1 : die "Missing property in AUTOLOAD.";
454 7 50       51 die "Unknown method or property [$prop]"
455             if $prop !~ /^(SSLeay_context|SSLeay_is_client|SSL_\w+)$/;
456              
457 3     3   62 no strict 'refs';
  3         8  
  3         634  
458 7         41 *{__PACKAGE__."::${prop}"} = sub {
459 32     32   938 my $sock = shift;
460 32 100       68 if (@_) {
461 15         23 ${*$sock}{$prop} = shift;
  15         68  
462 15 50       30 return delete ${*$sock}{$prop} if ! defined ${*$sock}{$prop};
  0         0  
  15         60  
463             } else {
464 17         25 return ${*$sock}{$prop};
  17         109  
465             }
466 7         58 };
467 7         24 return $sock->$prop(@_);
468             }
469              
470 2     2 0 11 sub tie_stdout { 1 }
471              
472             1;
473              
474             __END__