Mini Shell
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2019 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT
package CloudLinux;
use strict;
use warnings;
use JSON::XS;
use Text::Trim qw(trim);
use Cpanel::SafeRun::Object();
use Whostmgr::HTMLInterface ();
use Whostmgr::ACLS ();
use MIME::Base64;
use constant ASSETS_PATH => "/3rdparty/cloudlinux/assets";
use constant OWNER_ADMIN => 'admin';
use constant OWNER_USER => 'user';
use constant OWNER_RESELLER => 'reseller';
use constant APP_MODE => 'PRODUCTION_MODE';
use constant DEFAULT_LANGUAGE => 'en';
use constant DOC_ROOT => "/usr/local/cpanel/whostmgr/docroot";
use constant CLOUDLINUX_CLI => '/usr/share/l.v.e-manager/utils/cloudlinux-cli.py';
use constant CLOUDLINUX_CLI_USER => '/usr/share/l.v.e-manager/utils/cloudlinux-cli-user.py';
my $CURRENT_USER = $ENV{'TEAM_OWNER'} ? $ENV{'TEAM_OWNER'} : $ENV{'REMOTE_USER'};
my $current_locale;
my $user_type;
sub detectLocale
{
$current_locale = _getCurrentLocale($_[0]);
}
sub parseForm {
my (%DATA) = @_;
my %result;
foreach my $key (keys %DATA) {
if ($key =~ /^file\-/) {
if ($key =~ /^file-(.+)-key$/) {
my $fileName = $1;
my $filePath = $DATA{"file-$1"};
if ($filePath =~ /\/Cpanel_Form_file\.upload\.[a-z0-9]{8,10}$/) {
unshift (@{$result{$DATA{$key}}}, {'name' => $fileName, 'file' => $filePath});
}
}
}
elsif ($key =~ /^([^\[\]]+)(\[.+\])$/) {
my $name_of_param = $1;
my $path = $2;
my @parts = $path=~/\[([^\[\]]+)\]/g;
unshift(@parts, $name_of_param);
creatBranch(\@parts, \%result, $DATA{$key});
} else {
$result{$key} = $DATA{$key};
}
}
return %result;
}
sub creatBranch {
my ($parts, $post, $value) = @_;
my $first = shift(@$parts);
if (ref($_[1]) eq 'HASH') {
if (exists $_[1]{$first}) {
creatBranch(\@$parts, $_[1]{$first}, $value);
} else {
$_[1]{$first} = @$parts ? \%{getInnerValues($value, @$parts)} : $value;
}
}
}
sub getInnerValues {
my ($value, @parts) = @_;
my $first = shift(@parts);
if (@parts) {
return {$first => getInnerValues($value, @parts)} ;
} else {
return {$first => $value};
}
}
sub _getApplicationMode
{
my $modeFile = '/usr/share/l.v.e-manager/spa/app_mode.status';
if (-e $modeFile) {
return trim(safeRun('cat '.$modeFile));
}
return APP_MODE;
}
sub safeRun {
my $command;
if(ref($_[0]) eq 'ARRAY'){
$command = join ' ', $_[0];
} else {
$command = join ' ', @_;
}
my $proc = Cpanel::SafeRun::Object->new(
'program' => '/bin/bash',
'args' => [ '-c', $command ],
'keep_env' => 1
);
my $stdout = trim($proc->stdout());
my $stderr = $proc->stderr();
if($stdout eq '') {
return $stderr;
}
return $stdout;
}
sub _getUserIdByName
{
my ($user_name) = @_;
return trim(safeRun(
sprintf('id -u %s', $user_name)
));
}
sub setJsonHeader {
my ($content) = @_;
responseCustomHeaders("Content-type: application/json\n\n", $content);
}
sub responseFile {
my ($filename) = @_;
my $filesize;
sendError("File download error", 0, 0, "File $filename not available for panel user") if !-e $filename;
open FILE, "< $filename" or sendError("File download error", 0, 0, "File $filename is not available for reading");
binmode FILE;
$filesize = -s $filename;
print "Content-Type:application/x-download\n";
print "Content-Length: $filesize\n\n";
local $/ = \10240;
while (<FILE>){
print $_;
}
exit;
}
sub responseCustomHeaders {
my ($headers, $content) = @_;
print "HTTP/1.1 200 OK\n";
print $headers;
print $content;
}
sub knockKnock
{
setJsonHeader('{"result":"success"}');
}
sub sendError {
my ($errorMessage, $isJSON, $logoutSignal, $details) = @_;
print "HTTP/1.1 503 Service Unavailable\n";
print "Content-type: application/json\n\n";
if ($isJSON) {
print $errorMessage;
} else {
my %res = (
'result' => $errorMessage,
'logoutSignal' => $logoutSignal ? $logoutSignal : 0,
'details' => $details || ''
);
print encode_json \%res;
}
exit;
}
sub sendUnavailableError {
my ($pluginName) = @_;
print "HTTP/1.1 503 Service Unavailable\n";
print "Content-type: application/json\n\n";
my %res = (
'result' => '',
'code' => 503,
'error_id' => 'ERROR.not_available_plugin',
'context' => {
'pluginName' => $pluginName,
},
'icon' => 'disabled'
);
print encode_json \%res;
exit;
}
sub checkMethod {
if(($ENV{REQUEST_METHOD} ne $_[0]) && (!defined($_[1]) || $ENV{REQUEST_METHOD} ne $_[1])) {
print "HTTP/1.1 405 Method Not Allowed\n";
print "Content-type: text/html\n\n";
print "Method Not Allowed";
exit;
}
}
sub getPluginVersion
{
return safeRun('cat /usr/share/l.v.e-manager/version');
}
sub _getCurrentLocale
{
my $cgi = $_[0];
my $locale = _getLocaleFromCookie($cgi) || _getSystemLocale();
return $locale;
}
sub _getLocaleFromCookie
{
my $cgi = $_[0];
return $cgi->cookie('session_locale');
}
sub _getSystemLocale
{
my $userArgument = $user_type eq OWNER_USER ? '' : sprintf('--user=%s', $CURRENT_USER);
my $responseInJson = safeRun(
sprintf('cpapi2 %s Locale get_user_locale --output=json 2>/dev/null', $userArgument)
);
my %response;
eval {
%response = %{decode_json($responseInJson)};
};
# If decode_json is catched an exeption or specified key in result doesn't exist
# set default language
if ($@ || !exists $response{'cpanelresult'}{'data'}[0]{'locale'}) {
return DEFAULT_LANGUAGE;
} else {
return $response{'cpanelresult'}{'data'}[0]{'locale'};
}
}
sub loadAssets {
my ($assetsPath, $mainBundle, $config, $assetsStaticPath) = @_;
Whostmgr::HTMLInterface::load_css($assetsPath.'/css/bootstrap.min.css');
Whostmgr::HTMLInterface::load_css($assetsPath.'/css/lvemanager.css');
Whostmgr::HTMLInterface::load_css($assetsPath.'/static/common-styles.css');
loadGlobalVariables($assetsStaticPath);
Whostmgr::HTMLInterface::load_js($assetsPath.'/js/jquery.min.js');
Whostmgr::HTMLInterface::load_js($assetsPath.'/js/bootstrap.min.js');
Whostmgr::HTMLInterface::load_js($assetsPath.'/js/'.$config.'.js');
Whostmgr::HTMLInterface::load_js($assetsPath.'/js/common.js');
# For integration tests, don't remove comment in production in line below
#Whostmgr::HTMLInterface::load_js($assetsPath.'/js/interceptor.js'); #for integration tests
my $pluginVersion = getPluginVersion();
Whostmgr::HTMLInterface::load_js(
sprintf('%s/static/common.bundle.min.js?v=%s', $assetsPath, $pluginVersion)
);
Whostmgr::HTMLInterface::load_js(
sprintf('%s/static/polyfills.bundle.min.js?v=%s', $assetsPath, $pluginVersion)
);
Whostmgr::HTMLInterface::load_js(
sprintf('%s/static/vendor.bundle.min.js?v=%s', $assetsPath, $pluginVersion)
);
Whostmgr::HTMLInterface::load_js(
sprintf('%s/static/%s.bundle.min.js?v=%s', $assetsPath, $mainBundle, $pluginVersion)
);
}
sub getDataContent {
my ($folder, $file_name, $print) = @_;
my $file = DOC_ROOT.ASSETS_PATH."/$folder/$file_name";
my $content = '';
if (-e $file) {
open(FH, $file);
while (<FH>){
$content .= $_;
}
close(FH);
} else {
$content = qq{<div class="error_block">
The specified file does not exist</div>};
}
if ($print) {
print $content;
} else {
return $content;
}
}
sub jsonHandler {
my %data;
my %REQUEST = %{$_[0]};
my $requestBody = $_[1];
my @ALLOWED_COMMANDS = qw(lvectl cloudlinux-awp-admin cloudlinux-limits);
$data{'owner'} = $user_type;
$data{'command'} = 'lvectl';
$data{'plugin_name'} = 'jsonhandler';
foreach my $param (keys %REQUEST)
{
if ($param eq 'handler') {
$data{'method'} = $REQUEST{'handler'};
} elsif ($param eq 'command') {
if (grep {$REQUEST{'command'} eq $_} @ALLOWED_COMMANDS) {
$data{'command'} = $REQUEST{'command'};
}
else {
sendError('COMMAND NOT ALLOWED');
}
} elsif (ref($param) ne 'HASH' && $param ne 'cgiaction' ) {
if (exists $REQUEST{'command'} && $REQUEST{'command'} eq 'cloudlinux-limits' && $param eq 'lveid') {
$data{'params'}{'lve-id'} = $REQUEST{$param};
} else {
$data{'params'}{$param} = $REQUEST{$param};
}
}
}
if(defined $requestBody) {
$data{'params'}{'stdin'} = $requestBody;
}
my $fullCommandStr;
$fullCommandStr = "ulimit -m unlimited -v unlimited && " . CLOUDLINUX_CLI;
$fullCommandStr = sprintf(
"%s --data=%s",
$fullCommandStr, encode_base64(encode_json(\%data), '')
);
my $responseInJson = safeRun($fullCommandStr);
setJsonHeader($responseInJson);
}
sub lvemanagerHandler
{
my ($REQUEST_REF, $plugin_name) = @_;
my %REQUEST = %$REQUEST_REF;
unless (exists $REQUEST{'command'}) {
sendError('COMMAND NOT SPECIFIED');
}
my %data;
$data{'owner'} = $user_type;
$data{'command'} = $REQUEST{'command'};
$data{'plugin_name'} = $plugin_name;
if (exists $REQUEST{'method'}) {
$data{'method'} = $REQUEST{'method'};
}
if (exists $REQUEST{'params'}) {
$data{'params'} = $REQUEST{'params'};
}
if (exists $REQUEST{'attachments[]'}) {
$data{'attachments'} = [];
foreach my $file ( @{$REQUEST{'attachments[]'}} ) {
unshift (@{$data{'attachments'}}, $file);
}
}
if ($data{'owner'} ne OWNER_ADMIN) {
$data{'user_info'} = {
'username' => $CURRENT_USER,
'lve-id' => _getUserIdByName($CURRENT_USER)
};
}
if (exists $REQUEST{'mockJson'} && $REQUEST{'mockJson'}) {
$data{'mockJson'} = $REQUEST{'mockJson'};
}
if (exists $REQUEST{'lang'} && $REQUEST{'lang'}) {
$data{'lang'} = $REQUEST{'lang'};
}
my $fullCommandStr;
if ($data{'owner'} eq OWNER_ADMIN) {
$fullCommandStr = "ulimit -m unlimited -v unlimited && " . CLOUDLINUX_CLI;
} elsif ($data{'owner'} eq OWNER_RESELLER) {
$fullCommandStr = CLOUDLINUX_CLI
} elsif ($data{'owner'} eq OWNER_USER) {
$fullCommandStr = CLOUDLINUX_CLI_USER;
}
$fullCommandStr = sprintf(
"%s --data=%s",
$fullCommandStr, encode_base64(JSON::XS->new->encode(\%data), '')
);
my $responseInJson = safeRun($fullCommandStr);
my %response;
eval {
%response = %{decode_json($responseInJson)};
};
# If decode_json is catched an exeption, send error header with backtrace
if ($@ && $responseInJson ne '') {
sendError('ERROR.wrong_received_data', 0, 0, $responseInJson);
}
if (exists $response{'result'} && $response{'result'} eq 'file') {
responseFile($response{'filepath'})
}
if (exists $response{'result'} && $response{'result'} ne 'success' && $response{'result'} ne 'rollback') {
sendError($responseInJson, 1);
}
if ($responseInJson eq '') {
sendError('RESPONSE OF COMMAND IS EMPTY');
}
setJsonHeader($responseInJson);
}
sub detectOwner
{
if (_isAdmin()) {
return setOwner(OWNER_ADMIN);
}
if (_isReseller()) {
return setOwner(OWNER_RESELLER);
}
return setOwner(OWNER_USER);
}
sub setOwner
{
my ($owner) = @_;
$user_type = $owner;
return $owner;
}
sub _isAdmin
{
if (Whostmgr::ACLS::hasroot()) {
return 1;
}
return 0;
}
sub _isReseller
{
my $RESELLER_LIST_FILE = '/var/cpanel/resellers';
my $result = 0;
if (-e $RESELLER_LIST_FILE) {
open my $f, $RESELLER_LIST_FILE or die "Could not open $RESELLER_LIST_FILE: $!";
while( my $line = <$f>) {
my @data = split /:/, $line;
if ($CURRENT_USER eq $data[0]) {
$result = 1;
last;
}
}
close $f;
}
return $result;
}
sub loadGlobalVariables
{
my $appMode = _getApplicationMode();
my $pluginVersion = getPluginVersion();
my ($assetsStaticPath) = @_;
printf(
'<script type="text/javascript">' .
'var userType = "%s";'.
'var userName = "%s";'.
'var currentLanguage = "%s";'.
'var APP_MODE = "%s";'.
'var localePath = "%s";'.
'var assetsStaticPath = "%s";'.
'var pluginVersion = "%s";'.
'</script>',
$user_type,
$CURRENT_USER,
$current_locale,
$appMode,
$assetsStaticPath.'/i18n/',
$assetsStaticPath.'/',
trim($pluginVersion)
);
}
# Should be present for require
1;
Zerion Mini Shell 1.0