#!/usr/local/cpanel/3rdparty/bin/perl
# cpanel - autofixer2/fix_kernel_cmdline_post_elevate           Copyright 2021 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

package autofixer2::fix_kernel_cmdline_post_elevate;

use cPstrict;

BEGIN { unshift @INC, '/usr/local/cpanel'; }

use File::Slurper           ();    ## no critic qw(PreferredModules) -- elevate-cpanel uses it, so it makes for an easier transplant
use Cpanel::LoadModule      ();
use Cpanel::SafeRun::Simple ();
use Cpanel::SafeRun::Object ();

use Try::Tiny;

use constant DEFAULT_GRUB_FILE => '/etc/default/grub';
use constant GRUB_EDITENV      => '/usr/bin/grub2-editenv';
use constant GRUB_ENV_FILE     => '/boot/grub2/grubenv';

my %contents;

exit run() unless caller;

sub run () {

    return 0 unless supported_on_this_major( 102, 110 );    # Set this to the Min/Max we support this autofixer on.

    if ( was_elevated_with_buggy_version() && needs_net_ifnames() ) {
        do_update_etc_default_grub() unless etc_default_grub_contains_net_ifnames();
        do_update_grubenv()          unless grubenv_contains_net_ifnames();
    }

    return 0;                                               # exit(0);
}

sub was_elevated_with_buggy_version () {

    # If the system wasn't upgraded, then it can't be buggy:
    return unless -e '/var/cpanel/version/elevate';

    # XXX Get the current version of elevate-cpanel as a proxy for what version
    # was used to perform the upgrade.
    #
    # In general, that isn't true, but it's fine right now, because SecAdv
    # won't cause an update to elevate-cpanel after the system upgrade. This
    # won't be true if/when 8 => 9 is supported, because a system might have
    # been elevated twice.  It's also not true if the administrator updated
    # elevate-cpanel manually at the command line.
    #
    # Furthermore, prior to 108, Cpanel::Elevate didn't even have the version()
    # method; but instead of reproducing that logic here, assume that if cPanel
    # is currently 106 or older, it needs some kind of fixup. The few who
    # upgrade using the old LTS but a new version of elevate-cpanel without the
    # bug can cope.

    return try {
        Cpanel::LoadModule::load_perl_module('Cpanel::Elevate');
        my $elevate_obj = Cpanel::Elevate->new;

        $elevate_obj->version <= 5
    }
    catch { get_major_version() < 108 };
}

# The assumption here is that the autofixer will only act if the server hasn't
# been rebooted into the new kernel, and thus /proc/cmdline will still have
# net.ifnames=0 in it. If the server has rebooted into the new kernel, and that
# isn't in the command line, then one of three scenarios must be true:
#
# 1. The server is not accessible.
# 2. The server is accessible, and the administrator manually fixed the issue some other way.
# 3. The server is accessible, and net.ifnames=0 wasn't really needed in the first place.
#
# In each case, applying the fixups now won't do much of anything. If fixes
# need to be applied, they can be done later.

sub needs_net_ifnames () {
    my $cmdline = eval { File::Slurper::read_binary('/proc/cmdline') } // '';

    return $cmdline =~ m/\bnet\.ifnames/a;
}

sub etc_default_grub_contains_net_ifnames () {
    $contents{DEFAULT_GRUB_FILE} = eval { File::Slurper::read_binary(DEFAULT_GRUB_FILE) } // '';

    return $contents{DEFAULT_GRUB_FILE} =~ m/\bGRUB_CMDLINE_LINUX="(?:.*?)\bnet\.ifnames(?:.*?)"/;
}

sub do_update_etc_default_grub () {
    $contents{DEFAULT_GRUB_FILE} =~ s/\bGRUB_CMDLINE_LINUX="(.+?)"/GRUB_CMDLINE_LINUX="$1 net.ifnames=0"/m;
    File::Slurper::write_binary( DEFAULT_GRUB_FILE, $contents{DEFAULT_GRUB_FILE} );
    return;
}

sub grubenv_contains_net_ifnames () {
    my $grubenv = Cpanel::SafeRun::Simple::saferunnoerror( GRUB_EDITENV, GRUB_ENV_FILE, 'list' );

    foreach my $line ( split "\n", $grubenv ) {
        my ( $name, undef ) = split "=", $line, 2;
        next unless $name eq "kernelopts";

        $contents{GRUB_ENV_FILE} = $line;
        return $line =~ m/\bnet\.ifnames=/a;
    }

    return;
}

sub do_update_grubenv () {
    my $line = $contents{GRUB_ENV_FILE};

    $line .= ( $line && $line !~ m/ $/ ? " " : "" ) . 'net.ifnames=0';
    Cpanel::SafeRun::Object->new_or_die(
        program => GRUB_EDITENV,
        args    => [
            GRUB_ENV_FILE,
            "set",
            $line,
        ],
    );

    return;
}

# Do we run this code?
sub supported_on_this_major {
    my ( $min_ver, $max_ver ) = @_;

    my $major = get_major_version();

    return 0 if $major < $min_ver;
    return 0 if $major > $max_ver;

    return 1;
}

sub get_major_version {
    my $major_version;

    if ( open( my $fh, '<', '/usr/local/cpanel/version' ) ) {
        my $full_version = <$fh>;
        chomp $full_version;
        close($fh);
        ($major_version) = $full_version =~ /^[0-9]+\.([0-9]+)/;
    }

    # Safe default.
    return $major_version || 30;
}

1;
