Compare commits

..

4 Commits

Author SHA1 Message Date
f68c4bc34b fixes #1
Some checks reported errors
continuous-integration/drone/push Build was killed
Merge branch 'upgrade-2' of ssh://gitea.service.nr5:2222/maketank/Froxlor into upgrade-2
2023-12-06 19:56:20 +01:00
b9afa347d7 fixes #1: we don't have that column 2023-12-06 19:54:06 +01:00
97cf30ed7a we don't have that column
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-12-06 19:52:30 +01:00
305f3f6f86 drone deployment - test version
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-12-06 19:34:51 +01:00
228 changed files with 23861 additions and 6251 deletions

40
.drone.yml Normal file
View 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

View File

@@ -2,8 +2,7 @@ name: build-documentation
on: on:
release: release:
# only run for stable releases types: [published]
types: [released]
jobs: jobs:
build_docs: build_docs:
@@ -12,4 +11,4 @@ jobs:
- env: - env:
GITHUB_TOKEN: ${{ secrets.ORG_GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.ORG_GITHUB_TOKEN }}
run: | 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}}

View File

@@ -8,8 +8,8 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
php-versions: [ '7.4', '8.2' ] php-versions: ['7.4', '8.1']
mariadb-version: [ 10.11, 10.5 ] mariadb-version: [10.5, 10.4]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -19,7 +19,7 @@ jobs:
with: with:
php-version: ${{ matrix.php-versions }} php-version: ${{ matrix.php-versions }}
tools: composer:v2 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 - name: Install tools
run: sudo apt-get install -y ant run: sudo apt-get install -y ant
@@ -49,81 +49,33 @@ jobs:
- name: Run testing - name: Run testing
run: ant quick-build run: ant quick-build
nightly: # - name: irc push
name: Create nightly/testing tarball # uses: rectalogic/notify-irc@v1
runs-on: ubuntu-latest # if: github.event_name == 'push'
needs: froxlor # with:
if: ${{ github.event_name == 'push' }} # 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: irc pull request
- name: Checkout # uses: rectalogic/notify-irc@v1
uses: actions/checkout@v3 # 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 # - name: irc tag created
uses: shivammathur/setup-php@v2 # uses: rectalogic/notify-irc@v1
with: # if: github.event_name == 'create' && github.event.ref_type == 'tag'
php-version: '7.4' # with:
tools: composer:v2 # channel: "#froxlor"
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp, gnupg # server: "irc.libera.chat"
# nickname: froxlor-ci
- name: Install composer dependencies # message: |
run: composer install --no-dev # ${{ github.actor }} tagged ${{ github.repository }} ${{ github.event.ref }}
- 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 }}"

View File

@@ -8,7 +8,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
php-versions: ['7.4', '8.2'] php-versions: ['7.4', '8.1']
mysql-version: [8.0, 5.7] mysql-version: [8.0, 5.7]
steps: steps:
- name: Checkout - name: Checkout
@@ -19,7 +19,7 @@ jobs:
with: with:
php-version: ${{ matrix.php-versions }} php-version: ${{ matrix.php-versions }}
tools: composer:v2 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 - name: Install tools
run: sudo apt-get install -y ant run: sudo apt-get install -y ant

5
.gitignore vendored
View File

