#!/usr/local/cpanel/3rdparty/bin/perl # cpanel - scripts/importmydnsdb Copyright 2022 cPanel, L.L.C. # All rights reserved. # copyright@cpanel.net http://cpanel.net # This code is subject to the cPanel license. Unauthorized copying is prohibited use strict; use Cpanel::DNSLib::MydnsDb (); use Cpanel::Validate::Domain::Tiny (); use Cpanel::MysqlUtils::Connect (); use Cpanel::NameServer::Conf::Mydns (); use Cpanel::NameServer::Utils::MyDNS (); use Cpanel::PID (); use Cpanel::SafeRun::Errors (); use Cpanel::Usage (); use Cpanel::NameServer::Utils::MyDNS::DB (); use Try::Tiny; my ( $force, $debug ); Cpanel::Usage::wrap_options( \@ARGV, \&usage, { 'force' => \$force, 'debug' => \$debug } ); if ( $> != 0 ) { print STDERR "$0: must run as root\n"; exit 1; } my @messages = (); my $pid_obj = Cpanel::PID->new( { 'pid_file' => '/var/run/importmydnsdb.pid' } ); unless ( $pid_obj->create_pid_file() > 0 ) { push @messages, "Importmydnsdb appears to be running already."; push @messages, "Please wait for the import to finish before attempting another."; send_notification( { action => "already_running", result => "failure" } ); exit 1; } # perform a sanitfy check and try to repair if possible my ( $mydns_is_sanitize, $message ) = Cpanel::NameServer::Utils::MyDNS::DB::sanity_check(); unless ($mydns_is_sanitize) { push @messages, "Errors have been detected with the MyDNS configuration, trying to repair"; Cpanel::SafeRun::Errors::saferunallerrors('/usr/local/cpanel/bin/build_mydns_conf'); ( $mydns_is_sanitize, $message ) = Cpanel::NameServer::Utils::MyDNS::DB::sanity_check(); unless ($mydns_is_sanitize) { push @messages, "Cannot repair your MyDNS configuration, please reinstall it"; send_notification( { action => "cannot_repair", result => "failure" } ); exit 1; } push @messages, "MyDNS configuration repaired successfully"; } my $namedconf_obj = Cpanel::NameServer::Conf::Mydns->new(); $namedconf_obj->initialize(); $namedconf_obj->check_zonedir_cache(); my $bind_dir = $namedconf_obj->{'config'}->{'zonedir'}; my $dbh; try { $dbh = Cpanel::MysqlUtils::Connect::get_dbi_handle( 'extra_args' => { 'PrintError' => 0 } ); } catch { send_notification( { action => "cannot_connect_to_database", result => "failure", additional_parameters => { error_details => $_, } } ); exit 1; }; my $settings_ref = $namedconf_obj->{'dns_settings'}; my $soa_table = $settings_ref->{'database'} . '.' . $settings_ref->{'soa-table'}; my $dns_db = $namedconf_obj->{'dns_db'}; my $get_zones_query = qq { SELECT origin FROM $soa_table }; my $sth = $dbh->prepare($get_zones_query); unless ($sth) { send_notification( { action => "cannot_get_zones", result => "failure", additional_parameters => { error_details => $dbh->errstr } } ); exit 1; } unless ( eval { $sth->execute() } ) { send_notification( { action => "cannot_get_zones", result => "failure", additional_parameters => { error_details => $dbh->errstr } } ); exit 1; } my %existing_zones = (); while ( my $data = $sth->fetchrow_hashref() ) { $data->{'origin'} =~ s/\.$//; $existing_zones{ $data->{'origin'} } = 1; } $sth->finish; my $zone_dh; unless ( opendir $zone_dh, $bind_dir ) { send_notification( { action => "cannot_read_zone_file", result => "failure", additional_parameters => { zone_file_dir => $bind_dir, error_details => scalar $! } } ); exit 1; } my @zonedir_contents = readdir($zone_dh); closedir $zone_dh; my $sth_put_soa = $dbh->prepare( $dns_db->{put_soa_query} ); unless ($sth_put_soa) { send_notification( { action => "database_error", result => "failure", additional_parameters => { error_details => $dbh->errstr, } } ); exit 1; } my $sth_get_soa = $dbh->prepare( $dns_db->{get_soa_id} ); unless ($sth_get_soa) { send_notification( { action => "database_error", result => "failure", additional_parameters => { error_details => $dbh->errstr, } } ); exit 1; } my $sth_put_rr = $dbh->prepare( $dns_db->{put_rr_query} ); unless ($sth_put_rr) { send_notification( { action => "database_error", result => "failure", additional_parameters => { error_details => $dbh->errstr, } } ); exit 1; } if ($force) { unless ( $dns_db->purge_rss() ) { send_notification( { action => "cannot_purge_rss", result => "failure" } ); exit 1; } } my %needed_zone_files = (); push @messages, "Checking zones in MyDNS database"; foreach my $zonefile (@zonedir_contents) { if ( $zonefile =~ /^([\w\-.]+)\.db$/ && Cpanel::Validate::Domain::Tiny::validdomainname($1) ) { my $zone = $1; if ( $force || !$existing_zones{$zone} ) { $needed_zone_files{$zonefile} = $zone; next; } if ($debug) { push @messages, "$zone already exists in MyDNS, not imported"; } } } push @messages, "Done"; push @messages, "Importing zones into MyDNS database"; my $counter = 0; foreach my $zonefile ( keys %needed_zone_files ) { my ( $zone_fh, $zonedata ); unless ( open $zone_fh, '<', $bind_dir . '/' . $zonefile ) { push @messages, "Unable to read $bind_dir" . '/' . $zonefile; next; } { local $/; $zonedata = readline($zone_fh); } close $zone_fh; my ( $soa_hr, $rr_ar ) = Cpanel::NameServer::Utils::MyDNS::parsebind( $zonedata, $needed_zone_files{$zonefile} ); unless ( scalar keys(%$soa_hr) ) { push @messages, "Unable to parse and save $needed_zone_files{$zonefile}\n"; next; } my $ns = ( $soa_hr->{ns} =~ m/\.$/ ) ? $soa_hr->{ns} : $soa_hr->{ns} . '.'; my $origin = ( $soa_hr->{origin} =~ m/\.$/ ) ? $soa_hr->{origin} : $soa_hr->{origin} . '.'; my $serial = ( defined $soa_hr->{serial} ) ? $soa_hr->{serial} : Cpanel::DNSLib::MydnsDb::get_new_serial(); eval { unless ( eval { $sth_put_soa->execute( $origin, $ns, $soa_hr->{mbox}, $serial, $soa_hr->{refresh}, $soa_hr->{retry}, $soa_hr->{expire}, $soa_hr->{minimum}, $soa_hr->{ttl}, 'Y' ) } ) { push @messages, "Database error, unable to save soa records for $needed_zone_files{$zonefile}"; next; } $sth_put_soa->finish(); }; if ($@) { push @messages, "Database error, saving soa record failed: $@"; next; } my $soa_id; eval { unless ( eval { $sth_get_soa->execute($origin) } ) { push @messages, "Database error, unable to get soa id for $needed_zone_files{$zonefile}"; next; } my $result = $sth_get_soa->fetchrow_hashref(); $soa_id = $result->{'id'}; $sth_get_soa->finish(); }; if ($@) { push @messages, "Database error, fetching soa record failed: $@"; next; } eval { foreach my $rr_data ( @{$rr_ar} ) { my $aux = ( exists $rr_data->{aux} && $rr_data->{aux} =~ m/^\d+$/ ) ? $rr_data->{aux} : 0; unless ( eval { $sth_put_rr->execute( $soa_id, $rr_data->{name}, $rr_data->{type}, $aux, $rr_data->{data}, $rr_data->{ttl}, 'Y' ) } ) { push @messages, "Database error, unable to save $rr_data->{name} for $needed_zone_files{$zonefile}"; } $sth_put_rr->finish(); } }; if ($@) { push @messages, "Database error, inserting rr record failed: $@"; } $counter++; if ( $counter % 100000 == 0 ) { send_notification( { action => "zones_imported_successfully", result => "in_progress", additional_parameters => { imported_count => $counter } } ); } if ($debug) { push @messages, "$needed_zone_files{$zonefile} is successfully saved"; } } push @messages, "$counter zones have been successfully imported"; unless ($force) { } my $mydnschkbin = 0; if ( -x $namedconf_obj->{'mydnschkbin'} ) { push @messages, Cpanel::SafeRun::Errors::saferunallerrors( $namedconf_obj->{'mydnschkbin'}, '--consistency-only' ); push @messages, 'Zone consistency check is done'; $mydnschkbin = 1; } else { push @messages, 'There is no mydnscheck in your system, zone consistency check is skipped'; } send_notification( { action => 'successful_completion', result => 'success', additional_parameters => { mydnschkbin => $mydnschkbin, force => $force, imported_count => $counter, log_ => \@messages, } } ); $pid_obj->remove_pid_file(); exit 0; sub usage { print <{'result'} eq 'success' ) { my $log_file = join( "\n", @{ $obj->{'additional_parameters'}->{'log_'} } ); require Cpanel::Notify; return Cpanel::Notify::notification_class( 'class' => 'ImportMyDNSdb::Success', 'application' => 'ImportMyDNSdb::Success', 'constructor_args' => [ action => $obj->{'action'}, mydnschkbin => $mydnschkbin, attach_files => [ { name => 'import.log.txt', content => \$log_file } ], %{ $obj->{'additional_parameters'} }, ] ); } elsif ( $obj->{'result'} eq 'in_progress' ) { require Cpanel::Notify; return Cpanel::Notify::notification_class( 'class' => 'ImportMyDNSdb::InProgress', 'application' => 'ImportMyDNSdb::InProgress', 'constructor_args' => [ action => $obj->{'action'}, %{ $obj->{'additional_parameters'} }, ] ); } else { require Cpanel::Notify; return Cpanel::Notify::notification_class( 'class' => 'ImportMyDNSdb::Failure', 'application' => 'ImportMyDNSdb::Failure', 'constructor_args' => [ action => $obj->{'action'}, %{ $obj->{'additional_parameters'} }, ] ); } }