diff --git a/lib/Froxlor/Api/Commands/CustomerBackups.php b/lib/Froxlor/Api/Commands/CustomerBackups.php index efbbaa66..412bc0b6 100644 --- a/lib/Froxlor/Api/Commands/CustomerBackups.php +++ b/lib/Froxlor/Api/Commands/CustomerBackups.php @@ -49,6 +49,8 @@ class CustomerBackups extends ApiCommand implements ResourceEntity * * @param string $path * path to store the backup to + * @param string $pgp_public_key + * optional pgp public key to encrypt the backup, default is empty * @param bool $backup_dbs * optional whether to backup databases, default is 0 (false) * @param bool $backup_mail @@ -72,6 +74,7 @@ class CustomerBackups extends ApiCommand implements ResourceEntity $path = $this->getParam('path'); // parameter + $pgp_public_key = $this->getParam('pgp_public_key', true, ''); $backup_dbs = $this->getBoolParam('backup_dbs', true, 0); $backup_mail = $this->getBoolParam('backup_mail', true, 0); $backup_web = $this->getBoolParam('backup_web', true, 0); @@ -89,6 +92,19 @@ class CustomerBackups extends ApiCommand implements ResourceEntity Response::standardError('backupfoldercannotbedocroot', '', true); } + // pgp public key validation + if (!empty($pgp_public_key)) { + // check if gnupg extension is loaded + if (!extension_loaded('gnupg')) { + Response::standardError('gnupgextensionnotavailable', '', true); + } + // check if the pgp public key is a valid key + putenv('GNUPGHOME='.sys_get_temp_dir()); + if (gnupg_import(gnupg_init(), $pgp_public_key) === false) { + Response::standardError('invalidpgppublickey', '', true); + } + } + if ($backup_dbs != '1') { $backup_dbs = '0'; } @@ -107,10 +123,12 @@ class CustomerBackups extends ApiCommand implements ResourceEntity 'gid' => $customer['guid'], 'loginname' => $customer['loginname'], 'destdir' => $path, + 'pgp_public_key' => $pgp_public_key, 'backup_dbs' => $backup_dbs, 'backup_mail' => $backup_mail, 'backup_web' => $backup_web ]; + // schedule backup job Cronjob::inserttask(TaskId::CREATE_CUSTOMER_BACKUP, $task_data); diff --git a/lib/Froxlor/Cron/System/BackupCron.php b/lib/Froxlor/Cron/System/BackupCron.php index 5b818b56..eb67522c 100644 --- a/lib/Froxlor/Cron/System/BackupCron.php +++ b/lib/Froxlor/Cron/System/BackupCron.php @@ -36,9 +36,9 @@ class BackupCron extends FroxlorCron public static function run() { - // Check Traffic-Lock - if (function_exists('pcntl_fork')) { - $BackupLock = FileDir::makeCorrectFile(dirname(self::getLockfile()) . "/froxlor_cron_backup.lock"); + // Check Backup-Lock + if (function_exists('pcntl_fork') && !defined('CRON_NOFORK_FLAG')) { + $BackupLock = FileDir::makeCorrectFile("/var/run/froxlor_cron_backup.lock"); if (file_exists($BackupLock) && is_numeric($BackupPid = file_get_contents($BackupLock))) { if (function_exists('posix_kill')) { $BackupPidStatus = @posix_kill($BackupPid, 0); @@ -69,7 +69,7 @@ class BackupCron extends FroxlorCron // Fork failed return 1; } - } else { + } elseif (!defined('CRON_NOFORK_FLAG')) { if (extension_loaded('pcntl')) { $msg = "PHP compiled with pcntl but pcntl_fork function is not available."; } else { @@ -114,7 +114,8 @@ class BackupCron extends FroxlorCron ]); } - if (function_exists('pcntl_fork')) { + + if (function_exists('pcntl_fork') && !defined('CRON_NOFORK_FLAG')) { @unlink($BackupLock); die(); } @@ -152,7 +153,7 @@ class BackupCron extends FroxlorCron ]); $has_dbs = false; - $current_dbserver = null; + $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']) { @@ -183,7 +184,9 @@ class BackupCron extends FroxlorCron $create_backup_tar_data .= './mysql '; } - unlink($mysqlcnf_file); + if (file_exists($mysqlcnf_file)) { + unlink($mysqlcnf_file); + } unset($sql_root); } @@ -223,11 +226,24 @@ class BackupCron extends FroxlorCron } if (!empty($create_backup_tar_data)) { - $backup_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-backup_' . date('YmdHi', time()) . '.tar.gz'); + // set owner to customer + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> chown -R ' . (int)$data['uid'] . ':' . (int)$data['gid'] . ' ' . escapeshellarg($tmpdir)); + FileDir::safe_exec('chown -R ' . (int)$data['uid'] . ':' . (int)$data['gid'] . ' ' . escapeshellarg($tmpdir)); + // create tar-file + $backup_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-backup_' . date('YmdHi', time()) . '.tar.gz' . (!empty($data['pgp_public_key']) ? '.gpg' : '')); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating backup-file "' . $backup_file . '"'); - // pack all archives in tmp-dir to one - $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data)); - FileDir::safe_exec('tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data)); + if (!empty($data['pgp_public_key'])){ + // pack all archives in tmp-dir to one archive and encrypt it with gpg + $recipient_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-recipients.gpg'); + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating recipient-file "' . $recipient_file . '"'); + file_put_contents($recipient_file, $data['pgp_public_key']); + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data) . ' | gpg --encrypt --recipient-file '. escapeshellarg($recipient_file) .' --output ' . escapeshellarg($backup_file) . ' --trust-model always --batch --yes'); + FileDir::safe_exec('tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data) . ' | gpg --encrypt --recipient-file '. escapeshellarg($recipient_file) .' --output ' . escapeshellarg($backup_file) . ' --trust-model always --batch --yes', $return_value, ['|']); + } else { + // pack all archives in tmp-dir to one archive + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data)); + FileDir::safe_exec('tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data)); + } // move to destination directory $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mv ' . escapeshellarg($backup_file) . ' ' . escapeshellarg($data['destdir'])); FileDir::safe_exec('mv ' . escapeshellarg($backup_file) . ' ' . escapeshellarg($data['destdir'])); diff --git a/lib/Froxlor/Install/Install.php b/lib/Froxlor/Install/Install.php index ebf322cb..ebed9fdb 100644 --- a/lib/Froxlor/Install/Install.php +++ b/lib/Froxlor/Install/Install.php @@ -43,7 +43,7 @@ class Install public $formfield; public string $requiredVersion = '7.4.0'; public array $requiredExtensions = ['session', 'ctype', 'xml', 'filter', 'posix', 'mbstring', 'curl', 'gmp', 'json', 'gd']; - public array $suggestedExtensions = ['bcmath', 'zip']; + public array $suggestedExtensions = ['bcmath', 'zip', 'gnupg']; public array $suggestions = []; public array $criticals = []; public array $loadedExtensions; diff --git a/lib/configfiles/bionic.xml b/lib/configfiles/bionic.xml index f4edb083..22dd1233 100644 --- a/lib/configfiles/bionic.xml +++ b/lib/configfiles/bionic.xml @@ -4717,7 +4717,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/bookworm.xml b/lib/configfiles/bookworm.xml index 1cf35604..f82d87d4 100644 --- a/lib/configfiles/bookworm.xml +++ b/lib/configfiles/bookworm.xml @@ -3359,7 +3359,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/bullseye.xml b/lib/configfiles/bullseye.xml index b9bd8053..2e8fe249 100644 --- a/lib/configfiles/bullseye.xml +++ b/lib/configfiles/bullseye.xml @@ -4929,7 +4929,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/buster.xml b/lib/configfiles/buster.xml index f1bdbe82..17c988e8 100644 --- a/lib/configfiles/buster.xml +++ b/lib/configfiles/buster.xml @@ -4920,7 +4920,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/focal.xml b/lib/configfiles/focal.xml index cce32d7d..862a80b6 100644 --- a/lib/configfiles/focal.xml +++ b/lib/configfiles/focal.xml @@ -4156,7 +4156,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/gentoo.xml b/lib/configfiles/gentoo.xml index a03d6a16..aecee819 100644 --- a/lib/configfiles/gentoo.xml +++ b/lib/configfiles/gentoo.xml @@ -3934,7 +3934,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/jammy.xml b/lib/configfiles/jammy.xml index cc324a71..c7f7172e 100644 --- a/lib/configfiles/jammy.xml +++ b/lib/configfiles/jammy.xml @@ -4148,7 +4148,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/formfields/customer/extras/formfield.backup.php b/lib/formfields/customer/extras/formfield.backup.php index d6cdce7a..b8fe8898 100644 --- a/lib/formfields/customer/extras/formfield.backup.php +++ b/lib/formfields/customer/extras/formfield.backup.php @@ -35,6 +35,11 @@ return [ 'value' => $pathSelect['value'], 'note' => $pathSelect['note'] ?? '', ], + 'pgp_public_key' => [ + 'label' => lng('panel.backup_pgp_public_key.title'), + 'desc' => lng('panel.backup_pgp_public_key.description'), + 'type' => 'textarea', + ], 'path_protection_info' => [ 'label' => lng('extras.path_protection_label'), 'type' => 'infotext', diff --git a/lib/tablelisting/customer/tablelisting.backups.php b/lib/tablelisting/customer/tablelisting.backups.php index 1ccf3d10..8b5f2b24 100644 --- a/lib/tablelisting/customer/tablelisting.backups.php +++ b/lib/tablelisting/customer/tablelisting.backups.php @@ -39,6 +39,11 @@ return [ 'field' => 'data.destdir', 'callback' => [Ftp::class, 'pathRelative'] ], + 'pgp_public_key' => [ + 'label' => lng('panel.pgp_public_key'), + 'field' => 'data.pgp_public_key', + 'callback' => [Text::class, 'boolean'] + ], 'backup_web' => [ 'label' => lng('extras.backup_web'), 'field' => 'data.backup_web', @@ -57,6 +62,7 @@ return [ ], 'visible_columns' => Listing::getVisibleColumnsForListing('backup_list', [ 'destdir', + 'pgp_public_key', 'backup_web', 'backup_mail', 'backup_dbs' diff --git a/lng/de.lng.php b/lng/de.lng.php index c331ccd5..7ce6d207 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -927,6 +927,8 @@ return [ 'invalidcronjobintervalvalue' => 'Cronjob Intervall muss einer der folgenden Werte sein: %s', 'phpgdextensionnotavailable' => 'Die PHP GD Extension ist nicht verfügbar. Bild-Daten können nicht validiert werden.', '2fa_wrongcode' => 'Der angegebene Code ist nicht korrekt', + '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', ], 'extras' => [ 'description' => 'Hier können Sie zusätzliche Extras einrichten, wie zum Beispiel einen Verzeichnisschutz.
Die Änderungen sind erst nach einer kurzen Zeit wirksam.', @@ -1198,6 +1200,11 @@ Vielen Dank, Ihr Administrator', 'title' => 'Pfad zur Ablage der Backups', 'description' => 'In diesem Ordner werden die Backups abgelegt. Wenn das Sichern von Web-Daten aktiviert ist, werden alle Dateien aus dem Heimatverzeichnis gesichert, exklusive des hier angegebenen Backup-Ordners.', ], + 'backup_pgp_public_key' => [ + 'title' => 'Öffentlicher PGP-Schlüssel', + 'description' => 'Der öffentliche PGP-Schlüssel, mit dem die Backups verschlüsselt werden sollen. Wenn kein Schlüssel angegeben ist, werden die Backups nicht verschlüsselt.', + ], + 'pgp_public_key' => 'Öffentlicher PGP-Schlüssel', 'none_value' => 'Keine', 'viewlogs' => 'Logdateien einsehen', 'not_configured' => 'Das System wurde noch nicht konfiguriert. Klicke auf den Button um die Installation zu starten.', diff --git a/lng/en.lng.php b/lng/en.lng.php index e4645ca3..0a05daf4 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -996,6 +996,8 @@ return [ 'invalidcronjobintervalvalue' => 'Cronjob interval must be one of: %s', 'phpgdextensionnotavailable' => 'The PHP GD extension is not available. Unable to validate image-data', '2fa_wrongcode' => 'The code entered is not valid', + 'gnupgextensionnotavailable' => 'The PHP GnuPG extension is not available. Unable to validate PGP Public Key', + 'invalidpgppublickey' => 'The PGP Public Key is not valid', ], 'extras' => [ 'description' => 'Here you can add some extras, for example directory protection.
The system will need some time to apply the new settings after every change.', @@ -1310,6 +1312,11 @@ Yours sincerely, your administrator', 'title' => 'Destination path for the backup', 'description' => 'This is the path where the backups will be stored. If backup of web-data is selected, all files from the homedir are stored excluding the backup-folder specified here.', ], + 'backup_pgp_public_key' => [ + 'title' => 'Public PGP key for encryption', + 'description' => 'This is the public PGP key which will be used to encrypt the backup. If you leave this field empty, the backup will not be encrypted.', + ], + 'pgp_public_key' => 'Public PGP key', 'none_value' => 'None', 'viewlogs' => 'View logfiles', 'not_configured' => 'System not configured yet. Click here to go to configurations.',