#!/usr/local/cpanel/3rdparty/bin/perl
package scripts::fix_reseller_acls;
# cpanel - scripts/fix_reseller_acls
# 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 Try::Tiny;
use Pod::Usage ();
use Getopt::Long ();
use Cpanel::Exception ();
use Cpanel::LoadModule ();
use Cpanel::ConfigFiles ();
use Whostmgr::ACLS::Data ();
exit run(@ARGV) unless caller();
sub run {
my @cmdline_args = @_;
die Cpanel::Exception::create('RootRequired')->to_string_no_id() unless ( $> == 0 && $< == 0 );
return Pod::Usage::pod2usage( -exitval => 'NOEXIT', -output => \*STDERR, -verbose => 99, -sections => [qw(NAME DESCRIPTION SYNOPSIS)] ) if !@cmdline_args;
my $opts = _parse_and_validate_opts( \@cmdline_args );
# -1 to get the right exit code
return Pod::Usage::pod2usage( -exitval => 'NOEXIT', -output => \*STDOUT, -verbose => 99, -sections => [qw(NAME DESCRIPTION SYNOPSIS)] ) - 1 if $opts->{'help'};
return Pod::Usage::pod2usage( -exitval => 'NOEXIT', -verbose => 2 ) - 1 if $opts->{'man'};
if ( my @enabled_operations = map { __PACKAGE__->can( $_ =~ s/\-/_/gr ) } sort grep { $opts->{'operations'}->{$_} } keys %{ $opts->{'operations'} } ) {
process_users( $opts->{'resellers'}, \@enabled_operations )
if $opts->{'resellers'} && scalar @{ $opts->{'resellers'} };
process_acl_lists( $opts->{'acl-lists'}, \@enabled_operations )
if $opts->{'acl-lists'} && scalar @{ $opts->{'acl-lists'} };
return 0;
}
die Cpanel::Exception->create_raw("[!] You must specify a operation to perform. See --help\n")->to_string_no_id();
}
sub process_users {
my $resellers_to_process_ar = shift;
my $callbacks_ar = shift;
Cpanel::LoadModule::load_perl_module('Cpanel::Reseller');
Cpanel::LoadModule::load_perl_module('Whostmgr::Resellers');
# TODO: The current interfaces to the RESELLERS_FILE do not provide
# any way to do a 'mass-edit'. Depending on how slow this process is,
# we might need to implement one.
my %current_reseller_acls = Cpanel::Reseller::getresellersaclhash();
foreach my $reseller ( @{$resellers_to_process_ar} ) {
# We validated resellers beforehand, but just in case something
# changed between that check, and the getresellersaclhash call, check again.
next unless exists $current_reseller_acls{$reseller};
_output("[*] Processing reseller: '$reseller'...\n");
foreach my $cr ( @{$callbacks_ar} ) {
$cr->(
{
name => $reseller,
current_acls => $current_reseller_acls{$reseller},
}
);
}
# set_reseller_acls requires the ACLs to have a 'acl-' prefix
Whostmgr::Resellers::set_reseller_acls( $reseller, { map { 'acl-' . $_ => 1 } keys %{ $current_reseller_acls{$reseller} } } );
_output("[+] Processed reseller: '$reseller'\n");
}
return 0;
}
sub process_acl_lists {
my $acl_lists_to_process_ar = shift;
my $callbacks_ar = shift;
Cpanel::LoadModule::load_perl_module('Whostmgr::ACLS');
foreach my $acl_list ( @{$acl_lists_to_process_ar} ) {
my $list_file = "$Cpanel::ConfigFiles::ACL_LISTS_DIR/$acl_list";
next unless -f $list_file;
_output("[*] Processing ACL list: '$acl_list'...\n");
if ( open( my $acl_fh, '<', $list_file ) ) {
my $acls = { map { split /=/, $_, 2 } grep { !/^\s*$/ } map { s/\n//r } readline($acl_fh) };
close($acl_fh);
foreach my $cr ( @{$callbacks_ar} ) {
$cr->(
{
name => $acl_list,
current_acls => $acls,
}
);
}
Whostmgr::ACLS::save_acl_list(
'acllist' => $acl_list,
( map { 'acl-' . $_ => 1 } grep { $acls->{$_} } keys %{$acls} )
);
_output("[+] Processed ACL list: '$acl_list'\n");
}
else {
_output("[!] Failed to process ACL list '$acl_list': $!\n");
}
}
return 0;
}
my $defaults_to_apply_hr;
sub add_default_privs {
my $to_process_hr = shift;
$defaults_to_apply_hr //= { map { $_ => 1 } @{ Whostmgr::ACLS::Data::get_default_acls() } };
_output("\t[*] Adding default privileges to '$to_process_hr->{'name'}'...\n");
%{ $to_process_hr->{'current_acls'} } = (
%{ $to_process_hr->{'current_acls'} },
%{$defaults_to_apply_hr}
);
_output("\t[+] Added default privileges to '$to_process_hr->{'name'}'.\n");
return 1;
}
sub fix_disallow_shell {
my $to_process_hr = shift;
_output("\t[*] Fixing 'disallow-shell' privilege for '$to_process_hr->{'name'}'...\n");
my $had_disallow_shell = delete $to_process_hr->{'current_acls'}->{'disallow-shell'};
if ( !$had_disallow_shell ) {
%{ $to_process_hr->{'current_acls'} } = (
%{ $to_process_hr->{'current_acls'} },
'allow-shell' => 1,
);
}
_output("\t[+] Fixed 'disallow-shell' privilege for '$to_process_hr->{'name'}'.\n");
return 1;
}
# for mocking
sub _output { return print @_; }
sub _parse_and_validate_opts {
my $cmdline_args_ar = shift;
my $opts = {
'help' => 0,
'man' => 0,
'operations' => {
'add-default-privs' => 0,
'fix-disallow-shell' => 0,
},
'all-resellers' => 0,
'specified_resellers' => {},
'all-acl-lists' => 0,
'specified_acl_lists' => {}
};
Getopt::Long::GetOptionsFromArray(
$cmdline_args_ar,
'help|h' => \$opts->{'help'},
'man' => \$opts->{'man'},
# What should be changed?
'reseller=s' => sub { $opts->{'specified_resellers'}->{ $_[1] }++; }, # Avoid dupes
'acl-list=s' => sub { $opts->{'specified_acl_lists'}->{ $_[1] }++; }, # Avoid dupes
'all-resellers' => \$opts->{'all-resellers'},
'all-acl-lists' => \$opts->{'all-acl-lists'},
# What changes should be made?
'add-default-privs' => \$opts->{'operations'}->{'add-default-privs'},
'fix-disallow-shell' => \$opts->{'operations'}->{'fix-disallow-shell'},
) or die Cpanel::Exception->create_raw("[!] Invalid usage. See --help\n")->to_string_no_id();
return $opts if $opts->{'help'} || $opts->{'man'};
$opts->{'resellers'} = _validate_resellers($opts);
$opts->{'acl-lists'} = _validate_acl_lists($opts);
die Cpanel::Exception->create_raw("[!] You must specify a mode of operation. See --help\n")->to_string_no_id()
unless $opts->{'resellers'} // $opts->{'acl-lists'};
return $opts;
}
sub _validate_resellers {
my $opts = shift;
if ( $opts->{'all-resellers'} ) {
Cpanel::LoadModule::load_perl_module("Whostmgr::Resellers::List");
Cpanel::LoadModule::load_perl_module('Cpanel::Config::HasCpUserFile');
return [
# Skip 'resellers without a domain' when processing all resellers on the system:
# https://documentation.cpanel.net/display/CKB/How+to+Create+a+WHM+Reseller+Without+An+Associated+Domain
#
# These resellers are created "out of band" by editing the resellers file,
# so altering them should be left up to the server administrators.
grep { Cpanel::Config::HasCpUserFile::has_cpuser_file($_) }
keys %{ Whostmgr::Resellers::List::list() }
];
}
elsif ( my @specified_resellers = keys %{ $opts->{'specified_resellers'} } ) {
Cpanel::LoadModule::load_perl_module("Whostmgr::Resellers::Check");
if ( my @invalid_resellers = grep { !Whostmgr::Resellers::Check::is_reseller($_) } @specified_resellers ) {
die Cpanel::Exception->create_raw( "[!] Invalid resellers specified:\n" . join( "\n", map { " " x 8 . $_ } @invalid_resellers ) . "\n" )->to_string_no_id();
}
return \@specified_resellers;
}
return;
}
sub _validate_acl_lists {
my $opts = shift;
if ( $opts->{'all-acl-lists'} ) {
if ( opendir my $dh, $Cpanel::ConfigFiles::ACL_LISTS_DIR ) {
return [ grep { $_ !~ m/^\.+$/ && -f "$Cpanel::ConfigFiles::ACL_LISTS_DIR/$_" } readdir($dh) ];
}
}
elsif ( my @specified_acl_lists = keys %{ $opts->{'specified_acl_lists'} } ) {
if ( my @invalid_acl_lists = grep { !-f "$Cpanel::ConfigFiles::ACL_LISTS_DIR/$_" } @specified_acl_lists ) {
die Cpanel::Exception->create_raw( "[!] Invalid acl-lists specified:\n" . join( "\n", map { " " x 8 . $_ } @invalid_acl_lists ) . "\n" )->to_string_no_id();
}
return \@specified_acl_lists;
}
return;
}
1;
__END__
=encoding utf8
=head1 NAME
fix_reseller_acls
=head1 DESCRIPTION
Utility to update reseller privileges and ACL lists.
=head1 SYNOPSIS
fix_reseller_acls [OPERATION] [MODE]
Operations:
--add-default-privs Add the default set of privileges.
--fix-disallow-shell Clean up the 'disallow-shell' privilege.
Modes:
--reseller [reseller] Update the specified reseller.
--all-resellers Update all resellers on the system.
--acl-list [acl-list] Update the specified ACL list.
--all-acl-lists Update all ACL lists on the system.
--help This documentation.
--man Full documentation.
=head1 Operations
Specify at least one operation.
=over
=item B<--add-default-privs>
Add the default set of privileges, introduced in v68, to the set of resellers
and ACLS lists specified.
acct-summary
basic-system-info
basic-whm-functions
cors-proxy-get
cpanel-integration
cpanel-api
create-user-session
digest-auth
generate-email-config
list-pkgs
manage-api-tokens
manage-dns-records
manage-oidc
manage-styles
mysql-info
ns-config
ssl-info
track-email
=item B<--fix-disallow-shell>
Remove the 'disallow-shell' privilege from the set of resellers and specified ACL lists.
If the C<disallow-shell> privilege is set, then the script will remove it.
If the C<disallow-shell> privilege is not set, then the script adds the C<allow-shell> privilege.
=back
=head1 Modes
Specify at least one mode.
=over
=item B<--all-resellers>
Process all of the resellers on the system. This option overrides B<--reseller>.
B<Note>: The script does not process resellers without an associated domain in this mode.
=item B<--reseller [reseller-username]>
Process the reseller specified.
Specify this option multiple times to process mutiple resellers.
=item B<--all-acl-lists>
Process all ACL lists on the system. This option overrides B<--acl-list>.
=item B<--acl-list [acl-list]>
Process the ACL list specified.
Specify this option multiple times to process mutiple ACL lists.
=back
=head1 EXAMPLES
=over
=item C<--add-default-privs --fix-disallow-shell --all-resellers>
Update the privileges for all resellers on the system to include the default privilege and clean
up the C<disallow-shell> privilege.
=item C<--add-default-privs --reseller myreseller>
Update the privileges for the I<myreseller> reseller to include the new default privileges.
=item C<--add-default-privs --fix-disallow-shell --all-acl-lists>
Update all of the ACL lists on the system to include the default privileges, and clean up the
C<disallow-shell> privilege.
=back
=cut
Copyright 2K16 - 2K18 Indonesian Hacker Rulez