package Imunify::Wrapper;
use strict;
use warnings FATAL => 'all';
use Encode;
use Data::Dumper;
use File::HomeDir;
use File::Basename;
use IO::Handle;
use Imunify::Exception;
use Imunify::Render;
use Imunify::Utils;
use Imunify::Config;
use Imunify::Acls;
use Cpanel::JSON;
#use CGI::Carp qw(fatalsToBrowser); # uncomment to debug 500 error
# When the WHM caller is a reseller (not root admin), drop privileges
# to their UID so the agent's per-user scope filter applies (same path
# used by cPanel end-users today). Returning the guard from the
# entry-points keeps the priv-drop in scope for the lifetime of the
# Imunify::Utils::execute call; cPanel restores root automatically when
# the guard goes out of scope.
#
# Imunify::Wrapper::execute is shared between the WHM CGI (sendRequest.cgi,
# runs as root with init_acls()) and the cPanel end-user CGI
# (imunify.live.pl, dispatched by cpsrvd as the cPanel user, no ACL init).
# The end-user case is already non-root and already routes onto the
# non-root socket via the existing flow — no priv-drop needed (and would
# fail anyway because setuid requires CAP_SETUID). Short-circuit it.
sub _maybe_drop_privileges {
return undef if $> != 0; # already non-root (cPanel end-user)
return undef if Imunify::Acls::isEffectiveAdmin(); # admin or hoster opt-out
my $remote_user = $ENV{REMOTE_USER} // '';
die Imunify::Exception->new('Permission denied: empty REMOTE_USER')
unless length $remote_user;
my $guard;
eval {
require Cpanel::AccessIds::ReducedPrivileges;
$guard = Cpanel::AccessIds::ReducedPrivileges->new($remote_user);
};
die Imunify::Exception->new('Permission denied: priv-drop failed')
if $@ || !$guard;
return $guard;
}
sub execute {
my ($request, $isWHM) = @_;
my %data = ();
$data{'command'} = \@{$request->{'method'}};
$data{'params'} = $request->{'params'};
$data{'params'}{'remote_addr'} = $ENV{REMOTE_ADDR};
my $priv_guard = _maybe_drop_privileges();
return Imunify::Utils::execute('execute', \%data, $isWHM);
}
sub request {
my ($request, $isWHM) = @_;
my $response = execute($request, $isWHM);
Imunify::Render::JSONHeader(Imunify::Render->HTTP_STATUS_OK);
print $response;
}
sub imunfyEmailRequest {
my ($request, $isWHM) = @_;
my %data = ();
$data{'username'} = $ENV{REMOTE_USER};
$data{'command'} = \@{$request->{'method'}};
$data{'params'} = $request->{'params'};
my $priv_guard = _maybe_drop_privileges();
my $response = Imunify::Utils::execute('imunifyEmail', \%data, $isWHM);
Imunify::Render::JSONHeader(Imunify::Render->HTTP_STATUS_OK);
print $response;
}
sub upload {
my ($cgi, $isWHM) = @_;
my ($tmpPath);
my %data = (
'files' => {},
);
foreach my $file ($cgi->param('files[]')) {
$tmpPath = $cgi->tmpFileName($file);
$data{'files'}{$tmpPath} = "$file";
}
# Bug-report file storage only — moves CGI temp files into
# /var/imunify360/uploads (root-only, mode 0700) and chowns root:root.
# No agent socket involved. Priv-drop here would break the storage
# write and close no leak.
return Imunify::Utils::execute('uploadFile', \%data, $isWHM);
}
1;