On Sun, 28 Sep 2008 16:59:15 +0200, Luk Claes wrote:

> > It's the decision of the release team, therefore I'm cc'ing them and
> > ask for their opinion instead of guessing what they may think :)
> Please provide a diff with all the whitespace changes in SSL.pm stripped.

Attached is the output of 
$ svn diff -x -b -r21743:24723 SSL.pm 

/*
r24723: releasing version 1.15-1
r21743: releasing version 1.13-1
*/

Thanks for looking into this issue!

Cheers,
gregor
-- 
 .''`.   Home: http://info.comodo.priv.at/{,blog/} / GPG Key ID: 0x00F3CFE4
 : :' :  Debian GNU/Linux user, admin, & developer - http://www.debian.org/
 `. `'   Member of VIBE!AT, SPI Inc., fellow of FSFE | http://got.to/quote/
   `-    NP: Aimee Mann: I Know There's A Word
Index: SSL.pm
===================================================================
--- SSL.pm      (revision 21743)
+++ SSL.pm      (revision 24723)
@@ -32,7 +32,6 @@
        if $@;
 }
 
-
 use vars qw(@ISA $VERSION $DEBUG $SSL_ERROR $GLOBAL_CONTEXT_ARGS @EXPORT );
 
 {
@@ -46,13 +45,13 @@
     my $y = Net::SSLeay::ERROR_WANT_WRITE();
     use constant SSL_WANT_WRITE => dualvar( \$y, 'SSL wants a write first' );
 
-    @EXPORT = qw( SSL_WANT_READ SSL_WANT_WRITE $SSL_ERROR );
+       @EXPORT = qw( SSL_WANT_READ SSL_WANT_WRITE $SSL_ERROR GEN_DNS GEN_IPADD 
);
 }
 
 BEGIN {
     # Declare @ISA, $VERSION, $GLOBAL_CONTEXT_ARGS
     @ISA = qw(IO::Socket::INET);
-    $VERSION = '1.13';
+       $VERSION = '1.15';
     $GLOBAL_CONTEXT_ARGS = {};
 
     #Make $DEBUG another name for $Net::SSLeay::trace
@@ -65,18 +64,46 @@
     Net::SSLeay::load_error_strings();
     Net::SSLeay::SSLeay_add_ssl_algorithms();
     Net::SSLeay::randomize();
-
 }
 
 sub DEBUG {
-    $DEBUG or return;
+       $DEBUG>=shift or return; # check against debug level
     my (undef,$file,$line) = caller;
     my $msg = shift;
+       $file = '...'.substr( $file,-17 ) if length($file)>20;
     $msg = sprintf $msg,@_ if @_;
     print STDERR "DEBUG: $file:$line: $msg\n";
 }
 
+BEGIN {
+       # import some constants from Net::SSLeay or use hard-coded defaults
+       # if Net::SSLeay isn't recent enough to provide the constants
+       my %const = (
+               NID_CommonName => 13,
+               GEN_DNS => 2,
+               GEN_IPADD => 7,
+       );
+       while ( my ($name,$value) = each %const ) {
+               no strict 'refs';
+               *{$name} = UNIVERSAL::can( 'Net::SSLeay', $name ) || sub { 
$value };
+       }
 
+       # check if we have something to handle IDN
+       local $SIG{__DIE__}; local $SIG{__WARN__}; # be silent
+       if ( eval { require Net::IDN::Encode }) {
+               *{idn_to_ascii} = \&Net::IDN::Encode::domain_to_ascii;
+       } elsif ( eval { require Net::LibIDN }) {
+               *{idn_to_ascii} = \&Net::LibIDN::idn_to_ascii;
+       } else {
+               # default: croak if we really got an unencoded international 
domain
+               *{idn_to_ascii} = sub {
+                       my $domain = shift;
+                       return $domain if $domain =~m{^[a-zA-Z0-9-_\.]+$};
+                       croak "cannot handle international domains, please 
install Net::LibIDN or Net::IDN::Encode"
+               }
+       }
+}
+
 # Export some stuff
 # inet4|inet6|debug will be handeled by myself, everything
 # else will be handeld the Exporter way
@@ -85,11 +112,20 @@
 
     my @export;
     foreach (@_) { 
-       @ISA=qw(IO::Socket::INET), next if /inet4/i;
-       @ISA=qw(IO::Socket::INET6), next if /inet6/i;
-       $DEBUG=$1, next if /debug(\d)/; 
+               if ( /^inet4$/i ) {
+                       require IO::Socket::INET;
+                       @ISA = 'IO::Socket::INET'
+               } elsif ( /^inet6$/i ) {
+                       require IO::Socket::INET6;
+                       require Socket6;
+                       Socket6->import( 'inet_pton' );
+                       @ISA = 'IO::Socket::INET6'
+               } elsif ( /^:?debug(\d+)/ ) {
+                       $DEBUG=$1;
+               } else {
        push @export,$_
     }
+       }
 
     @_ = ( $class,@export );
     goto &Exporter::import;
@@ -130,16 +166,18 @@
     my ($self, $arg_hash) = @_;
 
     my $is_server = $arg_hash->{'SSL_server'} || $arg_hash->{'Listen'} || 0;
