File Coverage

blib/lib/Net/SSL.pm
Criterion Covered Total %
statement 62 259 23.9
branch 9 104 8.6
condition 4 76 5.2
subroutine 13 38 34.2
pod 27 27 100.0
total 115 504 22.8


line stmt bran cond sub pod time code
1             package Net::SSL;
2              
3 2     2   36858 use strict;
  2         5  
  2         56  
4 2     2   1811 use MIME::Base64;
  2         1825  
  2         133  
5 2     2   2518 use Socket;
  2         11734  
  2         1340  
6 2     2   23 use Carp;
  2         6  
  2         136  
7              
8 2     2   11 use vars qw(@ISA $VERSION $NEW_ARGS);
  2         5  
  2         14690  
9             $VERSION = '2.86';
10             $VERSION = eval $VERSION;
11              
12             require IO::Socket;
13             @ISA=qw(IO::Socket::INET);
14              
15             my %REAL; # private to this package only
16             my $DEFAULT_VERSION = '23';
17             my $CRLF = "\015\012";
18             my $SEND_USERAGENT_TO_PROXY = 0;
19              
20             require Crypt::SSLeay;
21              
22             sub _default_context {
23 1     1   621 require Crypt::SSLeay::MainContext;
24 1         6 Crypt::SSLeay::MainContext::main_ctx(@_);
25             }
26              
27             sub _alarm_set {
28 0 0 0 0   0 return if $^O eq 'MSWin32' or $^O eq 'NetWare';
29 0         0 alarm(shift);
30             }
31              
32             sub new {
33 1     1 1 110 my($class, %arg) = @_;
34 1         3 local $NEW_ARGS = \%arg;
35 1         19 $class->SUPER::new(%arg);
36             }
37              
38             sub DESTROY {
39 1     1   2 my $self = shift;
40 1         4 delete $REAL{$self};
41 1         2 local $@;
42 1         2 eval { $self->SUPER::DESTROY; };
  1         13  
43             }
44              
45             sub configure {
46 1     1 1 198 my($self, $arg) = @_;
47 1   33     16 my $ssl_version = delete $arg->{SSL_Version} ||
48             $ENV{HTTPS_VERSION} || $DEFAULT_VERSION;
49 1   50     11 my $ssl_debug = delete $arg->{SSL_Debug} || $ENV{HTTPS_DEBUG} || 0;
50              
51 1   33     7 my $ctx = delete $arg->{SSL_Context} || _default_context($ssl_version);
52              
53 1         4 *$self->{ssl_ctx} = $ctx;
54 1         4 *$self->{ssl_version} = $ssl_version;
55 1         3 *$self->{ssl_debug} = $ssl_debug;
56 1         3 *$self->{ssl_arg} = $arg;
57 1         4 *$self->{ssl_peer_addr} = $arg->{PeerAddr};
58 1         3 *$self->{ssl_peer_port} = $arg->{PeerPort};
59 1         3 *$self->{ssl_new_arg} = $NEW_ARGS;
60 1         3 *$self->{ssl_peer_verify} = 0;
61              
62             ## Crypt::SSLeay must also aware the SSL Proxy before calling
63             ## $socket->configure($args). Because the $sock->configure() will
64             ## die when failed to resolve the destination server IP address,
65             ## whether the SSL proxy is used or not!
66             ## - dqbai, 2003-05-10
67 1 50       6 if (my $proxy = $self->proxy) {
68 0         0 ($arg->{PeerAddr}, $arg->{PeerPort}) = split(':',$proxy);
69 0 0       0 $arg->{PeerPort} || croak("no port given for proxy server $proxy");
70             }
71              
72 1         14 $self->SUPER::configure($arg);
73             }
74              
75             # override to make sure there is really a timeout
76             sub timeout {
77 0 0   0 1 0 shift->SUPER::timeout || 60;
78             }
79              
80             sub blocking {
81 2     2 1 3010 my $self = shift;
82 2         13 $self->SUPER::blocking(@_);
83             }
84              
85             sub connect {
86 1     1 1 224 my $self = shift;
87              
88             # configure certs on connect() time, so we can throw an undef
89             # and have LWP understand the error
90 1         2 eval { $self->configure_certs() };
  1         5  
91 1 50       4 if($@) {
92 0         0 $@ = "configure certs failed: $@; $!";
93 0         0 $self->die_with_error($@);
94             }
95              
96             # finished, update set_verify status
97 1 50       13 if(my $rv = *$self->{ssl_ctx}->set_verify()) {
98 0         0 *$self->{ssl_peer_verify} = $rv;
99             }
100              
101 1 50       3 if ($self->proxy) {
102             # don't die() in connect, just return undef and set $@
103 0         0 my $proxy_connect = eval { $self->proxy_connect_helper(@_) };
  0         0  
104 0 0 0     0 if(! $proxy_connect || $@) {
105 0         0 $@ = "proxy connect failed: $@; $!";
106 0         0 croak($@);
107             }
108             }
109             else {
110 1 50       5 *$self->{io_socket_peername}=@_ == 1 ? $_[0] : IO::Socket::sockaddr_in(@_);
111 1 50       9 if(!$self->SUPER::connect(@_)) {
112             # better to die than return here
113 1         23 $@ = "Connect failed: $@; $!";
114 1         408 croak($@);
115             }
116             }
117              
118 0   0     0 my $debug = *$self->{ssl_debug} || 0;
119 0         0 my $ssl = Crypt::SSLeay::Conn->new(*$self->{ssl_ctx}, $debug, $self);
120 0         0 my $arg = *$self->{ssl_arg};
121 0         0 my $new_arg = *$self->{ssl_new_arg};
122 0         0 $arg->{SSL_Debug} = $debug;
123              
124             # setup SNI if available
125 0 0       0 $ssl->can("set_tlsext_host_name") and
126             $ssl->set_tlsext_host_name(*$self->{ssl_peer_addr});
127              
128 0         0 eval {
129 0     0   0 local $SIG{ALRM} = sub { $self->die_with_error("SSL connect timeout") };
  0         0  
130             # timeout / 2 because we have 3 possible connects here
131 0         0 _alarm_set($self->timeout / 2);
132              
133 0         0 my $rv;
134             {
135 0         0 local $SIG{PIPE} = \¨
  0         0  
136 0         0 $rv = eval { $ssl->connect; };
  0         0  
137             }
138 0 0 0     0 if (not defined $rv or $rv <= 0) {
139 0         0 _alarm_set(0);
140 0         0 $ssl = undef;
141             # See RT #59312
142 0         0 my %args = (%$arg, %$new_arg);
143 0 0       0 if(*$self->{ssl_version} == 23) {
    0          
144 0         0 $args{SSL_Version} = 3;
145             # the new connect might itself be overridden with a REAL SSL
146 0         0 my $new_ssl = Net::SSL->new(%args);
147 0   0     0 $REAL{$self} = $REAL{$new_ssl} || $new_ssl;
148 0         0 return $REAL{$self};
149             }
150             elsif(*$self->{ssl_version} == 3) {
151             # $self->die_with_error("SSL negotiation failed");
152 0         0 $args{SSL_Version} = 2;
153 0         0 my $new_ssl = Net::SSL->new(%args);
154 0         0 $REAL{$self} = $new_ssl;
155 0         0 return $new_ssl;
156             }
157             else {
158             # don't die, but do set $@, and return undef
159 0         0 eval { $self->die_with_error("SSL negotiation failed") };
  0         0  
160 0         0 croak($@);
161             }
162             }
163 0         0 _alarm_set(0);
164             };
165              
166             # odd error in eval {} block, maybe alarm outside the evals
167 0 0       0 if($@) {
168 0         0 $@ = "$@; $!";
169 0         0 croak($@);
170             }
171              
172             # successful SSL connection gets stored
173 0         0 *$self->{ssl_ssl} = $ssl;
174 0         0 $self;
175             }
176              
177             # Delegate these calls to the Crypt::SSLeay::Conn object
178             sub get_peer_certificate {
179 0     0 1 0 my $self = shift;
180 0   0     0 $self = $REAL{$self} || $self;
181 0         0 *$self->{ssl_ssl}->get_peer_certificate(@_);
182             }
183              
184             sub get_peer_verify {
185 0     0 1 0 my $self = shift;
186 0   0     0 $self = $REAL{$self} || $self;
187 0         0 *$self->{ssl_peer_verify};
188             }
189              
190             sub get_shared_ciphers {
191 0     0 1 0 my $self = shift;
192 0   0     0 $self = $REAL{$self} || $self;
193 0         0 *$self->{ssl_ssl}->get_shared_ciphers(@_);
194             }
195              
196             sub get_cipher {
197 0     0 1 0 my $self = shift;
198 0   0     0 $self = $REAL{$self} || $self;
199 0         0 *$self->{ssl_ssl}->get_cipher(@_);
200             }
201              
202             sub pending {
203 0     0 1 0 my $self = shift;
204 0   0     0 $self = $REAL{$self} || $self;
205 0         0 *$self->{ssl_ssl}->pending(@_);
206             }
207              
208             sub ssl_context {
209 0     0 1 0 my $self = shift;
210 0   0     0 $self = $REAL{$self} || $self;
211 0         0 *$self->{ssl_ctx};
212             }
213              
214             sub die_with_error {
215 0     0 1 0 my $self=shift;
216 0         0 my $reason=shift;
217              
218 0         0 my @err;
219 0         0 while(my $err=Crypt::SSLeay::Err::get_error_string()) {
220 0         0 push @err, $err;
221             }
222 0         0 croak("$reason: " . join( ' | ', @err ));
223             }
224              
225             sub read {
226 0     0 1 0 my $self = shift;
227 0   0     0 $self = $REAL{$self} || $self;
228              
229 0         0 local $SIG{__DIE__} = \&Carp::confess;
230 0     0   0 local $SIG{ALRM} = sub { $self->die_with_error("SSL read timeout") };
  0         0  
231              
232 0         0 _alarm_set($self->timeout);
233 0         0 my $n = *$self->{ssl_ssl}->read(@_);
234 0         0 _alarm_set(0);
235 0 0       0 $self->die_with_error("read failed") if !defined $n;
236              
237 0         0 $n;
238             }
239              
240             sub write {
241 0     0 1 0 my $self = shift;
242 0   0     0 $self = $REAL{$self} || $self;
243 0         0 my $n = *$self->{ssl_ssl}->write(@_);
244 0 0       0 $self->die_with_error("write failed") if !defined $n;
245 0         0 $n;
246             }
247              
248             *sysread = \&read;
249             *syswrite = \&write;
250              
251             sub print {
252 0     0 1 0 my $self = shift;
253 0   0     0 $self = $REAL{$self} || $self;
254             # should we care about $, and $\??
255             # I think it is too expensive...
256 0         0 $self->write(join("", @_));
257             }
258              
259             sub printf {
260 0     0 1 0 my $self = shift;
261 0   0     0 $self = $REAL{$self} || $self;
262 0         0 my $fmt = shift;
263 0         0 $self->write(sprintf($fmt, @_));
264             }
265              
266             sub getchunk {
267 0     0 1 0 my $self = shift;
268 0   0     0 $self = $REAL{$self} || $self;
269 0         0 my $buf = ''; # warnings
270 0         0 my $n = $self->read($buf, 32768);
271 0 0       0 return unless defined $n;
272 0         0 $buf;
273             }
274              
275             # This is really inefficient, but we only use it for reading the proxy response
276             # so that does not really matter.
277             sub getline {
278 0     0 1 0 my $self = shift;
279 0   0     0 $self = $REAL{$self} || $self;
280 0         0 my $val="";
281 0         0 my $buf;
282 0         0 do {
283 0         0 $self->SUPER::recv($buf, 1);
284 0         0 $val .= $buf;
285             } until ($buf eq "\n");
286              
287 0         0 $val;
288             }
289              
290             # XXX: no way to disable <$sock>?? (tied handle perhaps?)
291              
292             sub get_lwp_object {
293 0     0 1 0 my $self = shift;
294              
295 0         0 my $lwp_object;
296 0         0 my $i = 0;
297 0         0 while(1) {
298             package DB;
299 0         0 my @stack = caller($i++);
300 0 0       0 last unless @stack;
301 0         0 my @stack_args = @DB::args;
302 0   0     0 my $stack_object = $stack_args[0] || next;
303 0 0 0     0 return $stack_object
304             if ref($stack_object)
305             and $stack_object->isa('LWP::UserAgent');
306             }
307 0         0 return undef;
308             }
309              
310             sub send_useragent_to_proxy {
311 0 0   0 1 0 if (my $val = shift) {
312 0         0 $SEND_USERAGENT_TO_PROXY = $val;
313             }
314 0         0 return $SEND_USERAGENT_TO_PROXY;
315             }
316              
317             sub proxy_connect_helper {
318 0     0 1 0 my $self = shift;
319              
320 0         0 my $proxy = $self->proxy;
321 0         0 my ($proxy_host, $proxy_port) = split(':',$proxy);
322 0 0       0 $proxy_port || croak("no port given for proxy server $proxy");
323              
324 0         0 my $proxy_addr = gethostbyname($proxy_host);
325 0 0       0 $proxy_addr || croak("can't resolve proxy server name: $proxy_host, $!");
326              
327 0         0 my($peer_port, $peer_addr) = (*$self->{ssl_peer_port}, *$self->{ssl_peer_addr});
328 0 0       0 $peer_addr || croak("no peer addr given");
329 0 0       0 $peer_port || croak("no peer port given");
330              
331             # see if the proxy should be bypassed
332 0   0     0 my @no_proxy = split( /\s*,\s*/, $ENV{NO_PROXY} || $ENV{no_proxy} || '');
333 0         0 my $is_proxied = 1;
334 0         0 my $domain;
335 0         0 for $domain (@no_proxy) {
336 0 0       0 if ($peer_addr =~ /\Q$domain\E$/) {
337 0         0 $is_proxied = 0;
338 0         0 last;
339             }
340             }
341              
342 0 0       0 if ($is_proxied) {
343 0 0       0 $self->SUPER::connect($proxy_port, $proxy_addr)
344             || croak("proxy connect to $proxy_host:$proxy_port failed: $!");
345             }
346             else {
347             # see RT #57836
348 0         0 my $peer_addr_packed = gethostbyname($peer_addr);
349 0 0       0 $self->SUPER::connect($peer_port, $peer_addr_packed)
350             || croak("proxy bypass to $peer_addr:$peer_addr failed: $!");
351             }
352              
353 0         0 my $connect_string;
354 0 0 0     0 if ($ENV{"HTTPS_PROXY_USERNAME"} || $ENV{"HTTPS_PROXY_PASSWORD"}) {
355 0         0 my $user = $ENV{"HTTPS_PROXY_USERNAME"};
356 0         0 my $pass = $ENV{"HTTPS_PROXY_PASSWORD"};
357              
358 0         0 my $credentials = encode_base64("$user:$pass", "");
359 0         0 $connect_string = join($CRLF,
360             "CONNECT $peer_addr:$peer_port HTTP/1.0",
361             "Proxy-authorization: Basic $credentials"
362             );
363             }
364             else {
365 0         0 $connect_string = "CONNECT $peer_addr:$peer_port HTTP/1.0";
366             }
367 0         0 $connect_string .= $CRLF;
368              
369 0 0       0 if (send_useragent_to_proxy()) {
370 0         0 my $lwp_object = $self->get_lwp_object;
371 0 0 0     0 if($lwp_object && $lwp_object->agent) {
372 0         0 $connect_string .= "User-Agent: ".$lwp_object->agent.$CRLF;
373             }
374             }
375              
376 0         0 $connect_string .= $CRLF;
377 0         0 $self->SUPER::send($connect_string);
378              
379 0         0 my $timeout;
380 0         0 my $header = '';
381              
382             # See RT #33954
383             # See also RT #64054
384             # Handling incomplete reads and writes better (for some values of
385             # better) may actually make this problem go away, but either way,
386             # there is no good reason to use \d when checking for 0-9
387              
388 0         0 while ($header !~ m{HTTP/[0-9][.][0-9]\s+200\s+.*$CRLF$CRLF}s) {
389 0 0       0 $timeout = $self->timeout(5) unless length $header;
390 0         0 my $n = $self->SUPER::sysread($header, 8192, length $header);
391 0 0       0 last if $n <= 0;
392             }
393              
394 0 0       0 $self->timeout($timeout) if defined $timeout;
395 0 0       0 my $conn_ok = ($header =~ m{HTTP/[0-9]+[.][0-9]+\s+200\s+}is) ? 1 : 0;
396              
397 0 0       0 if (not $conn_ok) {
398 0         0 croak("PROXY ERROR HEADER, could be non-SSL URL:\n$header");
399             }
400              
401 0         0 $conn_ok;
402             }
403              
404             # code adapted from LWP::UserAgent, with $ua->env_proxy API
405             # see also RT #57836
406             sub proxy {
407 2     2 1 5 my $self = shift;
408 2   33     15 my $proxy_server = $ENV{HTTPS_PROXY} || $ENV{https_proxy};
409 2 50       14 return unless $proxy_server;
410              
411 0         0 my($peer_port, $peer_addr) = (
412             *$self->{ssl_peer_port},
413             *$self->{ssl_peer_addr}
414             );
415 0 0       0 $peer_addr || croak("no peer addr given");
416 0 0       0 $peer_port || croak("no peer port given");
417              
418             # see if the proxy should be bypassed
419 0   0     0 my @no_proxy = split( /\s*,\s*/,
420             $ENV{NO_PROXY} || $ENV{no_proxy} || ''
421             );
422 0         0 my $is_proxied = 1;
423 0         0 for my $domain (@no_proxy) {
424 0 0       0 if ($peer_addr =~ /\Q$domain\E\z/) {
425 0         0 return;
426             }
427             }
428              
429 0         0 $proxy_server =~ s|\Ahttps?://||i;
430             # sanitize the end of the string too
431             # see also http://www.nntp.perl.org/group/perl.libwww/2012/10/msg7629.html
432             # and https://github.com/nanis/Crypt-SSLeay/pull/1
433             # Thank you Mark Allen and YigangX Wen
434 0         0 $proxy_server =~ s|(:[1-9][0-9]{0,4})/\z|$1|;
435 0         0 $proxy_server;
436             }
437              
438             sub configure_certs {
439 1     1 1 2 my $self = shift;
440 1         4 my $ctx = *$self->{ssl_ctx};
441              
442 1         2 my $count = 0;
443 1         3 for (qw(HTTPS_PKCS12_FILE HTTPS_CERT_FILE HTTPS_KEY_FILE)) {
444 3         6 my $file = $ENV{$_};
445 3 50       10 if ($file) {
446 0 0       0 (-e $file) or croak("$file file does not exist: $!");
447 0 0       0 (-r $file) or croak("$file file is not readable");
448 0         0 $count++;
449 0 0       0 if (/PKCS12/) {
    0          
    0          
450 0         0 $count++;
451 0 0       0 $ctx->use_pkcs12_file($file ,$ENV{'HTTPS_PKCS12_PASSWORD'}) || croak("failed to load $file: $!");
452 0         0 last;
453             }
454             elsif (/CERT/) {
455 0 0       0 $ctx->use_certificate_file($file ,1) || croak("failed to load $file: $!");
456             }
457             elsif (/KEY/) {
458 0 0       0 $ctx->use_PrivateKey_file($file, 1) || croak("failed to load $file: $!");
459             }
460             else {
461 0         0 croak("setting $_ not supported");
462             }
463             }
464             }
465              
466             # if both configs are set, then verify them
467 1 50       3 if ($count == 2) {
468 0 0       0 if (! $ctx->check_private_key) {
469 0         0 croak("Private key and certificate do not match");
470             }
471             }
472              
473 1         3 $count; # number of successful cert loads/checks
474             }
475              
476 0     0 1   sub accept { shift->_unimpl("accept") }
477 0     0 1   sub getc { shift->_unimpl("getc") }
478 0     0 1   sub ungetc { shift->_unimpl("ungetc") }
479 0     0 1   sub getlines { shift->_unimpl("getlines"); }
480              
481             sub _unimpl {
482 0     0     my($self, $meth) = @_;
483 0           croak("$meth not implemented for Net::SSL sockets");
484             }
485              
486             1;
487              
488             __END__