Mini Shell
#!/usr/local/cpanel/3rdparty/bin/perl
# Copyright 2024 WebPros International, LLC
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited.
package scripts::migrate_pdns_conf;
use strict;
use warnings;
use Try::Tiny;
use Pod::Usage ();
use Getopt::Long ();
use Cpanel::LoadFile ();
use Cpanel::Exception ();
use Cpanel::FileUtils::Write ();
use Cpanel::Rand::Get ();
use Cpanel::PwCache ();
my $CONF_FILE = '/etc/pdns/pdns.conf';
exit( __PACKAGE__->script( \@ARGV ) ) unless caller;
sub script {
my ( $class, $argv ) = @_;
die Cpanel::Exception::create('RootRequired')->to_string_no_id() unless ( $> == 0 && $< == 0 );
my $self = bless { 'notify' => 1 }, $class;
my $help;
Getopt::Long::GetOptionsFromArray(
$argv,
'notify!' => \$self->{'notify'},
'dry-run' => \$self->{'dry-run'},
'man|help|h' => \$help,
) or return Pod::Usage::pod2usage( -exitval => 'NOEXIT', -output => \*STDERR, -verbose => 99, -sections => [qw(NAME DESCRIPTION SYNOPSIS)] );
# -1 to get the right exit code
return Pod::Usage::pod2usage( -exitval => 'NOEXIT', -output => \*STDOUT, -verbose => 99, -sections => [qw(NAME DESCRIPTION SYNOPSIS)] ) - 1 if $help;
return _nofile() unless -e $CONF_FILE;
my $current_conf = Cpanel::LoadFile::loadfileasarrayref($CONF_FILE);
my $changes = $self->migrate_conf($current_conf);
$changes->{enabled} = $self->enable_settings($current_conf);
return _nochanges() if !_has_changes($changes);
_print_changes($changes);
return 0 if $self->{'dry-run'};
if ( _overwrite_and_fix_ownership( $CONF_FILE, join( '', @{$current_conf} ) ) ) {
print "[+] Updated $CONF_FILE successfully\n";
$self->send_notification($changes)
if $self->{'notify'};
}
else {
print "[!] Failed to update $CONF_FILE: $!\n";
return 1;
}
return 0;
}
sub migrate_conf {
my ( $self, $current_conf ) = @_;
my $changes = {
'removed' => [],
'renamed' => [],
'manual' => [],
};
my @to_remove = qw/
pipebackend-abi-version
strict-rfc-axfrs
send-root-referral
experimental-lua-policy-script
allow-recursion
recursive-cache-ttl
recursor
api-readonly
experimental-api-readonly
api-logfile
default-soa-mail
default-soa-name
soa-expire-default
soa-minimum-ttl
soa-refresh-default
soa-retry-default
/;
my %remove_if_value_starts_with = (
'local-ipv6' => '::', #No longer force pdns to bind to ::
);
my %renamed = (
'allow-dns-update-from' => 'allow-dnsupdate-from',
'allow-unsigned-supermaster' => 'allow-unsigned-autoprimary',
'experimental-json-interface' => 'api',
'forward-dnsupdates' => 'forward-dnsupdate',
'local-ipv6-nonexist-fail' => 'local-address-nonexist-fail',
'master' => 'primary',
'slave' => 'secondary',
'slave-cycle-interval' => 'xfr-cycle-interval',
'slave-renotify' => 'secondary-do-renotify',
'superslave' => 'autosecondary',
( map { $_ => substr( $_, 0, -1 ) } qw{default-ksk-algorithms default-zsk-algorithms} ),
( map { $_ => substr( $_, 13 ) } qw{experimental-api-key experimental-dnsupdate experimental-dname-processing} ),
);
foreach my $line ( @{$current_conf} ) {
next if $line =~ m/^\s*(#|$)/;
# Parse it first to make life easier and run less regexes overall
my ( $key, $value ) = $line =~ /^\s*([^=]+?)\s*=\s*(.*?)\s*$/;
if ( my @remove = grep { $_ eq $key } @to_remove ) {
push @{ $changes->{'removed'} }, @remove;
$line = '#' . $line;
}
elsif ( exists( $remove_if_value_starts_with{$key} ) && index( $value, $remove_if_value_starts_with{$key} ) == 0 ) {
push @{ $changes->{'removed'} }, "$key=$remove_if_value_starts_with{$key}";
$line = '#' . $line;
}
elsif ( exists( $renamed{$key} ) ) {
# Possibly could have made this conditional on array of things?
# Anyways force list context as otherwise perl whines about it.
if ( ( grep { $_ eq $key } qw{default-ksk-algorithms default-zsk-algorithms} ) && split( /,/, $value ) > 1 ) {
$line = '#' . $line;
push @{ $changes->{'manual'} }, $key;
next;
}
push @{ $changes->{'renamed'} }, { $key => $renamed{$key} };
$line = "$renamed{$key}=$value\n";
}
}
return $changes;
}
sub enable_settings {
my ( $self, $current_conf ) = @_;
my @changes;
my %to_enable = (
'webserver' => 'yes',
'api' => 'yes',
'webserver-address' => '127.0.0.1',
'webserver-allow-from' => '127.0.0.1,::1',
'webserver-port' => '953',
'api-key' => undef,
'webserver-password' => undef,
'bind-ignore-broken-records' => 'yes',
'upgrade-unknown-types' => '1',
);
my %seen_settings;
foreach my $line ( @{$current_conf} ) {
next if $line =~ m/^\s*(#|$)/;
if ( $line =~ m{^\s*bind-dnssec-db\s*=\s*/etc/pdns/dnssec\.db\s*} ) {
$line = "bind-dnssec-db=/var/cpanel/pdns/dnssec.db\n";
push( @changes, { 'bind-dnssec-db' => '/var/cpanel/pdns/dnssec.db' } );
next;
}
my ( $key, $value ) = ( $line =~ /^\s*([^\s=]+)\s*=\s*(\S+)?\s*$/ );
next unless defined $key;
next unless exists $to_enable{$key};
# Remove duplicate, empty and templated values
if ( $seen_settings{$key} || !defined($value) || ( $value eq '@@REPLACE@@' || $value eq '@@REPLACE_PASS@@' ) ) {
$line = '';
next;
}
$seen_settings{$key} = 1;
# leave generated credentials as-is
next if ( !defined( $to_enable{$key} ) );
# leave correct settings alone
next if ( $value eq $to_enable{$key} );
# fix setting
$line = "$key=$to_enable{$key}\n";
push( @changes, { $key => $to_enable{$key} } );
}
# add missing settings
foreach my $key ( keys %to_enable ) {
next if $seen_settings{$key};
push( @changes, { $key => ( $to_enable{$key} // '***HIDDEN***' ) } );
$to_enable{$key} //= Cpanel::Rand::Get::getranddata(16);
unshift( @{$current_conf}, "$key=$to_enable{$key}\n" );
}
return \@changes;
}
sub _has_changes {
my $changes = shift;
foreach my $type (qw/removed renamed manual enabled/) {
return 1 if scalar @{ $changes->{$type} };
}
return 0;
}
sub _print_changes {
my $changes = shift;
foreach my $removed ( @{ $changes->{'removed'} } ) {
print "[*] Deprecated directive: '$removed' will be disabled.\n";
}
foreach my $renamed ( @{ $changes->{'renamed'} } ) {
print "[*] Renamed directive: '$_' will be updated to '$renamed->{$_}'.\n" foreach keys %{$renamed};
}
foreach my $manual ( @{ $changes->{'manual'} } ) {
print "[*] Deprecated configuration: '$manual' will be disabled as this requires admin intervention.\n";
}
foreach my $enabled ( @{ $changes->{'enabled'} } ) {
print "[*] New enabled settings: The '$_' directive was added and set to '$enabled->{$_}'.\n" foreach keys %{$enabled};
}
return 0;
}
sub _nochanges {
print "[+] $CONF_FILE does not contain any directives that need to be updated.\n";
return 0;
}
sub _nofile {
print "[*] $CONF_FILE is not present on the system. Nothing to do.\n";
return 0;
}
sub send_notification {
my ( $self, $changes ) = @_;
return if $ENV{'CPANEL_BASE_INSTALL'}; # Nothing is setup. it's meaningless to notify here.
my $old = $self->_locale()->set_context_plain();
require Cpanel::Notify;
my $ic_obj = Cpanel::Notify::notification_class(
'class' => 'Check::PdnsConf',
'application' => 'Check::PdnsConf',
'status' => 1,
'constructor_args' => [
'origin' => 'migrate-pdns-conf',
'skip_send' => 1,
%{$changes},
]
);
$ic_obj->send();
$self->_locale()->set_context($old);
return 1;
}
sub _locale {
my ($self) = @_;
require Cpanel::Locale;
return ( $self->{'_locale'} ||= Cpanel::Locale->get_handle() );
}
sub _overwrite_and_fix_ownership {
my ( $conf, $content ) = @_;
my ( $named_uid, $named_gid ) = ( Cpanel::PwCache::getpwnam_noshadow('named') )[ 2, 3 ];
my $overwrite_callback = sub {
my $fh = shift;
chmod( 0600, $fh );
chown( $named_uid, $named_gid, $fh );
};
return Cpanel::FileUtils::Write::overwrite( $conf, $content, { before_installation => $overwrite_callback } );
}
1;
__END__
=pod
=encoding utf8
=head1 NAME
migrate-pdns-conf
=head1 DESCRIPTION
Utility to update PowerDNS configuration from v3.x to v4.1:
* Deprecated options will be removed.
* Renamed configuration directives will be updated to the new names.
* New settings will be enabled.
=head1 SYNOPSIS
migrate-pdns-conf [OPTIONS]
OPTIONS:
--notify Send notification about changes made to System Administrator.
Default: on
To disable notifications, use --no-notify
--dry-run Do a dry-run without altering the file, or sending the notification.
Prints the changes that would be made to screen.
--help This documentation.
=cut
Zerion Mini Shell 1.0