File Coverage

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