-    my %default_args =
-       ('Proto'         => 'tcp',
-        'SSL_server'    => $is_server,
-        'SSL_ca_file'   => 'certs/my-ca.pem',
-        'SSL_ca_path'   => 'ca/',
-        'SSL_use_cert'  => $is_server,
-        'SSL_check_crl' => 0,
-        'SSL_version'   => 'sslv23',
-        'SSL_verify_mode' => Net::SSLeay::VERIFY_NONE(),
-        'SSL_verify_callback' => 0,
+       my %default_args = (
+               Proto => 'tcp',
+               SSL_server => $is_server,
+               SSL_ca_file => 'certs/my-ca.pem',
+               SSL_ca_path => 'ca/',
+               SSL_use_cert => $is_server,
+               SSL_check_crl => 0,
+               SSL_version     => 'sslv23',
+               SSL_verify_mode => Net::SSLeay::VERIFY_NONE(),
+               SSL_verify_callback => undef,
+               SSL_verifycn_scheme => undef,  # don't verify cn
+               SSL_verifycn_name => undef,    # use from PeerAddr/PeerHost
     );
      
     # SSL_key_file and SSL_cert_file will only be set in defaults if 
@@ -161,13 +199,44 @@
 
     #Handle CA paths properly if no CA file is specified
     if ($arg_hash->{'SSL_ca_path'} ne '' and !(-f $arg_hash->{'SSL_ca_file'})) 
{
-       warn "CA file $arg_hash->{'SSL_ca_file'} not found, using CA path 
instead.\n" if ($DEBUG);
+               DEBUG(1, "CA file $arg_hash->{SSL_ca_file} not found, using CA 
path instead.\n" )
+                       if $arg_hash->{SSL_ca_file};
        $arg_hash->{'SSL_ca_file'} = '';
     }
 
+       my $vcn_scheme = delete $arg_hash->{SSL_verifycn_scheme};
+       if ( $vcn_scheme && $vcn_scheme ne 'none' ) {
+               my $vcb = $arg_hash->{SSL_verify_callback};
+               $arg_hash->{SSL_verify_callback} = sub {
+                       my ($ok,$ctx_store,$cert,$error) = @_;
+                       $ok = $vcb->($ok,$ctx_store,$cert,$error) if $vcb;
+                       $ok or return;
+                       my $depth = 
Net::SSLeay::X509_STORE_CTX_get_error_depth($ctx_store);
+                       return $ok if $depth != 0;
+
+                       # use SSL_peer_hostname or determine from PeerAddr
+                       my $arg_hash = ${*$self}{_SSL_arguments};
+                       my $host = $arg_hash->{SSL_verifycn_name};
+                       if (not defined($host)) {
+                               $host = ( $arg_hash->{PeerAddr} || 
$arg_hash->{PeerHost} );
+                               $host =~s{:\w+$}{} if ! $host;
+                       }
+                       $host ||= ref($vcn_scheme) && $vcn_scheme->{callback} 
&& 'unknown';
+                       $host or return $self->error( "Cannot determine peer 
hostname for verification" );
+
+                       # verify name
+                       my $x509 = 
Net::SSLeay::X509_STORE_CTX_get_current_cert($ctx_store);
+                       my $rv = verify_hostname_of_cert( 
$host,$x509,$vcn_scheme );
+                       # just do some code here against optimization because 
x509 has no
+                       # increased reference and CRYPTO_add is not available 
from Net::SSLeay
+                       DEBUG(99999,"don't to anything with $x509" );
+                       return $rv;
+               };
+       }
+
     ${*$self}{'_SSL_arguments'} = $arg_hash;
     ${*$self}{'_SSL_ctx'} = IO::Socket::SSL::SSL_Context->new($arg_hash) || 
return;
-    ${*$self}{'_SSL_opened'} = 1 if ($is_server);
+       ${*$self}{'_SSL_opened'} = 1 if $is_server;
 
     return $self;
 }
@@ -181,7 +250,7 @@
        $err == Net::SSLeay::ERROR_WANT_WRITE() ? SSL_WANT_WRITE :
        return;
     $! ||= EAGAIN;
-    ${*$self}{'_SSL_last_err'} = $SSL_ERROR if (ref($self));
+       ${*$self}{'_SSL_last_err'} = $SSL_ERROR if ref($self);
     return 1;
 }
 
@@ -197,9 +266,9 @@
        # if this fails this might not be an error (e.g. if $! = EINPROGRESS
        # and socket is nonblocking this is normal), so keep any error
        # handling to the client
-       #DEBUG( 'socket not yet connected' );
+               DEBUG(2, 'socket not yet connected' );
        $self->SUPER::connect(@_) || return;
-       #DEBUG( 'socket connected' );
+               DEBUG(2,'socket connected' );
     }
     return $self->connect_SSL;
 }
