The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#===============================================================================
#
# inc/Module/Install/PRIVATE/Filter/Crypto.pm
#
# DESCRIPTION
#   Distribution-specific Module::Install private extension class for
#   Filter-Crypto distribution.
#
# COPYRIGHT
#   Copyright (C) 2004-2006 Steve Hay.  All rights reserved.
#
# LICENCE
#   You may distribute under the terms of either the GNU General Public License
#   or the Artistic License, as specified in the LICENCE file.
#
#===============================================================================

package Module::Install::PRIVATE::Filter::Crypto;

use 5.006000;

use strict;
use warnings;

use Config qw(%Config);
use Cwd qw(abs_path);
use Fcntl;
use File::Basename qw(dirname);
use File::Copy qw(copy);
use File::Spec::Functions qw(canonpath catdir catfile updir);
use Text::Wrap qw(wrap);

use constant CIPHER_NAME_DES        => 'DES';
use constant CIPHER_NAME_DES_EDE    => 'DES_EDE';
use constant CIPHER_NAME_DES_EDE3   => 'DES_EDE3';
use constant CIPHER_NAME_RC4        => 'RC4';
use constant CIPHER_NAME_IDEA       => 'IDEA';
use constant CIPHER_NAME_RC2        => 'RC2';
use constant CIPHER_NAME_DESX       => 'DESX';
use constant CIPHER_NAME_BLOWFISH   => 'Blowfish';
use constant CIPHER_NAME_NULL       => 'Null';
use constant CIPHER_NAME_RC5        => 'RC5';
use constant CIPHER_NAME_CAST5      => 'CAST5';
use constant CIPHER_NAME_AES        => 'AES';

use constant CIPHER_MODE_ECB        => 'ECB';
use constant CIPHER_MODE_CBC        => 'CBC';
use constant CIPHER_MODE_CFB        => 'CFB';
use constant CIPHER_MODE_OFB        => 'OFB';

use constant CIPHER_KEY_GIVEN_PSWD  => 1;
use constant CIPHER_KEY_RANDOM_PSWD => 2;
use constant CIPHER_KEY_GIVEN       => 3;
use constant CIPHER_KEY_RANDOM      => 4;

use constant RAND_OPTION_STR        => 'rand';
use constant RAND_PSWD_LEN          => 32;

use constant RNG_PERL_RAND          => 'Perl';
use constant RNG_CRYPT_RANDOM       => 'Crypt::Random';
use constant RNG_MATH_RANDOM        => 'Math::Random';
use constant RNG_OPENSSL_RAND       => 'OpenSSL';

use constant CIPHER_CONFIG_FILENAME => 'CipherConfig.h';

use constant BUILD_OPTION_BOTH      => 'both';
use constant BUILD_OPTION_CRYPTFILE => 'CryptFile';
use constant BUILD_OPTION_DECRYPT   => 'Decrypt';

#===============================================================================
# CLASS INITIALIZATION
#===============================================================================

our(@ISA, $VERSION);

BEGIN {
    @ISA = qw(Module::Install::PRIVATE);

    $VERSION = '1.03';

    # Define protected accessor/mutator methods.
    foreach my $prop (qw(
        prefix_dir inc_dir package ver_num ver_str lib_dir lib_name bin_file
        cipher_name cipher_func cipher_needs_iv key_len rc2_key_bits rc5_rounds
        pswd key
    )) {
        no strict 'refs';
        *$prop = sub {
            use strict 'refs';
            my $self = shift;
            $self->{$prop} = shift if @_;
            return $self->{$prop};
        };
    }
}

#===============================================================================
# PUBLIC API
#===============================================================================

# Method to return the instance of this class that it was invoked on, for use in
# invoking further methods in this class within Makefile.PL.  (This method has a
# suitably unique name to just be autoloaded from Makefile.PL; the other methods
# do not, so must be invoked on our object to ensure they are dispatched
# correctly.)

sub get_filter_crypto_private_obj {
    return shift;
}

sub locate_openssl {
    my $self = shift;

    print "\n";

    $self->query_prefix_dir();
    print "\n";

    $self->locate_inc_dir();
    $self->set_inc();

    $self->determine_ver_num();
    $self->set_define();

    $self->locate_lib_dir_and_file();
    $self->set_libs();

    $self->locate_bin_file();
    print "\n";
}

sub configure_cipher {
    my $self = shift;

    my $cipher_config = $self->opts()->{'cipher-config'};
    if (defined $cipher_config) {
        if (-f $cipher_config) {
            $self->show_found_var(
                'Using specified configuration file', $cipher_config
            );
            $self->copy_cipher_config($cipher_config);
        }
        else {
            $self->exit_with_error(100,
                "No such configuration file '%s'", $cipher_config
            );
        }
    }
    else {
        $self->query_cipher_name();

        my $lc_cipher_name = lc $self->cipher_name();
        my $cipher_config_method = "configure_${lc_cipher_name}_cipher";
        $self->$cipher_config_method();

        $self->query_pswd_or_key();

        $self->write_cipher_config();
    }
}

sub query_build {
    my $self = shift;

    my @build_options = (
        [ BUILD_OPTION_BOTH,      'Build both components'          ],
        [ BUILD_OPTION_CRYPTFILE, 'Build CryptFile component only' ],
        [ BUILD_OPTION_DECRYPT,   'Build Decrypt component only'   ]
    );

    my $build = $self->opts()->{'build'};
    if (defined $build) {
        my %build_options = map { $_->[0] => 1 } @build_options;
        if (exists $build_options{$build}) {
            $self->show_found_var('Using specified build option', $build);
        }
        else {
            $self->exit_with_error(101,
                "Invalid 'build' option value '%s'", $build
            );
        }
    }
    else {
        my $message  = 'Build options:';
        my $question = 'Which component(s) do you want to build?';
        my $default  = BUILD_OPTION_BOTH;

        $build = $self->prompt_list(
            $message, \@build_options, $question, $default
        );
    }
    print "\n";

    if ($build eq BUILD_OPTION_BOTH) {
        return [ BUILD_OPTION_CRYPTFILE, BUILD_OPTION_DECRYPT ];
    }
    else {
        return [ $build ];
    }
}

#===============================================================================
# PROTECTED API
#===============================================================================

