Compare commits

...

10 Commits

Author SHA1 Message Date
Michael Kaufmann
22aa197864 remove shortcode for --diff-params in configdiff command
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-12 09:07:37 +02:00
Daniel
d53f9b8e58 Add config-diff CLI Command (#1168)
---------

Co-authored-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-12 09:06:53 +02:00
Michael Kaufmann
9d4205acf6 correct validation of hostingplan name and description
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-11 13:57:58 +02:00
Michael Kaufmann
cb8b969ddd forgot to save one file for the last commit
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-11 12:14:47 +02:00
Michael Kaufmann
fcfd44f726 correctly redirect to last-page if session is timed out and remove passing script/qrystr url parameters
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-11 12:12:31 +02:00
Michael Kaufmann
52a06bf806 validate allowed php configurations to be none-empty if php is enabled for the customer
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-28 14:51:35 +02:00
overgrow
20aa162fcc Added support DNS TLSA record (#1165)
Co-authored-by: netcarlos <carlos@allhighseeds.com>
2023-07-28 14:18:53 +02:00
Michael Kaufmann
bb60df0709 more work on backup feature
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-28 12:20:06 +02:00
Michael Kaufmann
a86c8535e0 fix tablelisting of backup-storages
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-27 16:58:22 +02:00
Michael Kaufmann
ab82695806 adjustments in installation for debian 12 and fcgid / disabling mod_php; thx to Konstantin
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-27 11:25:43 +02:00
27 changed files with 435 additions and 56 deletions

View File

@@ -77,6 +77,7 @@ if (($page == 'admins' || $page == 'overview') && $userinfo['change_serversettin
$result['switched_user'] = CurrentUser::getData();
$result['adminsession'] = 1;
$result['userid'] = $result['adminid'];
session_regenerate_id(true);
CurrentUser::setData($result);
$log->logAction(

View File

@@ -82,15 +82,15 @@ if (($page == 'backups' || $page == 'overview')) {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "list backup storages");
try {
$admin_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/admin/tablelisting.backup_storages.php';
$backup_storage_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/admin/tablelisting.backup_storages.php';
$collection = (new Collection(BackupStorages::class, $userinfo))
->withPagination($admin_list_data['backup_storages_list']['columns'], $admin_list_data['backup_storages_list']['default_sorting']);
->withPagination($backup_storage_list_data['backup_storages_list']['columns'], $backup_storage_list_data['backup_storages_list']['default_sorting']);
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $admin_list_data, 'backup_storages_list'),
'listing' => Listing::format($collection, $backup_storage_list_data, 'backup_storages_list'),
'actions_links' => [
[
'href' => $linker->getLink(['section' => 'backups', 'page' => 'backups']),

View File

@@ -94,7 +94,7 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
$result['switched_user'] = CurrentUser::getData();
$result['adminsession'] = 0;
$result['userid'] = $result['customerid'];
session_regenerate_id();
session_regenerate_id(true);
CurrentUser::setData($result);
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "switched user and is now '" . $destination_user . "'");

View File

@@ -53,7 +53,7 @@ if ($action == 'logout') {
if (is_array(CurrentUser::getField('switched_user'))) {
$result = CurrentUser::getData();
$result = $result['switched_user'];
session_regenerate_id();
session_regenerate_id(true);
CurrentUser::setData($result);
$target = (isset($_GET['target']) ? $_GET['target'] : 'index');
$redirect = "admin_" . $target . ".php";

View File

@@ -26,6 +26,7 @@
declare(strict_types=1);
use Froxlor\Cli\ConfigDiff;
use Symfony\Component\Console\Application;
use Froxlor\Cli\RunApiCommand;
use Froxlor\Cli\ConfigServices;
@@ -61,4 +62,5 @@ $application->add(new InstallCommand());
$application->add(new MasterCron());
$application->add(new UserCommand());
$application->add(new ValidateAcmeWebroot());
$application->add(new ConfigDiff());
$application->run();

View File

@@ -52,6 +52,7 @@ if ($action == 'logout') {
if (is_array(CurrentUser::getField('switched_user'))) {
$result = CurrentUser::getData();
$result = $result['switched_user'];
session_regenerate_id(true);
CurrentUser::setData($result);
$target = (isset($_GET['target']) ? $_GET['target'] : 'index');
$redirect = "admin_" . $target . ".php";

View File

@@ -434,8 +434,13 @@ if ($action == '2fa_entercode') {
if (isset($_REQUEST['qrystr']) && $_REQUEST['qrystr'] != "") {
$lastqrystr = urlencode($_REQUEST['qrystr']);
}
$_SESSION['lastscript'] = $lastscript;
$_SESSION['lastqrystr'] = $lastqrystr;
if (!empty($lastscript)) {
$_SESSION['lastscript'] = $lastscript;
}
if (!empty($lastqrystr)) {
$_SESSION['lastqrystr'] = $lastqrystr;
}
UI::view('login/login.html.twig', [
'pagetitle' => 'Login',
@@ -634,7 +639,7 @@ if ($action == 'forgotpwd') {
UI::view('login/fpwd.html.twig', [
'pagetitle' => lng('login.presend'),
'formaction' => 'index.php?action='.$action,
'formaction' => 'index.php?action=' . $action,
'message' => $message,
]);
}
@@ -786,7 +791,7 @@ if ($action == 'll') {
function finishLogin($userinfo)
{
if (isset($userinfo['userid']) && $userinfo['userid'] != '') {
session_regenerate_id();
session_regenerate_id(true);
CurrentUser::setData($userinfo);
$language = $userinfo['def_language'] ?? Settings::Get('panel.standardlanguage');
@@ -800,7 +805,7 @@ function finishLogin($userinfo)
}
$qryparams = [];
if (isset($_SESSION['lastqrystr']) && !empty($_SESSION['lastqrystr'])) {
if (!empty($_SESSION['lastqrystr'])) {
parse_str(urldecode($_SESSION['lastqrystr']), $qryparams);
unset($_SESSION['lastqrystr']);
}
@@ -809,7 +814,7 @@ function finishLogin($userinfo)
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
Response::redirectTo('admin_updates.php?page=overview');
} else {
if (isset($_SESSION['lastscript']) && !empty($_SESSION['lastscript'])) {
if (!empty($_SESSION['lastscript'])) {
$lastscript = $_SESSION['lastscript'];
unset($_SESSION['lastscript']);
if (preg_match("/customer\_/", $lastscript) === 1) {
@@ -824,7 +829,7 @@ function finishLogin($userinfo)
}
}
} else {
if (isset($_SESSION['lastscript']) && !empty($_SESSION['lastscript'])) {
if (!empty($_SESSION['lastscript'])) {
$lastscript = $_SESSION['lastscript'];
unset($_SESSION['lastscript']);
Response::redirectTo($lastscript, $qryparams);

View File

@@ -424,6 +424,10 @@ class Customers extends ApiCommand implements ResourceEntity
}
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
if (empty($allowed_phpconfigs) && $phpenabled == 1) {
Response::standardError('customerphpenabledbutnoconfig', '', true);
}
$allowed_mysqlserver = array();
if (! empty($p_allowed_mysqlserver) && is_array($p_allowed_mysqlserver)) {
foreach ($p_allowed_mysqlserver as $allowed_ms) {
@@ -1163,6 +1167,9 @@ class Customers extends ApiCommand implements ResourceEntity
if (!empty($allowed_phpconfigs)) {
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
}
if (empty($allowed_phpconfigs) && $phpenabled == 1) {
Response::standardError('customerphpenabledbutnoconfig', '', true);
}
// add permission for allowed mysql usage if customer was not allowed to use mysql prior
if ($result['mysqls'] == 0 && ($mysqls == -1 || $mysqls > 0)) {

View File

@@ -302,6 +302,8 @@ class DomainZones extends ApiCommand implements ResourceEntity
}
} elseif ($type == 'SSHFP' && !empty($content)) {
$content = $content;
} elseif ($type == 'TLSA' && !empty($content)) {
$content = $content;
} elseif ($type == 'TXT' && !empty($content)) {
// check that TXT content is enclosed in " "
$content = Dns::encloseTXTContent($content);

View File

@@ -200,8 +200,8 @@ class HostingPlans extends ApiCommand implements ResourceEntity
$value_arr['logviewenabled'] = $this->getBoolParam('logviewenabled', true, 0);
// validation
$name = Validate::validate(trim($name), 'name', '', '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT);
$name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_CONF_TEXT);
if (Settings::Get('system.mail_quota_enabled') != '1') {
$value_arr['email_quota'] = -1;
@@ -382,8 +382,8 @@ class HostingPlans extends ApiCommand implements ResourceEntity
$value_arr['logviewenabled'] = $this->getBoolParam('logviewenabled', true, $result['logviewenabled']);
// validation
$name = Validate::validate(trim($name), 'name', '', '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT);
$name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_CONF_TEXT);
if (Settings::Get('system.mail_quota_enabled') != '1') {
$value_arr['email_quota'] = -1;

View File

@@ -35,19 +35,23 @@ class Ftp extends Storage
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return bool
* @return string
* @throws Exception
*/
protected function putFile(string $filename, string $tmp_source_directory): bool
protected function putFile(string $filename, string $tmp_source_directory): string
{
$source = FileDir::makeCorrectFile($tmp_source_directory . "/" . $filename);
$target = basename($filename);
if (file_exists($source) && !file_exists($target)) {
return ftp_put($this->ftp_conn, $target, $source, FTP_BINARY);
if (file_exists($source) && ftp_size($this->ftp_conn, $filename) == -1) {
if (ftp_put($this->ftp_conn, $filename, $source, FTP_BINARY)) {
return FileDir::makeCorrectFile($this->getDestinationDirectory() . '/' . $filename);
}
}
return false;
return "";
}
/**

View File

@@ -21,19 +21,23 @@ class Local extends Storage
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return bool
* @return string
* @throws Exception
*/
protected function putFile(string $filename, string $tmp_source_directory): bool
protected function putFile(string $filename, string $tmp_source_directory): string
{
$source = FileDir::makeCorrectFile($tmp_source_directory . "/" . $filename);
$target = FileDir::makeCorrectFile($this->getDestinationDirectory() . "/" . $filename);
if (file_exists($source) && !file_exists($target)) {
return rename($source, $target);
rename($source, $target);
return $target;
}
return false;
return "";
}
/**

View File

@@ -14,13 +14,16 @@ class Rsync extends Storage
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return bool
* @return string
*/
protected function putFile(string $filename, string $tmp_source_directory): bool
protected function putFile(string $filename, string $tmp_source_directory): string
{
// TODO: Implement putFiles() method.
return "";
}
/**

View File

@@ -14,13 +14,16 @@ class S3 extends Storage
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return bool
* @return string
*/
protected function putFile(string $filename, string $tmp_source_directory): bool
protected function putFile(string $filename, string $tmp_source_directory): string
{
// TODO: Implement putFiles() method.
return "";
}
/**

View File

@@ -14,13 +14,16 @@ class Sftp extends Storage
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return bool
* @return string
*/
protected function putFile(string $filename, string $tmp_source_directory): bool
protected function putFile(string $filename, string $tmp_source_directory): string
{
// TODO: Implement putFiles() method.
return "";
}
/**

View File

@@ -13,10 +13,13 @@ abstract class Storage
protected array $filesToStore;
/**
* @throws Exception
*/
public function __construct(array $storage_data)
{
$this->sData = $storage_data;
$this->tmpDirectory = sys_get_temp_dir();
$this->tmpDirectory = FileDir::makeCorrectDir(sys_get_temp_dir() . '/backup-' . $this->sData['loginname']);
}
/**
@@ -37,22 +40,126 @@ abstract class Storage
* prepare files to back up (e.g. create archive or similar) and fill $filesToStore
*
* @return void
* @throws Exception
*/
public function prepareFiles(): void
{
$this->filesToStore = [];
$tmpdir = FileDir::makeCorrectDir($this->tmpDirectory . '/.tmp/');
FileDir::safe_exec('mkdir -p ' . escapeshellarg($tmpdir));
// create archive of web, mail and database data
$this->prepareWebData();
$this->prepareDatabaseData();
$this->prepareMailData();
// create json-info-file
}
/**
* @throws Exception
*/
private function prepareWebData(): void
{
$tmpdir = FileDir::makeCorrectDir($this->tmpDirectory . '/.tmp/web');
FileDir::safe_exec('mkdir -p ' . escapeshellarg($tmpdir));
FileDir::safe_exec('tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/' . $this->sData['loginname'] . '-web.tar.gz')) . ' -C ' . escapeshellarg($this->sData['documentroot']) . ' .');
$this->filesToStore[] = FileDir::makeCorrectFile($tmpdir . '/' . $this->sData['loginname'] . '-web.tar.gz');
}
/**
* @throws Exception
*/
private function prepareDatabaseData(): void
{
$tmpdir = FileDir::makeCorrectDir($this->tmpDirectory . '/.tmp/mysql');
FileDir::safe_exec('mkdir -p ' . escapeshellarg($tmpdir));
// get all customer database-names
$sel_stmt = Database::prepare("
SELECT `databasename`, `dbserver` FROM `" . TABLE_PANEL_DATABASES . "`
WHERE `customerid` = :cid ORDER BY `dbserver`
");
Database::pexecute($sel_stmt, [
'cid' => $this->sData['customerid']
]);
$has_dbs = false;
$current_dbserver = -1;
while ($row = $sel_stmt->fetch()) {
// Get sql_root data for the specific database-server the database resides on
if ($current_dbserver != $row['dbserver']) {
Database::needRoot(true, $row['dbserver']);
Database::needSqlData();
$sql_root = Database::getSqlData();
Database::needRoot(false);
// create temporary mysql-defaults file for the connection-credentials/details
$mysqlcnf_file = tempnam("/tmp", "frx");
$mysqlcnf = "[mysqldump]\npassword=" . $sql_root['passwd'] . "\nhost=" . $sql_root['host'] . "\n";
if (!empty($sql_root['port'])) {
$mysqlcnf .= "port=" . $sql_root['port'] . "\n";
} elseif (!empty($sql_root['socket'])) {
$mysqlcnf .= "socket=" . $sql_root['socket'] . "\n";
}
file_put_contents($mysqlcnf_file, $mysqlcnf);
}
$bool_false = false;
FileDir::safe_exec('mysqldump --defaults-file=' . escapeshellarg($mysqlcnf_file) . ' -u ' . escapeshellarg($sql_root['user']) . ' ' . $row['databasename'] . ' > ' . FileDir::makeCorrectFile($tmpdir . '/' . $row['databasename'] . '_' . date('YmdHi', time()) . '.sql'), $bool_false, [
'>'
]);
$has_dbs = true;
$current_dbserver = $row['dbserver'];
}
if ($has_dbs) {
$this->filesToStore[] = $tmpdir;
}
if (@file_exists($mysqlcnf_file)) {
@unlink($mysqlcnf_file);
}
}
private function prepareMailData(): void
{
$tmpdir = FileDir::makeCorrectDir($this->tmpDirectory . '/.tmp/mail');
FileDir::safe_exec('mkdir -p ' . escapeshellarg($tmpdir));
// get all customer mail-accounts
$sel_stmt = Database::prepare("
SELECT `homedir`, `maildir` FROM `" . TABLE_MAIL_USERS . "`
WHERE `customerid` = :cid
");
Database::pexecute($sel_stmt, [
'cid' => $this->sData['customerid']
]);
$tar_file_list = "";
$mail_homedir = "";
while ($row = $sel_stmt->fetch()) {
$tar_file_list .= escapeshellarg("./" . $row['maildir']) . " ";
if (empty($mail_homedir)) {
// this should be equal for all entries
$mail_homedir = $row['homedir'];
}
}
if (!empty($tar_file_list)) {
FileDir::safe_exec('tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/' . $this->sData['loginname'] . '-mail.tar.gz')) . ' -C ' . escapeshellarg($mail_homedir) . ' ' . trim($tar_file_list));
$this->filesToStore[] = FileDir::makeCorrectFile($tmpdir . '/' . $this->sData['loginname'] . '-mail.tar.gz');
}
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return bool
* @return string
*/
abstract protected function putFile(string $filename, string $tmp_source_directory): bool;
abstract protected function putFile(string $filename, string $tmp_source_directory): string;
/**
* @param string $filename
@@ -91,6 +198,8 @@ abstract class Storage
}
/**
* Returns the storage configured destination path for all backups
*
* @return string
* @throws Exception
*/
@@ -111,18 +220,35 @@ abstract class Storage
return false;
}
$filename = FileDir::makeCorrectFile("/backup-" . $this->sData['loginname'] . "-" . date('c') . ".tar.gz");
$filename = FileDir::makeCorrectFile($this->tmpDirectory . "/backup-" . $this->sData['loginname'] . "-" . date('c') . ".tar.gz");
$tmpdir = FileDir::makeCorrectDir($this->tmpDirectory . '/.tmp/');
$create_export_tar_data = implode(" ", $this->filesToStore);
FileDir::safe_exec('chown -R ' . (int)$this->sData['guid'] . ':' . (int)$this->sData['guid'] . ' ' . escapeshellarg($tmpdir));
// @todo create archive $filename from $filesToStore
if (!empty($data['pgp_public_key'])) {
// pack all archives in tmp-dir to one archive and encrypt it with gpg
$recipient_file = FileDir::makeCorrectFile($this->tmpDirectory . '/' . $this->sData['loginname'] . '-recipients.gpg');
file_put_contents($recipient_file, $data['pgp_public_key']);
$return_value = [];
FileDir::safe_exec('tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data) . ' | gpg --encrypt --recipient-file ' . escapeshellarg($recipient_file) . ' --output ' . escapeshellarg($filename) . ' --trust-model always --batch --yes', $return_value, ['|']);
} else {
// pack all archives in tmp-dir to one archive
FileDir::safe_exec('tar cfz ' . escapeshellarg($filename) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data));
}
// determine filesize (use stat locally here b/c files are possibly large and php's filesize() can't handle them)
$sizeCheckFile = FileDir::makeCorrectFile($this->tmpDirectory . "/" . $filename);
$fileSizeOutput = FileDir::safe_exec('/usr/bin/stat -c "%s" ' . escapeshellarg($sizeCheckFile));
$fileSizeOutput = FileDir::safe_exec('/usr/bin/stat -c "%s" ' . escapeshellarg($filename));
$fileSize = (int)array_shift($fileSizeOutput);
// add entry to database and upload/store file
$this->addEntry($filename, $fileSize);
return $this->putFile($filename, $this->tmpDirectory);
FileDir::safe_exec('rm -rf ' . escapeshellarg($tmpdir));
$fileDest = $this->putFile(basename($filename), $this->tmpDirectory);
if (!empty($fileDest)) {
$this->addEntry($fileDest, $fileSize);
return true;
}
return false;
}
/**

View File

@@ -2,6 +2,9 @@
namespace Froxlor\Backup\Storages;
use Exception;
use Froxlor\Database\Database;
class StorageFactory
{
public static function fromType(string $type, array $storage_data): Storage
@@ -9,4 +12,28 @@ class StorageFactory
$type = "\\Froxlor\\Backup\\Storages\\" . ucfirst($type);
return new $type($storage_data);
}
/**
* @throws Exception
*/
public static function fromStorageId(int $storage_id, array $user_data): Storage
{
$storage = self::readStorageData($storage_id);
$storage_data = $user_data;
$storage_data['storage'] = $storage;
return self::fromType($storage['type'], $storage_data);
}
/**
* @throws Exception
*/
private static function readStorageData(int $storage_id): array
{
$stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` WHERE `id` = :bid");
$storage = Database::pexecute_first($stmt, ['bid' => $storage_id]);
if (empty($storage)) {
throw new Exception("Invalid/empty backup-storage. Unable to continue");
}
return $storage;
}
}

View File

@@ -0,0 +1,178 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor\Cli;
use Froxlor\Config\ConfigParser;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
final class ConfigDiff extends CliCommand
{
protected function configure(): void
{
$this->setName('froxlor:config-diff')
->setDescription('Shows differences in config templates between OS versions')
->addArgument('from', InputArgument::OPTIONAL, 'OS version to compare against')
->addArgument('to', InputArgument::OPTIONAL, 'OS version to compare from')
->addOption('list', 'l', InputOption::VALUE_NONE, 'List all possible OS versions')
->addOption('diff-params', '', InputOption::VALUE_REQUIRED, 'Additional parameters for `diff`, e.g. --diff-params="--color=always"');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
require Froxlor::getInstallDir() . '/lib/functions.php';
$parsers = $versions = [];
foreach (glob(Froxlor::getInstallDir() . '/lib/configfiles/*.xml') as $config) {
$name = str_replace(".xml", "", strtolower(basename($config)));
$parser = new ConfigParser($config);
$versions[$name] = $parser->getCompleteDistroName();
$parsers[$name] = $parser;
}
asort($versions);
if ($input->getOption('list') === true) {
$output->writeln('The following OS version templates are available:');
foreach ($versions as $k => $v) {
$output->writeln(str_pad($k, 20) . $v);
}
return self::SUCCESS;
}
if (!$input->hasArgument('from') || !array_key_exists($input->getArgument('from'), $versions)) {
$output->writeln('<error>Missing or invalid "from" argument.</error>');
$output->writeln('Available versions: ' . implode(', ', array_keys($versions)));
return self::INVALID;
}
if (!$input->hasArgument('to') || !array_key_exists($input->getArgument('to'), $versions)) {
$output->writeln('<error>Missing or invalid "to" argument.</error>');
$output->writeln('Available versions: ' . implode(', ', array_keys($versions)));
return self::INVALID;
}
// Make sure diff is installed
$check_diff_installed = FileDir::safe_exec('which diff');
if (count($check_diff_installed) === 0) {
$output->writeln('<error>Unable to find "diff" installation on your system.</error>');
return self::INVALID;
}
$parser_from = $parsers[$input->getArgument('from')];
$parser_to = $parsers[$input->getArgument('to')];
$tmp_from = tempnam(sys_get_temp_dir(), 'froxlor_config_diff_from');
$tmp_to = tempnam(sys_get_temp_dir(), 'froxlor_config_diff_to');
$files = [];
$titles_by_key = [];
// Aggregate content for each config file
foreach ([[$parser_from, 'from'], [$parser_to, 'to']] as $todo) {
foreach ($todo[0]->getServices() as $service_type => $service) {
foreach ($service->getDaemons() as $daemon_name => $daemon) {
foreach ($daemon->getConfig() as $instruction) {
if ($instruction['type'] !== 'file') {
continue;
}
if (isset($instruction['subcommands'])) {
foreach ($instruction['subcommands'] as $subinstruction) {
if ($subinstruction['type'] !== 'file') {
continue;
}
$content = $subinstruction['content'];
}
} else {
$content = $instruction['content'];
}
if (!isset($content)) {
throw new \Exception("Cannot find content for {$instruction['name']}");
}
$key = "{$service_type}_{$daemon_name}_{$instruction['name']}";
$titles_by_key[$key] = "{$service->title} : {$daemon->title} : {$instruction['name']}";
if (!isset($files[$key])) {
$files[$key] = ['from' => '', 'to' => ''];
}
$files[$key][$todo[1]] = $this->filterContent($content);
}
}
}
}
ksort($files);
$diff_params = '';
if ($input->hasOption('diff-params') && trim($input->getOption('diff-params')) !== '') {
$diff_params = trim($input->getOption('diff-params'));
}
// Run diff on each file and output, if anything changed
foreach ($files as $file_key => $content) {
file_put_contents($tmp_from, $content['from']);
file_put_contents($tmp_to, $content['to']);
$diff_output = FileDir::safe_exec("{$check_diff_installed[0]} {$diff_params} {$tmp_from} {$tmp_to}");
if (count($diff_output) === 0) {
continue;
}
$output->writeln('<info># ' . $titles_by_key[$file_key] . '</info>');
$output->writeln(implode("\n", $diff_output) . "\n");
unset($diff_output);
}
// Remove tmp files again
unlink($tmp_from);
unlink($tmp_to);
return self::SUCCESS;
}
private function filterContent(string $content): string
{
$new_content = '';
foreach (explode("\n", $content) as $n) {
$n = trim($n);
if (!$n) {
continue;
}
if (str_starts_with($n, '#')) {
continue;
}
$new_content .= $n . "\n";
}
return $new_content;
}
}

View File

@@ -449,7 +449,11 @@ class Core
$reload = "service php" . PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "-fpm restart";
$config_dir = "/etc/php/" . PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "/fpm/pool.d/";
// fcgid
$binary = "/usr/bin/php" . PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "-cgi";
if ($this->validatedData['distribution'] == 'bookworm') {
$binary = "/usr/bin/php-cgi" . PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;
} else {
$binary = "/usr/bin/php" . PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "-cgi";
}
}
$db_user->query("UPDATE `" . TABLE_PANEL_FPMDAEMONS . "` SET `reload_cmd` = '" . $reload . "', `config_dir` = '" . $config_dir . "' WHERE `id` ='1';");
$db_user->query("UPDATE `" . TABLE_PANEL_PHPCONFIGS . "` SET `binary` = '" . $binary . "';");

View File

@@ -290,7 +290,8 @@ class UI
];
}
public static function validateThemeTemplate(string $name, string $theme = "") {
public static function validateThemeTemplate(string $name, string $theme = "")
{
if (empty(trim($theme))) {
$theme = self::getTheme();
}

View File

@@ -3348,7 +3348,7 @@ aliases: files
<command><![CDATA[mkdir -p {{settings.system.mod_fcgid_configdir}}]]></command>
<command><![CDATA[mkdir -p {{settings.system.mod_fcgid_tmpdir}}]]></command>
<command><![CDATA[chmod 1777 {{settings.system.mod_fcgid_tmpdir}}]]></command>
<command><![CDATA[a2dismod php8.1]]></command>
<command><![CDATA[a2dismod php8.2]]></command>
</commands>
<!-- instead of just restarting apache, we let the cronjob do all the
dirty work -->
@@ -3381,7 +3381,7 @@ aliases: files
</visibility>
<visibility mode="true">{{settings.phpfpm.enabled_ownvhost}}
</visibility>
<command><![CDATA[a2dismod php8.1]]></command>
<command><![CDATA[a2dismod php8.2]]></command>
</commands>
<!-- instead of just restarting apache, we let the cronjob do all the
dirty work -->

View File

@@ -81,6 +81,7 @@ return [
'pgp_public_key' => [
'label' => lng('backup.backup_storage.pgp_public_key'),
'type' => 'textarea',
'value' => Settings::Get('backup.default_pgp_public_key')
],
'retention' => [
'label' => lng('backup.backup_storage.retention'),

View File

@@ -51,6 +51,7 @@ return [
'RP' => 'RP',
'SRV' => 'SRV',
'SSHFP' => 'SSHFP',
'TLSA' => 'TLSA',
'TXT' => 'TXT'
],
'selected' => $type

View File

@@ -181,8 +181,10 @@ if (@file_exists('templates/' . $theme . '/config.json')) {
}
// check for existence of variant in theme
if (is_array($_themeoptions) && (!array_key_exists('variants', $_themeoptions) || !array_key_exists($themevariant,
$_themeoptions['variants']))) {
if (is_array($_themeoptions) && (!array_key_exists('variants', $_themeoptions) || !array_key_exists(
$themevariant,
$_themeoptions['variants']
))) {
$themevariant = "default";
}
@@ -216,12 +218,11 @@ UI::twig()->addGlobal('header_logo', $header_logo);
if (!CurrentUser::hasSession() && AREA != 'login') {
unset($_SESSION['userinfo']);
CurrentUser::setData();
session_destroy();
$params = [
"script" => basename($_SERVER["SCRIPT_NAME"]),
"qrystr" => $_SERVER["QUERY_STRING"]
$_SESSION = [
"lastscript" => basename($_SERVER["SCRIPT_NAME"]),
"lastqrystr" => $_SERVER["QUERY_STRING"]
];
Response::redirectTo('index.php', $params);
Response::redirectTo('index.php');
exit();
}

View File

@@ -26,6 +26,7 @@
use Froxlor\UI\Callbacks\Admin;
use Froxlor\UI\Callbacks\Customer;
use Froxlor\UI\Callbacks\Impersonate;
use Froxlor\UI\Callbacks\PHPConf;
use Froxlor\UI\Callbacks\ProgressBar;
use Froxlor\UI\Callbacks\Style;
use Froxlor\UI\Callbacks\Text;
@@ -36,7 +37,7 @@ return [
'title' => lng('backup.backup_storages.list'),
'icon' => 'fa-solid fa-file-archive',
'self_overview' => ['section' => 'backups', 'page' => 'storages'],
'default_sorting' => ['loginname' => 'asc'],
'default_sorting' => ['description' => 'asc'],
'columns' => [
'id' => [
'label' => 'ID',
@@ -115,6 +116,7 @@ return [
'action' => 'delete',
'id' => ':id'
],
'visible' => [PHPConf::class, 'isNotDefault']
],
],
]

View File

@@ -938,6 +938,7 @@ return [
'gnupgextensionnotavailable' => 'Die PHP GnuPG Extension ist nicht verfügbar. PGP Schlüssel können nicht validiert werden.',
'invalidpgppublickey' => 'Der angegebene PGP Public Key ist ungültig',
'invalid_validtime' => 'Wert der valid_time in Sekunden muss zwischen 10 und 120 liegen.',
'customerphpenabledbutnoconfig' => 'Kunde hat PHP aktiviert aber keine PHP-Konfiguration wurde gewählt.',
],
'extras' => [
'description' => 'Hier können Sie zusätzliche Extras einrichten, wie zum Beispiel einen Verzeichnisschutz.<br />Die Änderungen sind erst nach einer kurzen Zeit wirksam.',

View File

@@ -709,6 +709,7 @@ return [
'RP' => 'Responsible Person record<br>Structure: <code>mailbox[replace @ with a dot] txt-record-name</code><br>Example: <code>team.froxlor.org. froxlor.org.</code>',
'SRV' => 'Service location record, used for newer protocols instead of creating protocol-specific records such as MX.<br>Structure: <code>priority weight port target</code><br>Example: <code>0 5 5060 sipserver.example.com.</code><br>Note: For priority, use field above',
'SSHFP' => 'The SSHFP resource record is used to publish secure shell (SSH) key fingerprints in the DNS.<br>Structure: <code>algorithm type fingerprint</code><br>Algorithms: <code>0: reserved, 1: RSA, 2: DSA, 3: ECDSA, 4: Ed25519, 6: Ed448</code><br>Types: <code>0: reserved, 1: SHA-1, 2: SHA-256</code><br>Example: <code>2 1 123456789abcdef67890123456789abcdef67890</code>',
'TLSA' => 'TLSA (TLS Authentication) record is used to publish fingerprint of a TLS/SSL certificate. It is commonly used for DANE.<br>TLSA records can only be trusted if DNSSEC is enabled on your domain.<br>Structure: <code>usage selector type fingerprint</code><br>Certificate usage: <code>0: PKIX-T, 1: PKIX-EE, 2: DANE-TA, 3: DANE-EE</code><br>Selector: <code>0: Use full certificate, 1: Use subject public key</code><br>Matching type: <code>0: Full: No Hash, 1: SHA-256 Hash, 2:SHA-512 Hash</code><br>Example: <code>3 1 1 123456789abcdef67890123456789abcdef123456789abcdef123456789abcde</code>',
'TXT' => 'Free definable, descriptive text.'
]
],
@@ -1008,6 +1009,7 @@ return [
'gnupgextensionnotavailable' => 'The PHP GnuPG extension is not available. Unable to validate PGP Public Key',
'invalidpgppublickey' => 'The PGP Public Key is not valid',
'invalid_validtime' => 'Valid time in seconds can only be between 10 and 120',
'customerphpenabledbutnoconfig' => 'Customer has PHP activated but no PHP-configuration was selected.',
],
'extras' => [
'description' => 'Here you can add some extras, for example directory protection.<br />The system will need some time to apply the new settings after every change.',