feat: add full Zonemaster stack with Docker and Spanish UI

- Clone all 5 Zonemaster component repos (LDNS, Engine, CLI, Backend, GUI)
- Dockerfile.backend: 8-stage multi-stage build LDNS→Engine→CLI→Backend
- Dockerfile.gui: Astro static build served via nginx
- docker-compose.yml: backend (internal) + frontend (port 5353)
- nginx.conf: root redirects to /es/, /api/ proxied to backend
- zonemaster-gui/config.ts: defaultLanguage set to 'es' (Spanish)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 08:19:24 +02:00
commit 8d4eaa1489
1567 changed files with 204155 additions and 0 deletions

View File

@@ -0,0 +1,292 @@
package Zonemaster::Engine::Constants;
use v5.16.0;
use warnings;
use version; our $VERSION = version->declare("v1.2.5");
use Carp;
use English qw( -no_match_vars ) ;
use parent 'Exporter';
use Net::IP::XS;
use Text::CSV;
use File::ShareDir qw[dist_dir dist_file];
use Readonly;
=head1 NAME
Zonemaster::Engine::Constants - module holding constants used in Test modules
=head1 SYNOPSIS
use Zonemaster::Engine::Constants ':all';
=head1 EXPORTED GROUPS
=over
=item all
All exportable names.
=item algo
DNSSEC algorithms.
=item cname
CNAME records.
=item name
Label and name lengths.
=item ip
IP version constants.
=item soa
SOA values limits.
=item misc
Other, uncategorized export names, e.g. UDP payload limit and minimum number of name servers per zone.
=item addresses
Address classes for IPv4 and IPv6.
=back
=cut
our @EXPORT_OK = qw[
$ALGO_STATUS_DEPRECATED
$ALGO_STATUS_PRIVATE
$ALGO_STATUS_RESERVED
$ALGO_STATUS_UNASSIGNED
$ALGO_STATUS_OTHER
$ALGO_STATUS_NOT_RECOMMENDED
$ALGO_STATUS_NOT_ZONE_SIGN
$BLACKLISTING_ENABLED
$CNAME_MAX_CHAIN_LENGTH
$CNAME_MAX_RECORDS
$DURATION_5_MINUTES_IN_SECONDS
$DURATION_1_HOUR_IN_SECONDS
$DURATION_4_HOURS_IN_SECONDS
$DURATION_12_HOURS_IN_SECONDS
$DURATION_1_DAY_IN_SECONDS
$DURATION_1_WEEK_IN_SECONDS
$DURATION_180_DAYS_IN_SECONDS
$FQDN_MAX_LENGTH
$IP_VERSION_4
$IP_VERSION_6
$LABEL_MAX_LENGTH
$SERIAL_BITS
$SERIAL_MAX_VARIATION
$MINIMUM_NUMBER_OF_NAMESERVERS
$UDP_PAYLOAD_LIMIT
$EDNS_UDP_PAYLOAD_DNSSEC_DEFAULT
$EDNS_UDP_PAYLOAD_DEFAULT
$EDNS_UDP_PAYLOAD_COMMON_LIMIT
@IPV4_SPECIAL_ADDRESSES
@IPV6_SPECIAL_ADDRESSES
];
our %EXPORT_TAGS = (
all => \@EXPORT_OK,
algo => [
qw($ALGO_STATUS_DEPRECATED $ALGO_STATUS_PRIVATE $ALGO_STATUS_RESERVED $ALGO_STATUS_UNASSIGNED $ALGO_STATUS_OTHER $ALGO_STATUS_NOT_ZONE_SIGN $ALGO_STATUS_NOT_RECOMMENDED)
],
cname => [ qw($CNAME_MAX_CHAIN_LENGTH $CNAME_MAX_RECORDS) ],
name => [qw($FQDN_MAX_LENGTH $LABEL_MAX_LENGTH)],
ip => [qw($IP_VERSION_4 $IP_VERSION_6)],
soa => [
qw($DURATION_5_MINUTES_IN_SECONDS $DURATION_1_HOUR_IN_SECONDS $DURATION_4_HOURS_IN_SECONDS $DURATION_12_HOURS_IN_SECONDS $DURATION_1_DAY_IN_SECONDS $DURATION_1_WEEK_IN_SECONDS $DURATION_180_DAYS_IN_SECONDS $SERIAL_BITS $SERIAL_MAX_VARIATION)
],
misc => [qw($UDP_PAYLOAD_LIMIT $EDNS_UDP_PAYLOAD_DNSSEC_DEFAULT $EDNS_UDP_PAYLOAD_DEFAULT $EDNS_UDP_PAYLOAD_COMMON_LIMIT $MINIMUM_NUMBER_OF_NAMESERVERS $BLACKLISTING_ENABLED)]
, # everything in %EXPORT_OK that isn't included in any of the other tags
addresses => [qw(@IPV4_SPECIAL_ADDRESSES @IPV6_SPECIAL_ADDRESSES)],
);
=head1 EXPORTED NAMES
=over
=item * C<$ALGO_STATUS_DEPRECATED>
=item * C<$ALGO_STATUS_PRIVATE>
=item * C<$ALGO_STATUS_RESERVED>
=item * C<$ALGO_STATUS_UNASSIGNED>
=item * C<$ALGO_STATUS_OTHER>
=item * C<$ALGO_STATUS_NOT_RECOMMENDED>
=item * C<$ALGO_STATUS_NOT_ZONE_SIGN>
=item * C<$BLACKLISTING_ENABLED>
A boolean, used to enable the name server blacklisting mechanism.
=item * C<$CNAME_MAX_CHAIN_LENGTH>
An integer, used to define the maximum length of a CNAME chain when doing consecutive recursive lookups.
=item * C<$CNAME_MAX_RECORDS>
An integer, used to define the maximum number of CNAME records in a response.
=item * C<$DURATION_5_MINUTES_IN_SECONDS>
=item * C<$DURATION_1_HOUR_IN_SECONDS>
=item * C<$DURATION_4_HOURS_IN_SECONDS>
=item * C<$DURATION_12_HOURS_IN_SECONDS>
=item * C<$DURATION_1_DAY_IN_SECONDS>
=item * C<$DURATION_1_WEEK_IN_SECONDS>
=item * C<$DURATION_180_DAYS_IN_SECONDS>
=item * C<$FQDN_MAX_LENGTH>
=item * C<$LABEL_MAX_LENGTH>
=item * C<$IP_VERSION_4>
=item * C<$IP_VERSION_6>
=item * C<$SERIAL_BITS>
An integer, used to define the size of the serial number space, as defined in RFC1982, section 2.
=item * C<$SERIAL_MAX_VARIATION>
=item * C<$MINIMUM_NUMBER_OF_NAMESERVERS>
=item * C<$UDP_PAYLOAD_LIMIT>
=item * C<$EDNS_UDP_PAYLOAD_DEFAULT>
An integer, used to define the EDNS0 UDP packet size in non-DNSSEC EDNS queries.
=item * C<$EDNS_UDP_PAYLOAD_COMMON_LIMIT>
=item * C<$EDNS_UDP_PAYLOAD_DNSSEC_DEFAULT>
An integer, used to define the EDNS0 UDP packet size in DNSSEC queries.
=item * C<@IPV4_SPECIAL_ADDRESSES>
=item * C<@IPV6_SPECIAL_ADDRESSES>
=back
=cut
Readonly our $ALGO_STATUS_DEPRECATED => 1;
Readonly our $ALGO_STATUS_PRIVATE => 4;
Readonly our $ALGO_STATUS_RESERVED => 2;
Readonly our $ALGO_STATUS_UNASSIGNED => 3;
Readonly our $ALGO_STATUS_OTHER => 5;
Readonly our $ALGO_STATUS_NOT_ZONE_SIGN => 8;
Readonly our $ALGO_STATUS_NOT_RECOMMENDED => 9;
Readonly our $BLACKLISTING_ENABLED => 1;
Readonly our $CNAME_MAX_CHAIN_LENGTH => 10;
Readonly our $CNAME_MAX_RECORDS => 9;
Readonly our $DURATION_5_MINUTES_IN_SECONDS => 5 * 60;
Readonly our $DURATION_1_HOUR_IN_SECONDS => 60 * 60;
Readonly our $DURATION_4_HOURS_IN_SECONDS => 4 * 60 * 60;
Readonly our $DURATION_12_HOURS_IN_SECONDS => 12 * 60 * 60;
Readonly our $DURATION_1_DAY_IN_SECONDS => 24 * 60 * 60;
Readonly our $DURATION_1_WEEK_IN_SECONDS => 7 * 24 * 60 * 60;
Readonly our $DURATION_180_DAYS_IN_SECONDS => 180 * 24 * 60 * 60;
# Maximum length of ASCII version of a domain name, with trailing dot.
Readonly our $FQDN_MAX_LENGTH => 254;
Readonly our $LABEL_MAX_LENGTH => 63;
Readonly our $IP_VERSION_4 => 4;
Readonly our $IP_VERSION_6 => 6;
Readonly our $MINIMUM_NUMBER_OF_NAMESERVERS => 2;
Readonly our $SERIAL_BITS => 32;
Readonly our $SERIAL_MAX_VARIATION => 0;
Readonly our $UDP_PAYLOAD_LIMIT => 512;
Readonly our $EDNS_UDP_PAYLOAD_DEFAULT => 512;
Readonly our $EDNS_UDP_PAYLOAD_COMMON_LIMIT => 4096;
Readonly our $EDNS_UDP_PAYLOAD_DNSSEC_DEFAULT => 1232;
Readonly::Array our @IPV4_SPECIAL_ADDRESSES => _extract_iana_ip_blocks($IP_VERSION_4);
Readonly::Array our @IPV6_SPECIAL_ADDRESSES => _extract_iana_ip_blocks($IP_VERSION_6);
=head1 METHODS
=over
=item _extract_iana_ip_blocks()
my @array = _extract_iana_ip_blocks( $ip_version );
Internal method that is used to extract IP blocks details from IANA files for a given IP version (i.e. 4 or 6).
Takes an integer (IP version).
Returns a list of hashes - the keys of which are C<ip> (L<Net::IP::XS> object), C<name> (string), C<reference> (string)
and C<globally_reachable> (string).
=back
=cut
sub _extract_iana_ip_blocks {
my $ip_version = shift;
my @list = ();
my $csv = Text::CSV->new ({
binary => 1,
auto_diag => 1,
sep_char => q{,}
});
my @files_details = (
{ name => q{iana-ipv4-special-registry.csv}, ip_version => $IP_VERSION_4 },
{ name => q{iana-ipv6-special-registry.csv}, ip_version => $IP_VERSION_6 },
);
foreach my $file_details ( @files_details ) {
my $first_line = 1;
next if ${$file_details}{ip_version} != $ip_version;
my $makefile_name = 'Zonemaster-Engine'; # This must be the same name as "name" in Makefile.PL
my $data_location = dist_file($makefile_name, ${$file_details}{name});
open(my $data, '<:encoding(utf8)', $data_location) or croak "Cannot open '${data_location}' : ${OS_ERROR}";
while (my $fields = $csv->getline( $data )) {
if ( $first_line ) {
$first_line = 0;
next;
}
my $address_data = $fields->[0];
$address_data =~ s/[ ]+//smx;
foreach my $address_item ( split /,/smx, $address_data ) {
$address_item =~ s/(\A.+\/\d+).*\z/$1/smx;
push @list, { ip => Net::IP::XS->new( $address_item ), name => $fields->[1], reference => $fields->[2], globally_reachable => $fields->[8] };
}
}
close $data or croak "Cannot close '${data_location}' : ${OS_ERROR}";
}
return @list;
} ## end sub _extract_iana_ip_blocks
1;