Viewing File: /usr/local/cpanel/whostmgr/docroot/cgi/cpaddons_report.pl
#!/usr/local/cpanel/3rdparty/bin/perl
# cpanel - whostmgr/docroot/cgi/cpaddons_report.pl 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
## no critic qw(RequireUseWarnings)
use strict;
BEGIN { unshift @INC, '/usr/local/cpanel', '/usr/local/cpanel/whostmgr/docroot/cgi', '/usr/local/cpanel/cpaddons'; }
use Cpanel;
use Cpanel::AccessIds ();
use Cpanel::AccessIds::ReducedPrivileges ();
use Cpanel::Config::Users ();
use Cpanel::Config::LoadCpConf ();
use Cpanel::ContactInfo ();
use Cpanel::ContactInfo::Email ();
use Cpanel::Encoder::URI ();
use Cpanel::PwCache ();
use Cpanel::PwCache::Build ();
use Cpanel::SafeDir::MK ();
use Cpanel::Server::Type ();
use Cpanel::Sys::Hostname ();
use Cpanel::Template ();
use Cpanel::Validate::EmailRFC ();
use Cpanel::cPAddons::Cache ();
use Cpanel::cPAddons::Filter ();
use Cpanel::cPAddons::LegacyNaming ();
use Cpanel::cPAddons;
use Cpanel::OS ();
use Whostmgr::ACLS ();
Whostmgr::ACLS::init_acls();
exit unless Cpanel::OS::supports_cpaddons();
my $notify = defined $ARGV[0] && $ARGV[0] eq '--notify' ? 1 : 0;
my $html = -t STDIN || $notify ? 0 : 1;
my %cpconf = Cpanel::Config::LoadCpConf::loadcpconf(); # we want a copy not a reference since initcp() will clear it
my $cpconf_ref = \%cpconf; # we want a copy not a reference since initcp() will clear it
if ( $notify && $cpconf_ref->{'cpaddons_autoupdate'} ) {
require Cpanel::SafeRun::Errors;
Cpanel::SafeRun::Errors::saferunallerrors( '/usr/local/cpanel/scripts/cpaddonsup', '--force', ( $html ? '--html' : '--nohtml' ) );
}
if ($html) {
print "Content-type: text/html\r\n\r\n";
print_header();
if ( !Whostmgr::ACLS::hasroot() ) {
print <<'EOM';
<div class="alert alert-danger">
<span class="glyphicon glyphicon-remove-sign"></span>
<div class="alert-message" id="permission-denied-error">
<strong>Error:</strong>
Permission denied.
</div>
</div>
EOM
print_footer();
exit;
}
if ( Cpanel::Server::Type::is_dnsonly() ) {
print <<'EOM';
<div class="alert alert-danger">
<span class="glyphicon glyphicon-remove-sign"></span>
<div class="alert-message" id="dns-only-error">
<strong>Error:</strong>
This feature is disabled for DNSONLY servers.
</div>
</div>
EOM
print_footer();
exit;
}
}
elsif ( $> != 0 ) {
print_footer();
exit;
}
sub hasfeature { goto &Cpanel::hasfeature; }
{
local $@;
eval 'use cPAddonsConf'; ## no critic qw(ProhibitStringyEval) -- I'm not interested in refactoring this
if ($@) {
my $err_str = "Please create a cPAddons Site Software Configuration";
if ($html) {
print "<h3 class=\"errors\">$err_str <a href=\"cpaddons.pl?force=1\">here</a></h3>\n";
print_footer();
}
elsif ( !$notify ) {
print "$err_str\n";
}
exit;
}
}
Cpanel::PwCache::Build::init_passwdless_pwcache();
use CGI;
use Carp;
delete $ENV{'REMOTE_PASSWORD'};
my $hostname = Cpanel::Sys::Hostname::gethostname();
my $user = CGI::param('user');
my %HOMES;
my @USERS = Cpanel::Config::Users::getcpusers();
if ($user) {
$HOMES{$user} = Cpanel::PwCache::gethomedir($user) || die "Could not determine homedir for user:[$user]";
$USERS[0] = $user;
}
else {
Cpanel::PwCache::Build::init_passwdless_pwcache();
my $pwcache_ref = Cpanel::PwCache::Build::fetch_pwcache();
%HOMES = map { $_->[0] => $_->[7] } @{$pwcache_ref};
}
my ($admincontactemail) = exists $cpconf_ref->{'cpaddons_adminemail'} && $cpconf_ref->{'cpaddons_adminemail'} ? $cpconf_ref->{'cpaddons_adminemail'} : "cpanel\@$hostname";
$admincontactemail = ( Cpanel::ContactInfo::Email::split_multi_email_string($admincontactemail) )[0];
my $back = '';
my $security_token = $ENV{'cp_security_token'} || '';
my $multi = do_action();
if ( !defined $user && $html ) {
print _moderation_view_reqs_link();
print _print_actions( \@USERS );
print _moderation_hash_form();
print_footer();
exit;
}
$user = $ARGV[0] || '' if !$user;
my $dump = CGI::param('dumpreg');
if ( $dump && $user ) {
# Gather the data
Cpanel::initcp($user);
$Cpanel::homedir = $HOMES{$user} || die "Could not determine homedir for user:[$user]";
my $path = "$Cpanel::homedir/.cpaddons/$dump";
my $hr = {};
Cpanel::cPAddons::Cache::read_cache( $path, $hr, $user );
# Output the data
require Data::Dumper;
1 or $Data::Dumper::Sortkeys or die;
local $Data::Dumper::Sortkeys = 1;
my $printable_path = CGI::escapeHTML($path);
my $data_html = CGI::escapeHTML( Data::Dumper::Dumper($hr) );
$data_html =~ s/\$VAR1 =//;
print qq{
<b>Registry Info for $printable_path</b>
<pre>
$data_html
</pre>
};
print_footer();
exit;
}
my $show = scalar CGI::param('show') || '';
if ( grep /^\Q$user\E$/, @USERS ) {
@USERS = ($user);
if ($html) {
print qq(
<p>
<b>Current User:</b> ${\_html($user)}
[ <a href="$ENV{'SCRIPT_NAME'}?show=${\_uri($show)}&user=">View All Users</a> ]
</p>
);
}
}
if ($html) {
print qq(<p><b><a href="$ENV{'SCRIPT_NAME'}?">Main Page</a></b></p>\n); # the "?" is not a mistake
if ( $show eq 'up' || $show eq 'in' ) {
print qq(<p><a href="$ENV{'SCRIPT_NAME'}?show=&user=${\_uri($user)}">Show all installs</a></p>\n);
print qq(<p><a href="$ENV{'SCRIPT_NAME'}?show=up&user=${\_uri($user)}">Show all installs that need updated</a></p>\n)
if $show ne 'up';
print qq(<p><a href="$ENV{'SCRIPT_NAME'}?show=in&user=${\_uri($user)}">Show all installs that are no longer installed in WHM</a></p>\n)
if $show ne 'in';
}
else {
$show = '';
print <<SHW;
<p><a href="$ENV{'SCRIPT_NAME'}?show=up&user=${\_uri($user)}">Show all installs that need updated</a></p>
<p><a href="$ENV{'SCRIPT_NAME'}?show=in&user=${\_uri($user)}">Show all installs that are no longer installed in WHM</a></p>
SHW
}
}
my $has_installs = 0;
print $html
? qq( <style>td { padding-left: 3px; padding-right: 3px; }</style>
<table>
<tr class="scellheader">
<td>User</td>
<td>Addon</td>
<td>Latest</td>
<td>Version</td>
<td><a href="cpaddons.pl">Currently installed in WHM</a></td>
</tr>)
: ''
if !$multi;
my $tdshade = 'tdshade2';
my %cpaddoninfo;
my %installedx = %cPAddonsConf::inst;
my %reseller_acls;
if ($notify) {
require Cpanel::Reseller;
%reseller_acls = Cpanel::Reseller::getresellersaclhash();
}
my %owner_emails;
USR:
for my $usr ( sort @USERS ) {
# Legacy compat
my $user_homedir = $HOMES{$usr} || die "Could not determine homedir for user:[$usr]";
next if !-d "$user_homedir/.cpaddons/";
my $needsupdated = '';
if ( opendir REG, "$user_homedir/.cpaddons/" ) {
my @installed = grep /\A(?:[A-Za-z0-9_]+::){2}[A-Za-z0-9_]+\.\d+(?:[.]yaml)?\Z/, readdir REG;
close REG;
next USR if !@installed;
Cpanel::initcp($usr);
# Legacy compat
$Cpanel::homedir = $user_homedir;
next USR if $Cpanel::user ne $usr;
for my $reg ( sort @installed ) {
next unless $reg =~ /\A((?:[A-Za-z0-9_]+::){2}[A-Za-z0-9_]+)\.\d+(?:[.]yaml)?\Z/;
my $base = $1;
my $module = $base;
$module =~ s{::}{/}g;
next unless -e "/usr/local/cpanel/cpaddons/$module.pm";
my $regst = {};
if ( Cpanel::cPAddons::Cache::read_cache( "$Cpanel::homedir/.cpaddons/$reg", $regst, $Cpanel::user ) ) {
my $vers = CGI::escapeHTML( $regst->{'version'} || 'registry did not contain version' );
my $url = CGI::escapeHTML( $regst->{'url_to_install'} ? " at $regst->{'url_to_install'}" : '' );
my ( undef, undef, $name ) = split /\:\:/, $reg;
$name =~ s{\.\d+$}{};
$name =~ s{_}{ }g;
$name =~ s{\.yaml$}{}g;
$cpaddoninfo{$usr}->{$base}->{$reg} = $vers;
my $is_wordpress = $name =~ m/WordPress/;
if ( $installedx{$base}->{'version'} ne $vers && !$is_wordpress ) {
require Cpanel::Redirect;
my $url_host = Cpanel::Redirect::getserviceSSLdomain( 'cpanel', $Cpanel::CPDATA{'DNS'} ) || $Cpanel::CPDATA{'DNS'};
my $base_url = "https://$url_host:2083/frontend/$Cpanel::CPDATA{'RS'}/addoncgi/cpaddons.html?addon=${\_uri($base)}";
$name .= " v$vers" if $vers;
$needsupdated .= qq( - $name
\tLocation: $url
\tLatest: v$installedx{$base}->{'version'}
\tUpgrade here: $base_url&action=upgrade&workinginstall=${\_uri($reg)}
);
}
}
else {
$needsupdated .= "cPAddons Site Software ${\_html($reg)} could not be checked: ${\_html($!)}\n";
$cpaddoninfo{$usr}->{$base}->{$reg} = 'Could not determine version';
}
if ($html) {
my $upgrade = '';
my $uninstall = '';
my $latest = $installedx{$base}->{'version'};
my $capabilities;
eval "use $base;";
if ( !$@ ) {
no strict 'refs';
my $hr = ${"$base\:\:meta_info"} || {};
$latest = $hr->{'version'}
|| 'Could not determine latest version';
$capabilities = $hr->{capabilities};
}
else {
$latest .= " Module Error <!-- $@ -->";
}
my $dontprint = 0;
my $img = $cPAddonsConf::inst{$base}->{'version'}
&& $base ? 'green' : 'red';
if ( $img eq 'green' && $latest =~ m/Module Error/ ) {
$img = 'yellow';
}
my $col =
( $latest eq $cpaddoninfo{$usr}->{$base}->{$reg} ) || _has_self_upgrade_capability($capabilities)
? '00aa00'
: 'FF0000';
if ( $show eq 'in' ) {
$dontprint = 1 if $installedx{$base}->{'version'};
}
if ( $show eq 'up' ) {
$dontprint = 1 if ( !_has_standard_upgrade_capability($capabilities)
|| ( $latest eq $cpaddoninfo{$usr}->{$base}->{$reg} ) );
}
if ( !$dontprint ) {
unless ( $latest =~ m/Module Error/
|| $cpaddoninfo{$usr}->{$base}->{$reg} =~ m/^registry did/ ) {
my ( $v, $c, $n ) = split /\:\:/, $base;
$uninstall = qq( (<a href="$ENV{'SCRIPT_NAME'}?action=un&user=$usr&inst=${\_uri($reg)}&show=${\_uri($show)}">uninstall</a>))
unless $img eq 'yellow';
$upgrade = qq( (<a href="$ENV{'SCRIPT_NAME'}?action=up&user=$usr&inst=${\_uri($reg)}&show=${\_uri($show)}">upgrade</a>))
if $latest ne $cpaddoninfo{$usr}->{$base}->{$reg}
&& _has_standard_upgrade_capability($capabilities)
&& $img ne 'yellow'
&& -d "/usr/local/cpanel/cpaddons/$v/$c/$n/";
}
if ( defined( CGI::param('action') ) && ( CGI::param('action') eq 'upgradeall' ) ) {
upgrade( $usr, $reg, '<hr />' ) if $upgrade;
}
elsif ( defined( CGI::param('action') ) && ( CGI::param('action') eq 'uninstallnonwhm' ) ) {
uninstall( $usr, $reg, '<hr />' )
if $uninstall && $img eq 'red';
}
else {
# Don't show the version string if this addon self-updates
# TODO: Use the version method from the addon module, if available (See LC-6540)
my $current_version_string = _has_self_upgrade_capability($capabilities) ? '' : $cpaddoninfo{$usr}->{$base}->{$reg};
$tdshade = $tdshade eq 'tdshade2' ? 'tdshade1' : 'tdshade2';
my $xinstalldir = CGI::escapeHTML( $regst->{installdir} );
$has_installs++;
my $display_app_name = Cpanel::cPAddons::LegacyNaming::get_app_name($base);
print qq(
<tr class="$tdshade">
<td>
<a href="$ENV{'SCRIPT_NAME'}?show=${\_uri($show)}&user=${\_uri($usr)}">${\_html($usr)}</a>
</td>
<td>
<a target="_blank" href="$ENV{'SCRIPT_NAME'}?dumpreg=${\_uri($reg)}&user=${\_uri($usr)}">$display_app_name</a>
</td>
<td>$latest</td>
<td>
<font color=#$col>$current_version_string</font>$upgrade
</td>
<td>
<img src="/$img\-status.gif" />$uninstall $xinstalldir
</td>
</tr>
);
}
}
}
else {
$has_installs++;
my $inst = exists $installedx{$base} ? '' : '[not in WHM list]';
print "${\_html($usr)} ${\_html($reg)} Current: $installedx{$base}->{'version'} Installed: ${\_html($cpaddoninfo{$usr}->{$base}->{$reg})} $inst\n"
if !$notify;
}
}
if ( $needsupdated && !$html && $notify ) {
if ( $cpconf_ref->{'cpaddons_notify_users'} ne 'never' && ( $cpconf_ref->{'cpaddons_notify_users'} eq 'always' || -e "$Cpanel::homedir/.cpaddons_notify" ) ) {
my $cpuser_ref = \%Cpanel::CPDATA;
my $user_contact = join( ',', $cpuser_ref->contact_emails_ar()->@* );
if ($user_contact) {
open my $SENDMAIL, '|-', '/usr/sbin/sendmail -t' or die "Failed to start sendmail: $!";
# Subject is 'Site Software' instead of 'cPAddons Site Software' since it is in cpanel context not WHM
print {$SENDMAIL} <<"EO_USER_MESSAGE";
To: $user_contact
From: $admincontactemail
Subject: Site Software Update Notice for $usr on $hostname
Server: $hostname
Account: $usr
In order to protect the security of your website, we recommend that you upgrade
the following scripts that were installed via the "Scripts Library" in your cPanel interface:
$needsupdated
EO_USER_MESSAGE
}
}
my $notify_owner = $cpconf_ref->{'cpaddons_notify_owner'} ? 1 : 0;
my $notify_root = $cpconf_ref->{'cpaddons_notify_root'} ? 1 : 0;
my $owner_email = $notify_owner ? ( Cpanel::ContactInfo::getownercontact($usr) )[0] : '';
require Cpanel::AcctUtils::Owner;
my $owner = Cpanel::AcctUtils::Owner::getowner($usr);
if ($notify_owner) {
if ($owner_email) {
if (
$owner eq 'root'
|| (
exists $reseller_acls{$owner}
&& ( ( ref $reseller_acls{$owner} eq 'ARRAY' && grep / \A all \z /xms, @{ $reseller_acls{$owner} } )
|| ( ref $reseller_acls{$owner} eq 'HASH' && $reseller_acls{$owner}{'all'} ) )
)
) {
$owner_emails{$owner} = {} if ref $owner_emails{$owner} ne 'HASH';
$owner_emails{$owner}->{'email_addr'} = $owner_email;
$owner_emails{$owner}->{'email_body'} .= "[$usr]\n$needsupdated\n";
}
}
}
if ($notify_root) {
if ( $owner ne 'root' || ( $owner eq 'root' && !$notify_owner ) ) {
$owner_emails{'root'} = {} if ref $owner_emails{$owner} ne 'HASH';
$owner_emails{'root'}->{'email_addr'} = $admincontactemail;
$owner_emails{'root'}->{'email_body'} .= "[$usr]\n$needsupdated\n";
}
}
}
}
else {
warn "Could not open registry dir for $_: $!";
}
}
sub _has_standard_upgrade_capability {
my $capabilities = shift;
return ( !$capabilities || $capabilities->{upgrade} && $capabilities->{upgrade}{standard} );
}
sub _has_self_upgrade_capability {
my $capabilities = shift;
return ( $capabilities && $capabilities->{upgrade} && $capabilities->{upgrade}{self} );
}
if ( keys %owner_emails ) {
foreach my $user ( keys %owner_emails ) {
next if !$owner_emails{$user}->{'email_addr'};
open SENDMAIL, '|/usr/sbin/sendmail -t';
print SENDMAIL <<"EO_OWNER_MESSAGE";
To: $owner_emails{$user}->{'email_addr'}
From: $admincontactemail
Subject: Software Security Notice - Script installs need upgrading
Hello $user,
In order to protect the security of your users' websites, we recommend that you
upgrade the following scripts installed via the "Scripts Library" in the cPanel interface:
$owner_emails{$user}->{'email_body'}
EO_OWNER_MESSAGE
close SENDMAIL;
}
}
if ( $html && !$has_installs ) {
my $txt = '<b>There are currently no installations that match your criteria</b>';
if ($multi) {
print "<p>$txt</p>\n";
}
else {
print qq{<tr><td colspan="5">$txt</td></tr>\n};
}
}
print $html ? '</table>' : '' if !$multi;
print $html ? $back : '' if $multi;
print_footer() if $html;
exit;
#### funkshuns ##
# do "main action
sub do_action {
my $action = CGI::param('action');
return 0 unless $action;
my $user = scalar CGI::param('user');
my $show = scalar CGI::param('show');
my $inst = scalar CGI::param('inst');
my $dispuser = defined $user ? "user=${\_uri($user)}&" : '';
my $back = qq(<p>[<a href="$ENV{'SCRIPT_NAME'}?${dispuser}show=${\_uri($show)}">Back</a>]</p>\n);
if ( $action eq 'up' ) {
upgrade( $user, $inst, $back );
print_footer();
exit;
}
elsif ( $action eq 'un' ) {
uninstall( $user, $inst, $back );
print_footer();
exit;
}
elsif ( $action eq 'save_moderation' ) {
_moderation_save_conf();
print_footer();
exit;
}
elsif ( $action eq 'moderation_request' ) {
_moderation_request_list();
print_footer();
exit;
}
elsif ( $action eq 'view_moderation_req' ) {
_moderation_view_request($admincontactemail);
print_footer();
exit;
}
elsif ( $action eq 'install_req' ) {
_moderation_install();
print_footer();
exit;
}
elsif ( $action eq 'deny_req' ) {
_moderation_deny();
print_footer();
exit;
}
elsif ( $action eq 'upgradeall' ) {
print "<b>Upgrading all installs ";
print $user ? "for ${\_html($user)}</b><br />" : "serverwide</b><br />";
return 1;
}
elsif ( $action eq 'uninstallnonwhm' ) {
print "<b>Uninstalling all installs of addons not installed via WHM ";
print $user ? "for ${\_html($user)}</b><br />" : "serverwide</b><br />";
if ( !CGI::param('verified') ) {
print qq(<p><b>Are you sure you want to uninstall this?</b></p>\n<a href="$ENV{'SCRIPT_NAME'}?action=uninstallnonwhm&user=${\_uri($user)}&show=${\_uri($show)}&verified=1">Yes I am sure I want to do this.</a>\n$back);
print_footer();
exit;
}
return 1;
}
return 0;
}
sub upgrade {
my ( $user, $inst, $back ) = @_;
domainpgasuser( 'Upgrading', 'upgrade', $user, $inst, $back );
return;
}
sub uninstall {
my ( $user, $inst, $back, $show ) = @_;
if ( CGI::param('verified') ) {
domainpgasuser( 'Uninstalling', 'uninstall', $user, $inst, $back );
}
else {
print qq(<p><b>Are you sure you want to uninstall this?</b></p>\n<a href="$ENV{'SCRIPT_NAME'}?action=un&user=${\_uri($user)}&inst=${\_uri($inst)}&show=${\_uri($show)}&verified=1">Yes I am sure I want to do this.</a>\n$back);
}
return;
}
sub domainpgasuser {
my ( $label, $akshn, $u, $r, $back ) = @_;
my ($addon) = $r =~ m/\A((?:[A-Za-z0-9_]+::){2}[A-Za-z0-9_]+)\.\d+(?:[.]yaml)?\Z/;
my %input = (
# setuid in cPAddons.pm does not work well when multiple users call this (See case 36772)
# 'asuser' => $u,
'action' => $akshn,
'workinginstall' => $r,
'addon' => $addon,
'verified' => scalar CGI::param('verified') || 0,
);
print "$label ${\_html($u)} ${\_html($r)} installation here...<br />\n";
Cpanel::AccessIds::do_as_user(
$u,
sub {
my $input_hr = \%input;
local $ENV{'CPASUSER'} = $u;
local $ENV{'CPNONAVFORPARENT'} = 1;
Cpanel::initcp($u);
# Legacy compat
$Cpanel::homedir = $HOMES{$u} || die "Could not determine homedir for user:[$u]";
Cpanel::cPAddons::cPAddons_init();
Cpanel::cPAddons::cPAddons_mainpg( \%input, { called_from_root => 1 } );
}
);
return print "<br />\n$back\n";
}
#### moderation ##
sub _get_installed_list {
eval "use cPAddonsConf;";
if ($@) {
print "<!-- cPAddonsConf Error: $@ -->\n";
%cPAddonsConf::inst = ();
}
return grep { $cPAddonsConf::inst{$_}->{'VERSION'} }
sort keys %cPAddonsConf::inst;
}
sub _get_available_hashref {
eval q{require "/usr/local/cpanel/cpaddons/cPAddonsAvailable.pm";};
if ($@) {
print "<p>Sorry - The list of available cPAddons Site Software packages could not be found or fetched!</p> <!-- $@ -->\n";
%cPAddonsAvailable::list = ();
}
return \%cPAddonsAvailable::list;
}
sub _moderation_hash_form { # return form of all installed addons Foo::Bar => 0|1
my $moderated = {};
if ( -e '/var/cpanel/cpaddons_moderated.yaml' ) {
if ( !Cpanel::cPAddons::Cache::read_cache( '/var/cpanel/cpaddons_moderated', $moderated ) ) {
return qq(<p id="er">/var/cpanel/cpaddons_moderated.yaml unreadable: $!</p>\n);
}
}
my $has_moderated_addon = 0;
my $form = '';
my $moderation_section = '';
my $available_ref = _get_available_hashref();
for my $ado ( sort keys %{$available_ref} ) {
my $display_app_name = Cpanel::cPAddons::LegacyNaming::get_app_name($ado);
# Do no allow any action on blacklisted addon
next if ( Cpanel::cPAddons::Filter::is_blacklisted($ado) );
# Do not display moderation capabilities if:
# 1. The cpaddon is not installed; and
# 2. The cpaddon appears on the deprecated list
next if ( $cPAddonsConf::inst{$ado}->{'VERSION'} == 0 && Cpanel::cPAddons::Filter::is_deprecated($ado) );
# If the addon is not already marked for moderation, don't display the option to enable moderation on it.
# This is the first step toward removing the moderation feature entirely.
next if !$moderated->{$ado};
my $checked =
exists $available_ref->{$ado}
&& exists $moderated->{$ado}
&& $moderated->{$ado} ? ' checked="checked"' : '';
$form .= qq(
<div class="checkbox">
<label><input type="checkbox" name="$ado" value="1"$checked /> $display_app_name</label>
</div>
);
$has_moderated_addon = 1;
}
if ($has_moderated_addon) {
$moderation_section .= qq{
<form action="$ENV{'SCRIPT_NAME'}" method="post">
<input type="hidden" name="action" value="save_moderation" />
$form
<p>
<button type="submit" class="btn btn-primary">
Update Moderation
</button>
</p>
</form>
};
}
my $instructions = "You cannot enable moderation for any cPAddons.";
if ($has_moderated_addon) {
$instructions = "You can disable moderation for a cPAddon that already uses moderation. However, you cannot enable moderation for any cPAddons.";
}
$moderation_section = <<"FORM_END";
<div style="margin-left:20px; width: 80%">
<h3>Moderation Configuration</h3>
<div class="alert alert-info alert-dismissable">
<span class="glyphicon glyphicon-info-sign"></span>
<div class="alert-message">
<strong>Info:</strong>
We have deprecated the moderation feature and will remove it in the future.
$instructions
</div>
</div>
$moderation_section
</div>
FORM_END
return $moderation_section;
}
sub _moderation_save_conf { # write /var/cpanel/cpaddons_moderated
my $mod_conf = {};
my $available_ref = _get_available_hashref();
for my $ado ( sort keys %{$available_ref} ) {
$mod_conf->{$ado} = 1 if CGI::param($ado);
}
Cpanel::cPAddons::Cache::write_cache( '/var/cpanel/cpaddons_moderated', $mod_conf );
chmod 0644, '/var/cpanel/cpaddons_moderated.yaml';
print qq{<br /><div><h4>Moderation configuration saved.</h4></div><br />\n};
print qq{<p>[<a href="$ENV{'SCRIPT_NAME'}">Back</a>]</p>\n};
}
sub _moderation_serverwide_requests {
my %req;
USERLOOP:
for my $user (@USERS) {
my $user_homedir = Cpanel::PwCache::gethomedir($user);
my $dir = "$user_homedir/.cpaddons/moderation/";
if ( !-d $dir ) {
Cpanel::AccessIds::ReducedPrivileges::call_as_user( sub { Cpanel::SafeDir::MK::safemkdir($dir) }, $user );
}
if ( opendir my $req_dh, $dir ) {
REQUESTLOOP:
for my $req ( readdir $req_dh ) {
next REQUESTLOOP if $req =~ m{ \A [.] [.]? \z }xms || $req =~ tr/<>&'"//;
$req{$user}->{"$dir$req"} = 1;
}
closedir $req_dh;
}
else {
# for now silence is golden
# could not readdir $dir: $!
}
}
return wantarray ? %req : \%req;
}
sub _moderation_view_reqs_link { # action=moderation_request
my $got_requests = _moderation_serverwide_requests();
my $link = '';
if ( scalar keys %{$got_requests} ) {
$link .= qq{
<style>
.pending-moderation-reqs {
margin-left: 20px;
margin-bottom: 20px;
border: 1px solid #e5e5e5;
padding: 0 15px 20px 15px;
width: 80%
}
.pending-moderation-reqs .links-list td {
padding-left: 10px;
padding-right: 10px;
}
.pending-moderation-reqs .links-list td
</style>
<div class="pending-moderation-reqs">
<h3>Pending Moderation Requests</h3>
<table class="links-list">
};
foreach my $user ( sort keys %{$got_requests} ) {
my $tcount = 0;
my $tclass = 'tdshade1';
$link .= qq{
<tr>
<th align="left">User: <span id="user">${\_html($user)}</span></th>
</tr>
};
foreach my $req ( sort keys %{ $got_requests->{$user} } ) {
$tclass = ( ( $tcount % 2 ) == 0 ) ? 'tdshade1' : 'tdshade2';
my ($yaml_file) = $req =~ m{ ([^/]+) \z }xms;
my ( $mod, $num ) = $yaml_file =~ m{^ ([^.]+) \. (\d+) \. yaml $}x;
my $request_name =
$mod
? sprintf( '%s - Request %s', Cpanel::cPAddons::LegacyNaming::get_app_name($mod), $num )
: $yaml_file;
my $url = "$ENV{'SCRIPT_NAME'}?action=view_moderation_req&req=${\_uri($req)}&source=popup";
my $id = $user . "_" . $req;
$link .= qq{
<tr class="$tclass">
<td>
<a href="" target="_blank" id="${\_html($id)}"
onclick="window.open('$url','name','height=450,width=600,toolbar=no,status=no,menubar=no,scrollbars=1'); return false;">
${\_html($request_name)}
</a>
</td>
</tr>
};
$tcount++;
}
}
$link .= qq{
</table>
</div>
};
#return qq(<p><a href="$ENV{'SCRIPT_NAME'}?action=moderation_request">View Pending moderation request</a></p>\n);
}
return $link;
}
sub _moderation_request_list { # action=view_moderation_req&req=$file
my $needsform = shift || 0;
my %requests = _moderation_serverwide_requests();
print qq{
<table width="80%">
};
for my $usr ( sort keys %requests ) {
print qq(
<tr>
<td><b>${\_html($usr)}</b></td>
</tr>
);
for my $req ( sort keys %{ $requests{$usr} } ) {
my $name = $req;
($name) = $name =~ m{ ([^/]+) \z }xms;
my $url = "$ENV{'SCRIPT_NAME'}?action=view_moderation_req&req=${\_uri($req)}";
if ($needsform) {
print qq(
<tr>
<td>
<input name="${\_html($req)}" type="check">
</td>
<td>
<a href="$url">${\_html($name)}</a>
</td>
</tr>
);
}
else {
print qq(
<tr>
<td>
<a href="$url">${\_html($name)}</a>
</td>
</tr>
);
}
}
print qq(
</table>
);
}
}
# This looks down for the first directory that is not root-owned and returns the
# user it belongs to.
sub _find_user_by_home_directory {
my $path = shift;
return unless -e $path;
my @components = grep { $_ ne ".." } split /\//, $path;
my $composed = "";
while ( defined( my $piece = shift @components ) ) {
$composed .= "/$piece";
return unless -d $composed;
my $uid = ( stat(_) )[4];
return scalar Cpanel::PwCache::getpwuid($uid) if $uid;
}
return;
}
sub _moderation_view_request { # action=install_req&req=$req action=deny_req&req=$req
my $admincontactemail = shift;
my $req;
{
# CGI::param called in list context can lead to vulnerabilities.
# http://blog.gerv.net/2014.10/new-class-of-vulnerability-in-perl-web-applications
# from the previous url sample, seems like we can have multiple 'req' entries
local $CGI::LIST_CONTEXT_WARN; # avoid a once warning
$CGI::LIST_CONTEXT_WARN = 0;
$req = CGI::param('req');
}
my $ispopup = CGI::param('source') eq 'popup' ? 1 : 0;
my $user = _find_user_by_home_directory($req);
if ($user) {
my $name = $req;
($name) = $name =~ m{ ([^/]+) \z }xms;
my $req_hr = {};
Cpanel::cPAddons::Cache::read_cache( $req, $req_hr, $user );
return if !keys %{$req_hr};
if ( $req_hr->{'input_hr'}{'asuser'} ) {
my ($resellercontactemail) = Cpanel::ContactInfo::getownercontact( $req_hr->{'input_hr'}{'asuser'}, 'root' => 'skip' );
$admincontactemail = $resellercontactemail ? $resellercontactemail : $admincontactemail;
}
my $msg = CGI::escapeHTML( $req_hr->{'msg'} );
$msg = '>> ' . $msg;
$msg =~ s{ \n }{\n>> }xmsg;
my $date = localtime( $req_hr->{'date'} );
my $asuser = $req_hr->{'iddnput_hr'}->{'asuser'};
my $email = $req_hr->{'input_hr'}->{'email'};
print <<"REQ_END";
<style>
#masterContainer {
margin-top: 0;
}
td {
padding: 0 5px 5px 5px
}
</style>
<table style="margin-top:20px">
<tr>
<td>
<form action="$ENV{'SCRIPT_NAME'}" name="moderation_action" id="form">
<input type="hidden" name="req" value="${\_html($req)}">
<input type="hidden" name="reqname" value="${\_html($name)}">
<input type="hidden" name="action" id="action">
<table>
<tr>
<td>
<b>From:</b>
</td>
<td>
<input type="text" name="reply-to" value="$admincontactemail" class="form-control" />
</td>
</tr>
<tr>
<td><b>To (${\_html($asuser)}):</b></td>
<td>
<input type="text" name="email" value="${\_html($email)}" class="form-control" />
</td>
</tr>
<tr>
<td><b>Request:</b><td>${\_html($name)}</td>
</tr>
<tr>
<td><b>Date:</b></b><td>$date</td>
</tr>
<tr>
<td colspan="2"><b>Reply:</b></td>
</tr>
<tr>
<td colspan="2">
<textarea name="msg" cols="70" rows="5" class="form-control" >$msg</textarea>
</td>
</tr>
<tr>
<td>
<button type="submit" class="btn btn-primary" id="install-req">
Install
</button>
<span id="install-req-spinner" style="display:none">
<i class="fas fa-spinner fa-spin" aria-hidden="true" ></i>
Installing ...
</span>
</td>
<td>
<span id="deny-req-spinner" style="display:none">
<i class="fas fa-spinner fa-spin" aria-hidden="true" ></i>
Notifying ...
</span>
<button type="submit" class="btn btn-btn-default pull-right" id="deny-req">
Deny Request
</button>
</td>
</tr>
</table>
<script>
window.addEventListener("load", function() {
\$("#install-req").click(function() {
\$("#action").val("install_req");
\$("#form").submit();
\$("#install-req").prop( "disabled", true );
\$('#install-req-spinner').show();
\$("#deny-req").prop( "disabled", true );
});
\$("#deny-req").click(function() {
\$("#action").val("deny_req");
\$("#form").submit();
\$("#install-req").prop( "disabled", true );
\$('#deny-req-spinner').show();
\$("#deny-req").prop( "disabled", true );
});
});
</script>
</form>
</td>
</tr>
</table>
REQ_END
}
else {
print "Could not find that request!";
}
print qq(
<p>
[<a href="$ENV{'SCRIPT_NAME'}?action=moderation_request">Back</a>]
</p>
) if !$ispopup;
}
sub _moderation_install {
my $req = CGI::param('req');
my $user = _find_user_by_home_directory($req);
if ($user) {
my $req_hr = {};
Cpanel::cPAddons::Cache::read_cache( $req, $req_hr, $user );
return if !keys %{$req_hr};
Cpanel::AccessIds::do_as_user(
$user,
sub {
my $input_hr = $req_hr->{input_hr};
local $ENV{'CPASUSER'} = $user;
local $ENV{'CPNONAVFORPARENT'} = 1;
Cpanel::initcp($user);
$Cpanel::homedir = $HOMES{$user} || die "Could not determine homedir for user:[$user]";
Cpanel::cPAddons::cPAddons_init();
Cpanel::cPAddons::cPAddons_mainpg( $input_hr, { called_from_root => 1 } );
unlink $req or print qq(Could not remove request: $!</p>\n);
},
);
_send_installed_message(
scalar CGI::param('msg'),
scalar CGI::param('reqname'),
scalar CGI::param('email'),
scalar CGI::param('reply-to')
);
}
else {
my $url = "$ENV{'SCRIPT_NAME'}?action=moderation_request";
print qq(
<p>
<b>Could not find that request!</b>
</p>
<p>
[<a href="$url" id="back">Back</a>]
</p>
);
}
return;
}
sub _moderation_deny {
my $quiet = shift || 0;
my $req = CGI::param('req');
if ( !$quiet && CGI::param('msg') && CGI::param('email') ) {
_send_request_denied_message( scalar CGI::param('msg'), scalar CGI::param('reqname'), scalar CGI::param('email'), scalar CGI::param('reply-to') );
}
if ( -e $req ) {
my $user = _find_user_by_home_directory($req);
if ($user) {
print qq(<h2>Removing request</h2>\n) if !$quiet;
Cpanel::AccessIds::ReducedPrivileges::call_as_user(
sub {
unlink $req or print qq(Could not remove request: $!\n);
},
$user
);
}
}
else {
print qq(<p><b>Could not find that request!</b></p>\n);
}
print qq(<p>[<a href="" onclick="window.close()">Close</a>]</p>\n)
if !$quiet;
return;
}
sub _send_installed_message {
my $msg = shift;
my $reqname = shift;
my $email = shift;
my $from = shift;
$reqname = 'RE: cPAddons Site Software request ' . $reqname . ' - Installed';
return _send_message( $email, $from, $reqname, $msg );
}
sub _send_request_denied_message {
my $msg = shift;
my $reqname = shift;
my $email = shift;
my $from = shift;
$reqname = 'RE: cPAddons Site Software request ' . $reqname . ' - Request Denied';
return _send_message( $email, $from, $reqname, $msg );
}
sub _send_message {
my $receiver = shift;
my $sender = shift || $admincontactemail;
my $subject = shift;
my $message = shift;
if ( !$receiver ) {
warn "Unable to send message to blank recipient\n";
return;
}
if ( !Cpanel::Validate::EmailRFC::is_valid($receiver) ) {
warn "Recipient email address is not valid\n";
return;
}
if ( !Cpanel::Validate::EmailRFC::is_valid($sender) ) {
warn "Sender email address is not valid\n";
return;
}
if ( $subject =~ /\n/ ) {
warn "Subject can not contain newlines\n";
return;
}
if ( open my $SENDMAIL, '|-', '/usr/sbin/sendmail -t' ) {
print {$SENDMAIL} <<"EO_MESSAGE";
To: $receiver
From: $sender
Subject: $subject
$message
EO_MESSAGE
close $SENDMAIL;
return 1;
}
else {
warn "Unable to send message: $!";
return;
}
}
sub print_header {
return if !$html;
my $ispopup = ( defined( CGI::param('source') ) && ( CGI::param('source') eq 'popup' ) ) ? 1 : 0;
Cpanel::Template::process_template(
'whostmgr',
{
'print' => 1,
'template_file' => 'master_templates/_defheader.tmpl',
'theme' => 'bootstrap',
'app_key' => 'manage_cpaddons_site_software',
'include_legacy_stylesheets' => 1,
'inside_frame_or_tab_or_popup' => $ispopup,
'extrastyle' => qq(
.whm-app-title__image{width:48px;height:48px}
),
},
);
return;
}
sub print_footer {
return if !$html;
my $ispopup = ( defined( CGI::param('source') ) && ( CGI::param('source') eq 'popup' ) ) ? 1 : 0;
Cpanel::Template::process_template(
'whostmgr',
{
'print' => 1,
'template_file' => 'master_templates/_deffooter.tmpl',
'theme' => 'bootstrap',
'include_legacy_stylesheets' => 1,
'inside_frame_or_tab_or_popup' => $ispopup,
'skipsupport' => 1,
'scripts' => [
'../libraries/jquery/current/jquery.min.js',
]
},
);
return;
}
sub _print_actions {
my ($users) = @_;
my $opts;
for ( sort @$users ) {
$opts .= qq( <option name="${\_html($_)}" value="${\_html($_)}">${\_html($_)}</option>\n);
}
my $form = <<"FORM";
<div style="margin-left:20px;">
<h3>Manage Users Site Software</h3>
<form action="$ENV{'SCRIPT_NAME'}" method="post">
<p>
Show
<select name="show" id="filter_type">
<option value="up" selected>All Outdated Installations</option>
<option value="in">Orphaned Installations</option>
<option value="">All Installations</option>
</select>
for
<select name="user" id="selected-user-to-manage">
<option value="">All Users</option>
$opts
</select>
<button type="submit" class="btn btn-primary" id="btn-manage-selected">
Manage
</button>
</p>
</form>
<form action="$ENV{'SCRIPT_NAME'}" method="post">
<p>
<select name="action">
<option value="upgradeall" selected>Upgrade All Installations</option>
<option value="uninstallnonwhm">Uninstall All Orphaned Installations</option>
</select>
for
<select name="user" id="selected-user-to-uninstall">
<option value="">All Users</option>
$opts
</select>
<button type="submit" class="btn btn-primary" id="btn-uninstall-selected">
Go
</button>
</p>
</form>
</div>
FORM
return $form;
}
sub _html {
return CGI::escapeHTML(@_);
}
sub _uri {
return Cpanel::Encoder::URI::uri_encode_str(@_);
}
Back to Directory
File Manager