#! /usr/bin/perl
# Written by Zack Weinberg <zackw at panix.com> in 2018 and 2020.
# To the extent possible under law, Zack Weinberg has waived all
# copyright and related or neighboring rights to this work.
#
# See https://creativecommons.org/publicdomain/zero/1.0/ for further
# details.
# Generate crypt-hashes.h from lib/hashes.conf and configure settings.
# Also responsible for checking lib/hashes.conf for errors.
#
# Caution: if you change the format of lib/hashes.conf you will
# probably need to modify expand-selected-hashes as well as this script.
use v5.14; # implicit use strict, use feature ':5.14'
use warnings FATAL => 'all';
use utf8;
use open qw(:std :utf8);
no if $] >= 5.022, warnings => 'experimental::re_strict';
use if $] >= 5.022, re => 'strict';
use File::Spec::Functions qw(splitpath);
use FindBin ();
use lib $FindBin::Bin;
use BuildCommon qw(
enabled_set
parse_hashes_conf
);
sub output {
my ($basehc, $hashes_enabled, $hconf) = @_;
my %enabled = enabled_set($hashes_enabled);
my @enabled_hashes;
print <<"EOT";
/* Generated by ${FindBin::Script} from $basehc. DO NOT EDIT. */
#ifndef _CRYPT_HASHES_H
#define _CRYPT_HASHES_H 1
EOT
for my $e (sort { $a->name cmp $b->name } values %{$hconf->hashes}) {
my $name = $e->name;
my $ena = $enabled{$name} // 0;
printf "#define INCLUDE_%-*s %d\n", $hconf->max_namelen, $name, $ena;
push @enabled_hashes, $e if $ena;
}
print <<'EOT';
/* Internal symbol renames for static linkage, see crypt-port.h. */
EOT
for my $e (@enabled_hashes) {
my $name_rn = $e->name . '_rn';
printf "#define crypt_%-*s _crypt_crypt_%s\n",
$hconf->max_namelen + 5, $name_rn, $name_rn;
printf "#define gensalt_%-*s _crypt_gensalt_%s\n",
$hconf->max_namelen + 3, $name_rn, $name_rn;
}
print <<'EOT';
/* Prototypes for hash algorithm entry points. */
EOT
for my $e (@enabled_hashes) {
my $name = $e->name;
print <<"EOT";
extern void crypt_${name}_rn (const char *, size_t, const char *,
size_t, uint8_t *, size_t, void *, size_t);
extern void gensalt_${name}_rn (unsigned long,
const uint8_t *, size_t, uint8_t *, size_t);
EOT
}
print <<'EOT';
#define HASH_ALGORITHM_TABLE_ENTRIES \
EOT
# Entries in this table can be in any order _except_ that the hash
# whose prefix is the empty string, if it's enabled, must be last.
# The simplest way to accomplish this is to sort the prefixes in
# descending order of length (and then alphabetically as a
# tiebreaker).
my @table_hashes = sort {
-(length($a->prefix) <=> length($b->prefix))
|| $a->prefix cmp $b->prefix
} @enabled_hashes;
for my $e (@table_hashes) {
my $name_rn = $e->name . '_rn,';
my $q_prefix = '"' . $e->prefix . '",';
my $str_nrbytes = $e->nrbytes . ',';
printf " { %-*s %d, crypt_%-*s gensalt_%-*s %-*s %s}, \\\n",
$hconf->max_prefixlen + 3, $q_prefix, length($e->prefix),
$hconf->max_namelen + 4, $name_rn,
$hconf->max_namelen + 4, $name_rn,
$hconf->max_nrbyteslen + 1, $str_nrbytes, $e->is_strong;
}
print " { 0, 0, 0, 0, 0, 0 }\n";
# The default_candidates array is in decreasing order of strength;
# select the first one that's enabled, if any.
my $default_prefix;
for my $e (@{$hconf->default_candidates}) {
if ($enabled{$e->name}) {
$default_prefix = $e->prefix;
last;
}
}
print <<"EOT" if defined $default_prefix;
#define HASH_ALGORITHM_DEFAULT "$default_prefix"
EOT
print <<'EOT';
#endif /* crypt-hashes.h */
EOT
close STDOUT or die "write error: $!\n";
return;
}
#
# Main
#
if (scalar(@ARGV) != 2) {
print {*STDERR} "usage: ${FindBin::Script}"
. "hashes.conf ,names,of,enabled,hashes,\n";
exit 1;
}
exit 0 if eval {
my ($hashes_conf, $hashes_enabled) = @ARGV;
my (undef, undef, $basehc) = splitpath($hashes_conf);
my $hconf = parse_hashes_conf($hashes_conf);
output($basehc, $hashes_enabled, $hconf);
1;
};
print {*STDERR} "${FindBin::Script}: $@";
exit 1;