tests/TESTS tests/data/krb5.conf tests/data/make-krb5-conf \
tests/data/passwords tests/data/perl.conf tests/data/perlcriticrc \
tests/data/perltidyrc tests/data/valgrind.supp tests/data/wordlist \
- tests/data/wordlist.cdb tests/docs/pod-spelling-t tests/docs/pod-t \
- tests/perl/critic-t tests/perl/minimum-version-t \
- tests/perl/strict-t tests/tap/libtap.sh tests/tap/perl/Test/RRA.pm \
+ tests/data/wordlist.cdb tests/data/wordlist.sqlite \
+ tests/docs/pod-spelling-t tests/docs/pod-t tests/perl/critic-t \
+ tests/perl/minimum-version-t tests/perl/strict-t \
+ tests/tap/libtap.sh tests/tap/perl/Test/RRA.pm \
tests/tap/perl/Test/RRA/Config.pm \
- tests/tap/perl/Test/RRA/Automake.pm tests/tools/cdbmake-wordlist-t \
- tests/tools/heimdal-history-t tests/tools/heimdal-strength-t \
+ tests/tap/perl/Test/RRA/Automake.pm tests/tools/heimdal-history-t \
+ tests/tools/heimdal-strength-t tests/tools/wordlist-cdb-t \
tests/util/xmalloc-t tools/heimdal-strength.pod
# Do this globally. Everything needs to find the Kerberos headers and
$(KRB5_LIBS) $(CDB_LIBS)
# Other tools.
-dist_bin_SCRIPTS = tools/cdbmake-wordlist tools/heimdal-history
+dist_bin_SCRIPTS = tools/heimdal-history tools/krb5-strength-wordlist
# Man pages for all tools.
dist_man_MANS = tools/heimdal-history.1 tools/heimdal-strength.1 \
- tools/cdbmake-wordlist.1
+ tools/krb5-strength-wordlist.1
# Handle the standard stuff that make maintainer-clean should probably remove
# but doesn't. This breaks the GNU coding standard, but in this area the GNU
build-aux/config.guess build-aux/config.sub build-aux/depcomp \
build-aux/install-sh build-aux/ltmain.sh build-aux/missing \
config.h.in config.h.in~ configure m4/libtool.m4 m4/ltoptions.m4 \
- m4/ltsugar.m4 m4/ltversion.m4 m4/lt~obsolete.m4
+ m4/ltsugar.m4 m4/ltversion.m4 m4/lt~obsolete.m4 \
+ tests/data/wordlist.cdb tests/data/wordlist.sqlite \
+ tools/heimdal-history.1 tools/heimdal-strength.1 \
+ tools/krb5-strength-wordlist.1
+
+# Also remove the generated *.c files from our JSON test data on
+# maintainer-clean.
+maintainer-clean-local:
+ rm -f tests/data/passwords/*.c
# A set of flags for warnings. Add -O because gcc won't find some warnings
# without optimization turned on. Desirable warnings that can't be turned
# Used by maintainers to run the main test suite under valgrind. Suppress
# the xmalloc and pod-spelling tests because the former won't work properly
# under valgrind (due to increased memory usage) and the latter is pointless
-# to run under valgrind. Don't try to trace the test for cdbmake-wordlist,
-# since it's pure Perl.
+# to run under valgrind. Don't try to trace the test for
+# krb5-strength-wordlist, since it's pure Perl.
check-valgrind: $(check_PROGRAMS) tests/data/dictionary.pwd
rm -rf $(abs_top_builddir)/tmp-valgrind
mkdir $(abs_top_builddir)/tmp-valgrind
--show-reachable=yes --trace-children=yes \
--log-file=$(abs_top_builddir)/tmp-valgrind/log.%p \
--suppressions=tests/data/valgrind.supp \
- --trace-children-skip="/bin/sh,*/cat,*/diff,*/expr,*/grep,*/mkdir,*/rm,*/rmdir,*/sed,*/sleep,*/true,*/wc,*/docs/*-t,*/perl/*-t,*/data/make-krb5-conf,*/tools/cdbmake-wordlist-t" \
+ --trace-children-skip="/bin/sh,*/cat,*/diff,*/expr,*/grep,*/mkdir,*/rm,*/rmdir,*/sed,*/sleep,*/true,*/wc,*/docs/*-t,*/perl/*-t,*/data/make-krb5-conf,*/tools/wordlist-*-t" \
tests/runtests -l '$(abs_top_srcdir)/tests/TESTS'
#!/bin/sh
#
-# Test suite for the cdbmake-wordlist utility.
+# Test suite for the CDB handling in the krb5-strength-wordlist utility.
#
# Written by Russ Allbery <eagle@eyrie.org>
-# Copyright 2013
+# Copyright 2013, 2014
# The Board of Trustees of the Leland Stanford Junior University
#
# See LICENSE for licensing terms.
echo 'عربى' >> "$tmpdir/wordlist"
# Test generation of the basic cdb file.
-cdbmake="$SOURCE/../tools/cdbmake-wordlist"
-ok_program 'Database generation' 0 '' "$cdbmake" "$tmpdir/wordlist"
+makelist="$SOURCE/../tools/krb5-strength-wordlist"
+ok_program 'Database generation' 0 '' \
+ "$makelist" -c "$tmpdir/wordlist.cdb" "$tmpdir/wordlist"
# Check the contents.
ok_program 'Database contains password' 0 '1' \
# Regenerate the database, filtering out short passwords.
ok_program 'Database generation with no short passwords' 0 '' \
- "$cdbmake" -l 8 "$tmpdir/wordlist"
+ "$makelist" -c "$tmpdir/wordlist.cdb" -l 8 "$tmpdir/wordlist"
ok_program 'Database still contains password' 0 '1' \
cdb -q "$tmpdir/wordlist.cdb" password
ok_program 'Database does not contain one' 100 '' \
# Regenerate the database, filtering out non-ASCII words.
ok_program 'Database generation with no non-ASCII' 0 '' \
- "$cdbmake" -a "$tmpdir/wordlist"
+ "$makelist" -c "$tmpdir/wordlist.cdb" -a "$tmpdir/wordlist"
ok_program 'Database still contains password' 0 '1' \
cdb -q "$tmpdir/wordlist.cdb" password
ok_program 'Database does not contain non-ASCII password' 100 '' \
# Regenerate the database, filtering out long passwords.
ok_program 'Database generation with no long passwords' 0 '' \
- "$cdbmake" -L 10 "$tmpdir/wordlist"
+ "$makelist" -c "$tmpdir/wordlist.cdb" -L 10 "$tmpdir/wordlist"
ok_program 'Database still contains bitterbane' 0 '1' \
cdb -q "$tmpdir/wordlist.cdb" bitterbane
ok_program 'Database does not contain happenstance' 100 '' \
# Regenerate the database, filtering out words starting with b or ending in d.
ok_program 'Database generation with no b passwords' 0 '' \
- "$cdbmake" -x '\Ab' -x '.*d' "$tmpdir/wordlist"
+ "$makelist" -c "$tmpdir/wordlist.cdb" -x '\Ab' -x '.*d' "$tmpdir/wordlist"
ok_program 'Database does not contain bitterbane' 100 '' \
cdb -q "$tmpdir/wordlist.cdb" bitterbane
ok_program 'Database still contains happenstance' 0 '1' \
# Try filtering the wordlist into a new wordlist.
ok_program 'Wordlist filtering' 0 '' \
- "$cdbmake" -a -x '.*d' -l 8 -o "$tmpdir/wordlist.new" "$tmpdir/wordlist"
+ "$makelist" -a -x '.*d' -l 8 -o "$tmpdir/wordlist.new" "$tmpdir/wordlist"
( echo 'bitterbane'; echo 'happenstance' ) > "$tmpdir/wordlist.expected"
ok_program 'Filtered wordlist is correct' 0 '' \
cmp "$tmpdir/wordlist.expected" "$tmpdir/wordlist.new"
# the user's PATH is searched for cdb.
my $CDB = 'cdb';
+# The SQL used to create the SQLite database.
+my $SQLITE_CREATE = q{
+ CREATE TABLE passwords (
+ password TEXT UNIQUE NOT NULL,
+ drowssap TEXT UNIQUE NOT NULL
+ )
+};
+
+# The SQL used to insert passwords into the database.
+my $SQLITE_INSERT = q{
+ INSERT OR IGNORE INTO passwords (password, drowssap) values (?, ?)
+};
+
# print with error checking and an explicit file handle.
#
# $fh - Output file handle
# cdb to turn that into a database.
#
# $in_fh - Input file handle for the source wordlist
-# $input - Name of the input file, from which the CDB file name is derived
+# $output - Name of the output CDB file
# $filter - Reference to sub that returns true to keep a word, false otherwise
#
# Returns: undef
# Throws: Text exception on output failure or pre-existing temporary file
sub write_cdb {
- my ($in_fh, $input, $filter) = @_;
+ my ($in_fh, $output, $filter) = @_;
+
+ # Check that the output CDB file doesn't exist.
+ if (-f $output) {
+ die "$0: output file $output already exists\n";
+ }
# Create a temporary file to write the CDB input into.
- my $tmp = $input . '.data';
+ my $tmp = $output . '.data';
if (-f $tmp) {
die "$0: temporary output file $tmp already exists\n";
}
close($tmp_fh) or die "$0: cannot write to temporary file $tmp: $!\n";
# Run cdb to turn the result into a CDB database. Ignore duplicate keys.
- system($CDB, '-c', '-u', "$input.cdb", $tmp) == 0
+ system($CDB, '-c', '-u', $output, $tmp) == 0
or die "$0: cdb -c failed\n";
# Remove the temporary file and return.
return;
}
+# Filter the given input file and write it to a newly-created SQLite database.
+# Requires the DBI and DBD::SQLite modules be installed. The database will
+# contain one table, passwords, with two columns, password and drowssap, which
+# store the word and the word reversed for each word that passes the filter.
+#
+# $in_fh - Input file handle for the source wordlist
+# $output - Name of the output SQLite database
+# $filter - Reference to sub that returns true to keep a word, false otherwise
+#
+# Returns: undef
+# Throws: Text exception on output failure, pre-existing output file, or
+# missing Perl modules
+sub write_sqlite {
+ my ($in_fh, $output, $filter) = @_;
+
+ # Check that the output SQLite file doesn't exist.
+ if (-f $output) {
+ die "$0: output file $output already exists\n";
+ }
+
+ # Load the required modules.
+ require DBI;
+ require DBD::SQLite;
+
+ # Open and create the database.
+ my $options = { PrintError => 0, RaiseError => 1, AutoCommit => 0 };
+ my $dbh = DBI->connect("dbi:SQLite:dbname=$output", q{}, q{}, $options);
+ $dbh->do($SQLITE_CREATE);
+
+ # Prepare the insert statement for each word.
+ my $sth = $dbh->prepare($SQLITE_INSERT);
+
+ # Walk through the input word list and add each word that passes the
+ # filter to the database, both as-is and reversed.
+ while (defined(my $word = <$in_fh>)) {
+ chomp($word);
+ next if !$filter->($word);
+ my $reversed = reverse($word);
+ $sth->execute($word, $reversed);
+ }
+
+ # Commit and close the database.
+ $dbh->commit;
+ $dbh->disconnect;
+ return;
+}
+
# Filter the given input file and write the results to a new wordlist.
#
# $in_fh - Input file handle for the source wordlist
local $0 = basename($0);
# Parse the argument list.
-my ($ascii, @exclude, $max_length, $min_length, $manual, $output);
+my ($ascii, $cdb, @exclude, $max_length, $min_length, $manual, $output,
+ $sqlite);
Getopt::Long::config('bundling', 'no_ignore_case');
GetOptions(
'ascii|a' => \$ascii,
+ 'cdb|c=s' => \$cdb,
'max-length|L=i' => \$max_length,
'min-length|l=i' => \$min_length,
'manual|man|m' => \$manual,
'output|o=s' => \$output,
+ 'sqlite|s=s' => \$sqlite,
'exclude|x=s' => \@exclude,
);
if ($manual) {
if (@ARGV != 1) {
die "Usage: cdbmake-wordlist <wordlist>\n";
}
+if (defined($cdb) && (defined($output) || defined($sqlite))) {
+ die "$0: -c cannot be used with -o or -s\n";
+} elsif (defined($output) && defined($sqlite)) {
+ die "$0: -o cannot be used with -c or -s\n";
+}
my $input = $ARGV[0];
# Build a filter from our command-line parameters. This is an anonymous sub
or die "$0: cannot open input file $input: $!\n";
if (defined($output)) {
write_wordlist($in_fh, $output, $filter);
-} else {
- write_cdb($in_fh, $input, $filter);
+} elsif (defined($cdb)) {
+ write_cdb($in_fh, $cdb, $filter);
+} elsif (defined($sqlite)) {
+ write_sqlite($in_fh, $sqlite, $filter);
}
close($in_fh) or die "$0: cannot read all of input file $input: $!\n";
__END__
=for stopwords
-cdbmake-wordlist cdb whitespace wordlist lookups lookup sublicense
-MERCHANTABILITY NONINFRINGEMENT krb5-strength --ascii Allbery regexes
-output-wordlist
+krb5-strength-wordlist krb5-strength cdb whitespace lookups lookup
+sublicense MERCHANTABILITY NONINFRINGEMENT krb5-strength --ascii Allbery
+regexes output-wordlist
=head1 NAME
-cdbmake-wordlist - Create a cdb database from a wordlist
+krb5-strength-wordlist - Create a krb5-strength database from a wordlist
=head1 SYNOPSIS
-B<cdbmake-wordlist> [B<-am>] [B<-l> I<min-length>] [B<-L> I<max-length>]
- [B<-o> I<output-wordlist>] [B<-x> I<exclude> ...] I<wordlist>
+B<krb5-strength-wordlist> [B<-am>] [B<-c> I<output-cdb>] [B<-l> I<min-length>]
+ [B<-L> I<max-length>] [B<-o> I<output-wordlist>] [B<-s> I<output-sqlite>]
+ [B<-x> I<exclude> ...] I<wordlist>
=head1 DESCRIPTION
-cdb is a format invented by Dan Bernstein for fast, constant databases.
+B<krb5-strength-wordlist> converts a word list (a file containing one word
+per line) into a database that can be used by the krb5-strength plugin or
+B<heimdal-strength> command for checking passwords. Two database formats
+are supported, with different features. CDB is more space-efficient and
+possibly faster, but supports checking passwords only against exact
+matches or simple transformations (removing small numbers of leading and
+trailing characters). SQLite creates a much larger database, but supports
+rejecting any password within edit distance one of a word in the word
+list.
+
+CDB is a format invented by Dan Bernstein for fast, constant databases.
The database is fixed during creation and cannot be changed without
-rebuilding it, and is optimized for very fast access. This program takes
-as input a wordlist file (a set of words, possibly including whitespace,
-separated by newlines) and turns it into a cdb file with the words as keys
-and the constant C<1> as a value. The resulting database is suitable for
-fast existence lookups in the wordlist, such as for password dictionary
-checks.
-
-B<cdbmake-wordlist> takes one argument, the input wordlist file. The
-output cdb database will have the same name as I<wordlist> but with
-C<.cdb> appended. The input wordlist file does not have to be sorted.
-
-B<cdbmake-wordlist> can, instead of building a CDB file, filter a wordlist
-against the criteria given on the command line and generate a new
-wordlist. See the B<-o> option for more details.
+rebuilding it, and is optimized for very fast access. For cdb, the
+database generated by this program will have keys for each word in the
+word list and the constant C<1> as the value.
+
+SQLite stores the word list in a single SQL table containing both each
+word and each word reversed. This allows the krb5-strength plugin or
+B<heimdal-strength> command to reject passwords within edit distance one
+of any word in the word list. (Edit distance one means that the word list
+entry can be formed by changing a single character of the password, either
+by adding one character, removing one character, or changing one character
+to a different character.) However, the SQLite database will be much
+larger and lookups may be somewhat slower.
+
+B<krb5-strength-wordlist> takes one argument, the input wordlist file.
+Use the B<-c> option to specify an output CDB file, B<-s> to specify an
+output SQLite file, or B<-o> to just filter the word list against the
+criteria given on the command line and generate a new word list.
+The input wordlist file does not have to be sorted. See the individual
+option descriptions for more information.
=head1 OPTIONS
from the resulting cdb file, leaving only words that consist solely of
ASCII non-control characters.
+=item B<-c> I<output-cdb>, B<--cdb>=I<output-cdb>
+
+Create a CDB database in I<output-cdb>. A temporary file named after
+I<output-cdb> with C<.data> appended will be created in the same directory
+and used to stage the database contents. The actual CDB file will be
+built using the B<cdb> command, which must be on the user's path. If
+either file already exists, B<krb5-strength-wordlist> will abort with an
+error.
+
+This option cannot be used with B<-o> or B<-s>.
+
=item B<-L> I<maximum>, B<--max-length>=I<maximum>
Filter all words of length greater than I<maximum> from the resulting cdb
=item B<-o> I<wordlist>, B<--output>=I<wordlist>
-Rather than creating a CDB database, apply the filter rules given by the
-other command-line arguments and generate a new wordlist in the file name
-given by the I<wordlist> option. This can be used to reduce the size of
-a raw wordlist file (such as one taken from Internet sources) by removing
-the words that will be filtered out of the CDB file anyway, thus reducing
-the size of the source required to regenerate the CDB database.
+Rather than creating a database, apply the filter rules given by the other
+command-line arguments and generate a new wordlist in the file name given
+by the I<wordlist> option. This can be used to reduce the size of a raw
+wordlist file (such as one taken from Internet sources) by removing the
+words that will be filtered out of the dictionary anyway, thus reducing
+the size of the source required to regenerate the dictionary.
+
+This option cannot be used with B<-c> or B<-s>.
+
+=item B<-s> I<output-sqlite>, B<--sqlite>=I<output-sqlite>
+
+Create a SQLite database in I<output-sqlite>. If this file already
+exists, B<krb5-strength-wordlist> will abort with an error. The resulting
+SQLite database will have one table, C<passwords>, with two columns,
+C<password> and C<drowssap>. The first holds a word from the word list,
+and the second holds the same word reversed.
+
+Using this option requires the DBI and DBD::SQLite Perl modules be
+installed.
-If this option is given, no CDB database will be created.
+This option cannot be used with B<-c> or B<-o>.
=item B<-x> I<exclude>, B<--exclude>=I<exclude>
=head1 COPYRIGHT AND LICENSE
-Copyright 2013 The Board of Trustees of the Leland Stanford Junior
+Copyright 2013, 2014 The Board of Trustees of the Leland Stanford Junior
University
Permission is hereby granted, free of charge, to any person obtaining a
=head1 SEE ALSO
-cdb(1)
+cdb(1), L<DBI>, L<DBD::SQLite>
The cdb file format is defined at L<http://cr.yp.to/cdb.html>.