@@ -212,7 +281,7 @@
     my ($ssl,$ctx);
     if ( ! ${*$self}{'_SSL_opening'} ) {
        # start ssl connection
-       #DEBUG( 'ssl handshake not started' );
+               DEBUG(2,'ssl handshake not started' );
        ${*$self}{'_SSL_opening'} = 1;
        my $arg_hash = ${*$self}{'_SSL_arguments'};
 
@@ -243,7 +312,7 @@
        ? $args->{Timeout} 
        : ${*$self}{io_socket_timeout}; # from IO::Socket
     if ( defined($timeout) && $timeout>0 && $self->blocking(0) ) {
-       #DEBUG( "set socket to non-blocking to enforce timeout=$timeout" );
+               DEBUG(2, "set socket to non-blocking to enforce 
timeout=$timeout" );
        # timeout was given and socket was blocking
        # enforce timeout with now non-blocking socket
     } else {
@@ -255,17 +324,17 @@
     for my $dummy (1) {
        #DEBUG( 'calling ssleay::connect' );
        my $rv = Net::SSLeay::connect($ssl);
-       #DEBUG( "connect -> rv=$rv" );
+               DEBUG( 3,"Net::SSLeay::connect -> $rv" );
        if ( $rv < 0 ) {
            unless ( $self->_set_rw_error( $ssl,$rv )) {
                $self->error("SSL connect attempt failed with unknown error");
                delete ${*$self}{'_SSL_opening'};
-               ${*$self}{'_SSL_opened'} = 1;
-               #DEBUG( "fatal SSL error: $SSL_ERROR" );
+                               ${*$self}{'_SSL_opened'} = -1;
+                               DEBUG(1, "fatal SSL error: $SSL_ERROR" );
                return $self->fatal_ssl_error();
            }
 
-           #DEBUG( 'ssl handshake in progress' );
+                       DEBUG(2,'ssl handshake in progress' );
            # connect failed because handshake needs to be completed
            # if socket was non-blocking or no timeout was given return with 
this error
            return if ! defined($timeout);
@@ -275,27 +344,27 @@
            if ( $timeout>0 ) {
                my $vec = '';
                vec($vec,$self->fileno,1) = 1;
-               #DEBUG( "waiting for fd to become ready: $SSL_ERROR" );
+                               DEBUG(2, "waiting for fd to become ready: 
$SSL_ERROR" );
                $rv = 
                    $SSL_ERROR == SSL_WANT_READ ? select( 
$vec,undef,undef,$timeout) :
                    $SSL_ERROR == SSL_WANT_WRITE ? select( 
undef,$vec,undef,$timeout) :
                    undef;
            } else {
-               #DEBUG( "handshake failed because no more time" );
+                               DEBUG(2,"handshake failed because no more time" 
);
                $! = ETIMEDOUT
            }
            if ( ! $rv ) {
-               #DEBUG( "handshake failed because socket did not became ready" 
);
+                               DEBUG(2,"handshake failed because socket did 
not became ready" );
                # failed because of timeout, return
                $! ||= ETIMEDOUT;
                delete ${*$self}{'_SSL_opening'};
-               ${*$self}{'_SSL_opened'} = 1;
+                               ${*$self}{'_SSL_opened'} = -1;
                $self->blocking(1); # was blocking before
                return 
            }
 
            # socket is ready, try non-blocking connect again after recomputing 
timeout
-           #DEBUG( "socket ready, retrying connect" );
+                       DEBUG(2,"socket ready, retrying connect" );
            my $now = time();
            $timeout -= $now - $start;
            $start = $now;
@@ -303,14 +372,14 @@
 
        } elsif ( $rv == 0 ) {
            delete ${*$self}{'_SSL_opening'};
-           #DEBUG( "connection failed - connect returned 0" );
+                       DEBUG(2,"connection failed - connect returned 0" );
            $self->error("SSL connect attempt failed because of handshake 
problems" );
-           ${*$self}{'_SSL_opened'} = 1;
+                       ${*$self}{'_SSL_opened'} = -1;
            return $self->fatal_ssl_error();
        }
     }
 
-    #DEBUG( 'ssl handshake done' );
+       DEBUG(2,'ssl handshake done' );
     # ssl connect successful
     delete ${*$self}{'_SSL_opening'};
     ${*$self}{'_SSL_opened'}=1;
@@ -352,13 +421,13 @@
     my $socket = ${*$self}{'_SSL_opening'};
     if ( ! $socket ) {
        # underlying socket not done
-       #DEBUG( 'no socket yet' );
+               DEBUG(2,'no socket yet' );
        $socket = $self->SUPER::accept($class) || return;
-       #DEBUG( 'accept created normal socket '.$socket );
+               DEBUG(2,'accept created normal socket '.$socket );
     }
 
     $self->accept_SSL($socket) || return;
-    #DEBUG( 'accept_SSL ok' );
+       DEBUG(2,'accept_SSL ok' );
 
     return wantarray ? ($socket, getpeername($socket) ) : $socket;
 }
@@ -370,7 +439,7 @@
 
     my $ssl;
     if ( ! ${*$self}{'_SSL_opening'} ) {
-       #DEBUG( 'starting sslifying' );
+               DEBUG(2,'starting sslifying' );
        ${*$self}{'_SSL_opening'} = $socket;
        my $arg_hash = ${*$self}{'_SSL_arguments'};
        ${*$socket}{'_SSL_arguments'} = { %$arg_hash, SSL_server => 0 };
@@ -394,7 +463,7 @@
     $ssl ||= ${*$socket}{'_SSL_object'};
 
     $SSL_ERROR = undef;
-    #DEBUG( 'calling ssleay::accept' );
+       #DEBUG(2,'calling ssleay::accept' );
 
     my $timeout = exists $args->{Timeout} 
        ? $args->{Timeout} 
@@ -410,12 +479,12 @@
     my $start = defined($timeout) && time();
     for my $dummy (1) {
        my $rv = Net::SSLeay::accept($ssl);
-       #DEBUG( 'called ssleay::accept rv='.$rv );
+               DEBUG(3, "Net::SSLeay::accept -> $rv" );
        if ( $rv < 0 ) {
            unless ( $socket->_set_rw_error( $ssl,$rv )) {
                $socket->error("SSL accept attempt failed with unknown error");
                delete ${*$self}{'_SSL_opening'};
-               ${*$socket}{'_SSL_opened'} = 1;
+                               ${*$socket}{'_SSL_opened'} = -1;
                return $socket->fatal_ssl_error();
            }
 
@@ -439,7 +508,7 @@
                # failed because of timeout, return
                $! ||= ETIMEDOUT;
                delete ${*$self}{'_SSL_opening'};
-               ${*$socket}{'_SSL_opened'} = 1;
+                               ${*$socket}{'_SSL_opened'} = -1;
                $socket->blocking(1); # was blocking before
                return 
            }
@@ -453,12 +522,12 @@
        } elsif ( $rv == 0 ) {
            $socket->error("SSL connect accept failed because of handshake 
problems" );
            delete ${*$self}{'_SSL_opening'};
-           ${*$socket}{'_SSL_opened'} = 1;
+                       ${*$socket}{'_SSL_opened'} = -1;
            return $socket->fatal_ssl_error();
        }
     }
 
-    #DEBUG( 'handshake done, socket ready' );
+       DEBUG(2,'handshake done, socket ready' );
     # socket opened
     delete ${*$self}{'_SSL_opening'};
     ${*$socket}{'_SSL_opened'} = 1;
@@ -674,7 +743,8 @@
 sub stop_SSL {
     my $self = shift || return _invalid_object();
     my $stop_args = (ref($_[0]) eq 'HASH') ? $_[0] : [EMAIL PROTECTED];
-    return $self->error("SSL object already closed") unless 
(${*$self}{'_SSL_opened'});
+       return $self->error("SSL object already closed") 
+               unless (${*$self}{'_SSL_opened'} == 1);
 
     if (my $ssl = ${*$self}{'_SSL_object'}) {
        my $shutdown_done;
@@ -748,7 +818,7 @@
 sub kill_socket {
     my $self = shift;
     shutdown($self, 2);
-    $self->close(SSL_no_shutdown => 1) if (${*$self}{'_SSL_opened'});
+       $self->close(SSL_no_shutdown => 1) if (${*$self}{'_SSL_opened'} == 1);
     delete(${*$self}{'_SSL_ctx'});
     return;
 }
@@ -799,7 +869,7 @@
     my $start_handshake = $arg_hash->{SSL_startHandshake};
     if ( ! defined($start_handshake) || $start_handshake ) {
        # if we have no callback force blocking mode
-       #DEBUG( "start handshake" );
+               DEBUG(2, "start handshake" );
        my $blocking = $socket->blocking(1);
        my $result = ${*$socket}{'_SSL_arguments'}{SSL_server}
            ? $socket->accept_SSL(%to)
@@ -807,7 +877,7 @@
        $socket->blocking(0) if !$blocking;
        return $result ? $socket : (bless($socket, $original_class) && ());
     } else {
-       #DEBUG( "dont start handshake: $socket" );
+               DEBUG(2, "dont start handshake: $socket" );
        return $socket; # just return upgraded socket 
     }
 
@@ -835,25 +905,201 @@
     return Net::SSLeay::dump_peer_certificate($ssl);
 }
 
-sub peer_certificate {
+{
+       my %dispatcher = (
+               issuer =>         sub { Net::SSLeay::X509_NAME_oneline( 
Net::SSLeay::X509_get_issuer_name( shift )) },
+               subject =>        sub { Net::SSLeay::X509_NAME_oneline( 
Net::SSLeay::X509_get_subject_name( shift )) },
+       );
+       if ( $Net::SSLeay::VERSION >= 1.30 ) {
+               # I think X509_NAME_get_text_by_NID got added in 1.30
+               $dispatcher{commonName} = sub { 
+                       my $cn = Net::SSLeay::X509_NAME_get_text_by_NID(
+                               Net::SSLeay::X509_get_subject_name( shift ), 
NID_CommonName);
+                       $cn =~s{\0$}{}; # work around Bug in Net::SSLeay <1.33
+                       $cn;
+               }
+       } else {
+               $dispatcher{commonName} = sub { 
+                       croak "you need at least Net::SSLeay version 1.30 for 
getting commonName"
+               }
+       }
+
+       if ( $Net::SSLeay::VERSION >= 1.33 ) {
+               # X509_get_subjectAltNames did not really work before
+               $dispatcher{subjectAltNames} = sub { 
Net::SSLeay::X509_get_subjectAltNames( shift ) };
+       } else {
+               $dispatcher{subjectAltNames} = sub {
+                       croak "you need at least Net::SSLeay version 1.33 for 
getting subjectAltNames"
+               };
+       }
+
+       # alternative names
+       $dispatcher{authority} = $dispatcher{issuer};
+       $dispatcher{owner}     = $dispatcher{subject};
+       $dispatcher{cn}        = $dispatcher{commonName};
+
+       sub peer_certificate {
     my ($self, $field) = @_;
-    my $ssl = $self->_get_ssl_object || return;
+               my $ssl = $self->_get_ssl_object or return;
 
-    my $cert = ${*$self}{'_SSL_certificate'} ||= 
Net::SSLeay::get_peer_certificate($ssl) ||
-       return $self->error("Could not retrieve peer certificate");
+               my $cert = ${*$self}{_SSL_certificate} 
+                       ||= Net::SSLeay::get_peer_certificate($ssl) 
+                       or return $self->error("Could not retrieve peer 
certificate");
 
     if ($field) {
-       my $name = ($field eq "issuer" or $field eq "authority")
-           ? Net::SSLeay::X509_get_issuer_name($cert)
-           : Net::SSLeay::X509_get_subject_name($cert);
+                       my $sub = $dispatcher{$field} or croak 
+                               "invalid argument for peer_certificate, valid 
are: ".join( " ",keys %dispatcher ).
+                               "\nMaybe you need to upgrade your Net::SSLeay";
+                       return $sub->($cert);
+               } else {
+                       return $cert
+               }
+       }
 
-       return $self->error("Could not retrieve peer certificate $field") 
unless ($name);
-       return Net::SSLeay::X509_NAME_oneline($name);
+       # known schemes, possible attributes are:
+       #  - wildcards_in_alt (0, 'leftmost', 'anywhere')
+       #  - wildcards_in_cn (0, 'leftmost', 'anywhere')
+       #  - check_cn (0, 'always', 'when_only')
+
+       my %scheme = (
+               # rfc 4513
+               ldap => {
+                       wildcards_in_cn  => 0,
+                       wildcards_in_alt => 'leftmost',
+                       check_cn         => 'always',
+               },
+               # rfc 2818
+               http => {
+                       wildcards_in_cn  => 0,
+                       wildcards_in_alt => 'anywhere',
+                       check_cn         => 'when_only',
+               },
+               # rfc 3207
+               # This is just a dumb guess
+               # RFC3207 itself just says, that the client should expect the
+               # domain name of the server in the certificate. It doesn't say
+               # anything about wildcards, so I forbid them. It doesn't say
+               # anything about alt names, but other documents show, that alt 
+               # names should be possible. The check_cn value again is a guess.
+               # Fix the spec!
+               smtp => {
+                       wildcards_in_cn  => 0,
+                       wildcards_in_alt => 0,
+                       check_cn         => 'always'
+               },
+               none => {}, # do not check
+       );
+
+       $scheme{www}  = $scheme{http}; # alias
+       $scheme{xmpp} = $scheme{http}; # rfc 3920
+       $scheme{pop3} = $scheme{ldap}; # rfc 2595
+       $scheme{imap} = $scheme{ldap}; # rfc 2595
+       $scheme{acap} = $scheme{ldap}; # rfc 2595
+       $scheme{nntp} = $scheme{ldap}; # rfc 4642
+
+       # function to verify the hostname
+       #
+       # as every application protocol has its own rules to do this
+       # we provide some default rules as well as a user-defined
+       # callback
+
+       sub verify_hostname_of_cert {
+               my $identity = shift;
+               my $cert = shift;
+               my $scheme = shift || 'none';
+               if ( ! ref($scheme) ) {
+                       DEBUG(3, "scheme=$scheme cert=$cert" );
+                       $scheme = $scheme{$scheme} or croak "scheme $scheme not 
defined";
+               }
+
+               # get data from certificate
+               my $commonName = $dispatcher{cn}->($cert);
+               my @altNames = $dispatcher{subjectAltNames}->($cert);
+               DEBUG(3,"identity=$identity cn=$commonName [EMAIL PROTECTED]" );
+
+               if ( my $sub = $scheme->{callback} ) {
+                       # use custom callback
+                       return $sub->($identity,$commonName,@altNames);
+               }
+
+               # is the given hostname an IP address? Then we have to convert 
to network byte order [RFC791][RFC2460]
+
+               my ($ip4,$ip6);
+               if ( $identity =~m{:} ) {
+                       # no IPv4 or hostname have ':'  in it, try IPv6.
+                       #  make sure that Socket6 was loaded properly
+                       UNIVERSAL::can( __PACKAGE__, 'inet_pton' ) or croak
+                               q[Looks like IPv6 address, make sure that 
Socket6 is loaded or make "use IO::Socket::SSL 'inet6'];
+                       $ip6 = inet_pton( $identity ) or croak "'$identity' is 
not IPv6, but neither IPv4 nor hostname";
+               } elsif ( $identity =~m{^\d+\.\d+\.\d+\.\d+$} ) {
+                        # definitly no hostname, try IPv4
+                       $ip4 = inet_aton( $identity ) or croak "'$identity' is 
not IPv4, but neither IPv6 nor hostname";
     } else {
-       return $cert
+                       # assume hostname
+                       if ( $identity !~m{^[a-zA-Z0-9-_\.]+$} ) {
+                               $identity = idn_to_ascii($identity) or
+                                       croak "Warning: Given name '$identity' 
could not be converted to IDNA!";
+                       }
+               }
+
+               # do the actual verification
+               my $check_name = sub {
+                       my ($name,$identity,$wtyp) = @_;
+                       $wtyp ||= '';
+                       my $pattern;
+                       ### IMPORTANT!
+                       # we accept only a single wildcard and only for a 
single part of the FQDN
+                       # e.g *.example.org does match www.example.org but not 
bla.www.example.org
+                       # The RFCs are in this regard unspecific but we don't 
want to have to
+                       # deal with certificates like *.com, *.co.uk or even *
+                       # see also 
http://nils.toedtmann.net/pub/subjectAltName.txt
+                       if ( $wtyp eq 'anywhere' and $name 
=~m{^([\w\-]*)\*(.+)} ) {
+                               $pattern = qr{^\Q$1\E[\w\-]*\Q$2\E$}i;
+                       } elsif ( $wtyp eq 'leftmost' and $name =~m{^\*(\..+)$} 
) {
+                               $pattern = qr{^[\w\-]*\Q$1\E$}i;
+                       } else {
+                               $pattern = qr{^\Q$name}i;
+                       }
+                       return $identity =~ $pattern;
     };
+
+               my $alt_dnsNames = 0;
+               while (@altNames) {
+                       my ($type, $name) = splice (@altNames, 0, 2);
+                       if ( $type == GEN_IPADD ) {
+                               # exakt match needed for IP
+                               # $name is already packed format (inet_xton)
+                               return 1 if 
+                                       $ip6 ? $ip6 eq $name : 
+                                       $ip4 ? $ip4 eq $name :
+                                       0;
+
+                       } elsif ( $type == GEN_DNS ) {
+                               $name =~s/\s+$//; $name =~s/^\s+//;
+                               $alt_dnsNames++;
+                               
$check_name->($name,$identity,$scheme->{wildcards_in_alt})
+                                       and return 1;
+                       }
+               }
+
+               if ( $scheme->{check_cn} eq 'always' or 
+                       $scheme->{check_cn} eq 'when_only' and !$alt_dnsNames) {
+                       
$check_name->($commonName,$identity,$scheme->{wildcars_in_cn})
+                               and return 1;
+               }
+
+               return 0; # no match
+       }
 }
 
+sub verify_hostname {
+       my $self = shift;
+       my $host = shift;
+       my $cert = $self->peer_certificate;
+       return verify_hostname_of_cert( $host,$cert,@_ );
+}
+
+
 sub get_cipher {
     my $ssl = shift()->_get_ssl_object || return;
     return Net::SSLeay::get_cipher($ssl);
@@ -870,7 +1116,9 @@
     $@ = $self->errstr;
     if (defined $error_trap and ref($error_trap) eq 'CODE') {
        $error_trap->($self, $self->errstr()."\n".$self->get_ssleay_error());
-    } else { $self->kill_socket; }
+       } else { 
+               $self->kill_socket; 
+       }
     return;
 }
 
@@ -884,7 +1132,7 @@
 sub error {
     my ($self, $error, $destroy_socket) = @_;
     $error .= Net::SSLeay::ERR_error_string(Net::SSLeay::ERR_get_error());
-    carp $error."\n".$self->get_ssleay_error() if $DEBUG;
+       DEBUG(2, $error."\n".$self->get_ssleay_error());
     $SSL_ERROR = dualvar( -1, $error );
     ${*$self}{'_SSL_last_err'} = $SSL_ERROR if (ref($self));
     return;
@@ -893,7 +1141,8 @@
 
 sub DESTROY {
     my $self = shift || return;
-    $self->close(_SSL_in_DESTROY => 1, SSL_no_shutdown => 1) if 
(${*$self}{'_SSL_opened'});
+       $self->close(_SSL_in_DESTROY => 1, SSL_no_shutdown => 1) 
+               if (${*$self}{'_SSL_opened'} == 1);
     delete(${*$self}{'_SSL_ctx'});
 }
 
@@ -918,10 +1167,18 @@
     $GLOBAL_CONTEXT_ARGS->{SSL_session_cache} = shift;
 }
 
+sub set_ctx_defaults {
+       my %args = @_;
+       while ( my ($k,$v) = each %args ) {
+               $k =~s{^(SSL_)?}{SSL_};
+               $GLOBAL_CONTEXT_ARGS->{$k} = $v;
+       }
+}
 
+
 sub opened {
     my $self = shift;
-    return IO::Handle::opened($self) && ${*$self}{'_SSL_opened'};
+       return IO::Handle::opened($self) && ( ${*$self}{'_SSL_opened'} == 1 );
 }
 
 sub opening {
@@ -935,8 +1192,10 @@
 
 #Redundant IO::Handle functionality
 sub getline  { return(scalar shift->readline()) }
-sub getlines { if (wantarray()) { return(shift->readline()) }
-              else { croak("Use of getlines() not allowed in scalar context"); 
 }}
+sub getlines { 
+       return(shift->readline()) if wantarray();
+       croak("Use of getlines() not allowed in scalar context");
+}
 
 #Useless IO::Handle functionality
 sub truncate { croak("Use of truncate() not allowed with SSL") }
@@ -971,15 +1230,15 @@
     bless \$handle, $class;
 }
 
-sub READ     { ${shift()}->sysread  (@_) }
-sub READLINE { ${shift()}->readline (@_) }
-sub GETC     { ${shift()}->getc     (@_) }
+sub READ     { ${shift()}->sysread(@_) }
+sub READLINE { ${shift()}->readline(@_) }
+sub GETC     { ${shift()}->getc(@_) }
 
-sub PRINT    { ${shift()}->print    (@_) }
-sub PRINTF   { ${shift()}->printf   (@_) }
-sub WRITE    { ${shift()}->syswrite (@_) }
+sub PRINT    { ${shift()}->print(@_) }
+sub PRINTF   { ${shift()}->printf(@_) }
+sub WRITE    { ${shift()}->syswrite(@_) }
 
-sub FILENO   { ${shift()}->fileno   (@_) }
+sub FILENO   { ${shift()}->fileno(@_) }
 
 sub TELL     { $! = EBADF; return -1 }
 sub BINMODE  { return 0 }  # not perfect, but better than not implementing the 
method
@@ -1007,7 +1266,7 @@
 # it can be blessed.
 sub new {
     my $class = shift;
-    DEBUG( "$class @_" );
+       #DEBUG( "$class @_" );
     my $arg_hash = (ref($_[0]) eq 'HASH') ? $_[0] : [EMAIL PROTECTED];
 
     my $ctx_object = $arg_hash->{'SSL_reuse_ctx'};
@@ -1036,24 +1295,23 @@
     # buffer was written and not block for the rest
     # SSL_MODE_ENABLE_PARTIAL_WRITE can be necessary for non-blocking because 
we
     # cannot guarantee, that the location of the buffer stays constant
-    Net::SSLeay::CTX_set_mode( $ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER
-       |SSL_MODE_ENABLE_PARTIAL_WRITE);
+       Net::SSLeay::CTX_set_mode( $ctx, 
+               
SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_ENABLE_PARTIAL_WRITE);
 
 
-    my ($verify_mode, $verify_cb) = 
@{$arg_hash}{'SSL_verify_mode','SSL_verify_callback'};
-    unless ($verify_mode == Net::SSLeay::VERIFY_NONE())
-    {
-       &Net::SSLeay::CTX_load_verify_locations
-           ($ctx, @{$arg_hash}{'SSL_ca_file','SSL_ca_path'}) ||
-            return IO::Socket::SSL->error("Invalid certificate authority 
locations");
+       my $verify_mode = $arg_hash->{SSL_verify_mode};
+       unless ($verify_mode == Net::SSLeay::VERIFY_NONE()) {
+               Net::SSLeay::CTX_load_verify_locations(
+                       $ctx, $arg_hash->{SSL_ca_file},$arg_hash->{SSL_ca_path}
+               ) || return IO::Socket::SSL->error("Invalid certificate 
authority locations");
     }
 
     if ($arg_hash->{'SSL_check_crl'}) {
-       if (Net::SSLeay::OPENSSL_VERSION_NUMBER() >= 0x0090702f)
-       {
-           Net::SSLeay::X509_STORE_CTX_set_flags
-               (Net::SSLeay::CTX_get_cert_store($ctx),
-                Net::SSLeay::X509_V_FLAG_CRL_CHECK());
+               if (Net::SSLeay::OPENSSL_VERSION_NUMBER() >= 0x0090702f) {
+                       Net::SSLeay::X509_STORE_CTX_set_flags(
+                               Net::SSLeay::CTX_get_cert_store($ctx),
+                               Net::SSLeay::X509_V_FLAG_CRL_CHECK()
+                       );
        } else {
            return IO::Socket::SSL->error("CRL not supported for OpenSSL < 
v0.9.7b");
        }
@@ -1108,8 +1366,8 @@
        }
     }
 
-    my $verify_callback = $verify_cb &&
-       sub {
+       my $verify_cb = $arg_hash->{SSL_verify_callback};
+       my $verify_callback = $verify_cb && sub {
            my ($ok, $ctx_store) = @_;
            my ($cert, $error);
            if ($ctx_store) {
@@ -1119,13 +1377,14 @@
                    
Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_subject_name($cert));
                $error &&= Net::SSLeay::ERR_error_string($error);
            }
+               DEBUG(3, "ok=$ok cert=$cert" );
            return $verify_cb->($ok, $ctx_store, $cert, $error);
        };
 
     Net::SSLeay::CTX_set_verify($ctx, $verify_mode, $verify_callback);
 
     $ctx_object = { context => $ctx };
-    DEBUG( "new ctx $ctx" );
+       DEBUG(3, "new ctx $ctx" );
     $CTX_CREATED_IN_THIS_THREAD{$ctx} = 1;
 
     if ( my $cache = $arg_hash->{SSL_session_cache} ) {
@@ -1156,14 +1415,14 @@
 }
 
 
-sub CLONE { %CTX_CREATED_IN_THIS_THREAD = (); DEBUG( "clone!" ) }
+sub CLONE { %CTX_CREATED_IN_THIS_THREAD = (); }
 sub DESTROY {
     my $self = shift;
     if ( my $ctx = $self->{context} ) {
-       DEBUG( "free ctx $ctx open=".join( " ",keys %CTX_CREATED_IN_THIS_THREAD 
));
+               DEBUG( 3,"free ctx $ctx open=".join( " ",keys 
%CTX_CREATED_IN_THIS_THREAD ));
        if ( %CTX_CREATED_IN_THIS_THREAD and 
            delete $CTX_CREATED_IN_THIS_THREAD{$ctx} ) {
-           DEBUG( "OK free ctx $ctx" );
+                       DEBUG( 3,"OK free ctx $ctx" );
            Net::SSLeay::CTX_free($ctx);
        }
     }
@@ -1237,18 +1496,16 @@
 
 =head1 SYNOPSIS
 
+       use strict;
     use IO::Socket::SSL;
 
-    my $client = IO::Socket::SSL->new("www.example.com:https");
+       my $client = IO::Socket::SSL->new("www.example.com:https") 
+               || warn "I encountered a problem: ".IO::Socket::SSL::errstr();
+       $client->verify_hostname( 'www.example.com','http' )
+               || die "hostname verification failed";
 
-    if ($client) {
        print $client "GET / HTTP/1.0\r\n\r\n";
        print <$client>;
-       close $client;
-    } else {
-       warn "I encountered a problem: ",
-         IO::Socket::SSL::errstr();
-    }
 
 
 =head1 DESCRIPTION
@@ -1376,6 +1633,7 @@
 (0x00) does no authentication.  You may combine 0x01 (verify peer), 0x02 (fail
 verification if no peer certificate exists; ignored for clients), and 0x04
 (verify client once) to change the default.
+See OpenSSL man page for SSL_CTX_set_verify for more information.
 
 =item SSL_verify_callback
 
@@ -1388,6 +1646,22 @@
 The function should return 1 or 0, depending on whether it thinks the 
certificate
 is valid or invalid.  The default is to let OpenSSL do all of the busy work.
 
+=item SSL_verifycn_scheme
+
+Set the scheme used to automatically verify the hostname of the peer.
+See the information about the verification schemes in B<verify_hostname>.
+The default is undef, e.g. to not automatically verify the hostname.
+
+=item SSL_verifycn_name
+
+Set the name which is used in verification of hostname. If SSL_verifycn_scheme
+is set and no SSL_verifycn_name is given it will try to use the PeerHost and
+PeerAddr settings and fail if no name caan be determined.
+
+Using PeerHost or PeerAddr works only if you create the connection directly
+with C<< IO::Socket::SSL->new >>, if an IO::Socket::INET object is upgraded
+with B<start_SSL> the name has to be given in B<SSL_verifycn_name>.
+
 =item SSL_check_crl
 
 If you want to verify that the peer certificate has not been revoked by the
@@ -1504,13 +1778,99 @@
 
 =item B<peer_certificate($field)>
 
-If a peer certificate exists, this function can retrieve values from it.  
Right now, the
-only fields it can return are "authority" and "owner" (or "issuer" and 
"subject" if
-you want to use OpenSSL names), corresponding to the certificate authority 
that signed the
-peer certificate and the owner of the peer certificate.  This function returns 
a string
-with all the information about the particular field in one parsable line.
-If no field is given it returns the full certificate (x509).
+If a peer certificate exists, this function can retrieve values from it. 
+If no field is given the internal representation of certificate from 
Net::SSLeay is
+returned.
+The following fields can be queried:
 
+=over 8
+
+=item authority (alias issuer)
+
+The certificate authority which signed the certificate.
+
+=item owner (alias subject)
+
+The owner of the certificate.
+
+=item commonName (alias cn) - only for Net::SSLeay version >=1.30
+
+The common name, usually the server name for SSL certificates.
+
+=item subjectAltNames - only for Net::SSLeay version >=1.33
+
+Alternative names for the subject, usually different names for the same
+server, like example.org, example.com, *.example.com.
+
+It returns a list of (typ,value) with typ GEN_DNS, GEN_IPADD etc (these
+constants are exported from IO::Socket::SSL). 
+See Net::SSLeay::X509_get_subjectAltNames.
+
+=back
+
+=item B<verify_hostname($hostname,$scheme)>
+
+This verifies the given hostname against the peer certificate using the
+given scheme. Hostname is usually what you specify within the PeerAddr.
+
+Verification of hostname against a certificate is different between various
+applications and RFCs. Some scheme allow wildcards for hostnames, some only
+in subjectAltNames, and even their different wildcard schemes are possible.
+
+To ease the verification the following schemes are predefined:
+
+=over 8
+
+=item ldap (rfc4513), pop3,imap,acap (rfc2995), nntp (rfc4642)
+
+Simple wildcards in subjectAltNames are possible, e.g. *.example.org matches
+www.example.org but not lala.www.example.org. If nothing from subjectAltNames
+match it checks against the common name, but there are no wildcards allowed.
+
+=item http (rfc2818), alias is www
+
+Extended wildcards in subjectAltNames are possible, e.g. *.example.org or
+even www*.example.org. Wildcards in the common name are not allowed. The common
+name will be only checked if no names are given in subjectAltNames.
+
+=item smtp (rfc3207)
+
+This RFC doesn't say much useful about the verification so it just assumes
+that subjectAltNames are possible, but no wildcards are possible anywhere.
+
+=back
+
+The scheme can be given either by specifying the name for one of the above 
predefined 
+schemes, by using a callback (see below) or by using a hash which can have the 
+following keys and values:
+
+=over 8
+
+=item check_cn:  0|'always'|'when_only'
+
+Determines if the common name gets checked. If 'always' it will always be 
checked 
+(like in ldap), if 'when_only' it will only be checked if no names are given in
+subjectAltNames (like in http), for any other values the common name will not 
be checked.
+
+=item wildcards_in_alt: 0|'leftmost'|'anywhere'
+
+Determines if and where wildcards in subjectAltNames are possible. If 
'leftmost'
+only cases like *.example.org will be possible (like in ldap), for 'anywhere' 
+www*.example.org is possible too (like http), dangerous things like but 
www.*.org 
+or even '*' will not be allowed.
+
+=item wildcards_in_cn: 0|'leftmost'|'anywhere'
+
+Similar to wildcards_in_alt, but checks the common name. There is no predefined
+scheme which allows wildcards in common names.
+
+=back
+
+If you give a subroutine for verification it will be called with the arguments
+($hostname,$commonName,@subjectAltNames), where hostname is the name given for
+verification, commonName is the result from peer_certificate('cn') and
+subjectAltNames is the result from peer_certificate('subjectAltNames').
+
 =item B<errstr()>
 
 Returns the last error (in string form) that occurred.  If you do not have a 
real
@@ -1577,9 +1937,27 @@
 See the SSL_session_cache option of new() for more details.  Note that this 
sets the default
 cache globally, so use with caution.
 
+=item B<IO::Socket::SSL::set_ctx_defaults(%args)>
 
+With this function one can set defaults for all SSL_* parameter used for 
creation of
+the context, like the SSL_verify* parameter.
+
+=over 8
+
+=item mode - set default SSL_verify_mode
+
+=item callback - set default SSL_verify_callback
+
+=item scheme - set default SSL_verifycn_scheme
+
+=item name - set default SSL_verifycn_name
+
+If not given and scheme is hash reference with key callback it will be set to 
'unknown'
+
 =back
 
+=back
+
 The following methods are unsupported (not to mention futile!) and 
IO::Socket::SSL
 will emit a large CROAK() if you are silly enough to use them:
 
@@ -1637,36 +2015,31 @@
 
 If you are having problems using IO::Socket::SSL despite the fact that can 
recite backwards
 the section of this documentation labelled 'Using SSL', you should try 
enabling debugging.  To
-specify the debug level, pass 'debug#' (where # is a number from 0 to 4) to 
IO::Socket::SSL
-when calling it:
+specify the debug level, pass 'debug#' (where # is a number from 0 to 3) to 
IO::Socket::SSL
+when calling it. 
+The debug level will also be propagated to Net::SSLeay::trace, see also 
L<Net::SSLeay>:
 
 =over 4
 
 =item use IO::Socket::SSL qw(debug0);
 
-#No debugging (default).
+No debugging (default).
 
 =item use IO::Socket::SSL qw(debug1);
 
-#Only print out errors.
+Print out errors from IO::Socket::SSL and ciphers from Net::SSLeay.
 
 =item use IO::Socket::SSL qw(debug2);
 
-#Print out errors and cipher negotiation.
+Print also information about call flow from IO::Socket::SSL and progress
+information from Net::SSLeay.
 
 =item use IO::Socket::SSL qw(debug3);
 
-#Print out progress, ciphers, and errors.
+Print also some data dumps from IO::Socket::SSL and from Net::SSLeay.
 
-=item use IO::Socket::SSL qw(debug4);
-
-#Print out everything, including data.
-
 =back
 
-You can also set $IO::Socket::SSL::DEBUG to 0-4, but that's a bit of a 
mouthful,
-isn't it?
-
 =head1 EXAMPLES
 
 See the 'example' directory.
@@ -1677,7 +2050,7 @@
 This is because IO::Socket::SSL is based on Net::SSLeay which 
 uses a global object to access some of the API of openssl
 and is therefore not threadsafe.
-It might probably work if you don't use SSL_verify_cb and
+It might probably work if you don't use SSL_verify_callback and
 SSL_password_cb.
 
 IO::Socket::SSL does not work together with Storable::fd_retrieve/fd_store.

Attachment: signature.asc
Description: Digital signature

Reply via email to