Viewing File: /usr/local/cpanel/whostmgr/bin/dnsqueue

#!/usr/local/cpanel/3rdparty/bin/perl

# cpanel - whostmgr/bin/dnsqueue                   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::SafeFile           ();
use Cpanel::SafeRun::InOut     ();
use Cpanel::Config::LoadCpConf ();
use Cpanel::Logger             ();
use Cpanel::PID                ();
use Cpanel::StringFunc::Match  ();
use Cpanel::Hostname           ();
use Cpanel::Encoder::URI       ();
use Cpanel::DNSLib::PeerConfig ();

if ( $ARGV[0] eq "--bincheck" ) {
    print "BinCheck ok\n";
    exit 0;
}

my $logger = Cpanel::Logger->new();

our $MAX_LOG_SEND_SIZE = 1024 * 128;    # Maximum amount of log to send see case 194133

$| = 1;

# Uncomment: For testing notifications (until we convert to a modulino)

#{
#    sub test_notify {
#        my $failed_hosts = { "pig.org-broken-" . rand() => 'dead', "frog.org-broken-" . rand() => 'dsd' };
#        local *_get_down_hosts_from_host_failures = sub { return keys %{$failed_hosts} };
#        _send_notification_of_down_peers($failed_hosts);
#        $failed_hosts = { "one.org-broken-" . rand() => 'dead' };
#        _send_notification_of_down_peers($failed_hosts);
#        exit();
#
#    }
#    test_notify();
#}

# If clustering is disabled there is no point in attempting to run the queue
# The only commands that get queued are remote ones at present
exit 1 unless -e '/var/cpanel/useclusteringdns';

# Check that we don't have multiple instances of the queue runner executing
my $pid_obj = Cpanel::PID->new( { 'pid_file' => '/var/run/cpanel-dnsqueue.pid' } );
exit 0 unless ( $pid_obj->create_pid_file() > 0 );

my $now    = time();
my $cpconf = Cpanel::Config::LoadCpConf::loadcpconf();

my %host_failures = ();

opendir( RETRIES, "/var/cpanel/clusterqueue/retry" );
my @RETRIES = grep { !/^\./ } readdir(RETRIES);
closedir(RETRIES);