@@ -22,5 +22,8 @@ fonts/
templates/* templates/*
!templates/index.html !templates/index.html
!templates/Froxlor/ !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/ !templates/misc/

View File

@@ -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 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 ### Forum
The community is located on https://forum.froxlor.org/ 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 ## License

View File

@@ -10,10 +10,9 @@ With that, good luck hacking us ;)
## Supported versions ## Supported versions
- ️✅ **2.1.x** (`main` git-branch) - ️✅ **2.x** (`main` git-branch)
-2.0.x (`2.0.x`-tags) -0.10.x (`0.10.x` git-branch)
- ❌ 0.10.x (`0.10.x`-tags) - ❌ 0.9.x (`0.9.x`git-branch)
- ❌ other git-branches
## Qualifying Vulnerabilities ## Qualifying Vulnerabilities
@@ -27,7 +26,7 @@ With that, good luck hacking us ;)
### Vulnerabilities we accept ### 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 ## 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 - Theoretical attacks without proof of exploitability
- Attacks that are the result of a third party library should be reported to the library maintainers - Attacks that are the result of a third party library should be reported to the library maintainers
- Social engineering - 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 - Reflected file download
- Physical attacks - Physical attacks
- Weak SSL/TLS/SSH algorithms or protocols - 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 ## 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).

View File

@@ -337,15 +337,7 @@ return [
'image_name' => 'logo_login', 'image_name' => 'logo_login',
'default' => '', 'default' => '',
'save_method' => 'storeSettingImage' 'save_method' => 'storeSettingImage'
], ]
'panel_menu_collapsed' => [
'label' => lng('serversettings.panel_menu_collapsed'),
'settinggroup' => 'panel',
'varname' => 'menu_collapsed',
'type' => 'checkbox',
'default' => true,
'save_method' => 'storeSettingField',
],
] ]
] ]
] ]

View File

@@ -130,8 +130,7 @@ return [
'default' => 'stable', 'default' => 'stable',
'select_var' => [ 'select_var' => [
'stable' => lng('serversettings.uc_stable'), 'stable' => lng('serversettings.uc_stable'),
'testing' => lng('serversettings.uc_testing'), 'testing' => lng('serversettings.uc_testing')
'nightly' => lng('serversettings.uc_nightly')
], ],
'save_method' => 'storeSettingField', 'save_method' => 'storeSettingField',
'advanced_mode' => true 'advanced_mode' => true
@@ -172,6 +171,16 @@ return [
'default' => false, 'default' => false,
'save_method' => 'storeSettingField' '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' => [ 'system_store_index_file_subs' => [
'label' => lng('serversettings.system_store_index_file_subs'), 'label' => lng('serversettings.system_store_index_file_subs'),
'settinggroup' => 'system', 'settinggroup' => 'system',

View File

@@ -43,8 +43,7 @@ return [
'settinggroup' => 'spf', 'settinggroup' => 'spf',
'varname' => 'spf_entry', 'varname' => 'spf_entry',
'type' => 'text', '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' 'save_method' => 'storeSettingField'
] ]
] ]

View 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
View 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');
}

View File

@@ -60,9 +60,7 @@ if ($userinfo['change_serversettings'] == '1') {
if (!empty($distribution)) { if (!empty($distribution)) {
if (!file_exists($config_dir . '/' . $distribution . ".xml")) { if (!file_exists($config_dir . '/' . $distribution . ".xml")) {
// unknown distribution -> redirect to select a valid distribution for config-templates Response::dynamicError("Unknown distribution");
Settings::Set('system.distribution', '');
Response::redirectTo('admin_configfiles.php', ['reselect' => 1]);
} }
// update setting if different // update setting if different

View File

@@ -27,6 +27,7 @@ const AREA = 'admin';
require __DIR__ . '/lib/init.php'; require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\Admins; use Froxlor\Api\Commands\Admins;
use Froxlor\Api\Commands\BackupStorages;
use Froxlor\Api\Commands\Customers; use Froxlor\Api\Commands\Customers;
use Froxlor\Api\Commands\MysqlServer; use Froxlor\Api\Commands\MysqlServer;
use Froxlor\CurrentUser; use Froxlor\CurrentUser;
@@ -225,6 +226,23 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
$hosting_plans[$row['id']] = $row['name']; $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'; $customer_add_data = include_once dirname(__FILE__) . '/lib/formfields/admin/customer/formfield.customer_add.php';
UI::view('user/form.html.twig', [ UI::view('user/form.html.twig', [
@@ -307,6 +325,23 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
$hosting_plans[$row['id']] = $row['name']; $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(" $available_admins_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_ADMINS . "` SELECT * FROM `" . TABLE_PANEL_ADMINS . "`
WHERE (`customers` = '-1' OR `customers` > `customers_used`) WHERE (`customers` = '-1' OR `customers` > `customers_used`)

View File

@@ -30,9 +30,9 @@ use Froxlor\Api\Commands\Customers as Customers;
use Froxlor\Api\Commands\Domains as Domains; use Froxlor\Api\Commands\Domains as Domains;
use Froxlor\Bulk\DomainBulkAction; use Froxlor\Bulk\DomainBulkAction;
use Froxlor\Cron\TaskId; use Froxlor\Cron\TaskId;
use Froxlor\CurrentUser;
use Froxlor\Customer\Customer; use Froxlor\Customer\Customer;
use Froxlor\Database\Database; use Froxlor\Database\Database;
use Froxlor\Domain\Domain;
use Froxlor\FileDir; use Froxlor\FileDir;
use Froxlor\FroxlorLogger; use Froxlor\FroxlorLogger;
use Froxlor\Settings; use Froxlor\Settings;
@@ -45,6 +45,7 @@ use Froxlor\UI\Request;
use Froxlor\UI\Response; use Froxlor\UI\Response;
use Froxlor\User; use Froxlor\User;
use Froxlor\Validate\Validate; use Froxlor\Validate\Validate;
use Froxlor\CurrentUser;
$id = (int)Request::any('id'); $id = (int)Request::any('id');
@@ -645,7 +646,7 @@ if ($page == 'domains' || $page == 'overview') {
Response::redirectTo($filename, [ Response::redirectTo($filename, [
'page' => $page, 'page' => $page,
'searchfield' => 'd.domain_ace', 'searchfield' => 'd.domain_ace',
'searchtext' => Request::post('domain', "") 'searchtext' => $_POST['domain'] ?? ""
]); ]);
} else { } else {
Response::redirectTo($filename, [ Response::redirectTo($filename, [

View File

@@ -60,8 +60,7 @@ if (Settings::Get('panel.sendalternativemail') == 1) {
} }
$file_templates = [ $file_templates = [
'index_html', 'index_html'
'unconfigured_html'
]; ];
$languages = Language::getLanguages(); $languages = Language::getLanguages();

View File

@@ -24,8 +24,20 @@
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2 * @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 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 // validate correct php version
if (version_compare("7.4.0", 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'; require dirname(__DIR__) . '/lib/tables.inc.php';
$application = new Application('froxlor-cli', Froxlor::getFullVersion()); $application = new Application('froxlor-cli', Froxlor::getFullVersion());
$application->add(new RunApiCommand());
// files that are no commands $application->add(new ConfigServices());
$fileIgnoreList = [ $application->add(new PhpSessionclean());
// Current non-command files $application->add(new SwitchServerIp());
'CliCommand.php', $application->add(new UpdateCommand());
'index.html', $application->add(new InstallCommand());
'install.functions.php', $application->add(new MasterCron());
]; $application->add(new UserCommand());
// directory of commands to include $application->add(new ValidateAcmeWebroot());
$cmd_files = glob(Froxlor::getInstallDir() . '/lib/Froxlor/Cli/*.php'); $application->add(new ConfigDiff());
// 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->run(); $application->run();

View File

@@ -46,17 +46,17 @@
"ext-fileinfo": "*", "ext-fileinfo": "*",
"ext-gmp": "*", "ext-gmp": "*",
"ext-gd": "*", "ext-gd": "*",
"ext-gnupg": "*", "ext-ftp": "*",
"phpmailer/phpmailer": "~6.0", "phpmailer/phpmailer": "~6.0",
"monolog/monolog": "^1.24", "monolog/monolog": "^1.24",
"robthree/twofactorauth": "^1.6", "robthree/twofactorauth": "^1.6",
"froxlor/idna-convert-legacy": "^2.1", "froxlor/idna-convert-legacy": "^2.1",
"voku/anti-xss": "^4.1", "voku/anti-xss": "^4.1",
"twig/twig": "^3.3", "twig/twig": "^3.3",
"erusev/parsedown": "^1.7",
"symfony/console": "^5.4", "symfony/console": "^5.4",
"pear/net_dns2": "^1.5", "pear/net_dns2": "^1.5",
"amnuts/opcache-gui": "^3.4", "amnuts/opcache-gui": "^3.4"
"league/commonmark": "^2.4"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9", "phpunit/phpunit": "^9",

812
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -27,7 +27,6 @@ const AREA = 'customer';
require __DIR__ . '/lib/init.php'; require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\SubDomains as SubDomains; use Froxlor\Api\Commands\SubDomains as SubDomains;
use Froxlor\CurrentUser;
use Froxlor\Database\Database; use Froxlor\Database\Database;
use Froxlor\Domain\Domain; use Froxlor\Domain\Domain;
use Froxlor\FileDir; use Froxlor\FileDir;
@@ -41,6 +40,7 @@ use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request; use Froxlor\UI\Request;
use Froxlor\UI\Response; use Froxlor\UI\Response;
use Froxlor\Validate\Validate; use Froxlor\Validate\Validate;
use Froxlor\CurrentUser;
// redirect if this customer page is hidden via settings // redirect if this customer page is hidden via settings
if (Settings::IsInList('panel.customer_hide_options', 'domains')) { if (Settings::IsInList('panel.customer_hide_options', 'domains')) {
@@ -63,21 +63,16 @@ if ($page == 'overview' || $page == 'domains') {
Response::dynamicError($e->getMessage()); Response::dynamicError($e->getMessage());
} }
$actions_links = []; $actions_links = false;
if (CurrentUser::canAddResource('subdomains')) { if (CurrentUser::canAddResource('subdomains')) {
$actions_links[] = [ $actions_links = [
[
'href' => $linker->getLink(['section' => 'domains', 'page' => 'domains', 'action' => 'add']), 'href' => $linker->getLink(['section' => 'domains', 'page' => 'domains', 'action' => 'add']),
'label' => lng('domains.subdomain_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'; $table_tpl = 'table.html.twig';
if ($collection->count() == 0) { if ($collection->count() == 0) {
$table_tpl = 'table-note.html.twig'; $table_tpl = 'table-note.html.twig';

View File

@@ -27,10 +27,9 @@ const AREA = 'customer';
require __DIR__ . '/lib/init.php'; require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\EmailAccounts; use Froxlor\Api\Commands\EmailAccounts;
use Froxlor\Api\Commands\EmailDomains;
use Froxlor\Api\Commands\EmailForwarders; use Froxlor\Api\Commands\EmailForwarders;
use Froxlor\Api\Commands\Emails; use Froxlor\Api\Commands\Emails;
use Froxlor\CurrentUser; use Froxlor\Api\Commands\EmailDomains;
use Froxlor\Database\Database; use Froxlor\Database\Database;
use Froxlor\FroxlorLogger; use Froxlor\FroxlorLogger;
use Froxlor\PhpHelper; use Froxlor\PhpHelper;
@@ -42,6 +41,7 @@ use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request; use Froxlor\UI\Request;
use Froxlor\UI\Response; use Froxlor\UI\Response;
use Froxlor\Validate\Check; use Froxlor\Validate\Check;
use Froxlor\CurrentUser;
// redirect if this customer page is hidden via settings // redirect if this customer page is hidden via settings
if (Settings::IsInList('panel.customer_hide_options', 'email') || $userinfo['emails'] == 0) { if (Settings::IsInList('panel.customer_hide_options', 'email') || $userinfo['emails'] == 0) {
@@ -67,24 +67,14 @@ if ($page == 'overview' || $page == 'emails') {
Response::dynamicError($e->getMessage()); 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', [ UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $emaildomain_list_data, 'emaildomain_list'), '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 { } else {
// only emails for one domain -> show email address listing directly // only emails for one domain -> show email address listing directly
@@ -137,12 +127,6 @@ if ($page == 'email_domain') {
'label' => lng('emails.emails_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', [ UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $email_list_data, 'email_list'), 'listing' => Listing::format($collection, $email_list_data, 'email_list'),

View File

@@ -68,22 +68,14 @@ if ($page == 'overview' || $page == 'htpasswds') {
Response::dynamicError($e->getMessage()); 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', [ UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $htpasswd_list_data, 'htpasswd_list'), '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') 'entity_info' => lng('extras.description')
]); ]);
} elseif ($action == 'delete' && $id != 0) { } elseif ($action == 'delete' && $id != 0) {
@@ -193,22 +185,14 @@ if ($page == 'overview' || $page == 'htpasswds') {
Response::dynamicError($e->getMessage()); 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', [ UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $htaccess_list_data, 'htaccess_list'), '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') 'entity_info' => lng('extras.description')
]); ]);
} elseif ($action == 'delete' && $id != 0) { } elseif ($action == 'delete' && $id != 0) {
@@ -347,19 +331,9 @@ if ($page == 'overview' || $page == 'htpasswds') {
$pathSelect = FileDir::makePathfield($userinfo['documentroot'], $userinfo['guid'], $userinfo['guid']); $pathSelect = FileDir::makePathfield($userinfo['documentroot'], $userinfo['guid'], $userinfo['guid']);
$export_data = include_once dirname(__FILE__) . '/lib/formfields/customer/extras/formfield.export.php'; $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', [ UI::view('user/form-datatable.html.twig', [
'formaction' => $linker->getLink(['section' => 'extras']), 'formaction' => $linker->getLink(['section' => 'extras']),
'formdata' => $export_data['export'], 'formdata' => $export_data['export'],
'actions_links' => $actions_links,
'tabledata' => Listing::format($collection, $export_list_data, 'export_list'), 'tabledata' => Listing::format($collection, $export_list_data, 'export_list'),
]); ]);
} }

View File

@@ -27,7 +27,6 @@ const AREA = 'customer';
require __DIR__ . '/lib/init.php'; require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\Ftps as Ftps; use Froxlor\Api\Commands\Ftps as Ftps;
use Froxlor\CurrentUser;
use Froxlor\Database\Database; use Froxlor\Database\Database;
use Froxlor\FileDir; use Froxlor\FileDir;
use Froxlor\FroxlorLogger; use Froxlor\FroxlorLogger;
@@ -38,6 +37,7 @@ use Froxlor\UI\Listing;
use Froxlor\UI\Panel\UI; use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request; use Froxlor\UI\Request;
use Froxlor\UI\Response; use Froxlor\UI\Response;
use Froxlor\CurrentUser;
// redirect if this customer page is hidden via settings // redirect if this customer page is hidden via settings
if (Settings::IsInList('panel.customer_hide_options', 'ftp')) { if (Settings::IsInList('panel.customer_hide_options', 'ftp')) {
@@ -57,19 +57,15 @@ if ($page == 'overview' || $page == 'accounts') {
Response::dynamicError($e->getMessage()); Response::dynamicError($e->getMessage());
} }
$actions_links = []; $actions_links = false;
if (CurrentUser::canAddResource('ftps')) { if (CurrentUser::canAddResource('ftps')) {
$actions_links[] = [ $actions_links = [
[
'href' => $linker->getLink(['section' => 'ftp', 'page' => 'accounts', 'action' => 'add']), 'href' => $linker->getLink(['section' => 'ftp', 'page' => 'accounts', 'action' => 'add']),
'label' => lng('ftp.account_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', [ UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $ftp_list_data, 'ftp_list'), 'listing' => Listing::format($collection, $ftp_list_data, 'ftp_list'),

View File

@@ -115,8 +115,8 @@ if ($page == 'overview') {
$userinfo['traffic_bytes_used'] = $userinfo['traffic_used'] * 1024; $userinfo['traffic_bytes_used'] = $userinfo['traffic_used'] * 1024;
if (Settings::Get('system.mail_quota_enabled')) { 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'] = ($userinfo['email_quota'] > -1) ? $userinfo['email_quota'] * 1024 : -1;
$userinfo['email_quota_bytes_used'] = $userinfo['email_quota_used'] * 1024 * 1024; $userinfo['email_quota_bytes_used'] = $userinfo['email_quota_used'] * 1024;
} }
if ($usages) { if ($usages) {

View File

@@ -28,7 +28,6 @@ require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\Mysqls; use Froxlor\Api\Commands\Mysqls;
use Froxlor\Api\Commands\MysqlServer; use Froxlor\Api\Commands\MysqlServer;
use Froxlor\CurrentUser;
use Froxlor\Database\Database; use Froxlor\Database\Database;
use Froxlor\FroxlorLogger; use Froxlor\FroxlorLogger;
use Froxlor\Settings; use Froxlor\Settings;
@@ -38,6 +37,7 @@ use Froxlor\UI\Listing;
use Froxlor\UI\Panel\UI; use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request; use Froxlor\UI\Request;
use Froxlor\UI\Response; use Froxlor\UI\Response;
use Froxlor\CurrentUser;
// redirect if this customer page is hidden via settings or no resources given // redirect if this customer page is hidden via settings or no resources given
if (Settings::IsInList('panel.customer_hide_options', 'mysql') || $userinfo['mysqls'] == 0) { if (Settings::IsInList('panel.customer_hide_options', 'mysql') || $userinfo['mysqls'] == 0) {
@@ -66,21 +66,16 @@ if ($page == 'overview' || $page == 'mysqls') {
Response::dynamicError($e->getMessage()); Response::dynamicError($e->getMessage());
} }
$actions_links = []; $actions_links = false;
if (CurrentUser::canAddResource('mysqls')) { if (CurrentUser::canAddResource('mysqls')) {
$actions_links[] = [ $actions_links = [
[
'href' => $linker->getLink(['section' => 'mysql', 'page' => 'mysqls', 'action' => 'add']), 'href' => $linker->getLink(['section' => 'mysql', 'page' => 'mysqls', 'action' => 'add']),
'label' => lng('mysql.database_create') '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', [ UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $mysql_list_data, 'mysql_list'), 'listing' => Listing::format($collection, $mysql_list_data, 'mysql_list'),
'actions_links' => $actions_links, 'actions_links' => $actions_links,

View File

@@ -74,26 +74,27 @@ if ($action == '2fa_entercode') {
$code = isset($_POST['2fa_code']) ? $_POST['2fa_code'] : null; $code = isset($_POST['2fa_code']) ? $_POST['2fa_code'] : null;
// verify entered code // verify entered code
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname')); $tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
$result = ($_SESSION['secret_2fa'] == 'email' ? true : $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3));
// get user-data // get user-data
$table = $_SESSION['uidtable_2fa']; $table = $_SESSION['uidtable_2fa'];
$field = $_SESSION['uidfield_2fa']; $field = $_SESSION['uidfield_2fa'];
$uid = $_SESSION['uid_2fa']; $uid = $_SESSION['uid_2fa'];
$isadmin = $_SESSION['unfo_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 // 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 // which is temporarily stored for the customer when using email-2fa
if ($result) { if ($result) {
$sel_param = [ $sel_param = [
'uid' => $uid 'uid' => $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"); $sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid");
}
$userinfo = Database::pexecute_first($sel_stmt, $sel_param); $userinfo = Database::pexecute_first($sel_stmt, $sel_param);
// whoops, no (valid) user? Start again // whoops, no (valid) user? Start again
if (empty($userinfo)) { if (empty($userinfo)) {
@@ -326,12 +327,11 @@ if ($action == '2fa_entercode') {
if ($userinfo['type_2fa'] == 1) { if ($userinfo['type_2fa'] == 1) {
// generate code // generate code
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname')); $tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
$secret = $tfa->createSecret(); $code = $tfa->getCode($tfa->createSecret());
$code = $tfa->getCode($secret);
// set code for user // set code for user
$stmt = Database::prepare("UPDATE $table SET `data_2fa` = :d2fa WHERE `$uid` = :uid"); $stmt = Database::prepare("UPDATE $table SET `data_2fa` = :d2fa WHERE `$uid` = :uid");
Database::pexecute($stmt, [ Database::pexecute($stmt, [
"d2fa" => $secret, "d2fa" => $code,
"uid" => $userinfo[$uid] "uid" => $userinfo[$uid]
]); ]);
// build up & send email // build up & send email

View File

@@ -157,7 +157,7 @@ CREATE TABLE `panel_admins` (
`api_allowed` tinyint(1) NOT NULL default '1', `api_allowed` tinyint(1) NOT NULL default '1',
PRIMARY KEY (`adminid`), PRIMARY KEY (`adminid`),
UNIQUE KEY `loginname` (`loginname`) 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`; DROP TABLE IF EXISTS `panel_customers`;
@@ -223,6 +223,8 @@ CREATE TABLE `panel_customers` (
`api_allowed` tinyint(1) NOT NULL default '1', `api_allowed` tinyint(1) NOT NULL default '1',
`logviewenabled` tinyint(1) NOT NULL default '0', `logviewenabled` tinyint(1) NOT NULL default '0',
`allowed_mysqlserver` text NOT NULL, `allowed_mysqlserver` text NOT NULL,
`backup` int(11) NOT NULL default '1',
`access_backups` tinyint(1) NOT NULL default '1',
PRIMARY KEY (`customerid`), PRIMARY KEY (`customerid`),
UNIQUE KEY `loginname` (`loginname`) UNIQUE KEY `loginname` (`loginname`)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC; ) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC;
@@ -299,7 +301,7 @@ CREATE TABLE `panel_domains` (
KEY `customerid` (`customerid`), KEY `customerid` (`customerid`),
KEY `parentdomain` (`parentdomainid`), KEY `parentdomain` (`parentdomainid`),
KEY `domain` (`domain`) 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`; DROP TABLE IF EXISTS `panel_ipsandports`;
@@ -356,6 +358,23 @@ CREATE TABLE `panel_htpasswds` (
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci; ) 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`; DROP TABLE IF EXISTS `panel_settings`;
CREATE TABLE `panel_settings` ( CREATE TABLE `panel_settings` (
`settingid` int(11) unsigned NOT NULL auto_increment, `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'), ('admin', 'show_version_footer', '0'),
('caa', 'caa_entry', ''), ('caa', 'caa_entry', ''),
('spf', 'use_spf', '0'), ('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_algorithm', 'all'),
('dkim', 'dkim_keylength', '1024'), ('dkim', 'dkim_keylength', '1024'),
('dkim', 'dkim_servicetype', '0'), ('dkim', 'dkim_servicetype', '0'),
@@ -545,7 +564,7 @@ opcache.validate_timestamps'),
('system', 'mod_fcgid', '0'), ('system', 'mod_fcgid', '0'),
('system', 'apacheconf_vhost', '/etc/apache2/sites-enabled/'), ('system', 'apacheconf_vhost', '/etc/apache2/sites-enabled/'),
('system', 'apacheconf_diroptions', '/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', 'webalizer_quiet', '2'),
('system', 'last_archive_run', '000000'), ('system', 'last_archive_run', '000000'),
('system', 'mod_fcgid_configdir', '/var/www/php-fcgi-scripts'), ('system', 'mod_fcgid_configdir', '/var/www/php-fcgi-scripts'),
@@ -562,6 +581,7 @@ opcache.validate_timestamps'),
('system', 'mod_fcgid_wrapper', '1'), ('system', 'mod_fcgid_wrapper', '1'),
('system', 'mod_fcgid_starter', '0'), ('system', 'mod_fcgid_starter', '0'),
('system', 'mod_fcgid_peardir', '/usr/share/php/:/usr/share/php5/'), ('system', 'mod_fcgid_peardir', '/usr/share/php/:/usr/share/php5/'),
('system', 'index_file_extension', 'html'),
('system', 'mod_fcgid_maxrequests', '250'), ('system', 'mod_fcgid_maxrequests', '250'),
('system', 'ssl_key_file','/etc/ssl/froxlor_selfsigned.key'), ('system', 'ssl_key_file','/etc/ssl/froxlor_selfsigned.key'),
('system', 'ssl_ca_file', ''), ('system', 'ssl_ca_file', ''),
@@ -678,10 +698,15 @@ opcache.validate_timestamps'),
('system', 'distribution', ''), ('system', 'distribution', ''),
('system', 'update_channel', 'stable'), ('system', 'update_channel', 'stable'),
('system', 'updatecheck_data', ''), ('system', 'updatecheck_data', ''),
('system', 'update_notify_last', ''), ('system', 'update_notify_last', '2.0.20'),
('system', 'traffictool', 'goaccess'), ('system', 'traffictool', 'goaccess'),
('system', 'req_limit_per_interval', 60), ('system', 'req_limit_per_interval', 60),
('system', 'req_limit_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', 'enabled', '0'),
('api', 'customer_default', '1'), ('api', 'customer_default', '1'),
('2fa', 'enabled', '1'), ('2fa', 'enabled', '1'),
@@ -725,9 +750,8 @@ opcache.validate_timestamps'),
('panel', 'logo_overridetheme', '0'), ('panel', 'logo_overridetheme', '0'),
('panel', 'logo_overridecustom', '0'), ('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'), ('panel', 'settings_mode', '0'),
('panel', 'menu_collapsed', '1'), ('panel', 'version', '2.0.20'),
('panel', 'version', '2.1.2'), ('panel', 'db_version', '202305240');
('panel', 'db_version', '202312120');
DROP TABLE IF EXISTS `panel_tasks`; DROP TABLE IF EXISTS `panel_tasks`;
@@ -750,7 +774,6 @@ CREATE TABLE `panel_templates` (
`templategroup` varchar(255) NOT NULL default '', `templategroup` varchar(255) NOT NULL default '',
`varname` varchar(255) NOT NULL default '', `varname` varchar(255) NOT NULL default '',
`value` longtext NOT NULL, `value` longtext NOT NULL,
`file_extension` varchar(50) NOT NULL default 'html',
PRIMARY KEY (id), PRIMARY KEY (id),
KEY adminid (adminid) KEY adminid (adminid)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci; ) 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'), (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'), (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'), (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`; DROP TABLE IF EXISTS `ftp_quotalimits`;
@@ -1045,4 +1069,38 @@ CREATE TABLE `panel_loginlinks` (
`allowed_from` text NOT NULL, `allowed_from` text NOT NULL,
UNIQUE KEY `loginname` (`loginname`) UNIQUE KEY `loginname` (`loginname`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; ) 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; FROXLORSQL;

View File

@@ -149,7 +149,7 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
Update::showUpdateStep("Adding new settings"); Update::showUpdateStep("Adding new settings");
$panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int)$_POST['panel_settings_mode'] : 0; $panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int)$_POST['panel_settings_mode'] : 0;
Settings::AddNew("panel.settings_mode", $panel_settings_mode); 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.distribution", $system_distribution);
Settings::AddNew("system.update_channel", 'stable'); Settings::AddNew("system.update_channel", 'stable');
Settings::AddNew("system.updatecheck_data", ''); 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); Update::showUpdateStep("Updating from 2.0.19 to 2.0.20", false);
Froxlor::updateToVersion('2.0.20'); 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');
}

View File

@@ -36,10 +36,9 @@ if (!defined('_CRON_UPDATE')) {
} }
} }
if (Froxlor::isFroxlorVersion('2.0.24')) { if (Froxlor::isDatabaseVersion('202304260')) {
Update::showUpdateStep("Cleaning domains table"); //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`;");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` DROP COLUMN `ismainbutsubto`;");
Update::lastStepStatus(0); Update::lastStepStatus(0);
Update::showUpdateStep("Creating new tables and fields"); Update::showUpdateStep("Creating new tables and fields");
@@ -54,10 +53,6 @@ if (Froxlor::isFroxlorVersion('2.0.24')) {
Database::query($sql); Database::query($sql);
Update::lastStepStatus(0); Update::lastStepStatus(0);
Update::showUpdateStep("Adding new settings");
Settings::AddNew('panel.menu_collapsed', 1);
Update::lastStepStatus(0);
Update::showUpdateStep("Adjusting setting for deactivated webroot"); Update::showUpdateStep("Adjusting setting for deactivated webroot");
$current_deactivated_webroot = Settings::Get('system.deactivateddocroot'); $current_deactivated_webroot = Settings::Get('system.deactivateddocroot');
if (empty($current_deactivated_webroot)) { if (empty($current_deactivated_webroot)) {
@@ -67,205 +62,80 @@ if (Froxlor::isFroxlorVersion('2.0.24')) {
Update::lastStepStatus(1, 'Customized setting, not changing'); 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"); Update::showUpdateStep("Adjusting cronjobs");
$cfupd_stmt = Database::prepare(" Database::query("
UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET
`module`= 'froxlor/export', `module`= 'froxlor/export',
`cronfile` = 'export', `cronfile` = 'export',
`cronclass` = :cc, `cronclass` = '\\Froxlor\\Cron\\System\\ExportCron',
`interval` = '1 HOUR', `interval` = '1 HOUR',
`desc_lng_key` = 'cron_export' `desc_lng_key` = 'cron_export'
WHERE `module` = 'froxlor/backup' WHERE `module` = 'froxlor/backup'
"); ");
Database::pexecute($cfupd_stmt, [ Database::query("
'cc' => '\\Froxlor\\Cron\\System\\ExportCron' 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::lastStepStatus(0);
Update::showUpdateStep("Adjusting system for data-export function"); 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("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_USERCOLUMNS . "` WHERE `section` = 'backup_list'");
Database::query("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'"); Database::query("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'");
Update::lastStepStatus(0); Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202305240'); 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');
} }

View File

@@ -54,7 +54,7 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
$config_dir = FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/lib/configfiles/'); $config_dir = FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/lib/configfiles/');
// show list of available distro's // show list of available distro's
$distros = glob($config_dir . '*.xml'); $distros = glob($config_dir . '*.xml');
// selection is required $distributions_select[''] = '-'; $distributions_select[''] = '-';
// read in all the distros // read in all the distros
foreach ($distros as $_distribution) { foreach ($distros as $_distribution) {
// get configparser object // get configparser object

View File

@@ -36,7 +36,19 @@ $preconfig = [
$return = []; $return = [];
if (Update::versionInUpdate($current_version, '2.1.0-dev1')) { 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; $preconfig['fields'] = $return;

View 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);
}
}

View 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);
}
}

View File

@@ -273,6 +273,13 @@ class Customers extends ApiCommand implements ResourceEntity
* @param array $allowed_mysqlserver * @param array $allowed_mysqlserver
* optional, array of IDs of defined mysql-servers the customer is allowed to use, * optional, array of IDs of defined mysql-servers the customer is allowed to use,
* default is to allow the default dbserver (id=0) * 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 * @access admin
* @return string json-encoded array * @return string json-encoded array
@@ -359,6 +366,24 @@ class Customers extends ApiCommand implements ResourceEntity
$p_allowed_mysqlserver = []; $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 // validation
$name = Validate::validate($name, 'name', Validate::REGEX_DESC_TEXT, '', [], true); $name = Validate::validate($name, 'name', Validate::REGEX_DESC_TEXT, '', [], true);
$firstname = Validate::validate($firstname, 'first 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, 'theme' => $_theme,
'custom_notes' => $custom_notes, 'custom_notes' => $custom_notes,
'custom_notes_show' => $custom_notes_show, '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(" $ins_stmt = Database::prepare("
@@ -580,7 +607,9 @@ class Customers extends ApiCommand implements ResourceEntity
`theme` = :theme, `theme` = :theme,
`custom_notes` = :custom_notes, `custom_notes` = :custom_notes,
`custom_notes_show` = :custom_notes_show, `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); Database::pexecute($ins_stmt, $ins_data, true, true);
@@ -1028,6 +1057,13 @@ class Customers extends ApiCommand implements ResourceEntity
* @param array $allowed_mysqlserver * @param array $allowed_mysqlserver
* optional, array of IDs of defined mysql-servers the customer is allowed to use, * optional, array of IDs of defined mysql-servers the customer is allowed to use,
* default is to allow the default dbserver (id=0) * 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 * @access admin, customer
* @return string json-encoded array * @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'])); $email = $this->getParam('email', true, $idna_convert->decode($result['email']));
$name = $this->getParam('name', true, $result['name']); $name = $this->getParam('name', true, $result['name']);
$firstname = $this->getParam('firstname', true, $result['firstname']); $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']); $company = $this->getParam('company', !$company_required, $result['company']);
$street = $this->getParam('street', true, $result['street']); $street = $this->getParam('street', true, $result['street']);
$zipcode = $this->getParam('zipcode', true, $result['zipcode']); $zipcode = $this->getParam('zipcode', true, $result['zipcode']);
@@ -1089,6 +1125,24 @@ class Customers extends ApiCommand implements ResourceEntity
$deactivated = $this->getBoolParam('deactivated', true, $result['deactivated']); $deactivated = $this->getBoolParam('deactivated', true, $result['deactivated']);
$theme = $this->getParam('theme', true, $result['theme']); $theme = $this->getParam('theme', true, $result['theme']);
$allowed_mysqlserver = $this->getParam('allowed_mysqlserver', true, json_decode($result['allowed_mysqlserver'], true)); $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 { } else {
// allowed parameters // allowed parameters
$def_language = $this->getParam('def_language', true, $result['def_language']); $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' => $custom_notes,
'custom_notes_show' => $custom_notes_show, 'custom_notes_show' => $custom_notes_show,
'api_allowed' => $api_allowed, '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; $upd_data += $admin_upd_data;
} }
@@ -1440,7 +1496,9 @@ class Customers extends ApiCommand implements ResourceEntity
`custom_notes` = :custom_notes, `custom_notes` = :custom_notes,
`custom_notes_show` = :custom_notes_show, `custom_notes_show` = :custom_notes_show,
`api_allowed` = :api_allowed, `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 .= $admin_upd_query;
} }
$upd_query .= " WHERE `customerid` = :customerid"; $upd_query .= " WHERE `customerid` = :customerid";

View File

@@ -93,7 +93,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
// validation // validation
$path = FileDir::makeCorrectDir(Validate::validate($path, 'path', Validate::REGEX_DIR, '', [], true)); $path = FileDir::makeCorrectDir(Validate::validate($path, 'path', Validate::REGEX_DIR, '', [], true));
$userpath = $path; $userpath = $path;
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']); $path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
if (!empty($error404path)) { if (!empty($error404path)) {
$error404path = $this->correctErrorDocument($error404path, true); $error404path = $this->correctErrorDocument($error404path, true);

View File

@@ -84,7 +84,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
// validation // validation
$path = FileDir::makeCorrectDir(Validate::validate($path, 'path', Validate::REGEX_DIR, '', [], true)); $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); $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); $authname = Validate::validate($authname, 'directory_authname', '/^[a-zA-Z0-9][a-zA-Z0-9\-_ ]+\$?$/', '', [], true);
$password = Validate::validate($password, 'password', '', '', [], true); $password = Validate::validate($password, 'password', '', '', [], true);

View File

@@ -316,9 +316,9 @@ class Domains extends ApiCommand implements ResourceEntity
$mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, -1); $mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, -1);
$ssl_redirect = $this->getBoolParam('ssl_redirect', true, 0); $ssl_redirect = $this->getBoolParam('ssl_redirect', true, 0);
$letsencrypt = $this->getBoolParam('letsencrypt', 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); $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'))); $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); $http2 = $this->getBoolParam('http2', true, 0);
$hsts_maxage = $this->getParam('hsts_maxage', true, 0); $hsts_maxage = $this->getParam('hsts_maxage', true, 0);
$hsts_sub = $this->getBoolParam('hsts_sub', 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--') { if (substr($p_domain, 0, 4) == 'xn--') {
Response::standardError('domain_nopunycode', '', true); Response::standardError('domain_nopunycode', '', true);
} elseif (Validate::validate_ip2($p_domain, true, '', true, true)) {
Response::standardError('domain_noipaddress', '', true);
} }
$idna_convert = new IdnaWrapper(); $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); $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)) { if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) {
$ssl_redirect = 0; $ssl_redirect = 0;
$letsencrypt = 0; $letsencrypt = 0;
@@ -1213,7 +1207,7 @@ class Domains extends ApiCommand implements ResourceEntity
$p_ssl_ipandports = $this->getParam('ssl_ipandport', true, $remove_ssl_ipandport ? [ $p_ssl_ipandports = $this->getParam('ssl_ipandport', true, $remove_ssl_ipandport ? [
-1 -1
] : null); ] : 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']); $http2 = $this->getBoolParam('http2', true, $result['http2']);
$hsts_maxage = $this->getParam('hsts_maxage', true, $result['hsts']); $hsts_maxage = $this->getParam('hsts_maxage', true, $result['hsts']);
$hsts_sub = $this->getBoolParam('hsts_sub', true, $result['hsts_sub']); $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)) { if ($remove_ssl_ipandport || (!empty($p_ssl_ipandports) && $p_ssl_ipandports[0] == -1)) {
$ssl_ipandports = []; $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)) { if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) {
$ssl_redirect = 0; $ssl_redirect = 0;
$letsencrypt = 0; $letsencrypt = 0;
@@ -1563,7 +1553,7 @@ class Domains extends ApiCommand implements ResourceEntity
} }
// Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated // 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; $ssl_redirect = 2;
} }
@@ -1652,7 +1642,6 @@ class Domains extends ApiCommand implements ResourceEntity
|| $iswildcarddomain != $result['iswildcarddomain'] || $iswildcarddomain != $result['iswildcarddomain']
|| $phpenabled != $result['phpenabled'] || $phpenabled != $result['phpenabled']
|| $openbasedir != $result['openbasedir'] || $openbasedir != $result['openbasedir']
|| $openbasedir_path != $result['openbasedir_path']
|| $phpsettingid != $result['phpsettingid'] || $phpsettingid != $result['phpsettingid']
|| $mod_fcgid_starter != $result['mod_fcgid_starter'] || $mod_fcgid_starter != $result['mod_fcgid_starter']
|| $mod_fcgid_maxrequests != $result['mod_fcgid_maxrequests'] || $mod_fcgid_maxrequests != $result['mod_fcgid_maxrequests']
@@ -1670,7 +1659,6 @@ class Domains extends ApiCommand implements ResourceEntity
|| $hsts_sub != $result['hsts_sub'] || $hsts_sub != $result['hsts_sub']
|| $hsts_preload != $result['hsts_preload'] || $hsts_preload != $result['hsts_preload']
|| $ocsp_stapling != $result['ocsp_stapling'] || $ocsp_stapling != $result['ocsp_stapling']
|| $sslenabled != $result['ssl_enabled']
) { ) {
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_VHOST);
} }
@@ -1819,7 +1807,7 @@ class Domains extends ApiCommand implements ResourceEntity
$update_data['wwwserveralias'] = $wwwserveralias; $update_data['wwwserveralias'] = $wwwserveralias;
$update_data['iswildcarddomain'] = $iswildcarddomain; $update_data['iswildcarddomain'] = $iswildcarddomain;
$update_data['phpenabled'] = $phpenabled; $update_data['phpenabled'] = $phpenabled;
$update_data['openbasedir'] = $openbasedir; $update_data['openbasedir'] = $openbasedir;;
$update_data['openbasedir_path'] = $openbasedir_path; $update_data['openbasedir_path'] = $openbasedir_path;
$update_data['speciallogfile'] = $speciallogfile; $update_data['speciallogfile'] = $speciallogfile;
$update_data['phpsettingid'] = $phpsettingid; $update_data['phpsettingid'] = $phpsettingid;
@@ -2325,10 +2313,6 @@ class Domains extends ApiCommand implements ResourceEntity
unset($result['wwwserveralias']); unset($result['wwwserveralias']);
unset($result['iswildcarddomain']); unset($result['iswildcarddomain']);
// translate sslenabled flag
$result['sslenabled'] = $result['ssl_enabled'];
unset($result['ssl_enabled']);
$additional_params = $this->getParamList(); $additional_params = $this->getParamList();
// unset unneeded params from this call // unset unneeded params from this call
unset($additional_params['id']); unset($additional_params['id']);

View File

@@ -202,7 +202,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
// validation // validation
$description = Validate::validate($description, 'description', Validate::REGEX_DESC_TEXT, '', [], true); $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"); $sel_stmt = Database::prepare("SELECT `id` FROM `".TABLE_PANEL_FPMDAEMONS."` WHERE `reload_cmd` = :rc");
$dupcheck = Database::pexecute_first($sel_stmt, ['rc' => $reload_cmd]); $dupcheck = Database::pexecute_first($sel_stmt, ['rc' => $reload_cmd]);
if ($dupcheck && $dupcheck['id']) { if ($dupcheck && $dupcheck['id']) {
@@ -327,7 +327,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
// validation // validation
$description = Validate::validate($description, 'description', Validate::REGEX_DESC_TEXT, '', [], true); $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"); $sel_stmt = Database::prepare("SELECT `id` FROM `".TABLE_PANEL_FPMDAEMONS."` WHERE `reload_cmd` = :rc");
$dupcheck = Database::pexecute_first($sel_stmt, ['rc' => $reload_cmd]); $dupcheck = Database::pexecute_first($sel_stmt, ['rc' => $reload_cmd]);
if ($dupcheck && $dupcheck['id'] != $id) { if ($dupcheck && $dupcheck['id'] != $id) {

View File

@@ -82,7 +82,7 @@ class Froxlor extends ApiCommand
if ($aucheck == 1) { if ($aucheck == 1) {
// anzeige über version-status mit ggfls. formular // anzeige über version-status mit ggfls. formular
// zum update schritt #1 -> download // 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 = [ $response = [
'isnewerversion' => (int) !AutoUpdate::getFromResult('has_latest'), 'isnewerversion' => (int) !AutoUpdate::getFromResult('has_latest'),
'version' => $this->version, 'version' => $this->version,

View File

@@ -174,7 +174,7 @@ class Ftps extends ApiCommand implements ResourceEntity
} elseif ($username == $password) { } elseif ($username == $password) {
Response::standardError('passwordshouldnotbeusername', '', true); Response::standardError('passwordshouldnotbeusername', '', true);
} else { } else {
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']); $path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
$cryptPassword = Crypt::makeCryptPassword($password, false, true); $cryptPassword = Crypt::makeCryptPassword($password, false, true);
$stmt = Database::prepare("INSERT INTO `" . TABLE_FTP_USERS . "` $stmt = Database::prepare("INSERT INTO `" . TABLE_FTP_USERS . "`
@@ -469,7 +469,7 @@ class Ftps extends ApiCommand implements ResourceEntity
// path update? // path update?
if ($path != '') { if ($path != '') {
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']); $path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
if ($path != $result['homedir']) { if ($path != $result['homedir']) {
$stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "` $stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "`

View File

@@ -201,7 +201,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
// validation // validation
$name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true); $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') { if (Settings::Get('system.mail_quota_enabled') != '1') {
$value_arr['email_quota'] = -1; $value_arr['email_quota'] = -1;
@@ -383,7 +383,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
// validation // validation
$name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true); $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') { if (Settings::Get('system.mail_quota_enabled') != '1') {
$value_arr['email_quota'] = -1; $value_arr['email_quota'] = -1;

View File

@@ -176,9 +176,8 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
if ((int)Settings::Get('system.use_ssl') == 1) { if ((int)Settings::Get('system.use_ssl') == 1) {
$ssl = (bool)$this->getBoolParam('ssl', true, 0); $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', !$ssl, ''), 'ssl_cert_file', '', '', [], true);
$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', !$ssl, ''), 'ssl_key_file', '', '', [], true);
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', $cert_optional, ''), 'ssl_key_file', '', '', [], true);
$ssl_ca_file = Validate::validate($this->getParam('ssl_ca_file', true, ''), 'ssl_ca_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); $ssl_cert_chainfile = Validate::validate($this->getParam('ssl_cert_chainfile', true, ''), 'ssl_cert_chainfile', '', '', [], true);
$sslss = $this->getParam('ssl_specialsettings', 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) { if ((int)Settings::Get('system.use_ssl') == 1) {
$ssl = (bool)$this->getBoolParam('ssl', true, $result['ssl']); $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', !$ssl, $result['ssl_cert_file']), 'ssl_cert_file', '', '', [], true);
$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', !$ssl, $result['ssl_key_file']), 'ssl_key_file', '', '', [], true);
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', $cert_optional, $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_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); $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']); $sslss = $this->getParam('ssl_specialsettings', true, $result['ssl_specialsettings']);

View File

@@ -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, // 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 // set default path to subdomain or domain name
if ((($path == '') || ($path == '/')) && Settings::Get('system.documentroot_use_default_value') == 1) { 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 { } else {
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']); $path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
} }
} else { } else {
// no it's not, create a redirect // no it's not, create a redirect
@@ -1078,8 +1078,10 @@ class SubDomains extends ApiCommand implements ResourceEntity
$custom_list_result = $_custom_list_result['list']; $custom_list_result = $_custom_list_result['list'];
} }
$customer_ids = []; $customer_ids = [];
$customer_stdsubs = [];
foreach ($custom_list_result as $customer) { foreach ($custom_list_result as $customer) {
$customer_ids[] = $customer['customerid']; $customer_ids[] = $customer['customerid'];
$customer_stdsubs[$customer['customerid']] = $customer['standardsubdomain'];
} }
} else { } else {
if (Settings::IsInList('panel.customer_hide_options', 'domains')) { if (Settings::IsInList('panel.customer_hide_options', 'domains')) {
@@ -1088,6 +1090,9 @@ class SubDomains extends ApiCommand implements ResourceEntity
$customer_ids = [ $customer_ids = [
$this->getUserDetail('customerid') $this->getUserDetail('customerid')
]; ];
$customer_stdsubs = [
$this->getUserDetail('customerid') => $this->getUserDetail('standardsubdomain')
];
} }
if (!empty($customer_ids)) { if (!empty($customer_ids)) {
// prepare select statement // prepare select statement
@@ -1096,6 +1101,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
FROM `" . TABLE_PANEL_DOMAINS . "` `d` FROM `" . TABLE_PANEL_DOMAINS . "` `d`
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ") WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
AND `d`.`email_only` = '0' AND `d`.`email_only` = '0'
AND `d`.`id` NOT IN (" . implode(', ', $customer_stdsubs) . ")
"); ");
$result = Database::pexecute_first($domains_stmt, null, true, true); $result = Database::pexecute_first($domains_stmt, null, true, true);
if ($result) { if ($result) {

View 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;
}
}

View 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, "/");
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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
]);
}
}

View 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;
}
}

View File

@@ -25,18 +25,19 @@
namespace Froxlor\Cli; namespace Froxlor\Cli;
use PDO;
use Exception; use Exception;
use Froxlor\Database\Database;
use Froxlor\Froxlor; use Froxlor\Froxlor;
use Froxlor\Settings; use Froxlor\Settings;
use PDO; use Froxlor\Database\Database;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
class CliCommand extends Command 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')) { 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.</>"); $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; 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'; include_once Froxlor::getInstallDir() . '/lib/tables.inc.php';
define('_CRON_UPDATE', 1); define('_CRON_UPDATE', 1);
ob_start([ ob_start([
@@ -128,11 +127,11 @@ class CliCommand extends Command
]); ]);
include_once Froxlor::getInstallDir() . '/install/updatesql.php'; include_once Froxlor::getInstallDir() . '/install/updatesql.php';
ob_end_flush(); 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; return self::SUCCESS;
} }
private function cleanUpdateOutput($buffer): string private function cleanUpdateOutput($buffer)
{ {
return strip_tags(preg_replace("/<br\W*?\/>/", "\n", $buffer)); return strip_tags(preg_replace("/<br\W*?\/>/", "\n", $buffer));
} }

View File

@@ -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"'); ->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 protected function execute(InputInterface $input, OutputInterface $output): int
{ {
require Froxlor::getInstallDir() . '/lib/functions.php'; require Froxlor::getInstallDir() . '/lib/functions.php';

View File

@@ -25,7 +25,6 @@
namespace Froxlor\Cli; namespace Froxlor\Cli;
use Exception;
use Froxlor\Config\ConfigParser; use Froxlor\Config\ConfigParser;
use Froxlor\Database\Database; use Froxlor\Database\Database;
use Froxlor\FileDir; use Froxlor\FileDir;
@@ -41,6 +40,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
final class ConfigServices extends CliCommand final class ConfigServices extends CliCommand
{ {
private $yes_to_all_supported = [ private $yes_to_all_supported = [
'bookworm', 'bookworm',
'bionic', '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)'); ->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'; require Froxlor::getInstallDir() . '/lib/functions.php';
@@ -91,7 +93,7 @@ final class ConfigServices extends CliCommand
if ($result == self::SUCCESS) { if ($result == self::SUCCESS) {
$io = new SymfonyStyle($input, $output); $io = new SymfonyStyle($input, $output);
if ($input->getOption('create')) { if ($input->getOption('create')) {
$result = $this->createConfig($output, $io); $result = $this->createConfig($input, $output, $io);
} elseif ($input->getOption('apply')) { } elseif ($input->getOption('apply')) {
$result = $this->applyConfig($input, $output, $io); $result = $this->applyConfig($input, $output, $io);
} elseif ($input->getOption('list') || $input->getOption('daemon')) { } elseif ($input->getOption('list') || $input->getOption('daemon')) {
@@ -156,10 +158,7 @@ final class ConfigServices extends CliCommand
fclose($fp); fclose($fp);
} }
/** private function createConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
* @throws Exception
*/
private function createConfig(OutputInterface $output, SymfonyStyle $io): int
{ {
$_daemons_config = [ $_daemons_config = [
'distro' => "" 'distro' => ""
@@ -286,10 +285,7 @@ final class ConfigServices extends CliCommand
return self::SUCCESS; return self::SUCCESS;
} }
/** private function applyConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
* @throws Exception
*/
private function applyConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io): int
{ {
$applyFile = $input->getOption('apply'); $applyFile = $input->getOption('apply');
@@ -433,10 +429,7 @@ final class ConfigServices extends CliCommand
} }
} }
/** private function getReplacerArray()
* @throws Exception
*/
private function getReplacerArray(): array
{ {
$customer_tmpdir = '/tmp/'; $customer_tmpdir = '/tmp/';
if (Settings::Get('system.mod_fcgid') == '1' && Settings::Get('system.mod_fcgid_tmpdir') != '') { 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'); $customer_tmpdir = Settings::Get('phpfpm.tmpdir');
} }
// try to convert nameserver hosts to ip's // try to convert namserver hosts to ip's
$ns_ips = ""; $ns_ips = "";
$known_ns_ips = []; $known_ns_ips = [];
if (Settings::Get('system.nameservers') != '') { if (Settings::Get('system.nameservers') != '') {
@@ -491,12 +484,12 @@ final class ConfigServices extends CliCommand
Database::needSqlData(); Database::needSqlData();
$sql = Database::getSqlData(); $sql = Database::getSqlData();
return [ $replace_arr = [
'<SQL_UNPRIVILEGED_USER>' => $sql['user'], '<SQL_UNPRIVILEGED_USER>' => $sql['user'],
'<SQL_UNPRIVILEGED_PASSWORD>' => $sql['passwd'], '<SQL_UNPRIVILEGED_PASSWORD>' => $sql['passwd'],
'<SQL_DB>' => $sql['db'], '<SQL_DB>' => $sql['db'],
'<SQL_HOST>' => $sql['host'], '<SQL_HOST>' => $sql['host'],
'<SQL_SOCKET>' => $sql['socket'] ?? null, '<SQL_SOCKET>' => isset($sql['socket']) ? $sql['socket'] : null,
'<SERVERNAME>' => Settings::Get('system.hostname'), '<SERVERNAME>' => Settings::Get('system.hostname'),
'<SERVERIP>' => Settings::Get('system.ipaddress'), '<SERVERIP>' => Settings::Get('system.ipaddress'),
'<NAMESERVERS>' => Settings::Get('system.nameservers'), '<NAMESERVERS>' => Settings::Get('system.nameservers'),
@@ -515,5 +508,6 @@ final class ConfigServices extends CliCommand
'<SSL_CERT_FILE>' => Settings::Get('system.ssl_cert_file'), '<SSL_CERT_FILE>' => Settings::Get('system.ssl_cert_file'),
'<SSL_KEY_FILE>' => Settings::Get('system.ssl_key_file'), '<SSL_KEY_FILE>' => Settings::Get('system.ssl_key_file'),
]; ];
return $replace_arr;
} }
} }

