User-Visible krb5-strength Changes
+krb5-strength 3.2 (unreleased)
+
+ Support building without CrackLib support by passing
+ --without-cracklib to configure. This makes the code a bit simpler
+ and lighter if you don't intend to ever use the CrackLib support.
+
krb5-strength 3.1 (2016-12-25)
A new configuration option, cracklib_maxlen, can be set to skip
with the system version of CrackLib, pass --with-cracklib to configure.
You can optionally add a directory, giving the root directory where
CrackLib was installed, or separately set the include and library path
- with --with-cracklib-include and --with-cracklib-lib.
+ with --with-cracklib-include and --with-cracklib-lib. You can also
+ build without any CrackLib support by passing --without-cracklib to
+ configure.
krb5-strength will automatically build with TinyCDB if it is found. To
specify the installation path of TinyCDB, use --with-tinycdb. You can
`/usr/local/bin/heimdal-strength`, and the plugin is installed as
`/usr/local/lib/krb5/plugins/pwqual/strength.so`. You can change these
paths with the `--prefix`, `--libdir`, and `--bindir` options to
-configure.
+`configure`.
By default, the embedded version of CrackLib will be used. To build with
-the system version of CrackLib, pass `--with-cracklib` to configure. You
-can optionally add a directory, giving the root directory where CrackLib
-was installed, or separately set the include and library path with
-`--with-cracklib-include` and `--with-cracklib-lib`.
+the system version of CrackLib, pass `--with-cracklib` to `configure`.
+You can optionally add a directory, giving the root directory where
+CrackLib was installed, or separately set the include and library path
+with `--with-cracklib-include` and `--with-cracklib-lib`. You can also
+build without any CrackLib support by passing `--without-cracklib` to
+`configure`.
krb5-strength will automatically build with TinyCDB if it is found. To
specify the installation path of TinyCDB, use `--with-tinycdb`. You can
`/usr/local/bin/heimdal-strength`, and the plugin is installed as
`/usr/local/lib/krb5/plugins/pwqual/strength.so`. You can change these
paths with the `--prefix`, `--libdir`, and `--bindir` options to
-configure.
+`configure`.
By default, the embedded version of CrackLib will be used. To build with
-the system version of CrackLib, pass `--with-cracklib` to configure. You
-can optionally add a directory, giving the root directory where CrackLib
-was installed, or separately set the include and library path with
-`--with-cracklib-include` and `--with-cracklib-lib`.
+the system version of CrackLib, pass `--with-cracklib` to `configure`.
+You can optionally add a directory, giving the root directory where
+CrackLib was installed, or separately set the include and library path
+with `--with-cracklib-include` and `--with-cracklib-lib`. You can also
+build without any CrackLib support by passing `--without-cracklib` to
+`configure`.
krb5-strength will automatically build with TinyCDB if it is found. To
specify the installation path of TinyCDB, use `--with-tinycdb`. You can
dnl The main macro.
AC_DEFUN([RRA_LIB_CRACKLIB],
[rra_system_cracklib=
+ rra_no_cracklib=
rra_cracklib_root=
rra_cracklib_libdir=
rra_cracklib_includedir=
[AS_HELP_STRING([--with-cracklib@<:@=DIR@:>@],
[Use system CrackLib instead of embedded copy])],
[AS_IF([test x"$withval" != xno], [rra_system_cracklib=yes])
+ AS_IF([test x"$withval" == xno], [rra_no_cracklib=yes])
AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
[rra_cracklib_root="$withval"])])
AC_ARG_WITH([cracklib-include],
[AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
[rra_cracklib_libdir="$withval"])])
- AM_CONDITIONAL([EMBEDDED_CRACKLIB], [test x"$rra_system_cracklib" != xyes])
+ AS_IF([test x"$rra_no_cracklib" != xyes],
+ [AC_DEFINE([HAVE_CRACKLIB], 1, [Define if CrackLib is available.])])
+ AM_CONDITIONAL([EMBEDDED_CRACKLIB],
+ [test x"$rra_system_cracklib" != xyes && test x"$rra_no_cracklib" != xyes])
AS_IF([test x"$rra_system_cracklib" = xyes],
[_RRA_LIB_CRACKLIB_PATHS
CRACKLIB_LIBS="-lcrack"
* Developed by Derrick Brashear and Ken Hornstein of Sine Nomine Associates,
* on behalf of Stanford University
* Extensive modifications by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2017 Russ Allbery <eagle@eyrie.org>
* Copyright 2006, 2007, 2009, 2012, 2013
* The Board of Trustees of the Leland Stanford Junior University
*
*/
#include <config.h>
+#include <portable/kadmin.h>
#include <portable/system.h>
#include <plugin/internal.h>
+#include <util/macros.h>
/* When using the embedded CrackLib, we need to provide our own prototype. */
-#ifdef HAVE_CRACK_H
-# include <crack.h>
-#else
+#ifdef HAVE_CRACKLIB
+# ifdef HAVE_CRACK_H
+# include <crack.h>
+# else
extern const char *FascistCheck(const char *password, const char *dict);
+# endif
#endif
+/*
+ * Stub for strength_init_cracklib if not built with CrackLib support.
+ */
+#ifndef HAVE_CRACKLIB
+krb5_error_code
+strength_init_cracklib(krb5_context ctx, krb5_pwqual_moddata data UNUSED,
+ const char *dictionary UNUSED)
+{
+ char *path = NULL;
+
+ /* Get CDB dictionary path from krb5.conf. */
+ strength_config_string(ctx, "password_dictionary", &path);
+
+ /* If it was set, report an error, since we don't have CrackLib support. */
+ if (path == NULL)
+ return 0;
+ free(path);
+ krb5_set_error_message(ctx, KADM5_BAD_SERVER_PARAMS, "CrackLib dictionary"
+ " requested but not built with CrackLib support");
+ return KADM5_BAD_SERVER_PARAMS;
+}
+#endif
+
+
+/* Skip the rest of this file if CrackLib is not available. */
+#ifdef HAVE_CRACKLIB
+
/*
* Initialize the CrackLib dictionary. Ensure that the dictionary file exists
* and is readable and store the path in the module context. Returns 0 on
else
return 0;
}
+
+#endif /* HAVE_CRACKLIB */
* CrackLib handling. strength_init_cracklib gets the dictionary
* configuration does some sanity checks on it, and strength_check_cracklib
* checks the password against CrackLib.
+ *
+ * If not built with CrackLib support, provide a stub for check. init is
+ * always a real function, which reports an error if CrackLib is requested and
+ * not availble.
*/
krb5_error_code strength_init_cracklib(krb5_context, krb5_pwqual_moddata,
const char *dictionary);
+#ifdef HAVE_CRACKLIB
krb5_error_code strength_check_cracklib(krb5_context, krb5_pwqual_moddata,
const char *password);
+#else
+# define strength_check_cracklib(c, d, p) 0
+#endif
/*
* SQLite handling. strength_init_sqlite gets the database configuration and
/* Get CDB dictionary path from krb5.conf. */
strength_config_string(ctx, "password_dictionary_sqlite", &path);
- /* If it was set, report an error, since we don't have CDB support. */
+ /* If it was set, report an error, since we don't have SQLite support. */
if (path == NULL)
return 0;
free(path);
/* Load the plugin. */
verifier = load_plugin(&handle);
- /* Set up our krb5.conf with the dictionary configuration. */
+ /* Set up our krb5.conf with a basic configuration. */
setup_argv[0] = test_file_path("data/make-krb5-conf");
if (setup_argv[0] == NULL)
bail("cannot find data/make-krb5-conf in the test suite");
tmpdir = test_tmpdir();
setup_argv[1] = path;
setup_argv[2] = tmpdir;
- setup_argv[3] = (char *) "password_dictionary";
- basprintf(&setup_argv[4], "%s/data/dictionary", getenv("BUILD"));
- setup_argv[5] = NULL;
+ setup_argv[3] = NULL;
run_setup((const char **) setup_argv);
/* Point KRB5_CONFIG at the newly-generated krb5.conf file. */
putenv(krb5_config);
free(krb5_config_empty);
+ /* Run principal tests. */
+ for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
+ is_password_test(verifier, &principal_tests[i]);
+
+#ifdef HAVE_CRACKLIB
+
+ /* Add CrackLib tests. */
+ setup_argv[3] = (char *) "password_dictionary";
+ basprintf(&setup_argv[4], "%s/data/dictionary", getenv("BUILD"));
+ setup_argv[5] = NULL;
+ run_setup((const char **) setup_argv);
+
/* Now, run all of the tests. */
for (i = 0; i < ARRAY_SIZE(cracklib_tests); i++)
is_password_test(verifier, &cracklib_tests[i]);
- for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
- is_password_test(verifier, &principal_tests[i]);
/*
* Add length restrictions and a maximum length for CrackLib. This should
for (i = 0; i < ARRAY_SIZE(length_tests); i++)
is_password_test(verifier, &length_tests[i]);
+ /* Free the memory allocated for the CrackLib test. */
+ free(setup_argv[4]);
+
+#else
+
+ /* Otherwise, mark the CrackLib tests as skipped. */
+ count = ARRAY_SIZE(cracklib_tests) + ARRAY_SIZE(length_tests);
+ skip_block(count * 2, "not built with CDB support");
+
+#endif /* !HAVE_CRACKLIB */
+
/* Add simple character class restrictions. */
- setup_argv[5] = (char *) "minimum_different";
- setup_argv[6] = (char *) "8";
- setup_argv[7] = (char *) "require_ascii_printable";
+ setup_argv[3] = (char *) "minimum_different";
+ setup_argv[4] = (char *) "8";
+ setup_argv[5] = (char *) "require_ascii_printable";
+ setup_argv[6] = (char *) "true";
+ setup_argv[7] = (char *) "require_non_letter";
setup_argv[8] = (char *) "true";
- setup_argv[9] = (char *) "require_non_letter";
- setup_argv[10] = (char *) "true";
- setup_argv[11] = NULL;
+ setup_argv[9] = NULL;
run_setup((const char **) setup_argv);
/* Run the simple character class tests. */
is_password_test(verifier, &letter_tests[i]);
/* Add complex character class restrictions and remove the dictionary. */
- free(setup_argv[4]);
setup_argv[3] = (char *) "require_classes";
setup_argv[4] = (char *) "8-19:lower,upper 8-15:digit 8-11:symbol 24-24:3";
setup_argv[5] = NULL;
* Test for the MIT Kerberos shared module API.
*
* Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2017 Russ Allbery <eagle@eyrie.org>
* Copyright 2010, 2013, 2014
* The Board of Trustees of the Leland Stanford Junior University
*
if (code != 0)
bail("cannot continue after plugin initialization failure");
- /* Now, run all of the tests, with principal tests. */
- for (i = 0; i < ARRAY_SIZE(cracklib_tests); i++)
- is_password_test(ctx, vtable, data, &cracklib_tests[i]);
+ /* Run the principal tests. */
for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
is_password_test(ctx, vtable, data, &principal_tests[i]);
+ /* Run the CrackLib tests if CrackLib is available, otherwise skip them. */
+#ifdef HAVE_CRACKLIB
+ for (i = 0; i < ARRAY_SIZE(cracklib_tests); i++)
+ is_password_test(ctx, vtable, data, &cracklib_tests[i]);
+#else
+ count = ARRAY_SIZE(cracklib_tests);
+ skip_block(count * 2, "not built with CrackLib support");
+#endif
+
/* Close that initialization of the plugin and destroy that context. */
vtable->close(ctx, data);
krb5_free_context(ctx);
ctx = NULL;
- /* Set up our krb5.conf with the dictionary configuration. */
+ /* Set up our krb5.conf with a base configuration. */
tmpdir = test_tmpdir();
setup_argv[0] = test_file_path("data/make-krb5-conf");
if (setup_argv[0] == NULL)
bail("cannot find data/make-krb5-conf in the test suite");
setup_argv[1] = path;
setup_argv[2] = tmpdir;
- setup_argv[3] = (char *) "password_dictionary";
- setup_argv[4] = dictionary;
- setup_argv[5] = NULL;
- run_setup((const char **) setup_argv);
+ setup_argv[3] = NULL;
/* Point KRB5_CONFIG at the newly-generated krb5.conf file. */
basprintf(&krb5_config, "KRB5_CONFIG=%s/krb5.conf", tmpdir);
putenv(krb5_config);
free(krb5_config_empty);
+#ifdef HAVE_CRACKLIB
+
+ /* Add CrackLib configuration. */
+ setup_argv[3] = (char *) "password_dictionary";
+ setup_argv[4] = dictionary;
+ setup_argv[5] = NULL;
+ run_setup((const char **) setup_argv);
+
/* Obtain a new Kerberos context with that krb5.conf file. */
krb5_free_context(ctx);
code = krb5_init_context(&ctx);
is_password_test(ctx, vtable, data, &length_tests[i]);
vtable->close(ctx, data);
- /* Add simple character class configuration to krb5.conf. */
- setup_argv[5] = (char *) "minimum_different";
- setup_argv[6] = (char *) "8";
- setup_argv[7] = (char *) "require_ascii_printable";
+#else
+
+ /* Otherwise mark the CrackLib tests as skipped. */
+ count = ARRAY_SIZE(cracklib_tests) + ARRAY_SIZE(length_tests);
+ skip_block(count * 2 + 1, "not built with CrackLib support");
+
+#endif /* !HAVE_CRACKLIB */
+
+ /* Switch to simple character class configuration in krb5.conf. */
+ setup_argv[3] = (char *) "minimum_different";
+ setup_argv[4] = (char *) "8";
+ setup_argv[5] = (char *) "require_ascii_printable";
+ setup_argv[6] = (char *) "true";
+ setup_argv[7] = (char *) "require_non_letter";
setup_argv[8] = (char *) "true";
- setup_argv[9] = (char *) "require_non_letter";
- setup_argv[10] = (char *) "true";
- setup_argv[11] = NULL;
+ setup_argv[9] = NULL;
run_setup((const char **) setup_argv);
/* Obtain a new Kerberos context with that krb5.conf file. */
is_password_test(ctx, vtable, data, &letter_tests[i]);
vtable->close(ctx, data);
- /*
- * Add complex character class configuration to krb5.conf but drop
- * the dictionary configuration.
- */
+ /* Add complex character class configuration to krb5.conf. */
setup_argv[3] = (char *) "require_classes";
setup_argv[4] = (char *) "8-19:lower,upper 8-15:digit 8-11:symbol 24-24:3";
setup_argv[5] = NULL;
# Test suite for basic Heimdal external strength checking functionality.
#
# Written by Russ Allbery <eagle@eyrie.org>
-# Copyright 2016 Russ Allbery <eagle@eyrie.org>
+# Copyright 2016, 2017 Russ Allbery <eagle@eyrie.org>
# Copyright 2009, 2012, 2013, 2014
# The Board of Trustees of the Leland Stanford Junior University
#
use_prereq('Perl6::Slurp', 'slurp');
use_prereq('Test::More', '0.87_01');
+# Data directory to use for dictionaries.
+my $DATADIR = $ENV{BUILD} ? "$ENV{BUILD}/data" : 'tests/data';
+
+# This data structure drives most of our tests. Each list element is a block
+# of tests to run together with a specific Kerberos configuration. The keys
+# are:
+#
+# title - Title of the tests for test output
+# config - Hash of Kerberos configuration to use
+# needs - Dictionary type name we have to have to run this test
+# tests - List of classes of tests to run (JSON files in tests/data/passwords)
+my @TESTS = (
+ {
+ title => 'Generic tests',
+ config => {},
+ tests => [qw(principal)],
+ },
+ {
+ title => 'CrackLib tests',
+ config => {
+ password_dictionary => "$DATADIR/dictionary",
+ },
+ needs => 'CrackLib',
+ tests => [qw(cracklib principal)],
+ },
+ {
+ title => 'Password length tests',
+ config => {
+ minimum_length => 12,
+ },
+ tests => [qw(length)],
+ },
+ {
+ title => 'Password length tests with cracklib_maxlen',
+ config => {
+ password_dictionary => "$DATADIR/dictionary",
+ minimum_length => 12,
+ cracklib_maxlen => 11,
+ },
+ needs => 'CrackLib',
+ tests => [qw(length)],
+ },
+ {
+ title => 'Simple password character class tests',
+ config => {
+ minimum_different => 8,
+ require_ascii_printable => 'true',
+ require_non_letter => 'true',
+ },
+ tests => [qw(letter)],
+ },
+ {
+ title => 'Complex password character class tests',
+ config => {
+ require_classes =>
+ '8-19:lower,upper 8-15:digit 8-11:symbol 24-24:3',
+ },
+ tests => [qw(classes)],
+ },
+ {
+ title => 'CDB tests',
+ config => {
+ password_dictionary_cdb => test_file_path('data/wordlist.cdb'),
+ },
+ tests => [qw(cdb principal)],
+ },
+ {
+ title => 'SQLite tests',
+ config => {
+ password_dictionary_sqlite =>
+ test_file_path('data/wordlist.sqlite'),
+ },
+ tests => [qw(sqlite principal)],
+ },
+);
+
# Run the newly-built heimdal-strength command and return the status, output,
# and error output as a list. If told to expect an immediate error, does not
# pass input to the process.
return $json->decode($testdata);
}
+# Run a block of password tests, handling krb5.conf setup and skipping tests
+# if required dictionary support isn't available.
+#
+# $spec_ref - Test specification (from @TESTS)
+# $tests_ref - Hash structure containing all loaded password tests
+#
+# Returns: undef
+sub run_password_tests {
+ my ($spec_ref, $tests_ref) = @_;
+ my $krb5_conf = create_krb5_conf($spec_ref->{config});
+ local $ENV{KRB5_CONFIG} = $krb5_conf;
+ note($spec_ref->{title});
+
+ # If we need support for a type of dictionary, check for that and skip the
+ # tests if that dictionary wasn't supported.
+ SKIP: {
+ if ($spec_ref->{needs}) {
+ my $type = $spec_ref->{needs};
+ my ($status, undef, $err) = run_heimdal_strength('test', 'pass');
+ my $err_regex = qr{ not [ ] built [ ] with [ ] \Q$type\E }xms;
+ if ($status == 1 && $err =~ $err_regex) {
+ my $total = 0;
+ for my $block (@{ $spec_ref->{tests} }) {
+ $total += scalar(@{ $tests_ref->{$block} });
+ }
+ skip("not built with $type support", $total * 3);
+ }
+ }
+
+ # Run the tests.
+ for my $block (@{ $spec_ref->{tests} }) {
+ if (scalar(@{ $spec_ref->{tests} }) > 1) {
+ note('... ', $block);
+ }
+ for my $test (@{ $tests_ref->{$block} }) {
+ check_password($test);
+ }
+ }
+ }
+ return;
+}
+
# Test a required_classes syntax error. Takes the string for required_classes
# and verifies that the appropriate error message is returned.
#
return;
}
-# Load the password tests from JSON. Accumulate a total count of tests for
-# the testing plan.
-my (%tests, $count);
+# Load the password tests from JSON.
+my %tests;
for my $type (qw(cdb classes cracklib length letter principal sqlite)) {
my $tests = load_password_tests("$type.json");
$tests{$type} = $tests;
- $count += scalar(@{$tests});
-}
-
-# We run the principal tests three times, for CrackLib, CDB, and SQLite.
-$count += 2 * scalar(@{ $tests{principal} });
-
-# We run the length checks twice.
-$count += scalar(@{ $tests{length} });
-
-# We can now calculate our plan based on three tests for each password test,
-# plus 27 additional tests for error handling.
-plan(tests => $count * 3 + 27);
-
-# Install the krb5.conf file with a configuration pointing to the test
-# CrackLib dictionary.
-my $datadir = $ENV{BUILD} ? "$ENV{BUILD}/data" : 'tests/data';
-my $krb5_conf
- = create_krb5_conf({ password_dictionary => "$datadir/dictionary" });
-local $ENV{KRB5_CONFIG} = $krb5_conf;
-
-# Run the CrackLib password tests and based-on-principal tests from JSON.
-note('CrackLib tests');
-for my $test (@{ $tests{cracklib} }) {
- check_password($test);
}
-note('Generic tests with CrackLib');
-for my $test (@{ $tests{principal} }) {
- check_password($test);
-}
-
-# Install the krb5.conf file with a length restriction.
-$krb5_conf = create_krb5_conf({ minimum_length => 12 });
-local $ENV{KRB5_CONFIG} = $krb5_conf;
-# Run the password length checks.
-note('Password length checks');
-for my $test (@{ $tests{length} }) {
- check_password($test);
-}
-
-# Add a CrackLib dictionary and a maximum password length setting.
-$krb5_conf = create_krb5_conf(
- {
- password_dictionary => "$datadir/dictionary",
- minimum_length => 12,
- cracklib_maxlen => 11,
- }
-);
-local $ENV{KRB5_CONFIG} = $krb5_conf;
-
-# Run the length checks again. They should have the same result, even though
-# there's a CrackLib dictionary, since the dictionary hit is above the minimum
-# length.
-note('Password length checks with cracklib_maxlen');
-for my $test (@{ $tests{length} }) {
- check_password($test);
-}
-
-# Install the krb5.conf file for simple character class restrictions.
-$krb5_conf = create_krb5_conf(
- {
- minimum_different => 8,
- require_ascii_printable => 'true',
- require_non_letter => 'true',
+# Determine our plan based on the test blocks we run (there are three test
+# results for each password test), plus 27 additional tests for error
+# handling.
+my $count = 0;
+for my $spec_ref (@TESTS) {
+ for my $block (@{ $spec_ref->{tests} }) {
+ $count += scalar(@{ $tests{$block} });
}
-);
-local $ENV{KRB5_CONFIG} = $krb5_conf;
-
-# Run the simple character class tests.
-note('Simple password character class checks');
-for my $test (@{ $tests{letter} }) {
- check_password($test);
-}
-
-# Install the krb5.conf file for complex character class restrictions.
-my $classes = '8-19:lower,upper 8-15:digit 8-11:symbol 24-24:3';
-$krb5_conf = create_krb5_conf({ require_classes => $classes });
-local $ENV{KRB5_CONFIG} = $krb5_conf;
-
-# Run the complex character class tests.
-note('Complex password character class checks');
-for my $test (@{ $tests{classes} }) {
- check_password($test);
}
+plan(tests => $count * 3 + 27);
-# Install the krb5.conf file with configuration pointing to the CDB
-# dictionary.
-my $cdb_database = test_file_path('data/wordlist.cdb');
-$krb5_conf = create_krb5_conf({ password_dictionary_cdb => $cdb_database });
-local $ENV{KRB5_CONFIG} = $krb5_conf;
-
-# Check whether we were built with CDB support. If so, run those tests.
-my ($status, $output, $err) = run_heimdal_strength('test', 'password');
-SKIP: {
- if ($status == 1 && $err =~ m{ not [ ] built [ ] with [ ] CDB }xms) {
- my $total = scalar(@{ $tests{cdb} }) + scalar(@{ $tests{principal} });
- skip('not built with CDB support', $total * 3);
- }
-
- # Run the CDB and principal password tests from JSON.
- note('CDB tests');
- for my $test (@{ $tests{cdb} }) {
- check_password($test);
- }
- note('Generic tests with CDB');
- for my $test (@{ $tests{principal} }) {
- check_password($test);
- }
-}
-
-# Install the krb5.conf file with configuration pointing to the SQLite
-# dictionary.
-my $sqlite_database = test_file_path('data/wordlist.sqlite');
-$krb5_conf
- = create_krb5_conf({ password_dictionary_sqlite => $sqlite_database });
-local $ENV{KRB5_CONFIG} = $krb5_conf;
-
-# Check whether we were built with SQLite support. If so, run those tests.
-($status, $output, $err) = run_heimdal_strength('test', 'password');
-SKIP: {
- if ($status == 1 && $err =~ m{ not [ ] built [ ] with [ ] SQLite }xms) {
- my $total = scalar(@{ $tests{sqlite} });
- $total += scalar(@{ $tests{principal} });
- skip('not built with SQLite support', $total * 3);
- }
-
- # Run the SQLite and principal password tests from JSON.
- note('SQLite tests');
- for my $test (@{ $tests{sqlite} }) {
- check_password($test);
- }
- note('Generic tests with SQLite');
- for my $test (@{ $tests{principal} }) {
- check_password($test);
- }
+# Run all the tests.
+for my $spec_ref (@TESTS) {
+ run_password_tests($spec_ref, \%tests);
}
# Test error for an unknown character class.
-$krb5_conf = create_krb5_conf({ require_classes => 'bogus' });
+my $krb5_conf = create_krb5_conf({ require_classes => 'bogus' });
local $ENV{KRB5_CONFIG} = $krb5_conf;
my $error_prefix = 'Cannot initialize strength checking';
-($status, $output, $err) = run_heimdal_strength('test', 'password', 1);
+my ($status, $output, $err) = run_heimdal_strength('test', 'password', 1);
is($status, 1, 'Bad character class (status)');
is($output, q{}, '...no output');
is($err, "$error_prefix: unknown character class bogus\n", '...correct error');