diff --git a/actions/admin/settings/125.cronjob.php b/actions/admin/settings/125.cronjob.php index 383c0d19..99291c59 100644 --- a/actions/admin/settings/125.cronjob.php +++ b/actions/admin/settings/125.cronjob.php @@ -62,14 +62,6 @@ return [ 'type' => 'checkbox', 'default' => false, 'save_method' => 'storeSettingField' - ], - 'system_debug_cron' => [ - 'label' => lng('serversettings.cron.debug'), - 'settinggroup' => 'system', - 'varname' => 'debug_cron', - 'type' => 'checkbox', - 'default' => false, - 'save_method' => 'storeSettingField' ] ] ] diff --git a/bin/froxlor-cli b/bin/froxlor-cli index b6a62b4b..26bdc2b7 100755 --- a/bin/froxlor-cli +++ b/bin/froxlor-cli @@ -33,6 +33,7 @@ use Froxlor\Cli\PhpSessionclean; use Froxlor\Cli\SwitchServerIp; use Froxlor\Cli\UpdateCommand; use Froxlor\Cli\InstallCommand; +use Froxlor\Cli\MasterCron; use Froxlor\Froxlor; // validate correct php version @@ -55,4 +56,5 @@ $application->add(new PhpSessionclean()); $application->add(new SwitchServerIp()); $application->add(new UpdateCommand()); $application->add(new InstallCommand()); +$application->add(new MasterCron()); $application->run(); diff --git a/install/updates/froxlor/update_0.11.inc.php b/install/updates/froxlor/update_0.11.inc.php index 5177e26b..d4454423 100644 --- a/install/updates/froxlor/update_0.11.inc.php +++ b/install/updates/froxlor/update_0.11.inc.php @@ -24,6 +24,7 @@ */ use Froxlor\Froxlor; +use Froxlor\FileDir; use Froxlor\Database\Database; use Froxlor\Settings; use Froxlor\Install\Update; @@ -105,15 +106,16 @@ if (Froxlor::isFroxlorVersion('0.10.99')) { "lng/lng_references.php", "lng/portugues.lng.php", "lng/swedish.lng.php", + "scripts", ); $disabled = explode(',', ini_get('disable_functions')); $exec_allowed = !in_array('exec', $disabled); $del_list = ""; foreach ($to_clean as $filedir) { - $complete_filedir = \Froxlor\Froxlor::getInstallDir() . $filedir; + $complete_filedir = Froxlor::getInstallDir() . $filedir; if (file_exists($complete_filedir)) { if ($exec_allowed) { - Froxlor\FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir)); + FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir)); } else { $del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL; } @@ -154,6 +156,7 @@ if (Froxlor::isFroxlorVersion('0.10.99')) { 'Česká republika' => 'cs' ]; Settings::Set('panel.standardlanguage', $lang_map[Settings::Get('panel_standardlanguage')] ?? 'en'); + Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'system' AND `varname` = 'debug_cron'"); Update::lastStepStatus(0); Froxlor::updateToVersion($update_to); diff --git a/install/updatesql.php b/install/updatesql.php index e0c90fd5..d7e5ae56 100644 --- a/install/updatesql.php +++ b/install/updatesql.php @@ -64,10 +64,10 @@ if (Froxlor::isFroxlor()) { $integrity = new IntegrityCheck(); if (!$integrity->checkAll()) { - Update::lastStepStatus(1, 'Monkeys ate the integrity'); - Update::showUpdateStep("Trying to remove monkeys, feeding bananas"); + Update::lastStepStatus(1, 'Integrity could not be validated'); + Update::showUpdateStep("Trying to automatically restore integrity"); if (!$integrity->fixAll()) { - Update::lastStepStatus(2, 'failed', 'Some monkeys just would not move, you should contact team@froxlor.org'); + Update::lastStepStatus(2, 'failed', 'Check "database validation" as admin on the left-side menu to see where the problem is'); } else { Update::lastStepStatus(0, 'Integrity restored'); } diff --git a/lib/Froxlor/Cli/CliCommand.php b/lib/Froxlor/Cli/CliCommand.php index 935c41e6..36c29223 100644 --- a/lib/Froxlor/Cli/CliCommand.php +++ b/lib/Froxlor/Cli/CliCommand.php @@ -25,7 +25,9 @@ namespace Froxlor\Cli; +use Exception; use Froxlor\Froxlor; +use Froxlor\Settings; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -39,10 +41,42 @@ class CliCommand extends Command $output->writeln("Could not find froxlor's userdata.inc.php file. You should use this script only with an installed froxlor system."); return self::INVALID; } - if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) { - $output->writeln("It seems that the froxlor files have been updated. Please login and finish the update procedure."); + // try database connection + try { + Database::query("SELECT 1"); + } catch (Exception $e) { + // Do not proceed further if no database connection could be established + $output->writeln("" . $e->getMessage() . ""); return self::INVALID; } + if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) { + if ((int)Settings::Get('system.cron_allowautoupdate') == 1) { + return $this->runUpdate($output); + } else { + $output->writeln("It seems that the froxlor files have been updated. Please login and finish the update procedure."); + return self::INVALID; + } + } return self::SUCCESS; } + + private function runUpdate(OutputInterface $output): int + { + $output->writeln('Automatic update is activated and we are going to proceed without any notices'); + include_once Froxlor::getInstallDir() . '/lib/tables.inc.php'; + define('_CRON_UPDATE', 1); + ob_start([ + 'this', + 'cleanUpdateOutput' + ]); + include_once Froxlor::getInstallDir() . '/install/updatesql.php'; + ob_end_flush(); + $output->writeln('Automatic update done - you should check your settings to be sure everything is fine'); + return self::SUCCCESS; + } + + private function cleanUpdateOutput($buffer) + { + return strip_tags(preg_replace("//", "\n", $buffer)); + } } diff --git a/lib/Froxlor/Cli/ConfigServices.php b/lib/Froxlor/Cli/ConfigServices.php index 07902d0c..ed4bb272 100644 --- a/lib/Froxlor/Cli/ConfigServices.php +++ b/lib/Froxlor/Cli/ConfigServices.php @@ -410,7 +410,7 @@ final class ConfigServices extends CliCommand // set is_configured flag Settings::Set('panel.is_configured', '1', true); // run cronjob at the end to ensure configs are all up to date - exec('php ' . Froxlor::getInstallDir() . 'scripts/froxlor_master_cronjob.php --force'); + exec('php ' . Froxlor::getInstallDir() . 'bin/froxlor-cli froxlor:cron --force'); // and done $output->writeln('All services have been configured'); return self::SUCCESS; diff --git a/lib/Froxlor/Cli/MasterCron.php b/lib/Froxlor/Cli/MasterCron.php new file mode 100644 index 00000000..c2c8a0ff --- /dev/null +++ b/lib/Froxlor/Cli/MasterCron.php @@ -0,0 +1,258 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +namespace Froxlor\Cli; + +use Froxlor\Froxlor; +use Froxlor\FileDir; +use Froxlor\Settings; +use Froxlor\FroxlorLogger; +use Froxlor\Database\Database; +use Froxlor\System\Cronjob; +use Froxlor\Cron\TaskId; +use Froxlor\Cron\System\Extrausers; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; + +final class MasterCron extends CliCommand +{ + private $lockFile = null; + + private $cronLog = null; + + protected function configure() + { + $this->setName('froxlor:cron'); + $this->setDescription('Regulary perform tasks created by froxlor'); + $this->addArgument('job', InputArgument::IS_ARRAY, 'Job(s) to run'); + $this->addOption('run-task', 'r', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Run a specific task [1 = re-generate configs, 4 = re-generate dns zones, 10 = re-set quotas, 99 = re-create cron.d-file]') + ->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces re-generating of config-files (webserver, nameserver, etc.)') + ->addOption('debug', 'd', InputOption::VALUE_NONE, 'Output debug information about what is going on to STDOUT.') + ->addOption('no-fork', 'N', InputOption::VALUE_NONE, 'Do not fork to background (traffic cron only).'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $result = self::SUCCESS; + $result = $this->validateRequirements($input, $output); + + $jobs = $input->getArgument('job'); + + // handle force option + if ($input->getOption('force')) { + // rebuild all config files + Cronjob::inserttask(TaskId::REBUILD_VHOST); + Cronjob::inserttask(TaskId::REBUILD_DNS); + Cronjob::inserttask(TaskId::CREATE_QUOTA); + Cronjob::inserttask(TaskId::REBUILD_CRON); + array_push($jobs, 'tasks'); + define('CRON_IS_FORCED', 1); + } + // handle debug option + if ($input->getOption('debug')) { + define('CRON_DEBUG_FLAG', 1); + } + // handle no-fork option + if ($input->getOption('no-fork')) { + define('CRON_NOFORK_FLAG', 1); + } + // handle run-task option + if ($input->getOption('run-task')) { + $tasks_to_run = $input->getOption('run-task'); + foreach ($tasks_to_run as $ttr) { + if (in_array($ttr, [1, 4, 10, 99])) { + Cronjob::inserttask($ttr); + array_push($jobs, 'tasks'); + } else { + $output->writeln('Unknown task number "' . $ttr . '"'); + } + } + } + + // unique job-array + $jobs = array_unique($jobs); + + // check for given job(s) to execute and return if empty + if (empty($jobs)) { + $output->writeln('No job given. Nothing to do.'); + return self::INVALID; + } + + $this->validateOwnership($output); + + $this->cronLog = FroxlorLogger::getInstanceOf([ + 'loginname' => 'cronjob' + ]); + $this->cronLog->setCronDebugFlag(defined('CRON_DEBUG_FLAG')); + + // check whether there are actual tasks to perform by 'tasks'-cron so + // we dont regenerate files unnecessarily + $tasks_cnt_stmt = Database::query("SELECT COUNT(*) as jobcnt FROM `panel_tasks`"); + $tasks_cnt = $tasks_cnt_stmt->fetch(PDO::FETCH_ASSOC); + + // iterate through all needed jobs + foreach ($jobs as $job) { + // lock the job + if ($this->lockJob($job, $output)) { + // get FQDN of cron-class + $cronfile = $this->getCronModule($job, $output); + // validate + if ($cronfile && class_exists($cronfile)) { + // info + $output->writeln('Running "' . $job . '" job' . (defined('CRON_IS_FORCED') ? ' (forced)' : '') . (defined('CRON_DEBUG_FLAG') ? ' (debug)' : '') . (defined('CRON_NOFORK_FLAG') ? ' (not forking)' : '') . ''); + // update time of last run + Cronjob::updateLastRunOfCron($job); + // set logger + $cronfile::setCronlog($this->cronLog); + // run the job + $cronfile::run(); + } + // free the lockfile + $this->unlockJob($job); + } + } + + // regenerate nss-extrausers files / invalidate nscd cache (if used) + $this->refreshUsers((int) $tasks_cnt['jobcnt']); + + // we have to check the system's last guid with every cron run + // in case the admin installed new software which added a new user + //so users in the database don't conflict with system users + $this->cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'Checking system\'s last guid'); + Cronjob::checkLastGuid(); + + // check for cron.d-generation task and create it if necessary + CronConfig::checkCrondConfigurationFile(); + + // reset cronlog-flag if set to "once" + if ((int) Settings::Get('logger.log_cron') == 1) { + FroxlorLogger::getInstanceOf()->setCronLog(0); + } + + return $result; + } + + private function refreshUsers(int $jobcount = 0) + { + if ($jobcount > 0) { + if (Settings::Get('system.nssextrausers') == 1) { + Extrausers::generateFiles(self::$cronlog); + return; + } + + // clear NSCD cache if using fcgid or fpm, #1570 - not needed for nss-extrausers + if ((Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && Settings::Get('system.nssextrausers') == 0) { + $false_val = false; + FileDir::safe_exec('nscd -i passwd 1> /dev/null', $false_val, [ + '>' + ]); + FileDir::safe_exec('nscd -i group 1> /dev/null', $false_val, [ + '>' + ]); + } + } + } + + private function validateOwnership(OutputInterface $output) + { + // when using fcgid or fpm for froxlor-vhost itself, we have to check + // whether the permission of the files are still correct + $output->write('Checking froxlor file permissions...'); + $_mypath = FileDir::makeCorrectDir(Froxlor::getInstallDir()); + + if (((int)Settings::Get('system.mod_fcgid') == 1 && (int)Settings::Get('system.mod_fcgid_ownvhost') == 1) || ((int)Settings::Get('phpfpm.enabled') == 1 && (int)Settings::Get('phpfpm.enabled_ownvhost') == 1)) { + $user = Settings::Get('system.mod_fcgid_httpuser'); + $group = Settings::Get('system.mod_fcgid_httpgroup'); + + if (Settings::Get('phpfpm.enabled') == 1) { + $user = Settings::Get('phpfpm.vhost_httpuser'); + $group = Settings::Get('phpfpm.vhost_httpgroup'); + } + // all the files and folders have to belong to the local user + FileDir::safe_exec('chown -R ' . $user . ':' . $group . ' ' . escapeshellarg($_mypath)); + } else { + // back to webserver permission + $user = Settings::Get('system.httpuser'); + $group = Settings::Get('system.httpgroup'); + FileDir::safe_exec('chown -R ' . $user . ':' . $group . ' ' . escapeshellarg($_mypath)); + } + $output->writeln('OK'); + } + + private function getCronModule(string $cronname, OutputInterface $output) + { + $upd_stmt = Database::prepare(" + SELECT `cronclass` FROM `" . TABLE_PANEL_CRONRUNS . "` WHERE `cronfile` = :cron; + "); + $cron = Database::pexecute_first($upd_stmt, [ + 'cron' => $cronname + ]); + if ($cron) { + return $cron['cronclass']; + } + $output->writeln("Requested cronjob '" . $cronname . "' could not be found."); + return false; + } + + private function lockJob(string $job, OutputInterface $output): bool + { + + $this->lockFile = '/run/lock/froxlor_' . $job . '.lock'; + + if (file_exists($this->lockFile)) { + $jobinfo = json_decode(file_get_contents($this->lockFile), true); + $check_pid_return = null; + // get status of process + system("kill -CHLD " . (int)$jobinfo['pid'] . " 1> /dev/null 2> /dev/null", $check_pid_return); + if ($check_pid_return == 1) { + // Process does not seem to run, most likely it has died + $this->unlockJob($job); + } else { + // cronjob still running, output info and stop + $output->writeln([ + 'Job "' . $jobinfo['job'] . '" is currently running.', + 'Started: ' . date('d.m.Y H:i', (int) $jobinfo['startts']), + 'PID: ' . $jobinfo['pid'] . '' + ]); + return false; + } + } + + $jobinfo = [ + 'job' => $job, + 'startts' => time(), + 'pid' => getmypid() + ]; + file_put_contents($this->lockFile, json_encode($jobinfo)); + return true; + } + + private function unlockJob(string $job): bool + { + return @unlink($this->lockFile); + } +} diff --git a/lib/Froxlor/Cron/CronConfig.php b/lib/Froxlor/Cron/CronConfig.php index 1f7ff2d1..4be56929 100644 --- a/lib/Froxlor/Cron/CronConfig.php +++ b/lib/Froxlor/Cron/CronConfig.php @@ -110,7 +110,7 @@ class CronConfig } // create entry-line - $cronfile .= "root " . $binpath . " " . FileDir::makeCorrectFile(Froxlor::getInstallDir() . "/scripts/froxlor_master_cronjob.php") . " --" . $row_cronentry['cronfile'] . " 1> /dev/null\n"; + $cronfile .= "root " . $binpath . " " . FileDir::makeCorrectFile(Froxlor::getInstallDir() . "/bin/froxlor-cli") . " froxlor:cron " . escapeshellarg($row_cronentry['cronfile']) . " -q 1> /dev/null\n"; } } @@ -135,7 +135,7 @@ class CronConfig $newcrontab = ""; foreach ($crontablines as $ctl) { $ctl = trim($ctl); - if (!empty($ctl) && !preg_match("/(.*)froxlor_master_cronjob\.php(.*)/", $ctl)) { + if (!empty($ctl) && !preg_match("/(.*)froxlor\:cron(.*)/", $ctl)) { $newcrontab .= $ctl . "\n"; } } diff --git a/lib/Froxlor/Cron/FroxlorCron.php b/lib/Froxlor/Cron/FroxlorCron.php index 3ac2ab0a..7e5b4097 100644 --- a/lib/Froxlor/Cron/FroxlorCron.php +++ b/lib/Froxlor/Cron/FroxlorCron.php @@ -42,4 +42,9 @@ abstract class FroxlorCron { static::$lockfile = $lockfile; } + + public static function setCronlog($cronlog = null) + { + static::$cronlog = $cronlog; + } } diff --git a/lib/Froxlor/Cron/Http/Apache.php b/lib/Froxlor/Cron/Http/Apache.php index b3e1e264..ad79ed88 100644 --- a/lib/Froxlor/Cron/Http/Apache.php +++ b/lib/Froxlor/Cron/Http/Apache.php @@ -25,6 +25,7 @@ namespace Froxlor\Cron\Http; +use Froxlor\Froxlor; use Froxlor\Cron\Http\Php\PhpInterface; use Froxlor\Customer\Customer; use Froxlor\Database\Database; @@ -161,6 +162,27 @@ class Apache extends HttpConfigBase } if (!$is_redirect) { + // protect lib/userdata.inc.php + $this->virtualhosts_data[$vhosts_filename] .= ' ' . "\n"; + $this->virtualhosts_data[$vhosts_filename] .= ' ' . "\n"; + if (Settings::Get('system.apache24') == '1') { + $this->virtualhosts_data[$vhosts_filename] .= ' Require all denied' . "\n"; + } else { + $this->virtualhosts_data[$vhosts_filename] .= ' Order deny,allow' . "\n"; + $this->virtualhosts_data[$vhosts_filename] .= ' deny from all' . "\n"; + } + $this->virtualhosts_data[$vhosts_filename] .= ' ' . "\n"; + $this->virtualhosts_data[$vhosts_filename] .= ' ' . "\n"; + // protect bin/ + $this->virtualhosts_data[$vhosts_filename] .= ' ' . "\n"; + if (Settings::Get('system.apache24') == '1') { + $this->virtualhosts_data[$vhosts_filename] .= ' Require all denied' . "\n"; + } else { + $this->virtualhosts_data[$vhosts_filename] .= ' Order deny,allow' . "\n"; + $this->virtualhosts_data[$vhosts_filename] .= ' deny from all' . "\n"; + } + $this->virtualhosts_data[$vhosts_filename] .= ' ' . "\n"; + // create fcgid -Part (starter is created in apache_fcgid) if (Settings::Get('system.mod_fcgid_ownvhost') == '1' && Settings::Get('system.mod_fcgid') == '1') { $configdir = FileDir::makeCorrectDir(Settings::Get('system.mod_fcgid_configdir') . '/froxlor.panel/' . Settings::Get('system.hostname')); diff --git a/lib/Froxlor/Cron/Http/Lighttpd.php b/lib/Froxlor/Cron/Http/Lighttpd.php index 3e0ac959..660113cd 100644 --- a/lib/Froxlor/Cron/Http/Lighttpd.php +++ b/lib/Froxlor/Cron/Http/Lighttpd.php @@ -125,6 +125,15 @@ class Lighttpd extends HttpConfigBase } if (!$is_redirect) { + // protect lib/userdata.inc.php + $this->lighttpd_data[$vhosts_filename] .= ' $HTTP["host"] =~ "' . rtrim(Froxlor::getInstallDir(), "/") . '/lib" {' . "\n"; + $this->lighttpd_data[$vhosts_filename] .= ' url.access-deny = ("userdata.inc.php")' . "\n"; + $this->lighttpd_data[$vhosts_filename] .= ' }' . "\n"; + // protect bin/ + $this->lighttpd_data[$vhosts_filename] .= ' $HTTP["host"] =~ "' . rtrim(Froxlor::getInstallDir(), "/") . '/bin" {' . "\n"; + $this->lighttpd_data[$vhosts_filename] .= ' url.access-deny = ("")' . "\n"; + $this->lighttpd_data[$vhosts_filename] .= ' }' . "\n"; + /** * dirprotection, see #72 * diff --git a/lib/Froxlor/Cron/Http/Nginx.php b/lib/Froxlor/Cron/Http/Nginx.php index b5b7d05d..a0c9c1bb 100644 --- a/lib/Froxlor/Cron/Http/Nginx.php +++ b/lib/Froxlor/Cron/Http/Nginx.php @@ -218,15 +218,25 @@ class Nginx extends HttpConfigBase $this->nginx_data[$vhost_filename] .= "\t" . 'index index.php index.html index.htm;' . "\n\n"; $this->nginx_data[$vhost_filename] .= "\t" . 'location / {' . "\n"; $this->nginx_data[$vhost_filename] .= "\t" . '}' . "\n"; + + // protect lib/userdata.inc.php + $this->nginx_data[$vhosts_filename] .= "\t" . 'location = ' . rtrim(Froxlor::getInstallDir(), "/") . '/lib/userdata.inc.php {' . "\n"; + $this->nginx_data[$vhosts_filename] .= "\t" . ' deny all;' . "\n"; + $this->nginx_data[$vhosts_filename] .= "\t" . '}' . "\n"; + + // protect bin/ + $this->nginx_data[$vhosts_filename] .= "\t" . 'location = ' . rtrim(Froxlor::getInstallDir(), "/") . '/bin {' . "\n"; + $this->nginx_data[$vhosts_filename] .= "\t" . ' deny all;' . "\n"; + $this->nginx_data[$vhosts_filename] .= "\t" . '}' . "\n"; } if ($row_ipsandports['specialsettings'] != '' && ($row_ipsandports['ssl'] == '0' || ($row_ipsandports['ssl'] == '1' && Settings::Get('system.use_ssl') == '1' && $row_ipsandports['include_specialsettings'] == '1'))) { $this->nginx_data[$vhost_filename] .= $this->processSpecialConfigTemplate($row_ipsandports['specialsettings'], [ - 'domain' => Settings::Get('system.hostname'), - 'loginname' => Settings::Get('phpfpm.vhost_httpuser'), - 'documentroot' => $mypath, - 'customerroot' => $mypath - ], $row_ipsandports['ip'], $row_ipsandports['port'], $row_ipsandports['ssl'] == '1') . "\n"; + 'domain' => Settings::Get('system.hostname'), + 'loginname' => Settings::Get('phpfpm.vhost_httpuser'), + 'documentroot' => $mypath, + 'customerroot' => $mypath + ], $row_ipsandports['ip'], $row_ipsandports['port'], $row_ipsandports['ssl'] == '1') . "\n"; } /** @@ -239,11 +249,11 @@ class Nginx extends HttpConfigBase $this->nginx_data[$vhost_filename] .= $this->composeSslSettings($row_ipsandports); if ($row_ipsandports['ssl_specialsettings'] != '') { $this->nginx_data[$vhost_filename] .= $this->processSpecialConfigTemplate($row_ipsandports['ssl_specialsettings'], [ - 'domain' => Settings::Get('system.hostname'), - 'loginname' => Settings::Get('phpfpm.vhost_httpuser'), - 'documentroot' => $mypath, - 'customerroot' => $mypath - ], $row_ipsandports['ip'], $row_ipsandports['port'], $row_ipsandports['ssl'] == '1') . "\n"; + 'domain' => Settings::Get('system.hostname'), + 'loginname' => Settings::Get('phpfpm.vhost_httpuser'), + 'documentroot' => $mypath, + 'customerroot' => $mypath + ], $row_ipsandports['ip'], $row_ipsandports['port'], $row_ipsandports['ssl'] == '1') . "\n"; } } diff --git a/lib/Froxlor/Cron/MasterCron.php b/lib/Froxlor/Cron/MasterCron.php deleted file mode 100644 index f58f3be6..00000000 --- a/lib/Froxlor/Cron/MasterCron.php +++ /dev/null @@ -1,392 +0,0 @@ - - * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 - */ - -namespace Froxlor\Cron; - -use Exception; -use Froxlor\Cron\System\Extrausers; -use Froxlor\Database\Database; -use Froxlor\FileDir; -use Froxlor\Froxlor; -use Froxlor\FroxlorLogger; -use Froxlor\Settings; -use Froxlor\System\Cronjob; -use PDO; - -class MasterCron extends FroxlorCron -{ - - private static $argv = null; - - private static $debugHandler = null; - - private static $noncron_params = [ - 'force', - 'debug', - 'no-fork', - 'run-task' - ]; - - public static function setArguments($argv = null) - { - self::$argv = $argv; - } - - public static function run() - { - self::init(); - - $jobs_to_run = []; - - $argv = self::$argv; - /** - * check for --help - */ - if (count($argv) < 2 || (isset($argv[1]) && strtolower($argv[1]) == '--help')) { - echo "\n*** Froxlor Master Cronjob ***\n\n"; - echo "Below are possible parameters for this file\n\n"; - echo "--[cronname]\t\tincludes the given cron-file\n"; - echo "--force\t\t\tforces re-generating of config-files (webserver, nameserver, etc.)\n"; - echo "--run-task\t\trun a specific task [1 = re-generate configs, 4 = re-generate dns zones, 10 = re-set quotas, 99 = re-create cron.d-file]\n"; - echo "--debug\t\t\toutput debug information about what is going on to STDOUT.\n"; - echo "--no-fork\t\tdo not fork to backkground (traffic cron only).\n\n"; - exit(); - } - - /** - * check for parameters - * - * --[cronname] include [cronname] - * --force to include cron_tasks even if it's not its turn - * --debug to output debug information - */ - for ($x = 1; $x < count($argv); $x++) { - // check argument - if (isset($argv[$x])) { - // --force - if (strtolower($argv[$x]) == '--force') { - // really force re-generating of config-files by - // inserting task 1 - Cronjob::inserttask(TaskId::REBUILD_VHOST); - // bind (if enabled, \Froxlor\System\Cronjob::inserttask() checks this) - Cronjob::inserttask(TaskId::REBUILD_DNS); - // set quotas (if enabled) - Cronjob::inserttask(TaskId::CREATE_QUOTA); - // also regenerate cron.d-file - Cronjob::inserttask(TaskId::REBUILD_CRON); - array_push($jobs_to_run, 'tasks'); - define('CRON_IS_FORCED', 1); - } elseif (strtolower($argv[$x]) == '--debug') { - define('CRON_DEBUG_FLAG', 1); - } elseif (strtolower($argv[$x]) == '--no-fork') { - define('CRON_NOFORK_FLAG', 1); - } elseif (strtolower($argv[$x]) == '--run-task') { - if (isset($argv[$x + 1]) && in_array($argv[$x + 1], [1, 4, 10, 99])) { - Cronjob::inserttask($argv[$x + 1]); - array_push($jobs_to_run, 'tasks'); - } else { - echo "Invalid argument for --run-task\n"; - exit; - } - } elseif (substr(strtolower($argv[$x]), 0, 2) == '--') { - // --[cronname] - if (strlen($argv[$x]) > 3) { - $cronname = substr(strtolower($argv[$x]), 2); - array_push($jobs_to_run, $cronname); - } - } - } - } - - $jobs_to_run = array_unique($jobs_to_run); - - self::$cronlog->setCronDebugFlag(defined('CRON_DEBUG_FLAG')); - - $tasks_cnt_stmt = Database::query("SELECT COUNT(*) as jobcnt FROM `panel_tasks`"); - $tasks_cnt = $tasks_cnt_stmt->fetch(PDO::FETCH_ASSOC); - - // do we have anything to include? - if (count($jobs_to_run) > 0) { - // include all jobs we want to execute - foreach ($jobs_to_run as $cron) { - Cronjob::updateLastRunOfCron($cron); - $cronfile = self::getCronModule($cron); - if ($cronfile && class_exists($cronfile)) { - $cronfile::run(); - } - } - self::refreshUsers($tasks_cnt['jobcnt']); - } - - /** - * we have to check the system's last guid with every cron run - * in case the admin installed new software which added a new user - * so users in the database don't conflict with system users - */ - self::$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'Checking system\'s last guid'); - Cronjob::checkLastGuid(); - - // shutdown cron - self::shutdown(); - } - - private static function init() - { - if (@php_sapi_name() != 'cli' && @php_sapi_name() != 'cgi' && @php_sapi_name() != 'cgi-fcgi') { - die('This script will only work in the shell.'); - } - - // ensure that default timezone is set - if (function_exists("date_default_timezone_set") && function_exists("date_default_timezone_get")) { - @date_default_timezone_set(@date_default_timezone_get()); - } - - $basename = basename($_SERVER['PHP_SELF'], '.php'); - $crontype = ""; - if (isset(self::$argv) && is_array(self::$argv) && count(self::$argv) > 1) { - for ($x = 1; $x < count(self::$argv); $x++) { - if (substr(self::$argv[$x], 0, 2) == '--' && strlen(self::$argv[$x]) > 3 && !in_array(substr(strtolower(self::$argv[$x]), 2), self::$noncron_params)) { - $crontype = substr(strtolower(self::$argv[$x]), 2); - $basename .= "-" . $crontype; - break; - } - } - } - $lockdir = '/var/run/'; - $lockFilename = 'froxlor_' . $basename . '.lock-'; - $lockfName = $lockFilename . getmypid(); - $lockfile = $lockdir . $lockfName; - self::setLockfile($lockfile); - - // create and open the lockfile! - self::$debugHandler = fopen($lockfile, 'w'); - fwrite(self::$debugHandler, 'Setting Lockfile to ' . $lockfile . "\n"); - fwrite(self::$debugHandler, 'Setting Froxlor installation path to ' . Froxlor::getInstallDir() . "\n"); - - if (!file_exists(Froxlor::getInstallDir() . '/lib/userdata.inc.php')) { - die("Froxlor does not seem to be installed yet - skipping cronjob"); - } - - $sql = []; - $sql_root = []; - // Includes the Usersettings eg. MySQL-Username/Passwort etc. - require Froxlor::getInstallDir() . '/lib/userdata.inc.php'; - fwrite(self::$debugHandler, 'Userdatas included' . "\n"); - - // Legacy sql-root-information - if (isset($sql['root_user']) && isset($sql['root_password']) && (!isset($sql_root) || !is_array($sql_root))) { - $sql_root = [ - 0 => [ - 'caption' => 'Default', - 'host' => $sql['host'], - 'user' => $sql['root_user'], - 'password' => $sql['root_password'] - ] - ]; - unset($sql['root_user']); - unset($sql['root_password']); - } - - require Froxlor::getInstallDir() . '/lib/functions.php'; - // Includes the MySQL-Tabledefinitions etc. - require Froxlor::getInstallDir() . '/lib/tables.inc.php'; - fwrite(self::$debugHandler, 'Table definitions included' . "\n"); - - // try database connection, it will throw - // and exception itself if failed - try { - Database::query("SELECT 1"); - } catch (Exception $e) { - // Do not proceed further if no database connection could be established - fclose(self::$debugHandler); - unlink($lockfile); - die($e->getMessage()); - } - - fwrite(self::$debugHandler, 'Database-connection established' . "\n"); - - // open the lockfile directory and scan for existing lockfiles - $lockDirHandle = opendir($lockdir); - - while ($fName = readdir($lockDirHandle)) { - if ($lockFilename == substr($fName, 0, strlen($lockFilename)) && $lockfName != $fName) { - // Check if last run jailed out with an exception - $croncontent = file($lockdir . $fName); - $lastline = $croncontent[(count($croncontent) - 1)]; - - if ($lastline == '=== Keep lockfile because of exception ===') { - fclose(self::$debugHandler); - unlink($lockfile); - Cronjob::dieWithMail('Last cron jailed out with an exception. Exiting...' . "\n" . 'Take a look into the contents of ' . $lockdir . $fName . '* for more information!' . "\n"); - } - - // Check if cron is running or has died. - $check_pid = substr(strrchr($fName, "-"), 1); - $check_pid_return = null; - system("kill -CHLD " . (int)$check_pid . " 1> /dev/null 2> /dev/null", $check_pid_return); - - if ($check_pid_return == 1) { - // Result: Existing lockfile/pid isn't running - // Most likely it has died - // - // Action: Remove it and continue - // - fwrite(self::$debugHandler, 'Previous cronjob didn\'t exit clean. PID: ' . $check_pid . "\n"); - fwrite(self::$debugHandler, 'Removing lockfile: ' . $lockdir . $fName . "\n"); - @unlink($lockdir . $fName); - } else { - // Result: A Cronscript with this pid - // is still running - // Action: remove my own Lock and die - // - // close the current lockfile - fclose(self::$debugHandler); - - // ... and delete it - unlink($lockfile); - Cronjob::dieWithMail('There is already a Cronjob for ' . $crontype . ' in progress. Exiting...' . "\n" . 'Take a look into the contents of ' . $lockdir . $lockFilename . '* for more information!' . "\n"); - } - } - } - - /** - * if using fcgid or fpm for froxlor-vhost itself, we have to check - * whether the permission of the files are still correct - */ - fwrite(self::$debugHandler, 'Checking froxlor file permissions' . "\n"); - $_mypath = FileDir::makeCorrectDir(Froxlor::getInstallDir()); - - if (((int)Settings::Get('system.mod_fcgid') == 1 && (int)Settings::Get('system.mod_fcgid_ownvhost') == 1) || ((int)Settings::Get('phpfpm.enabled') == 1 && (int)Settings::Get('phpfpm.enabled_ownvhost') == 1)) { - $user = Settings::Get('system.mod_fcgid_httpuser'); - $group = Settings::Get('system.mod_fcgid_httpgroup'); - - if (Settings::Get('phpfpm.enabled') == 1) { - $user = Settings::Get('phpfpm.vhost_httpuser'); - $group = Settings::Get('phpfpm.vhost_httpgroup'); - } - // all the files and folders have to belong to the local user - // now because we also use fcgid for our own vhost - FileDir::safe_exec('chown -R ' . $user . ':' . $group . ' ' . escapeshellarg($_mypath)); - } else { - // back to webserver permission - $user = Settings::Get('system.httpuser'); - $group = Settings::Get('system.httpgroup'); - FileDir::safe_exec('chown -R ' . $user . ':' . $group . ' ' . escapeshellarg($_mypath)); - } - - // Initialize logging - self::$cronlog = FroxlorLogger::getInstanceOf([ - 'loginname' => 'cronjob' - ]); - fwrite(self::$debugHandler, 'Logger has been included' . "\n"); - - if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) { - if (Settings::Get('system.cron_allowautoupdate') == null || Settings::Get('system.cron_allowautoupdate') == 0) { - /** - * Do not proceed further if the Database version is not the same as the script version - */ - fclose(self::$debugHandler); - unlink($lockfile); - $errormessage = "Version of file doesn't match version of database. Exiting...\n\n"; - $errormessage .= "Possible reason: Froxlor update\n"; - $errormessage .= "Information: Current version in database: " . Settings::Get('panel.version') . (!empty(Froxlor::BRANDING) ? "-" . Froxlor::BRANDING : "") . " (DB: " . Settings::Get('panel.db_version') . ") - version of Froxlor files: " . Froxlor::getVersionString() . ")\n"; - $errormessage .= "Solution: Please visit your Foxlor admin interface for further information.\n"; - Cronjob::dieWithMail($errormessage); - } - - if (Settings::Get('system.cron_allowautoupdate') == 1) { - /** - * let's walk the walk - do the dangerous shit - */ - self::$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, 'Automatic update is activated and we are going to proceed without any notices'); - self::$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, 'all new settings etc. will be stored with the default value, that might not always be right for your system!'); - self::$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "If you don't want this to happen in the future consider removing the --allow-autoupdate flag from the cronjob"); - fwrite(self::$debugHandler, '*** WARNING *** - Automatic update is activated and we are going to proceed without any notices' . "\n"); - fwrite(self::$debugHandler, '*** WARNING *** - all new settings etc. will be stored with the default value, that might not always be right for your system!' . "\n"); - fwrite(self::$debugHandler, "*** WARNING *** - If you don't want this to happen in the future consider removing the --allow-autoupdate flag from the cronjob\n"); - // including update procedures - define('_CRON_UPDATE', 1); - include_once Froxlor::getInstallDir() . '/install/updatesql.php'; - // pew - everything went better than expected - self::$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, 'Automatic update done - you should check your settings to be sure everything is fine'); - fwrite(self::$debugHandler, '*** WARNING *** - Automatic update done - you should check your settings to be sure everything is fine' . "\n"); - } - } - - fwrite(self::$debugHandler, 'Froxlor version and database version are correct' . "\n"); - } - - private static function getCronModule($cronname) - { - $upd_stmt = Database::prepare(" - SELECT `cronclass` FROM `" . TABLE_PANEL_CRONRUNS . "` WHERE `cronfile` = :cron; - "); - $cron = Database::pexecute_first($upd_stmt, [ - 'cron' => $cronname - ]); - if ($cron) { - return $cron['cronclass']; - } - self::$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Requested cronjob '" . $cronname . "' could not be found."); - return false; - } - - private static function refreshUsers($jobcount = 0) - { - if ($jobcount > 0) { - if (Settings::Get('system.nssextrausers') == 1) { - Extrausers::generateFiles(self::$cronlog); - } - - // clear NSCD cache if using fcgid or fpm, #1570 - not needed for nss-extrausers - if ((Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && Settings::Get('system.nssextrausers') == 0) { - $false_val = false; - FileDir::safe_exec('nscd -i passwd 1> /dev/null', $false_val, [ - '>' - ]); - FileDir::safe_exec('nscd -i group 1> /dev/null', $false_val, [ - '>' - ]); - } - } - } - - private static function shutdown() - { - // check for cron.d-generation task and create it if necessary - CronConfig::checkCrondConfigurationFile(); - - if (Settings::Get('logger.log_cron') == '1') { - FroxlorLogger::getInstanceOf()->setCronLog(0); - fwrite(self::$debugHandler, 'Logging for cron has been shutdown' . "\n"); - } - - fclose(self::$debugHandler); - - if (Settings::Get('system.debug_cron') != '1') { - unlink(self::getLockfile()); - } - } -} diff --git a/lib/Froxlor/FroxlorLogger.php b/lib/Froxlor/FroxlorLogger.php index 959fb7ca..dcc2eb2f 100644 --- a/lib/Froxlor/FroxlorLogger.php +++ b/lib/Froxlor/FroxlorLogger.php @@ -258,30 +258,28 @@ class FroxlorLogger /** * Set whether to log cron-runs * - * @param bool $_cronlog + * @param int $cronlog * - * @return boolean + * @return int */ - public function setCronLog($_cronlog = 0) + public function setCronLog(int $cronlog = 0) { - $_cronlog = (int)$_cronlog; - - if ($_cronlog < 0 || $_cronlog > 2) { - $_cronlog = 0; + if ($cronlog < 0 || $cronlog > 2) { + $cronlog = 0; } - Settings::Set('logger.log_cron', $_cronlog); - return $_cronlog; + Settings::Set('logger.log_cron', $cronlog); + return $cronlog; } /** * setter for crondebug-flag * - * @param bool $_flag + * @param bool $flag * * @return void */ - public function setCronDebugFlag($_flag = false) + public function setCronDebugFlag(bool $flag = false) { - self::$crondebug_flag = (bool)$_flag; + self::$crondebug_flag = $flag; } } diff --git a/lib/Froxlor/UI/Linker.php b/lib/Froxlor/UI/Linker.php index eb8e2bea..6de28b26 100644 --- a/lib/Froxlor/UI/Linker.php +++ b/lib/Froxlor/UI/Linker.php @@ -153,7 +153,7 @@ class Linker } // Encode parameters and add them to the link - $link .= urlencode($key) . (!empty($value) ? '=' . urlencode($value) : ''); + $link .= urlencode($key) . ($value !== "" ? '=' . urlencode($value) : ''); } // Reset our class for further use diff --git a/lib/configfiles/bionic.xml b/lib/configfiles/bionic.xml index 021d7423..231e85dd 100644 --- a/lib/configfiles/bionic.xml +++ b/lib/configfiles/bionic.xml @@ -4646,7 +4646,7 @@ aliases: files - + - + - scripts/froxlor_master_cronjob.php --run-task 99]]> + bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/bullseye.xml b/lib/configfiles/bullseye.xml index 2b7e201b..18dcbe5d 100644 --- a/lib/configfiles/bullseye.xml +++ b/lib/configfiles/bullseye.xml @@ -4857,7 +4857,7 @@ aliases: files - + - + - scripts/froxlor_master_cronjob.php --run-task 99]]> + bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/buster.xml b/lib/configfiles/buster.xml index cfb9c3e1..e28d4033 100644 --- a/lib/configfiles/buster.xml +++ b/lib/configfiles/buster.xml @@ -4848,7 +4848,7 @@ aliases: files - + - + - scripts/froxlor_master_cronjob.php --run-task 99]]> + bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/focal.xml b/lib/configfiles/focal.xml index 8adca449..ccdfb0af 100644 --- a/lib/configfiles/focal.xml +++ b/lib/configfiles/focal.xml @@ -4071,7 +4071,7 @@ aliases: files - + - + - scripts/froxlor_master_cronjob.php --run-task 99]]> + bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/gentoo.xml b/lib/configfiles/gentoo.xml index 2f63c357..9f25e4ab 100644 --- a/lib/configfiles/gentoo.xml +++ b/lib/configfiles/gentoo.xml @@ -3859,7 +3859,7 @@ aliases: files - + - + - scripts/froxlor_master_cronjob.php --run-task 99]]> + bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/init.php b/lib/init.php index b4fe63b5..6e90ff2d 100644 --- a/lib/init.php +++ b/lib/init.php @@ -62,6 +62,7 @@ use Froxlor\UI\Linker; use Froxlor\UI\Panel\UI; use Froxlor\UI\Request; use Froxlor\UI\Response; +use Froxlor\Install\Update; // include MySQL-tabledefinitions require Froxlor::getInstallDir() . '/lib/tables.inc.php'; @@ -153,13 +154,14 @@ UI::setLinker($linker); /** * Global Theme-variable */ -$theme = (Settings::Get('panel.default_theme') !== null) ? Settings::Get('panel.default_theme') : $_deftheme; - -/** - * Overwrite with customer/admin theme if defined - */ -if (CurrentUser::hasSession() && CurrentUser::getField('theme') != $theme) { - $theme = CurrentUser::getField('theme'); +if (Update::versionInUpdate(Settings::Get('panel.version'), '0.11.0-dev1')) { + $theme = $_deftheme; +} else { + $theme = (Settings::Get('panel.default_theme') !== null) ? Settings::Get('panel.default_theme') : $_deftheme; + // Overwrite with customer/admin theme if defined + if (CurrentUser::hasSession() && CurrentUser::getField('theme') != $theme) { + $theme = CurrentUser::getField('theme'); + } } // Check if a different variant of the theme is used diff --git a/lng/de.lng.php b/lng/de.lng.php index bab12045..71c244a0 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -1559,12 +1559,6 @@ Vielen Dank, Ihr Administrator', 'title' => 'Erlaube Verschieben von Domains unter Kunden', 'description' => 'Wenn diese Option aktiviert ist, kann unter Domaineinstellungen die Domain einem anderen Kunden zugewiesen werden.
Achtung: Der Dokumenten-Pfad der Domain wird auf den Heimatpfad (+ Domain-Ordner, sofern aktiviert) des neuen Kunden gesetzt.', ], - 'cron' => [ - 'debug' => [ - 'title' => 'Debuggen des Cronscripts', - 'description' => 'Wenn aktiviert, wird die Lockdatei nach dem Cronlauf zum Debuggen nicht gelöscht
Achtung: Eine alte Lockdatei kann weitere Cronjobs behindern und dafür sorgen, dass diese nicht vollständig ausgeführt werden.', - ], - ], 'specialsettingsforsubdomains' => [ 'description' => 'Wenn ja, werden die individuellen Einstellungen für alle Subdomains übernommen.
Wenn nein, werden Subdomain-Specialsettings entfernt.', ], diff --git a/lng/en.lng.php b/lng/en.lng.php index d5a5632f..ac6fcc82 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -1935,12 +1935,6 @@ Yours sincerely, your administrator', 'title' => 'Allow moving domains between customers', 'description' => 'If activated you can change the customer of a domain at domainsettings.
Attention: Froxlor changes the documentroot to the new customer\'s default homedir (+ domain-folder if activated)', ], - 'cron' => [ - 'debug' => [ - 'title' => 'Cronscript debugging', - 'description' => 'Activate to keep the lockfile after a cron-run for debugging.
Attention:Keeping the lockfile can cause the next scheduled cron not to run properly.', - ], - ], 'specialsettingsforsubdomains' => [ 'description' => 'If yes these custom vHost-settings will be added to all subdomains; if no subdomain-specialsettings are being removed.', ], diff --git a/scripts/froxlor_master_cronjob.php b/scripts/froxlor_master_cronjob.php deleted file mode 100644 index 99a091ae..00000000 --- a/scripts/froxlor_master_cronjob.php +++ /dev/null @@ -1,34 +0,0 @@ - - * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 - */ - -// validate correct php version -if (version_compare("7.4.0", PHP_VERSION, ">=")) { - die('Froxlor requires at least php-7.4. Please validate that your php-cli version and the cron execution command are correct.'); -} - -require dirname(__DIR__) . '/vendor/autoload.php'; - -\Froxlor\Cron\MasterCron::setArguments($argv); -\Froxlor\Cron\MasterCron::run(); diff --git a/scripts/index.html b/scripts/index.html deleted file mode 100644 index e69de29b..00000000