Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f68c4bc34b | |||
| b9afa347d7 | |||
| 97cf30ed7a | |||
| 305f3f6f86 |
40
.drone.yml
Normal file
40
.drone.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
kind: pipeline
|
||||
name: deploy-froxlor
|
||||
type: docker
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- upgrade-2
|
||||
event:
|
||||
include:
|
||||
- push
|
||||
|
||||
steps:
|
||||
- name: deploy
|
||||
image: cr.wks/drone/drone-rsync:latest
|
||||
settings:
|
||||
hosts: ["rechner02.maketank.net"]
|
||||
source: ./
|
||||
target: ~/froxlor-test
|
||||
user: www-data
|
||||
exclude: ['vendor', '.git*', '*drone.yml', '.settings', '.buildpath', '.editorconfig', '.project', '.travis.yml']
|
||||
args: '-v --delete'
|
||||
log_level: quiet
|
||||
key:
|
||||
from_secret: ssh-www-data-maketank-rsa
|
||||
command_timeout: 10m
|
||||
- name: compose-install
|
||||
image: appleboy/drone-ssh
|
||||
settings:
|
||||
host:
|
||||
- rechner02.maketank.net
|
||||
username: www-data
|
||||
key:
|
||||
from_secret: ssh-www-data-maketank-rsa
|
||||
script:
|
||||
- cd ~/froxlor-test && composer install --no-dev
|
||||
|
||||
5
.github/workflows/build-docs.yml
vendored
5
.github/workflows/build-docs.yml
vendored
@@ -2,8 +2,7 @@ name: build-documentation
|
||||
|
||||
on:
|
||||
release:
|
||||
# only run for stable releases
|
||||
types: [released]
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build_docs:
|
||||
@@ -12,4 +11,4 @@ jobs:
|
||||
- env:
|
||||
GITHUB_TOKEN: ${{ secrets.ORG_GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh workflow run --repo Froxlor/Documentation build-and-deploy.yml -f type=tags -f ref=${{github.ref_name}}
|
||||
gh workflow run --repo Froxlor/Documentation build-and-deploy.yml -f type=tags ref=${{github.ref_name}}
|
||||
|
||||
112
.github/workflows/build-mariadb.yml
vendored
112
.github/workflows/build-mariadb.yml
vendored
@@ -1,5 +1,5 @@
|
||||
name: Froxlor-CI-MariaDB
|
||||
on: [ 'push', 'pull_request', 'create' ]
|
||||
on: ['push', 'pull_request', 'create']
|
||||
|
||||
jobs:
|
||||
froxlor:
|
||||
@@ -8,8 +8,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: [ '7.4', '8.2' ]
|
||||
mariadb-version: [ 10.11, 10.5 ]
|
||||
php-versions: ['7.4', '8.1']
|
||||
mariadb-version: [10.5, 10.4]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: composer:v2
|
||||
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp, gnupg
|
||||
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp
|
||||
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y ant
|
||||
@@ -49,81 +49,33 @@ jobs:
|
||||
- name: Run testing
|
||||
run: ant quick-build
|
||||
|
||||
nightly:
|
||||
name: Create nightly/testing tarball
|
||||
runs-on: ubuntu-latest
|
||||
needs: froxlor
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
# - name: irc push
|
||||
# uses: rectalogic/notify-irc@v1
|
||||
# if: github.event_name == 'push'
|
||||
# with:
|
||||
# channel: "#froxlor"
|
||||
# server: "irc.libera.chat"
|
||||
# nickname: froxlor-ci
|
||||
# message: |
|
||||
# ${{ github.actor }} pushed ${{ github.event.ref }} ${{ github.event.compare }}
|
||||
# ${{ join(github.event.commits.*.message) }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
# - name: irc pull request
|
||||
# uses: rectalogic/notify-irc@v1
|
||||
# if: github.event_name == 'pull_request'
|
||||
# with:
|
||||
# channel: "#froxlor"
|
||||
# server: "irc.libera.chat"
|
||||
# nickname: froxlor-ci
|
||||
# message: |
|
||||
# ${{ github.actor }} opened PR ${{ github.event.pull_request.html_url }}
|
||||
|
||||
- name: Setup PHP with PECL extension
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '7.4'
|
||||
tools: composer:v2
|
||||
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp, gnupg
|
||||
|
||||
- name: Install composer dependencies
|
||||
run: composer install --no-dev
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Build assets
|
||||
run: npm run build
|
||||
working-directory: .
|
||||
|
||||
- name: Setting file/directory permissions
|
||||
run: |
|
||||
find -exec chmod ugo+r,u+w,go-w {} \;
|
||||
find -type f -exec chmod ugo-x {} \;
|
||||
find -type d -exec chmod ugo+x {} \;
|
||||
chmod 0755 bin/froxlor-cli
|
||||
|
||||
- name: Remove vcs and unneeded files
|
||||
run: |
|
||||
rm .gitignore
|
||||
rm .editorconfig
|
||||
rm -rf node_modules
|
||||
rm composer.json
|
||||
rm composer.lock
|
||||
rm package.json
|
||||
rm package-lock.json
|
||||
rm *.xml
|
||||
rm vite.config.js
|
||||
|
||||
- name: Create empty index.html in built assets directory
|
||||
run: |
|
||||
touch templates/Froxlor/build/index.html
|
||||
touch templates/Froxlor/build/assets/index.html
|
||||
|
||||
- name: Set outputs
|
||||
id: vars
|
||||
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set nightly branding
|
||||
run: |
|
||||
sed -i "s/const BRANDING = '';/const BRANDING = '+nightly.${{steps.vars.outputs.sha_short}}';/" lib/Froxlor/Froxlor.php
|
||||
zip -r froxlor-nightly.${{steps.vars.outputs.sha_short}}.zip . -x "*.git*"
|
||||
sha256sum froxlor-nightly.${{steps.vars.outputs.sha_short}}.zip > froxlor-nightly.${{steps.vars.outputs.sha_short}}.zip.sha256
|
||||
mkdir dist
|
||||
mv froxlor-nightly.${{steps.vars.outputs.sha_short}}.zip dist/
|
||||
mv froxlor-nightly.${{steps.vars.outputs.sha_short}}.zip.sha256 dist/
|
||||
|
||||
- name: Deploy nightly to server
|
||||
uses: easingthemes/ssh-deploy@v3.4.3
|
||||
env:
|
||||
ARGS: "-rltDzvO --chown=${{ secrets.WEB_USER }}:${{ secrets.WEB_USER }}"
|
||||
SOURCE: "dist/"
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }}
|
||||
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.REMOTE_USER }}
|
||||
TARGET: "${{ secrets.REMOTE_TARGET }}"
|
||||
# - name: irc tag created
|
||||
# uses: rectalogic/notify-irc@v1
|
||||
# if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
# with:
|
||||
# channel: "#froxlor"
|
||||
# server: "irc.libera.chat"
|
||||
# nickname: froxlor-ci
|
||||
# message: |
|
||||
# ${{ github.actor }} tagged ${{ github.repository }} ${{ github.event.ref }}
|
||||
|
||||
4
.github/workflows/build-mysql.yml
vendored
4
.github/workflows/build-mysql.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['7.4', '8.2']
|
||||
php-versions: ['7.4', '8.1']
|
||||
mysql-version: [8.0, 5.7]
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: composer:v2
|
||||
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp, gnupg
|
||||
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp
|
||||
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y ant
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -22,5 +22,8 @@ fonts/
|
||||
templates/*
|
||||
!templates/index.html
|
||||
!templates/Froxlor/
|
||||
templates/Froxlor/build/
|
||||
templates/Froxlor/assets/mix-manifest.json
|
||||
templates/Froxlor/assets/css/
|
||||
templates/Froxlor/assets/js/
|
||||
templates/Froxlor/assets/webfonts/
|
||||
!templates/misc/
|
||||
|
||||
10
README.md
10
README.md
@@ -34,13 +34,19 @@ You may find help in the following places:
|
||||
|
||||
The froxlor community discord server can be found here: https://discord.froxlor.org
|
||||
|
||||
### IRC
|
||||
|
||||
froxlor may be found on libera.chat, channel #froxlor:
|
||||
irc://irc.libera.chat/froxlor
|
||||
|
||||
### Forum
|
||||
|
||||
The community is located on https://forum.froxlor.org/
|
||||
|
||||
### Documentation
|
||||
### Wiki
|
||||
|
||||
The documentation may be found at https://docs.froxlor.org/
|
||||
More documentation may be found in the froxlor - documentation:
|
||||
https://docs.froxlor.org/
|
||||
|
||||
## License
|
||||
|
||||
|
||||
13
SECURITY.md
13
SECURITY.md
@@ -10,10 +10,9 @@ With that, good luck hacking us ;)
|
||||
|
||||
## Supported versions
|
||||
|
||||
- ️✅ **2.1.x** (`main` git-branch)
|
||||
- ❌ 2.0.x (`2.0.x`-tags)
|
||||
- ❌ 0.10.x (`0.10.x`-tags)
|
||||
- ❌ other git-branches
|
||||
- ️✅ **2.x** (`main` git-branch)
|
||||
- ❌ 0.10.x (`0.10.x` git-branch)
|
||||
- ❌ 0.9.x (`0.9.x`git-branch)
|
||||
|
||||
## Qualifying Vulnerabilities
|
||||
|
||||
@@ -27,7 +26,7 @@ With that, good luck hacking us ;)
|
||||
|
||||
### Vulnerabilities we accept
|
||||
|
||||
Only reproducible issues on a default/clean setup from the latest stable release of a supported version will be accepted.
|
||||
Only reproducable issues on a default/clean setup from the latest stable release of a supported version will be accepted.
|
||||
|
||||
## Non-Qualifying Vulnerabilities
|
||||
|
||||
@@ -35,8 +34,6 @@ Only reproducible issues on a default/clean setup from the latest stable release
|
||||
- Theoretical attacks without proof of exploitability
|
||||
- Attacks that are the result of a third party library should be reported to the library maintainers
|
||||
- Social engineering
|
||||
- Attacks that require disabling security features or reducing the security level of the environment
|
||||
- Exploits by an admin user itself (privileged user and implicitly trusted)
|
||||
- Reflected file download
|
||||
- Physical attacks
|
||||
- Weak SSL/TLS/SSH algorithms or protocols
|
||||
@@ -47,4 +44,4 @@ Only reproducible issues on a default/clean setup from the latest stable release
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you think you have found a vulnerability in froxlor, please head over to [https://github.com/Froxlor/Froxlor/security/advisories](https://github.com/Froxlor/Froxlor/security/advisories/new) and use the reporting possibilities there. Also, please give us appropriate time to fix the issue and build update-packages before publishing anything into the wild. Alternatively you can email us to [team@froxlor.org](team@froxlor.org).
|
||||
If you think you have found a vulnerability in froxlor, please head over to [https://huntr.dev/repos/froxlor/froxlor](https://huntr.dev/repos/froxlor/froxlor) and use the reporting possibilities there as we are funding the prize-pot for froxlor on this platform. Also, please give us appropriate time to fix the issue and build update-packages before publishing anything into the wild. Alternatively you can send us an email to [team@froxlor.org](team@froxlor.org).
|
||||
|
||||
@@ -337,15 +337,7 @@ return [
|
||||
'image_name' => 'logo_login',
|
||||
'default' => '',
|
||||
'save_method' => 'storeSettingImage'
|
||||
],
|
||||
'panel_menu_collapsed' => [
|
||||
'label' => lng('serversettings.panel_menu_collapsed'),
|
||||
'settinggroup' => 'panel',
|
||||
'varname' => 'menu_collapsed',
|
||||
'type' => 'checkbox',
|
||||
'default' => true,
|
||||
'save_method' => 'storeSettingField',
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
@@ -130,8 +130,7 @@ return [
|
||||
'default' => 'stable',
|
||||
'select_var' => [
|
||||
'stable' => lng('serversettings.uc_stable'),
|
||||
'testing' => lng('serversettings.uc_testing'),
|
||||
'nightly' => lng('serversettings.uc_nightly')
|
||||
'testing' => lng('serversettings.uc_testing')
|
||||
],
|
||||
'save_method' => 'storeSettingField',
|
||||
'advanced_mode' => true
|
||||
@@ -172,6 +171,16 @@ return [
|
||||
'default' => false,
|
||||
'save_method' => 'storeSettingField'
|
||||
],
|
||||
'system_index_file_extension' => [
|
||||
'label' => lng('serversettings.index_file_extension'),
|
||||
'settinggroup' => 'system',
|
||||
'varname' => 'index_file_extension',
|
||||
'type' => 'text',
|
||||
'string_regexp' => '/^[a-zA-Z0-9]{1,6}$/',
|
||||
'default' => 'html',
|
||||
'save_method' => 'storeSettingField',
|
||||
'advanced_mode' => true
|
||||
],
|
||||
'system_store_index_file_subs' => [
|
||||
'label' => lng('serversettings.system_store_index_file_subs'),
|
||||
'settinggroup' => 'system',
|
||||
|
||||
@@ -43,8 +43,7 @@ return [
|
||||
'settinggroup' => 'spf',
|
||||
'varname' => 'spf_entry',
|
||||
'type' => 'text',
|
||||
'string_regexp' => '/^v=spf[a-z0-9:~?\s.-]+$/i',
|
||||
'default' => 'v=spf1 a mx -all',
|
||||
'default' => '"v=spf1 a mx -all"',
|
||||
'save_method' => 'storeSettingField'
|
||||
]
|
||||
]
|
||||
|
||||
87
actions/admin/settings/230.backup.php
Normal file
87
actions/admin/settings/230.backup.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?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
|
||||
*/
|
||||
|
||||
return [
|
||||
'groups' => [
|
||||
'backup' => [
|
||||
'title' => lng('backup'),
|
||||
'icon' => 'fa-solid fa-sliders',
|
||||
'advanced_mode' => true,
|
||||
'fields' => [
|
||||
'backup_enabled' => [
|
||||
'label' => lng('serversettings.backup_enabled'),
|
||||
'settinggroup' => 'backup',
|
||||
'varname' => 'enabled',
|
||||
'type' => 'checkbox',
|
||||
'default' => false,
|
||||
'save_method' => 'storeSettingField',
|
||||
'overview_option' => true,
|
||||
'cronmodule' => 'froxlor/backup'
|
||||
],
|
||||
'backup_default_storage' => [
|
||||
'label' => lng('serversettings.backup_default_storage'),
|
||||
'settinggroup' => 'backup',
|
||||
'varname' => 'default_storage',
|
||||
'type' => 'select',
|
||||
'default' => '1',
|
||||
'option_options_method' => [
|
||||
'\\Froxlor\\Backup\\Backup',
|
||||
'getBackupStorages'
|
||||
],
|
||||
'save_method' => 'storeSettingField'
|
||||
],
|
||||
'backup_default_retention' => [
|
||||
'label' => lng('serversettings.backup_default_retention'),
|
||||
'settinggroup' => 'backup',
|
||||
'varname' => 'default_retention',
|
||||
'type' => 'number',
|
||||
'default' => 3,
|
||||
'min' => 0,
|
||||
'save_method' => 'storeSettingField',
|
||||
],
|
||||
'backup_default_customer_access' => [
|
||||
'label' => lng('serversettings.backup_default_customer_access'),
|
||||
'settinggroup' => 'backup',
|
||||
'varname' => 'default_customer_access',
|
||||
'type' => 'checkbox',
|
||||
'default' => true,
|
||||
'save_method' => 'storeSettingField',
|
||||
],
|
||||
'backup_default_pgp_public_key' => [
|
||||
'label' => lng('serversettings.backup_default_pgp_public_key'),
|
||||
'settinggroup' => 'backup',
|
||||
'varname' => 'default_pgp_public_key',
|
||||
'type' => 'textarea',
|
||||
'default' => '',
|
||||
'save_method' => 'storeSettingField',
|
||||
'plausibility_check_method' => [
|
||||
'\\Froxlor\\Validate\\Check',
|
||||
'checkPgpPublicKeySetting'
|
||||
],
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
183
admin_backups.php
Normal file
183
admin_backups.php
Normal file
@@ -0,0 +1,183 @@
|
||||
<?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
|
||||
*/
|
||||
|
||||
const AREA = 'admin';
|
||||
require __DIR__ . '/lib/init.php';
|
||||
|
||||
use Froxlor\Api\Commands\Backups;
|
||||
use Froxlor\Api\Commands\BackupStorages;
|
||||
use Froxlor\FroxlorLogger;
|
||||
use Froxlor\UI\Collection;
|
||||
use Froxlor\UI\HTML;
|
||||
use Froxlor\UI\Listing;
|
||||
use Froxlor\UI\Panel\UI;
|
||||
use Froxlor\UI\Request;
|
||||
use Froxlor\UI\Response;
|
||||
|
||||
$id = (int)Request::any('id');
|
||||
|
||||
if (($page == 'backups' || $page == 'overview')) {
|
||||
if ($action == '') {
|
||||
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "viewed admin_backups");
|
||||
|
||||
try {
|
||||
$admin_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/admin/tablelisting.backups.php';
|
||||
$collection = (new Collection(Backups::class, $userinfo))
|
||||
->withPagination($admin_list_data['backups_list']['columns'], $admin_list_data['backups_list']['default_sorting']);
|
||||
} catch (Exception $e) {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
|
||||
UI::view('user/table.html.twig', [
|
||||
'listing' => Listing::format($collection, $admin_list_data, 'backups_list'),
|
||||
'actions_links' => [
|
||||
[
|
||||
'href' => $linker->getLink(['section' => 'backups', 'page' => $page, 'action' => 'restore']),
|
||||
'label' => lng('admin.backups_restore'),
|
||||
'icon' => 'fa-solid fa-file-import',
|
||||
'class' => 'btn-outline-secondary'
|
||||
],
|
||||
[
|
||||
'href' => $linker->getLink(['section' => 'backups', 'page' => 'storages']),
|
||||
'label' => lng('admin.backup_storages'),
|
||||
'icon' => 'fa-solid fa-hard-drive',
|
||||
'class' => 'btn-outline-secondary',
|
||||
'visible' => $userinfo['change_serversettings'] == '1'
|
||||
]
|
||||
]
|
||||
]);
|
||||
} elseif ($action == 'delete' && $id != 0) {
|
||||
|
||||
} elseif ($action == 'add') {
|
||||
|
||||
} elseif ($action == 'edit' && $id != 0) {
|
||||
|
||||
} elseif ($action == 'restore') {
|
||||
|
||||
}
|
||||
} else if ($page == 'storages' && $userinfo['change_serversettings'] == '1') {
|
||||
if ($action == '') {
|
||||
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "list backup storages");
|
||||
|
||||
try {
|
||||
$backup_storage_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/admin/tablelisting.backup_storages.php';
|
||||
$collection = (new Collection(BackupStorages::class, $userinfo))
|
||||
->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, $backup_storage_list_data, 'backup_storages_list'),
|
||||
'actions_links' => [
|
||||
[
|
||||
'href' => $linker->getLink(['section' => 'backups', 'page' => 'backups']),
|
||||
'label' => lng('admin.backups'),
|
||||
'icon' => 'fa-solid fa-reply'
|
||||
],
|
||||
[
|
||||
'href' => $linker->getLink(['section' => 'backups', 'page' => $page, 'action' => 'add']),
|
||||
'label' => lng('admin.backup_storage_add')
|
||||
]
|
||||
]
|
||||
]);
|
||||
} elseif ($action == 'delete' && $id != 0) {
|
||||
try {
|
||||
$json_result = BackupStorages::getLocal($userinfo, [
|
||||
'id' => $id
|
||||
])->get();
|
||||
} catch (Exception $e) {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
$result = json_decode($json_result, true)['data'];
|
||||
|
||||
if ($result['id'] != '') {
|
||||
if (isset($_POST['send']) && $_POST['send'] == 'send') {
|
||||
BackupStorages::getLocal($userinfo, [
|
||||
'id' => $id
|
||||
])->delete();
|
||||
Response::redirectTo($filename, [
|
||||
'page' => $page
|
||||
]);
|
||||
} else {
|
||||
HTML::askYesNo('backup_backup_server_reallydelete', $filename, [
|
||||
'id' => $id,
|
||||
'page' => $page,
|
||||
'action' => $action
|
||||
], $result['id']);
|
||||
}
|
||||
}
|
||||
} elseif ($action == 'add') {
|
||||
if (isset($_POST['send']) && $_POST['send'] == 'send') {
|
||||
try {
|
||||
BackupStorages::getLocal($userinfo, $_POST)->add();
|
||||
} catch (Exception $e) {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
Response::redirectTo($filename, [
|
||||
'page' => $page
|
||||
]);
|
||||
} else {
|
||||
$admin_add_data = include_once dirname(__FILE__) . '/lib/formfields/admin/backup_storages/formfield.backup_storage_add.php';
|
||||
|
||||
UI::view('user/form.html.twig', [
|
||||
'formaction' => $linker->getLink(['section' => 'backups']),
|
||||
'formdata' => $admin_add_data['backup_storage_add']
|
||||
]);
|
||||
}
|
||||
} elseif ($action == 'edit' && $id != 0) {
|
||||
try {
|
||||
$json_result = BackupStorages::getLocal($userinfo, [
|
||||
'id' => $id
|
||||
])->get();
|
||||
} catch (Exception $e) {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
$result = json_decode($json_result, true)['data'];
|
||||
|
||||
if ($result['id'] != '') {
|
||||
if (isset($_POST['send']) && $_POST['send'] == 'send') {
|
||||
try {
|
||||
BackupStorages::getLocal($userinfo, $_POST)->update();
|
||||
} catch (Exception $e) {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
Response::redirectTo($filename, [
|
||||
'page' => $page
|
||||
]);
|
||||
} else {
|
||||
$backup_storage_edit_data = include_once dirname(__FILE__) . '/lib/formfields/admin/backup_storages/formfield.backup_storage_edit.php';
|
||||
|
||||
UI::view('user/form.html.twig', [
|
||||
'formaction' => $linker->getLink(['section' => 'backups', 'id' => $id]),
|
||||
'formdata' => $backup_storage_edit_data['backup_storage_edit'],
|
||||
'editid' => $id
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Response::dynamicError('403');
|
||||
}
|
||||
@@ -60,9 +60,7 @@ if ($userinfo['change_serversettings'] == '1') {
|
||||
|
||||
if (!empty($distribution)) {
|
||||
if (!file_exists($config_dir . '/' . $distribution . ".xml")) {
|
||||
// unknown distribution -> redirect to select a valid distribution for config-templates
|
||||
Settings::Set('system.distribution', '');
|
||||
Response::redirectTo('admin_configfiles.php', ['reselect' => 1]);
|
||||
Response::dynamicError("Unknown distribution");
|
||||
}
|
||||
|
||||
// update setting if different
|
||||
|
||||
@@ -27,6 +27,7 @@ const AREA = 'admin';
|
||||
require __DIR__ . '/lib/init.php';
|
||||
|
||||
use Froxlor\Api\Commands\Admins;
|
||||
use Froxlor\Api\Commands\BackupStorages;
|
||||
use Froxlor\Api\Commands\Customers;
|
||||
use Froxlor\Api\Commands\MysqlServer;
|
||||
use Froxlor\CurrentUser;
|
||||
@@ -225,6 +226,23 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
|
||||
$hosting_plans[$row['id']] = $row['name'];
|
||||
}
|
||||
|
||||
// backup storages
|
||||
$backup_storages = [];
|
||||
if (Settings::Get('backup.enabled') == '1' && $userinfo['change_serversettings'] == '1') {
|
||||
$backup_storages = [
|
||||
0 => lng('backup.storage_none')
|
||||
];
|
||||
try {
|
||||
$result_json = BackupStorages::getLocal($userinfo)->listing();
|
||||
$result_decoded = json_decode($result_json, true)['data']['list'];
|
||||
foreach ($result_decoded as $storagedata) {
|
||||
$backup_storages[$storagedata['id']] = "[" . $storagedata['type'] . "] " . html_entity_decode($storagedata['description']);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
/* just none */
|
||||
}
|
||||
}
|
||||
|
||||
$customer_add_data = include_once dirname(__FILE__) . '/lib/formfields/admin/customer/formfield.customer_add.php';
|
||||
|
||||
UI::view('user/form.html.twig', [
|
||||
@@ -307,6 +325,23 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
|
||||
$hosting_plans[$row['id']] = $row['name'];
|
||||
}
|
||||
|
||||
// backup storages
|
||||
$backup_storages = [];
|
||||
if (Settings::Get('backup.enabled') == '1' && $userinfo['change_serversettings'] == '1') {
|
||||
$backup_storages = [
|
||||
0 => lng('backup.storage_none')
|
||||
];
|
||||
try {
|
||||
$result_json = BackupStorages::getLocal($userinfo)->listing();
|
||||
$result_decoded = json_decode($result_json, true)['data']['list'];
|
||||
foreach ($result_decoded as $storagedata) {
|
||||
$backup_storages[$storagedata['id']] = "[" . $storagedata['type'] . "] " . html_entity_decode($storagedata['description']);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
/* just none */
|
||||
}
|
||||
}
|
||||
|
||||
$available_admins_stmt = Database::prepare("
|
||||
SELECT * FROM `" . TABLE_PANEL_ADMINS . "`
|
||||
WHERE (`customers` = '-1' OR `customers` > `customers_used`)
|
||||
|
||||
@@ -30,9 +30,9 @@ use Froxlor\Api\Commands\Customers as Customers;
|
||||
use Froxlor\Api\Commands\Domains as Domains;
|
||||
use Froxlor\Bulk\DomainBulkAction;
|
||||
use Froxlor\Cron\TaskId;
|
||||
use Froxlor\CurrentUser;
|
||||
use Froxlor\Customer\Customer;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Domain\Domain;
|
||||
use Froxlor\FileDir;
|
||||
use Froxlor\FroxlorLogger;
|
||||
use Froxlor\Settings;
|
||||
@@ -45,6 +45,7 @@ use Froxlor\UI\Request;
|
||||
use Froxlor\UI\Response;
|
||||
use Froxlor\User;
|
||||
use Froxlor\Validate\Validate;
|
||||
use Froxlor\CurrentUser;
|
||||
|
||||
$id = (int)Request::any('id');
|
||||
|
||||
@@ -645,7 +646,7 @@ if ($page == 'domains' || $page == 'overview') {
|
||||
Response::redirectTo($filename, [
|
||||
'page' => $page,
|
||||
'searchfield' => 'd.domain_ace',
|
||||
'searchtext' => Request::post('domain', "")
|
||||
'searchtext' => $_POST['domain'] ?? ""
|
||||
]);
|
||||
} else {
|
||||
Response::redirectTo($filename, [
|
||||
|
||||
@@ -60,8 +60,7 @@ if (Settings::Get('panel.sendalternativemail') == 1) {
|
||||
}
|
||||
|
||||
$file_templates = [
|
||||
'index_html',
|
||||
'unconfigured_html'
|
||||
'index_html'
|
||||
];
|
||||
|
||||
$languages = Language::getLanguages();
|
||||
|
||||
@@ -24,8 +24,20 @@
|
||||
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
|
||||
*/
|
||||
|
||||
use Froxlor\Froxlor;
|
||||
declare(strict_types=1);
|
||||
|
||||
use Froxlor\Cli\ConfigDiff;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Froxlor\Cli\RunApiCommand;
|
||||
use Froxlor\Cli\ConfigServices;
|
||||
use Froxlor\Cli\PhpSessionclean;
|
||||
use Froxlor\Cli\SwitchServerIp;
|
||||
use Froxlor\Cli\UpdateCommand;
|
||||
use Froxlor\Cli\InstallCommand;
|
||||
use Froxlor\Cli\MasterCron;
|
||||
use Froxlor\Cli\UserCommand;
|
||||
use Froxlor\Cli\ValidateAcmeWebroot;
|
||||
use Froxlor\Froxlor;
|
||||
|
||||
// validate correct php version
|
||||
if (version_compare("7.4.0", PHP_VERSION, ">=")) {
|
||||
@@ -41,31 +53,14 @@ require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
require dirname(__DIR__) . '/lib/tables.inc.php';
|
||||
|
||||
$application = new Application('froxlor-cli', Froxlor::getFullVersion());
|
||||
|
||||
// files that are no commands
|
||||
$fileIgnoreList = [
|
||||
// Current non-command files
|
||||
'CliCommand.php',
|
||||
'index.html',
|
||||
'install.functions.php',
|
||||
];
|
||||
// directory of commands to include
|
||||
$cmd_files = glob(Froxlor::getInstallDir() . '/lib/Froxlor/Cli/*.php');
|
||||
|
||||
// include and add commands
|
||||
foreach ($cmd_files as $cmdFile) {
|
||||
// check ignore-list
|
||||
if (!in_array(basename($cmdFile), $fileIgnoreList)) {
|
||||
// include class-file
|
||||
require $cmdFile;
|
||||
// create class-name including namespace
|
||||
$cmdClass = "\\Froxlor\\Cli\\" . substr(basename($cmdFile), 0, -4);
|
||||
// check whether it exists
|
||||
if (class_exists($cmdClass) && is_subclass_of($cmdClass, '\Symfony\Component\Console\Command\Command')) {
|
||||
// add to cli application
|
||||
$application->add(new $cmdClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$application->add(new RunApiCommand());
|
||||
$application->add(new ConfigServices());
|
||||
$application->add(new PhpSessionclean());
|
||||
$application->add(new SwitchServerIp());
|
||||
$application->add(new UpdateCommand());
|
||||
$application->add(new InstallCommand());
|
||||
$application->add(new MasterCron());
|
||||
$application->add(new UserCommand());
|
||||
$application->add(new ValidateAcmeWebroot());
|
||||
$application->add(new ConfigDiff());
|
||||
$application->run();
|
||||
|
||||
@@ -46,17 +46,17 @@
|
||||
"ext-fileinfo": "*",
|
||||
"ext-gmp": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-gnupg": "*",
|
||||
"ext-ftp": "*",
|
||||
"phpmailer/phpmailer": "~6.0",
|
||||
"monolog/monolog": "^1.24",
|
||||
"robthree/twofactorauth": "^1.6",
|
||||
"froxlor/idna-convert-legacy": "^2.1",
|
||||
"voku/anti-xss": "^4.1",
|
||||
"twig/twig": "^3.3",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"symfony/console": "^5.4",
|
||||
"pear/net_dns2": "^1.5",
|
||||
"amnuts/opcache-gui": "^3.4",
|
||||
"league/commonmark": "^2.4"
|
||||
"amnuts/opcache-gui": "^3.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9",
|
||||
|
||||
812
composer.lock
generated
812
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,6 @@ const AREA = 'customer';
|
||||
require __DIR__ . '/lib/init.php';
|
||||
|
||||
use Froxlor\Api\Commands\SubDomains as SubDomains;
|
||||
use Froxlor\CurrentUser;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Domain\Domain;
|
||||
use Froxlor\FileDir;
|
||||
@@ -41,6 +40,7 @@ use Froxlor\UI\Panel\UI;
|
||||
use Froxlor\UI\Request;
|
||||
use Froxlor\UI\Response;
|
||||
use Froxlor\Validate\Validate;
|
||||
use Froxlor\CurrentUser;
|
||||
|
||||
// redirect if this customer page is hidden via settings
|
||||
if (Settings::IsInList('panel.customer_hide_options', 'domains')) {
|
||||
@@ -63,21 +63,16 @@ if ($page == 'overview' || $page == 'domains') {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
|
||||
$actions_links = [];
|
||||
$actions_links = false;
|
||||
if (CurrentUser::canAddResource('subdomains')) {
|
||||
$actions_links[] = [
|
||||
'href' => $linker->getLink(['section' => 'domains', 'page' => 'domains', 'action' => 'add']),
|
||||
'label' => lng('domains.subdomain_add')
|
||||
$actions_links = [
|
||||
[
|
||||
'href' => $linker->getLink(['section' => 'domains', 'page' => 'domains', 'action' => 'add']),
|
||||
'label' => lng('domains.subdomain_add')
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$actions_links[] = [
|
||||
'href' => \Froxlor\Froxlor::DOCS_URL . 'user-guide/domains/',
|
||||
'target' => '_blank',
|
||||
'icon' => 'fa-solid fa-circle-info',
|
||||
'class' => 'btn-outline-secondary'
|
||||
];
|
||||
|
||||
$table_tpl = 'table.html.twig';
|
||||
if ($collection->count() == 0) {
|
||||
$table_tpl = 'table-note.html.twig';
|
||||
@@ -244,7 +239,7 @@ if ($page == 'overview' || $page == 'domains') {
|
||||
|
||||
if (isset($result['customerid']) && $result['customerid'] == $userinfo['customerid']) {
|
||||
|
||||
if ((int)$result['caneditdomain'] == 0) {
|
||||
if ((int) $result['caneditdomain'] == 0) {
|
||||
Response::standardError('domaincannotbeedited', $result['domain']);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,10 +27,9 @@ const AREA = 'customer';
|
||||
require __DIR__ . '/lib/init.php';
|
||||
|
||||
use Froxlor\Api\Commands\EmailAccounts;
|
||||
use Froxlor\Api\Commands\EmailDomains;
|
||||
use Froxlor\Api\Commands\EmailForwarders;
|
||||
use Froxlor\Api\Commands\Emails;
|
||||
use Froxlor\CurrentUser;
|
||||
use Froxlor\Api\Commands\EmailDomains;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FroxlorLogger;
|
||||
use Froxlor\PhpHelper;
|
||||
@@ -42,6 +41,7 @@ use Froxlor\UI\Panel\UI;
|
||||
use Froxlor\UI\Request;
|
||||
use Froxlor\UI\Response;
|
||||
use Froxlor\Validate\Check;
|
||||
use Froxlor\CurrentUser;
|
||||
|
||||
// redirect if this customer page is hidden via settings
|
||||
if (Settings::IsInList('panel.customer_hide_options', 'email') || $userinfo['emails'] == 0) {
|
||||
@@ -67,24 +67,14 @@ if ($page == 'overview' || $page == 'emails') {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
|
||||
$actions_links = [];
|
||||
if (CurrentUser::canAddResource('emails')) {
|
||||
$actions_links[] = [
|
||||
'href' => $linker->getLink(['section' => 'email', 'page' => 'email_domain', 'action' => 'add']),
|
||||
'label' => lng('emails.emails_add')
|
||||
];
|
||||
}
|
||||
|
||||
$actions_links[] = [
|
||||
'href' => \Froxlor\Froxlor::DOCS_URL . 'user-guide/emails/',
|
||||
'target' => '_blank',
|
||||
'icon' => 'fa-solid fa-circle-info',
|
||||
'class' => 'btn-outline-secondary'
|
||||
];
|
||||
|
||||
UI::view('user/table.html.twig', [
|
||||
'listing' => Listing::format($collection, $emaildomain_list_data, 'emaildomain_list'),
|
||||
'actions_links' => $actions_links,
|
||||
'actions_links' => CurrentUser::canAddResource('emails') ? [
|
||||
[
|
||||
'href' => $linker->getLink(['section' => 'email', 'page' => 'email_domain', 'action' => 'add']),
|
||||
'label' => lng('emails.emails_add')
|
||||
]
|
||||
] : null,
|
||||
]);
|
||||
} else {
|
||||
// only emails for one domain -> show email address listing directly
|
||||
@@ -137,12 +127,6 @@ if ($page == 'email_domain') {
|
||||
'label' => lng('emails.emails_add')
|
||||
];
|
||||
}
|
||||
$actions_links[] = [
|
||||
'href' => \Froxlor\Froxlor::DOCS_URL . 'user-guide/emails/',
|
||||
'target' => '_blank',
|
||||
'icon' => 'fa-solid fa-circle-info',
|
||||
'class' => 'btn-outline-secondary'
|
||||
];
|
||||
|
||||
UI::view('user/table.html.twig', [
|
||||
'listing' => Listing::format($collection, $email_list_data, 'email_list'),
|
||||
|
||||
@@ -68,22 +68,14 @@ if ($page == 'overview' || $page == 'htpasswds') {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
|
||||
$actions_links = [];
|
||||
$actions_links[] = [
|
||||
'href' => $linker->getLink(['section' => 'extras', 'page' => 'htpasswds', 'action' => 'add']),
|
||||
'label' => lng('extras.directoryprotection_add')
|
||||
];
|
||||
|
||||
$actions_links[] = [
|
||||
'href' => \Froxlor\Froxlor::DOCS_URL . 'user-guide/extras/',
|
||||
'target' => '_blank',
|
||||
'icon' => 'fa-solid fa-circle-info',
|
||||
'class' => 'btn-outline-secondary'
|
||||
];
|
||||
|
||||
UI::view('user/table.html.twig', [
|
||||
'listing' => Listing::format($collection, $htpasswd_list_data, 'htpasswd_list'),
|
||||
'actions_links' => $actions_links,
|
||||
'actions_links' => [
|
||||
[
|
||||
'href' => $linker->getLink(['section' => 'extras', 'page' => 'htpasswds', 'action' => 'add']),
|
||||
'label' => lng('extras.directoryprotection_add')
|
||||
]
|
||||
],
|
||||
'entity_info' => lng('extras.description')
|
||||
]);
|
||||
} elseif ($action == 'delete' && $id != 0) {
|
||||
@@ -193,22 +185,14 @@ if ($page == 'overview' || $page == 'htpasswds') {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
|
||||
$actions_links = [];
|
||||
$actions_links[] = [
|
||||
'href' => $linker->getLink(['section' => 'extras', 'page' => 'htaccess', 'action' => 'add']),
|
||||
'label' => lng('extras.pathoptions_add')
|
||||
];
|
||||
|
||||
$actions_links[] = [
|
||||
'href' => \Froxlor\Froxlor::DOCS_URL . 'user-guide/extras/',
|
||||
'target' => '_blank',
|
||||
'icon' => 'fa-solid fa-circle-info',
|
||||
'class' => 'btn-outline-secondary'
|
||||
];
|
||||
|
||||
UI::view('user/table.html.twig', [
|
||||
'listing' => Listing::format($collection, $htaccess_list_data, 'htaccess_list'),
|
||||
'actions_links' => $actions_links,
|
||||
'actions_links' => [
|
||||
[
|
||||
'href' => $linker->getLink(['section' => 'extras', 'page' => 'htaccess', 'action' => 'add']),
|
||||
'label' => lng('extras.pathoptions_add')
|
||||
]
|
||||
],
|
||||
'entity_info' => lng('extras.description')
|
||||
]);
|
||||
} elseif ($action == 'delete' && $id != 0) {
|
||||
@@ -347,19 +331,9 @@ if ($page == 'overview' || $page == 'htpasswds') {
|
||||
$pathSelect = FileDir::makePathfield($userinfo['documentroot'], $userinfo['guid'], $userinfo['guid']);
|
||||
$export_data = include_once dirname(__FILE__) . '/lib/formfields/customer/extras/formfield.export.php';
|
||||
|
||||
$actions_links = [
|
||||
[
|
||||
'href' => \Froxlor\Froxlor::DOCS_URL . 'user-guide/extras/',
|
||||
'target' => '_blank',
|
||||
'icon' => 'fa-solid fa-circle-info',
|
||||
'class' => 'btn-outline-secondary'
|
||||
]
|
||||
];
|
||||
|
||||
UI::view('user/form-datatable.html.twig', [
|
||||
'formaction' => $linker->getLink(['section' => 'extras']),
|
||||
'formdata' => $export_data['export'],
|
||||
'actions_links' => $actions_links,
|
||||
'tabledata' => Listing::format($collection, $export_list_data, 'export_list'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ const AREA = 'customer';
|
||||
require __DIR__ . '/lib/init.php';
|
||||
|
||||
use Froxlor\Api\Commands\Ftps as Ftps;
|
||||
use Froxlor\CurrentUser;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FileDir;
|
||||
use Froxlor\FroxlorLogger;
|
||||
@@ -38,6 +37,7 @@ use Froxlor\UI\Listing;
|
||||
use Froxlor\UI\Panel\UI;
|
||||
use Froxlor\UI\Request;
|
||||
use Froxlor\UI\Response;
|
||||
use Froxlor\CurrentUser;
|
||||
|
||||
// redirect if this customer page is hidden via settings
|
||||
if (Settings::IsInList('panel.customer_hide_options', 'ftp')) {
|
||||
@@ -57,19 +57,15 @@ if ($page == 'overview' || $page == 'accounts') {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
|
||||
$actions_links = [];
|
||||
$actions_links = false;
|
||||
if (CurrentUser::canAddResource('ftps')) {
|
||||
$actions_links[] = [
|
||||
'href' => $linker->getLink(['section' => 'ftp', 'page' => 'accounts', 'action' => 'add']),
|
||||
'label' => lng('ftp.account_add')
|
||||
$actions_links = [
|
||||
[
|
||||
'href' => $linker->getLink(['section' => 'ftp', 'page' => 'accounts', 'action' => 'add']),
|
||||
'label' => lng('ftp.account_add')
|
||||
]
|
||||
];
|
||||
}
|
||||
$actions_links[] = [
|
||||
'href' => \Froxlor\Froxlor::DOCS_URL . 'user-guide/ftp-accounts/',
|
||||
'target' => '_blank',
|
||||
'icon' => 'fa-solid fa-circle-info',
|
||||
'class' => 'btn-outline-secondary'
|
||||
];
|
||||
|
||||
UI::view('user/table.html.twig', [
|
||||
'listing' => Listing::format($collection, $ftp_list_data, 'ftp_list'),
|
||||
|
||||
@@ -115,8 +115,8 @@ if ($page == 'overview') {
|
||||
$userinfo['traffic_bytes_used'] = $userinfo['traffic_used'] * 1024;
|
||||
|
||||
if (Settings::Get('system.mail_quota_enabled')) {
|
||||
$userinfo['email_quota_bytes'] = ($userinfo['email_quota'] > -1) ? $userinfo['email_quota'] * 1024 * 1024 : -1;
|
||||
$userinfo['email_quota_bytes_used'] = $userinfo['email_quota_used'] * 1024 * 1024;
|
||||
$userinfo['email_quota_bytes'] = ($userinfo['email_quota'] > -1) ? $userinfo['email_quota'] * 1024 : -1;
|
||||
$userinfo['email_quota_bytes_used'] = $userinfo['email_quota_used'] * 1024;
|
||||
}
|
||||
|
||||
if ($usages) {
|
||||
|
||||
@@ -28,7 +28,6 @@ require __DIR__ . '/lib/init.php';
|
||||
|
||||
use Froxlor\Api\Commands\Mysqls;
|
||||
use Froxlor\Api\Commands\MysqlServer;
|
||||
use Froxlor\CurrentUser;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FroxlorLogger;
|
||||
use Froxlor\Settings;
|
||||
@@ -38,6 +37,7 @@ use Froxlor\UI\Listing;
|
||||
use Froxlor\UI\Panel\UI;
|
||||
use Froxlor\UI\Request;
|
||||
use Froxlor\UI\Response;
|
||||
use Froxlor\CurrentUser;
|
||||
|
||||
// redirect if this customer page is hidden via settings or no resources given
|
||||
if (Settings::IsInList('panel.customer_hide_options', 'mysql') || $userinfo['mysqls'] == 0) {
|
||||
@@ -66,21 +66,16 @@ if ($page == 'overview' || $page == 'mysqls') {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
|
||||
$actions_links = [];
|
||||
$actions_links = false;
|
||||
if (CurrentUser::canAddResource('mysqls')) {
|
||||
$actions_links[] = [
|
||||
'href' => $linker->getLink(['section' => 'mysql', 'page' => 'mysqls', 'action' => 'add']),
|
||||
'label' => lng('mysql.database_create')
|
||||
$actions_links = [
|
||||
[
|
||||
'href' => $linker->getLink(['section' => 'mysql', 'page' => 'mysqls', 'action' => 'add']),
|
||||
'label' => lng('mysql.database_create')
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$actions_links[] = [
|
||||
'href' => \Froxlor\Froxlor::DOCS_URL . 'user-guide/databases/',
|
||||
'target' => '_blank',
|
||||
'icon' => 'fa-solid fa-circle-info',
|
||||
'class' => 'btn-outline-secondary'
|
||||
];
|
||||
|
||||
UI::view('user/table.html.twig', [
|
||||
'listing' => Listing::format($collection, $mysql_list_data, 'mysql_list'),
|
||||
'actions_links' => $actions_links,
|
||||
@@ -184,7 +179,7 @@ if ($page == 'overview' || $page == 'mysqls') {
|
||||
$result_json = MysqlServer::getLocal($userinfo)->listing();
|
||||
$result_decoded = json_decode($result_json, true)['data']['list'];
|
||||
foreach ($result_decoded as $dbserver => $dbdata) {
|
||||
$mysql_servers[$dbserver] = $dbdata['caption'] . ' (' . $dbdata['host'] . (isset($dbdata['port']) && !empty($dbdata['port']) ? ':' . $dbdata['port'] : '') . ')';
|
||||
$mysql_servers[$dbserver] = $dbdata['caption'] . ' (' . $dbdata['host'] . (isset($dbdata['port']) && !empty($dbdata['port']) ? ':' . $dbdata['port'] : '').')';
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
/* just none */
|
||||
|
||||
24
index.php
24
index.php
@@ -74,26 +74,27 @@ if ($action == '2fa_entercode') {
|
||||
$code = isset($_POST['2fa_code']) ? $_POST['2fa_code'] : null;
|
||||
// verify entered code
|
||||
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
|
||||
$result = ($_SESSION['secret_2fa'] == 'email' ? true : $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3));
|
||||
// get user-data
|
||||
$table = $_SESSION['uidtable_2fa'];
|
||||
$field = $_SESSION['uidfield_2fa'];
|
||||
$uid = $_SESSION['uid_2fa'];
|
||||
$isadmin = $_SESSION['unfo_2fa'];
|
||||
if ($_SESSION['secret_2fa'] == 'email') {
|
||||
// verify code set to user's data_2fa field
|
||||
$sel_stmt = Database::prepare("SELECT `data_2fa` FROM " . $table . " WHERE `" . $field . "` = :uid");
|
||||
$userinfo_code = Database::pexecute_first($sel_stmt, ['uid' => $uid]);
|
||||
$result = $tfa->verifyCode($userinfo_code['data_2fa'], $code);
|
||||
} else {
|
||||
$result = $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3);
|
||||
}
|
||||
// either the code is valid when using authenticator-app, or we will select userdata by id and entered code
|
||||
// which is temporarily stored for the customer when using email-2fa
|
||||
if ($result) {
|
||||
$sel_param = [
|
||||
'uid' => $uid
|
||||
];
|
||||
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid");
|
||||
if ($_SESSION['secret_2fa'] == 'email') {
|
||||
// verify code by selecting user by id and the temp. stored code,
|
||||
// so only if it's the correct code, we get the user-data
|
||||
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid AND `data_2fa` = :code");
|
||||
$sel_param['code'] = $code;
|
||||
} else {
|
||||
// Authenticator-verification has already happened at this point, so just get the user-data
|
||||
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid");
|
||||
}
|
||||
$userinfo = Database::pexecute_first($sel_stmt, $sel_param);
|
||||
// whoops, no (valid) user? Start again
|
||||
if (empty($userinfo)) {
|
||||
@@ -326,12 +327,11 @@ if ($action == '2fa_entercode') {
|
||||
if ($userinfo['type_2fa'] == 1) {
|
||||
// generate code
|
||||
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
|
||||
$secret = $tfa->createSecret();
|
||||
$code = $tfa->getCode($secret);
|
||||
$code = $tfa->getCode($tfa->createSecret());
|
||||
// set code for user
|
||||
$stmt = Database::prepare("UPDATE $table SET `data_2fa` = :d2fa WHERE `$uid` = :uid");
|
||||
Database::pexecute($stmt, [
|
||||
"d2fa" => $secret,
|
||||
"d2fa" => $code,
|
||||
"uid" => $userinfo[$uid]
|
||||
]);
|
||||
// build up & send email
|
||||
|
||||
@@ -157,7 +157,7 @@ CREATE TABLE `panel_admins` (
|
||||
`api_allowed` tinyint(1) NOT NULL default '1',
|
||||
PRIMARY KEY (`adminid`),
|
||||
UNIQUE KEY `loginname` (`loginname`)
|
||||
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC;
|
||||
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `panel_customers`;
|
||||
@@ -223,6 +223,8 @@ CREATE TABLE `panel_customers` (
|
||||
`api_allowed` tinyint(1) NOT NULL default '1',
|
||||
`logviewenabled` tinyint(1) NOT NULL default '0',
|
||||
`allowed_mysqlserver` text NOT NULL,
|
||||
`backup` int(11) NOT NULL default '1',
|
||||
`access_backups` tinyint(1) NOT NULL default '1',
|
||||
PRIMARY KEY (`customerid`),
|
||||
UNIQUE KEY `loginname` (`loginname`)
|
||||
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC;
|
||||
@@ -299,7 +301,7 @@ CREATE TABLE `panel_domains` (
|
||||
KEY `customerid` (`customerid`),
|
||||
KEY `parentdomain` (`parentdomainid`),
|
||||
KEY `domain` (`domain`)
|
||||
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC;
|
||||
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `panel_ipsandports`;
|
||||
@@ -356,6 +358,23 @@ CREATE TABLE `panel_htpasswds` (
|
||||
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `panel_sessions`;
|
||||
CREATE TABLE `panel_sessions` (
|
||||
`hash` varchar(32) NOT NULL default '',
|
||||
`userid` int(11) unsigned NOT NULL default '0',
|
||||
`ipaddress` varchar(255) NOT NULL default '',
|
||||
`useragent` varchar(255) NOT NULL default '',
|
||||
`lastactivity` int(11) unsigned NOT NULL default '0',
|
||||
`lastpaging` varchar(255) NOT NULL default '',
|
||||
`formtoken` char(32) NOT NULL default '',
|
||||
`language` varchar(64) NOT NULL default '',
|
||||
`adminsession` tinyint(1) unsigned NOT NULL default '0',
|
||||
`theme` varchar(255) NOT NULL default '',
|
||||
PRIMARY KEY (`hash`),
|
||||
KEY `userid` (`userid`)
|
||||
) ENGINE=HEAP;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `panel_settings`;
|
||||
CREATE TABLE `panel_settings` (
|
||||
`settingid` int(11) unsigned NOT NULL auto_increment,
|
||||
@@ -391,7 +410,7 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES
|
||||
('admin', 'show_version_footer', '0'),
|
||||
('caa', 'caa_entry', ''),
|
||||
('spf', 'use_spf', '0'),
|
||||
('spf', 'spf_entry', 'v=spf1 a mx -all'),
|
||||
('spf', 'spf_entry', '"v=spf1 a mx -all"'),
|
||||
('dkim', 'dkim_algorithm', 'all'),
|
||||
('dkim', 'dkim_keylength', '1024'),
|
||||
('dkim', 'dkim_servicetype', '0'),
|
||||
@@ -545,7 +564,7 @@ opcache.validate_timestamps'),
|
||||
('system', 'mod_fcgid', '0'),
|
||||
('system', 'apacheconf_vhost', '/etc/apache2/sites-enabled/'),
|
||||
('system', 'apacheconf_diroptions', '/etc/apache2/sites-enabled/'),
|
||||
('system', 'apacheconf_htpasswddir', '/etc/apache2/froxlor-htpasswd/'),
|
||||
('system', 'apacheconf_htpasswddir', '/etc/apache2/htpasswd/'),
|
||||
('system', 'webalizer_quiet', '2'),
|
||||
('system', 'last_archive_run', '000000'),
|
||||
('system', 'mod_fcgid_configdir', '/var/www/php-fcgi-scripts'),
|
||||
@@ -562,6 +581,7 @@ opcache.validate_timestamps'),
|
||||
('system', 'mod_fcgid_wrapper', '1'),
|
||||
('system', 'mod_fcgid_starter', '0'),
|
||||
('system', 'mod_fcgid_peardir', '/usr/share/php/:/usr/share/php5/'),
|
||||
('system', 'index_file_extension', 'html'),
|
||||
('system', 'mod_fcgid_maxrequests', '250'),
|
||||
('system', 'ssl_key_file','/etc/ssl/froxlor_selfsigned.key'),
|
||||
('system', 'ssl_ca_file', ''),
|
||||
@@ -678,10 +698,15 @@ opcache.validate_timestamps'),
|
||||
('system', 'distribution', ''),
|
||||
('system', 'update_channel', 'stable'),
|
||||
('system', 'updatecheck_data', ''),
|
||||
('system', 'update_notify_last', ''),
|
||||
('system', 'update_notify_last', '2.0.20'),
|
||||
('system', 'traffictool', 'goaccess'),
|
||||
('system', 'req_limit_per_interval', 60),
|
||||
('system', 'req_limit_interval', 60),
|
||||
('backup', 'enabled', 0),
|
||||
('backup', 'default_storage', '1'),
|
||||
('backup', 'default_customer_access', '1'),
|
||||
('backup', 'default_pgp_public_key', ''),
|
||||
('backup', 'default_retention', '3'),
|
||||
('api', 'enabled', '0'),
|
||||
('api', 'customer_default', '1'),
|
||||
('2fa', 'enabled', '1'),
|
||||
@@ -725,9 +750,8 @@ opcache.validate_timestamps'),
|
||||
('panel', 'logo_overridetheme', '0'),
|
||||
('panel', 'logo_overridecustom', '0'),
|
||||
('panel', 'settings_mode', '0'),
|
||||
('panel', 'menu_collapsed', '1'),
|
||||
('panel', 'version', '2.1.2'),
|
||||
('panel', 'db_version', '202312120');
|
||||
('panel', 'version', '2.0.20'),
|
||||
('panel', 'db_version', '202305240');
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `panel_tasks`;
|
||||
@@ -750,7 +774,6 @@ CREATE TABLE `panel_templates` (
|
||||
`templategroup` varchar(255) NOT NULL default '',
|
||||
`varname` varchar(255) NOT NULL default '',
|
||||
`value` longtext NOT NULL,
|
||||
`file_extension` varchar(50) NOT NULL default 'html',
|
||||
PRIMARY KEY (id),
|
||||
KEY adminid (adminid)
|
||||
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
@@ -897,7 +920,8 @@ INSERT INTO `cronjobs_run` (`id`, `module`, `cronfile`, `cronclass`, `interval`,
|
||||
(3, 'froxlor/reports', 'usage_report', '\\Froxlor\\Cron\\Traffic\\ReportsCron', '1 DAY', '1', 'cron_usage_report'),
|
||||
(4, 'froxlor/core', 'mailboxsize', '\\Froxlor\\Cron\\System\\MailboxsizeCron', '6 HOUR', '1', 'cron_mailboxsize'),
|
||||
(5, 'froxlor/letsencrypt', 'letsencrypt', '\\Froxlor\\Cron\\Http\\LetsEncrypt\\AcmeSh', '5 MINUTE', '0', 'cron_letsencrypt'),
|
||||
(6, 'froxlor/export', 'export', '\\Froxlor\\Cron\\System\\ExportCron', '1 HOUR', '0', 'cron_export');
|
||||
(6, 'froxlor/export', 'export', '\\Froxlor\\Cron\\System\\ExportCron', '1 HOUR', '0', 'cron_export'),
|
||||
(7, 'froxlor/backup', 'backup', '\\Froxlor\\Cron\\Backup\\BackupCron', '1 DAY', '0', 'cron_backup');
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `ftp_quotalimits`;
|
||||
@@ -1045,4 +1069,38 @@ CREATE TABLE `panel_loginlinks` (
|
||||
`allowed_from` text NOT NULL,
|
||||
UNIQUE KEY `loginname` (`loginname`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `panel_backup_storages`;
|
||||
CREATE TABLE `panel_backup_storages` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`description` varchar(255) NOT NULL,
|
||||
`type` varchar(255) NOT NULL DEFAULT 'local',
|
||||
`region` varchar(255) NULL,
|
||||
`bucket` varchar(255) NULL,
|
||||
`destination_path` varchar(255) NOT NULL,
|
||||
`hostname` varchar(255) NULL,
|
||||
`username` varchar(255) NULL,
|
||||
`password` text,
|
||||
`pgp_public_key` text,
|
||||
`retention` int(3) NOT NULL DEFAULT 3,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
INSERT INTO `panel_backup_storages` (`id`, `description`, `destination_path`) VALUES
|
||||
(1, 'Local backup storage', '/var/customers/backups');
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `panel_backups`;
|
||||
CREATE TABLE `panel_backups` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`adminid` int(11) NOT NULL,
|
||||
`customerid` int(11) NOT NULL,
|
||||
`loginname` varchar(255) NOT NULL,
|
||||
`size` bigint(20) NOT NULL,
|
||||
`storage_id` int(11) NOT NULL,
|
||||
`filename` varchar(255) NOT NULL,
|
||||
`created_at` int(15) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
FROXLORSQL;
|
||||
|
||||
@@ -149,7 +149,7 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
|
||||
Update::showUpdateStep("Adding new settings");
|
||||
$panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int)$_POST['panel_settings_mode'] : 0;
|
||||
Settings::AddNew("panel.settings_mode", $panel_settings_mode);
|
||||
$system_distribution = isset($_POST['system_distribution']) ? $_POST['system_distribution'] : 'bullseye';
|
||||
$system_distribution = isset($_POST['system_distribution']) ? $_POST['system_distribution'] : '';
|
||||
Settings::AddNew("system.distribution", $system_distribution);
|
||||
Settings::AddNew("system.update_channel", 'stable');
|
||||
Settings::AddNew("system.updatecheck_data", '');
|
||||
@@ -497,23 +497,3 @@ if (Froxlor::isFroxlorVersion('2.0.19')) {
|
||||
Update::showUpdateStep("Updating from 2.0.19 to 2.0.20", false);
|
||||
Froxlor::updateToVersion('2.0.20');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.0.20')) {
|
||||
Update::showUpdateStep("Updating from 2.0.20 to 2.0.21", false);
|
||||
Froxlor::updateToVersion('2.0.21');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.0.21')) {
|
||||
Update::showUpdateStep("Updating from 2.0.21 to 2.0.22", false);
|
||||
Froxlor::updateToVersion('2.0.22');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.0.22')) {
|
||||
Update::showUpdateStep("Updating from 2.0.22 to 2.0.23", false);
|
||||
Froxlor::updateToVersion('2.0.23');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.0.23')) {
|
||||
Update::showUpdateStep("Updating from 2.0.23 to 2.0.24", false);
|
||||
Froxlor::updateToVersion('2.0.24');
|
||||
}
|
||||
|
||||
@@ -36,10 +36,9 @@ if (!defined('_CRON_UPDATE')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.0.24')) {
|
||||
Update::showUpdateStep("Cleaning domains table");
|
||||
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` ROW_FORMAT=DYNAMIC;");
|
||||
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` DROP COLUMN `ismainbutsubto`;");
|
||||
if (Froxlor::isDatabaseVersion('202304260')) {
|
||||
//Update::showUpdateStep("Cleaning domains table");
|
||||
//Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` DROP COLUMN `ismainbutsubto`;");
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Update::showUpdateStep("Creating new tables and fields");
|
||||
@@ -54,10 +53,6 @@ if (Froxlor::isFroxlorVersion('2.0.24')) {
|
||||
Database::query($sql);
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Update::showUpdateStep("Adding new settings");
|
||||
Settings::AddNew('panel.menu_collapsed', 1);
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Update::showUpdateStep("Adjusting setting for deactivated webroot");
|
||||
$current_deactivated_webroot = Settings::Get('system.deactivateddocroot');
|
||||
if (empty($current_deactivated_webroot)) {
|
||||
@@ -67,205 +62,80 @@ if (Froxlor::isFroxlorVersion('2.0.24')) {
|
||||
Update::lastStepStatus(1, 'Customized setting, not changing');
|
||||
}
|
||||
|
||||
Update::showUpdateStep("Creating new tables and fields for backups");
|
||||
Database::query("DROP TABLE IF EXISTS `". TABLE_PANEL_BACKUP_STORAGES ."`;");
|
||||
$sql = "CREATE TABLE `". TABLE_PANEL_BACKUP_STORAGES ."` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`description` varchar(255) NOT NULL,
|
||||
`type` varchar(255) NOT NULL DEFAULT 'local',
|
||||
`region` varchar(255) NULL,
|
||||
`bucket` varchar(255) NULL,
|
||||
`destination_path` varchar(255) NOT NULL,
|
||||
`hostname` varchar(255) NULL,
|
||||
`username` varchar(255) NULL,
|
||||
`password` text,
|
||||
`pgp_public_key` text,
|
||||
`retention` int(3) NOT NULL DEFAULT 3,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
|
||||
Database::query($sql);
|
||||
Database::query("
|
||||
INSERT INTO `panel_backup_storages` (`id`, `description`, `destination_path`) VALUES
|
||||
(1, 'Local backup storage', '/var/customers/backups');
|
||||
");
|
||||
Database::query("DROP TABLE IF EXISTS `". TABLE_PANEL_BACKUPS ."`;");
|
||||
$sql = "CREATE TABLE `". TABLE_PANEL_BACKUPS ."` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`adminid` int(11) NOT NULL,
|
||||
`customerid` int(11) NOT NULL,
|
||||
`loginname` varchar(255) NOT NULL,
|
||||
`size` bigint(20) NOT NULL,
|
||||
`storage_id` int(11) NOT NULL,
|
||||
`filename` varchar(255) NOT NULL,
|
||||
`created_at` int(15) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
|
||||
Database::query($sql);
|
||||
// add customer backup-target-storage
|
||||
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` ADD `backup` int(11) NOT NULL default '1' AFTER `allowed_mysqlserver`;");
|
||||
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` ADD `access_backups` tinyint(1) NOT NULL default '1' AFTER `backup`;");
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Update::showUpdateStep("Adding new backup settings");
|
||||
Settings::AddNew('backup.enabled', 0);
|
||||
Settings::AddNew('backup.default_storage', 1);
|
||||
Settings::AddNew('backup.default_customer_access', 1);
|
||||
Settings::AddNew('backup.default_pgp_public_key', '');
|
||||
Settings::AddNew('backup.default_retention', 3);
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Update::showUpdateStep("Adjusting cronjobs");
|
||||
$cfupd_stmt = Database::prepare("
|
||||
Database::query("
|
||||
UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET
|
||||
`module`= 'froxlor/export',
|
||||
`cronfile` = 'export',
|
||||
`cronclass` = :cc,
|
||||
`cronclass` = '\\Froxlor\\Cron\\System\\ExportCron',
|
||||
`interval` = '1 HOUR',
|
||||
`desc_lng_key` = 'cron_export'
|
||||
WHERE `module` = 'froxlor/backup'
|
||||
");
|
||||
Database::pexecute($cfupd_stmt, [
|
||||
'cc' => '\\Froxlor\\Cron\\System\\ExportCron'
|
||||
]);
|
||||
Database::query("
|
||||
INSERT INTO `" . TABLE_PANEL_CRONRUNS . "` SET
|
||||
`module`= 'froxlor/backup',
|
||||
`cronfile` = 'backup',
|
||||
`cronclass` = '\\Froxlor\\Cron\\Backup\\BackupCron',
|
||||
`interval` = '1 DAY',
|
||||
`isactive` = '0',
|
||||
`desc_lng_key` = 'cron_backup'
|
||||
");
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Update::showUpdateStep("Adjusting system for data-export function");
|
||||
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "`SET `varname` = 'exportenabled' WHERE `settinggroup`= 'system' AND `varname`= 'backupenabled'");
|
||||
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "`SET `varname` = 'exportenabled' WHERE `settinggroup`= 'system' AND `varname`= 'backupenabled");
|
||||
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "`SET `value` = REPLACE(`value`, 'extras.backup', 'extras.export') WHERE `settinggroup` = 'panel' AND `varname` = 'customer_hide_options'");
|
||||
Database::query("DELETE FROM `" . TABLE_PANEL_USERCOLUMNS . "` WHERE `section` = 'backup_list'");
|
||||
Database::query("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'");
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Froxlor::updateToDbVersion('202305240');
|
||||
Froxlor::updateToVersion('2.1.0-dev1');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.0-dev1')) {
|
||||
Update::showUpdateStep("Updating from 2.1.0-dev1 to 2.1.0-beta1", false);
|
||||
Froxlor::updateToVersion('2.1.0-beta1');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.0-beta1')) {
|
||||
Update::showUpdateStep("Updating from 2.1.0-beta1 to 2.1.0-beta2", false);
|
||||
|
||||
Update::showUpdateStep("Removing unused table");
|
||||
Database::query("DROP TABLE IF EXISTS `panel_sessions`;");
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Froxlor::updateToVersion('2.1.0-beta2');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.0-beta2')) {
|
||||
Update::showUpdateStep("Updating from 2.1.0-beta2 to 2.1.0-rc1", false);
|
||||
Froxlor::updateToVersion('2.1.0-rc1');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.0-rc1')) {
|
||||
Update::showUpdateStep("Updating from 2.1.0-rc1 to 2.1.0-rc2", false);
|
||||
|
||||
Update::showUpdateStep("Adjusting setting spf_entry");
|
||||
$spf_entry = Settings::Get('spf.spf_entry');
|
||||
if (!preg_match('/^v=spf[a-z0-9:~?\s.-]+$/i', $spf_entry)) {
|
||||
Settings::Set('spf.spf_entry', 'v=spf1 a mx -all');
|
||||
Update::lastStepStatus(1, 'corrected');
|
||||
} else {
|
||||
Update::lastStepStatus(0);
|
||||
}
|
||||
|
||||
Froxlor::updateToVersion('2.1.0-rc2');
|
||||
}
|
||||
|
||||
if (Froxlor::isDatabaseVersion('202305240')) {
|
||||
|
||||
Update::showUpdateStep("Adjusting file-template file extension setttings");
|
||||
$current_fileextension = Settings::Get('system.index_file_extension');
|
||||
Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup`= 'system' AND `varname`= 'index_file_extension'");
|
||||
Database::query("ALTER TABLE `" . TABLE_PANEL_TEMPLATES . "` ADD `file_extension` varchar(50) NOT NULL default 'html';");
|
||||
if (!empty(trim($current_fileextension)) && strtolower(trim($current_fileextension)) != 'html') {
|
||||
$stmt = Database::prepare("UPDATE `" . TABLE_PANEL_TEMPLATES . "` SET `file_extension` = :ext WHERE `templategroup` = 'files'");
|
||||
Database::pexecute($stmt, ['ext' => strtolower(trim($current_fileextension))]);
|
||||
}
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Froxlor::updateToDbVersion('202311260');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.0-rc2')) {
|
||||
Update::showUpdateStep("Updating from 2.1.0-rc2 to 2.1.0-rc3", false);
|
||||
Froxlor::updateToVersion('2.1.0-rc3');
|
||||
}
|
||||
|
||||
if (Froxlor::isDatabaseVersion('202311260')) {
|
||||
Update::showUpdateStep("Cleaning up old files");
|
||||
$to_clean = array(
|
||||
"install/updates/froxlor/update_2.x.inc.php",
|
||||
"install/updates/preconfig/preconfig_2.x.inc.php",
|
||||
"lib/Froxlor/Api/Commands/CustomerBackups.php",
|
||||
"lib/Froxlor/Cli/Action",
|
||||
"lib/Froxlor/Cli/Action.php",
|
||||
"lib/Froxlor/Cli/CmdLineHandler.php",
|
||||
"lib/Froxlor/Cli/ConfigServicesCmd.php",
|
||||
"lib/Froxlor/Cli/PhpSessioncleanCmd.php",
|
||||
"lib/Froxlor/Cli/SwitchServerIpCmd.php",
|
||||
"lib/Froxlor/Cli/UpdateCliCmd.php",
|
||||
"lib/Froxlor/Cron/System/BackupCron.php",
|
||||
"lib/formfields/customer/extras/formfield.backup.php",
|
||||
"lib/tablelisting/customer/tablelisting.backups.php",
|
||||
"templates/Froxlor/assets/mix-manifest.json",
|
||||
"templates/Froxlor/assets/css",
|
||||
"templates/Froxlor/assets/webfonts",
|
||||
"templates/Froxlor/assets/js/main.js",
|
||||
"templates/Froxlor/assets/js/main.js.LICENSE.txt",
|
||||
"templates/Froxlor/src",
|
||||
"templates/Froxlor/user/change_language.html.twig",
|
||||
"templates/Froxlor/user/change_password.html.twig",
|
||||
"templates/Froxlor/user/change_theme.html.twig",
|
||||
"tests/Backup/CustomerBackupsTest.php"
|
||||
);
|
||||
$disabled = explode(',', ini_get('disable_functions'));
|
||||
$exec_allowed = !in_array('exec', $disabled);
|
||||
$del_list = "";
|
||||
foreach ($to_clean as $filedir) {
|
||||
$complete_filedir = Froxlor::getInstallDir() . $filedir;
|
||||
if (file_exists($complete_filedir)) {
|
||||
if ($exec_allowed) {
|
||||
FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir));
|
||||
} else {
|
||||
$del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($exec_allowed) {
|
||||
Update::lastStepStatus(0);
|
||||
} else {
|
||||
if (empty($del_list)) {
|
||||
// none of the files existed
|
||||
Update::lastStepStatus(0);
|
||||
} else {
|
||||
Update::lastStepStatus(
|
||||
1,
|
||||
'manual commands needed',
|
||||
'Please run the following commands manually:<br><pre>' . $del_list . '</pre>'
|
||||
);
|
||||
}
|
||||
}
|
||||
Froxlor::updateToDbVersion('202312050');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.0-rc3')) {
|
||||
Update::showUpdateStep("Updating from 2.1.0-rc3 to 2.1.0 stable", false);
|
||||
Froxlor::updateToVersion('2.1.0');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.0')) {
|
||||
Update::showUpdateStep("Updating from 2.1.0 to 2.1.1", false);
|
||||
Froxlor::updateToVersion('2.1.1');
|
||||
}
|
||||
|
||||
if (Froxlor::isDatabaseVersion('202312050')) {
|
||||
Update::showUpdateStep("Cleaning up old files");
|
||||
$to_clean = array(
|
||||
"lib/configfiles/centos7.xml",
|
||||
"lib/configfiles/centos8.xml",
|
||||
"lib/configfiles/stretch.xml",
|
||||
"lib/configfiles/xenial.xml",
|
||||
"lib/configfiles/buster.xml",
|
||||
"lib/configfiles/bionic.xml",
|
||||
);
|
||||
$disabled = explode(',', ini_get('disable_functions'));
|
||||
$exec_allowed = !in_array('exec', $disabled);
|
||||
$del_list = "";
|
||||
foreach ($to_clean as $filedir) {
|
||||
$complete_filedir = Froxlor::getInstallDir() . $filedir;
|
||||
if (file_exists($complete_filedir)) {
|
||||
if ($exec_allowed) {
|
||||
FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir));
|
||||
} else {
|
||||
$del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($exec_allowed) {
|
||||
Update::lastStepStatus(0);
|
||||
} else {
|
||||
if (empty($del_list)) {
|
||||
// none of the files existed
|
||||
Update::lastStepStatus(0);
|
||||
} else {
|
||||
Update::lastStepStatus(
|
||||
1,
|
||||
'manual commands needed',
|
||||
'Please run the following commands manually:<br><pre>' . $del_list . '</pre>'
|
||||
);
|
||||
}
|
||||
}
|
||||
Froxlor::updateToDbVersion('202312100');
|
||||
}
|
||||
|
||||
if (Froxlor::isDatabaseVersion('202312100')) {
|
||||
|
||||
Update::showUpdateStep("Adjusting table row format of larger tables");
|
||||
Database::query("ALTER TABLE `" . TABLE_PANEL_ADMINS . "` ROW_FORMAT=DYNAMIC;");
|
||||
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` ROW_FORMAT=DYNAMIC;");
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Froxlor::updateToDbVersion('202312120');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.1')) {
|
||||
Update::showUpdateStep("Updating from 2.1.1 to 2.1.2", false);
|
||||
Froxlor::updateToVersion('2.1.2');
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
|
||||
$config_dir = FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/lib/configfiles/');
|
||||
// show list of available distro's
|
||||
$distros = glob($config_dir . '*.xml');
|
||||
// selection is required $distributions_select[''] = '-';
|
||||
$distributions_select[''] = '-';
|
||||
// read in all the distros
|
||||
foreach ($distros as $_distribution) {
|
||||
// get configparser object
|
||||
|
||||
@@ -36,7 +36,19 @@ $preconfig = [
|
||||
$return = [];
|
||||
|
||||
if (Update::versionInUpdate($current_version, '2.1.0-dev1')) {
|
||||
|
||||
// Backup
|
||||
$description = 'Froxlor now comes with a backup capability (More info see [DOCS LINK].';
|
||||
$question = '<strong>Would you like to enable the backup-feature (default: yes)</strong>';
|
||||
$return['panel_settings_mode'] = [
|
||||
'type' => 'select',
|
||||
'select_var' => [
|
||||
0 => 'No',
|
||||
1 => 'Yes'
|
||||
],
|
||||
'selected' => 1,
|
||||
'label' => $question,
|
||||
'prior_infotext' => $description
|
||||
];
|
||||
}
|
||||
|
||||
$preconfig['fields'] = $return;
|
||||
|
||||
487
lib/Froxlor/Api/Commands/BackupStorages.php
Normal file
487
lib/Froxlor/Api/Commands/BackupStorages.php
Normal file
@@ -0,0 +1,487 @@
|
||||
<?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\Api\Commands;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Api\ApiCommand;
|
||||
use Froxlor\Api\ResourceEntity;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FileDir;
|
||||
use Froxlor\FroxlorLogger;
|
||||
use Froxlor\Settings;
|
||||
use Froxlor\UI\Response;
|
||||
use Froxlor\Validate\Validate;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
*/
|
||||
class BackupStorages extends ApiCommand implements ResourceEntity
|
||||
{
|
||||
const SUPPORTED_TYPES = [
|
||||
'local',
|
||||
'ftp',
|
||||
'sftp',
|
||||
'rsync',
|
||||
's3',
|
||||
];
|
||||
|
||||
/**
|
||||
* lists all backup storages entries
|
||||
*
|
||||
* @param array $sql_search
|
||||
* optional array with index = fieldname, and value = array with 'op' => operator (one of <, > or =),
|
||||
* LIKE is used if left empty and 'value' => searchvalue
|
||||
* @param int $sql_limit
|
||||
* optional specify number of results to be returned
|
||||
* @param int $sql_offset
|
||||
* optional specify offset for resultset
|
||||
* @param array $sql_orderby
|
||||
* optional array with index = fieldname and value = ASC|DESC to order the resultset by one or more
|
||||
* fields
|
||||
*
|
||||
* @access admin
|
||||
* @return string json-encoded array count|list
|
||||
* @throws Exception
|
||||
*/
|
||||
public function listing()
|
||||
{
|
||||
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
|
||||
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list backup storages");
|
||||
$query_fields = [];
|
||||
$result_stmt = Database::prepare("
|
||||
SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` ". $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit()
|
||||
);
|
||||
Database::pexecute($result_stmt, $query_fields, true, true);
|
||||
$result = [];
|
||||
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$result[] = $row;
|
||||
}
|
||||
return $this->response([
|
||||
'count' => count($result),
|
||||
'list' => $result
|
||||
]);
|
||||
}
|
||||
throw new Exception("Not allowed to execute given command.", 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the total number of backup storages
|
||||
*
|
||||
* @access admin
|
||||
* @return string json-encoded response message
|
||||
* @throws Exception
|
||||
*/
|
||||
public function listingCount()
|
||||
{
|
||||
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
|
||||
$result_stmt = Database::prepare("
|
||||
SELECT COUNT(*) as num_backup_storagess
|
||||
FROM `" . TABLE_PANEL_BACKUP_STORAGES . "`
|
||||
");
|
||||
$result = Database::pexecute_first($result_stmt, null, true, true);
|
||||
if ($result) {
|
||||
return $this->response($result['num_backup_storagess']);
|
||||
}
|
||||
$this->response(0);
|
||||
}
|
||||
throw new Exception("Not allowed to execute given command.", 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a backup storage
|
||||
*
|
||||
* @param string $type
|
||||
* required, backup storage type
|
||||
* @param string $destination_path
|
||||
* required, destination path for backup storage
|
||||
* @param string $description
|
||||
* required, description for backup storage
|
||||
* @param string $region
|
||||
* optional, required if type=s3. Region for backup storage (used for S3)
|
||||
* @param string $bucket
|
||||
* optional, required if type=s3. Bucket for backup storage (used for S3)
|
||||
* @param string $hostname
|
||||
* optional, required if type != local. Hostname for backup storage
|
||||
* @param string $username
|
||||
* optional, required if type != local. Username for backup storage (also used as access key for S3)
|
||||
* @param string $password
|
||||
* optional, required if type != local. Password for backup storage (also used as secret key for S3)
|
||||
* @param string $pgp_public_key
|
||||
* optional, pgp public key for backup storage
|
||||
* @param string $retention
|
||||
* optional, retention for backup storage (default 3)
|
||||
*
|
||||
* @access admin
|
||||
* @return string json-encoded array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
|
||||
// required parameters
|
||||
$type = $this->getParam('type');
|
||||
$destination_path = $this->getParam('destination_path');
|
||||
$description = $this->getParam('description');
|
||||
|
||||
// type related requirements
|
||||
$optional_flags = [
|
||||
'region' => true,
|
||||
'bucket' => true,
|
||||
'hostname' => true,
|
||||
'username' => true,
|
||||
'password' => true,
|
||||
];
|
||||
|
||||
if (!in_array($type, self::SUPPORTED_TYPES)) {
|
||||
throw new Exception("Unsupported storage type: '" . $type . "'", 406);
|
||||
}
|
||||
|
||||
if ($type != 'local') {
|
||||
$optional_flags['hostname'] = false;
|
||||
$optional_flags['username'] = false;
|
||||
$optional_flags['password'] = false;
|
||||
}
|
||||
if ($type == 's3') {
|
||||
$optional_flags['region'] = false;
|
||||
$optional_flags['bucket'] = false;
|
||||
}
|
||||
|
||||
// parameters
|
||||
$region = $this->getParam('region', $optional_flags['region']);
|
||||
$bucket = $this->getParam('bucket', $optional_flags['bucket']);
|
||||
$hostname = $this->getParam('hostname', $optional_flags['hostname']);
|
||||
$username = $this->getParam('username', $optional_flags['username']);
|
||||
$password = $this->getParam('password', $optional_flags['password']);
|
||||
$pgp_public_key = $this->getParam('pgp_public_key', true, null);
|
||||
$retention = $this->getParam('retention', true, 3);
|
||||
|
||||
// validation
|
||||
$destination_path = FileDir::makeCorrectDir(Validate::validate($destination_path, 'destination_path', Validate::REGEX_DIR, '', [], true));
|
||||
// TODO: add more validation
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// store
|
||||
$stmt = Database::prepare("
|
||||
INSERT INTO `" . TABLE_PANEL_BACKUP_STORAGES . "` (
|
||||
`description`,
|
||||
`type`,
|
||||
`region`,
|
||||
`bucket`,
|
||||
`destination_path`,
|
||||
`hostname`,
|
||||
`username`,
|
||||
`password`,
|
||||
`pgp_public_key`,
|
||||
`retention`
|
||||
) VALUES (
|
||||
:description,
|
||||
:type,
|
||||
:region,
|
||||
:bucket,
|
||||
:destination_path,
|
||||
:hostname,
|
||||
:username,
|
||||
:password,
|
||||
:pgp_public_key,
|
||||
:retention
|
||||
)
|
||||
");
|
||||
$params = [
|
||||
"description" => $description,
|
||||
"type" => $type,
|
||||
"region" => $region,
|
||||
"bucket" => $bucket,
|
||||
"destination_path" => $destination_path,
|
||||
"hostname" => $hostname,
|
||||
"username" => $username,
|
||||
"password" => $password,
|
||||
"pgp_public_key" => $pgp_public_key,
|
||||
"retention" => $retention,
|
||||
];
|
||||
Database::pexecute($stmt, $params, true, true);
|
||||
$id = Database::lastInsertId();
|
||||
|
||||
// return
|
||||
$result = $this->apiCall('BackupStorages.get', [
|
||||
'id' => $id
|
||||
]);
|
||||
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] added backup storage '" . $result['description'] . "' (" . $result['type'] . ")");
|
||||
return $this->response($result);
|
||||
}
|
||||
throw new Exception("Not allowed to execute given command.", 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* return a backup storage entry by id
|
||||
*
|
||||
* @param int $id
|
||||
* the backup-storage-id
|
||||
*
|
||||
* @access admin
|
||||
* @return string json-encoded array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
$id = $this->getParam('id');
|
||||
|
||||
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
|
||||
$result_stmt = Database::prepare("
|
||||
SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "`
|
||||
WHERE `id` = :id"
|
||||
);
|
||||
$params = [
|
||||
'id' => $id
|
||||
];
|
||||
$result = Database::pexecute_first($result_stmt, $params, true, true);
|
||||
if ($result) {
|
||||
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get backup storage '" . $result['description'] . "'");
|
||||
return $this->response($result);
|
||||
}
|
||||
throw new Exception("Backup storage with " . $id . " could not be found", 404);
|
||||
}
|
||||
throw new Exception("Not allowed to execute given command.", 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* update a backup storage by given id
|
||||
*
|
||||
* @param int $id
|
||||
* required, the backup-storage-id
|
||||
* @param string $type
|
||||
* optional, backup storage type
|
||||
* @param string $destination_path
|
||||
* optional, destination path for backup storage
|
||||
* @param string $description
|
||||
* required, description for backup storage
|
||||
* @param string $region
|
||||
* optional, region for backup storage (used for S3)
|
||||
* @param string $bucket
|
||||
* optional, bucket for backup storage (used for S3)
|
||||
* @param string $hostname
|
||||
* optional, hostname for backup storage
|
||||
* @param string $username
|
||||
* optional, username for backup storage (also used as access key for S3)
|
||||
* @param string $password
|
||||
* optional, password for backup storage (also used as secret key for S3)
|
||||
* @param string $pgp_public_key
|
||||
* optional, pgp public key for backup storage
|
||||
* @param string $retention
|
||||
* optional, retention for backup storage (default 3)
|
||||
*
|
||||
* @access admin
|
||||
* @return string json-encoded array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$id = $this->getParam('id');
|
||||
|
||||
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
|
||||
// validation
|
||||
$result = $this->apiCall('BackupStorages.get', [
|
||||
'id' => $id
|
||||
]);
|
||||
|
||||
// parameters
|
||||
$description = $this->getParam('description', true, $result['description']);
|
||||
$type = $this->getParam('type', true, $result['type']);
|
||||
$region = $this->getParam('region', true, $result['region']);
|
||||
$bucket = $this->getParam('bucket', true, $result['bucket']);
|
||||
$destination_path = $this->getParam('destination_path', true, $result['destination_path']);
|
||||
$hostname = $this->getParam('hostname', true, $result['hostname']);
|
||||
$username = $this->getParam('username', true, $result['username']);
|
||||
$password = $this->getParam('password', true, '');
|
||||
$pgp_public_key = $this->getParam('pgp_public_key', true, $result['pgp_public_key']);
|
||||
$retention = $this->getParam('retention', true, $result['retention']);
|
||||
|
||||
if (!in_array($type, self::SUPPORTED_TYPES)) {
|
||||
throw new Exception("Unsupported storage type: '" . $type . "'", 406);
|
||||
}
|
||||
|
||||
if ($type != 'local') {
|
||||
if (empty($hostname)) {
|
||||
throw new Exception("Field 'hostname' cannot be empty", 406);
|
||||
}
|
||||
if (empty($username)) {
|
||||
throw new Exception("Field 'username' cannot be empty", 406);
|
||||
}
|
||||
$password = Validate::validate($password, 'password', '', '', [], true);
|
||||
}
|
||||
if ($type == 's3') {
|
||||
if (empty($region)) {
|
||||
throw new Exception("Field 'region' cannot be empty", 406);
|
||||
}
|
||||
if (empty($bucket)) {
|
||||
throw new Exception("Field 'bucket' cannot be empty", 406);
|
||||
}
|
||||
}
|
||||
|
||||
// validation
|
||||
$destination_path = FileDir::makeCorrectDir(Validate::validate($destination_path, 'destination_path', Validate::REGEX_DIR, '', [], true));
|
||||
// TODO: add more validation
|
||||
|
||||
// pgp public key validation
|
||||
if (!empty($pgp_public_key) && $pgp_public_key != $result['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 (!empty($password)) {
|
||||
$stmt = Database::prepare("UPDATE `" . TABLE_PANEL_BACKUP_STORAGES . "`
|
||||
SET `password` = :password
|
||||
WHERE `id` = :id
|
||||
");
|
||||
Database::pexecute($stmt, [
|
||||
"id" => $id,
|
||||
"password" => $password
|
||||
], true, true);
|
||||
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] updated password for backup-storage '" . $result['description'] . "'");
|
||||
}
|
||||
|
||||
// update
|
||||
$stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_PANEL_BACKUP_STORAGES . "`
|
||||
SET `description` = :description,
|
||||
`type` = :type,
|
||||
`region` = :region,
|
||||
`bucket` = :bucket,
|
||||
`destination_path` = :destination_path,
|
||||
`hostname` = :hostname,
|
||||
`username` = :username,
|
||||
`pgp_public_key` = :pgp_public_key,
|
||||
`retention` = :retention
|
||||
WHERE `id` = :id
|
||||
");
|
||||
$params = [
|
||||
"id" => $id,
|
||||
"description" => $description,
|
||||
"type" => $type,
|
||||
"region" => $region,
|
||||
"bucket" => $bucket,
|
||||
"destination_path" => $destination_path,
|
||||
"hostname" => $hostname,
|
||||
"username" => $username,
|
||||
"pgp_public_key" => $pgp_public_key,
|
||||
"retention" => $retention,
|
||||
];
|
||||
Database::pexecute($stmt, $params, true, true);
|
||||
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] edited backup storage '" . $result['description'] . "'");
|
||||
|
||||
// return
|
||||
$result = $this->apiCall('BackupStorages.get', [
|
||||
'id' => $id
|
||||
]);
|
||||
return $this->response($result);
|
||||
}
|
||||
throw new Exception("Not allowed to execute given command.", 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* delete a backup-storage entry by id
|
||||
*
|
||||
* @param int $id
|
||||
* required, the backup-storage-id
|
||||
*
|
||||
* @access admin
|
||||
* @return string json-encoded array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$id = $this->getParam('id');
|
||||
|
||||
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
|
||||
// validation
|
||||
$result = $this->apiCall('BackupStorages.get', [
|
||||
'id' => $id
|
||||
]);
|
||||
|
||||
// validate no-one's using it
|
||||
|
||||
// settings
|
||||
if ($id == Settings::Get('backup.default_storage')) {
|
||||
throw new Exception("Given backup storage is currently set as default storage and cannot be deleted.", 406);
|
||||
}
|
||||
// customers
|
||||
$sel_stmt = Database::prepare("
|
||||
SELECT COUNT(*) as num_storage_users
|
||||
FROM `" . TABLE_PANEL_CUSTOMERS . "`
|
||||
WHERE `backup` = :id
|
||||
");
|
||||
$storage_users_result = Database::pexecute_first($sel_stmt, ['id' => $id]);
|
||||
if ($storage_users_result && $storage_users_result['num_storage_users'] > 0) {
|
||||
throw new Exception("Given backup storage is currently assigned to " . $storage_users_result['num_storage_users'] . " customers and cannot be deleted.", 406);
|
||||
}
|
||||
// existing backups
|
||||
$sel_stmt = Database::prepare("
|
||||
SELECT COUNT(*) as num_storage_backups
|
||||
FROM `" . TABLE_PANEL_BACKUPS . "`
|
||||
WHERE `storage_id` = :id
|
||||
");
|
||||
$storage_backups_result = Database::pexecute_first($sel_stmt, ['id' => $id]);
|
||||
if ($storage_backups_result && $storage_backups_result['num_storage_backups'] > 0) {
|
||||
throw new Exception("Given backup storage has still " . $storage_backups_result['num_storage_backups'] . " backups on it and cannot be deleted.", 406);
|
||||
}
|
||||
|
||||
// delete
|
||||
$stmt = Database::prepare("
|
||||
DELETE FROM `" . TABLE_PANEL_BACKUP_STORAGES . "`
|
||||
WHERE `id` = :id
|
||||
");
|
||||
$params = [
|
||||
"id" => $id
|
||||
];
|
||||
Database::pexecute($stmt, $params, true, true);
|
||||
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] deleted backup storage '" . $result['description'] . "'");
|
||||
|
||||
// return
|
||||
return $this->response(true);
|
||||
}
|
||||
|
||||
throw new Exception("Not allowed to execute given command.", 403);
|
||||
}
|
||||
}
|
||||
211
lib/Froxlor/Api/Commands/Backups.php
Normal file
211
lib/Froxlor/Api/Commands/Backups.php
Normal file
@@ -0,0 +1,211 @@
|
||||
<?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\Api\Commands;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Api\ApiCommand;
|
||||
use Froxlor\Api\ResourceEntity;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FroxlorLogger;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
*/
|
||||
class Backups extends ApiCommand implements ResourceEntity
|
||||
{
|
||||
/**
|
||||
* lists all admin entries
|
||||
*
|
||||
* @param array $sql_search
|
||||
* optional array with index = fieldname, and value = array with 'op' => operator (one of <, > or =),
|
||||
* LIKE is used if left empty and 'value' => searchvalue
|
||||
* @param int $sql_limit
|
||||
* optional specify number of results to be returned
|
||||
* @param int $sql_offset
|
||||
* optional specify offset for resultset
|
||||
* @param array $sql_orderby
|
||||
* optional array with index = fieldname and value = ASC|DESC to order the resultset by one or more
|
||||
* fields
|
||||
*
|
||||
* @access admin
|
||||
* @return string json-encoded array count|list
|
||||
* @throws Exception
|
||||
*/
|
||||
public function listing()
|
||||
{
|
||||
if ($this->isAdmin()) {
|
||||
// if we're an admin, list all backups of all the admins customers
|
||||
// or optionally for one specific customer identified by id or loginname
|
||||
$customerid = $this->getParam('customerid', true, 0);
|
||||
$loginname = $this->getParam('loginname', true, '');
|
||||
|
||||
if (!empty($customerid) || !empty($loginname)) {
|
||||
$result = $this->apiCall('Customers.get', [
|
||||
'id' => $customerid,
|
||||
'loginname' => $loginname
|
||||
]);
|
||||
$custom_list_result = [
|
||||
$result
|
||||
];
|
||||
} else {
|
||||
$_custom_list_result = $this->apiCall('Customers.listing');
|
||||
$custom_list_result = $_custom_list_result['list'];
|
||||
}
|
||||
$customer_ids = [];
|
||||
foreach ($custom_list_result as $customer) {
|
||||
$customer_ids[] = $customer['customerid'];
|
||||
}
|
||||
if (empty($customer_ids)) {
|
||||
throw new Exception("Required resource unsatisfied.", 405);
|
||||
}
|
||||
} else {
|
||||
$customer_ids = [
|
||||
$this->getUserDetail('customerid')
|
||||
];
|
||||
}
|
||||
|
||||
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list backups");
|
||||
$query_fields = [];
|
||||
$result_stmt = Database::prepare("
|
||||
SELECT `b`.*, `a`.`loginname` as `adminname`
|
||||
FROM `" . TABLE_PANEL_BACKUPS . "` `b`
|
||||
LEFT JOIN `" . TABLE_PANEL_ADMINS . "` `a` USING(`adminid`)
|
||||
WHERE `b`.`customerid` IN (" . implode(', ', $customer_ids) . ")
|
||||
" . $this->getSearchWhere($query_fields, true) . $this->getOrderBy() . $this->getLimit()
|
||||
);
|
||||
Database::pexecute($result_stmt, $query_fields, true, true);
|
||||
$result = [];
|
||||
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$result[] = $row;
|
||||
}
|
||||
return $this->response([
|
||||
'count' => count($result),
|
||||
'list' => $result
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the total number of backups for the given admin
|
||||
*
|
||||
* @access admin
|
||||
* @return string json-encoded response message
|
||||
* @throws Exception
|
||||
*/
|
||||
public function listingCount()
|
||||
{
|
||||
if ($this->isAdmin()) {
|
||||
// if we're an admin, list all backups of all the admins customers
|
||||
// or optionally for one specific customer identified by id or loginname
|
||||
$customerid = $this->getParam('customerid', true, 0);
|
||||
$loginname = $this->getParam('loginname', true, '');
|
||||
|
||||
if (!empty($customerid) || !empty($loginname)) {
|
||||
$result = $this->apiCall('Customers.get', [
|
||||
'id' => $customerid,
|
||||
'loginname' => $loginname
|
||||
]);
|
||||
$custom_list_result = [
|
||||
$result
|
||||
];
|
||||
} else {
|
||||
$_custom_list_result = $this->apiCall('Customers.listing');
|
||||
$custom_list_result = $_custom_list_result['list'];
|
||||
}
|
||||
$customer_ids = [];
|
||||
foreach ($custom_list_result as $customer) {
|
||||
$customer_ids[] = $customer['customerid'];
|
||||
}
|
||||
if (empty($customer_ids)) {
|
||||
throw new Exception("Required resource unsatisfied.", 405);
|
||||
}
|
||||
} else {
|
||||
$customer_ids = [
|
||||
$this->getUserDetail('customerid')
|
||||
];
|
||||
}
|
||||
$result_stmt = Database::prepare("
|
||||
SELECT COUNT(*) as num_backups
|
||||
FROM `" . TABLE_PANEL_BACKUPS . "` `b`
|
||||
WHERE `b`.`customerid` IN (" . implode(', ', $customer_ids) . ")
|
||||
");
|
||||
$result = Database::pexecute_first($result_stmt, null, true, true);
|
||||
if ($result) {
|
||||
return $this->response($result['num_backups']);
|
||||
}
|
||||
$this->response(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* You cannot add a backup entry
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
throw new Exception('You cannot add a backup entry', 303);
|
||||
}
|
||||
|
||||
/**
|
||||
* return a backup entry by id
|
||||
*
|
||||
* @param int $id
|
||||
* optional, the backup-entry-id
|
||||
*
|
||||
* @access admin, customers
|
||||
* @return string json-encoded array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
throw new Exception("@TODO", 303);
|
||||
}
|
||||
|
||||
/**
|
||||
* You cannot update a backup entry
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
throw new Exception('You cannot update a backup entry', 303);
|
||||
}
|
||||
|
||||
/**
|
||||
* delete a backup entry by id
|
||||
*
|
||||
* @param int $id
|
||||
* required, the backup-entry-id
|
||||
*
|
||||
* @access admin, customer
|
||||
* @return string json-encoded array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
throw new Exception("@TODO", 303);
|
||||
}
|
||||
}
|
||||
@@ -273,6 +273,13 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
* @param array $allowed_mysqlserver
|
||||
* optional, array of IDs of defined mysql-servers the customer is allowed to use,
|
||||
* default is to allow the default dbserver (id=0)
|
||||
* @param int $backup
|
||||
* optional, either 0 to disable backup for this customer or a backup-storage-id
|
||||
* where backups are to be stored, requires change_serversettings permissions,
|
||||
* default is system-setting backup.default_storage
|
||||
* @param bool $access_backups
|
||||
* optional, where the customer is allowed to view backups, default is system-setting
|
||||
* default_customer_access
|
||||
*
|
||||
* @access admin
|
||||
* @return string json-encoded array
|
||||
@@ -359,6 +366,24 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
$p_allowed_mysqlserver = [];
|
||||
}
|
||||
|
||||
if ($this->getUserDetail('change_serversettings')) {
|
||||
$backup = $this->getParam('backup', true, Settings::Get('backup.default_storage'));
|
||||
if ($backup > 0) {
|
||||
try {
|
||||
$this->apiCall('BackupStorages.get', [
|
||||
'id' => $backup
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
// not found or other issue, set default
|
||||
$backup = Settings::Get('backup.default_storage');
|
||||
}
|
||||
}
|
||||
$access_backups = $this->getBoolParam('access_backups', true, Settings::Get('backup.default_customer_access'));
|
||||
} else {
|
||||
$backup = Settings::Get('backup.default_storage');
|
||||
$access_backups = Settings::Get('backup.default_customer_access');
|
||||
}
|
||||
|
||||
// validation
|
||||
$name = Validate::validate($name, 'name', Validate::REGEX_DESC_TEXT, '', [], true);
|
||||
$firstname = Validate::validate($firstname, 'first name', Validate::REGEX_DESC_TEXT, '', [], true);
|
||||
@@ -537,7 +562,9 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
'theme' => $_theme,
|
||||
'custom_notes' => $custom_notes,
|
||||
'custom_notes_show' => $custom_notes_show,
|
||||
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver)
|
||||
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver),
|
||||
'backup' => $backup,
|
||||
'access_backups' => $access_backups
|
||||
];
|
||||
|
||||
$ins_stmt = Database::prepare("
|
||||
@@ -580,7 +607,9 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
`theme` = :theme,
|
||||
`custom_notes` = :custom_notes,
|
||||
`custom_notes_show` = :custom_notes_show,
|
||||
`allowed_mysqlserver`= :allowed_mysqlserver
|
||||
`allowed_mysqlserver`= :allowed_mysqlserver,
|
||||
`backup` = :backup,
|
||||
`access_backups` = :access_backups
|
||||
");
|
||||
Database::pexecute($ins_stmt, $ins_data, true, true);
|
||||
|
||||
@@ -1028,6 +1057,13 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
* @param array $allowed_mysqlserver
|
||||
* optional, array of IDs of defined mysql-servers the customer is allowed to use,
|
||||
* default is to allow the default dbserver (id=0)
|
||||
* @param int $backup
|
||||
* optional, either 0 to disable backup for this customer or a backup-storage-id
|
||||
* where backups are to be stored, requires change_serversettings permissions,
|
||||
* default is system-setting backup.default_storage
|
||||
* @param bool $access_backups
|
||||
* optional, where the customer is allowed to view backups, default is system-setting
|
||||
* default_customer_access
|
||||
*
|
||||
* @access admin, customer
|
||||
* @return string json-encoded array
|
||||
@@ -1053,7 +1089,7 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
$email = $this->getParam('email', true, $idna_convert->decode($result['email']));
|
||||
$name = $this->getParam('name', true, $result['name']);
|
||||
$firstname = $this->getParam('firstname', true, $result['firstname']);
|
||||
$company_required = (!empty($name) && empty($firstname)) || (empty($name) && !empty($firstname)) || (empty($name) && empty($firstname));
|
||||
$company_required = empty($result['company']) && ((!empty($name) && empty($firstname)) || (empty($name) && !empty($firstname)) || (empty($name) && empty($firstname)));
|
||||
$company = $this->getParam('company', !$company_required, $result['company']);
|
||||
$street = $this->getParam('street', true, $result['street']);
|
||||
$zipcode = $this->getParam('zipcode', true, $result['zipcode']);
|
||||
@@ -1089,6 +1125,24 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
$deactivated = $this->getBoolParam('deactivated', true, $result['deactivated']);
|
||||
$theme = $this->getParam('theme', true, $result['theme']);
|
||||
$allowed_mysqlserver = $this->getParam('allowed_mysqlserver', true, json_decode($result['allowed_mysqlserver'], true));
|
||||
|
||||
if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) {
|
||||
$backup = $this->getParam('backup', true, $result['backup']);
|
||||
if ($backup > 0) {
|
||||
try {
|
||||
$this->apiCall('BackupStorages.get', [
|
||||
'id' => $backup
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
// not found or other issue, dont update
|
||||
$backup = $result['backup'];
|
||||
}
|
||||
}
|
||||
$access_backups = $this->getBoolParam('access_backups', true, Settings::Get('backup.default_customer_access'));
|
||||
} else {
|
||||
$backup = $result['backup'];
|
||||
$access_backups = $result['access_backups'];
|
||||
}
|
||||
} else {
|
||||
// allowed parameters
|
||||
$def_language = $this->getParam('def_language', true, $result['def_language']);
|
||||
@@ -1397,7 +1451,9 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
'custom_notes' => $custom_notes,
|
||||
'custom_notes_show' => $custom_notes_show,
|
||||
'api_allowed' => $api_allowed,
|
||||
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver)
|
||||
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver),
|
||||
'backup' => $backup,
|
||||
'access_backups' => $access_backups
|
||||
];
|
||||
$upd_data += $admin_upd_data;
|
||||
}
|
||||
@@ -1440,7 +1496,9 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
`custom_notes` = :custom_notes,
|
||||
`custom_notes_show` = :custom_notes_show,
|
||||
`api_allowed` = :api_allowed,
|
||||
`allowed_mysqlserver` = :allowed_mysqlserver";
|
||||
`allowed_mysqlserver` = :allowed_mysqlserver,
|
||||
`backup`= :backup,
|
||||
`access_backups` = :access_backups";
|
||||
$upd_query .= $admin_upd_query;
|
||||
}
|
||||
$upd_query .= " WHERE `customerid` = :customerid";
|
||||
|
||||
@@ -93,7 +93,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
|
||||
// validation
|
||||
$path = FileDir::makeCorrectDir(Validate::validate($path, 'path', Validate::REGEX_DIR, '', [], true));
|
||||
$userpath = $path;
|
||||
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
|
||||
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
|
||||
|
||||
if (!empty($error404path)) {
|
||||
$error404path = $this->correctErrorDocument($error404path, true);
|
||||
|
||||
@@ -84,7 +84,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
|
||||
|
||||
// validation
|
||||
$path = FileDir::makeCorrectDir(Validate::validate($path, 'path', Validate::REGEX_DIR, '', [], true));
|
||||
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
|
||||
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
|
||||
$username = Validate::validate($username, 'username', '/^[a-zA-Z0-9][a-zA-Z0-9\-_]+\$?$/', '', [], true);
|
||||
$authname = Validate::validate($authname, 'directory_authname', '/^[a-zA-Z0-9][a-zA-Z0-9\-_ ]+\$?$/', '', [], true);
|
||||
$password = Validate::validate($password, 'password', '', '', [], true);
|
||||
|
||||
@@ -316,9 +316,9 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, -1);
|
||||
$ssl_redirect = $this->getBoolParam('ssl_redirect', true, 0);
|
||||
$letsencrypt = $this->getBoolParam('letsencrypt', true, 0);
|
||||
$sslenabled = $this->getBoolParam('sslenabled', true, 1);
|
||||
$dont_use_default_ssl_ipandport_if_empty = $this->getBoolParam('dont_use_default_ssl_ipandport_if_empty', true, 0);
|
||||
$p_ssl_ipandports = $this->getParam('ssl_ipandport', true, $dont_use_default_ssl_ipandport_if_empty ? [] : explode(',', Settings::Get('system.defaultsslip')));
|
||||
$sslenabled = $this->getBoolParam('sslenabled', true, 1);
|
||||
$http2 = $this->getBoolParam('http2', true, 0);
|
||||
$hsts_maxage = $this->getParam('hsts_maxage', true, 0);
|
||||
$hsts_sub = $this->getBoolParam('hsts_sub', true, 0);
|
||||
@@ -349,8 +349,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
|
||||
if (substr($p_domain, 0, 4) == 'xn--') {
|
||||
Response::standardError('domain_nopunycode', '', true);
|
||||
} elseif (Validate::validate_ip2($p_domain, true, '', true, true)) {
|
||||
Response::standardError('domain_noipaddress', '', true);
|
||||
}
|
||||
|
||||
$idna_convert = new IdnaWrapper();
|
||||
@@ -546,10 +544,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$ssl_specialsettings = Validate::validate(str_replace("\r\n", "\n", $ssl_specialsettings), 'ssl_specialsettings', '/^[^\0]*$/', '', [], true);
|
||||
}
|
||||
}
|
||||
if (Settings::Get('system.use_ssl') == "1" && $sslenabled == 1 && empty($ssl_ipandports)) {
|
||||
// enabled ssl for the domain but no ssl ip/port is selected
|
||||
Response::standardError('nosslippportgiven', '', true);
|
||||
}
|
||||
if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) {
|
||||
$ssl_redirect = 0;
|
||||
$letsencrypt = 0;
|
||||
@@ -1213,7 +1207,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$p_ssl_ipandports = $this->getParam('ssl_ipandport', true, $remove_ssl_ipandport ? [
|
||||
-1
|
||||
] : null);
|
||||
$sslenabled = $remove_ssl_ipandport ? false : $this->getBoolParam('sslenabled', true, $result['ssl_enabled']);
|
||||
$sslenabled = $this->getBoolParam('sslenabled', true, $result['ssl_enabled']);
|
||||
$http2 = $this->getBoolParam('http2', true, $result['http2']);
|
||||
$hsts_maxage = $this->getParam('hsts_maxage', true, $result['hsts']);
|
||||
$hsts_sub = $this->getBoolParam('hsts_sub', true, $result['hsts_sub']);
|
||||
@@ -1523,10 +1517,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
if ($remove_ssl_ipandport || (!empty($p_ssl_ipandports) && $p_ssl_ipandports[0] == -1)) {
|
||||
$ssl_ipandports = [];
|
||||
}
|
||||
if (Settings::Get('system.use_ssl') == "1" && $sslenabled && empty($ssl_ipandports)) {
|
||||
// enabled ssl for the domain but no ssl ip/port is selected
|
||||
Response::standardError('nosslippportgiven', '', true);
|
||||
}
|
||||
if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) {
|
||||
$ssl_redirect = 0;
|
||||
$letsencrypt = 0;
|
||||
@@ -1563,7 +1553,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
}
|
||||
|
||||
// Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated
|
||||
if (($result['letsencrypt'] != $letsencrypt || $result['ssl_redirect'] != $ssl_redirect) && $ssl_redirect > 0 && $letsencrypt == 1) {
|
||||
if ($ssl_redirect > 0 && $letsencrypt == 1 && $result['letsencrypt'] != $letsencrypt) {
|
||||
$ssl_redirect = 2;
|
||||
}
|
||||
|
||||
@@ -1652,7 +1642,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
|| $iswildcarddomain != $result['iswildcarddomain']
|
||||
|| $phpenabled != $result['phpenabled']
|
||||
|| $openbasedir != $result['openbasedir']
|
||||
|| $openbasedir_path != $result['openbasedir_path']
|
||||
|| $phpsettingid != $result['phpsettingid']
|
||||
|| $mod_fcgid_starter != $result['mod_fcgid_starter']
|
||||
|| $mod_fcgid_maxrequests != $result['mod_fcgid_maxrequests']
|
||||
@@ -1670,7 +1659,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
|| $hsts_sub != $result['hsts_sub']
|
||||
|| $hsts_preload != $result['hsts_preload']
|
||||
|| $ocsp_stapling != $result['ocsp_stapling']
|
||||
|| $sslenabled != $result['ssl_enabled']
|
||||
) {
|
||||
Cronjob::inserttask(TaskId::REBUILD_VHOST);
|
||||
}
|
||||
@@ -1819,7 +1807,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$update_data['wwwserveralias'] = $wwwserveralias;
|
||||
$update_data['iswildcarddomain'] = $iswildcarddomain;
|
||||
$update_data['phpenabled'] = $phpenabled;
|
||||
$update_data['openbasedir'] = $openbasedir;
|
||||
$update_data['openbasedir'] = $openbasedir;;
|
||||
$update_data['openbasedir_path'] = $openbasedir_path;
|
||||
$update_data['speciallogfile'] = $speciallogfile;
|
||||
$update_data['phpsettingid'] = $phpsettingid;
|
||||
@@ -2325,10 +2313,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
unset($result['wwwserveralias']);
|
||||
unset($result['iswildcarddomain']);
|
||||
|
||||
// translate sslenabled flag
|
||||
$result['sslenabled'] = $result['ssl_enabled'];
|
||||
unset($result['ssl_enabled']);
|
||||
|
||||
$additional_params = $this->getParamList();
|
||||
// unset unneeded params from this call
|
||||
unset($additional_params['id']);
|
||||
|
||||
@@ -202,7 +202,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
|
||||
|
||||
// validation
|
||||
$description = Validate::validate($description, 'description', Validate::REGEX_DESC_TEXT, '', [], true);
|
||||
$reload_cmd = Validate::validate($reload_cmd, 'reload_cmd', '/^[a-z0-9\/\._\-@ ]+$/i', '', [], true);
|
||||
$reload_cmd = Validate::validate($reload_cmd, 'reload_cmd', '/^[a-z0-9\/\._\- ]+$/i', '', [], true);
|
||||
$sel_stmt = Database::prepare("SELECT `id` FROM `".TABLE_PANEL_FPMDAEMONS."` WHERE `reload_cmd` = :rc");
|
||||
$dupcheck = Database::pexecute_first($sel_stmt, ['rc' => $reload_cmd]);
|
||||
if ($dupcheck && $dupcheck['id']) {
|
||||
@@ -327,7 +327,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
|
||||
|
||||
// validation
|
||||
$description = Validate::validate($description, 'description', Validate::REGEX_DESC_TEXT, '', [], true);
|
||||
$reload_cmd = Validate::validate($reload_cmd, 'reload_cmd', '/^[a-z0-9\/\._\-@ ]+$/i', '', [], true);
|
||||
$reload_cmd = Validate::validate($reload_cmd, 'reload_cmd', '/^[a-z0-9\/\._\- ]+$/i', '', [], true);
|
||||
$sel_stmt = Database::prepare("SELECT `id` FROM `".TABLE_PANEL_FPMDAEMONS."` WHERE `reload_cmd` = :rc");
|
||||
$dupcheck = Database::pexecute_first($sel_stmt, ['rc' => $reload_cmd]);
|
||||
if ($dupcheck && $dupcheck['id'] != $id) {
|
||||
|
||||
@@ -82,7 +82,7 @@ class Froxlor extends ApiCommand
|
||||
if ($aucheck == 1) {
|
||||
// anzeige über version-status mit ggfls. formular
|
||||
// zum update schritt #1 -> download
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel').' ' : ''), AutoUpdate::getFromResult('version'), $this->version]);
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') == 'testing' ? 'testing ' : ''), AutoUpdate::getFromResult('version'), $this->version]);
|
||||
$response = [
|
||||
'isnewerversion' => (int) !AutoUpdate::getFromResult('has_latest'),
|
||||
'version' => $this->version,
|
||||
@@ -91,7 +91,7 @@ class Froxlor extends ApiCommand
|
||||
'additional_info' => AutoUpdate::getFromResult('info'),
|
||||
'aucheck' => $aucheck
|
||||
];
|
||||
} elseif ($aucheck < 0 || $aucheck > 1) {
|
||||
} else if ($aucheck < 0 || $aucheck > 1) {
|
||||
// errors
|
||||
if ($aucheck < 0) {
|
||||
$errmsg = AutoUpdate::getLastError();
|
||||
|
||||
@@ -174,7 +174,7 @@ class Ftps extends ApiCommand implements ResourceEntity
|
||||
} elseif ($username == $password) {
|
||||
Response::standardError('passwordshouldnotbeusername', '', true);
|
||||
} else {
|
||||
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
|
||||
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
|
||||
$cryptPassword = Crypt::makeCryptPassword($password, false, true);
|
||||
|
||||
$stmt = Database::prepare("INSERT INTO `" . TABLE_FTP_USERS . "`
|
||||
@@ -469,7 +469,7 @@ class Ftps extends ApiCommand implements ResourceEntity
|
||||
|
||||
// path update?
|
||||
if ($path != '') {
|
||||
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
|
||||
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
|
||||
|
||||
if ($path != $result['homedir']) {
|
||||
$stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "`
|
||||
|
||||
@@ -201,7 +201,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
|
||||
|
||||
// validation
|
||||
$name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true);
|
||||
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT);
|
||||
$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;
|
||||
@@ -383,7 +383,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
|
||||
|
||||
// validation
|
||||
$name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true);
|
||||
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT);
|
||||
$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;
|
||||
|
||||
@@ -176,9 +176,8 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
|
||||
|
||||
if ((int)Settings::Get('system.use_ssl') == 1) {
|
||||
$ssl = (bool)$this->getBoolParam('ssl', true, 0);
|
||||
$cert_optional = !($ssl && empty(Settings::Get('system.ssl_cert_file')));
|
||||
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', $cert_optional, ''), 'ssl_cert_file', '', '', [], true);
|
||||
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', $cert_optional, ''), 'ssl_key_file', '', '', [], true);
|
||||
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', !$ssl, ''), 'ssl_cert_file', '', '', [], true);
|
||||
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', !$ssl, ''), 'ssl_key_file', '', '', [], true);
|
||||
$ssl_ca_file = Validate::validate($this->getParam('ssl_ca_file', true, ''), 'ssl_ca_file', '', '', [], true);
|
||||
$ssl_cert_chainfile = Validate::validate($this->getParam('ssl_cert_chainfile', true, ''), 'ssl_cert_chainfile', '', '', [], true);
|
||||
$sslss = $this->getParam('ssl_specialsettings', true, '');
|
||||
@@ -416,9 +415,8 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
|
||||
|
||||
if ((int)Settings::Get('system.use_ssl') == 1) {
|
||||
$ssl = (bool)$this->getBoolParam('ssl', true, $result['ssl']);
|
||||
$cert_optional = !($ssl && empty(Settings::Get('system.ssl_cert_file')));
|
||||
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', $cert_optional, $result['ssl_cert_file']), 'ssl_cert_file', '', '', [], true);
|
||||
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', $cert_optional, $result['ssl_key_file']), 'ssl_key_file', '', '', [], true);
|
||||
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', !$ssl, $result['ssl_cert_file']), 'ssl_cert_file', '', '', [], true);
|
||||
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', !$ssl, $result['ssl_key_file']), 'ssl_key_file', '', '', [], true);
|
||||
$ssl_ca_file = Validate::validate($this->getParam('ssl_ca_file', true, $result['ssl_ca_file']), 'ssl_ca_file', '', '', [], true);
|
||||
$ssl_cert_chainfile = Validate::validate($this->getParam('ssl_cert_chainfile', true, $result['ssl_cert_chainfile']), 'ssl_cert_chainfile', '', '', [], true);
|
||||
$sslss = $this->getParam('ssl_specialsettings', true, $result['ssl_specialsettings']);
|
||||
|
||||
@@ -564,9 +564,9 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
// If path is empty or '/' and 'Use domain name as default value for DocumentRoot path' is enabled in settings,
|
||||
// set default path to subdomain or domain name
|
||||
if ((($path == '') || ($path == '/')) && Settings::Get('system.documentroot_use_default_value') == 1) {
|
||||
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $completedomain, $customer['documentroot']);
|
||||
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $completedomain);
|
||||
} else {
|
||||
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
|
||||
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
|
||||
}
|
||||
} else {
|
||||
// no it's not, create a redirect
|
||||
@@ -1078,8 +1078,10 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
$custom_list_result = $_custom_list_result['list'];
|
||||
}
|
||||
$customer_ids = [];
|
||||
$customer_stdsubs = [];
|
||||
foreach ($custom_list_result as $customer) {
|
||||
$customer_ids[] = $customer['customerid'];
|
||||
$customer_stdsubs[$customer['customerid']] = $customer['standardsubdomain'];
|
||||
}
|
||||
} else {
|
||||
if (Settings::IsInList('panel.customer_hide_options', 'domains')) {
|
||||
@@ -1088,6 +1090,9 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
$customer_ids = [
|
||||
$this->getUserDetail('customerid')
|
||||
];
|
||||
$customer_stdsubs = [
|
||||
$this->getUserDetail('customerid') => $this->getUserDetail('standardsubdomain')
|
||||
];
|
||||
}
|
||||
if (!empty($customer_ids)) {
|
||||
// prepare select statement
|
||||
@@ -1096,6 +1101,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
FROM `" . TABLE_PANEL_DOMAINS . "` `d`
|
||||
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
|
||||
AND `d`.`email_only` = '0'
|
||||
AND `d`.`id` NOT IN (" . implode(', ', $customer_stdsubs) . ")
|
||||
");
|
||||
$result = Database::pexecute_first($domains_stmt, null, true, true);
|
||||
if ($result) {
|
||||
|
||||
53
lib/Froxlor/Backup/Backup.php
Normal file
53
lib/Froxlor/Backup/Backup.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?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\Backup;
|
||||
|
||||
use Froxlor\Database\Database;
|
||||
use PDO;
|
||||
|
||||
class Backup
|
||||
{
|
||||
/**
|
||||
* returns an array of existing backup-storages
|
||||
* in our database for the settings-array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getBackupStorages(): array
|
||||
{
|
||||
$storages_array = [
|
||||
0 => lng('backup.storage_none')
|
||||
];
|
||||
// get all storages
|
||||
$result_stmt = Database::query("SELECT id, type, description FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` ORDER BY type, description");
|
||||
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
if (!isset($storages_array[$row['id']]) && !in_array($row['id'], $storages_array)) {
|
||||
$storages_array[$row['id']] = "[" . $row['type'] . "] " . html_entity_decode($row['description']);
|
||||
}
|
||||
}
|
||||
return $storages_array;
|
||||
}
|
||||
}
|
||||
102
lib/Froxlor/Backup/Storages/Ftp.php
Normal file
102
lib/Froxlor/Backup/Storages/Ftp.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Backup\Storages;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\FileDir;
|
||||
|
||||
class Ftp extends Storage
|
||||
{
|
||||
private $ftp_conn = null;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public function init(): bool
|
||||
{
|
||||
$hostname = $this->sData['storage']['hostname'] ?? '';
|
||||
$username = $this->sData['storage']['username'] ?? '';
|
||||
$password = $this->sData['storage']['password'] ?? '';
|
||||
if (!empty($hostname) && !empty($username) && !empty($password)) {
|
||||
$tmp = explode(":", $hostname);
|
||||
$hostname = $tmp[0];
|
||||
$port = $tmp[1] ?? 21;
|
||||
$this->ftp_conn = ftp_connect($hostname, $port);
|
||||
if ($this->ftp_conn === false) {
|
||||
throw new Exception('Unable to connect to ftp-server "' . $hostname . ':' . $port . '"');
|
||||
}
|
||||
if (!ftp_login($this->ftp_conn, $username, $password)) {
|
||||
throw new Exception('Unable to login to ftp-server "' . $hostname . ':' . $port . '"');
|
||||
}
|
||||
return $this->changeToCorrectDirectory();
|
||||
}
|
||||
throw new Exception('Empty hostname for FTP backup 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 string
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function putFile(string $filename, string $tmp_source_directory): string
|
||||
{
|
||||
$source = FileDir::makeCorrectFile($tmp_source_directory . "/" . $filename);
|
||||
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 "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function rmFile(string $filename): bool
|
||||
{
|
||||
$target = basename($filename);
|
||||
if (ftp_size($this->ftp_conn, $target) >= 0) {
|
||||
return ftp_delete($this->ftp_conn, $target);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function shutdown(): bool
|
||||
{
|
||||
return ftp_close($this->ftp_conn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
private function changeToCorrectDirectory(): bool
|
||||
{
|
||||
$dirs = explode("/", $this->getDestinationDirectory());
|
||||
array_shift($dirs);
|
||||
if (count($dirs) > 0 && !empty($dirs[0])) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (empty($dir)) {
|
||||
continue;
|
||||
}
|
||||
if (!@ftp_chdir($this->ftp_conn, $dir)) {
|
||||
ftp_mkdir($this->ftp_conn, $dir);
|
||||
ftp_chmod($this->ftp_conn, 0700, $dir);
|
||||
ftp_chdir($this->ftp_conn, $dir);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return ftp_chdir($this->ftp_conn, "/");
|
||||
}
|
||||
}
|
||||
64
lib/Froxlor/Backup/Storages/Local.php
Normal file
64
lib/Froxlor/Backup/Storages/Local.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Backup\Storages;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\FileDir;
|
||||
|
||||
class Local extends Storage
|
||||
{
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function init(): bool
|
||||
{
|
||||
// create destination_path
|
||||
if (!file_exists($this->getDestinationDirectory())) {
|
||||
return mkdir($this->getDestinationDirectory(), 0700, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 string
|
||||
* @throws Exception
|
||||
*/
|
||||
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)) {
|
||||
rename($source, $target);
|
||||
return $target;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function rmFile(string $filename): bool
|
||||
{
|
||||
$target = FileDir::makeCorrectFile($this->getDestinationDirectory() . "/" . $filename);
|
||||
if (file_exists($target)) {
|
||||
return @unlink($target);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function shutdown(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
45
lib/Froxlor/Backup/Storages/Rsync.php
Normal file
45
lib/Froxlor/Backup/Storages/Rsync.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Backup\Storages;
|
||||
|
||||
class Rsync extends Storage
|
||||
{
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function init(): bool
|
||||
{
|
||||
// TODO: Implement init() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 string
|
||||
*/
|
||||
protected function putFile(string $filename, string $tmp_source_directory): string
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
protected function rmFile(string $filename): bool
|
||||
{
|
||||
// TODO: Implement removeOld() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function shutdown(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
45
lib/Froxlor/Backup/Storages/S3.php
Normal file
45
lib/Froxlor/Backup/Storages/S3.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Backup\Storages;
|
||||
|
||||
class S3 extends Storage
|
||||
{
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function init(): bool
|
||||
{
|
||||
// TODO: Implement init() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 string
|
||||
*/
|
||||
protected function putFile(string $filename, string $tmp_source_directory): string
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
protected function rmFile(string $filename): bool
|
||||
{
|
||||
// TODO: Implement removeOld() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function shutdown(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
45
lib/Froxlor/Backup/Storages/Sftp.php
Normal file
45
lib/Froxlor/Backup/Storages/Sftp.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Backup\Storages;
|
||||
|
||||
class Sftp extends Storage
|
||||
{
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function init(): bool
|
||||
{
|
||||
// TODO: Implement init() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 string
|
||||
*/
|
||||
protected function putFile(string $filename, string $tmp_source_directory): string
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
protected function rmFile(string $filename): bool
|
||||
{
|
||||
// TODO: Implement removeOld() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function shutdown(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
281
lib/Froxlor/Backup/Storages/Storage.php
Normal file
281
lib/Froxlor/Backup/Storages/Storage.php
Normal file
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Backup\Storages;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FileDir;
|
||||
|
||||
abstract class Storage
|
||||
{
|
||||
private string $tmpDirectory;
|
||||
protected array $sData;
|
||||
|
||||
protected array $filesToStore;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct(array $storage_data)
|
||||
{
|
||||
$this->sData = $storage_data;
|
||||
$this->tmpDirectory = FileDir::makeCorrectDir(sys_get_temp_dir() . '/backup-' . $this->sData['loginname']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate sData, open connection to target storage, etc.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function init(): bool;
|
||||
|
||||
/**
|
||||
* Disconnect / clean up connection if needed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function shutdown(): bool;
|
||||
|
||||
/**
|
||||
* 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 string
|
||||
*/
|
||||
abstract protected function putFile(string $filename, string $tmp_source_directory): string;
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
abstract protected function rmFile(string $filename): bool;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public function removeOld(): bool
|
||||
{
|
||||
// retention in days
|
||||
$retention = $this->sData['storage']['retention'] ?? 3;
|
||||
// keep date
|
||||
$keepDate = new \DateTime();
|
||||
$keepDate->setTime(0, 0, 0, 1);
|
||||
// subtract retention days
|
||||
$keepDate->sub(new \DateInterval('P' . $retention . 'D'));
|
||||
// select target backups to remove for this storage-id and customer
|
||||
$sel_stmt = Database::prepare("
|
||||
SELECT * FROM `" . TABLE_PANEL_BACKUPS . "`
|
||||
WHERE `created_at` < :keepdate
|
||||
AND `storage_id` = :sid
|
||||
AND `customerid` = :cid
|
||||
");
|
||||
Database::pexecute($sel_stmt, [
|
||||
'keepdate' => $keepDate->format('U'),
|
||||
'sid' => $this->sData['backup'],
|
||||
'cid' => $this->sData['customerid']
|
||||
]);
|
||||
while ($oldBackup = $sel_stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$this->rmFile($oldBackup['filename']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the storage configured destination path for all backups
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getDestinationDirectory(): string
|
||||
{
|
||||
return FileDir::makeCorrectDir($this->sData['storage']['destination_path'] ?? "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create backup-archive/file from $filesToStore and call putFile()
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public function createFromFiles(): bool
|
||||
{
|
||||
if (empty($this->filesToStore)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$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));
|
||||
|
||||
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)
|
||||
$fileSizeOutput = FileDir::safe_exec('/usr/bin/stat -c "%s" ' . escapeshellarg($filename));
|
||||
$fileSize = (int)array_shift($fileSizeOutput);
|
||||
|
||||
// add entry to database and upload/store file
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param int $fileSize
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function addEntry(string $filename, int $fileSize): void
|
||||
{
|
||||
$ins_stmt = Database::prepare("
|
||||
INSERT INTO `" . TABLE_PANEL_BACKUPS . "` SET
|
||||
`adminid` = :adminid,
|
||||
`customerid` = :customerid,
|
||||
`loginname` = :loginname,
|
||||
`size` = :size,
|
||||
`storage_id` = :sid,
|
||||
`filename` = :filename,
|
||||
`created_at` = UNIX_TIMESTAMP()
|
||||
");
|
||||
Database::pexecute($ins_stmt, [
|
||||
'adminid' => $this->sData['adminid'],
|
||||
'customerid' => $this->sData['customerid'],
|
||||
'loginname' => $this->sData['loginname'],
|
||||
'size' => $fileSize,
|
||||
'sid' => $this->sData['backup'],
|
||||
'filename' => $filename
|
||||
]);
|
||||
}
|
||||
}
|
||||
39
lib/Froxlor/Backup/Storages/StorageFactory.php
Normal file
39
lib/Froxlor/Backup/Storages/StorageFactory.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Backup\Storages;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Database\Database;
|
||||
|
||||
class StorageFactory
|
||||
{
|
||||
public static function fromType(string $type, array $storage_data): Storage
|
||||
{
|
||||
$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;
|
||||
}
|
||||
}
|
||||
@@ -25,18 +25,19 @@
|
||||
|
||||
namespace Froxlor\Cli;
|
||||
|
||||
use PDO;
|
||||
use Exception;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\Settings;
|
||||
use PDO;
|
||||
use Froxlor\Database\Database;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CliCommand extends Command
|
||||
{
|
||||
|
||||
protected function validateRequirements(OutputInterface $output, bool $ignore_has_updates = false): int
|
||||
protected function validateRequirements(InputInterface $input, OutputInterface $output, bool $ignore_has_updates = false): int
|
||||
{
|
||||
if (!file_exists(Froxlor::getInstallDir() . '/lib/userdata.inc.php')) {
|
||||
$output->writeln("<error>Could not find froxlor's userdata.inc.php file. You should use this script only with an installed froxlor system.</>");
|
||||
@@ -115,11 +116,9 @@ class CliCommand extends Command
|
||||
return $userinfo;
|
||||
}
|
||||
|
||||
protected function runUpdate(OutputInterface $output, bool $manual = false): int
|
||||
private function runUpdate(OutputInterface $output): int
|
||||
{
|
||||
if (!$manual) {
|
||||
$output->writeln('<comment>Automatic update is activated and we are going to proceed without any notices</>');
|
||||
}
|
||||
$output->writeln('<comment>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([
|
||||
@@ -128,11 +127,11 @@ class CliCommand extends Command
|
||||
]);
|
||||
include_once Froxlor::getInstallDir() . '/install/updatesql.php';
|
||||
ob_end_flush();
|
||||
$output->writeln('<info>' . ($manual ? 'Database' : 'Automatic') . ' update done - you should check your settings to be sure everything is fine</>');
|
||||
$output->writeln('<info>Automatic update done - you should check your settings to be sure everything is fine</>');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function cleanUpdateOutput($buffer): string
|
||||
private function cleanUpdateOutput($buffer)
|
||||
{
|
||||
return strip_tags(preg_replace("/<br\W*?\/>/", "\n", $buffer));
|
||||
}
|
||||
|
||||
@@ -45,9 +45,6 @@ final class ConfigDiff extends CliCommand
|
||||
->addOption('diff-params', '', InputOption::VALUE_REQUIRED, 'Additional parameters for `diff`, e.g. --diff-params="--color=always"');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
require Froxlor::getInstallDir() . '/lib/functions.php';
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
namespace Froxlor\Cli;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Config\ConfigParser;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FileDir;
|
||||
@@ -41,6 +40,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
final class ConfigServices extends CliCommand
|
||||
{
|
||||
|
||||
private $yes_to_all_supported = [
|
||||
'bookworm',
|
||||
'bionic',
|
||||
@@ -62,9 +62,11 @@ final class ConfigServices extends CliCommand
|
||||
->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Install packages without asking questions (Debian/Ubuntu only currently)');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$result = $this->validateRequirements($output);
|
||||
$result = self::SUCCESS;
|
||||
|
||||
$result = $this->validateRequirements($input, $output);
|
||||
|
||||
require Froxlor::getInstallDir() . '/lib/functions.php';
|
||||
|
||||
@@ -91,7 +93,7 @@ final class ConfigServices extends CliCommand
|
||||
if ($result == self::SUCCESS) {
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
if ($input->getOption('create')) {
|
||||
$result = $this->createConfig($output, $io);
|
||||
$result = $this->createConfig($input, $output, $io);
|
||||
} elseif ($input->getOption('apply')) {
|
||||
$result = $this->applyConfig($input, $output, $io);
|
||||
} elseif ($input->getOption('list') || $input->getOption('daemon')) {
|
||||
@@ -156,10 +158,7 @@ final class ConfigServices extends CliCommand
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function createConfig(OutputInterface $output, SymfonyStyle $io): int
|
||||
private function createConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
|
||||
{
|
||||
$_daemons_config = [
|
||||
'distro' => ""
|
||||
@@ -286,10 +285,7 @@ final class ConfigServices extends CliCommand
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function applyConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io): int
|
||||
private function applyConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
|
||||
{
|
||||
$applyFile = $input->getOption('apply');
|
||||
|
||||
@@ -433,10 +429,7 @@ final class ConfigServices extends CliCommand
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getReplacerArray(): array
|
||||
private function getReplacerArray()
|
||||
{
|
||||
$customer_tmpdir = '/tmp/';
|
||||
if (Settings::Get('system.mod_fcgid') == '1' && Settings::Get('system.mod_fcgid_tmpdir') != '') {
|
||||
@@ -445,7 +438,7 @@ final class ConfigServices extends CliCommand
|
||||
$customer_tmpdir = Settings::Get('phpfpm.tmpdir');
|
||||
}
|
||||
|
||||
// try to convert nameserver hosts to ip's
|
||||
// try to convert namserver hosts to ip's
|
||||
$ns_ips = "";
|
||||
$known_ns_ips = [];
|
||||
if (Settings::Get('system.nameservers') != '') {
|
||||
@@ -491,12 +484,12 @@ final class ConfigServices extends CliCommand
|
||||
Database::needSqlData();
|
||||
$sql = Database::getSqlData();
|
||||
|
||||
return [
|
||||
$replace_arr = [
|
||||
'<SQL_UNPRIVILEGED_USER>' => $sql['user'],
|
||||
'<SQL_UNPRIVILEGED_PASSWORD>' => $sql['passwd'],
|
||||
'<SQL_DB>' => $sql['db'],
|
||||
'<SQL_HOST>' => $sql['host'],
|
||||
'<SQL_SOCKET>' => $sql['socket'] ?? null,
|
||||
'<SQL_SOCKET>' => isset($sql['socket']) ? $sql['socket'] : null,
|
||||
'<SERVERNAME>' => Settings::Get('system.hostname'),
|
||||
'<SERVERIP>' => Settings::Get('system.ipaddress'),
|
||||
'<NAMESERVERS>' => Settings::Get('system.nameservers'),
|
||||
@@ -515,5 +508,6 @@ final class ConfigServices extends CliCommand
|
||||
'<SSL_CERT_FILE>' => Settings::Get('system.ssl_cert_file'),
|
||||
'<SSL_KEY_FILE>' => Settings::Get('system.ssl_key_file'),
|
||||
];
|
||||
return $replace_arr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@
|
||||
namespace Froxlor\Cli;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Config\ConfigParser;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\Config\ConfigParser;
|
||||
use Froxlor\Install\Install;
|
||||
use Froxlor\Install\Install\Core;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
@@ -53,10 +53,7 @@ final class InstallCommand extends Command
|
||||
->addOption('create-userdata-from-str', 'c', InputOption::VALUE_REQUIRED, 'Creates lib/userdata.inc.php file from string created by web-install process');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$result = self::SUCCESS;
|
||||
|
||||
@@ -140,12 +137,10 @@ final class InstallCommand extends Command
|
||||
$decoded_input = [];
|
||||
}
|
||||
|
||||
return $this->showStep(0, $extended, $decoded_input);
|
||||
$result = $this->showStep(0, $extended, $decoded_input);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function showStep(int $step = 0, bool $extended = false, array $decoded_input = []): int
|
||||
{
|
||||
$result = self::SUCCESS;
|
||||
|
||||
@@ -25,20 +25,19 @@
|
||||
|
||||
namespace Froxlor\Cli;
|
||||
|
||||
use Exception;
|
||||
use PDO;
|
||||
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\CronConfig;
|
||||
use Froxlor\Cron\System\Extrausers;
|
||||
use Froxlor\Cron\TaskId;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FileDir;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\FroxlorLogger;
|
||||
use Froxlor\Settings;
|
||||
use Froxlor\System\Cronjob;
|
||||
use PDO;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
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
|
||||
@@ -58,12 +57,10 @@ final class MasterCron extends CliCommand
|
||||
->addOption('no-fork', 'N', InputOption::VALUE_NONE, 'Do not fork to background (traffic cron only).');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$result = $this->validateRequirements($output);
|
||||
$result = self::SUCCESS;
|
||||
$result = $this->validateRequirements($input, $output);
|
||||
|
||||
if ($result != self::SUCCESS) {
|
||||
// requirements failed, exit
|
||||
@@ -79,7 +76,7 @@ final class MasterCron extends CliCommand
|
||||
Cronjob::inserttask(TaskId::REBUILD_DNS);
|
||||
Cronjob::inserttask(TaskId::CREATE_QUOTA);
|
||||
Cronjob::inserttask(TaskId::REBUILD_CRON);
|
||||
$jobs[] = 'tasks';
|
||||
array_push($jobs, 'tasks');
|
||||
}
|
||||
define('CRON_IS_FORCED', 1);
|
||||
}
|
||||
@@ -97,7 +94,7 @@ final class MasterCron extends CliCommand
|
||||
foreach ($tasks_to_run as $ttr) {
|
||||
if (in_array($ttr, [TaskId::REBUILD_VHOST, TaskId::REBUILD_DNS, TaskId::CREATE_QUOTA, TaskId::REBUILD_CRON])) {
|
||||
Cronjob::inserttask($ttr);
|
||||
$jobs[] = 'tasks';
|
||||
array_push($jobs, 'tasks');
|
||||
} else {
|
||||
$output->writeln('<comment>Unknown task number "' . $ttr . '"</>');
|
||||
}
|
||||
@@ -143,12 +140,12 @@ final class MasterCron extends CliCommand
|
||||
$cronfile::run();
|
||||
}
|
||||
// free the lockfile
|
||||
$this->unlockJob();
|
||||
$this->unlockJob($job);
|
||||
}
|
||||
}
|
||||
|
||||
// regenerate nss-extrausers files / invalidate nscd cache (if used)
|
||||
$this->refreshUsers((int)$tasks_cnt['jobcnt']);
|
||||
$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
|
||||
@@ -160,13 +157,13 @@ final class MasterCron extends CliCommand
|
||||
CronConfig::checkCrondConfigurationFile();
|
||||
|
||||
// check for old/compatibility cronjob file
|
||||
if (file_exists(Froxlor::getInstallDir() . '/scripts/froxlor_master_cronjob.php')) {
|
||||
@unlink(Froxlor::getInstallDir() . '/scripts/froxlor_master_cronjob.php');
|
||||
@rmdir(Froxlor::getInstallDir() . '/scripts');
|
||||
if (file_exists(Froxlor::getInstallDir().'/scripts/froxlor_master_cronjob.php')) {
|
||||
@unlink(Froxlor::getInstallDir().'/scripts/froxlor_master_cronjob.php');
|
||||
@rmdir(Froxlor::getInstallDir().'/scripts');
|
||||
}
|
||||
|
||||
// reset cronlog-flag if set to "once"
|
||||
if ((int)Settings::Get('logger.log_cron') == 1) {
|
||||
if ((int) Settings::Get('logger.log_cron') == 1) {
|
||||
FroxlorLogger::getInstanceOf()->setCronLog(0);
|
||||
}
|
||||
|
||||
@@ -176,9 +173,27 @@ final class MasterCron extends CliCommand
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function refreshUsers(int $jobcount = 0)
|
||||
{
|
||||
if ($jobcount > 0) {
|
||||
if (Settings::Get('system.nssextrausers') == 1) {
|
||||
Extrausers::generateFiles($this->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
|
||||
@@ -205,44 +220,6 @@ final class MasterCron extends CliCommand
|
||||
$output->writeln('OK');
|
||||
}
|
||||
|
||||
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();
|
||||
} else {
|
||||
// cronjob still running, output info and stop
|
||||
$output->writeln([
|
||||
'<comment>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(): bool
|
||||
{
|
||||
return @unlink($this->lockFile);
|
||||
}
|
||||
|
||||
private function getCronModule(string $cronname, OutputInterface $output)
|
||||
{
|
||||
$upd_stmt = Database::prepare("
|
||||
@@ -258,24 +235,41 @@ final class MasterCron extends CliCommand
|
||||
return false;
|
||||
}
|
||||
|
||||
private function refreshUsers(int $jobcount = 0)
|
||||
private function lockJob(string $job, OutputInterface $output): bool
|
||||
{
|
||||
if ($jobcount > 0) {
|
||||
if (Settings::Get('system.nssextrausers') == 1) {
|
||||
Extrausers::generateFiles($this->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, [
|
||||
'>'
|
||||
$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([
|
||||
'<comment>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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,9 +43,9 @@ final class PhpSessionclean extends CliCommand
|
||||
$this->addArgument('max-lifetime', InputArgument::OPTIONAL, 'The number of seconds after which data will be seen as "garbage" and potentially cleaned up. Defaults to "1440"');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$result = $this->validateRequirements($output);
|
||||
$result = $this->validateRequirements($input, $output);
|
||||
|
||||
if ($result == self::SUCCESS) {
|
||||
if ((int)Settings::Get('phpfpm.enabled') == 1) {
|
||||
@@ -89,7 +89,7 @@ final class PhpSessionclean extends CliCommand
|
||||
|
||||
if (count($paths_to_clean) > 0) {
|
||||
foreach ($paths_to_clean as $ptc) {
|
||||
// find all files older than maxlifetime and delete them
|
||||
// find all files older then maxlifetime and delete them
|
||||
FileDir::safe_exec("find -O3 \"" . $ptc . "\" -ignore_readdir_race -depth -mindepth 1 -name 'sess_*' -type f -cmin \"+" . $maxlifetime . "\" -delete");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,14 @@
|
||||
namespace Froxlor\Cli;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Froxlor;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use PDO;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Froxlor;
|
||||
|
||||
final class RunApiCommand extends CliCommand
|
||||
{
|
||||
@@ -46,9 +48,11 @@ final class RunApiCommand extends CliCommand
|
||||
$this->addOption('show-params', 's', InputOption::VALUE_NONE, 'Show possible parameters for given api-command (given command will *not* be called)');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$result = $this->validateRequirements($output);
|
||||
$result = self::SUCCESS;
|
||||
|
||||
$result = $this->validateRequirements($input, $output);
|
||||
|
||||
require Froxlor::getInstallDir() . '/lib/functions.php';
|
||||
|
||||
@@ -106,9 +110,6 @@ final class RunApiCommand extends CliCommand
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function validateCommand(string $command): array
|
||||
{
|
||||
$command = explode(".", $command);
|
||||
|
||||
@@ -43,9 +43,11 @@ final class SwitchServerIp extends CliCommand
|
||||
->addOption('list', 'l', InputOption::VALUE_NONE, 'List all IP addresses currently added for this server in froxlor');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$result = $this->validateRequirements($output);
|
||||
$result = self::SUCCESS;
|
||||
|
||||
$result = $this->validateRequirements($input, $output);
|
||||
|
||||
if ($result == self::SUCCESS && $input->getOption('list') == false && $input->getOption('switch') == false) {
|
||||
$output->writeln('<error>Either --list or --switch option must be provided. Nothing to do, exiting.</>');
|
||||
@@ -81,7 +83,6 @@ final class SwitchServerIp extends CliCommand
|
||||
$ip_list = $input->getOption('switch');
|
||||
|
||||
$has_error = false;
|
||||
$ips_to_switch = [];
|
||||
foreach ($ip_list as $ips_combo) {
|
||||
$ip_pair = explode(",", $ips_combo);
|
||||
if (count($ip_pair) != 2) {
|
||||
|
||||
@@ -27,9 +27,9 @@ namespace Froxlor\Cli;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\Install\AutoUpdate;
|
||||
use Froxlor\Install\Update;
|
||||
use Froxlor\Settings;
|
||||
use Froxlor\Install\Update;
|
||||
use Froxlor\Install\AutoUpdate;
|
||||
use Froxlor\System\Mailer;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
@@ -44,7 +44,6 @@ final class UpdateCommand extends CliCommand
|
||||
$this->setName('froxlor:update');
|
||||
$this->setDescription('Check for newer version and update froxlor');
|
||||
$this->addOption('check-only', 'c', InputOption::VALUE_NONE, 'Only check for newer version and exit')
|
||||
->addOption('database', 'd', InputOption::VALUE_NONE, 'Only run database updates in case updates are done via apt or manually.')
|
||||
->addOption('mail-notify', 'm', InputOption::VALUE_NONE, 'Additionally inform administrator via email if a newer version was found')
|
||||
->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for download, extract and database-update, just do it (if not --check-only is set)')
|
||||
->addOption('integer-return', 'i', InputOption::VALUE_NONE, 'Return integer whether a new version is available or not (implies --check-only). Useful for programmatic use.');
|
||||
@@ -54,35 +53,7 @@ final class UpdateCommand extends CliCommand
|
||||
{
|
||||
$result = self::SUCCESS;
|
||||
|
||||
// database update only
|
||||
if ($input->getOption('database')) {
|
||||
$result = $this->validateRequirements($output, true);
|
||||
if ($result == self::SUCCESS) {
|
||||
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
|
||||
$output->writeln('<info>' . lng('updates.dbupdate_required') . '</>');
|
||||
if ($input->getOption('check-only')) {
|
||||
$output->writeln('<comment>Doing nothing because of "check-only" flag.</>');
|
||||
} else {
|
||||
$yestoall = $input->getOption('yes-to-all') !== false;
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
|
||||
if ($yestoall || $helper->ask($input, $output, $question)) {
|
||||
$result = $this->runUpdate($output, true);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
$output->writeln('<info>' . lng('update.noupdatesavail', (Settings::Get('system.update_channel') == 'testing' ? lng('serversettings.uc_testing') . ' ' : '')) . '</>');
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result = $this->validateRequirements($output);
|
||||
|
||||
if ($result != self::SUCCESS) {
|
||||
// requirements failed, exit
|
||||
return $result;
|
||||
}
|
||||
$result = $this->validateRequirements($input, $output);
|
||||
|
||||
require Froxlor::getInstallDir() . '/lib/functions.php';
|
||||
|
||||
@@ -100,7 +71,7 @@ final class UpdateCommand extends CliCommand
|
||||
}
|
||||
// there is a new version
|
||||
if ($input->getOption('check-only')) {
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel').' ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') == 'testing' ? 'testing ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||
} else {
|
||||
$text = lng('admin.newerversionavailable') . ' ' . lng('admin.newerversiondetails', [AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||
}
|
||||
@@ -175,7 +146,7 @@ final class UpdateCommand extends CliCommand
|
||||
$result = self::SUCCESS;
|
||||
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
|
||||
if ($yestoall || $helper->ask($input, $output, $question)) {
|
||||
$result = $this->runUpdate($output, true);
|
||||
$result = $this->updateDatabase();
|
||||
}
|
||||
} else {
|
||||
$errmsg = 'error.autoupdate_' . $auex;
|
||||
@@ -199,7 +170,7 @@ final class UpdateCommand extends CliCommand
|
||||
if ($input->getOption('mail-notify')) {
|
||||
$last_check_version = Settings::Get('system.update_notify_last');
|
||||
if (Update::versionInUpdate($last_check_version, AutoUpdate::getFromResult('version'))) {
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel').' ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') == 'testing' ? 'testing ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||
$mail = new Mailer(true);
|
||||
$mail->Body = $text;
|
||||
$mail->Subject = "[froxlor] " . lng('update.notify_subject');
|
||||
@@ -211,4 +182,22 @@ final class UpdateCommand extends CliCommand
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function updateDatabase()
|
||||
{
|
||||
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();
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function cleanUpdateOutput($buffer)
|
||||
{
|
||||
return strip_tags(preg_replace("/<br\W*?\/>/", "\n", $buffer));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,15 +26,15 @@
|
||||
namespace Froxlor\Cli;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Api\Commands\Admins;
|
||||
use Froxlor\Api\Commands\Customers;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\System\Crypt;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Froxlor\Api\Commands\Admins;
|
||||
use Froxlor\Api\Commands\Customers;
|
||||
use Froxlor\System\Crypt;
|
||||
use Froxlor\Froxlor;
|
||||
|
||||
final class UserCommand extends CliCommand
|
||||
{
|
||||
@@ -50,11 +50,11 @@ final class UserCommand extends CliCommand
|
||||
->addOption('show-info', 's', InputOption::VALUE_NONE, 'Output information details of given user');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$result = self::SUCCESS;
|
||||
|
||||
$result = $this->validateRequirements($output);
|
||||
$result = $this->validateRequirements($input, $output);
|
||||
|
||||
require Froxlor::getInstallDir() . '/lib/functions.php';
|
||||
|
||||
|
||||
@@ -48,16 +48,15 @@ final class ValidateAcmeWebroot extends CliCommand
|
||||
$this->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for confirmation, update files if necessary');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$result = $this->validateRequirements($output, true);
|
||||
$result = self::SUCCESS;
|
||||
|
||||
$result = $this->validateRequirements($input, $output, true);
|
||||
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
if ((int)Settings::Get('system.leenabled') == 0) {
|
||||
if ((int) Settings::Get('system.leenabled') == 0) {
|
||||
$io->info("Let's Encrypt not activated in froxlor settings.");
|
||||
$result = self::INVALID;
|
||||
}
|
||||
@@ -95,7 +94,7 @@ final class ValidateAcmeWebroot extends CliCommand
|
||||
$acmesh_challenge_dir = $recommended;
|
||||
// need to update the corresponding acme-alias config-file
|
||||
$acme_alias_file = Settings::Get('system.letsencryptacmeconf');
|
||||
$sed_params = "s@" . $former_value . "@" . $acmesh_challenge_dir . "@";
|
||||
$sed_params = "s@".$former_value."@" . $acmesh_challenge_dir . "@";
|
||||
FileDir::safe_exec('sed -i -e "' . $sed_params . '" ' . escapeshellarg($acme_alias_file));
|
||||
$count_changes++;
|
||||
}
|
||||
@@ -139,6 +138,8 @@ final class ValidateAcmeWebroot extends CliCommand
|
||||
$io->info("Domain '" . $domain . "' Le_Webroot value is correct");
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
95
lib/Froxlor/Cron/Backup/BackupCron.php
Normal file
95
lib/Froxlor/Cron/Backup/BackupCron.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?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\Cron\Backup;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Backup\Storages\StorageFactory;
|
||||
use Froxlor\Cron\Forkable;
|
||||
use Froxlor\Cron\FroxlorCron;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FroxlorLogger;
|
||||
use Froxlor\Settings;
|
||||
use PDO;
|
||||
|
||||
class BackupCron extends FroxlorCron
|
||||
{
|
||||
use Forkable;
|
||||
|
||||
public static function run()
|
||||
{
|
||||
if (!Settings::Get('backup.enabled')) {
|
||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'BackupCron: disabled - exiting');
|
||||
return -1;
|
||||
}
|
||||
|
||||
$stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "`");
|
||||
Database::pexecute($stmt);
|
||||
|
||||
$storages = [];
|
||||
while ($storage = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$storages[$storage['id']] = $storage;
|
||||
}
|
||||
|
||||
$stmt = Database::prepare("SELECT
|
||||
customerid,
|
||||
loginname,
|
||||
adminid,
|
||||
backup,
|
||||
guid,
|
||||
documentroot
|
||||
FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `backup` > 0
|
||||
");
|
||||
Database::pexecute($stmt);
|
||||
|
||||
$customers = [];
|
||||
while ($customer = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$customer['storage'] = $storages[$customer['backup']];
|
||||
$customers[] = $customer;
|
||||
}
|
||||
|
||||
self::runFork([self::class, 'handle'], $customers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function handle(array $userdata)
|
||||
{
|
||||
echo "BackupCron: started - creating customer backup for user " . $userdata['loginname'] . "\n";
|
||||
|
||||
$backupStorage = StorageFactory::fromType($userdata['storage']['type'], $userdata);
|
||||
// initialize storage
|
||||
$backupStorage->init();
|
||||
// do what is required to obtain files/archives and move/upload
|
||||
$backupStorage->prepareFiles();
|
||||
// upload/move to target
|
||||
$backupStorage->createFromFiles();
|
||||
// remove by retention
|
||||
$backupStorage->removeOld();
|
||||
|
||||
echo "BackupCron: finished - creating customer backup for user " . $userdata['loginname'] . "\n";
|
||||
}
|
||||
}
|
||||
@@ -244,7 +244,7 @@ abstract class DnsBase
|
||||
'zonefile' => '',
|
||||
'froxlorhost' => '1'
|
||||
];
|
||||
$domains[0] = $hostname_arr;
|
||||
$domains['none'] = $hostname_arr;
|
||||
}
|
||||
|
||||
if (empty($domains)) {
|
||||
|
||||
@@ -129,7 +129,7 @@ class Apache extends HttpConfigBase
|
||||
if ($row_ipsandports['ssl'] == '0' && Settings::Get('system.le_froxlor_redirect') == '1') {
|
||||
$is_redirect = true;
|
||||
// check whether froxlor uses Let's Encrypt and not cert is being generated yet
|
||||
// or a renewal is ongoing - disable redirect
|
||||
// or a renew is ongoing - disable redirect
|
||||
if (Settings::Get('system.le_froxlor_enabled') && ($this->froxlorVhostHasLetsEncryptCert() == false || $this->froxlorVhostLetsEncryptNeedsRenew())) {
|
||||
$this->virtualhosts_data[$vhosts_filename] .= '# temp. disabled ssl-redirect due to Let\'s Encrypt certificate generation.' . PHP_EOL;
|
||||
$is_redirect = false;
|
||||
@@ -515,7 +515,13 @@ class Apache extends HttpConfigBase
|
||||
*/
|
||||
private function createStandardDirectoryEntry()
|
||||
{
|
||||
$vhosts_filename = $this->getCustomVhostFilename('05_froxlor_dirfix_nofcgid.conf');
|
||||
$vhosts_folder = '';
|
||||
if (is_dir(Settings::Get('system.apacheconf_vhost'))) {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(Settings::Get('system.apacheconf_vhost'));
|
||||
} else {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(dirname(Settings::Get('system.apacheconf_vhost')));
|
||||
}
|
||||
$vhosts_filename = FileDir::makeCorrectFile($vhosts_folder . '/05_froxlor_dirfix_nofcgid.conf');
|
||||
|
||||
if (!isset($this->virtualhosts_data[$vhosts_filename])) {
|
||||
$this->virtualhosts_data[$vhosts_filename] = '';
|
||||
@@ -539,7 +545,7 @@ class Apache extends HttpConfigBase
|
||||
}
|
||||
$this->virtualhosts_data[$vhosts_filename] .= ' </Directory>' . "\n";
|
||||
|
||||
$ocsp_cache_filename = $this->getCustomVhostFilename('03_froxlor_ocsp_cache.conf');
|
||||
$ocsp_cache_filename = FileDir::makeCorrectFile($vhosts_folder . '/03_froxlor_ocsp_cache.conf');
|
||||
if (Settings::Get('system.use_ssl') == '1' && Settings::Get('system.apache24') == 1) {
|
||||
$this->virtualhosts_data[$ocsp_cache_filename] = 'SSLStaplingCache ' . Settings::Get('system.apache24_ocsp_cache_path') . "\n";
|
||||
} else {
|
||||
@@ -556,7 +562,14 @@ class Apache extends HttpConfigBase
|
||||
private function createStandardErrorHandler()
|
||||
{
|
||||
if (Settings::Get('defaultwebsrverrhandler.enabled') == '1' && (Settings::Get('defaultwebsrverrhandler.err401') != '' || Settings::Get('defaultwebsrverrhandler.err403') != '' || Settings::Get('defaultwebsrverrhandler.err404') != '' || Settings::Get('defaultwebsrverrhandler.err500') != '')) {
|
||||
$vhosts_filename = $this->getCustomVhostFilename('05_froxlor_default_errorhandler.conf');
|
||||
$vhosts_folder = '';
|
||||
if (is_dir(Settings::Get('system.apacheconf_vhost'))) {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(Settings::Get('system.apacheconf_vhost'));
|
||||
} else {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(dirname(Settings::Get('system.apacheconf_vhost')));
|
||||
}
|
||||
|
||||
$vhosts_filename = FileDir::makeCorrectFile($vhosts_folder . '/05_froxlor_default_errorhandler.conf');
|
||||
|
||||
if (!isset($this->virtualhosts_data[$vhosts_filename])) {
|
||||
$this->virtualhosts_data[$vhosts_filename] = '';
|
||||
@@ -1242,7 +1255,7 @@ class Apache extends HttpConfigBase
|
||||
// >=apache-2.4 enabled?
|
||||
if (Settings::Get('system.apache24') == '1') {
|
||||
$mypath_dir = new Directory($row_diroptions['path']);
|
||||
// only create the' require all granted' if there is no active directory-protection
|
||||
// only create the require all granted if there is not active directory-protection
|
||||
// for this path, as this would be the first require and therefore grant all access
|
||||
if ($mypath_dir->isUserProtected() == false) {
|
||||
$this->diroptions_data[$diroptions_filename] .= ' Require all granted' . "\n";
|
||||
|
||||
@@ -202,13 +202,4 @@ class HttpConfigBase
|
||||
}
|
||||
return FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $filename);
|
||||
}
|
||||
|
||||
protected function getCustomVhostFilename(string $name)
|
||||
{
|
||||
$vhosts_folder = FileDir::makeCorrectDir(dirname(Settings::Get('system.apacheconf_vhost')));
|
||||
if (is_dir(Settings::Get('system.apacheconf_vhost'))) {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(Settings::Get('system.apacheconf_vhost'));
|
||||
}
|
||||
return FileDir::makeCorrectFile($vhosts_folder . '/' . $name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
namespace Froxlor\Cron\Http\LetsEncrypt;
|
||||
|
||||
use Froxlor\Cron\FroxlorCron;
|
||||
use Froxlor\Cron\TaskId;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Domain\Domain;
|
||||
use Froxlor\FileDir;
|
||||
@@ -84,7 +83,7 @@ class AcmeSh extends FroxlorCron
|
||||
$renew_domains = self::renewDomains(true);
|
||||
if ($issue_froxlor || !empty($issue_domains) || !empty($renew_froxlor) || $renew_domains) {
|
||||
// insert task to generate certificates and vhost-configs
|
||||
Cronjob::inserttask(TaskId::REBUILD_VHOST);
|
||||
Cronjob::inserttask(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -204,7 +203,7 @@ class AcmeSh extends FroxlorCron
|
||||
// This is easiest done by just creating a new task ;)
|
||||
if ($changedetected) {
|
||||
if (self::$no_inserttask == false) {
|
||||
Cronjob::inserttask(TaskId::REBUILD_VHOST);
|
||||
Cronjob::inserttask(1);
|
||||
}
|
||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Let's Encrypt certificates have been updated");
|
||||
} else {
|
||||
|
||||
@@ -863,7 +863,13 @@ class Nginx extends HttpConfigBase
|
||||
// remove comments
|
||||
$vhost = implode("\n", preg_replace('/^(\s+)?#(.*)$/', '', explode("\n", $vhost)));
|
||||
// Break blocks into lines
|
||||
$vhost = preg_replace("/^(\s+)?location(.+)\{(.+)\}$/misU", "location $2 {\n $3 \n}", $vhost);
|
||||
$vhost = str_replace([
|
||||
"{",
|
||||
"}"
|
||||
], [
|
||||
" {\n",
|
||||
"\n}"
|
||||
], $vhost);
|
||||
// Break into array items
|
||||
$vhost = explode("\n", preg_replace('/[ \t]+/', ' ', trim(preg_replace('/\t+/', '', $vhost))));
|
||||
// Remove empty lines
|
||||
@@ -1161,7 +1167,14 @@ class Nginx extends HttpConfigBase
|
||||
private function createStandardErrorHandler()
|
||||
{
|
||||
if (Settings::Get('defaultwebsrverrhandler.enabled') == '1' && (Settings::Get('defaultwebsrverrhandler.err401') != '' || Settings::Get('defaultwebsrverrhandler.err403') != '' || Settings::Get('defaultwebsrverrhandler.err404') != '' || Settings::Get('defaultwebsrverrhandler.err500') != '')) {
|
||||
$vhosts_filename = $this->getCustomVhostFilename('05_froxlor_default_errorhandler.conf');
|
||||
$vhosts_folder = '';
|
||||
if (is_dir(Settings::Get('system.apacheconf_vhost'))) {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(Settings::Get('system.apacheconf_vhost'));
|
||||
} else {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(dirname(Settings::Get('system.apacheconf_vhost')));
|
||||
}
|
||||
|
||||
$vhosts_filename = FileDir::makeCorrectFile($vhosts_folder . '/05_froxlor_default_errorhandler.conf');
|
||||
|
||||
if (!isset($this->nginx_data[$vhosts_filename])) {
|
||||
$this->nginx_data[$vhosts_filename] = '';
|
||||
|
||||
@@ -154,21 +154,17 @@ class CurrentUser
|
||||
]);
|
||||
$addition = $result['emaildomains'] != 0;
|
||||
} elseif ($resource == 'subdomains') {
|
||||
if (Settings::IsInList('panel.customer_hide_options', 'domains')) {
|
||||
$addition = false;
|
||||
} else {
|
||||
$parentDomainCollection = (new Collection(
|
||||
SubDomains::class,
|
||||
$_SESSION['userinfo'],
|
||||
['sql_search' => [
|
||||
'd.parentdomainid' => 0,
|
||||
'd.deactivated' => 0,
|
||||
'd.id' => ['op' => '<>', 'value' => $_SESSION['userinfo']['standardsubdomain']]
|
||||
]
|
||||
]
|
||||
));
|
||||
$addition = $parentDomainCollection->count() != 0;
|
||||
}
|
||||
$parentDomainCollection = (new Collection(
|
||||
SubDomains::class,
|
||||
$_SESSION['userinfo'],
|
||||
['sql_search' => [
|
||||
'd.parentdomainid' => 0,
|
||||
'd.deactivated' => 0,
|
||||
'd.id' => ['op' => '<>', 'value' => $_SESSION['userinfo']['standardsubdomain']]
|
||||
]
|
||||
]
|
||||
));
|
||||
$addition = $parentDomainCollection->count() != 0;
|
||||
} elseif ($resource == 'domains') {
|
||||
$customerCollection = (new Collection(Customers::class, $_SESSION['userinfo']));
|
||||
$addition = $customerCollection->count() != 0;
|
||||
@@ -187,8 +183,7 @@ class CurrentUser
|
||||
if (self::getField('type_2fa') == 1) {
|
||||
// generate code
|
||||
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
|
||||
$secret = $tfa->createSecret();
|
||||
$code = $tfa->getCode($secret);
|
||||
$code = $tfa->getCode($tfa->createSecret());
|
||||
// set code for user
|
||||
$table = TABLE_PANEL_CUSTOMERS;
|
||||
$uid = 'customerid';
|
||||
@@ -198,7 +193,7 @@ class CurrentUser
|
||||
}
|
||||
$stmt = Database::prepare("UPDATE $table SET `data_2fa` = :d2fa WHERE `$uid` = :uid");
|
||||
Database::pexecute($stmt, [
|
||||
"d2fa" => $secret,
|
||||
"d2fa" => $code,
|
||||
"uid" => self::getField($uid)
|
||||
]);
|
||||
// build up & send email
|
||||
|
||||
@@ -256,7 +256,7 @@ class Domain
|
||||
]);
|
||||
$result = [];
|
||||
while ($entry = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$result[] = $entry['id'];
|
||||
$result = $entry['id'];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
@@ -320,15 +320,12 @@ class Domain
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function triggerLetsEncryptCSRForAliasDestinationDomain(
|
||||
int $aliasDestinationDomainID,
|
||||
int $aliasDestinationDomainID,
|
||||
FroxlorLogger $log
|
||||
) {
|
||||
if ($aliasDestinationDomainID > 0) {
|
||||
$log->logAction(
|
||||
FroxlorLogger::ADM_ACTION,
|
||||
LOG_INFO,
|
||||
"LetsEncrypt CSR triggered for domain ID " . $aliasDestinationDomainID
|
||||
);
|
||||
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO,
|
||||
"LetsEncrypt CSR triggered for domain ID " . $aliasDestinationDomainID);
|
||||
$upd_stmt = Database::prepare("UPDATE
|
||||
`" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`
|
||||
SET
|
||||
@@ -352,20 +349,15 @@ class Domain
|
||||
$acmesh = AcmeSh::getAcmeSh();
|
||||
if (file_exists($acmesh)) {
|
||||
$certificate_folder = AcmeSh::getWorkingDirFromEnv($domainname);
|
||||
$certificate_ecc_folder = AcmeSh::getWorkingDirFromEnv($domainname, true);
|
||||
if (file_exists($certificate_folder) || file_exists($certificate_ecc_folder)) {
|
||||
if (file_exists($certificate_folder)) {
|
||||
$params = " --remove -d " . $domainname;
|
||||
if (file_exists($certificate_ecc_folder)) {
|
||||
if (Settings::Get('system.leecc') > 0) {
|
||||
$params .= " --ecc";
|
||||
}
|
||||
// run remove command
|
||||
FileDir::safe_exec($acmesh . $params);
|
||||
// remove certificates directory
|
||||
if (file_exists($certificate_folder)) {
|
||||
FileDir::safe_exec('rm -rf ' . $certificate_folder);
|
||||
} elseif (file_exists($certificate_ecc_folder)) {
|
||||
FileDir::safe_exec('rm -rf ' . $certificate_ecc_folder);
|
||||
}
|
||||
FileDir::safe_exec('rm -rf ' . $certificate_folder);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -55,7 +55,6 @@ class IpAddr
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getSslIpPortCombinations(): array
|
||||
{
|
||||
@@ -76,7 +75,7 @@ class IpAddr
|
||||
$additional_conditions_params = [];
|
||||
$additional_conditions_array = [];
|
||||
|
||||
if (!empty($userinfo) && $userinfo['ip'] != '-1') {
|
||||
if ($userinfo['ip'] != '-1') {
|
||||
$admin_ip_stmt = Database::prepare("
|
||||
SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = IN (:ipid)
|
||||
");
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
namespace Froxlor;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Customer\Customer;
|
||||
use Froxlor\Database\Database;
|
||||
use PDO;
|
||||
use RecursiveCallbackFilterIterator;
|
||||
use Froxlor\Customer\Customer;
|
||||
use Froxlor\Database\Database;
|
||||
|
||||
class FileDir
|
||||
{
|
||||
@@ -51,12 +51,11 @@ class FileDir
|
||||
public static function mkDirWithCorrectOwnership(
|
||||
string $homeDir,
|
||||
string $dirToCreate,
|
||||
int $uid,
|
||||
int $gid,
|
||||
bool $placeindex = false,
|
||||
bool $allow_notwithinhomedir = false
|
||||
): bool
|
||||
{
|
||||
int $uid,
|
||||
int $gid,
|
||||
bool $placeindex = false,
|
||||
bool $allow_notwithinhomedir = false
|
||||
): bool {
|
||||
if ($homeDir != '' && $dirToCreate != '') {
|
||||
$homeDir = self::makeCorrectDir($homeDir);
|
||||
$dirToCreate = self::makeCorrectDir($dirToCreate);
|
||||
@@ -108,16 +107,15 @@ class FileDir
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a correct/secure dirname, means to add slashes at the beginning and at the end if there weren't
|
||||
* some. If $fixes_homedir is specified,
|
||||
*
|
||||
* Function which returns a correct dirname, means to add slashes at the beginning and at the end if there weren't
|
||||
* some
|
||||
*
|
||||
* @param string $dir the path to correct
|
||||
*
|
||||
* @return string the corrected path
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function makeCorrectDir(string $dir, string $fixed_homedir = ""): string
|
||||
public static function makeCorrectDir(string $dir): string
|
||||
{
|
||||
if (strlen($dir) > 0) {
|
||||
$dir = trim($dir);
|
||||
@@ -127,30 +125,6 @@ class FileDir
|
||||
if (substr($dir, 0, 1) != '/') {
|
||||
$dir = '/' . $dir;
|
||||
}
|
||||
|
||||
// if given, check that the target path is within the $fixed_homedir
|
||||
// by checking each folder for being a symlink and whether it targets
|
||||
// the customers homedir or points outside of it
|
||||
if (!empty($fixed_homedir)) {
|
||||
$to_check = explode("/", substr($dir, strlen($fixed_homedir) + 1), -1);
|
||||
$check_dir = substr($fixed_homedir, 0, -1);
|
||||
// Symlink check
|
||||
foreach ($to_check as $sub_dir) {
|
||||
$check_dir .= '/' . $sub_dir;
|
||||
if (is_link($check_dir)) {
|
||||
$original_target = $check_dir;
|
||||
$check_dir = readlink($check_dir);
|
||||
if (substr($check_dir, 0, strlen($fixed_homedir)) != $fixed_homedir) {
|
||||
throw new Exception("Found symlink pointing outside of customer home directory: " . substr($original_target, strlen($fixed_homedir)));
|
||||
}
|
||||
}
|
||||
}
|
||||
// check for the path to be within the given homedir
|
||||
if (substr($dir, 0, strlen($fixed_homedir)) != $fixed_homedir) {
|
||||
throw new Exception("Target path not within the required customer home directory");
|
||||
}
|
||||
}
|
||||
|
||||
return self::makeSecurePath($dir);
|
||||
}
|
||||
throw new Exception("Cannot validate directory in " . __FUNCTION__ . " which is very dangerous.");
|
||||
@@ -257,41 +231,6 @@ class FileDir
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read unconfigured-domain template from database if exists or fallback to default
|
||||
*
|
||||
* @param string $servername
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getUnknownDomainTemplate(string $servername = "")
|
||||
{
|
||||
$result_stmt = Database::prepare("
|
||||
SELECT * FROM `" . TABLE_PANEL_TEMPLATES . "` WHERE `templategroup` = 'files' AND `varname` = 'unconfigured_html'
|
||||
");
|
||||
Database::pexecute($result_stmt);
|
||||
if (Database::num_rows() > 0) {
|
||||
$template = $result_stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$replace_arr = [
|
||||
'SERVERNAME' => $servername,
|
||||
];
|
||||
$tpl_content = PhpHelper::replaceVariables($template['value'], $replace_arr);
|
||||
$tpl_ext = $template['file_extension'];
|
||||
} else {
|
||||
$tpl_ext = 'html';
|
||||
$unconfiguredPath = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/templates/misc/unconfigured/index.html');
|
||||
if (file_exists($unconfiguredPath)) {
|
||||
$tpl_content = file_get_contents($unconfiguredPath);
|
||||
} else {
|
||||
$tpl_content = lng('admin.templates.unconfigured_content_fallback');
|
||||
}
|
||||
}
|
||||
$redirect_file = FileDir::makeCorrectFile(Froxlor::getInstallDir().'/notice.'.$tpl_ext);
|
||||
file_put_contents($redirect_file, $tpl_content);
|
||||
return basename($redirect_file);
|
||||
}
|
||||
|
||||
/**
|
||||
* store the default index-file in a given destination folder
|
||||
*
|
||||
@@ -306,13 +245,12 @@ class FileDir
|
||||
public static function storeDefaultIndex(
|
||||
string $loginname,
|
||||
string $destination,
|
||||
$logger = null,
|
||||
bool $force = false
|
||||
)
|
||||
{
|
||||
$logger = null,
|
||||
bool $force = false
|
||||
) {
|
||||
if ($force || (int)Settings::Get('system.store_index_file_subs') == 1) {
|
||||
$result_stmt = Database::prepare("
|
||||
SELECT `t`.`value`, `t`.`file_extension`, `c`.`email` AS `customer_email`, `a`.`email` AS `admin_email`, `c`.`loginname` AS `customer_login`, `a`.`loginname` AS `admin_login`
|
||||
SELECT `t`.`value`, `c`.`email` AS `customer_email`, `a`.`email` AS `admin_email`, `c`.`loginname` AS `customer_login`, `a`.`loginname` AS `admin_login`
|
||||
FROM `" . TABLE_PANEL_CUSTOMERS . "` AS `c` INNER JOIN `" . TABLE_PANEL_ADMINS . "` AS `a`
|
||||
ON `c`.`adminid` = `a`.`adminid`
|
||||
INNER JOIN `" . TABLE_PANEL_TEMPLATES . "` AS `t`
|
||||
@@ -335,7 +273,7 @@ class FileDir
|
||||
|
||||
// replaceVariables
|
||||
$htmlcontent = PhpHelper::replaceVariables($template['value'], $replace_arr);
|
||||
$indexhtmlpath = self::makeCorrectFile($destination . '/index.' . $template['file_extension']);
|
||||
$indexhtmlpath = self::makeCorrectFile($destination . '/index.' . Settings::Get('system.index_file_extension'));
|
||||
$index_html_handler = fopen($indexhtmlpath, 'w');
|
||||
fwrite($index_html_handler, $htmlcontent);
|
||||
fclose($index_html_handler);
|
||||
@@ -343,7 +281,7 @@ class FileDir
|
||||
$logger->logAction(
|
||||
FroxlorLogger::CRON_ACTION,
|
||||
LOG_NOTICE,
|
||||
'Creating \'index.' . $template['file_extension'] . '\' for Customer \'' . $template['customer_login'] . '\' based on template in directory ' . escapeshellarg($indexhtmlpath)
|
||||
'Creating \'index.' . Settings::Get('system.index_file_extension') . '\' for Customer \'' . $template['customer_login'] . '\' based on template in directory ' . escapeshellarg($indexhtmlpath)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -31,16 +31,14 @@ final class Froxlor
|
||||
{
|
||||
|
||||
// Main version variable
|
||||
const VERSION = '2.1.2';
|
||||
const VERSION = '2.1.0-dev1';
|
||||
|
||||
// Database version (YYYYMMDDC where C is a daily counter)
|
||||
const DBVERSION = '202312120';
|
||||
const DBVERSION = '202305240';
|
||||
|
||||
// Distribution branding-tag (used for Debian etc.)
|
||||
const BRANDING = '';
|
||||
|
||||
const DOCS_URL = 'https://docs.froxlor.org/v2.1/';
|
||||
|
||||
/**
|
||||
* return path to where froxlor is installed, e.g.
|
||||
* /var/www/froxlor/
|
||||
|
||||
@@ -104,15 +104,17 @@ class FroxlorLogger
|
||||
self::$ml->pushHandler(new SyslogHandler('froxlor', LOG_USER, Logger::DEBUG));
|
||||
break;
|
||||
case 'file':
|
||||
$setings_logfile = Settings::Get('logger.logfile');
|
||||
if (empty($setings_logfile)) {
|
||||
Settings::Set('logger.logfile', 'froxlor.log');
|
||||
}
|
||||
$logger_logfile = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/logs/' . Settings::Get('logger.logfile'));
|
||||
// is_writable needs an existing file to check if it's actually writable
|
||||
if (!@touch($logger_logfile) || !is_writable($logger_logfile)) {
|
||||
// not writable in our own directory? Skip
|
||||
break;
|
||||
@touch($logger_logfile);
|
||||
if (empty($logger_logfile) || !is_writable($logger_logfile)) {
|
||||
Settings::Set('logger.logfile', 'froxlor.log');
|
||||
$logger_logfile = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/logs/froxlor.log');
|
||||
@touch($logger_logfile);
|
||||
if (empty($logger_logfile) || !is_writable($logger_logfile)) {
|
||||
// not writable in our own directory? Skip
|
||||
break;
|
||||
}
|
||||
}
|
||||
self::$ml->pushHandler(new StreamHandler($logger_logfile, Logger::DEBUG));
|
||||
break;
|
||||
|
||||
@@ -64,7 +64,7 @@ class IdnaWrapper
|
||||
*/
|
||||
public function encode(string $to_encode): string
|
||||
{
|
||||
$to_encode = $this->isUtf8($to_encode) ? $to_encode : mb_convert_encoding($to_encode, 'UTF-8');
|
||||
$to_encode = $this->isUtf8($to_encode) ? $to_encode : utf8_encode($to_encode);
|
||||
try {
|
||||
return $this->idna_converter->encode($to_encode);
|
||||
} catch (InvalidArgumentException $iae) {
|
||||
|
||||
@@ -51,13 +51,13 @@ class AutoUpdate
|
||||
|
||||
/**
|
||||
* returns status about whether there is a newer version
|
||||
*
|
||||
*
|
||||
* 0 = no new version available
|
||||
* 1 = new version available
|
||||
* -1 = remote error message
|
||||
* >1 = local error message
|
||||
*
|
||||
* @return int
|
||||
* @return int
|
||||
*/
|
||||
public static function checkVersion(): int
|
||||
{
|
||||
@@ -68,12 +68,6 @@ class AutoUpdate
|
||||
$channel = '';
|
||||
if (Settings::Get('system.update_channel') == 'testing') {
|
||||
$channel = '/testing';
|
||||
} elseif (Settings::Get('system.update_channel') == 'nightly') {
|
||||
if (empty(Froxlor::BRANDING)) {
|
||||
$channel = '/nightly.0000000';
|
||||
} else {
|
||||
$channel = '/' . substr(Froxlor::BRANDING, 1);
|
||||
}
|
||||
}
|
||||
$latestversion = HttpClient::urlGet(self::UPDATE_URI . Froxlor::VERSION . $channel, true, 3);
|
||||
} catch (Exception $e) {
|
||||
@@ -87,7 +81,7 @@ class AutoUpdate
|
||||
if (!empty(self::$latestversion['error']) && self::$latestversion['error']) {
|
||||
$result = -1;
|
||||
self::$lasterror = self::$latestversion['message'];
|
||||
} elseif (isset(self::$latestversion['has_latest']) && self::$latestversion['has_latest'] == false) {
|
||||
} else if (isset(self::$latestversion['has_latest']) && self::$latestversion['has_latest'] == false) {
|
||||
$result = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,14 +26,13 @@
|
||||
namespace Froxlor\Install;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Config\ConfigParser;
|
||||
use Froxlor\Froxlor;
|
||||
use PDO;
|
||||
use Froxlor\Install\Install\Core;
|
||||
use Froxlor\System\IPTools;
|
||||
use Froxlor\UI\Panel\UI;
|
||||
use Froxlor\UI\Request;
|
||||
use Froxlor\Config\ConfigParser;
|
||||
use Froxlor\Validate\Validate;
|
||||
use PDO;
|
||||
use Froxlor\System\IPTools;
|
||||
|
||||
class Install
|
||||
{
|
||||
@@ -42,37 +41,34 @@ class Install
|
||||
public $maxSteps;
|
||||
public $phpVersion;
|
||||
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', 'gnupg'];
|
||||
public array $suggestions = [];
|
||||
public array $criticals = [];
|
||||
public array $loadedExtensions;
|
||||
public array $supportedOS = [];
|
||||
public array $webserverBackend = [
|
||||
'php-fpm' => 'PHP-FPM',
|
||||
'fcgid' => 'FCGID (apache2 only)',
|
||||
'fcgid' => 'FCGID',
|
||||
'mod_php' => 'mod_php (not recommended)',
|
||||
];
|
||||
|
||||
public function __construct(array $cliData = [])
|
||||
{
|
||||
// set actual php version and extensions
|
||||
$this->phpVersion = phpversion();
|
||||
$this->loadedExtensions = get_loaded_extensions();
|
||||
|
||||
// get all supported OS
|
||||
// show list of available distro's
|
||||
$distros = glob(dirname(__DIR__, 3) . '/lib/configfiles/*.xml');
|
||||
$distributions_select[''] = '-';
|
||||
if (in_array('xml', $this->loadedExtensions)) {
|
||||
// read in all the distros
|
||||
foreach ($distros as $distribution) {
|
||||
// get configparser object
|
||||
$dist = new ConfigParser($distribution);
|
||||
// store in tmp array
|
||||
$this->supportedOS[str_replace(".xml", "", strtolower(basename($distribution)))] = $dist->getCompleteDistroName();
|
||||
}
|
||||
// sort by distribution name
|
||||
asort($this->supportedOS);
|
||||
// read in all the distros
|
||||
foreach ($distros as $distribution) {
|
||||
// get configparser object
|
||||
$dist = new ConfigParser($distribution);
|
||||
// store in tmp array
|
||||
$this->supportedOS[str_replace(".xml", "", strtolower(basename($distribution)))] = $dist->getCompleteDistroName();
|
||||
}
|
||||
// sort by distribution name
|
||||
asort($this->supportedOS);
|
||||
|
||||
// guess distribution and webserver to preselect in formfield
|
||||
$webserverBackend = $this->webserverBackend;
|
||||
@@ -88,6 +84,10 @@ class Install
|
||||
$this->extendedView = $cliData['extended'] ?? Request::any('extended', 0);
|
||||
$this->maxSteps = count($this->formfield['install']['sections']);
|
||||
|
||||
// set actual php version and extensions
|
||||
$this->phpVersion = phpversion();
|
||||
$this->loadedExtensions = get_loaded_extensions();
|
||||
|
||||
if (empty($cliData)) {
|
||||
// set global variables
|
||||
UI::twig()->addGlobal('install_mode', true);
|
||||
@@ -99,7 +99,7 @@ class Install
|
||||
}
|
||||
|
||||
// check for url manipulation or wrong step
|
||||
if ((isset($_SESSION['installation']['stepCompleted']) && ($this->currentStep + 1) > $_SESSION['installation']['stepCompleted'])
|
||||
if ((isset($_SESSION['installation']['stepCompleted']) && ($this->currentStep + 1) > ($_SESSION['installation']['stepCompleted'] ?? 0))
|
||||
|| (!isset($_SESSION['installation']['stepCompleted']) && $this->currentStep > 0)
|
||||
) {
|
||||
$this->currentStep = isset($_SESSION['installation']['stepCompleted']) ? $_SESSION['installation']['stepCompleted'] + 1 : 1;
|
||||
@@ -136,7 +136,6 @@ class Install
|
||||
'section' => $this->formfield['install']['sections']['step' . $this->currentStep] ?? [],
|
||||
'error' => $error ?? null,
|
||||
'extended' => $this->extendedView,
|
||||
'csrf_token' => Froxlor::genSessionId(20),
|
||||
]);
|
||||
|
||||
// output view
|
||||
@@ -152,14 +151,16 @@ class Install
|
||||
if ($this->currentStep <= $this->maxSteps) {
|
||||
// Validate user data
|
||||
$validatedData = $this->validateRequest($formfield['sections']['step' . $this->currentStep]['fields']);
|
||||
// Check database connection (
|
||||
if ($this->currentStep == 1) {
|
||||
// Check database connection
|
||||
$this->checkDatabase($validatedData);
|
||||
} elseif ($this->currentStep == 2) {
|
||||
// Check validity of admin user data
|
||||
}
|
||||
// Check validity of admin user data
|
||||
elseif ($this->currentStep == 2) {
|
||||
$this->checkAdminUser($validatedData);
|
||||
} elseif ($this->currentStep == 3) {
|
||||
// Check validity of system data
|
||||
}
|
||||
// Check validity of system data
|
||||
elseif ($this->currentStep == 3) {
|
||||
$this->checkSystem($validatedData);
|
||||
}
|
||||
$validatedData['stepCompleted'] = ($this->currentStep < $this->maxSteps) ? $this->currentStep : ($this->maxSteps - 1);
|
||||
@@ -191,7 +192,7 @@ class Install
|
||||
private function checkInstallStateFinished(): bool
|
||||
{
|
||||
$core = new Core($_SESSION['installation']);
|
||||
if (isset($_SESSION['installation']['manual_config']) && (int)$_SESSION['installation']['manual_config'] == 1) {
|
||||
if (isset($_SESSION['installation']['manual_config']) && (int) $_SESSION['installation']['manual_config'] == 1) {
|
||||
$core->createUserdataConf();
|
||||
return true;
|
||||
}
|
||||
@@ -199,7 +200,7 @@ class Install
|
||||
$stmt = $pdo->prepare("SELECT `value` FROM `panel_settings` WHERE `settinggroup` = 'panel' AND `varname` = 'is_configured'");
|
||||
$stmt->execute();
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($result && (int)$result['value'] == 1) {
|
||||
if ($result && (int) $result['value'] == 1) {
|
||||
$core->createUserdataConf();
|
||||
return true;
|
||||
}
|
||||
@@ -222,7 +223,7 @@ class Install
|
||||
}
|
||||
|
||||
// check for required extensions
|
||||
foreach (Requirements::REQUIRED_EXTENSIONS as $requiredExtension) {
|
||||
foreach ($this->requiredExtensions as $requiredExtension) {
|
||||
if (in_array($requiredExtension, $this->loadedExtensions)) {
|
||||
continue;
|
||||
}
|
||||
@@ -230,7 +231,7 @@ class Install
|
||||
}
|
||||
|
||||
// check for suggested extensions
|
||||
foreach (Requirements::SUGGESTED_EXTENSIONS as $suggestedExtension) {
|
||||
foreach ($this->suggestedExtensions as $suggestedExtension) {
|
||||
if (in_array($suggestedExtension, $this->loadedExtensions)) {
|
||||
continue;
|
||||
}
|
||||
@@ -249,11 +250,11 @@ class Install
|
||||
*/
|
||||
private function getInformationText(): string
|
||||
{
|
||||
if (version_compare(Requirements::REQUIRED_VERSION, PHP_VERSION, "<")) {
|
||||
if (version_compare($this->requiredVersion, PHP_VERSION, "<")) {
|
||||
$text = lng('install.phpinfosuccess', [$this->phpVersion]);
|
||||
} else {
|
||||
$text = lng('install.phpinfowarn', [Requirements::REQUIRED_VERSION]);
|
||||
$this->criticals[] = lng('install.phpinfoupdate', [$this->phpVersion, Requirements::REQUIRED_VERSION]);
|
||||
$text = lng('install.phpinfowarn', [$this->requiredVersion]);
|
||||
$this->criticals[] = lng('install.phpinfoupdate', [$this->phpVersion, $this->requiredVersion]);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
@@ -301,9 +302,9 @@ class Install
|
||||
throw new Exception(lng('install.errors.nov4andnov6ip'));
|
||||
} elseif (!empty($serveripv4) && (!Validate::validate_ip2($serveripv4, true, '', false, true) || IPTools::is_ipv6($serveripv4))) {
|
||||
throw new Exception(lng('error.invalidip', [$serveripv4]));
|
||||
} elseif (!empty($serveripv6) && (!Validate::validate_ip2($serveripv6, true, '', false, true) || !IPTools::is_ipv6($serveripv6))) {
|
||||
} elseif (!empty($serveripv6) && (!Validate::validate_ip2($serveripv6, true, '', false, true) || IPTools::is_ipv6($serveripv6) == false)) {
|
||||
throw new Exception(lng('error.invalidip', [$serveripv6]));
|
||||
} elseif (!Validate::validateDomain($servername)) {
|
||||
} elseif (!Validate::validateDomain($servername) && !Validate::validateLocalHostname($servername)) {
|
||||
throw new Exception(lng('install.errors.servernameneedstobevalid'));
|
||||
} elseif (posix_getpwnam($httpuser) === false) {
|
||||
throw new Exception(lng('install.errors.websrvuserdoesnotexist'));
|
||||
@@ -409,7 +410,7 @@ class Install
|
||||
} else {
|
||||
$osrf = explode("\n", file_get_contents('/etc/os-release'));
|
||||
foreach ($osrf as $line) {
|
||||
$osrfline = explode("=", $line);
|
||||
$osrfline = explode("\n", $line);
|
||||
if ($osrfline[0] == 'VERSION_CODENAME') {
|
||||
$os_dist['VERSION_CODENAME'] = $osrfline[1];
|
||||
} else if ($osrfline[0] == 'ID') {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Install;
|
||||
|
||||
class Requirements
|
||||
{
|
||||
const REQUIRED_VERSION = '7.4.0';
|
||||
const REQUIRED_EXTENSIONS = ['session', 'ctype', 'xml', 'filter', 'posix', 'mbstring', 'pdo_mysql', 'curl', 'gmp', 'json', 'gd'];
|
||||
const SUGGESTED_EXTENSIONS = ['bcmath', 'zip', 'gnupg'];
|
||||
}
|
||||
@@ -85,7 +85,7 @@ class Update
|
||||
self::$update_tasks[self::$task_counter]['result'] = 1;
|
||||
break;
|
||||
default:
|
||||
self::$update_tasks[self::$task_counter]['result'] = -1;
|
||||
self::$update_tasks[self::$task_counter]['result'] = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -220,11 +220,8 @@ class PhpHelper
|
||||
if (is_dir($data_dirname)) {
|
||||
$data_dirhandle = opendir($data_dirname);
|
||||
while (false !== ($data_filename = readdir($data_dirhandle))) {
|
||||
if ($data_filename != '.'
|
||||
&& $data_filename != '..'
|
||||
&& $data_filename != ''
|
||||
&& substr($data_filename, -4) == '.php'
|
||||
) {
|
||||
if ($data_filename != '.' && $data_filename != '..' && $data_filename != '' && substr($data_filename,
|
||||
-4) == '.php') {
|
||||
$data_files[] = $data_dirname . $data_filename;
|
||||
}
|
||||
}
|
||||
@@ -418,8 +415,8 @@ class PhpHelper
|
||||
*/
|
||||
public static function recursive_array_search(
|
||||
string $needle,
|
||||
array $haystack,
|
||||
array &$keys = [],
|
||||
array $haystack,
|
||||
array &$keys = [],
|
||||
string $currentKey = ''
|
||||
): bool {
|
||||
foreach ($haystack as $key => $value) {
|
||||
@@ -461,10 +458,6 @@ class PhpHelper
|
||||
'directory_password',
|
||||
'ftp_password',
|
||||
'mysql_password',
|
||||
'mysql_root_pass',
|
||||
'mysql_unprivileged_pass',
|
||||
'admin_pass',
|
||||
'admin_pass_confirm',
|
||||
];
|
||||
if (!empty($global)) {
|
||||
$tmp = $global;
|
||||
@@ -564,17 +557,4 @@ class PhpHelper
|
||||
}
|
||||
return $tab . $str;
|
||||
}
|
||||
|
||||
public static function array_merge_recursive_distinct(array &$array1, array &$array2)
|
||||
{
|
||||
$merged = $array1;
|
||||
foreach ($array2 as $key => &$value) {
|
||||
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
|
||||
$merged[$key] = self::array_merge_recursive_distinct($merged[$key], $value);
|
||||
} else {
|
||||
$merged[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $merged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class SImExporter
|
||||
public static function export()
|
||||
{
|
||||
$settings_definitions = [];
|
||||
foreach (PhpHelper::loadConfigArrayDir(Froxlor::getInstallDir() . '/actions/admin/settings/')['groups'] as $group) {
|
||||
foreach (PhpHelper::loadConfigArrayDir('./actions/admin/settings/')['groups'] as $group) {
|
||||
foreach ($group['fields'] as $field) {
|
||||
$settings_definitions[$field['settinggroup']][$field['varname']] = $field;
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
|
||||
namespace Froxlor\Traffic;
|
||||
|
||||
use Froxlor\Api\Commands\Customers;
|
||||
use Froxlor\Api\Commands\Traffic as TrafficAPI;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Api\Commands\Customers;
|
||||
use Froxlor\UI\Collection;
|
||||
use Froxlor\Api\Commands\Traffic as TrafficAPI;
|
||||
|
||||
class Traffic
|
||||
{
|
||||
@@ -38,10 +38,10 @@ class Traffic
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getCustomerStats(array $userinfo, string $range = null, bool $overview = false): array
|
||||
public static function getCustomerStats(array $userinfo, string $range = null): array
|
||||
{
|
||||
$trafficCollectionObj = (new Collection(TrafficAPI::class, $userinfo,
|
||||
self::getParamsByRange($range, ['customer_traffic' => true])));
|
||||
self::getParamsByRange($range, ['customer_traffic' => true,])));
|
||||
if ($userinfo['adminsession'] == 1) {
|
||||
$trafficCollectionObj->has('customer', Customers::class, 'customerid', 'customerid');
|
||||
}
|
||||
@@ -53,36 +53,27 @@ class Traffic
|
||||
$months = [];
|
||||
$days = [];
|
||||
foreach ($trafficCollection['data']['list'] as $item) {
|
||||
$http = $item['http'];
|
||||
$ftp = ($item['ftp_up'] + $item['ftp_down']);
|
||||
$mail = $item['mail'];
|
||||
$total = $http + $ftp + $mail;
|
||||
|
||||
// per user total
|
||||
if ($userinfo['adminsession'] == 1) {
|
||||
$users[$item['customerid']]['loginname'] = $item['customer']['loginname'];
|
||||
}
|
||||
$users[$item['customerid']]['total'] += $total;
|
||||
$users[$item['customerid']]['http'] += $http;
|
||||
$users[$item['customerid']]['ftp'] += $ftp;
|
||||
$users[$item['customerid']]['mail'] += $mail;
|
||||
if (!$overview) {
|
||||
// per year
|
||||
$years[$item['year']]['total'] += $total;
|
||||
$years[$item['year']]['http'] += $http;
|
||||
$years[$item['year']]['ftp'] += $ftp;
|
||||
$years[$item['year']]['mail'] += $mail;
|
||||
// per month
|
||||
$months[$item['month'] . '/' . $item['year']]['total'] += $total;
|
||||
$months[$item['month'] . '/' . $item['year']]['http'] += $http;
|
||||
$months[$item['month'] . '/' . $item['year']]['ftp'] += $ftp;
|
||||
$months[$item['month'] . '/' . $item['year']]['mail'] += $mail;
|
||||
// per day
|
||||
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['total'] += $total;
|
||||
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['http'] += $http;
|
||||
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['ftp'] += $ftp;
|
||||
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['mail'] += $mail;
|
||||
}
|
||||
$users[$item['customerid']]['loginname'] = $item['customer']['loginname'];
|
||||
$users[$item['customerid']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
|
||||
$users[$item['customerid']]['http'] += $item['http'];
|
||||
$users[$item['customerid']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
|
||||
$users[$item['customerid']]['mail'] += $item['mail'];
|
||||
// per year
|
||||
$years[$item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
|
||||
$years[$item['year']]['http'] += $item['http'];
|
||||
$years[$item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
|
||||
$years[$item['year']]['mail'] += $item['mail'];
|
||||
// per month
|
||||
$months[$item['month'] . '/' . $item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
|
||||
$months[$item['month'] . '/' . $item['year']]['http'] += $item['http'];
|
||||
$months[$item['month'] . '/' . $item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
|
||||
$months[$item['month'] . '/' . $item['year']]['mail'] += $item['mail'];
|
||||
// per day
|
||||
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
|
||||
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['http'] += $item['http'];
|
||||
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
|
||||
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['mail'] += $item['mail'];
|
||||
}
|
||||
|
||||
// calculate overview for given range from users
|
||||
@@ -94,13 +85,10 @@ class Traffic
|
||||
$metrics['mail'] += $user['mail'];
|
||||
}
|
||||
|
||||
$years_avail = [];
|
||||
if (!$overview) {
|
||||
// get all possible years for filter
|
||||
$sel_stmt = Database::prepare("SELECT DISTINCT year FROM `" . TABLE_PANEL_TRAFFIC . "` WHERE 1 ORDER BY `year` DESC");
|
||||
Database::pexecute($sel_stmt);
|
||||
$years_avail = $sel_stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
// get all possible years for filter
|
||||
$sel_stmt = Database::prepare("SELECT DISTINCT year FROM `" . TABLE_PANEL_TRAFFIC . "` WHERE 1 ORDER BY `year` DESC");
|
||||
Database::pexecute($sel_stmt);
|
||||
$years_avail = $sel_stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
return [
|
||||
'metrics' => $metrics,
|
||||
|
||||
50
lib/Froxlor/UI/Callbacks/Backup.php
Normal file
50
lib/Froxlor/UI/Callbacks/Backup.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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\UI\Callbacks;
|
||||
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\UI\Panel\UI;
|
||||
|
||||
class Backup
|
||||
{
|
||||
public static function backupStorageLink(array $attributes)
|
||||
{
|
||||
$sel_stmt = Database::prepare("SELECT `description` FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` WHERE `id` = :id");
|
||||
$backupstorage = Database::pexecute_first($sel_stmt, ['id' => $attributes['data']]);
|
||||
if ((int)UI::getCurrentUser()['adminsession'] == 1 && UI::getCurrentUser()['change_serversettings']) {
|
||||
$linker = UI::getLinker();
|
||||
$result = '<a href="' . $linker->getLink([
|
||||
'section' => 'backups',
|
||||
'page' => 'storages',
|
||||
'searchfield' => 'id',
|
||||
'searchtext' => $attributes['data'],
|
||||
]) . '">' . $backupstorage['description'] . '</a>';
|
||||
} else {
|
||||
$result = $backupstorage['description'];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -26,19 +26,17 @@
|
||||
namespace Froxlor\UI\Callbacks;
|
||||
|
||||
use Froxlor\Settings;
|
||||
use Froxlor\System\Markdown;
|
||||
|
||||
class Customer
|
||||
{
|
||||
public static function isLocked(array $attributes): bool
|
||||
public static function isLocked(array $attributes)
|
||||
{
|
||||
return $attributes['fields']['loginfail_count'] >= Settings::Get('login.maxloginattempts')
|
||||
&& $attributes['fields']['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime'));
|
||||
}
|
||||
|
||||
public static function hasNote(array $attributes): bool
|
||||
public static function hasNote(array $attributes)
|
||||
{
|
||||
$cleanNote = Markdown::cleanCustomNotes($attributes['fields']['custom_notes'] ?? "");
|
||||
return !empty($cleanNote);
|
||||
return !empty($attributes['fields']['custom_notes']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,11 +33,6 @@ use Froxlor\UI\Panel\UI;
|
||||
|
||||
class Domain
|
||||
{
|
||||
public static function domainLink(array $attributes)
|
||||
{
|
||||
return '<a href="https://' . $attributes['data'] . '" target="_blank">' . $attributes['data'] . '</a>';
|
||||
}
|
||||
|
||||
public static function domainWithCustomerLink(array $attributes)
|
||||
{
|
||||
$linker = UI::getLinker();
|
||||
@@ -195,7 +190,7 @@ class Domain
|
||||
// specified certificate for domain
|
||||
if ($attributes['fields']['domain_hascert'] == 1) {
|
||||
$result['icon'] .= ' text-success';
|
||||
} // shared certificates (e.g. subdomain of domain where certificate is specified)
|
||||
} // shared certificates (e.g. subdomain if domain where certificate is specified)
|
||||
elseif ($attributes['fields']['domain_hascert'] == 2) {
|
||||
$result['icon'] .= ' text-warning';
|
||||
$result['title'] .= "\n" . lng('panel.ssleditor_infoshared');
|
||||
|
||||
@@ -95,7 +95,7 @@ class ProgressBar
|
||||
$skip_customer_traffic = false;
|
||||
try {
|
||||
$attributes['fields']['deactivated'] = 0;
|
||||
$result = Traffic::getCustomerStats($attributes['fields'], 'currentmonth', true);
|
||||
$result = Traffic::getCustomerStats($attributes['fields'], 'currentmonth');
|
||||
} catch (Exception $e) {
|
||||
if ($e->getCode() === 405) {
|
||||
$skip_customer_traffic = true;
|
||||
|
||||
@@ -47,9 +47,4 @@ class SSLCertificate
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function isNotLetsEncrypt(array $attributes): bool
|
||||
{
|
||||
return (int)$attributes['fields']['letsencrypt'] == 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,17 +31,17 @@ class Style
|
||||
{
|
||||
public static function deactivated(array $attributes): string
|
||||
{
|
||||
return $attributes['fields']['deactivated'] ? 'table-danger' : '';
|
||||
return $attributes['fields']['deactivated'] ? 'bg-danger' : '';
|
||||
}
|
||||
|
||||
public static function loginDisabled(array $attributes): string
|
||||
{
|
||||
return $attributes['fields']['login_enabled'] == 'N' ? 'table-danger' : '';
|
||||
return $attributes['fields']['login_enabled'] == 'N' ? 'bg-danger' : '';
|
||||
}
|
||||
|
||||
public static function resultIntegrityBad(array $attributes): string
|
||||
{
|
||||
return $attributes['fields']['result'] ? '' : 'table-warning';
|
||||
return $attributes['fields']['result'] ? '' : 'bg-warning';
|
||||
}
|
||||
|
||||
public static function invalidApiKey(array $attributes): string
|
||||
@@ -53,7 +53,7 @@ class Style
|
||||
$isValid = false;
|
||||
}
|
||||
}
|
||||
return $isValid ? '' : 'table-danger';
|
||||
return $isValid ? '' : 'bg-danger';
|
||||
}
|
||||
|
||||
public static function resultDomainTerminatedOrDeactivated(array $attributes): string
|
||||
@@ -63,24 +63,25 @@ class Style
|
||||
if (!empty($termination_date)) {
|
||||
$cdate = strtotime($termination_date . " 23:59:59");
|
||||
$today = time();
|
||||
$termination_css = 'table-warning';
|
||||
$termination_css = 'bg-warning';
|
||||
if ($cdate < $today) {
|
||||
$termination_css = 'table-danger';
|
||||
$termination_css = 'bg-danger text-light';
|
||||
}
|
||||
}
|
||||
$deactivated = $attributes['fields']['deactivated'] || $attributes['fields']['customer_deactivated'];
|
||||
return $deactivated ? 'table-info' : $termination_css;
|
||||
return $deactivated ? 'bg-info text-light' : $termination_css;
|
||||
}
|
||||
|
||||
public static function resultCustomerLockedOrDeactivated(array $attributes): string
|
||||
{
|
||||
$row_css = '';
|
||||
if ((int)$attributes['fields']['deactivated'] == 1) {
|
||||
$row_css = 'table-info';
|
||||
} elseif ($attributes['fields']['loginfail_count'] >= Settings::Get('login.maxloginattempts')
|
||||
$row_css = 'bg-info text-light';
|
||||
} elseif (
|
||||
$attributes['fields']['loginfail_count'] >= Settings::Get('login.maxloginattempts')
|
||||
&& $attributes['fields']['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime'))
|
||||
) {
|
||||
$row_css = 'table-warning';
|
||||
$row_css = 'bg-warning';
|
||||
}
|
||||
|
||||
return $row_css;
|
||||
@@ -96,9 +97,9 @@ class Style
|
||||
$style = '';
|
||||
if ((int)$attributes[$field] >= 0) {
|
||||
if (($attributes[$field] / 100) * $report_max < $attributes[$field . '_used']) {
|
||||
$style = 'table-danger';
|
||||
$style = 'bg-danger';
|
||||
} elseif (($attributes[$field] / 100) * ($report_max - 15) < $attributes[$field . '_used']) {
|
||||
$style = 'table-warning';
|
||||
$style = 'bg-warning';
|
||||
}
|
||||
}
|
||||
return $style;
|
||||
|
||||
@@ -29,7 +29,6 @@ use Froxlor\CurrentUser;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\PhpHelper;
|
||||
use Froxlor\System\Markdown;
|
||||
use Froxlor\UI\Panel\UI;
|
||||
use Froxlor\User;
|
||||
use PDO;
|
||||
@@ -94,7 +93,7 @@ class Text
|
||||
'entry' => $attributes['fields']['id'],
|
||||
'id' => 'cnModal' . $attributes['fields']['id'],
|
||||
'title' => lng('usersettings.custom_notes.title') . ': ' . ($attributes['fields']['loginname'] ?? $attributes['fields']['adminname']),
|
||||
'body' => nl2br(Markdown::cleanCustomNotes($note))
|
||||
'body' => nl2br($note)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -193,14 +193,10 @@ class Form
|
||||
if (!$do_show) {
|
||||
$fielddata['note'] = lng('serversettings.option_requires_otp');
|
||||
if (!$otp_enabled_system) {
|
||||
$fielddata['disabled'] = true;
|
||||
$fielddata['note'] .= '<br>' . lng('2fa.2fa_not_activated');
|
||||
} elseif (!$otp_enabled_user) {
|
||||
$fielddata['disabled'] = true;
|
||||
$fielddata['note'] .= '<br>' . lng('2fa.2fa_not_activated_for_user');
|
||||
}
|
||||
// show field in any case
|
||||
$do_show = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
|
||||
namespace Froxlor\UI;
|
||||
|
||||
use Froxlor\Settings;
|
||||
|
||||
class HTML
|
||||
{
|
||||
|
||||
@@ -118,7 +116,7 @@ class HTML
|
||||
'label' => $navlabel,
|
||||
'icon' => $icon,
|
||||
'items' => $navigation_links,
|
||||
'active' => ((int)Settings::Get('panel.menu_collapsed') == 0 ? 1 : $box_active)
|
||||
'active' => $box_active
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ namespace Froxlor\UI\Panel;
|
||||
|
||||
use Froxlor\Idna\IdnaWrapper;
|
||||
use Froxlor\Settings;
|
||||
use Froxlor\System\Markdown;
|
||||
use Parsedown;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
@@ -54,9 +53,9 @@ class FroxlorTwig extends AbstractExtension
|
||||
$this,
|
||||
'idnDecodeFilter'
|
||||
]),
|
||||
new TwigFilter('markdown', [
|
||||
new TwigFilter('parsedown', [
|
||||
$this,
|
||||
'callMarkdown'
|
||||
'callParsedown'
|
||||
])
|
||||
];
|
||||
}
|
||||
@@ -92,10 +91,6 @@ class FroxlorTwig extends AbstractExtension
|
||||
new TwigFunction('mix', [
|
||||
$this,
|
||||
'getMix'
|
||||
]),
|
||||
new TwigFunction('vite', [
|
||||
$this,
|
||||
'getVite'
|
||||
])
|
||||
];
|
||||
}
|
||||
@@ -153,9 +148,10 @@ class FroxlorTwig extends AbstractExtension
|
||||
return UI::getLinker()->getLink($linkopts);
|
||||
}
|
||||
|
||||
public function callMarkdown($string): string
|
||||
public function callParsedown($string)
|
||||
{
|
||||
return Markdown::cleanCustomNotes($string ?? "");
|
||||
$pd = new Parsedown();
|
||||
return $pd->line($string);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,9 +167,4 @@ class FroxlorTwig extends AbstractExtension
|
||||
{
|
||||
return mix($mix);
|
||||
}
|
||||
|
||||
public function getVite($basehref = '', $vite = [], $defaults = [])
|
||||
{
|
||||
return vite($basehref, $vite ?? $defaults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +142,8 @@ class UI
|
||||
header("X-Content-Security-Policy: " . $csp_content);
|
||||
header("X-WebKit-CSP: " . $csp_content);
|
||||
|
||||
header("X-XSS-Protection: 1; mode=block");
|
||||
|
||||
// Don't allow to load Froxlor in an iframe to prevent i.e. clickjacking
|
||||
header("X-Frame-Options: DENY");
|
||||
|
||||
@@ -321,10 +323,6 @@ class UI
|
||||
}
|
||||
}
|
||||
}
|
||||
// check for template-variant
|
||||
if (preg_match("/([a-z0-9.\-]+)_([a-z0-9.\-]+)/i", $theme, $matches)) {
|
||||
$theme = $matches[1];
|
||||
}
|
||||
if (!file_exists(Froxlor::getInstallDir() . '/templates/' . $theme)) {
|
||||
PhpHelper::phpErrHandler(E_USER_WARNING, "Theme '" . $theme . "' could not be found.", __FILE__, __LINE__);
|
||||
$theme = self::$default_theme;
|
||||
|
||||
@@ -35,11 +35,6 @@ class Data
|
||||
return self::validateFormFieldString($fieldname, $fielddata, $newfieldvalue);
|
||||
}
|
||||
|
||||
public static function validateFormFieldPassword($fieldname, $fielddata, $newfieldvalue)
|
||||
{
|
||||
return self::validateFormFieldString($fieldname, $fielddata, $newfieldvalue);
|
||||
}
|
||||
|
||||
public static function validateFormFieldString($fieldname, $fielddata, $newfieldvalue)
|
||||
{
|
||||
if (isset($fielddata['string_delimiter']) && $fielddata['string_delimiter'] != '') {
|
||||
@@ -215,6 +210,75 @@ class Data
|
||||
}
|
||||
}
|
||||
|
||||
public static function validateFormFieldHiddenString($fieldname, $fielddata, $newfieldvalue)
|
||||
{
|
||||
if (isset($fielddata['string_delimiter']) && $fielddata['string_delimiter'] != '') {
|
||||
$newfieldvalues = explode($fielddata['string_delimiter'], $newfieldvalue);
|
||||
unset($fielddata['string_delimiter']);
|
||||
|
||||
$returnvalue = true;
|
||||
foreach ($newfieldvalues as $single_newfieldvalue) {
|
||||
/**
|
||||
* don't use tabs in value-fields, #81
|
||||
*/
|
||||
$single_newfieldvalue = str_replace("\t", " ", $single_newfieldvalue);
|
||||
$single_returnvalue = Data::validateFormFieldString($fieldname, $fielddata, $single_newfieldvalue);
|
||||
if ($single_returnvalue !== true) {
|
||||
$returnvalue = $single_returnvalue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$returnvalue = false;
|
||||
|
||||
/**
|
||||
* don't use tabs in value-fields, #81
|
||||
*/
|
||||
$newfieldvalue = str_replace("\t", " ", $newfieldvalue);
|
||||
|
||||
if (isset($fielddata['string_type']) && $fielddata['string_type'] == 'mail') {
|
||||
$returnvalue = Validate::validateEmail($newfieldvalue);
|
||||
} elseif (isset($fielddata['string_type']) && $fielddata['string_type'] == 'url') {
|
||||
$returnvalue = Validate::validateUrl($newfieldvalue);
|
||||
} elseif (isset($fielddata['string_type']) && $fielddata['string_type'] == 'dir') {
|
||||
// add trailing slash to validate path if needed
|
||||
// refs #331
|
||||
if (substr($newfieldvalue, -1) != '/') {
|
||||
$newfieldvalue .= '/';
|
||||
}
|
||||
$returnvalue = ($newfieldvalue == FileDir::makeCorrectDir($newfieldvalue));
|
||||
} elseif (isset($fielddata['string_type']) && $fielddata['string_type'] == 'file') {
|
||||
$returnvalue = ($newfieldvalue == FileDir::makeCorrectFile($newfieldvalue));
|
||||
} elseif (isset($fielddata['string_type']) && $fielddata['string_type'] == 'filedir') {
|
||||
$returnvalue = (($newfieldvalue == FileDir::makeCorrectDir($newfieldvalue)) || ($newfieldvalue == FileDir::makeCorrectFile($newfieldvalue)));
|
||||
} elseif (preg_match('/^[^\r\n\t\f\0]*$/D', $newfieldvalue)) {
|
||||
$returnvalue = true;
|
||||
}
|
||||
|
||||
if (isset($fielddata['string_regexp']) && $fielddata['string_regexp'] != '') {
|
||||
if (preg_match($fielddata['string_regexp'], $newfieldvalue)) {
|
||||
$returnvalue = true;
|
||||
} else {
|
||||
$returnvalue = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($fielddata['string_emptyallowed']) && $fielddata['string_emptyallowed'] === true && $newfieldvalue === '') {
|
||||
$returnvalue = true;
|
||||
} elseif (isset($fielddata['string_emptyallowed']) && $fielddata['string_emptyallowed'] === false && $newfieldvalue === '') {
|
||||
$returnvalue = 'stringmustntbeempty';
|
||||
}
|
||||
}
|
||||
|
||||
if ($returnvalue === true) {
|
||||
return true;
|
||||
} elseif ($returnvalue === false) {
|
||||
return 'stringformaterror';
|
||||
} else {
|
||||
return $returnvalue;
|
||||
}
|
||||
}
|
||||
|
||||
public static function validateFormFieldNumber($fieldname, $fielddata, $newfieldvalue)
|
||||
{
|
||||
if (isset($fielddata['min']) && (int)$newfieldvalue < (int)$fielddata['min']) {
|
||||
@@ -259,16 +323,10 @@ class Data
|
||||
if (preg_match($fielddata['string_regexp'], $newfieldvalue)) {
|
||||
$returnvalue = true;
|
||||
}
|
||||
} elseif (preg_match('/^[^\0]*$/', $newfieldvalue)) {
|
||||
} else if (preg_match('/^[^\0]*$/', $newfieldvalue)) {
|
||||
$returnvalue = true;
|
||||
}
|
||||
|
||||
return $returnvalue;
|
||||
}
|
||||
|
||||
public static function validateFormFieldImage($fieldname, $fielddata, $newfieldvalue)
|
||||
{
|
||||
// validation is handled in \Froxlor\Settings\Store::storeSettingImage()
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ class Validate
|
||||
* Check if the submitted string is a valid domainname
|
||||
*
|
||||
* @param string $domainname The domainname which should be checked.
|
||||
* @param bool $allow_underscore optional if true, allows the underscore character in a domain label (DKIM etc.)
|
||||
* @param bool $allow_underscore optional if true, allowes the underscore character in a domain label (DKIM etc.)
|
||||
*
|
||||
* @return string|boolean the domain-name if the domain is valid, false otherwise
|
||||
*/
|
||||
|
||||
@@ -9,18 +9,11 @@ return [
|
||||
* recommended value for debian/ubuntu package users is false to rely on apt and not have version mixup.
|
||||
* This is also useful for providers that manage the servers but give admin access to froxlor to handle
|
||||
* updates the way the providers does it (e.g. automation, etc.)
|
||||
*
|
||||
* Default: false
|
||||
*/
|
||||
'enable_webupdate' => false,
|
||||
|
||||
/**
|
||||
* settings that have a major impact on the system or which values are used to be executed with high
|
||||
* privileges on the system require the admin-user to have set up and enabled OTP for the corresponding
|
||||
* account to change these values.
|
||||
* To disable this extra security validation, set the value of this to true
|
||||
*
|
||||
* Default: false
|
||||
* @todo description
|
||||
*/
|
||||
'disable_otp_security_check' => false,
|
||||
];
|
||||
|
||||
4759
lib/configfiles/bionic.xml
Normal file
4759
lib/configfiles/bionic.xml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -92,7 +92,7 @@ Alias "/.well-known/acme-challenge" "{{settings.system.letsencryptchallengepath}
|
||||
<command><![CDATA[{{settings.system.apachereload_command}}]]></command>
|
||||
</daemon>
|
||||
<!-- HTTP Lighttpd -->
|
||||
<daemon name="lighttpd" title="LigHTTPd (deprecated)">
|
||||
<daemon name="lighttpd" title="LigHTTPd">
|
||||
<install><![CDATA[apt-get install lighttpd]]></install>
|
||||
<file name="/etc/lighttpd/lighttpd.conf">
|
||||
<content><![CDATA[
|
||||
@@ -3383,11 +3383,6 @@ aliases: files
|
||||
</visibility>
|
||||
<command><![CDATA[a2dismod php8.2]]></command>
|
||||
</commands>
|
||||
<commands index="5">
|
||||
<visibility mode="equals" value="apache2">{{settings.system.webserver}}
|
||||
</visibility>
|
||||
<command><![CDATA[a2disconf php8.2-fpm]]></command>
|
||||
</commands>
|
||||
<!-- instead of just restarting apache, we let the cronjob do all the
|
||||
dirty work -->
|
||||
<command><![CDATA[php {{const.install_dir}}bin/froxlor-cli froxlor:cron --force]]></command>
|
||||
|
||||
@@ -92,7 +92,7 @@ Alias "/.well-known/acme-challenge" "{{settings.system.letsencryptchallengepath}
|
||||
<command><![CDATA[{{settings.system.apachereload_command}}]]></command>
|
||||
</daemon>
|
||||
<!-- HTTP Lighttpd -->
|
||||
<daemon name="lighttpd" title="LigHTTPd (deprecated)">
|
||||
<daemon name="lighttpd" title="LigHTTPd">
|
||||
<install><![CDATA[apt-get install lighttpd]]></install>
|
||||
<file name="/etc/lighttpd/lighttpd.conf">
|
||||
<content><![CDATA[
|
||||
|
||||
4962
lib/configfiles/buster.xml
Normal file
4962
lib/configfiles/buster.xml
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user