Server : Apache System : Linux server1.cgrithy.com 3.10.0-1160.95.1.el7.x86_64 #1 SMP Mon Jul 24 13:59:37 UTC 2023 x86_64 User : nobody ( 99) PHP Version : 8.1.23 Disable Function : NONE Directory : /scripts/ |
#!/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