sub query_prefix_dir {
    my $self = shift;

    my $prefix_dir = $self->opts()->{'prefix-dir'};
    if (defined $prefix_dir) {
        $prefix_dir = canonpath(abs_path($prefix_dir));
        if (-d $prefix_dir) {
            $self->show_found_var(
                'Using specified prefix directory', $prefix_dir
            );
        }
        else {
            $self->exit_with_error(102,
                "No such prefix directory '%s'", $prefix_dir
            );
        }
    }
    else {
        # Look for the main binary executable "openssl" or "ssleay" and use the
        # parent directory of where that is located; otherwise use the default
        # prefix directory as specified in the latest OpenSSL's own INSTALL
        # file if it exists.
        my $bin_file;
        if ($bin_file = $self->can_run('openssl') or
            $bin_file = $self->can_run('ssleay'))
        {
            if ($self->is_win32()) {
                # Find out (if we can) which platform this binary was built for.
                # This information is normally contained in the output of the
                # binary's "version -a" command, labelled "platform: " (or
                # "Platform:" before 0.9.2).
                my $bin_cmd = "$bin_file version -a 2>&1";

                my $bin_output = `$bin_cmd`;
                my $bin_rc = $? >> 8;

                if ($bin_rc) {
                    $self->exit_with_error(133,
                        "Could not get OpenSSL/SSLeay version information " .
                        "(%d):\n%s", $bin_rc, $bin_output
                    );
                }

                if ((my $platform) = $bin_output =~ /platform: ?(.*)$/imo) {
                    # If we have found a Cygwin binary then we had better not
                    # try to use it with our Win32 perl.
                    if ($platform =~ /^Cygwin/io) {
                        warn("Warning: Ignoring Cygwin OpenSSL/SSLeay binary " .
                             "'$bin_file' on Win32\n");
                        $bin_file = undef;
                    }
                }
            }
        }

        my $default;
        if (defined $bin_file) {
            # The binaries are normally located in a sub-directory (bin/,
            # out32/, out32dll/, out32.dbg/, out32dll.dbg or out/) of the prefix
            # directory.  See locate_bin_file().
            my $bin_dir = dirname($bin_file);
            $default = canonpath(abs_path(catdir($bin_dir, updir())));
        }
        else {
            $default = $self->is_win32() ? 'C:\\openssl' : '/usr/local/ssl';
            unless (-d $default) {
                if ($self->use_default_response()) {
                    $self->exit_with_error(132,
                        'No prefix directory found for OpenSSL or SSLeay'
                    );
                }
                else {
                    $default = '';
                }
            }
        }

        my $question = 'Where is your OpenSSL or SSLeay?';

        $prefix_dir = $self->prompt_dir($question, $default);
    }

    $self->prefix_dir($prefix_dir)
}

sub locate_inc_dir {
    my $self = shift;

    # The headers are normally located in the include/ sub-directory of the
    # prefix directory.
    # Again, build directories on "native" Windows platforms may have the files
    # in a different sub-directory, in this case inc32/ (0.9.0 onwards) or out/
    # (up to and including 0.8.1b), or even outinc/ for MinGW builds.  (Beware
    # of version 0.6.0 build directories, which contain an include/ sub-
    # directory containing "Shortcuts" to the real header files in the out/ sub-
    # directory.  Check for the presence of the "cyrypto.h" header file to be
    # sure we find the correct sub-directory.  The header files are now located
    # in the openssl/ sub-directory of the include directory (0.9.3 onwards),
    # but were located in the include directory itself (up to and including
    # 0.8.1b).)
    my $prefix_dir = $self->prefix_dir();
    my($dir, $inc_dir);
    if (-d ($dir = catdir($prefix_dir, 'include')) and
        (-f catfile($dir, 'openssl', 'crypto.h') or
         -f catfile($dir, 'crypto.h')))
    {
        $inc_dir = $dir;
    }
    elsif ($self->is_win32()) {
        if (-d ($dir = catdir($prefix_dir, 'inc32')) and
            (-f catfile($dir, 'openssl', 'crypto.h') or
             -f catfile($dir, 'crypto.h')))
        {
            $inc_dir = $dir;
        }
        elsif (-d ($dir = catdir($prefix_dir, 'outinc')) and
               (-f catfile($dir, 'openssl', 'crypto.h') or
                -f catfile($dir, 'crypto.h')))
        {
            $inc_dir = $dir;
        }
        elsif (-d ($dir = catdir($prefix_dir, 'out')) and
               -f catfile($dir, 'crypto.h'))
        {
            $inc_dir = $dir;
        }
    }

    if (defined $inc_dir) {
        $self->show_found_var('Found include directory', $inc_dir);
        $self->inc_dir($inc_dir)
    }
    else {
        $self->exit_with_error(103, 'No include directory found');
    }
}

sub set_inc {
    my $self = shift;

    my $inc_dir = $self->inc_dir();
    $self->inc("-I$inc_dir");
}

sub determine_ver_num {
    my $self = shift;

    # The header files are now located in the openssl/ sub-directory of the
    # include directory (0.9.3 onwards), but were located in the include
    # directory itself (up to and including 0.8.1b).
    my $inc_dir = $self->inc_dir();
    my($dir, $inc_files_dir);
    if (-d ($dir = catdir($inc_dir, 'openssl'))) {
        $inc_files_dir = $dir;
    }
    else {
        $inc_files_dir = $inc_dir;
    }

    # The version number is now specified by an OPENSSL_VERSION_NUMBER #define
    # in the opensslv.h header file (0.9.2 onwards).  That #define was in the
    # crypto.h header file (0.9.1's), and was called SSLEAY_VERSION_NUMBER (from
    # 0.6.0 to 0.9.0b inclusive).  Earlier versions do not seem to have a
    # version number defined in this way, but we do not support anything earlier
    # anyway.  The version number is specified as a hexadecimal integer of the
    # form MNNFFPPS (major, minor, fix, patch, status [0 for dev, 1 to 14 for
    # betas, and f for release) (0.9.5a onwards, but with the highest bit set in
    # the patch byte for the 0.9.5's), or of the form MNNFFRBB (major, minor,
    # fix, release, patch or beta) (0.9.3's, 0.9.4's and 0.9.5), or of the form
    # MNFP (major, minor, fix, patch) (up to and including 0.9.2b).
    my($file, $ver_file);
    if (-f ($file = catfile($inc_files_dir, 'opensslv.h'))) {
        $ver_file = $file;
    }
    elsif (-f ($file = catfile($inc_files_dir, 'crypto.h'))) {
        $ver_file = $file;
    }
    else {
        $self->exit_with_error(104, 'No version number header file found');
    }

    my $ver_define;
    if (open my $ver_fh, '<', $ver_file) {
        while (<$ver_fh>) {
            if (/^\#define\s+(?:OPENSSL|SSLEAY)_VERSION_NUMBER\s+
                 0x([0-9a-f]+)/iox)
            {
                $ver_define = $1;
                last;
            }
        }
        close $ver_fh;
    }
    else {
        $self->exit_with_error(105,
            "Could not open version number header file '%s' for reading: %s",
            $ver_file, $!
        );
    }

    my($major, $minor, $fix, $patch, $status_str);
    if (defined $ver_define) {
        if (length $ver_define == 8 and
            $ver_define =~ /^([0-9a-f])([0-9a-f]{2})([0-9a-f]{2})/io)
        {
            ($major, $minor, $fix) = map { hex } ($1, $2, $3);

            my $mmf_ver_num = $major * 10000 + $minor * 100 + $fix;

            if ( $mmf_ver_num >  905 or
                ($mmf_ver_num == 905 and $ver_define !~ /100$/o))
            {
                my $status_num;
                ($patch, $status_num) = map { hex }
                    $ver_define =~ /([0-9a-f]{2})([0-9a-f])$/io;

                $patch = 0xff & ($patch & ~0x80) if $mmf_ver_num == 905;

                if ($status_num == 0) {
                    $status_str = '-dev';
                }
                elsif ($status_num < 0xf) {
                    $status_str = '-beta' . (1 .. 0xe)[$status_num - 1];
                }
                else {
                    $status_str = '';
                }
            }
            else {
                my($release, $patch_or_beta) = map { hex }
                    $ver_define =~ /([0-9a-f])([0-9a-f]{2})$/io;

                if ($release == 0) {
                    $patch = 0;
                    if ($patch_or_beta == 0) {
                        $status_str = '-dev';
                    }
                    else {
                        $status_str = '-beta' . (1 .. 0xff)[$patch_or_beta - 1];
                    }
                }
                else {
                    $patch = $patch_or_beta;
                    $status_str = '';
                }
            }
        }
        elsif (length $ver_define == 4 and
               $ver_define =~ /^([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])$/io)
        {
            ($major, $minor, $fix, $patch) = map { hex } ($1, $2, $3, $4);
            $status_str = '';
        }
        else {
            $self->exit_with_error(106,
                'Unrecognized version number found (%s)', $ver_define
            );
        }
    }
    else {
        $self->exit_with_error(107, 'No version number found');
    }

    my $ver_num = $major * 1000000 + $minor * 10000 + $fix * 100 + $patch;
    my $ver_str = "$major.$minor.$fix";
    $ver_str .= ('', 'a' .. 'z')[$patch];
    $ver_str .= $status_str;

    my $package = $ver_num >= 90100 ? 'OpenSSL' : 'SSLeay';
    $self->show_found_var("Found $package version", $ver_str);
    $self->package($package);
    $self->ver_str($ver_str);
    $self->ver_num($ver_num);
}

