#!/usr/local/cpanel/3rdparty/bin/perl
# cpanel - autofixer2/sshd_denygroups                Copyright 2017 cPanel, Inc.
#                                                           All rights Reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited

use strict;
use warnings;

use Cpanel::FindBin         ();
use Cpanel::SafeRun::Errors ();
use Cpanel::TempFile        ();

unless ( -e '/var/cpanel/version/cpanelsec-247-sshd' ) {
    print "SSHD DenyGroups fix was not applied on this system\n";
    exit;
}

# This copies the sshd finding logic in Cpanel/Services/Installed.pm
my $sshd_binary = Cpanel::FindBin::findbin( 'sshd', 'path' => [qw{ /sbin /usr/sbin /usr/local/sbin /bin /usr/bin /usr/local/bin /usr/local/cpanel/3rdparty/bin }] );

unless ( $sshd_binary && -x $sshd_binary ) {
    print "SSHD binary not found or not executable\n";
    exit;
}

my $conf_file = find_sshd_conf();

unless ($conf_file) {
    print "SSHD conf file not found\n";
    exit;
}

# Read sshd_config contents
my $sshd_config;
my $conf_fh;
if ( open $conf_fh, '+<', $conf_file ) {
    local $/ = undef;
    $sshd_config = readline($conf_fh);
}
else {
    print "Failed to open $conf_file for reading and writing: $!\n";
    exit;
}

# Stop unless we see the Match before DenyGroups combination
unless ( $sshd_config =~ /^\s*Match.*^\s*DenyGroups\s+((?:\S+[ \t]+)*cpaneldemo[ \t]+(?:\S+[ \t]+)*cpanelsuspended(?:[ \t]+\S+)*[ \t]*)\n/ims ) {
    print "SSHD config file does not need an update.\n";
    exit;
}

# If the DenyGroups setting contains more than cpaneldemo and cpanelsuspended, we need to remove those two from the list, otherwise, remove it.
my $denied_groups = $1;
$denied_groups =~ s/(^|\s+)cpaneldemo(\s+|$)//g;
$denied_groups =~ s/(^|\s+)cpanelsuspended(\s+|$)//g;
$denied_groups =~ s/^\s+|\s+$//;
if ($denied_groups) {
    $sshd_config =~ s/^(\s*DenyGroups\s+)((?:\S+[ \t]+)*cpaneldemo[ \t]+(?:\S+[ \t]+)*cpanelsuspended(?:[ \t]+\S+)*[ \t]*)\n/$1$denied_groups\n/ims;
}
else {
    $sshd_config =~ s/^(\s*DenyGroups\s+)((?:\S+[ \t]+)*cpaneldemo[ \t]+(?:\S+[ \t]+)*cpanelsuspended(?:[ \t]+\S+)*[ \t]*)\n//ims;
}

# Put the two cPanel groups before the Match directive.
$sshd_config =~ s/(^\s*Match.*)/DenyGroups\tcpaneldemo cpanelsuspended\n$1/ims;

# Write new conf to a temporary file
my $tmp_obj = Cpanel::TempFile->new();
my ( $test_conf, $test_fh ) = $tmp_obj->file();
unless ( $test_conf && $test_fh ) {
    print "Failed to open a temporary file to test sshd_config changes.\n";
    exit;
}

print {$test_fh} $sshd_config;
close $test_fh;

# Verify new conf has valid syntax
unless ( sshd_syntax_test( $sshd_binary, $test_conf ) ) {
    print "Rewritten sshd_config is still failing the syntax test. No changes will be made.\n";
    exit;
}

# Write new conf to real sshd_config location and restart
seek( $conf_fh, 0, 0 );
print {$conf_fh} $sshd_config;
truncate( $conf_fh, tell($conf_fh) );
close $conf_fh;

print "SSHD config file updated. Restarting sshd.\n";
system('/usr/local/cpanel/scripts/restartsrv_sshd');

sub sshd_syntax_test {
    my ( $sshd_binary, $conf_file ) = @_;

    Cpanel::SafeRun::Errors::saferunallerrors( $sshd_binary, '-t', '-f', $conf_file );

    my $syntax_valid = $? == 0;

    return $syntax_valid;
}

sub find_sshd_conf {

    # This mirrors the logic in Whostmgr::Services::SSH::Config::get_file() without the new directory creation.
    my $filename = 'sshd_config';
    my @locations = ( '/usr/local/etc/ssh', '/etc/ssh' );
    foreach my $dir (@locations) {
        return $dir . '/' . $filename if -e $dir . '/' . $filename;
    }
    return;
}
