Edit File: fix_reseller_acls
#!/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::LoadCpUserFile'); 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::LoadCpUserFile::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