sub set_define {
    my $self = shift;

    my $ver_num = $self->ver_num();
    my $unsafe_mode = exists $self->opts()->{'unsafe-mode'};
    my $debug_mode  = exists $self->opts()->{'debug-mode'};

    my $define =  "-DFILTER_CRYPTO_OPENSSL_VERSION=$ver_num";
    $define   .= ' -DFILTER_CRYPTO_UNSAFE_MODE' if $unsafe_mode;
    $define   .= ' -DFILTER_CRYPTO_DEBUG_MODE'  if $debug_mode;

    $self->define($define);
}

sub locate_lib_dir_and_file {
    my $self = shift;

    # The libraries are normally located in the lib/ sub-directory of the prefix
    # directory, but may be in the lib64/ sub-directory on 64-bit systems.  (The
    # latter may have lib/ directories as well, so check in lib64/ first.)
    # Again, build directories on "native" Windows platforms may have the files
    # in a different sub-directory, in this case out32/, out32dll/, out32.dbg/
    # or out32dll.dbg/ (0.9.0 onwards, depending on whether static or dynamic
    # libraries were built and whether they were built in release or debug mode)
    # or out/ (up to and including 0.8.1b).
    my $prefix_dir = $self->prefix_dir();
    my($dir, $lib_dir, $lib_file, $lib_name);
    if (-d ($dir = catdir($prefix_dir, 'lib64')) and
        ($lib_file, $lib_name) = $self->probe_for_lib_file($dir))
    {
        $lib_dir = $dir;
    }
    elsif (-d ($dir = catdir($prefix_dir, 'lib')) and
           ($lib_file, $lib_name) = $self->probe_for_lib_file($dir))
    {
        $lib_dir = $dir;
    }
    elsif ($self->is_win32()) {
        if (-d ($dir = catdir($prefix_dir, 'out32')) and
            ($lib_file, $lib_name) = $self->probe_for_lib_file($dir))
        {
            $lib_dir = $dir;
        }
        elsif (-d ($dir = catdir($prefix_dir, 'out32dll')) and
               ($lib_file, $lib_name) = $self->probe_for_lib_file($dir))
        {
            $lib_dir = $dir;
        }
        elsif (-d ($dir = catdir($prefix_dir, 'out32.dbg')) and
               ($lib_file, $lib_name) = $self->probe_for_lib_file($dir))
        {
            $lib_dir = $dir;
        }
        elsif (-d ($dir = catdir($prefix_dir, 'out32dll.dbg')) and
               ($lib_file, $lib_name) = $self->probe_for_lib_file($dir))
        {
            $lib_dir = $dir;
        }
        elsif (-d ($dir = catdir($prefix_dir, 'out')) and
               ($lib_file, $lib_name) = $self->probe_for_lib_file($dir))
        {
            $lib_dir = $dir;
        }
    }

    if (defined $lib_dir) {
        $self->show_found_var('Found crypto library', $lib_file);
        $self->lib_dir($lib_dir);
        $self->lib_name($lib_name);
    }
    else {
        $self->exit_with_error(109, 'No crypto library found');
    }
}

sub probe_for_lib_file {
    my $self = shift;
    my $candidate_lib_dir = shift;

    # The libraries on UNIX-type platforms (which includes Cygwin) are called
    # libssl.a (which contains the SSL and TLS implmentations) and libcrypto.a
    # (which contains the ciphers, digests, etc) and are specified as -lssl and
    # -lcrypto respectively.
    # On "native" Windows platforms built with the Microsoft Visual C++ (cl) or
    # Borland C++ (bcc32) they are called ssleay32.lib and libeay32.lib and are
    # specified as -lssleay32 and -llibeay32 (0.8.0 onwards), or ssl32.lib and
    # crypt32.lib, specified as -lssl32 and -lcrypt32 (0.6.0 to 0.6.6b
    # inclusive), or ssl.lib and crypto.lib, specified as -lssl and -lcrypto
    # (0.5.2 and 0.5.2a).
    # It is also possible to produce "native" Windows builds using GCC (i.e.
    # binaries and libraries that are linked against the Microsoft C run-time
    # library msvcrt.dll rather than Cygwin's POSIX C run-time library
    # cygwin1.dll) via MinGW (gcc).  In that case, the OpenSSL libraries are
    # called either libssl.a and libcrypto.a (for static builds) or libssl32.a
    # and libeay32.a [sic] (for dynamic builds).  They are specified as on UNIX-
    # type platforms, as described in the ExtUtils::Liblist manpage.
    my($file, $lib_file, $lib_name);
    if ($self->is_win32()) {
        if ($Config{cc} =~ /gcc/io) {
            if (-f ($file = catfile($candidate_lib_dir, 'libcrypto.a'))) {
                $lib_file = $file;
                $lib_name = 'crypto';
            }
            elsif (-f ($file = catfile($candidate_lib_dir, 'libeay32.a'))) {
                $lib_file = $file;
                $lib_name = 'eay32';
            }
        }
        else {
            if (-f ($file = catfile($candidate_lib_dir, 'libeay32.lib'))) {
                $lib_file = $file;
                $lib_name = 'libeay32';
            }
            elsif (-f ($file = catfile($candidate_lib_dir, 'crypt32.lib'))) {
                $lib_file = $file;
                $lib_name = 'crypt32';
            }
            elsif (-f ($file = catfile($candidate_lib_dir, 'crypto.lib'))) {
                $lib_file = $file;
                $lib_name = 'crypto';
            }
        }
    }
    else {
        if (-f ($file = catfile($candidate_lib_dir, 'libcrypto.a'))) {
            $lib_file = $file;
            $lib_name = 'crypto';
        }
    }

    return $lib_file ? ($lib_file, $lib_name) : ();
}

sub set_libs {
    my $self = shift;

    my $lib_dir  = $self->lib_dir();
    my $lib_name = $self->lib_name();
    $self->libs("-L$lib_dir -l$lib_name");
}

