#! /usr/bin/env perl # Copyright 2018-2021 The OpenSSL Project Authors. All Rights Reserved. # # Licensed under the Apache License 2.0 (the "License"). You may not use # this file except in compliance with the License. You can obtain a copy # in the file LICENSE in the source distribution or at # https://www.openssl.org/source/license.html use strict; no strict 'refs'; # To be able to use strings as function refs use OpenSSL::Test; use OpenSSL::Test::Utils; use Errno qw(:POSIX); use POSIX qw(:limits_h strerror); use Data::Dumper; setup('test_errstr'); # In a cross compiled situation, there are chances that our # application is linked against different C libraries than # perl, and may thereby get different error messages for the # same error. # The safest is not to test under such circumstances. plan skip_all => 'This is unsupported for cross compiled configurations' if config('CROSS_COMPILE'); # The same can be said when compiling OpenSSL with mingw configuration # on Windows when built with msys perl. Similar problems are also observed # in MSVC builds, depending on the perl implementation used. plan skip_all => 'This is unsupported on MSYS/MinGW or MSWin32' if $^O eq 'msys' or $^O eq 'MSWin32'; plan skip_all => 'OpenSSL is configured "no-autoerrinit" or "no-err"' if disabled('autoerrinit') || disabled('err'); # OpenSSL constants found in use constant ERR_SYSTEM_FLAG => INT_MAX + 1; use constant ERR_LIB_OFFSET => 23; # Offset of the "library" errcode section # OpenSSL "library" numbers use constant ERR_LIB_NONE => 1; # We use Errno::EXPORT_OK as a list of known errno values on the current # system. libcrypto's ERR should either use the same string as perl, or if # it was outside the range that ERR looks at, ERR gives the reason string # "reason(nnn)", where nnn is the errno number. plan tests => scalar @Errno::EXPORT_OK +1 # Checking that error 128 gives 'reason(128)' +1 # Checking that error 0 gives the library name +1; # Check trailing whitespace is removed. # Test::More:ok() has a sub prototype, which means we need to use the '&ok' # syntax to force it to accept a list as a series of arguments. foreach my $errname (@Errno::EXPORT_OK) { # The error names are perl constants, which are implemented as functions # returning the numeric value of that name. my $errcode = "Errno::$errname"->(); SKIP: { # On most systems, there is no E macro for errcode zero in , # which means that it seldom comes up here. However, reports indicate # that some platforms do have an E macro for errcode zero. # With perl, errcode zero is a bit special. Perl consistently gives # the empty string for that one, while the C strerror() may give back # something else. The easiest way to deal with that possible mismatch # is to skip this errcode. skip "perl error strings and ssystem error strings for errcode 0 differ", 1 if $errcode == 0; # On some systems (for example Hurd), there are negative error codes. # These are currently unsupported in OpenSSL error reports. skip "negative error codes are not supported in OpenSSL", 1 if $errcode < 0; &ok(match_syserr_reason($errcode)); } } # OpenSSL library 1 is the "unknown" library &ok(match_opensslerr_reason(ERR_LIB_NONE << ERR_LIB_OFFSET | 256, "reason(256)")); # Reason code 0 of any library gives the library name as reason &ok(match_opensslerr_reason(ERR_LIB_NONE << ERR_LIB_OFFSET | 0, "unknown library")); &ok(match_any("Trailing whitespace \n\t", "?", ( "Trailing whitespace" ))); exit 0; # For an error string "error:xxxxxxxx:lib:func:reason", this returns # the following array: # # ( "xxxxxxxx", "lib", "func", "reason" ) sub split_error { # Limit to 5 items, in case the reason contains a colon my @erritems = split /:/, $_[0], 5; # Remove the first item, which is always "error" shift @erritems; return @erritems; } # Compares the first argument as string to each of the arguments 3 and on, # and returns an array of two elements: # 0: True if the first argument matched any of the others, otherwise false # 1: A string describing the test # The returned array can be used as the arguments to Test::More::ok() sub match_any { my $first = shift; my $desc = shift; my @strings = @_; # ignore trailing whitespace $first =~ s/\s+$//; if (scalar @strings > 1) { $desc = "match '$first' ($desc) with one of ( '" . join("', '", @strings) . "' )"; } else { $desc = "match '$first' ($desc) with '$strings[0]'"; } return ( scalar( grep { ref $_ eq 'Regexp' ? $first =~ $_ : $first eq $_ } @strings ) > 0, $desc ); } sub match_opensslerr_reason { my $errcode = shift; my @strings = @_; my $errcode_hex = sprintf "%x", $errcode; my $reason = ( run(app([ qw(openssl errstr), $errcode_hex ]), capture => 1) )[0]; $reason =~ s|\R$||; $reason = ( split_error($reason) )[3]; return match_any($reason, $errcode_hex, @strings); } sub match_syserr_reason { my $errcode = shift; my @strings = (); # The POSIX reason string push @strings, eval { # Set $! to the error number... local $! = $errcode; # ... and $! will give you the error string back $! }; # Occasionally, we get an error code that is simply not translatable # to POSIX semantics on VMS, and we get an error string saying so. push @strings, qr/^non-translatable vms error code:/ if $^O eq 'VMS'; # The OpenSSL fallback string push @strings, "reason($errcode)"; return match_opensslerr_reason(ERR_SYSTEM_FLAG | $errcode, @strings); }