View File

@@ -26,13 +26,13 @@
namespace Froxlor\Cli; namespace Froxlor\Cli;
use Exception; use Exception;
use Froxlor\Config\ConfigParser;
use Froxlor\Froxlor; use Froxlor\Froxlor;
use Froxlor\Config\ConfigParser;
use Froxlor\Install\Install; use Froxlor\Install\Install;
use Froxlor\Install\Install\Core; use Froxlor\Install\Install\Core;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; 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'); ->addOption('create-userdata-from-str', 'c', InputOption::VALUE_REQUIRED, 'Creates lib/userdata.inc.php file from string created by web-install process');
} }
/** protected function execute(InputInterface $input, OutputInterface $output)
* @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{ {
$result = self::SUCCESS; $result = self::SUCCESS;
@@ -140,12 +137,10 @@ final class InstallCommand extends Command
$decoded_input = []; $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 private function showStep(int $step = 0, bool $extended = false, array $decoded_input = []): int
{ {
$result = self::SUCCESS; $result = self::SUCCESS;

View File

@@ -25,20 +25,19 @@
namespace Froxlor\Cli; 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\CronConfig;
use Froxlor\Cron\System\Extrausers; 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\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
final class MasterCron extends CliCommand 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).'); ->addOption('no-fork', 'N', InputOption::VALUE_NONE, 'Do not fork to background (traffic cron only).');
} }
/** protected function execute(InputInterface $input, OutputInterface $output)
* @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{ {
$result = $this->validateRequirements($output); $result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
if ($result != self::SUCCESS) { if ($result != self::SUCCESS) {
// requirements failed, exit // requirements failed, exit
@@ -79,7 +76,7 @@ final class MasterCron extends CliCommand
Cronjob::inserttask(TaskId::REBUILD_DNS); Cronjob::inserttask(TaskId::REBUILD_DNS);
Cronjob::inserttask(TaskId::CREATE_QUOTA); Cronjob::inserttask(TaskId::CREATE_QUOTA);
Cronjob::inserttask(TaskId::REBUILD_CRON); Cronjob::inserttask(TaskId::REBUILD_CRON);
$jobs[] = 'tasks'; array_push($jobs, 'tasks');
} }
define('CRON_IS_FORCED', 1); define('CRON_IS_FORCED', 1);
} }
@@ -97,7 +94,7 @@ final class MasterCron extends CliCommand
foreach ($tasks_to_run as $ttr) { foreach ($tasks_to_run as $ttr) {
if (in_array($ttr, [TaskId::REBUILD_VHOST, TaskId::REBUILD_DNS, TaskId::CREATE_QUOTA, TaskId::REBUILD_CRON])) { if (in_array($ttr, [TaskId::REBUILD_VHOST, TaskId::REBUILD_DNS, TaskId::CREATE_QUOTA, TaskId::REBUILD_CRON])) {
Cronjob::inserttask($ttr); Cronjob::inserttask($ttr);
$jobs[] = 'tasks'; array_push($jobs, 'tasks');
} else { } else {
$output->writeln('<comment>Unknown task number "' . $ttr . '"</>'); $output->writeln('<comment>Unknown task number "' . $ttr . '"</>');
} }
@@ -143,7 +140,7 @@ final class MasterCron extends CliCommand
$cronfile::run(); $cronfile::run();
} }
// free the lockfile // free the lockfile
$this->unlockJob(); $this->unlockJob($job);
} }
} }
@@ -176,9 +173,27 @@ final class MasterCron extends CliCommand
return $result; return $result;
} }
/** private function refreshUsers(int $jobcount = 0)
* @throws Exception {
*/ 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) private function validateOwnership(OutputInterface $output)
{ {
// when using fcgid or fpm for froxlor-vhost itself, we have to check // when using fcgid or fpm for froxlor-vhost itself, we have to check
@@ -205,6 +220,21 @@ final class MasterCron extends CliCommand
$output->writeln('OK'); $output->writeln('OK');
} }
private function getCronModule(string $cronname, OutputInterface $output)
{
$upd_stmt = Database::prepare("
SELECT `cronclass` FROM `" . TABLE_PANEL_CRONRUNS . "` WHERE `cronfile` = :cron;
");
$cron = Database::pexecute_first($upd_stmt, [
'cron' => $cronname
]);
if ($cron) {
return $cron['cronclass'];
}
$output->writeln("<error>Requested cronjob '" . $cronname . "' could not be found.</>");
return false;
}
private function lockJob(string $job, OutputInterface $output): bool private function lockJob(string $job, OutputInterface $output): bool
{ {
@@ -217,7 +247,7 @@ final class MasterCron extends CliCommand
system("kill -CHLD " . (int)$jobinfo['pid'] . " 1> /dev/null 2> /dev/null", $check_pid_return); system("kill -CHLD " . (int)$jobinfo['pid'] . " 1> /dev/null 2> /dev/null", $check_pid_return);
if ($check_pid_return == 1) { if ($check_pid_return == 1) {
// Process does not seem to run, most likely it has died // Process does not seem to run, most likely it has died
$this->unlockJob(); $this->unlockJob($job);
} else { } else {
// cronjob still running, output info and stop // cronjob still running, output info and stop
$output->writeln([ $output->writeln([
@@ -238,44 +268,8 @@ final class MasterCron extends CliCommand
return true; return true;
} }
private function unlockJob(): bool private function unlockJob(string $job): bool
{ {
return @unlink($this->lockFile); return @unlink($this->lockFile);
} }
private function getCronModule(string $cronname, OutputInterface $output)
{
$upd_stmt = Database::prepare("
SELECT `cronclass` FROM `" . TABLE_PANEL_CRONRUNS . "` WHERE `cronfile` = :cron;
");
$cron = Database::pexecute_first($upd_stmt, [
'cron' => $cronname
]);
if ($cron) {
return $cron['cronclass'];
}
$output->writeln("<error>Requested cronjob '" . $cronname . "' could not be found.</>");
return false;
}
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, [
'>'
]);
}
}
}
} }

View File

@@ -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"'); $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 ($result == self::SUCCESS) {
if ((int)Settings::Get('phpfpm.enabled') == 1) { if ((int)Settings::Get('phpfpm.enabled') == 1) {
@@ -89,7 +89,7 @@ final class PhpSessionclean extends CliCommand
if (count($paths_to_clean) > 0) { if (count($paths_to_clean) > 0) {
foreach ($paths_to_clean as $ptc) { 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"); FileDir::safe_exec("find -O3 \"" . $ptc . "\" -ignore_readdir_race -depth -mindepth 1 -name 'sess_*' -type f -cmin \"+" . $maxlifetime . "\" -delete");
} }
} }

View File

@@ -26,12 +26,14 @@
namespace Froxlor\Cli; namespace Froxlor\Cli;
use Exception; use Exception;
use Froxlor\Froxlor; use PDO;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use Froxlor\Database\Database;
use Froxlor\Froxlor;
final class RunApiCommand extends CliCommand 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)'); $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'; require Froxlor::getInstallDir() . '/lib/functions.php';
@@ -106,9 +110,6 @@ final class RunApiCommand extends CliCommand
return self::SUCCESS; return self::SUCCESS;
} }
/**
* @throws Exception
*/
private function validateCommand(string $command): array private function validateCommand(string $command): array
{ {
$command = explode(".", $command); $command = explode(".", $command);

View File

@@ -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'); ->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) { 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.</>'); $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'); $ip_list = $input->getOption('switch');
$has_error = false; $has_error = false;
$ips_to_switch = [];
foreach ($ip_list as $ips_combo) { foreach ($ip_list as $ips_combo) {
$ip_pair = explode(",", $ips_combo); $ip_pair = explode(",", $ips_combo);
if (count($ip_pair) != 2) { if (count($ip_pair) != 2) {

View File

@@ -27,9 +27,9 @@ namespace Froxlor\Cli;
use Exception; use Exception;
use Froxlor\Froxlor; use Froxlor\Froxlor;
use Froxlor\Install\AutoUpdate;
use Froxlor\Install\Update;
use Froxlor\Settings; use Froxlor\Settings;
use Froxlor\Install\Update;
use Froxlor\Install\AutoUpdate;
use Froxlor\System\Mailer; use Froxlor\System\Mailer;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
@@ -44,7 +44,6 @@ final class UpdateCommand extends CliCommand
$this->setName('froxlor:update'); $this->setName('froxlor:update');
$this->setDescription('Check for newer version and update froxlor'); $this->setDescription('Check for newer version and update froxlor');
$this->addOption('check-only', 'c', InputOption::VALUE_NONE, 'Only check for newer version and exit') $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('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('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.'); ->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; $result = self::SUCCESS;
// database update only $result = $this->validateRequirements($input, $output);
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;
}
require Froxlor::getInstallDir() . '/lib/functions.php'; require Froxlor::getInstallDir() . '/lib/functions.php';
@@ -100,7 +71,7 @@ final class UpdateCommand extends CliCommand
} }
// there is a new version // there is a new version
if ($input->getOption('check-only')) { 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 { } else {
$text = lng('admin.newerversionavailable') . ' ' . lng('admin.newerversiondetails', [AutoUpdate::getFromResult('version'), Froxlor::VERSION]); $text = lng('admin.newerversionavailable') . ' ' . lng('admin.newerversiondetails', [AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
} }
@@ -175,7 +146,7 @@ final class UpdateCommand extends CliCommand
$result = self::SUCCESS; $result = self::SUCCESS;
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i'); $question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
if ($yestoall || $helper->ask($input, $output, $question)) { if ($yestoall || $helper->ask($input, $output, $question)) {
$result = $this->runUpdate($output, true); $result = $this->updateDatabase();
} }
} else { } else {
$errmsg = 'error.autoupdate_' . $auex; $errmsg = 'error.autoupdate_' . $auex;
@@ -199,7 +170,7 @@ final class UpdateCommand extends CliCommand
if ($input->getOption('mail-notify')) { if ($input->getOption('mail-notify')) {
$last_check_version = Settings::Get('system.update_notify_last'); $last_check_version = Settings::Get('system.update_notify_last');
if (Update::versionInUpdate($last_check_version, AutoUpdate::getFromResult('version'))) { 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 = new Mailer(true);
$mail->Body = $text; $mail->Body = $text;
$mail->Subject = "[froxlor] " . lng('update.notify_subject'); $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));
}
} }

View File

@@ -26,15 +26,15 @@
namespace Froxlor\Cli; namespace Froxlor\Cli;
use Exception; 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\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; 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 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'); ->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 = self::SUCCESS;
$result = $this->validateRequirements($output); $result = $this->validateRequirements($input, $output);
require Froxlor::getInstallDir() . '/lib/functions.php'; require Froxlor::getInstallDir() . '/lib/functions.php';

View File

@@ -48,12 +48,11 @@ final class ValidateAcmeWebroot extends CliCommand
$this->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for confirmation, update files if necessary'); $this->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for confirmation, update files if necessary');
} }
/** protected function execute(InputInterface $input, OutputInterface $output)
* @throws \Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{ {
$result = $this->validateRequirements($output, true); $result = self::SUCCESS;
$result = $this->validateRequirements($input, $output, true);
$io = new SymfonyStyle($input, $output); $io = new SymfonyStyle($input, $output);
@@ -139,6 +138,8 @@ final class ValidateAcmeWebroot extends CliCommand
$io->info("Domain '" . $domain . "' Le_Webroot value is correct"); $io->info("Domain '" . $domain . "' Le_Webroot value is correct");
} }
break; break;
} else {
continue;
} }
} }
} }

View 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";
}
}

View File

@@ -244,7 +244,7 @@ abstract class DnsBase
'zonefile' => '', 'zonefile' => '',
'froxlorhost' => '1' 'froxlorhost' => '1'
]; ];
$domains[0] = $hostname_arr; $domains['none'] = $hostname_arr;
} }
if (empty($domains)) { if (empty($domains)) {

View File

@@ -129,7 +129,7 @@ class Apache extends HttpConfigBase
if ($row_ipsandports['ssl'] == '0' && Settings::Get('system.le_froxlor_redirect') == '1') { if ($row_ipsandports['ssl'] == '0' && Settings::Get('system.le_froxlor_redirect') == '1') {
$is_redirect = true; $is_redirect = true;
// check whether froxlor uses Let's Encrypt and not cert is being generated yet // 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())) { 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; $this->virtualhosts_data[$vhosts_filename] .= '# temp. disabled ssl-redirect due to Let\'s Encrypt certificate generation.' . PHP_EOL;
$is_redirect = false; $is_redirect = false;
@@ -515,7 +515,13 @@ class Apache extends HttpConfigBase
*/ */
private function createStandardDirectoryEntry() 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])) { if (!isset($this->virtualhosts_data[$vhosts_filename])) {
$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"; $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) { 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"; $this->virtualhosts_data[$ocsp_cache_filename] = 'SSLStaplingCache ' . Settings::Get('system.apache24_ocsp_cache_path') . "\n";
} else { } else {
@@ -556,7 +562,14 @@ class Apache extends HttpConfigBase
private function createStandardErrorHandler() 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') != '')) { 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])) { if (!isset($this->virtualhosts_data[$vhosts_filename])) {
$this->virtualhosts_data[$vhosts_filename] = ''; $this->virtualhosts_data[$vhosts_filename] = '';
@@ -1242,7 +1255,7 @@ class Apache extends HttpConfigBase
// >=apache-2.4 enabled? // >=apache-2.4 enabled?
if (Settings::Get('system.apache24') == '1') { if (Settings::Get('system.apache24') == '1') {
$mypath_dir = new Directory($row_diroptions['path']); $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 // for this path, as this would be the first require and therefore grant all access
if ($mypath_dir->isUserProtected() == false) { if ($mypath_dir->isUserProtected() == false) {
$this->diroptions_data[$diroptions_filename] .= ' Require all granted' . "\n"; $this->diroptions_data[$diroptions_filename] .= ' Require all granted' . "\n";

View File

@@ -202,13 +202,4 @@ class HttpConfigBase
} }
return FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $filename); 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);
}
} }