sub locate_bin_file {
    my $self = shift;

    # The binaries are normally located in the bin/ sub-directory of the prefix
    # directory.
    # However, we may be working with a build directory rather than an
    # installation directory, in which case the binary files will be in a
    # different sub-directory on "native" Windows platforms, in this case
    # out32/, out32dll/, out32.dbg/ or out32dll.dbg/ (0.9.0 onwards, depending
    # on whether static or dynamic libraries were built and whether they were
    # built in release or debug mode) or out/ (up to and including 0.8.1b).
    my $prefix_dir = $self->prefix_dir();
    my($dir, $bin_file);
    my $found = 0;
    if (-d ($dir = catdir($prefix_dir, 'bin')) and
        defined($bin_file = $self->probe_for_bin_file($dir)))
    {
        $found = 1;
    }
    elsif ($self->is_win32()) {
        if (-d ($dir = catdir($prefix_dir, 'out32')) and
            defined($bin_file = $self->probe_for_bin_file($dir)))
        {
            $found = 1;
        }
        elsif (-d ($dir = catdir($prefix_dir, 'out32dll')) and
               defined($bin_file = $self->probe_for_bin_file($dir)))
        {
            $found = 1;
        }
        elsif (-d ($dir = catdir($prefix_dir, 'out32.dbg')) and
               defined($bin_file = $self->probe_for_bin_file($dir)))
        {
            $found = 1;
        }
        elsif (-d ($dir = catdir($prefix_dir, 'out32dll.dbg')) and
               defined($bin_file = $self->probe_for_bin_file($dir)))
        {
            $found = 1;
        }
        elsif (-d ($dir = catdir($prefix_dir, 'out')) and
               defined($bin_file = $self->probe_for_bin_file($dir)))
        {
            $found = 1;
        }
    }

    if ($found) {
        $self->show_found_var('Found binary executable', $bin_file);
        $self->bin_file($bin_file)
    }
    else {
        $self->exit_with_error(111, 'No binary executable found');
    }
}

sub probe_for_bin_file {
    my $self = shift;
    my $candidate_bin_dir = shift;

    # The main binary executable is called "openssl" from 0.9.3 onwards, but
    # used to be called "ssleay" up to and including 0.9.2b.
    my($file, $bin_file);
    if (-f ($file = catfile($candidate_bin_dir, "openssl$Config{_exe}"))) {
        $bin_file = $file;
    }
    elsif (-f ($file = catfile($candidate_bin_dir, "ssleay$Config{_exe}"))) {
        $bin_file = $file;
    }

    return $bin_file;
}