foreach my $retry ( sort @RETRIES ) {
    my $queuetime = $now;
    my $dnsuniqid = $retry;
    if ( $retry =~ /^(\d+)-(\w+)$/ && length($2) >= 32 ) {
        $queuetime = $1;
        $dnsuniqid = $2;
    }

    next if ( $retry =~ /\.lock$/ );

    my @HOSTS;
    my ($request);

    if ( isdaysold( $queuetime, 14 ) ) {
        unlink( "/var/cpanel/clusterqueue/retry/${retry}", "/var/cpanel/clusterqueue/requests/${retry}" );
        next();
    }

    my $qlock = Cpanel::SafeFile::safeopen( \*CREQ, "/var/cpanel/clusterqueue/retry/${retry}" );
    if ( !$qlock ) {
        $logger->warn("Could not read from /var/cpanel/clusterqueue/retry/${retry}");
        next;
    }
    while (<CREQ>) {
        chomp();
        push( @HOSTS, $_ );
    }

    # Check to see if any hosts are valid:
    # Always valid when not disabled.
    # If it's disabled we'll force 5 attempts before giving up.
    my $valid_request = 0;
    my $forced_hosts  = '';
    my %check_hosts   = ();
    foreach my $host (@HOSTS) {
        if ( -e '/var/cpanel/clusterqueue/status/' . $host . '-down' ) {
            if ( !defined $host_failures{$host} || $host_failures{$host} < 5 ) {
                $forced_hosts .= $host . ',';
                $check_hosts{$host} = 1;
                $valid_request = 1;
            }
        }
        else {
            $valid_request = 1;
        }
    }
    unless ($valid_request) {
        Cpanel::SafeFile::safeclose( \*CREQ, $qlock );
        next;
    }

    open( my $req_fh, '<', "/var/cpanel/clusterqueue/requests/${retry}" );
    {
        local $/;
        $request .= readline($req_fh);
    }
    close($req_fh);
    chomp($request);

    my $hosts = join( ",", @HOSTS );

    my ($action);
    ( $action, $request ) = split( /\n/, $request );
    $request =~ s/\&(?:hosts|dnsqueuetime|dnsuniqid)=[^&]+//g;
    $request =~ s/^(?:hosts|dnsqueuetime|dnsuniqid)=[^&]+(\&|$)//g;
    $request .= ( $request ? '&' : '' ) . "hosts=${hosts}&dnsqueuetime=${queuetime}&dnsuniqid=${dnsuniqid}";

    if ($forced_hosts) {
        $forced_hosts =~ s/,$//;
        $request .= '&ignore_downed_hosts=' . $forced_hosts;
    }

    Cpanel::SafeFile::safeclose( \*CREQ, $qlock );

    unlink("/var/cpanel/clusterqueue/retry/${retry}");
    unlink("/var/cpanel/clusterqueue/requests/${retry}");
    my @CALL;
    if ( exists $cpconf->{'dnsadminapp'} && $cpconf->{'dnsadminapp'} ne '' && substr( $cpconf->{'dnsadminapp'}, 0, 1 ) eq '/' && -x $cpconf->{'dnsadminapp'} ) {
        push @CALL, $cpconf->{'dnsadminapp'};
    }
    else {
        push @CALL, '/usr/local/cpanel/whostmgr/bin/dnsadmin';
        if ( defined $CALL[$#CALL] && -e $CALL[$#CALL] . '-ssl' ) {
            $CALL[$#CALL] .= '-ssl';
        }
    }

    if ( Cpanel::SafeRun::InOut::inout( \*DNSWRITE, \*DNSREAD, @CALL ) ) {
        print DNSWRITE "$action\n$request";
        close(DNSWRITE);
        {
            local $/;
            print readline(DNSREAD);
        }
        close(DNSREAD);
    }

    # Evaluate success of forced retries
    foreach my $host ( keys %check_hosts ) {
        my $hlog_lock = Cpanel::SafeFile::safeopen( \*HLOG, '<', '/var/cpanel/clusterqueue/status/' . $host );
        if ( !$hlog_lock ) {
            $logger->warn("Could not read from /var/cpanel/clusterqueue/status/$host");
            next;
        }
        my $status = <HLOG> || '';
        Cpanel::SafeFile::safeclose( \*HLOG, $hlog_lock );
        chomp $status;
        if ( Cpanel::StringFunc::Match::endmatch( $status, '1' ) ) {
            unlink '/var/cpanel/clusterqueue/status/' . $host . '-down';
        }
        else {
            $host_failures{$host} ||= 0;
            $host_failures{$host} += 1;
        }
    }

}

# Send notification about cluster members that are still unreachable
if ( !exists $cpconf->{'cluster_failure_notifications'} || $cpconf->{'cluster_failure_notifications'} eq '1' ) {
    _send_notification_of_down_peers( \%host_failures );
}

$pid_obj->remove_pid_file();
exit 0;

sub _send_notification_of_down_peers {
    my ($host_failures_ref) = @_;

    if ( my @down_hosts = _get_down_hosts_from_host_failures($host_failures_ref) ) {
        my $hostname = Cpanel::Hostname::gethostname();
        require Cpanel::Redirect;
        my $url_host        = Cpanel::Redirect::getserviceSSLdomain('cpanel') || $hostname;
        my $host_events_ref = _get_failure_events_for_hosts( \@down_hosts );

        require Cpanel::IP::Remote;
        require Cpanel::Notify;
        Cpanel::Notify::notification_class(
            'class'            => 'DnsAdmin::UnreachablePeer',
            'application'      => 'dnsadmin-' . join( '-', sort @down_hosts ),
            'status'           => 'failed',
            'interval'         => 3600,
            'priority'         => 1,
            'constructor_args' => [
                'down_hosts'        => \@down_hosts,
                'host_events'       => $host_events_ref,
                'url_host'          => $url_host,
                'origin'            => 'dnsadmin',
                'source_ip_address' => Cpanel::IP::Remote::get_current_remote_ip(),
                'attach_files'      => [ map { { 'name' => "$_-failures-log.txt", 'content' => \join( "\n", @{ $host_events_ref->{$_} } ) } } keys %{$host_events_ref} ],
            ],
        );
    }
}

sub _get_down_hosts_from_host_failures {
    my ($host_failures_ref) = @_;

    my @down_hosts;
    if ( scalar keys %{$host_failures_ref} ) {

        # Dont send warnings about cluster members that are disabled.  The queue for these systems will expire
        # over time or be cleared when they are reenabled
        my %peerlist = map { $_ => 1 } Cpanel::DNSLib::PeerConfig::getdnspeers();

        foreach my $host ( sort keys %{$host_failures_ref} ) {
            if ( $peerlist{$host} && -e '/var/cpanel/clusterqueue/status/' . $host . '-down' ) {
                push @down_hosts, $host;
            }
        }
    }
    return @down_hosts;
}

sub _get_failure_events_for_hosts {
    my ($down_hosts_ref) = @_;

    my %host_events;
    foreach my $host ( @{$down_hosts_ref} ) {
        $host_events{$host} = [];
        my $failure_log_file = '/var/cpanel/clusterqueue/status/' . $host . '-failure_log';
        if ( open( my $fail_log_fh, '<', $failure_log_file ) ) {
            my $log_size = ( stat($fail_log_fh) )[7];
            if ( $log_size > $MAX_LOG_SEND_SIZE ) {    # truncate the message to avoid huge log files - see case 194133
                my $log_start_position = $log_size - $MAX_LOG_SEND_SIZE;
                seek( $fail_log_fh, $log_start_position, 0 );
                readline($fail_log_fh);                # discard partial line
                push @{ $host_events{$host} }, '--- TRUNCATED ---';
            }
            while ( readline($fail_log_fh) ) {
                chomp();
                my $event = Cpanel::Encoder::URI::uri_decode_str($_);
                push @{ $host_events{$host} }, $event;
            }
            close($fail_log_fh);
        }
    }
    return \%host_events;
}

sub isdaysold {
    my ( $mtime, $days ) = @_;

    if ( ( $mtime + ( $days * 86400 ) ) < $now ) {
        return (1);
    }

    return (0);
}
Back to Directory File Manager