1e1051a39Sopenharmony_ci#! /usr/bin/env perl
2e1051a39Sopenharmony_ci# Copyright 2018-2021 The OpenSSL Project Authors. All Rights Reserved.
3e1051a39Sopenharmony_ci#
4e1051a39Sopenharmony_ci# Licensed under the Apache License 2.0 (the "License").  You may not use
5e1051a39Sopenharmony_ci# this file except in compliance with the License.  You can obtain a copy
6e1051a39Sopenharmony_ci# in the file LICENSE in the source distribution or at
7e1051a39Sopenharmony_ci# https://www.openssl.org/source/license.html
8e1051a39Sopenharmony_ci
9e1051a39Sopenharmony_ciuse strict;
10e1051a39Sopenharmony_cino strict 'refs';               # To be able to use strings as function refs
11e1051a39Sopenharmony_ciuse OpenSSL::Test;
12e1051a39Sopenharmony_ciuse OpenSSL::Test::Utils;
13e1051a39Sopenharmony_ciuse Errno qw(:POSIX);
14e1051a39Sopenharmony_ciuse POSIX qw(:limits_h strerror);
15e1051a39Sopenharmony_ci
16e1051a39Sopenharmony_ciuse Data::Dumper;
17e1051a39Sopenharmony_ci
18e1051a39Sopenharmony_cisetup('test_errstr');
19e1051a39Sopenharmony_ci
20e1051a39Sopenharmony_ci# In a cross compiled situation, there are chances that our
21e1051a39Sopenharmony_ci# application is linked against different C libraries than
22e1051a39Sopenharmony_ci# perl, and may thereby get different error messages for the
23e1051a39Sopenharmony_ci# same error.
24e1051a39Sopenharmony_ci# The safest is not to test under such circumstances.
25e1051a39Sopenharmony_ciplan skip_all => 'This is unsupported for cross compiled configurations'
26e1051a39Sopenharmony_ci    if config('CROSS_COMPILE');
27e1051a39Sopenharmony_ci
28e1051a39Sopenharmony_ci# The same can be said when compiling OpenSSL with mingw configuration
29e1051a39Sopenharmony_ci# on Windows when built with msys perl.  Similar problems are also observed
30e1051a39Sopenharmony_ci# in MSVC builds, depending on the perl implementation used.
31e1051a39Sopenharmony_ciplan skip_all => 'This is unsupported on MSYS/MinGW or MSWin32'
32e1051a39Sopenharmony_ci    if $^O eq 'msys' or $^O eq 'MSWin32';
33e1051a39Sopenharmony_ci
34e1051a39Sopenharmony_ciplan skip_all => 'OpenSSL is configured "no-autoerrinit" or "no-err"'
35e1051a39Sopenharmony_ci    if disabled('autoerrinit') || disabled('err');
36e1051a39Sopenharmony_ci
37e1051a39Sopenharmony_ci# OpenSSL constants found in <openssl/err.h>
38e1051a39Sopenharmony_ciuse constant ERR_SYSTEM_FLAG => INT_MAX + 1;
39e1051a39Sopenharmony_ciuse constant ERR_LIB_OFFSET => 23; # Offset of the "library" errcode section
40e1051a39Sopenharmony_ci
41e1051a39Sopenharmony_ci# OpenSSL "library" numbers
42e1051a39Sopenharmony_ciuse constant ERR_LIB_NONE => 1;
43e1051a39Sopenharmony_ci
44e1051a39Sopenharmony_ci# We use Errno::EXPORT_OK as a list of known errno values on the current
45e1051a39Sopenharmony_ci# system.  libcrypto's ERR should either use the same string as perl, or if
46e1051a39Sopenharmony_ci# it was outside the range that ERR looks at, ERR gives the reason string
47e1051a39Sopenharmony_ci# "reason(nnn)", where nnn is the errno number.
48e1051a39Sopenharmony_ci
49e1051a39Sopenharmony_ciplan tests => scalar @Errno::EXPORT_OK
50e1051a39Sopenharmony_ci    +1                          # Checking that error 128 gives 'reason(128)'
51e1051a39Sopenharmony_ci    +1                          # Checking that error 0 gives the library name
52e1051a39Sopenharmony_ci    +1;                         # Check trailing whitespace is removed.
53e1051a39Sopenharmony_ci
54e1051a39Sopenharmony_ci# Test::More:ok() has a sub prototype, which means we need to use the '&ok'
55e1051a39Sopenharmony_ci# syntax to force it to accept a list as a series of arguments.
56e1051a39Sopenharmony_ci
57e1051a39Sopenharmony_ciforeach my $errname (@Errno::EXPORT_OK) {
58e1051a39Sopenharmony_ci    # The error names are perl constants, which are implemented as functions
59e1051a39Sopenharmony_ci    # returning the numeric value of that name.
60e1051a39Sopenharmony_ci    my $errcode = "Errno::$errname"->();
61e1051a39Sopenharmony_ci
62e1051a39Sopenharmony_ci  SKIP: {
63e1051a39Sopenharmony_ci      # On most systems, there is no E macro for errcode zero in <errno.h>,
64e1051a39Sopenharmony_ci      # which means that it seldom comes up here.  However, reports indicate
65e1051a39Sopenharmony_ci      # that some platforms do have an E macro for errcode zero.
66e1051a39Sopenharmony_ci      # With perl, errcode zero is a bit special.  Perl consistently gives
67e1051a39Sopenharmony_ci      # the empty string for that one, while the C strerror() may give back
68e1051a39Sopenharmony_ci      # something else.  The easiest way to deal with that possible mismatch
69e1051a39Sopenharmony_ci      # is to skip this errcode.
70e1051a39Sopenharmony_ci      skip "perl error strings and ssystem error strings for errcode 0 differ", 1
71e1051a39Sopenharmony_ci          if $errcode == 0;
72e1051a39Sopenharmony_ci      # On some systems (for example Hurd), there are negative error codes.
73e1051a39Sopenharmony_ci      # These are currently unsupported in OpenSSL error reports.
74e1051a39Sopenharmony_ci      skip "negative error codes are not supported in OpenSSL", 1
75e1051a39Sopenharmony_ci          if $errcode < 0;
76e1051a39Sopenharmony_ci
77e1051a39Sopenharmony_ci      &ok(match_syserr_reason($errcode));
78e1051a39Sopenharmony_ci    }
79e1051a39Sopenharmony_ci}
80e1051a39Sopenharmony_ci
81e1051a39Sopenharmony_ci# OpenSSL library 1 is the "unknown" library
82e1051a39Sopenharmony_ci&ok(match_opensslerr_reason(ERR_LIB_NONE << ERR_LIB_OFFSET | 256,
83e1051a39Sopenharmony_ci                            "reason(256)"));
84e1051a39Sopenharmony_ci# Reason code 0 of any library gives the library name as reason
85e1051a39Sopenharmony_ci&ok(match_opensslerr_reason(ERR_LIB_NONE << ERR_LIB_OFFSET |   0,
86e1051a39Sopenharmony_ci                            "unknown library"));
87e1051a39Sopenharmony_ci&ok(match_any("Trailing whitespace  \n\t", "?", ( "Trailing whitespace" )));
88e1051a39Sopenharmony_ci
89e1051a39Sopenharmony_ciexit 0;
90e1051a39Sopenharmony_ci
91e1051a39Sopenharmony_ci# For an error string "error:xxxxxxxx:lib:func:reason", this returns
92e1051a39Sopenharmony_ci# the following array:
93e1051a39Sopenharmony_ci#
94e1051a39Sopenharmony_ci# ( "xxxxxxxx", "lib", "func", "reason" )
95e1051a39Sopenharmony_cisub split_error {
96e1051a39Sopenharmony_ci    # Limit to 5 items, in case the reason contains a colon
97e1051a39Sopenharmony_ci    my @erritems = split /:/, $_[0], 5;
98e1051a39Sopenharmony_ci
99e1051a39Sopenharmony_ci    # Remove the first item, which is always "error"
100e1051a39Sopenharmony_ci    shift @erritems;
101e1051a39Sopenharmony_ci
102e1051a39Sopenharmony_ci    return @erritems;
103e1051a39Sopenharmony_ci}
104e1051a39Sopenharmony_ci
105e1051a39Sopenharmony_ci# Compares the first argument as string to each of the arguments 3 and on,
106e1051a39Sopenharmony_ci# and returns an array of two elements:
107e1051a39Sopenharmony_ci# 0:  True if the first argument matched any of the others, otherwise false
108e1051a39Sopenharmony_ci# 1:  A string describing the test
109e1051a39Sopenharmony_ci# The returned array can be used as the arguments to Test::More::ok()
110e1051a39Sopenharmony_cisub match_any {
111e1051a39Sopenharmony_ci    my $first = shift;
112e1051a39Sopenharmony_ci    my $desc = shift;
113e1051a39Sopenharmony_ci    my @strings = @_;
114e1051a39Sopenharmony_ci
115e1051a39Sopenharmony_ci    # ignore trailing whitespace
116e1051a39Sopenharmony_ci    $first =~ s/\s+$//;
117e1051a39Sopenharmony_ci
118e1051a39Sopenharmony_ci    if (scalar @strings > 1) {
119e1051a39Sopenharmony_ci        $desc = "match '$first' ($desc) with one of ( '"
120e1051a39Sopenharmony_ci            . join("', '", @strings) . "' )";
121e1051a39Sopenharmony_ci    } else {
122e1051a39Sopenharmony_ci        $desc = "match '$first' ($desc) with '$strings[0]'";
123e1051a39Sopenharmony_ci    }
124e1051a39Sopenharmony_ci
125e1051a39Sopenharmony_ci    return ( scalar(
126e1051a39Sopenharmony_ci                 grep { ref $_ eq 'Regexp' ? $first =~ $_ : $first eq $_ }
127e1051a39Sopenharmony_ci                 @strings
128e1051a39Sopenharmony_ci             ) > 0,
129e1051a39Sopenharmony_ci             $desc );
130e1051a39Sopenharmony_ci}
131e1051a39Sopenharmony_ci
132e1051a39Sopenharmony_cisub match_opensslerr_reason {
133e1051a39Sopenharmony_ci    my $errcode = shift;
134e1051a39Sopenharmony_ci    my @strings = @_;
135e1051a39Sopenharmony_ci
136e1051a39Sopenharmony_ci    my $errcode_hex = sprintf "%x", $errcode;
137e1051a39Sopenharmony_ci    my $reason =
138e1051a39Sopenharmony_ci        ( run(app([ qw(openssl errstr), $errcode_hex ]), capture => 1) )[0];
139e1051a39Sopenharmony_ci    $reason =~ s|\R$||;
140e1051a39Sopenharmony_ci    $reason = ( split_error($reason) )[3];
141e1051a39Sopenharmony_ci
142e1051a39Sopenharmony_ci    return match_any($reason, $errcode_hex, @strings);
143e1051a39Sopenharmony_ci}
144e1051a39Sopenharmony_ci
145e1051a39Sopenharmony_cisub match_syserr_reason {
146e1051a39Sopenharmony_ci    my $errcode = shift;
147e1051a39Sopenharmony_ci
148e1051a39Sopenharmony_ci    my @strings = ();
149e1051a39Sopenharmony_ci    # The POSIX reason string
150e1051a39Sopenharmony_ci    push @strings, eval {
151e1051a39Sopenharmony_ci          # Set $! to the error number...
152e1051a39Sopenharmony_ci          local $! = $errcode;
153e1051a39Sopenharmony_ci          # ... and $! will give you the error string back
154e1051a39Sopenharmony_ci          $!
155e1051a39Sopenharmony_ci    };
156e1051a39Sopenharmony_ci    # Occasionally, we get an error code that is simply not translatable
157e1051a39Sopenharmony_ci    # to POSIX semantics on VMS, and we get an error string saying so.
158e1051a39Sopenharmony_ci    push @strings, qr/^non-translatable vms error code:/ if $^O eq 'VMS';
159e1051a39Sopenharmony_ci    # The OpenSSL fallback string
160e1051a39Sopenharmony_ci    push @strings, "reason($errcode)";
161e1051a39Sopenharmony_ci
162e1051a39Sopenharmony_ci    return match_opensslerr_reason(ERR_SYSTEM_FLAG | $errcode, @strings);
163e1051a39Sopenharmony_ci}
164