View File

@@ -26,7 +26,6 @@
namespace Froxlor\Cron\Http\LetsEncrypt; namespace Froxlor\Cron\Http\LetsEncrypt;
use Froxlor\Cron\FroxlorCron; use Froxlor\Cron\FroxlorCron;
use Froxlor\Cron\TaskId;
use Froxlor\Database\Database; use Froxlor\Database\Database;
use Froxlor\Domain\Domain; use Froxlor\Domain\Domain;
use Froxlor\FileDir; use Froxlor\FileDir;
@@ -84,7 +83,7 @@ class AcmeSh extends FroxlorCron
$renew_domains = self::renewDomains(true); $renew_domains = self::renewDomains(true);
if ($issue_froxlor || !empty($issue_domains) || !empty($renew_froxlor) || $renew_domains) { if ($issue_froxlor || !empty($issue_domains) || !empty($renew_froxlor) || $renew_domains) {
// insert task to generate certificates and vhost-configs // insert task to generate certificates and vhost-configs
Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(1);
} }
return 0; return 0;
} }
@@ -204,7 +203,7 @@ class AcmeSh extends FroxlorCron
// This is easiest done by just creating a new task ;) // This is easiest done by just creating a new task ;)
if ($changedetected) { if ($changedetected) {
if (self::$no_inserttask == false) { 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"); FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Let's Encrypt certificates have been updated");
} else { } else {

View File

@@ -863,7 +863,13 @@ class Nginx extends HttpConfigBase
// remove comments // remove comments
$vhost = implode("\n", preg_replace('/^(\s+)?#(.*)$/', '', explode("\n", $vhost))); $vhost = implode("\n", preg_replace('/^(\s+)?#(.*)$/', '', explode("\n", $vhost)));
// Break blocks into lines // 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 // Break into array items
$vhost = explode("\n", preg_replace('/[ \t]+/', ' ', trim(preg_replace('/\t+/', '', $vhost)))); $vhost = explode("\n", preg_replace('/[ \t]+/', ' ', trim(preg_replace('/\t+/', '', $vhost))));
// Remove empty lines // Remove empty lines
@@ -1161,7 +1167,14 @@ class Nginx extends HttpConfigBase
private function createStandardErrorHandler() 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') != '')) { 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])) { if (!isset($this->nginx_data[$vhosts_filename])) {
$this->nginx_data[$vhosts_filename] = ''; $this->nginx_data[$vhosts_filename] = '';

View File

@@ -154,9 +154,6 @@ class CurrentUser
]); ]);
$addition = $result['emaildomains'] != 0; $addition = $result['emaildomains'] != 0;
} elseif ($resource == 'subdomains') { } elseif ($resource == 'subdomains') {
if (Settings::IsInList('panel.customer_hide_options', 'domains')) {
$addition = false;
} else {
$parentDomainCollection = (new Collection( $parentDomainCollection = (new Collection(
SubDomains::class, SubDomains::class,
$_SESSION['userinfo'], $_SESSION['userinfo'],
@@ -168,7 +165,6 @@ class CurrentUser
] ]
)); ));
$addition = $parentDomainCollection->count() != 0; $addition = $parentDomainCollection->count() != 0;
}
} elseif ($resource == 'domains') { } elseif ($resource == 'domains') {
$customerCollection = (new Collection(Customers::class, $_SESSION['userinfo'])); $customerCollection = (new Collection(Customers::class, $_SESSION['userinfo']));
$addition = $customerCollection->count() != 0; $addition = $customerCollection->count() != 0;
@@ -187,8 +183,7 @@ class CurrentUser
if (self::getField('type_2fa') == 1) { if (self::getField('type_2fa') == 1) {
// generate code // generate code
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname')); $tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
$secret = $tfa->createSecret(); $code = $tfa->getCode($tfa->createSecret());
$code = $tfa->getCode($secret);
// set code for user // set code for user
$table = TABLE_PANEL_CUSTOMERS; $table = TABLE_PANEL_CUSTOMERS;
$uid = 'customerid'; $uid = 'customerid';
@@ -198,7 +193,7 @@ class CurrentUser
} }
$stmt = Database::prepare("UPDATE $table SET `data_2fa` = :d2fa WHERE `$uid` = :uid"); $stmt = Database::prepare("UPDATE $table SET `data_2fa` = :d2fa WHERE `$uid` = :uid");
Database::pexecute($stmt, [ Database::pexecute($stmt, [
"d2fa" => $secret, "d2fa" => $code,
"uid" => self::getField($uid) "uid" => self::getField($uid)
]); ]);
// build up & send email // build up & send email

View File

@@ -256,7 +256,7 @@ class Domain
]); ]);
$result = []; $result = [];
while ($entry = $result_stmt->fetch(PDO::FETCH_ASSOC)) { while ($entry = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $entry['id']; $result = $entry['id'];
} }
return $result; return $result;
} }
@@ -324,11 +324,8 @@ class Domain
FroxlorLogger $log FroxlorLogger $log
) { ) {
if ($aliasDestinationDomainID > 0) { if ($aliasDestinationDomainID > 0) {
$log->logAction( $log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO,
FroxlorLogger::ADM_ACTION, "LetsEncrypt CSR triggered for domain ID " . $aliasDestinationDomainID);
LOG_INFO,
"LetsEncrypt CSR triggered for domain ID " . $aliasDestinationDomainID
);
$upd_stmt = Database::prepare("UPDATE $upd_stmt = Database::prepare("UPDATE
`" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`
SET SET
@@ -352,20 +349,15 @@ class Domain
$acmesh = AcmeSh::getAcmeSh(); $acmesh = AcmeSh::getAcmeSh();
if (file_exists($acmesh)) { if (file_exists($acmesh)) {
$certificate_folder = AcmeSh::getWorkingDirFromEnv($domainname); $certificate_folder = AcmeSh::getWorkingDirFromEnv($domainname);
$certificate_ecc_folder = AcmeSh::getWorkingDirFromEnv($domainname, true); if (file_exists($certificate_folder)) {
if (file_exists($certificate_folder) || file_exists($certificate_ecc_folder)) {
$params = " --remove -d " . $domainname; $params = " --remove -d " . $domainname;
if (file_exists($certificate_ecc_folder)) { if (Settings::Get('system.leecc') > 0) {
$params .= " --ecc"; $params .= " --ecc";
} }
// run remove command // run remove command
FileDir::safe_exec($acmesh . $params); FileDir::safe_exec($acmesh . $params);
// remove certificates directory // remove certificates directory
if (file_exists($certificate_folder)) {
FileDir::safe_exec('rm -rf ' . $certificate_folder); FileDir::safe_exec('rm -rf ' . $certificate_folder);
} elseif (file_exists($certificate_ecc_folder)) {
FileDir::safe_exec('rm -rf ' . $certificate_ecc_folder);
}
} }
} }
return true; return true;

View File

@@ -55,7 +55,6 @@ class IpAddr
/** /**
* @return array * @return array
* @throws \Exception
*/ */
public static function getSslIpPortCombinations(): array public static function getSslIpPortCombinations(): array
{ {
@@ -76,7 +75,7 @@ class IpAddr
$additional_conditions_params = []; $additional_conditions_params = [];
$additional_conditions_array = []; $additional_conditions_array = [];
if (!empty($userinfo) && $userinfo['ip'] != '-1') { if ($userinfo['ip'] != '-1') {
$admin_ip_stmt = Database::prepare(" $admin_ip_stmt = Database::prepare("
SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = IN (:ipid) SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = IN (:ipid)
"); ");

View File

@@ -26,10 +26,10 @@
namespace Froxlor; namespace Froxlor;
use Exception; use Exception;
use Froxlor\Customer\Customer;
use Froxlor\Database\Database;
use PDO; use PDO;
use RecursiveCallbackFilterIterator; use RecursiveCallbackFilterIterator;
use Froxlor\Customer\Customer;
use Froxlor\Database\Database;
class FileDir class FileDir
{ {
@@ -55,8 +55,7 @@ class FileDir
int $gid, int $gid,
bool $placeindex = false, bool $placeindex = false,
bool $allow_notwithinhomedir = false bool $allow_notwithinhomedir = false
): bool ): bool {
{
if ($homeDir != '' && $dirToCreate != '') { if ($homeDir != '' && $dirToCreate != '') {
$homeDir = self::makeCorrectDir($homeDir); $homeDir = self::makeCorrectDir($homeDir);
$dirToCreate = self::makeCorrectDir($dirToCreate); $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 * Function which returns a correct dirname, means to add slashes at the beginning and at the end if there weren't
* some. If $fixes_homedir is specified, * some
*
* *
* @param string $dir the path to correct * @param string $dir the path to correct
* *
* @return string the corrected path * @return string the corrected path
* @throws Exception * @throws Exception
*/ */
public static function makeCorrectDir(string $dir, string $fixed_homedir = ""): string public static function makeCorrectDir(string $dir): string
{ {
if (strlen($dir) > 0) { if (strlen($dir) > 0) {
$dir = trim($dir); $dir = trim($dir);
@@ -127,30 +125,6 @@ class FileDir
if (substr($dir, 0, 1) != '/') { if (substr($dir, 0, 1) != '/') {
$dir = '/' . $dir; $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); return self::makeSecurePath($dir);
} }
throw new Exception("Cannot validate directory in " . __FUNCTION__ . " which is very dangerous."); throw new Exception("Cannot validate directory in " . __FUNCTION__ . " which is very dangerous.");
@@ -257,41 +231,6 @@ class FileDir
return $return; 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 * store the default index-file in a given destination folder
* *
@@ -308,11 +247,10 @@ class FileDir
string $destination, string $destination,
$logger = null, $logger = null,
bool $force = false bool $force = false
) ) {
{
if ($force || (int)Settings::Get('system.store_index_file_subs') == 1) { if ($force || (int)Settings::Get('system.store_index_file_subs') == 1) {
$result_stmt = Database::prepare(" $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` FROM `" . TABLE_PANEL_CUSTOMERS . "` AS `c` INNER JOIN `" . TABLE_PANEL_ADMINS . "` AS `a`
ON `c`.`adminid` = `a`.`adminid` ON `c`.`adminid` = `a`.`adminid`
INNER JOIN `" . TABLE_PANEL_TEMPLATES . "` AS `t` INNER JOIN `" . TABLE_PANEL_TEMPLATES . "` AS `t`
@@ -335,7 +273,7 @@ class FileDir
// replaceVariables // replaceVariables
$htmlcontent = PhpHelper::replaceVariables($template['value'], $replace_arr); $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'); $index_html_handler = fopen($indexhtmlpath, 'w');
fwrite($index_html_handler, $htmlcontent); fwrite($index_html_handler, $htmlcontent);
fclose($index_html_handler); fclose($index_html_handler);
@@ -343,7 +281,7 @@ class FileDir
$logger->logAction( $logger->logAction(
FroxlorLogger::CRON_ACTION, FroxlorLogger::CRON_ACTION,
LOG_NOTICE, 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 { } else {

View File

@@ -31,16 +31,14 @@ final class Froxlor
{ {
// Main version variable // Main version variable
const VERSION = '2.1.2'; const VERSION = '2.1.0-dev1';
// Database version (YYYYMMDDC where C is a daily counter) // Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '202312120'; const DBVERSION = '202305240';
// Distribution branding-tag (used for Debian etc.) // Distribution branding-tag (used for Debian etc.)
const BRANDING = ''; const BRANDING = '';
const DOCS_URL = 'https://docs.froxlor.org/v2.1/';
/** /**
* return path to where froxlor is installed, e.g. * return path to where froxlor is installed, e.g.
* /var/www/froxlor/ * /var/www/froxlor/

View File

@@ -104,16 +104,18 @@ class FroxlorLogger
self::$ml->pushHandler(new SyslogHandler('froxlor', LOG_USER, Logger::DEBUG)); self::$ml->pushHandler(new SyslogHandler('froxlor', LOG_USER, Logger::DEBUG));
break; break;
case 'file': 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')); $logger_logfile = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/logs/' . Settings::Get('logger.logfile'));
// is_writable needs an existing file to check if it's actually writable // is_writable needs an existing file to check if it's actually writable
if (!@touch($logger_logfile) || !is_writable($logger_logfile)) { @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 // not writable in our own directory? Skip
break; break;
} }
}
self::$ml->pushHandler(new StreamHandler($logger_logfile, Logger::DEBUG)); self::$ml->pushHandler(new StreamHandler($logger_logfile, Logger::DEBUG));
break; break;
case 'mysql': case 'mysql':

View File

@@ -64,7 +64,7 @@ class IdnaWrapper
*/ */
public function encode(string $to_encode): string 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 { try {
return $this->idna_converter->encode($to_encode); return $this->idna_converter->encode($to_encode);
} catch (InvalidArgumentException $iae) { } catch (InvalidArgumentException $iae) {

View File

@@ -68,12 +68,6 @@ class AutoUpdate
$channel = ''; $channel = '';
if (Settings::Get('system.update_channel') == 'testing') { if (Settings::Get('system.update_channel') == 'testing') {
$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); $latestversion = HttpClient::urlGet(self::UPDATE_URI . Froxlor::VERSION . $channel, true, 3);
} catch (Exception $e) { } catch (Exception $e) {

View File

@@ -26,14 +26,13 @@
namespace Froxlor\Install; namespace Froxlor\Install;
use Exception; use Exception;
use Froxlor\Config\ConfigParser; use PDO;
use Froxlor\Froxlor;
use Froxlor\Install\Install\Core; use Froxlor\Install\Install\Core;
use Froxlor\System\IPTools;
use Froxlor\UI\Panel\UI; use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request; use Froxlor\UI\Request;
use Froxlor\Config\ConfigParser;
use Froxlor\Validate\Validate; use Froxlor\Validate\Validate;
use PDO; use Froxlor\System\IPTools;
class Install class Install
{ {
@@ -42,27 +41,25 @@ class Install
public $maxSteps; public $maxSteps;
public $phpVersion; public $phpVersion;
public $formfield; 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 $suggestions = [];
public array $criticals = []; public array $criticals = [];
public array $loadedExtensions; public array $loadedExtensions;
public array $supportedOS = []; public array $supportedOS = [];
public array $webserverBackend = [ public array $webserverBackend = [
'php-fpm' => 'PHP-FPM', 'php-fpm' => 'PHP-FPM',
'fcgid' => 'FCGID (apache2 only)', 'fcgid' => 'FCGID',
'mod_php' => 'mod_php (not recommended)', 'mod_php' => 'mod_php (not recommended)',
]; ];
public function __construct(array $cliData = []) public function __construct(array $cliData = [])
{ {
// set actual php version and extensions
$this->phpVersion = phpversion();
$this->loadedExtensions = get_loaded_extensions();
// get all supported OS // get all supported OS
// show list of available distro's // show list of available distro's
$distros = glob(dirname(__DIR__, 3) . '/lib/configfiles/*.xml'); $distros = glob(dirname(__DIR__, 3) . '/lib/configfiles/*.xml');
$distributions_select[''] = '-'; $distributions_select[''] = '-';
if (in_array('xml', $this->loadedExtensions)) {
// read in all the distros // read in all the distros
foreach ($distros as $distribution) { foreach ($distros as $distribution) {
// get configparser object // get configparser object
@@ -72,7 +69,6 @@ class Install
} }
// sort by distribution name // sort by distribution name
asort($this->supportedOS); asort($this->supportedOS);
}
// guess distribution and webserver to preselect in formfield // guess distribution and webserver to preselect in formfield
$webserverBackend = $this->webserverBackend; $webserverBackend = $this->webserverBackend;
@@ -88,6 +84,10 @@ class Install
$this->extendedView = $cliData['extended'] ?? Request::any('extended', 0); $this->extendedView = $cliData['extended'] ?? Request::any('extended', 0);
$this->maxSteps = count($this->formfield['install']['sections']); $this->maxSteps = count($this->formfield['install']['sections']);
// set actual php version and extensions
$this->phpVersion = phpversion();
$this->loadedExtensions = get_loaded_extensions();
if (empty($cliData)) { if (empty($cliData)) {
// set global variables // set global variables
UI::twig()->addGlobal('install_mode', true); UI::twig()->addGlobal('install_mode', true);
@@ -99,7 +99,7 @@ class Install
} }
// check for url manipulation or wrong step // 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) || (!isset($_SESSION['installation']['stepCompleted']) && $this->currentStep > 0)
) { ) {
$this->currentStep = isset($_SESSION['installation']['stepCompleted']) ? $_SESSION['installation']['stepCompleted'] + 1 : 1; $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] ?? [], 'section' => $this->formfield['install']['sections']['step' . $this->currentStep] ?? [],
'error' => $error ?? null, 'error' => $error ?? null,
'extended' => $this->extendedView, 'extended' => $this->extendedView,
'csrf_token' => Froxlor::genSessionId(20),
]); ]);
// output view // output view
@@ -152,14 +151,16 @@ class Install
if ($this->currentStep <= $this->maxSteps) { if ($this->currentStep <= $this->maxSteps) {
// Validate user data // Validate user data
$validatedData = $this->validateRequest($formfield['sections']['step' . $this->currentStep]['fields']); $validatedData = $this->validateRequest($formfield['sections']['step' . $this->currentStep]['fields']);
// Check database connection (
if ($this->currentStep == 1) { if ($this->currentStep == 1) {
// Check database connection
$this->checkDatabase($validatedData); $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); $this->checkAdminUser($validatedData);
} elseif ($this->currentStep == 3) { }
// Check validity of system data // Check validity of system data
elseif ($this->currentStep == 3) {
$this->checkSystem($validatedData); $this->checkSystem($validatedData);
} }
$validatedData['stepCompleted'] = ($this->currentStep < $this->maxSteps) ? $this->currentStep : ($this->maxSteps - 1); $validatedData['stepCompleted'] = ($this->currentStep < $this->maxSteps) ? $this->currentStep : ($this->maxSteps - 1);
@@ -222,7 +223,7 @@ class Install
} }
// check for required extensions // check for required extensions
foreach (Requirements::REQUIRED_EXTENSIONS as $requiredExtension) { foreach ($this->requiredExtensions as $requiredExtension) {
if (in_array($requiredExtension, $this->loadedExtensions)) { if (in_array($requiredExtension, $this->loadedExtensions)) {
continue; continue;
} }
@@ -230,7 +231,7 @@ class Install
} }
// check for suggested extensions // check for suggested extensions
foreach (Requirements::SUGGESTED_EXTENSIONS as $suggestedExtension) { foreach ($this->suggestedExtensions as $suggestedExtension) {
if (in_array($suggestedExtension, $this->loadedExtensions)) { if (in_array($suggestedExtension, $this->loadedExtensions)) {
continue; continue;
} }
@@ -249,11 +250,11 @@ class Install
*/ */
private function getInformationText(): string 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]); $text = lng('install.phpinfosuccess', [$this->phpVersion]);
} else { } else {
$text = lng('install.phpinfowarn', [Requirements::REQUIRED_VERSION]); $text = lng('install.phpinfowarn', [$this->requiredVersion]);
$this->criticals[] = lng('install.phpinfoupdate', [$this->phpVersion, Requirements::REQUIRED_VERSION]); $this->criticals[] = lng('install.phpinfoupdate', [$this->phpVersion, $this->requiredVersion]);
} }
return $text; return $text;
} }
@@ -301,9 +302,9 @@ class Install
throw new Exception(lng('install.errors.nov4andnov6ip')); throw new Exception(lng('install.errors.nov4andnov6ip'));
} elseif (!empty($serveripv4) && (!Validate::validate_ip2($serveripv4, true, '', false, true) || IPTools::is_ipv6($serveripv4))) { } elseif (!empty($serveripv4) && (!Validate::validate_ip2($serveripv4, true, '', false, true) || IPTools::is_ipv6($serveripv4))) {
throw new Exception(lng('error.invalidip', [$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])); 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')); throw new Exception(lng('install.errors.servernameneedstobevalid'));
} elseif (posix_getpwnam($httpuser) === false) { } elseif (posix_getpwnam($httpuser) === false) {
throw new Exception(lng('install.errors.websrvuserdoesnotexist')); throw new Exception(lng('install.errors.websrvuserdoesnotexist'));
@@ -409,7 +410,7 @@ class Install
} else { } else {
$osrf = explode("\n", file_get_contents('/etc/os-release')); $osrf = explode("\n", file_get_contents('/etc/os-release'));
foreach ($osrf as $line) { foreach ($osrf as $line) {
$osrfline = explode("=", $line); $osrfline = explode("\n", $line);
if ($osrfline[0] == 'VERSION_CODENAME') { if ($osrfline[0] == 'VERSION_CODENAME') {
$os_dist['VERSION_CODENAME'] = $osrfline[1]; $os_dist['VERSION_CODENAME'] = $osrfline[1];
} else if ($osrfline[0] == 'ID') { } else if ($osrfline[0] == 'ID') {

View File

@@ -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'];
}

View File

@@ -220,11 +220,8 @@ class PhpHelper
if (is_dir($data_dirname)) { if (is_dir($data_dirname)) {
$data_dirhandle = opendir($data_dirname); $data_dirhandle = opendir($data_dirname);
while (false !== ($data_filename = readdir($data_dirhandle))) { while (false !== ($data_filename = readdir($data_dirhandle))) {
if ($data_filename != '.' if ($data_filename != '.' && $data_filename != '..' && $data_filename != '' && substr($data_filename,
&& $data_filename != '..' -4) == '.php') {
&& $data_filename != ''
&& substr($data_filename, -4) == '.php'
) {
$data_files[] = $data_dirname . $data_filename; $data_files[] = $data_dirname . $data_filename;
} }
} }
@@ -461,10 +458,6 @@ class PhpHelper
'directory_password', 'directory_password',
'ftp_password', 'ftp_password',
'mysql_password', 'mysql_password',
'mysql_root_pass',
'mysql_unprivileged_pass',
'admin_pass',
'admin_pass_confirm',
]; ];
if (!empty($global)) { if (!empty($global)) {
$tmp = $global; $tmp = $global;
@@ -564,17 +557,4 @@ class PhpHelper
} }
return $tab . $str; 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;
}
} }

View File

@@ -65,7 +65,7 @@ class SImExporter
public static function export() public static function export()
{ {
$settings_definitions = []; $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) { foreach ($group['fields'] as $field) {
$settings_definitions[$field['settinggroup']][$field['varname']] = $field; $settings_definitions[$field['settinggroup']][$field['varname']] = $field;
} }

View File

@@ -25,10 +25,10 @@
namespace Froxlor\Traffic; namespace Froxlor\Traffic;
use Froxlor\Api\Commands\Customers;
use Froxlor\Api\Commands\Traffic as TrafficAPI;
use Froxlor\Database\Database; use Froxlor\Database\Database;
use Froxlor\Api\Commands\Customers;
use Froxlor\UI\Collection; use Froxlor\UI\Collection;
use Froxlor\Api\Commands\Traffic as TrafficAPI;
class Traffic class Traffic
{ {
@@ -38,10 +38,10 @@ class Traffic
* @return array * @return array
* @throws \Exception * @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, $trafficCollectionObj = (new Collection(TrafficAPI::class, $userinfo,
self::getParamsByRange($range, ['customer_traffic' => true]))); self::getParamsByRange($range, ['customer_traffic' => true,])));
if ($userinfo['adminsession'] == 1) { if ($userinfo['adminsession'] == 1) {
$trafficCollectionObj->has('customer', Customers::class, 'customerid', 'customerid'); $trafficCollectionObj->has('customer', Customers::class, 'customerid', 'customerid');
} }
@@ -53,36 +53,27 @@ class Traffic
$months = []; $months = [];
$days = []; $days = [];
foreach ($trafficCollection['data']['list'] as $item) { 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 // per user total
if ($userinfo['adminsession'] == 1) {
$users[$item['customerid']]['loginname'] = $item['customer']['loginname']; $users[$item['customerid']]['loginname'] = $item['customer']['loginname'];
} $users[$item['customerid']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
$users[$item['customerid']]['total'] += $total; $users[$item['customerid']]['http'] += $item['http'];
$users[$item['customerid']]['http'] += $http; $users[$item['customerid']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
$users[$item['customerid']]['ftp'] += $ftp; $users[$item['customerid']]['mail'] += $item['mail'];
$users[$item['customerid']]['mail'] += $mail;
if (!$overview) {
// per year // per year
$years[$item['year']]['total'] += $total; $years[$item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
$years[$item['year']]['http'] += $http; $years[$item['year']]['http'] += $item['http'];
$years[$item['year']]['ftp'] += $ftp; $years[$item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
$years[$item['year']]['mail'] += $mail; $years[$item['year']]['mail'] += $item['mail'];
// per month // per month
$months[$item['month'] . '/' . $item['year']]['total'] += $total; $months[$item['month'] . '/' . $item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
$months[$item['month'] . '/' . $item['year']]['http'] += $http; $months[$item['month'] . '/' . $item['year']]['http'] += $item['http'];
$months[$item['month'] . '/' . $item['year']]['ftp'] += $ftp; $months[$item['month'] . '/' . $item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
$months[$item['month'] . '/' . $item['year']]['mail'] += $mail; $months[$item['month'] . '/' . $item['year']]['mail'] += $item['mail'];
// per day // per day
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['total'] += $total; $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'] += $http; $days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['http'] += $item['http'];
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['ftp'] += $ftp; $days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['mail'] += $mail; $days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['mail'] += $item['mail'];
}
} }
// calculate overview for given range from users // calculate overview for given range from users
@@ -94,13 +85,10 @@ class Traffic
$metrics['mail'] += $user['mail']; $metrics['mail'] += $user['mail'];
} }
$years_avail = [];
if (!$overview) {
// get all possible years for filter // get all possible years for filter
$sel_stmt = Database::prepare("SELECT DISTINCT year FROM `" . TABLE_PANEL_TRAFFIC . "` WHERE 1 ORDER BY `year` DESC"); $sel_stmt = Database::prepare("SELECT DISTINCT year FROM `" . TABLE_PANEL_TRAFFIC . "` WHERE 1 ORDER BY `year` DESC");
Database::pexecute($sel_stmt); Database::pexecute($sel_stmt);
$years_avail = $sel_stmt->fetchAll(\PDO::FETCH_ASSOC); $years_avail = $sel_stmt->fetchAll(\PDO::FETCH_ASSOC);
}
return [ return [
'metrics' => $metrics, 'metrics' => $metrics,

View 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;
}
}

View File

@@ -26,19 +26,17 @@
namespace Froxlor\UI\Callbacks; namespace Froxlor\UI\Callbacks;
use Froxlor\Settings; use Froxlor\Settings;
use Froxlor\System\Markdown;
class Customer class Customer
{ {
public static function isLocked(array $attributes): bool public static function isLocked(array $attributes)
{ {
return $attributes['fields']['loginfail_count'] >= Settings::Get('login.maxloginattempts') return $attributes['fields']['loginfail_count'] >= Settings::Get('login.maxloginattempts')
&& $attributes['fields']['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime')); && $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($attributes['fields']['custom_notes']);
return !empty($cleanNote);
} }
} }

View File

@@ -33,11 +33,6 @@ use Froxlor\UI\Panel\UI;
class Domain 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) public static function domainWithCustomerLink(array $attributes)
{ {
$linker = UI::getLinker(); $linker = UI::getLinker();
@@ -195,7 +190,7 @@ class Domain
// specified certificate for domain // specified certificate for domain
if ($attributes['fields']['domain_hascert'] == 1) { if ($attributes['fields']['domain_hascert'] == 1) {
$result['icon'] .= ' text-success'; $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) { elseif ($attributes['fields']['domain_hascert'] == 2) {
$result['icon'] .= ' text-warning'; $result['icon'] .= ' text-warning';
$result['title'] .= "\n" . lng('panel.ssleditor_infoshared'); $result['title'] .= "\n" . lng('panel.ssleditor_infoshared');

View File

@@ -95,7 +95,7 @@ class ProgressBar
$skip_customer_traffic = false; $skip_customer_traffic = false;
try { try {
$attributes['fields']['deactivated'] = 0; $attributes['fields']['deactivated'] = 0;
$result = Traffic::getCustomerStats($attributes['fields'], 'currentmonth', true); $result = Traffic::getCustomerStats($attributes['fields'], 'currentmonth');
} catch (Exception $e) { } catch (Exception $e) {
if ($e->getCode() === 405) { if ($e->getCode() === 405) {
$skip_customer_traffic = true; $skip_customer_traffic = true;

View File

@@ -47,9 +47,4 @@ class SSLCertificate
} }
return false; return false;
} }
public static function isNotLetsEncrypt(array $attributes): bool
{
return (int)$attributes['fields']['letsencrypt'] == 0;
}
} }

View File

@@ -31,17 +31,17 @@ class Style
{ {
public static function deactivated(array $attributes): string 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 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 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 public static function invalidApiKey(array $attributes): string
@@ -53,7 +53,7 @@ class Style
$isValid = false; $isValid = false;
} }
} }
return $isValid ? '' : 'table-danger'; return $isValid ? '' : 'bg-danger';
} }
public static function resultDomainTerminatedOrDeactivated(array $attributes): string public static function resultDomainTerminatedOrDeactivated(array $attributes): string
@@ -63,24 +63,25 @@ class Style
if (!empty($termination_date)) { if (!empty($termination_date)) {
$cdate = strtotime($termination_date . " 23:59:59"); $cdate = strtotime($termination_date . " 23:59:59");
$today = time(); $today = time();
$termination_css = 'table-warning'; $termination_css = 'bg-warning';
if ($cdate < $today) { if ($cdate < $today) {
$termination_css = 'table-danger'; $termination_css = 'bg-danger text-light';
} }
} }
$deactivated = $attributes['fields']['deactivated'] || $attributes['fields']['customer_deactivated']; $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 public static function resultCustomerLockedOrDeactivated(array $attributes): string
{ {
$row_css = ''; $row_css = '';
if ((int)$attributes['fields']['deactivated'] == 1) { if ((int)$attributes['fields']['deactivated'] == 1) {
$row_css = 'table-info'; $row_css = 'bg-info text-light';
} elseif ($attributes['fields']['loginfail_count'] >= Settings::Get('login.maxloginattempts') } elseif (
$attributes['fields']['loginfail_count'] >= Settings::Get('login.maxloginattempts')
&& $attributes['fields']['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime')) && $attributes['fields']['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime'))
) { ) {
$row_css = 'table-warning'; $row_css = 'bg-warning';
} }
return $row_css; return $row_css;
@@ -96,9 +97,9 @@ class Style
$style = ''; $style = '';
if ((int)$attributes[$field] >= 0) { if ((int)$attributes[$field] >= 0) {
if (($attributes[$field] / 100) * $report_max < $attributes[$field . '_used']) { 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']) { } elseif (($attributes[$field] / 100) * ($report_max - 15) < $attributes[$field . '_used']) {
$style = 'table-warning'; $style = 'bg-warning';
} }
} }
return $style; return $style;

View File

@@ -29,7 +29,6 @@ use Froxlor\CurrentUser;
use Froxlor\Database\Database; use Froxlor\Database\Database;
use Froxlor\Froxlor; use Froxlor\Froxlor;
use Froxlor\PhpHelper; use Froxlor\PhpHelper;
use Froxlor\System\Markdown;
use Froxlor\UI\Panel\UI; use Froxlor\UI\Panel\UI;
use Froxlor\User; use Froxlor\User;
use PDO; use PDO;
@@ -94,7 +93,7 @@ class Text
'entry' => $attributes['fields']['id'], 'entry' => $attributes['fields']['id'],
'id' => 'cnModal' . $attributes['fields']['id'], 'id' => 'cnModal' . $attributes['fields']['id'],
'title' => lng('usersettings.custom_notes.title') . ': ' . ($attributes['fields']['loginname'] ?? $attributes['fields']['adminname']), 'title' => lng('usersettings.custom_notes.title') . ': ' . ($attributes['fields']['loginname'] ?? $attributes['fields']['adminname']),
'body' => nl2br(Markdown::cleanCustomNotes($note)) 'body' => nl2br($note)
]; ];
} }

View File

@@ -193,14 +193,10 @@ class Form
if (!$do_show) { if (!$do_show) {
$fielddata['note'] = lng('serversettings.option_requires_otp'); $fielddata['note'] = lng('serversettings.option_requires_otp');
if (!$otp_enabled_system) { if (!$otp_enabled_system) {
$fielddata['disabled'] = true;
$fielddata['note'] .= '<br>' . lng('2fa.2fa_not_activated'); $fielddata['note'] .= '<br>' . lng('2fa.2fa_not_activated');
} elseif (!$otp_enabled_user) { } elseif (!$otp_enabled_user) {
$fielddata['disabled'] = true;
$fielddata['note'] .= '<br>' . lng('2fa.2fa_not_activated_for_user'); $fielddata['note'] .= '<br>' . lng('2fa.2fa_not_activated_for_user');
} }
// show field in any case
$do_show = true;
} }
} }

View File

@@ -25,8 +25,6 @@
namespace Froxlor\UI; namespace Froxlor\UI;
use Froxlor\Settings;
class HTML class HTML
{ {
@@ -118,7 +116,7 @@ class HTML
'label' => $navlabel, 'label' => $navlabel,
'icon' => $icon, 'icon' => $icon,
'items' => $navigation_links, 'items' => $navigation_links,
'active' => ((int)Settings::Get('panel.menu_collapsed') == 0 ? 1 : $box_active) 'active' => $box_active
]; ];
} }
} }

View File

@@ -29,7 +29,6 @@ namespace Froxlor\UI\Panel;
use Froxlor\Idna\IdnaWrapper; use Froxlor\Idna\IdnaWrapper;
use Froxlor\Settings; use Froxlor\Settings;
use Froxlor\System\Markdown;
use Parsedown; use Parsedown;
use Twig\Extension\AbstractExtension; use Twig\Extension\AbstractExtension;
use Twig\TwigFilter; use Twig\TwigFilter;
@@ -54,9 +53,9 @@ class FroxlorTwig extends AbstractExtension
$this, $this,
'idnDecodeFilter' 'idnDecodeFilter'
]), ]),
new TwigFilter('markdown', [ new TwigFilter('parsedown', [
$this, $this,
'callMarkdown' 'callParsedown'
]) ])
]; ];
} }
@@ -92,10 +91,6 @@ class FroxlorTwig extends AbstractExtension
new TwigFunction('mix', [ new TwigFunction('mix', [
$this, $this,
'getMix' 'getMix'
]),
new TwigFunction('vite', [
$this,
'getVite'
]) ])
]; ];
} }
@@ -153,9 +148,10 @@ class FroxlorTwig extends AbstractExtension
return UI::getLinker()->getLink($linkopts); 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); return mix($mix);
} }
public function getVite($basehref = '', $vite = [], $defaults = [])
{
return vite($basehref, $vite ?? $defaults);
}
} }