sub query_cipher_name {
    my $self = shift;

    my $ver_num = $self->ver_num();

    # Find out (as best as we can) which ciphers, if any, have been disabled in
    # the particular crypto library that we are using.  Ciphers can be disabled
    # at build time via "-DOPENSSL_NO_<cipher_name>" (or "-DNO_<cipher_name>"
    # before 0.9.7), where <cipher_name> can be one of: "DES", "RC4", "IDEA",
    # "RC2", "BF" (or "BLOWFISH" before 0.9.3), "RC5", "CAST" or "AES".  This
    # information is normally contained in the output of the main binary
    # executable's "version -a" command, labelled "compiler: " (or "C flags:"
    # before 0.9.2) and not always on a line of its own.
    my $bin_file = $self->bin_file();
    my $bin_cmd = "$bin_file version -a 2>&1";

    my $bin_output = `$bin_cmd`;
    my $bin_rc = $? >> 8;

    if ($bin_rc) {
        $self->exit_with_error(112,
            "Could not get %s version information (%d):\n%s",
            $self->package(), $bin_rc, $bin_output
        );
    }

    my %disabled = ();
    if ((my $compiler) = $bin_output =~ /(?:C flags|compiler): ?(.*)$/imo) {
        %disabled = map { $_ => 1 }
                    $compiler =~ m|[-/]D ?"?(?:OPENSSL_)?NO_(\w+)"?|go;
    }

    my @cipher_names = ();

    # The DES, DES-EDE, DES-EDE3, RC4 and IDEA ciphers have been in the crypto
    # library since the earliest version that had the EVP_*() functions
    # (SSLeay 0.5.1).
    if (not exists $disabled{DES}) {
        push @cipher_names, (
            [ CIPHER_NAME_DES,      'DES block cipher'                  ],
            [ CIPHER_NAME_DES_EDE,  'Two key triple DES block cipher'   ],
            [ CIPHER_NAME_DES_EDE3, 'Three key triple DES block cipher' ]
        );
    }

    if (not exists $disabled{RC4}) {
        push @cipher_names, (
            [ CIPHER_NAME_RC4, 'RC4 stream cipher' ]
        );
    }

    if (not exists $disabled{IDEA}) {
        push @cipher_names, (
            [ CIPHER_NAME_IDEA, 'IDEA block cipher' ]
        );
    }

    # The RC2 cipher was added in SSLeay 0.5.2.
    if (not exists $disabled{RC2} and $ver_num >= 50200) {
        push @cipher_names, (
            [ CIPHER_NAME_RC2, 'RC2 block cipher' ]
        );
    }

    # The DESX cipher was added in SSLeay 0.6.2.
    if (not exists $disabled{DES} and $ver_num >= 60200) {
        push @cipher_names, (
            [ CIPHER_NAME_DESX, 'DESX block cipher' ]
        );
    }

    # The Blowfish cipher was added in SSLeay 0.6.6.
    if (not exists $disabled{BLOWFISH} and not exists $disabled{BF} and
        $ver_num >= 60600)
    {
        push @cipher_names, (
            [ CIPHER_NAME_BLOWFISH, 'Blowfish block cipher' ]
        );
    }

    # The null cipher was added in SSLeay 0.8.0.
    if ($ver_num >= 80000) {
        push @cipher_names, (
            [ CIPHER_NAME_NULL, 'Null cipher' ]
        );
    }

    # The RC5 and CAST5 ciphers were added in SSLeay 0.9.0.
    if (not exists $disabled{RC5} and $ver_num >= 90000) {
        push @cipher_names, (
            [ CIPHER_NAME_RC5, 'RC5 block cipher' ]
        );
    }

    if (not exists $disabled{CAST} and $ver_num >= 90000) {
        push @cipher_names, (
            [ CIPHER_NAME_CAST5, 'CAST5 block cipher' ]
        );
    }

    # The AES cipher was added in OpenSSL 0.9.7.
    if (not exists $disabled{AES} and $ver_num >= 90700) {
        push @cipher_names, (
            [ CIPHER_NAME_AES, 'AES block cipher' ]
        );
    }

    my $cipher_name = $self->opts()->{'cipher-name'};
    if (defined $cipher_name) {
        my %lc_cipher_names = map { lc $_->[0] => 1 } @cipher_names;
        if (exists $lc_cipher_names{lc $cipher_name}) {
            $self->show_found_var('Using specified cipher name', $cipher_name);
        }
        else {
            $self->exit_with_error(113,
                "No such cipher name '%s'", $cipher_name
            );
        }
    }
    else {
        my $message  = 'Cipher algorithms available:';
        my $question = 'Which cipher algorithm do you want to use?';

        my $default;
        if (not exists $disabled{DES} and $ver_num < 90700) {
            $default = CIPHER_NAME_DES_EDE3;
        }
        elsif (not exists $disabled{AES} and $ver_num >= 90700) {
            $default = CIPHER_NAME_AES;
        }
        else {
            $default = $cipher_names[$#cipher_names][0];
        }

        $cipher_name = $self->prompt_list(
            $message, \@cipher_names, $question, $default
        );
    }
    print "\n";

    $self->cipher_name($cipher_name);
}

sub query_cipher_mode {
    my $self = shift;

    my @cipher_modes = (
        [ CIPHER_MODE_ECB, 'ECB (Electronic Codebook Mode)'    ],
        [ CIPHER_MODE_CBC, 'CBC (Cipher Block Chaining Mode)'  ],
        [ CIPHER_MODE_CFB, 'CFB (64-Bit Cipher Feedback Mode)' ],
        [ CIPHER_MODE_OFB, 'OFB (64-Bit Output Feedback Mode)' ]
    );

    my $cipher_mode = $self->opts()->{'cipher-mode'};
    if (defined $cipher_mode) {
        my %lc_cipher_modes = map { lc $_->[0] => $_->[0] } @cipher_modes;
        if (exists $lc_cipher_modes{lc $cipher_mode}) {
            $self->show_found_var('Using specified cipher mode', $cipher_mode);
            $cipher_mode = $lc_cipher_modes{lc $cipher_mode};
        }
        else {
            $self->exit_with_error(114,
                "No such cipher mode '%s'", $cipher_mode
            );
        }
    }
    else {
        my $message  = 'Modes of operation available:';
        my $question = 'Which mode of operation do you want to use?';
        my $default  = CIPHER_MODE_CBC;

        $cipher_mode = $self->prompt_list(
            $message, \@cipher_modes, $question, $default
        );
    }
    print "\n";

    return $cipher_mode;
}

sub query_key_len {
    my $self = shift;
    my %args = @_;

    my $ver_num = $self->ver_num();

    my $validate;
    if (exists $args{-fixed}) {
        $validate = sub { $_[0] eq $args{-fixed} };
    }
    elsif ($ver_num < 90600) {
        # Before 0.9.6 there was no facility in the EVP library API for setting
        # the key length for variable key lengths ciphers so we can only use the
        # default value.  This should have been specified in the %args, but we
        # provide a default default value of 16 just in case.
        $args{-default} = 16 unless exists $args{-default};
        $validate = sub { $_[0] eq $args{-default} };
    }
    elsif (exists $args{-valid}) {
        my %valid = map { $_ => 1 } @{$args{-valid}};
        $validate = sub { exists $valid{$_[0]} };
    }
    else {
        my $int_pat = qr/^(?:0|[1-9](?:\d+)?)$/o;
        # Minimum key size is clearly 0 bytes if it is not otherwise set
        # already.  Restrict the maximum key size to some sensible value if it
        # is not set already: we do not want to allow the user to enter an
        # arbitrarily large integer.
        $args{-min} = 0    unless exists $args{-min};
        $args{-max} = 1024 unless exists $args{-max};
        $validate = sub {
            $_[0] =~ $int_pat and $_[0] >= $args{-min} and $_[0] <= $args{-max}
        };
    }

    my $key_len = $self->opts()->{'key-len'};
    my $key = $self->opts()->{key};
    if (defined $key_len) {
        if ($validate->($key_len)) {
            $self->show_found_var('Using specified key length', $key_len);
        }
        else {
            $self->exit_with_error(115, "Invalid key length '%d'", $key_len);
        }
    }
    elsif (defined $key and $key ne RAND_OPTION_STR) {
        $key_len = length($key) / 2;
        if ($validate->($key_len)) {
            $self->show_found_var('Using inferred key length', $key_len);
        }
        else {
            $self->exit_with_error(116, "Invalid length key (%d)", $key_len);
        }
    }
    elsif (exists $args{-fixed}) {
        $key_len = $args{-fixed};
        $self->show_found_var('Using fixed key length', $key_len);
    }
    elsif ($ver_num < 90600) {
        $key_len = $args{-default};
        $self->show_found_var('Using default key length', $key_len);
    }
    else {
        my $message = "This is a variable key length algorithm.\n";

        if (exists $args{-valid}) {
            my @key_lens = @{$args{-valid}};
            my $max_key_len = pop @key_lens;
            $message .= sprintf 'Valid key lengths are: %s or %d bytes.',
                                join(', ', @key_lens), $max_key_len;
        }
        else {
            $message .= sprintf 'Valid key lengths are from %d byte%s up to ' .
                                '%d byte%s.',
                                $args{-min}, $args{-min} == 1 ? '' : 's',
                                $args{-max}, $args{-max} == 1 ? '' : 's';
        }

        my $question = 'What key length (in bytes) do you want to use?';

        $key_len = $self->prompt_validate(
            -message  => $message,
            -question => $question,
            -default  => $args{-default},
            -validate => $validate
        );
    }
    print "\n";

    $self->key_len($key_len);
}

sub query_rc2_key_bits {
    my $self = shift;

    # The "effective key bits" parameter can be from 1 to 1024 bits: see RFC
    # 2268.
    my %args = (-min => 1, -max => 1024, -default => 128);

    my $ver_num = $self->ver_num();

    my $validate;
    if ($ver_num < 90600) {
        # Before 0.9.6 there was no facility in the EVP library API for setting
        # the effective key bits for the RC2 cipher so we can only use the
        # default value.
        $validate = sub { $_[0] eq $args{-default} };
    }
    else {
        my $int_pat = qr/^(?:0|[1-9](?:\d+)?)$/o;
        $validate = sub {
            $_[0] =~ $int_pat and $_[0] >= $args{-min} and $_[0] <= $args{-max}
        };
    }

    my $rc2_key_bits = $self->opts()->{'rc2-key-bits'};
    if (defined $rc2_key_bits) {
        if ($validate->($rc2_key_bits)) {
            $self->show_found_var(
                'Using specified RC2 key bits', $rc2_key_bits
            );
        }
        else {
            $self->exit_with_error(117,
                "Invalid RC2 key bits '%d'", $rc2_key_bits
            );
        }
    }
    elsif ($ver_num < 90600) {
        $rc2_key_bits = $args{-default};
        $self->show_found_var('Using default RC2 key bits', $rc2_key_bits);
    }
    else {
        my $message = "This algorithm also has an 'effective key bits' (EKB) " .
                      "parameter.\n";

        $message .= sprintf 'Valid EKB values are from %d bit%s up to %d ' .
                            'bit%s.',
                            $args{-min}, $args{-min} == 1 ? '' : 's',
                            $args{-max}, $args{-max} == 1 ? '' : 's';

        my $question = 'What EKB value (in bits) do you want to use?';

        $rc2_key_bits = $self->prompt_validate(
            -message  => $message,
            -question => $question,
            -default  => $args{-default},
            -validate => $validate
        );
    }
    print "\n";

    $self->rc2_key_bits($rc2_key_bits);
}

sub query_rc5_rounds {
    my $self = shift;

    # The "number of rounds" parameter can be from 0 to 255: see RFC 2040.
    # However, it can currently only be set to 8, 12 or 16 by the RC5 code in
    # OpenSSL: see EVP_EncryptInit.pod in recent OpenSSL distributions.
    my %args = (-valid => [8, 12, 16], -default => 12);

    my $ver_num = $self->ver_num();

    my $validate;
    if ($ver_num < 90600) {
        # Before 0.9.6 there was no facility in the EVP library API for setting
        # the number of rounds for the RC5 cipher so we can only use the default
        # value.
        $validate = sub { $_[0] eq $args{-default} };
    }
    else {
        my %valid = map { $_ => 1 } @{$args{-valid}};
        $validate = sub { exists $valid{$_[0]} };
    }

    my $rc5_rounds = $self->opts()->{'rc5-rounds'};
    if (defined $rc5_rounds) {
        if ($validate->($rc5_rounds)) {
            $self->show_found_var('Using specified RC5 rounds', $rc5_rounds);
        }
        else {
            $self->exit_with_error(118,
                "Invalid RC5 rounds '%d'", $rc5_rounds
            );
        }
    }
    elsif ($ver_num < 90600) {
        $rc5_rounds = $args{-default};
        $self->show_found_var('Using default RC5 rounds', $rc5_rounds);
    }
    else {
        my $message = "This algorithm also has a 'number of rounds' " .
                      "parameter.\n";

        my @rc5_rounds = @{$args{-valid}};
        my $max_rc5_rounds = pop @rc5_rounds;
        $message .= sprintf 'Valid numbers of rounds are: %s or %d.',
                            join(', ', @rc5_rounds), $max_rc5_rounds;

        my $question = 'What number of rounds do you want to use?';

        $rc5_rounds = $self->prompt_validate(
            -message  => $message,
            -question => $question,
            -default  => $args{-default},
            -validate => $validate
        );
    }
    print "\n";

    $self->rc5_rounds($rc5_rounds);
}

sub query_pswd_or_key {
    my $self = shift;

    my $key_len = $self->key_len();

    if ($key_len == 0) {
        $self->key('');
        return;
    }

    my $validate_pswd = sub {
        $_[0] ne ''
    };

    my $validate_key = sub {
        $_[0] =~ /^[0-9a-f]*$/io and length $_[0] == 2 * $key_len
    };

    my $pswd = $self->opts()->{pswd};
    my $key  = $self->opts()->{key};
    if (defined $pswd) {
        if (lc $pswd eq lc RAND_OPTION_STR) {
            $pswd = $self->generate_rand_pswd();
            print "\n";

            $self->show_found_var('Using randomly generated password', $pswd);
            $self->pswd($pswd);
        }
        elsif ($validate_pswd->($pswd)) {
            $self->show_found_var('Using specified password', $pswd);
            $self->pswd(unpack 'H*', $pswd);
        }
        else {
            $self->exit_with_error(119, "Invalid password '%s'", $pswd);
        }
    }
    elsif (defined $key) {
        if (lc $key eq lc RAND_OPTION_STR) {
            $key = $self->generate_rand_key();
            print "\n";

            $self->show_found_var('Using randomly generated key', $key);
            $self->key($key);
        }
        elsif ($validate_key->($key)) {
            $self->show_found_var('Using specified key', $key);
            $self->key($key);
        }
        else {
            $self->exit_with_error(120, "Invalid key '%s'", $key);
        }
    }
    else {
        my @cipher_key_sources = (
            [ CIPHER_KEY_GIVEN_PSWD,  'Enter a password when prompted'     ],
            [ CIPHER_KEY_RANDOM_PSWD, 'Have a password randomly generated' ],
            [ CIPHER_KEY_GIVEN,       'Enter a key when prompted'          ],
            [ CIPHER_KEY_RANDOM,      'Have a key randomly generated'      ]
        );
    
        my $message  = 'You can either specify a password from which the ' .
                       'key to be used for encryption/decryption will be ' .
                       'derived using a PKCS#5 key derivation algorithm, or ' .
                       "you can directly specify the key to use.\n" .
                       'You can also have a password or key randomly ' .
                       "generated for you.\n\n" .
                       'Options for specifying or deriving the key:';
        my $question = 'How do you want to specify or derive the key?';
        my $default  = CIPHER_KEY_RANDOM_PSWD;

        my $cipher_key_source = $self->prompt_list(
            $message, \@cipher_key_sources, $question, $default
        );
    
        print "\n";
    
        if ($cipher_key_source == CIPHER_KEY_GIVEN_PSWD) {
            $message  = 'Enter your password:';
            $question = 'Password?';
            $default  = '';

            $pswd = $self->prompt_validate(
                -message  => $message,
                -question => $question,
                -default  => $default,
                -validate => $validate_pswd
            );

            $self->pswd(unpack 'H*', $pswd);
        }
        elsif ($cipher_key_source == CIPHER_KEY_RANDOM_PSWD) {
            $pswd = $self->generate_rand_pswd();
            $self->pswd($pswd);
        }
        elsif ($cipher_key_source == CIPHER_KEY_GIVEN) {
            $message  = "Enter your ${key_len}-byte key with each byte " .
                        "written as a pair of hexadecimal digits with the " .
                        "high nybble first:";
            $question = 'Key?';
            $default  = '';

            $key = $self->prompt_validate(
                -message  => $message,
                -question => $question,
                -default  => $default,
                -validate => $validate_key
            );

            $self->key($key);
        }
        elsif ($cipher_key_source == CIPHER_KEY_RANDOM) {
            $key = $self->generate_rand_key();
            $self->key($key);
        }
        else {
            $self->exit_with_error(121,
                "Unknown key source '%s'", $cipher_key_source
            );
        }
    }

    print "\n";
}

sub generate_rand_key {
    my $self = shift;
    return $self->generate_rand_octets_hex($self->key_len());
}

sub generate_rand_pswd {
    my $self = shift;
    return $self->generate_rand_octets_hex(RAND_PSWD_LEN);
}

sub generate_rand_octets_hex {
    my $self = shift;
    my $num_octets = shift;

    my $rng = $self->query_rng();

    my $octets;
    if (lc $rng eq lc RNG_PERL_RAND) {
        $octets = '';
        for (1 .. $num_octets) {
            $octets .= chr int rand 256;
        }
    }
    elsif (lc $rng eq lc RNG_CRYPT_RANDOM) {
        # Delay the loading of Crypt::Random until it is actually required since
        # it is not a standard module.
        eval {
            require Crypt::Random;
            Crypt::Random->import(qw(makerandom_octet));
        };

        if ($@) {
            $self->exit_with_error(122,
                "Can't load Crypt::Random module for random number generation"
            );
        }

        # Specify "Strength => 0" to use /dev/urandom rather than /dev/random
        # to avoid potentially blocking for a long time.
        $octets = makerandom_octet(
            Length => $num_octets, Strength => 0
        );
    }
    elsif (lc $rng eq lc RNG_MATH_RANDOM) {
        # Delay the loading of Math::Random until it is actually required since
        # it is not a standard module.
        eval {
            require Math::Random;
            Math::Random->import(qw(random_uniform_integer));
        };

        if ($@) {
            $self->exit_with_error(123,
                "Can't load Math::Random module for random number generation"
            );
        }

        $octets = join '',
                       map { chr } random_uniform_integer($num_octets, 0, 255);
    }
    elsif (lc $rng eq lc RNG_OPENSSL_RAND) {
        my $bin_file = $self->bin_file();
        my $out_filename = 'rand.out';

        my $bin_cmd = "$bin_file rand -out $out_filename $num_octets 2>&1";

        my $bin_output = `$bin_cmd`;
        my $bin_rc = $? >> 8;

        if ($bin_rc) {
            $self->exit_with_error(124,
                "Could not generate %d random bytes (%d):\n%s",
                $num_octets, $bin_rc, $bin_output
            );
        }

        sysopen my $out_fh, $out_filename, O_RDONLY | O_BINARY or
            $self->exit_with_error(125,
                "Could not open random bytes output file '%s' for reading: %s",
                $out_filename, $!
            );

        my $num_octets_read = sysread $out_fh, $octets, $num_octets;
        if (not defined $num_octets_read) {
            $self->exit_with_error(126,
                "Could not read random bytes from output file '%s': %s",
                $out_filename, $!
            );
        }
        elsif ($num_octets_read != $num_octets) {
            $self->exit_with_error(127,
                "Could not read random bytes from output file '%s': %d bytes " .
                "read, %d bytes expected",
                $out_filename, $num_octets_read, $num_octets
            );
        }

        close $out_fh;
        unlink $out_filename;
    }
    else {
        $self->exit_with_error(128,
            "Unknown random number generator '%s'", $rng
        );
    }

    return unpack 'H*', $octets;
}

sub query_rng {
    my $self = shift;

    my $ver_num = $self->ver_num();
    my $package = $self->package();

    my @rngs = (
        [ RNG_PERL_RAND, "Perl's built-in rand() function" ]
    );

    if (eval { require Crypt::Random; 1 }) {
        push @rngs, (
            [ RNG_CRYPT_RANDOM, 'Crypt::Random' ]
        );
    }

    if (eval { require Math::Random; 1 }) {
        push @rngs, (
            [ RNG_MATH_RANDOM, 'Math::Random' ]
        );
    }

    # The "rand" command was added in OpenSSL 0.9.5a.
    if ($ver_num >= 90501) {
        push @rngs, (
            [ RNG_OPENSSL_RAND, "${package}'s rand command" ]
        );
    }

    my $rng = $self->opts()->{rng};
    if (defined $rng) {
        my %lc_rngs = map { lc $_->[0] => $_->[0] } @rngs;
        if (exists $lc_rngs{lc $rng}) {
            $self->show_found_var('Using specified RNG', $rng);
            $rng = $lc_rngs{lc $rng};
        }
        else {
            $self->exit_with_error(129,
                "Invalid random number generator '%s'", $rng
            );
        }
    }
    else {
        my $message  = 'Random number generators:';
        my $question = 'Which RNG do you want to use?';
        my $default  = $rngs[$#rngs][0];

        $rng = $self->prompt_list(
            $message, \@rngs, $question, $default
        );
    }

    return $rng;
}

sub configure_des_cipher {
    my $self = shift;

    my %cipher_funcs = (
        CIPHER_MODE_ECB, 'EVP_des_ecb()',
        CIPHER_MODE_CBC, 'EVP_des_cbc()',
        CIPHER_MODE_CFB, 'EVP_des_cfb()',
        CIPHER_MODE_OFB, 'EVP_des_ofb()'
    );
    my $cipher_mode = $self->query_cipher_mode();
    $self->cipher_func($cipher_funcs{$cipher_mode});
    $self->cipher_needs_iv(1);

    # The DES cipher can only use an 8 byte key (of which only 7 bytes are
    # actually used by the algorithm): see FIPS PUB 46-3.
    $self->query_key_len(-fixed => 8);
}

sub configure_des_ede_cipher {
    my $self = shift;

    my $ver_num = $self->ver_num();
    my %cipher_funcs = (
        CIPHER_MODE_ECB, ($ver_num < 90700
                          ? 'EVP_des_ede()' : 'EVP_des_ede_ecb()'),
        CIPHER_MODE_CBC, 'EVP_des_ede_cbc()',
        CIPHER_MODE_CFB, 'EVP_des_ede_cfb()',
        CIPHER_MODE_OFB, 'EVP_des_ede_ofb()'
    );
    my $cipher_mode = $self->query_cipher_mode();
    $self->cipher_func($cipher_funcs{$cipher_mode});
    $self->cipher_needs_iv(1);

    # The DES-EDE cipher is two-key triple-DES (i.e. in which an encrypt
    # operation is encrypt with key 1, decrypt with key 2, encrypt with key 1),
    # and therefore requires a key length equivalent to two DES keys, i.e. 16
    # bytes (of which only 14 are used).
    $self->query_key_len(-fixed => 16);
}

sub configure_des_ede3_cipher {
    my $self = shift;

    my $ver_num = $self->ver_num();
    my %cipher_funcs = (
        CIPHER_MODE_ECB, ($ver_num < 90700
                          ? 'EVP_des_ede3()' : 'EVP_des_ede3_ecb()'),
        CIPHER_MODE_CBC, 'EVP_des_ede3_cbc()',
        CIPHER_MODE_CFB, 'EVP_des_ede3_cfb()',
        CIPHER_MODE_OFB, 'EVP_des_ede3_ofb()'
    );
    my $cipher_mode = $self->query_cipher_mode();
    $self->cipher_func($cipher_funcs{$cipher_mode});
    $self->cipher_needs_iv(1);

    # The DES-EDE3 cipher is three-key triple-DES (i.e. in which an encrypt
    # operation is encrypt with key 1, decrypt with key 2, encrypt with key 3),
    # and therefore requires a key length equivalent to two DES keys, i.e. 24
    # bytes (of which only 21 are used).
    $self->query_key_len(-fixed => 24);
}

sub configure_rc4_cipher {
    my $self = shift;

    $self->cipher_func('EVP_rc4()');
    $self->cipher_needs_iv(0);

    # The RC4 cipher can use any key length: see rc4.doc in old SSLeay
    # distributions.
    $self->query_key_len(-min => 1, -default => 16);
}

sub configure_idea_cipher {
    my $self = shift;

    my %cipher_funcs = (
        CIPHER_MODE_ECB, 'EVP_idea_ecb()',
        CIPHER_MODE_CBC, 'EVP_idea_cbc()',
        CIPHER_MODE_CFB, 'EVP_idea_cfb()',
        CIPHER_MODE_OFB, 'EVP_idea_ofb()'
    );
    my $cipher_mode = $self->query_cipher_mode();
    $self->cipher_func($cipher_funcs{$cipher_mode});
    $self->cipher_needs_iv(1);

    # The IDEA cipher can only use a 16 byte key: see idea.doc in old SSLeay
    # distributions.
    $self->query_key_len(-fixed => 16);
}

sub configure_rc2_cipher {
    my $self = shift;

    my %cipher_funcs = (
        CIPHER_MODE_ECB, 'EVP_rc2_ecb()',
        CIPHER_MODE_CBC, 'EVP_rc2_cbc()',
        CIPHER_MODE_CFB, 'EVP_rc2_cfb()',
        CIPHER_MODE_OFB, 'EVP_rc2_ofb()'
    );
    my $cipher_mode = $self->query_cipher_mode();
    $self->cipher_func($cipher_funcs{$cipher_mode});
    $self->cipher_needs_iv(1);

    # The RC2 cipher can use any key length from 1 to 128 bytes: see RFC 2268.
    $self->query_key_len(-min => 1, -max => 128, -default => 16);

    # The RC2 cipher also has a parameter called "effective key bits".
    $self->query_rc2_key_bits();
}

sub configure_desx_cipher {
    my $self = shift;

    $self->cipher_func('EVP_desx_cbc()');
    $self->cipher_needs_iv(1);

    # The DESX cipher can only use a 24 byte key: see des.pod in recent OpenSSL
    # distributions.
    $self->query_key_len(-fixed => 24);
}

sub configure_blowfish_cipher {
    my $self = shift;

    my %cipher_funcs = (
        CIPHER_MODE_ECB, 'EVP_bf_ecb()',
        CIPHER_MODE_CBC, 'EVP_bf_cbc()',
        CIPHER_MODE_CFB, 'EVP_bf_cfb()',
        CIPHER_MODE_OFB, 'EVP_bf_ofb()'
    );
    my $cipher_mode = $self->query_cipher_mode();
    $self->cipher_func($cipher_funcs{$cipher_mode});
    $self->cipher_needs_iv(1);

    # The Blowfish cipher can use any key length up to 72 bytes: see
    # blowfish.doc in old SSLeay distributions.
    $self->query_key_len(-min => 1, -max => 72, -default => 16);
}

sub configure_null_cipher {
    my $self = shift;

    $self->cipher_func('EVP_enc_null()');
    $self->cipher_needs_iv(0);

    # The null cipher does not require a key: it does nothing.
    $self->query_key_len(-fixed => 0);
}

sub configure_rc5_cipher {
    my $self = shift;

    my %cipher_funcs = (
        CIPHER_MODE_ECB, 'EVP_rc5_32_12_16_ecb()',
        CIPHER_MODE_CBC, 'EVP_rc5_32_12_16_cbc()',
        CIPHER_MODE_CFB, 'EVP_rc5_32_12_16_cfb()',
        CIPHER_MODE_OFB, 'EVP_rc5_32_12_16_ofb()'
    );
    my $cipher_mode = $self->query_cipher_mode();
    $self->cipher_func($cipher_funcs{$cipher_mode});
    $self->cipher_needs_iv(1);

    # The RC5 cipher can use any key length from 0 to 255 bytes: see RFC 2040.
    $self->query_key_len(-min => 0, -max => 255, -default => 16);

    # The RC5 cipher also has a parameter called "number of rounds".
    $self->query_rc5_rounds();
}

sub configure_cast5_cipher {
    my $self = shift;

    my %cipher_funcs = (
        CIPHER_MODE_ECB, 'EVP_cast5_ecb()',
        CIPHER_MODE_CBC, 'EVP_cast5_cbc()',
        CIPHER_MODE_CFB, 'EVP_cast5_cfb()',
        CIPHER_MODE_OFB, 'EVP_cast5_ofb()'
    );
    my $cipher_mode = $self->query_cipher_mode();
    $self->cipher_func($cipher_funcs{$cipher_mode});
    $self->cipher_needs_iv(1);

    # The CAST5 cipher can use any key length from 5 to 16 bytes: see RFC 2144.
    $self->query_key_len(-min => 5, -max => 16, -default => 16);
}

sub configure_aes_cipher {
    my $self = shift;

    my %cipher_funcs = (
        CIPHER_MODE_ECB, 'EVP_aes_ecb()',
        CIPHER_MODE_CBC, 'EVP_aes_cbc()',
        CIPHER_MODE_CFB, 'EVP_aes_cfb()',
        CIPHER_MODE_OFB, 'EVP_aes_ofb()'
    );
    my $cipher_mode = $self->query_cipher_mode();
    my $cipher_func = $cipher_funcs{$cipher_mode};

    # The AES cipher can only use a 16, 24 or 32 byte key: see FIPS PUB 197.  Do
    # not offer the choice of 24 or 32 byte keys for 0.9.7 because they do not
    # seem to work.  I do not know why, and the problem does not seem to occur
    # with debug OpenSSL builds, which does not make it very easy to find out
    # why.
    my $ver_num = $self->ver_num();
    if ($ver_num == 90700) {
        $self->query_key_len(-fixed => 16);
    }
    else {
        $self->query_key_len(-valid => [16, 24, 32], -default => 32);
    }

    my $key_len_bits = $self->key_len() * 8;
    $cipher_func =~ s/_aes_/_aes_${key_len_bits}_/;
    $self->cipher_func($cipher_func);
    $self->cipher_needs_iv(1);
}

sub write_cipher_config {
    my $self = shift;

    open my $cfg_fh, '>', CIPHER_CONFIG_FILENAME or
        $self->exit_with_error(130,
            "Could not open configuration file '%s' for writing: %s",
            CIPHER_CONFIG_FILENAME, $!
        );

    my $package    = $self->package();
    my $prefix_dir = $self->prefix_dir();
    my $ver_str    = $self->ver_str();

    print $cfg_fh <<"EOT";
/*============================================================================
 *
 * @{[CIPHER_CONFIG_FILENAME]}
 *
 * DESCRIPTION
 *   Cipher configuration file for Filter::Crypto modules.
 *
 *   DO NOT EDIT THIS FILE!
 *
 *   This file is written by Makefile.PL from its command-line option values
 *   and/or default values.  Any changes made here will be lost the next time
 *   Makefile.PL is run.
 *
 *   Created at @{[scalar localtime]} by Perl version $], installed as
 *   $^X
 *
 *   Configured against $package version $ver_str, installed under
 *   $prefix_dir
 *
 *============================================================================*/

EOT

    my $cipher_func = $self->cipher_func();
    print $cfg_fh "#define FILTER_CRYPTO_CIPHER_FUNC  $cipher_func\n";

    if ($self->cipher_needs_iv()) {
        print $cfg_fh "#define FILTER_CRYPTO_NEED_IV      1\n";
    }
    else {
        print $cfg_fh "#define FILTER_CRYPTO_NEED_IV      0\n";
    }

    my $key_len = $self->key_len();
    print $cfg_fh "#define FILTER_CRYPTO_KEY_LEN      $key_len\n";

    my $rc2_key_bits = $self->rc2_key_bits();
    my $rc5_rounds   = $self->rc5_rounds();
    if (defined $rc2_key_bits) {
        print $cfg_fh "#define FILTER_CRYPTO_RC2_KEY_BITS $rc2_key_bits\n";
    }
    elsif (defined $rc5_rounds) {
        print $cfg_fh "#define FILTER_CRYPTO_RC5_ROUNDS   $rc5_rounds\n";
    }

    my($def, $var);
    if ($key_len == 0) {
        $def = '#define FILTER_CRYPTO_USING_PBE    0';
        $var = 'static const unsigned char *filter_crypto_key = NULL;';
    }
    else {
        my $pswd = $self->pswd();
        if (defined $pswd) {
            $def = '#define FILTER_CRYPTO_USING_PBE    1';
            $pswd = $self->format_chars($pswd);
            my $ver_num = $self->ver_num();
            if ($ver_num < 90400) {
                $var = "static unsigned char filter_crypto_pswd[] = {\n" .
                       "$pswd\n" .
                       "};";
            }
            else {
                $var = "static const unsigned char filter_crypto_pswd[] = {\n" .
                       "$pswd\n" .
                       "};";
            }
        }
        else {
            $def = '#define FILTER_CRYPTO_USING_PBE    0';
            my $key = $self->key();
            $key = $self->format_chars($key);
            $var = "static const unsigned char filter_crypto_key[] = {\n" .
                   "$key\n" .
                   "};";
        }
    }
    print $cfg_fh "$def\n\n";
    print $cfg_fh "$var\n";

    print $cfg_fh <<'EOT';

/*============================================================================*/
EOT

    close $cfg_fh;

    print wrap('', '',
        "Your cipher configuration has been written to the file '" .
        CIPHER_CONFIG_FILENAME . "'.  You may want to keep this file in a " .
        "safe place if you ever need to rebuild these modules using the same " .
        "configuration, especially if your key was randomly generated."
    ), "\n\n";
}

sub format_chars {
    my $self = shift;
    my $chars = shift;

    $chars =~ s/(..)/0x$1, /g;
    $chars =~ s/^/    /;
    $chars =~ s/, $//;
    $chars =~ s/((?:0x.., ){8})/$1\n    /g;
    $chars =~ s/ \n/\n/g;
    $chars =~ s/\n    $//;

    return $chars;
}

sub copy_cipher_config {
    my $self = shift;
    my $cipher_config_file = shift;

    if ($cipher_config_file ne CIPHER_CONFIG_FILENAME) {
        copy($cipher_config_file, CIPHER_CONFIG_FILENAME) or
            $self->exit_with_error(131,
                "Could not copy configuration file '%s' to '%s': %s",
                $cipher_config_file, CIPHER_CONFIG_FILENAME, $!
            );
    }

    print "\n";
}

1;

__END__

#===============================================================================