View File

@@ -142,6 +142,8 @@ class UI
header("X-Content-Security-Policy: " . $csp_content); header("X-Content-Security-Policy: " . $csp_content);
header("X-WebKit-CSP: " . $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 // Don't allow to load Froxlor in an iframe to prevent i.e. clickjacking
header("X-Frame-Options: DENY"); 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)) { if (!file_exists(Froxlor::getInstallDir() . '/templates/' . $theme)) {
PhpHelper::phpErrHandler(E_USER_WARNING, "Theme '" . $theme . "' could not be found.", __FILE__, __LINE__); PhpHelper::phpErrHandler(E_USER_WARNING, "Theme '" . $theme . "' could not be found.", __FILE__, __LINE__);
$theme = self::$default_theme; $theme = self::$default_theme;

View File

@@ -35,11 +35,6 @@ class Data
return self::validateFormFieldString($fieldname, $fielddata, $newfieldvalue); 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) public static function validateFormFieldString($fieldname, $fielddata, $newfieldvalue)
{ {
if (isset($fielddata['string_delimiter']) && $fielddata['string_delimiter'] != '') { 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) public static function validateFormFieldNumber($fieldname, $fielddata, $newfieldvalue)
{ {
if (isset($fielddata['min']) && (int)$newfieldvalue < (int)$fielddata['min']) { if (isset($fielddata['min']) && (int)$newfieldvalue < (int)$fielddata['min']) {
@@ -265,10 +329,4 @@ class Data
return $returnvalue; return $returnvalue;
} }
public static function validateFormFieldImage($fieldname, $fielddata, $newfieldvalue)
{
// validation is handled in \Froxlor\Settings\Store::storeSettingImage()
return true;
}
} }

View File

@@ -224,7 +224,7 @@ class Validate
* Check if the submitted string is a valid domainname * Check if the submitted string is a valid domainname
* *
* @param string $domainname The domainname which should be checked. * @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 * @return string|boolean the domain-name if the domain is valid, false otherwise
*/ */

View File

@@ -9,18 +9,11 @@ return [
* recommended value for debian/ubuntu package users is false to rely on apt and not have version mixup. * 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 * 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.) * updates the way the providers does it (e.g. automation, etc.)
*
* Default: false
*/ */
'enable_webupdate' => false, 'enable_webupdate' => false,
/** /**
* settings that have a major impact on the system or which values are used to be executed with high * @todo description
* 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
*/ */
'disable_otp_security_check' => false, 'disable_otp_security_check' => false,
]; ];

4759
lib/configfiles/bionic.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -92,7 +92,7 @@ Alias "/.well-known/acme-challenge" "{{settings.system.letsencryptchallengepath}
<command><![CDATA[{{settings.system.apachereload_command}}]]></command> <command><![CDATA[{{settings.system.apachereload_command}}]]></command>
</daemon> </daemon>
<!-- HTTP Lighttpd --> <!-- HTTP Lighttpd -->
<daemon name="lighttpd" title="LigHTTPd (deprecated)"> <daemon name="lighttpd" title="LigHTTPd">
<install><![CDATA[apt-get install lighttpd]]></install> <install><![CDATA[apt-get install lighttpd]]></install>
<file name="/etc/lighttpd/lighttpd.conf"> <file name="/etc/lighttpd/lighttpd.conf">
<content><![CDATA[ <content><![CDATA[
@@ -3383,11 +3383,6 @@ aliases: files
</visibility> </visibility>
<command><![CDATA[a2dismod php8.2]]></command> <command><![CDATA[a2dismod php8.2]]></command>
</commands> </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 <!-- instead of just restarting apache, we let the cronjob do all the
dirty work --> dirty work -->
<command><![CDATA[php {{const.install_dir}}bin/froxlor-cli froxlor:cron --force]]></command> <command><![CDATA[php {{const.install_dir}}bin/froxlor-cli froxlor:cron --force]]></command>

View File

@@ -92,7 +92,7 @@ Alias "/.well-known/acme-challenge" "{{settings.system.letsencryptchallengepath}
<command><![CDATA[{{settings.system.apachereload_command}}]]></command> <command><![CDATA[{{settings.system.apachereload_command}}]]></command>
</daemon> </daemon>
<!-- HTTP Lighttpd --> <!-- HTTP Lighttpd -->
<daemon name="lighttpd" title="LigHTTPd (deprecated)"> <daemon name="lighttpd" title="LigHTTPd">
<install><![CDATA[apt-get install lighttpd]]></install> <install><![CDATA[apt-get install lighttpd]]></install>
<file name="/etc/lighttpd/lighttpd.conf"> <file name="/etc/lighttpd/lighttpd.conf">
<content><![CDATA[ <content><![CDATA[

4962
lib/configfiles/buster.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -91,7 +91,7 @@ Alias "/.well-known/acme-challenge" "{{settings.system.letsencryptchallengepath}
<command><![CDATA[{{settings.system.apachereload_command}}]]></command> <command><![CDATA[{{settings.system.apachereload_command}}]]></command>
</daemon> </daemon>
<!-- HTTP Lighttpd --> <!-- HTTP Lighttpd -->
<daemon name="lighttpd" title="LigHTTPd (deprecated)"> <daemon name="lighttpd" title="LigHTTPd">
<install><![CDATA[apt-get install lighttpd]]></install> <install><![CDATA[apt-get install lighttpd]]></install>
<file name="/etc/lighttpd/lighttpd.conf"> <file name="/etc/lighttpd/lighttpd.conf">
<content><![CDATA[ <content><![CDATA[

Some files were not shown because too many files have changed in this diff Show More