Compare commits

..

25 Commits

Author SHA1 Message Date
d7a3568506 reject policy dmarc 2025-09-29 19:06:10 +02:00
10c13bc5b1 not generating disabled zones 2025-09-26 13:01:26 +02:00
dcb3f6f568 DKIM stuff with our own selector 2025-09-25 11:16:48 +02:00
7566def0d1 TODO: This is a dkim hack 2025-09-25 09:40:40 +02:00
3630f82817 greylisting 2.0 2025-09-24 16:45:43 +02:00
9ddd2e9154 styles 2025-09-03 12:10:46 +02:00
53afe4ebd1 new files
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-30 17:12:26 +01:00
4f69e8ee0e new css 2024-01-30 17:12:16 +01:00
32f5b0d5e9 new Theme
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-30 16:52:34 +01:00
53a6485a6e Maketank Theme migration
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-30 13:52:59 +01:00
f2643ac887 env test 3
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-13 12:46:20 +01:00
e37687a85d env test 3
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-13 12:44:29 +01:00
ccbc3286a5 env test 2
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-13 12:39:58 +01:00
929a562324 env test 2
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-13 12:36:50 +01:00
3704cf6621 env test 2
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-13 12:35:09 +01:00
10238a1466 env test
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-12 14:04:03 +01:00
9002ddf4a2 env test
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-12-12 14:03:10 +01:00
8a2de5a44a env test
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-12-12 13:59:34 +01:00
96c0af18dd npm and compose
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-12 13:50:23 +01:00
5bb228ce78 npm and compose
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-12 13:48:08 +01:00
804128280c npm and compose 2023-12-12 13:47:32 +01:00
5b8e918f75 ssh test
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-12 13:45:23 +01:00
0e3e83d184 ssh test
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-12-12 10:51:21 +01:00
8ced61c6aa bogus edit for pipeline trigger
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-12-11 15:31:39 +01:00
29a2ab7567 2.0 upgrade test first
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-12-07 12:39:20 +01:00
408 changed files with 26285 additions and 6483 deletions

53
.drone.yml Normal file
View File

@@ -0,0 +1,53 @@
kind: pipeline
name: deploy-froxlor
type: docker
platform:
os: linux
arch: arm64
trigger:
branch:
- upgrade-2.0
event:
include:
- push
environment:
DEPLOY_HOST: rechner.maketank.net
DEPLOY_DIR: ~/froxlor-test
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', 'node_modules']
args: '-v --delete'
log_level: quiet
key:
from_secret: ssh-www-data-maketank-rsa
command_timeout: 10m
- name: compose
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
- name: npm
image: appleboy/drone-ssh
settings:
host:
- rechner02.maketank.net
username: www-data
key:
from_secret: ssh-www-data-maketank-rsa
script:
- cd ~/froxlor-test && npm install && npm run build

View File

@@ -2,8 +2,7 @@ name: build-documentation
on:
release:
# only run for stable releases
types: [released]
types: [published]
jobs:
build_docs:
@@ -12,4 +11,4 @@ jobs:
- env:
GITHUB_TOKEN: ${{ secrets.ORG_GITHUB_TOKEN }}
run: |
gh workflow run --repo Froxlor/Documentation build-and-deploy.yml -f type=tags -f ref=${{github.ref_name}}
gh workflow run --repo Froxlor/Documentation build-and-deploy.yml -f type=tags ref=${{github.ref_name}}

View File

@@ -1,5 +1,5 @@
name: Froxlor-CI-MariaDB
on: [ 'push', 'pull_request', 'create' ]
on: ['push', 'pull_request', 'create']
jobs:
froxlor:
@@ -8,8 +8,8 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: [ '7.4', '8.2' ]
mariadb-version: [ 10.11, 10.5 ]
php-versions: ['7.4', '8.1']
mariadb-version: [10.5, 10.4]
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -19,7 +19,7 @@ jobs:
with:
php-version: ${{ matrix.php-versions }}
tools: composer:v2
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp, gnupg
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp
- name: Install tools
run: sudo apt-get install -y ant
@@ -49,81 +49,33 @@ jobs:
- name: Run testing
run: ant quick-build
nightly:
name: Create nightly/testing tarball
runs-on: ubuntu-latest
needs: froxlor
if: ${{ github.event_name == 'push' }}
# - name: irc push
# uses: rectalogic/notify-irc@v1
# if: github.event_name == 'push'
# with:
# channel: "#froxlor"
# server: "irc.libera.chat"
# nickname: froxlor-ci
# message: |
# ${{ github.actor }} pushed ${{ github.event.ref }} ${{ github.event.compare }}
# ${{ join(github.event.commits.*.message) }}
steps:
- name: Checkout
uses: actions/checkout@v3
# - name: irc pull request
# uses: rectalogic/notify-irc@v1
# if: github.event_name == 'pull_request'
# with:
# channel: "#froxlor"
# server: "irc.libera.chat"
# nickname: froxlor-ci
# message: |
# ${{ github.actor }} opened PR ${{ github.event.pull_request.html_url }}
- name: Setup PHP with PECL extension
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
tools: composer:v2
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp, gnupg
- name: Install composer dependencies
run: composer install --no-dev
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: '20.x'
- name: Install npm dependencies
run: npm install
- name: Build assets
run: npm run build
working-directory: .
- name: Setting file/directory permissions
run: |
find -exec chmod ugo+r,u+w,go-w {} \;
find -type f -exec chmod ugo-x {} \;
find -type d -exec chmod ugo+x {} \;
chmod 0755 bin/froxlor-cli
- name: Remove vcs and unneeded files
run: |
rm .gitignore
rm .editorconfig
rm -rf node_modules
rm composer.json
rm composer.lock
rm package.json
rm package-lock.json
rm *.xml
rm vite.config.js
- name: Create empty index.html in built assets directory
run: |
touch templates/Froxlor/build/index.html
touch templates/Froxlor/build/assets/index.html
- name: Set outputs
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Set nightly branding
run: |
sed -i "s/const BRANDING = '';/const BRANDING = '+nightly.${{steps.vars.outputs.sha_short}}';/" lib/Froxlor/Froxlor.php
zip -r froxlor-nightly.${{steps.vars.outputs.sha_short}}.zip . -x "*.git*"
sha256sum froxlor-nightly.${{steps.vars.outputs.sha_short}}.zip > froxlor-nightly.${{steps.vars.outputs.sha_short}}.zip.sha256
mkdir dist
mv froxlor-nightly.${{steps.vars.outputs.sha_short}}.zip dist/
mv froxlor-nightly.${{steps.vars.outputs.sha_short}}.zip.sha256 dist/
- name: Deploy nightly to server
uses: easingthemes/ssh-deploy@v3.4.3
env:
ARGS: "-rltDzvO --chown=${{ secrets.WEB_USER }}:${{ secrets.WEB_USER }}"
SOURCE: "dist/"
SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }}
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
TARGET: "${{ secrets.REMOTE_TARGET }}"
# - name: irc tag created
# uses: rectalogic/notify-irc@v1
# if: github.event_name == 'create' && github.event.ref_type == 'tag'
# with:
# channel: "#froxlor"
# server: "irc.libera.chat"
# nickname: froxlor-ci
# message: |
# ${{ github.actor }} tagged ${{ github.repository }} ${{ github.event.ref }}

View File

@@ -8,7 +8,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ['7.4', '8.2']
php-versions: ['7.4', '8.1']
mysql-version: [8.0, 5.7]
steps:
- name: Checkout
@@ -19,7 +19,7 @@ jobs:
with:
php-version: ${{ matrix.php-versions }}
tools: composer:v2
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp, gnupg
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp
- name: Install tools
run: sudo apt-get install -y ant

7
.gitignore vendored
View File

@@ -13,14 +13,15 @@ logs/*
*~
.well-known
.idea
.DS_Store
*.iml
img/
vendor/
node_modules/
fonts/
templates/*
!templates/index.html
!templates/Froxlor/
templates/Froxlor/build/
templates/Froxlor/assets/mix-manifest.json
templates/Froxlor/assets/css/
templates/Froxlor/assets/js/
templates/Froxlor/assets/webfonts/
!templates/misc/

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
### IRC
froxlor may be found on libera.chat, channel #froxlor:
irc://irc.libera.chat/froxlor
### Forum
The community is located on https://forum.froxlor.org/
### Documentation
### Wiki
The documentation may be found at https://docs.froxlor.org/
More documentation may be found in the froxlor - documentation:
https://docs.froxlor.org/
## License

View File

@@ -10,10 +10,9 @@ With that, good luck hacking us ;)
## Supported versions
- ️✅ **2.1.x** (`main` git-branch)
-2.0.x (`2.0.x`-tags)
- ❌ 0.10.x (`0.10.x`-tags)
- ❌ other git-branches
- ️✅ **2.x** (`main` git-branch)
-0.10.x (`0.10.x` git-branch)
- ❌ 0.9.x (`0.9.x`git-branch)
## Qualifying Vulnerabilities
@@ -27,7 +26,7 @@ With that, good luck hacking us ;)
### Vulnerabilities we accept
Only reproducible issues on a default/clean setup from the latest stable release of a supported version will be accepted.
Only reproducable issues on a default/clean setup from the latest stable release of a supported version will be accepted.
## Non-Qualifying Vulnerabilities
@@ -35,8 +34,6 @@ Only reproducible issues on a default/clean setup from the latest stable release
- Theoretical attacks without proof of exploitability
- Attacks that are the result of a third party library should be reported to the library maintainers
- Social engineering
- Attacks that require disabling security features or reducing the security level of the environment
- Exploits by an admin user itself (privileged user and implicitly trusted)
- Reflected file download
- Physical attacks
- Weak SSL/TLS/SSH algorithms or protocols
@@ -47,4 +44,4 @@ Only reproducible issues on a default/clean setup from the latest stable release
## Reporting a Vulnerability
If you think you have found a vulnerability in froxlor, please head over to [https://github.com/Froxlor/Froxlor/security/advisories](https://github.com/Froxlor/Froxlor/security/advisories/new) and use the reporting possibilities there. Also, please give us appropriate time to fix the issue and build update-packages before publishing anything into the wild. Alternatively you can email us to [team@froxlor.org](team@froxlor.org).
If you think you have found a vulnerability in froxlor, please head over to [https://huntr.dev/repos/froxlor/froxlor](https://huntr.dev/repos/froxlor/froxlor) and use the reporting possibilities there as we are funding the prize-pot for froxlor on this platform. Also, please give us appropriate time to fix the issue and build update-packages before publishing anything into the wild. Alternatively you can send us an email to [team@froxlor.org](team@froxlor.org).

View File

@@ -265,7 +265,7 @@ return [
'extras.directoryprotection' => lng('menue.extras.extras') . " / " . lng('menue.extras.directoryprotection'),
'extras.pathoptions' => lng('menue.extras.extras') . " / " . lng('menue.extras.pathoptions'),
'extras.logger' => lng('menue.extras.extras') . " / " . lng('menue.logger.logger'),
'extras.export' => lng('menue.extras.extras') . " / " . lng('menue.extras.export'),
'extras.backup' => lng('menue.extras.extras') . " / " . lng('menue.extras.backup'),
'traffic' => lng('menue.traffic.traffic'),
'traffic.http' => lng('menue.traffic.traffic') . " / HTTP",
'traffic.ftp' => lng('menue.traffic.traffic') . " / FTP",
@@ -337,15 +337,7 @@ return [
'image_name' => 'logo_login',
'default' => '',
'save_method' => 'storeSettingImage'
],
'panel_menu_collapsed' => [
'label' => lng('serversettings.panel_menu_collapsed'),
'settinggroup' => 'panel',
'varname' => 'menu_collapsed',
'type' => 'checkbox',
'default' => true,
'save_method' => 'storeSettingField',
],
]
]
]
]

View File

@@ -230,13 +230,13 @@ return [
'onlyif' => 1
]
],
'system_exportenabled' => [
'label' => lng('serversettings.exportenabled'),
'system_backupenabled' => [
'label' => lng('serversettings.backupenabled'),
'settinggroup' => 'system',
'varname' => 'exportenabled',
'varname' => 'backupenabled',
'type' => 'checkbox',
'default' => false,
'cronmodule' => 'froxlor/export',
'cronmodule' => 'froxlor/backup',
'save_method' => 'storeSettingField'
],
'system_createstdsubdom_default' => [

View File

@@ -107,8 +107,7 @@ return [
'varname' => 'enabled',
'type' => 'checkbox',
'default' => false,
'save_method' => 'storeSettingField',
'required_otp' => true
'save_method' => 'storeSettingField'
],
'api_customer_default' => [
'label' => lng('serversettings.api_customer_default'),
@@ -130,8 +129,7 @@ return [
'default' => 'stable',
'select_var' => [
'stable' => lng('serversettings.uc_stable'),
'testing' => lng('serversettings.uc_testing'),
'nightly' => lng('serversettings.uc_nightly')
'testing' => lng('serversettings.uc_testing')
],
'save_method' => 'storeSettingField',
'advanced_mode' => true
@@ -172,6 +170,16 @@ return [
'default' => false,
'save_method' => 'storeSettingField'
],
'system_index_file_extension' => [
'label' => lng('serversettings.index_file_extension'),
'settinggroup' => 'system',
'varname' => 'index_file_extension',
'type' => 'text',
'string_regexp' => '/^[a-zA-Z0-9]{1,6}$/',
'default' => 'html',
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_store_index_file_subs' => [
'label' => lng('serversettings.system_store_index_file_subs'),
'settinggroup' => 'system',

View File

@@ -46,8 +46,7 @@ return [
'type' => 'text',
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
'default' => '/usr/bin/nice -n 5 /usr/bin/php -q',
'save_method' => 'storeSettingField',
'required_otp' => true
'save_method' => 'storeSettingField'
],
'system_crondreload' => [
'label' => lng('serversettings.system_crondreload'),
@@ -56,8 +55,7 @@ return [
'type' => 'text',
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
'default' => '/etc/init.d/cron reload',
'save_method' => 'storeSettingField',
'required_otp' => true
'save_method' => 'storeSettingField'
],
'system_cron_allowautoupdate' => [
'label' => lng('serversettings.system_cron_allowautoupdate'),
@@ -65,8 +63,7 @@ return [
'varname' => 'cron_allowautoupdate',
'type' => 'checkbox',
'default' => false,
'save_method' => 'storeSettingField',
'required_otp' => true
'save_method' => 'storeSettingField'
]
]
]

View File

@@ -181,8 +181,7 @@ return [
'label' => lng('serversettings.logfiles_format'),
'settinggroup' => 'system',
'varname' => 'logfiles_format',
'type' => (strpos(Settings::Get('system.logfiles_format'), '"') !== false ? 'textarea' : 'text'),
'string_regexp' => '/^[^\0\r\n<>]*$/i',
'type' => 'text',
'default' => '',
'string_emptyallowed' => true,
'save_method' => 'storeSettingField',
@@ -308,8 +307,7 @@ return [
'type' => 'text',
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
'default' => '/etc/init.d/apache2 reload',
'save_method' => 'storeSettingField',
'required_otp' => true
'save_method' => 'storeSettingField'
],
'system_phpreload_command' => [
'label' => lng('serversettings.phpreload_command'),
@@ -321,8 +319,7 @@ return [
'save_method' => 'storeSettingField',
'websrv_avail' => [
'nginx'
],
'required_otp' => true
]
],
'system_nginx_php_backend' => [
'label' => lng('serversettings.nginx_php_backend'),

View File

@@ -157,8 +157,7 @@ return [
'string_type' => 'file',
'default' => '/root/.acme.sh/acme.sh',
'save_method' => 'storeSettingField',
'advanced_mode' => true,
'required_otp' => true
'advanced_mode' => true
],
'system_letsencryptacmeconf' => [
'label' => lng('serversettings.letsencryptacmeconf'),

View File

@@ -126,8 +126,7 @@ return [
'type' => 'textarea',
'default' => '',
'save_method' => 'storeSettingField',
'advanced_mode' => true,
'required_otp' => true
'advanced_mode' => true
],
'phpfpm_ini_values' => [
'label' => lng('phpfpm.ini_values'),
@@ -136,8 +135,7 @@ return [
'type' => 'textarea',
'default' => '',
'save_method' => 'storeSettingField',
'advanced_mode' => true,
'required_otp' => true
'advanced_mode' => true
],
'phpfpm_ini_admin_flags' => [
'label' => lng('phpfpm.ini_admin_flags'),
@@ -146,8 +144,7 @@ return [
'type' => 'textarea',
'default' => '',
'save_method' => 'storeSettingField',
'advanced_mode' => true,
'required_otp' => true
'advanced_mode' => true
],
'phpfpm_ini_admin_values' => [
'label' => lng('phpfpm.ini_admin_values'),
@@ -156,8 +153,7 @@ return [
'type' => 'textarea',
'default' => '',
'save_method' => 'storeSettingField',
'advanced_mode' => true,
'required_otp' => true
'advanced_mode' => true
]
]
]

View File

@@ -80,8 +80,7 @@ return [
'type' => 'text',
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
'default' => '/etc/init.d/bind9 reload',
'save_method' => 'storeSettingField',
'required_otp' => true
'save_method' => 'storeSettingField'
],
'system_nameservers' => [
'label' => lng('serversettings.nameservers'),
@@ -112,8 +111,7 @@ return [
'string_delimiter' => ',',
'string_emptyallowed' => true,
'default' => '',
'save_method' => 'storeSettingField',
'required_otp' => true
'save_method' => 'storeSettingField'
],
'system_powerdns_mode' => [
'label' => lng('serversettings.powerdns_mode'),

View File

@@ -137,8 +137,7 @@ return [
'type' => 'text',
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
'default' => '/etc/init.d/dkim-filter restart',
'save_method' => 'storeSettingField',
'required_otp' => true
'save_method' => 'storeSettingField'
]
]
]

View File

@@ -43,8 +43,7 @@ return [
'settinggroup' => 'spf',
'varname' => 'spf_entry',
'type' => 'text',
'string_regexp' => '/^v=spf[a-z0-9:~?\s.-]+$/i',
'default' => 'v=spf1 a mx -all',
'default' => '"v=spf1 a mx -all"',
'save_method' => 'storeSettingField'
]
]

View File

@@ -37,8 +37,7 @@ return [
'varname' => 'unix_names',
'type' => 'checkbox',
'default' => true,
'save_method' => 'storeSettingField',
'required_otp' => true
'save_method' => 'storeSettingField'
],
'system_mailpwcleartext' => [
'label' => lng('serversettings.mailpwcleartext'),
@@ -47,8 +46,7 @@ return [
'type' => 'checkbox',
'default' => false,
'save_method' => 'storeSettingField',
'advanced_mode' => true,
'required_otp' => true
'advanced_mode' => true
],
'system_passwordcryptfunc' => [
'label' => lng('serversettings.passwordcryptfunc'),
@@ -61,8 +59,7 @@ return [
'getAvailablePasswordHashes'
],
'save_method' => 'storeSettingField',
'advanced_mode' => true,
'required_otp' => true
'advanced_mode' => true
],
'system_allow_error_report_admin' => [
'label' => lng('serversettings.allow_error_report_admin'),
@@ -70,8 +67,7 @@ return [
'varname' => 'allow_error_report_admin',
'type' => 'checkbox',
'default' => false,
'save_method' => 'storeSettingField',
'required_otp' => true
'save_method' => 'storeSettingField'
],
'system_allow_error_report_customer' => [
'label' => lng('serversettings.allow_error_report_customer'),
@@ -79,8 +75,7 @@ return [
'varname' => 'allow_error_report_customer',
'type' => 'checkbox',
'default' => false,
'save_method' => 'storeSettingField',
'required_otp' => true
'save_method' => 'storeSettingField'
],
'system_allow_customer_shell' => [
'label' => lng('serversettings.allow_allow_customer_shell'),
@@ -89,8 +84,7 @@ return [
'type' => 'checkbox',
'default' => false,
'save_method' => 'storeSettingField',
'advanced_mode' => true,
'required_otp' => true
'advanced_mode' => true
],
'system_available_shells' => [
'label' => lng('serversettings.available_shells'),
@@ -100,8 +94,7 @@ return [
'string_emptyallowed' => true,
'default' => '',
'save_method' => 'storeSettingField',
'advanced_mode' => true,
'required_otp' => true
'advanced_mode' => true
],
'system_froxlorusergroup' => [
'label' => lng('serversettings.froxlorusergroup'),
@@ -115,8 +108,7 @@ return [
'checkLocalGroup'
],
'visible' => Settings::Get('system.nssextrausers'),
'advanced_mode' => true,
'required_otp' => true
'advanced_mode' => true
],
]
]

View File

@@ -44,30 +44,24 @@ return [
'settinggroup' => 'system',
'varname' => 'diskquota_repquota_path',
'type' => 'text',
'string_type' => 'file',
'default' => '/usr/sbin/repquota',
'save_method' => 'storeSettingField',
'required_otp' => true
'save_method' => 'storeSettingField'
],
'system_diskquota_quotatool_path' => [
'label' => lng('serversettings.diskquota_quotatool_path.description'),
'settinggroup' => 'system',
'varname' => 'diskquota_quotatool_path',
'type' => 'text',
'string_type' => 'file',
'default' => '/usr/bin/quotatool',
'save_method' => 'storeSettingField',
'required_otp' => true
'save_method' => 'storeSettingField'
],
'system_diskquota_customer_partition' => [
'label' => lng('serversettings.diskquota_customer_partition.description'),
'settinggroup' => 'system',
'varname' => 'diskquota_customer_partition',
'type' => 'text',
'string_type' => 'file',
'default' => '/dev/root',
'save_method' => 'storeSettingField',
'required_otp' => true
'save_method' => 'storeSettingField'
]
]
]

View File

@@ -62,7 +62,7 @@ if ($action == 'delete' && function_exists('apcu_clear_cache') && $userinfo['cha
}
if (!function_exists('apcu_cache_info') || !function_exists('apcu_sma_info')) {
Response::standardError('no_apcuinfo');
Response::standardError(lng('error.no_apcuinfo'));
}
if ($page == 'showinfo' && $userinfo['change_serversettings'] == '1') {

View File

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

View File

@@ -30,9 +30,9 @@ use Froxlor\Api\Commands\Customers as Customers;
use Froxlor\Api\Commands\Domains as Domains;
use Froxlor\Bulk\DomainBulkAction;
use Froxlor\Cron\TaskId;
use Froxlor\CurrentUser;
use Froxlor\Customer\Customer;
use Froxlor\Database\Database;
use Froxlor\Domain\Domain;
use Froxlor\FileDir;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
@@ -45,6 +45,7 @@ use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\User;
use Froxlor\Validate\Validate;
use Froxlor\CurrentUser;
$id = (int)Request::any('id');
@@ -113,11 +114,15 @@ if ($page == 'domains' || $page == 'overview') {
} elseif ($alias_check['count'] > 0) {
Response::standardError('domains_cantdeletedomainwithaliases');
} else {
HTML::askYesNo('admin_domain_reallydelete', $filename, [
$showcheck = false;
if (Domain::domainHasMainSubDomains($id)) {
$showcheck = true;
}
HTML::askYesNoWithCheckbox('admin_domain_reallydelete', 'remove_subbutmain_domains', $filename, [
'id' => $id,
'page' => $page,
'action' => $action
], $idna_convert->decode($result['domain']));
], $idna_convert->decode($result['domain']), $showcheck);
}
}
} elseif ($action == 'add') {
@@ -247,6 +252,21 @@ if ($page == 'domains' || $page == 'overview') {
$domains[$row_domain['id']] = $idna_convert->decode($row_domain['domain']) . ' (' . $row_domain['loginname'] . ')';
}
$subtodomains = [
0 => lng('domains.nosubtomaindomain')
];
$result_domains_stmt = Database::prepare("
SELECT `d`.`id`, `d`.`domain`, `c`.`loginname` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c`
WHERE `d`.`aliasdomain` IS NULL AND `d`.`parentdomainid` = 0 AND `d`.`ismainbutsubto` = 0 " . $standardsubdomains . ($userinfo['customers_see_all'] ? '' : " AND `d`.`adminid` = :adminid") . "
AND `d`.`customerid`=`c`.`customerid` ORDER BY `loginname`, `domain` ASC
");
// params from above still valid
Database::pexecute($result_domains_stmt, $params);
while ($row_domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) {
$subtodomains[$row_domain['id']] = $idna_convert->decode($row_domain['domain']) . ' (' . $row_domain['loginname'] . ')';
}
$phpconfigs = [];
$configs = Database::query("
SELECT c.*, fc.description as interpreter
@@ -449,6 +469,27 @@ if ($page == 'domains' || $page == 'overview') {
$domains[$row_domain['id']] = $idna_convert->decode($row_domain['domain']);
}
$subtodomains = [
0 => lng('domains.nosubtomaindomain')
];
$result_domains_stmt = Database::prepare("
SELECT `d`.`id`, `d`.`domain` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c`
WHERE `d`.`aliasdomain` IS NULL AND `d`.`parentdomainid` = '0' AND `d`.`id` <> :id
AND `c`.`standardsubdomain`<>`d`.`id` AND `c`.`customerid`=`d`.`customerid`" . ($userinfo['customers_see_all'] ? '' : " AND `d`.`adminid` = :adminid") . "
ORDER BY `d`.`domain` ASC
");
$params = [
'id' => $result['id']
];
if ($userinfo['customers_see_all'] == '0') {
$params['adminid'] = $userinfo['adminid'];
}
Database::pexecute($result_domains_stmt, $params);
while ($row_domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) {
$subtodomains[$row_domain['id']] = $idna_convert->decode($row_domain['domain']);
}
if ($userinfo['ip'] == "-1") {
$result_ipsandports_stmt = Database::query("
SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `ssl`='0' ORDER BY `ip`, `port` ASC
@@ -635,23 +676,6 @@ if ($page == 'domains' || $page == 'overview') {
'alert_msg' => lng('domains.import_description')
]);
}
} elseif ($action == 'duplicate') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
try {
Domains::getLocal($userinfo, $_POST)->duplicate();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page,
'searchfield' => 'd.domain_ace',
'searchtext' => Request::post('domain', "")
]);
} else {
Response::redirectTo($filename, [
'page' => 'overview'
]);
}
}
} elseif ($page == 'domainssleditor') {
require_once __DIR__ . '/ssl_editor.php';

View File

@@ -31,7 +31,6 @@ use Froxlor\Api\Commands\Froxlor as Froxlor;
use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
use Froxlor\Language;
use Froxlor\Settings;
use Froxlor\System\Cronjob;
use Froxlor\System\Crypt;
@@ -39,6 +38,7 @@ use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\Validate\Validate;
use Froxlor\Language;
$id = (int)Request::any('id');
@@ -197,104 +197,107 @@ if ($page == 'overview') {
'outstanding_tasks' => $outstanding_tasks,
'cron_last_runs' => $cron_last_runs
]);
} elseif ($page == 'profile') {
$languages = Language::getLanguages();
} elseif ($page == 'change_password') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
$old_password = Validate::validate($_POST['old_password'], 'old password');
if (!empty($_POST)) {
if ($_POST['send'] == 'changepassword') {
$old_password = Validate::validate($_POST['old_password'], 'old password');
if (!Crypt::validatePasswordLogin($userinfo, $old_password, TABLE_PANEL_ADMINS, 'adminid')) {
Response::standardError('oldpasswordnotcorrect');
}
if (!Crypt::validatePasswordLogin($userinfo, $old_password, TABLE_PANEL_ADMINS, 'adminid')) {
Response::standardError('oldpasswordnotcorrect');
}
try {
$new_password = Crypt::validatePassword($_POST['new_password'], 'new password');
$new_password_confirm = Crypt::validatePassword($_POST['new_password_confirm'], 'new password confirm');
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
if ($old_password == '') {
Response::standardError([
'stringisempty',
'changepassword.old_password'
]);
} elseif ($new_password == '') {
Response::standardError([
'stringisempty',
'changepassword.new_password'
]);
} elseif ($new_password_confirm == '') {
Response::standardError([
'stringisempty',
'changepassword.new_password_confirm'
]);
} elseif ($new_password != $new_password_confirm) {
Response::standardError('newpasswordconfirmerror');
} else {
try {
$new_password = Crypt::validatePassword($_POST['new_password'], 'new password');
$new_password_confirm = Crypt::validatePassword($_POST['new_password_confirm'], 'new password confirm');
Admins::getLocal($userinfo, [
'id' => $userinfo['adminid'],
'admin_password' => $new_password
])->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
if ($old_password == '') {
Response::standardError([
'stringisempty',
'changepassword.old_password'
]);
} elseif ($new_password == '') {
Response::standardError([
'stringisempty',
'changepassword.new_password'
]);
} elseif ($new_password_confirm == '') {
Response::standardError([
'stringisempty',
'changepassword.new_password_confirm'
]);
} elseif ($new_password != $new_password_confirm) {
Response::standardError('newpasswordconfirmerror');
} else {
try {
Admins::getLocal($userinfo, [
'id' => $userinfo['adminid'],
'admin_password' => $new_password
])->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, 'changed password');
Response::redirectTo($filename);
}
} elseif ($_POST['send'] == 'changetheme') {
if (Settings::Get('panel.allow_theme_change_admin') == 1) {
$theme = Validate::validate($_POST['theme'], 'theme');
try {
Admins::getLocal($userinfo, [
'id' => $userinfo['adminid'],
'theme' => $theme
])->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "changed his/her theme to '" . $theme . "'");
}
Response::redirectTo($filename);
} elseif ($_POST['send'] == 'changelanguage') {
$def_language = Validate::validate($_POST['def_language'], 'default language');
if (isset($languages[$def_language])) {
try {
Admins::getLocal($userinfo, [
'id' => $userinfo['adminid'],
'def_language' => $def_language
])->update();
CurrentUser::setField('language', $def_language);
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
}
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "changed his/her default language to '" . $def_language . "'");
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, 'changed password');
Response::redirectTo($filename);
}
} else {
// change theme
$default_theme = Settings::Get('panel.default_theme');
if ($userinfo['theme'] != '') {
$default_theme = $userinfo['theme'];
}
$themes_avail = UI::getThemes();
UI::view('user/change_password.html.twig');
}
} elseif ($page == 'change_language') {
$languages = Language::getLanguages();
if (isset($_POST['send']) && $_POST['send'] == 'send') {
$def_language = Validate::validate($_POST['def_language'], 'default language');
// change language
if (isset($languages[$def_language])) {
try {
Admins::getLocal($userinfo, [
'id' => $userinfo['adminid'],
'def_language' => $def_language
])->update();
CurrentUser::setField('language', $def_language);
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
}
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "changed his/her default language to '" . $def_language . "'");
Response::redirectTo($filename);
} else {
$default_lang = Settings::Get('panel.standardlanguage');
if ($userinfo['def_language'] != '') {
$default_lang = $userinfo['def_language'];
}
UI::view('user/profile.html.twig', [
'themes' => $themes_avail,
'default_theme' => $default_theme,
UI::view('user/change_language.html.twig', [
'languages' => $languages,
'default_lang' => $default_lang,
'default_lang' => $default_lang
]);
}
} elseif ($page == 'change_theme') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
$theme = Validate::validate($_POST['theme'], 'theme');
try {
Admins::getLocal($userinfo, [
'id' => $userinfo['adminid'],
'theme' => $theme
])->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "changed his/her theme to '" . $theme . "'");
Response::redirectTo($filename);
} else {
$default_theme = Settings::Get('panel.default_theme');
if ($userinfo['theme'] != '') {
$default_theme = $userinfo['theme'];
}
$themes_avail = UI::getThemes();
UI::view('user/change_theme.html.twig', [
'themes' => $themes_avail,
'default_theme' => $default_theme
]);
}
} elseif ($page == 'send_error_report' && Settings::Get('system.allow_error_report_admin') == '1') {

View File

@@ -33,9 +33,9 @@ const AREA = 'admin';
require __DIR__ . '/lib/init.php';
use Froxlor\FroxlorLogger;
use Froxlor\UI\HTML;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Response;
use Froxlor\UI\HTML;
if ($action == 'reset' && function_exists('opcache_reset') && $userinfo['change_serversettings'] == '1') {
if ($_POST['send'] == 'send') {
@@ -57,30 +57,252 @@ if ($action == 'reset' && function_exists('opcache_reset') && $userinfo['change_
}
}
if (!extension_loaded('Zend OPcache')) {
Response::standardError('no_opcacheinfo');
}
$ocEnabled = ini_get('opcache.enable');
if (empty($ocEnabled)) {
Response::standardError('inactive_opcacheinfo');
if (!function_exists('opcache_get_configuration')) {
Response::standardError(lng('error.no_opcacheinfo'));
}
if ($page == 'showinfo' && $userinfo['change_serversettings'] == '1') {
$time = time();
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "viewed OPcache info");
$opcache = (new \Amnuts\Opcache\Service())->getData();
$optimizationLevels = [
1 << 0 => 'CSE, STRING construction',
1 << 1 => 'Constant conversion and jumps',
1 << 2 => '++, +=, series of jumps',
1 << 3 => 'INIT_FCALL_BY_NAME -> DO_FCALL',
1 << 4 => 'CFG based optimization',
1 << 5 => 'DFA based optimization',
1 << 6 => 'CALL GRAPH optimization',
1 << 7 => 'SCCP (constant propagation)',
1 << 8 => 'TMP VAR usage',
1 << 9 => 'NOP removal',
1 << 10 => 'Merge equal constants',
1 << 11 => 'Adjust used stack',
1 << 12 => 'Remove unused variables',
1 << 13 => 'DCE (dead code elimination)',
1 << 14 => '(unsafe) Collect constants',
1 << 15 => 'Inline functions'
];
$jitModes = [
[
'flag' => 'CPU-specific optimization',
'value' => [
'Disable CPU-specific optimization',
'Enable use of AVX, if the CPU supports it'
]
],
[
'flag' => 'Register allocation',
'value' => [
'Do not perform register allocation',
'Perform block-local register allocation',
'Perform global register allocation'
]
],
[
'flag' => 'Trigger',
'value' => [
'Compile all functions on script load',
'Compile functions on first execution',
'Profile functions on first request and compile the hottest functions afterwards',
'Profile on the fly and compile hot functions',
'Currently unused',
'Use tracing JIT. Profile on the fly and compile traces for hot code segments'
]
],
[
'flag' => 'Optimization level',
'value' => [
'No JIT',
'Minimal JIT (call standard VM handlers)',
'Inline VM handlers',
'Use type inference',
'Use call graph',
'Optimize whole script'
]
]
];
$jitModeMapping = [
'tracing' => 1254,
'on' => 1254,
'function' => 1205
];
$status = opcache_get_status(false);
$config = opcache_get_configuration();
$missingConfig = array_diff_key(ini_get_all('zend opcache', false), $config['directives']);
if (!empty($missingConfig)) {
$config['directives'] = array_merge($config['directives'], $missingConfig);
}
$files = [];
if (!empty($status['scripts'])) {
uasort($status['scripts'], static function ($a, $b) {
return $a['hits'] <=> $b['hits'];
});
foreach ($status['scripts'] as &$file) {
$file['full_path'] = str_replace('\\', '/', $file['full_path']);
$file['readable'] = [
'hits' => number_format($file['hits']),
'memory_consumption' => bsize($file['memory_consumption'])
];
}
$files = array_values($status['scripts']);
}
if ($config['directives']['opcache.file_cache_only'] || !empty($status['file_cache_only'])) {
$overview = false;
} else {
$status['opcache_statistics']['start_time'] = $status['opcache_statistics']['start_time'] ?? time();
$status['opcache_statistics']['last_restart_time'] = $status['opcache_statistics']['last_restart_time'] ?? time();
$overview = array_merge(
$status['memory_usage'],
$status['opcache_statistics'],
[
'total_memory' => $config['directives']['opcache.memory_consumption'],
'used_memory_percentage' => round(100 * (
($status['memory_usage']['used_memory'] + $status['memory_usage']['wasted_memory'])
/ $config['directives']['opcache.memory_consumption']
)),
'hit_rate_percentage' => round($status['opcache_statistics']['opcache_hit_rate']),
'used_key_percentage' => round(100 * ($status['opcache_statistics']['num_cached_keys']
/ $status['opcache_statistics']['max_cached_keys']
)),
'wasted_percentage' => round($status['memory_usage']['current_wasted_percentage'], 2),
'readable' => [
'total_memory' => bsize($config['directives']['opcache.memory_consumption']),
'used_memory' => bsize($status['memory_usage']['used_memory']),
'free_memory' => bsize($status['memory_usage']['free_memory']),
'wasted_memory' => bsize($status['memory_usage']['wasted_memory']),
'num_cached_scripts' => number_format($status['opcache_statistics']['num_cached_scripts']),
'hits' => number_format($status['opcache_statistics']['hits']),
'misses' => number_format($status['opcache_statistics']['misses']),
'blacklist_miss' => number_format($status['opcache_statistics']['blacklist_misses']),
'num_cached_keys' => number_format($status['opcache_statistics']['num_cached_keys']),
'max_cached_keys' => number_format($status['opcache_statistics']['max_cached_keys']),
'interned' => null,
'start_time' => (new DateTimeImmutable("@{$status['opcache_statistics']['start_time']}"))
->setTimezone(new DateTimeZone(date_default_timezone_get()))
->format('Y-m-d H:i:s'),
'last_restart_time' => ($status['opcache_statistics']['last_restart_time'] == 0
? 'never'
: (new DateTimeImmutable("@{$status['opcache_statistics']['last_restart_time']}"))
->setTimezone(new DateTimeZone(date_default_timezone_get()))
->format('Y-m-d H:i:s')
)
]
]
);
}
$preload = [];
if (!empty($status['preload_statistics']['scripts'])) {
$preload = $status['preload_statistics']['scripts'];
sort($preload, SORT_STRING);
if ($overview) {
$overview['preload_memory'] = $status['preload_statistics']['memory_consumption'];
$overview['readable']['preload_memory'] = bsize($status['preload_statistics']['memory_consumption']);
}
}
if (!empty($status['interned_strings_usage'])) {
$overview['readable']['interned'] = [
'buffer_size' => bsize($status['interned_strings_usage']['buffer_size']),
'strings_used_memory' => bsize($status['interned_strings_usage']['used_memory']),
'strings_free_memory' => bsize($status['interned_strings_usage']['free_memory']),
'number_of_strings' => number_format($status['interned_strings_usage']['number_of_strings'])
];
}
if ($overview && !empty($status['jit'])) {
$overview['jit_buffer_used_percentage'] = ($status['jit']['buffer_size']
? round(100 * (($status['jit']['buffer_size'] - $status['jit']['buffer_free']) / $status['jit']['buffer_size']))
: 0
);
$overview['readable'] = array_merge($overview['readable'], [
'jit_buffer_size' => bsize($status['jit']['buffer_size']),
'jit_buffer_free' => bsize($status['jit']['buffer_free'])
]);
}
$directives = [];
ksort($config['directives']);
foreach ($config['directives'] as $k => $v) {
if (in_array($k, ['opcache.max_file_size', 'opcache.memory_consumption', 'opcache.jit_buffer_size']) && $v) {
$v = bsize($v) . " ({$v})";
} elseif ($k === 'opcache.optimization_level') {
$levels = [];
foreach ($optimizationLevels as $level => $info) {
if ($level & $v) {
$levels[] = "{$info} [{$level}]";
}
}
$v = $levels ?: 'none';
} elseif ($k === 'opcache.jit') {
if ($v === '1') {
$v = 'on';
}
if (isset($jitModeMapping[$v]) || is_numeric($v)) {
$levels = [];
foreach (str_split((string)($jitModeMapping[$v] ?? $v)) as $type => $level) {
$levels[] = "{$level}: {$jitModes[$type]['value'][$level]} ({$jitModes[$type]['flag']})";
}
$v = [$v, $levels];
} elseif (empty($v) || strtolower($v) === 'off') {
$v = 'Off';
}
}
$directives[] = [
'k' => $k,
'v' => $v
];
}
$version = array_merge(
$config['version'],
[
'php' => phpversion(),
'server' => $_SERVER['SERVER_SOFTWARE'] ?: '',
'host' => (function_exists('gethostname')
? gethostname()
: (php_uname('n')
?: (empty($_SERVER['SERVER_NAME'])
? $_SERVER['HOST_NAME']
: $_SERVER['SERVER_NAME']
)
)
)
]
);
UI::view('settings/opcacheinfo.html.twig', [
'opcacheinfo' => [
'version' => $opcache['version'],
'overview' => $opcache['overview'],
'files' => $opcache['files'],
'preload' => $opcache['preload'],
'directives' => $opcache['directives'],
'blacklist' => $opcache['blacklist'],
'functions' => $opcache['functions'],
'version' => $version,
'overview' => $overview,
'files' => $files,
'preload' => $preload,
'directives' => $directives,
'blacklist' => $config['blacklist'],
'functions' => get_extension_funcs('Zend OPcache')
]
]);
}
function bsize($size)
{
$i = 0;
$val = ['b', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
while (($size / 1024) > 1) {
$size /= 1024;
++$i;
}
return sprintf(
'%.2f%s%s',
$size,
'',
$val[$i]
);
}

View File

@@ -70,15 +70,14 @@ if ($page == 'overview' && $userinfo['change_serversettings'] == '1') {
// check if the session timeout is too low #815
if (isset($_POST['session_sessiontimeout']) && $_POST['session_sessiontimeout'] < 60) {
Response::standardError(['session_timeout', 'session_timeout_desc']);
Response::standardError(lng('error.session_timeout'), lng('error.session_timeout_desc'));
}
try {
if (Form::processForm($settings_data, $_POST, [
'filename' => $filename,
'action' => $action,
'page' => $page,
'part' => $_part,
'page' => $page
], $_part, $settings_all, $settings_part, $only_enabledisable)) {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "rebuild configfiles due to changed setting");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
@@ -133,7 +132,7 @@ if ($page == 'overview' && $userinfo['change_serversettings'] == '1') {
}
}
} else {
Response::standardError('error.no_phpinfo');
Response::standardError(lng('error.no_phpinfo'));
}
UI::view('settings/phpinfo.html.twig', [
'phpversion' => PHP_VERSION,

View File

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

View File

@@ -24,8 +24,20 @@
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
use Froxlor\Froxlor;
declare(strict_types=1);
use Froxlor\Cli\ConfigDiff;
use Symfony\Component\Console\Application;
use Froxlor\Cli\RunApiCommand;
use Froxlor\Cli\ConfigServices;
use Froxlor\Cli\PhpSessionclean;
use Froxlor\Cli\SwitchServerIp;
use Froxlor\Cli\UpdateCommand;
use Froxlor\Cli\InstallCommand;
use Froxlor\Cli\MasterCron;
use Froxlor\Cli\UserCommand;
use Froxlor\Cli\ValidateAcmeWebroot;
use Froxlor\Froxlor;
// validate correct php version
if (version_compare("7.4.0", PHP_VERSION, ">=")) {
@@ -41,31 +53,14 @@ require dirname(__DIR__) . '/vendor/autoload.php';
require dirname(__DIR__) . '/lib/tables.inc.php';
$application = new Application('froxlor-cli', Froxlor::getFullVersion());
// files that are no commands
$fileIgnoreList = [
// Current non-command files
'CliCommand.php',
'index.html',
'install.functions.php',
];
// directory of commands to include
$cmd_files = glob(Froxlor::getInstallDir() . '/lib/Froxlor/Cli/*.php');
// include and add commands
foreach ($cmd_files as $cmdFile) {
// check ignore-list
if (!in_array(basename($cmdFile), $fileIgnoreList)) {
// include class-file
require $cmdFile;
// create class-name including namespace
$cmdClass = "\\Froxlor\\Cli\\" . substr(basename($cmdFile), 0, -4);
// check whether it exists
if (class_exists($cmdClass) && is_subclass_of($cmdClass, '\Symfony\Component\Console\Command\Command')) {
// add to cli application
$application->add(new $cmdClass());
}
}
}
$application->add(new RunApiCommand());
$application->add(new ConfigServices());
$application->add(new PhpSessionclean());
$application->add(new SwitchServerIp());
$application->add(new UpdateCommand());
$application->add(new InstallCommand());
$application->add(new MasterCron());
$application->add(new UserCommand());
$application->add(new ValidateAcmeWebroot());
$application->add(new ConfigDiff());
$application->run();

View File

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

911
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';
use Froxlor\Api\Commands\SubDomains as SubDomains;
use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\Domain\Domain;
use Froxlor\FileDir;
@@ -41,6 +40,7 @@ use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\Validate\Validate;
use Froxlor\CurrentUser;
// redirect if this customer page is hidden via settings
if (Settings::IsInList('panel.customer_hide_options', 'domains')) {
@@ -51,7 +51,7 @@ $id = (int)Request::any('id');
if ($page == 'overview' || $page == 'domains') {
if ($action == '') {
$log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "viewed customer_domains::domains");
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_domains::domains");
$parentdomain_id = (int)Request::any('pid', '0');
@@ -63,32 +63,20 @@ if ($page == 'overview' || $page == 'domains') {
Response::dynamicError($e->getMessage());
}
$actions_links = [];
$actions_links = false;
if (CurrentUser::canAddResource('subdomains')) {
$actions_links[] = [
'href' => $linker->getLink(['section' => 'domains', 'page' => 'domains', 'action' => 'add']),
'label' => lng('domains.subdomain_add')
$actions_links = [
[
'href' => $linker->getLink(['section' => 'domains', 'page' => 'domains', 'action' => 'add']),
'label' => lng('domains.subdomain_add')
]
];
}
$actions_links[] = [
'href' => \Froxlor\Froxlor::DOCS_URL . 'user-guide/domains/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
$table_tpl = 'table.html.twig';
if ($collection->count() == 0) {
$table_tpl = 'table-note.html.twig';
}
UI::view('user/' . $table_tpl, [
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $domain_list_data, 'domain_list'),
'actions_links' => $actions_links,
'entity_info' => lng('domains.description'),
// alert-box
'type' => 'warning',
'alert_msg' => lng('domains.nodomainsassignedbyadmin')
'entity_info' => lng('domains.description')
]);
} elseif ($action == 'delete' && $id != 0) {
try {
@@ -142,7 +130,6 @@ if ($page == 'overview' || $page == 'domains') {
AND `parentdomainid` = '0'
AND `email_only` = '0'
AND `caneditdomain` = '1'
AND `deactivated` = '0'
ORDER BY `domain` ASC");
Database::pexecute($stmt, [
"customerid" => $userinfo['customerid']
@@ -152,14 +139,6 @@ if ($page == 'overview' || $page == 'domains') {
$domains[$row['domain']] = $idna_convert->decode($row['domain']);
}
// check of there are any domains to be used
if (count($domains) <= 0) {
// no, possible direct URL access, redirect to overview
Response::redirectTo($filename, [
'page' => $page
]);
}
$aliasdomains[0] = lng('domains.noaliasdomain');
$domains_stmt = Database::prepare("SELECT `d`.`id`, `d`.`domain` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c`
WHERE `d`.`aliasdomain` IS NULL
@@ -244,7 +223,7 @@ if ($page == 'overview' || $page == 'domains') {
if (isset($result['customerid']) && $result['customerid'] == $userinfo['customerid']) {
if ((int)$result['caneditdomain'] == 0) {
if ((int) $result['caneditdomain'] == 0) {
Response::standardError('domaincannotbeedited', $result['domain']);
}
@@ -394,23 +373,6 @@ if ($page == 'overview' || $page == 'domains') {
} else {
Response::standardError('domains_canteditdomain');
}
} elseif ($action == 'jqSpeciallogfileNote') {
$domainid = intval($_POST['id']);
$newval = intval($_POST['newval']);
try {
$json_result = SubDomains::getLocal($userinfo, [
'id' => $domainid
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
if ($newval != $result['speciallogfile']) {
echo json_encode(['changed' => true, 'info' => lng('admin.speciallogwarning')]);
exit();
}
echo 0;
exit();
}
} elseif ($page == 'domainssleditor') {
require_once __DIR__ . '/ssl_editor.php';

View File

@@ -27,10 +27,9 @@ const AREA = 'customer';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\EmailAccounts;
use Froxlor\Api\Commands\EmailDomains;
use Froxlor\Api\Commands\EmailForwarders;
use Froxlor\Api\Commands\Emails;
use Froxlor\CurrentUser;
use Froxlor\Api\Commands\EmailDomains;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
use Froxlor\PhpHelper;
@@ -42,6 +41,7 @@ use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\Validate\Check;
use Froxlor\CurrentUser;
// redirect if this customer page is hidden via settings
if (Settings::IsInList('panel.customer_hide_options', 'email') || $userinfo['emails'] == 0) {
@@ -67,24 +67,14 @@ if ($page == 'overview' || $page == 'emails') {
Response::dynamicError($e->getMessage());
}
$actions_links = [];
if (CurrentUser::canAddResource('emails')) {
$actions_links[] = [
'href' => $linker->getLink(['section' => 'email', 'page' => 'email_domain', 'action' => 'add']),
'label' => lng('emails.emails_add')
];
}
$actions_links[] = [
'href' => \Froxlor\Froxlor::DOCS_URL . 'user-guide/emails/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $emaildomain_list_data, 'emaildomain_list'),
'actions_links' => $actions_links,
'actions_links' => CurrentUser::canAddResource('emails') ? [
[
'href' => $linker->getLink(['section' => 'email', 'page' => 'email_domain', 'action' => 'add']),
'label' => lng('emails.emails_add')
]
] : null,
]);
} else {
// only emails for one domain -> show email address listing directly
@@ -94,7 +84,7 @@ if ($page == 'overview' || $page == 'emails') {
if ($page == 'email_domain') {
$email_domainid = Request::any('domainid', 0);
if ($action == '') {
$log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "viewed customer_email::emails");
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_email::emails");
$sql_search = [];
if ($email_domainid > 0) {
@@ -137,12 +127,6 @@ if ($page == 'email_domain') {
'label' => lng('emails.emails_add')
];
}
$actions_links[] = [
'href' => \Froxlor\Froxlor::DOCS_URL . 'user-guide/emails/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $email_list_data, 'email_list'),
@@ -315,6 +299,30 @@ if ($page == 'email_domain') {
'action' => 'edit',
'id' => $id,
]);
} elseif ($action == 'togglegreylist' && $id != 0) {
try {
$json_result = Emails::getLocal($userinfo, [
'id' => $id
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
try {
Emails::getLocal($userinfo, [
'id' => $id,
'disablegreylist' => ($result['disablegreylist'] == '1' ? 0 : 1)
])->updateGreylist();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page,
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id,
]);
}
} elseif ($page == 'accounts') {
$email_domainid = Request::any('domainid', 0);

View File

@@ -26,7 +26,7 @@
const AREA = 'customer';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\DataDump as DataDump;
use Froxlor\Api\Commands\CustomerBackups as CustomerBackups;
use Froxlor\Api\Commands\DirOptions as DirOptions;
use Froxlor\Api\Commands\DirProtections as DirProtections;
use Froxlor\Customer\Customer;
@@ -68,22 +68,14 @@ if ($page == 'overview' || $page == 'htpasswds') {
Response::dynamicError($e->getMessage());
}
$actions_links = [];
$actions_links[] = [
'href' => $linker->getLink(['section' => 'extras', 'page' => 'htpasswds', 'action' => 'add']),
'label' => lng('extras.directoryprotection_add')
];
$actions_links[] = [
'href' => \Froxlor\Froxlor::DOCS_URL . 'user-guide/extras/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $htpasswd_list_data, 'htpasswd_list'),
'actions_links' => $actions_links,
'actions_links' => [
[
'href' => $linker->getLink(['section' => 'extras', 'page' => 'htpasswds', 'action' => 'add']),
'label' => lng('extras.directoryprotection_add')
]
],
'entity_info' => lng('extras.description')
]);
} elseif ($action == 'delete' && $id != 0) {
@@ -193,22 +185,14 @@ if ($page == 'overview' || $page == 'htpasswds') {
Response::dynamicError($e->getMessage());
}
$actions_links = [];
$actions_links[] = [
'href' => $linker->getLink(['section' => 'extras', 'page' => 'htaccess', 'action' => 'add']),
'label' => lng('extras.pathoptions_add')
];
$actions_links[] = [
'href' => \Froxlor\Froxlor::DOCS_URL . 'user-guide/extras/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $htaccess_list_data, 'htaccess_list'),
'actions_links' => $actions_links,
'actions_links' => [
[
'href' => $linker->getLink(['section' => 'extras', 'page' => 'htaccess', 'action' => 'add']),
'label' => lng('extras.pathoptions_add')
]
],
'entity_info' => lng('extras.description')
]);
} elseif ($action == 'delete' && $id != 0) {
@@ -298,18 +282,18 @@ if ($page == 'overview' || $page == 'htpasswds') {
}
}
}
} elseif ($page == 'export') {
} elseif ($page == 'backup') {
// redirect if this customer sub-page is hidden via settings
if (Settings::IsInList('panel.customer_hide_options', 'extras.export')) {
if (Settings::IsInList('panel.customer_hide_options', 'extras.backup')) {
Response::redirectTo('customer_index.php');
}
if (Settings::Get('system.exportenabled') == 1) {
if (Settings::Get('system.backupenabled') == 1) {
if ($action == 'abort') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "customer_extras::export - aborted scheduled data export job");
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "customer_extras::backup - aborted scheduled backupjob");
try {
DataDump::getLocal($userinfo, $_POST)->delete();
CustomerBackups::getLocal($userinfo, $_POST)->delete();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -318,53 +302,43 @@ if ($page == 'overview' || $page == 'htpasswds') {
'action' => ''
]);
} else {
HTML::askYesNo('extras_reallydelete_export', $filename, [
'job_entry' => $id,
HTML::askYesNo('extras_reallydelete_backup', $filename, [
'backup_job_entry' => $id,
'section' => 'extras',
'page' => $page,
'action' => $action
]);
}
} elseif ($action == '') {
$log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "viewed customer_extras::export");
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_extras::backup");
// check whether there is a backup-job for this customer
try {
$export_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.export.php';
$collection = (new Collection(DataDump::class, $userinfo));
$backup_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.backups.php';
$collection = (new Collection(CustomerBackups::class, $userinfo));
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
if (isset($_POST['send']) && $_POST['send'] == 'send') {
try {
DataDump::getLocal($userinfo, $_POST)->add();
CustomerBackups::getLocal($userinfo, $_POST)->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::standardSuccess('exportscheduled');
Response::standardSuccess('backupscheduled');
} else {
$pathSelect = FileDir::makePathfield($userinfo['documentroot'], $userinfo['guid'], $userinfo['guid']);
$export_data = include_once dirname(__FILE__) . '/lib/formfields/customer/extras/formfield.export.php';
$actions_links = [
[
'href' => \Froxlor\Froxlor::DOCS_URL . 'user-guide/extras/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
]
];
$backup_data = include_once dirname(__FILE__) . '/lib/formfields/customer/extras/formfield.backup.php';
UI::view('user/form-datatable.html.twig', [
'formaction' => $linker->getLink(['section' => 'extras']),
'formdata' => $export_data['export'],
'actions_links' => $actions_links,
'tabledata' => Listing::format($collection, $export_list_data, 'export_list'),
'formdata' => $backup_data['backup'],
'tabledata' => Listing::format($collection, $backup_list_data, 'backup_list'),
]);
}
}
} else {
Response::standardError('exportfunctionnotenabled');
Response::standardError('backupfunctionnotenabled');
}
}

View File

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

View File

@@ -27,21 +27,21 @@ const AREA = 'customer';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\Customers as Customers;
use Froxlor\Cron\TaskId;
use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\Froxlor;
use Froxlor\FroxlorLogger;
use Froxlor\Language;
use Froxlor\Settings;
use Froxlor\System\Cronjob;
use Froxlor\System\Crypt;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Response;
use Froxlor\Validate\Validate;
use Froxlor\Language;
use Froxlor\System\Cronjob;
use Froxlor\Cron\TaskId;
if ($action == 'logout') {
$log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, 'logged out');
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, 'logged out');
unset($_SESSION['userinfo']);
CurrentUser::setData();
@@ -66,7 +66,7 @@ if ($action == 'logout') {
}
if ($page == 'overview') {
$log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "viewed customer_index");
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_index");
$domain_stmt = Database::prepare("SELECT `domain` FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE `customerid` = :customerid
@@ -114,20 +114,15 @@ if ($page == 'overview') {
$userinfo['traffic_bytes'] = ($userinfo['traffic'] > -1) ? $userinfo['traffic'] * 1024 : -1;
$userinfo['traffic_bytes_used'] = $userinfo['traffic_used'] * 1024;
if (Settings::Get('system.mail_quota_enabled')) {
$userinfo['email_quota_bytes'] = ($userinfo['email_quota'] > -1) ? $userinfo['email_quota'] * 1024 * 1024 : -1;
$userinfo['email_quota_bytes_used'] = $userinfo['email_quota_used'] * 1024 * 1024;
}
if ($usages) {
$userinfo['diskspace_bytes_used'] = $usages['webspace'] * 1024;
$userinfo['mailspace_used'] = $usages['mail'] * 1024;
$userinfo['mailspace_used'] = $usages['mail'] * 1024;
$userinfo['dbspace_used'] = $usages['mysql'] * 1024;
$userinfo['total_bytes_used'] = ($usages['webspace'] + $usages['mail'] + $usages['mysql']) * 1024;
} else {
$userinfo['diskspace_bytes_used'] = 0;
$userinfo['total_bytes_used'] = 0;
$userinfo['mailspace_used'] = 0;
$userinfo['mailspace_used'] = 0;
$userinfo['dbspace_used'] = 0;
}
@@ -136,138 +131,141 @@ if ($page == 'overview') {
'domains' => $domainArray,
'stdsubdomain' => $stdsubdomain
]);
} elseif ($page == 'profile') {
$languages = Language::getLanguages();
} elseif ($page == 'change_password') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
$old_password = Validate::validate($_POST['old_password'], 'old password');
if (!empty($_POST)) {
if ($_POST['send'] == 'changepassword') {
$old_password = Validate::validate($_POST['old_password'], 'old password');
if (!Crypt::validatePasswordLogin($userinfo, $old_password, TABLE_PANEL_CUSTOMERS, 'customerid')) {
Response::standardError('oldpasswordnotcorrect');
}
if (!Crypt::validatePasswordLogin($userinfo, $old_password, TABLE_PANEL_CUSTOMERS, 'customerid')) {
Response::standardError('oldpasswordnotcorrect');
}
try {
$new_password = Crypt::validatePassword($_POST['new_password'], 'new password');
$new_password_confirm = Crypt::validatePassword($_POST['new_password_confirm'], 'new password confirm');
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
if ($old_password == '') {
Response::standardError([
'stringisempty',
'changepassword.old_password'
]);
} elseif ($new_password == '') {
Response::standardError([
'stringisempty',
'changepassword.new_password'
]);
} elseif ($new_password_confirm == '') {
Response::standardError([
'stringisempty',
'changepassword.new_password_confirm'
]);
} elseif ($new_password != $new_password_confirm) {
Response::standardError('newpasswordconfirmerror');
} else {
// Update user password
try {
$new_password = Crypt::validatePassword($_POST['new_password'], 'new password');
$new_password_confirm = Crypt::validatePassword($_POST['new_password_confirm'], 'new password confirm');
Customers::getLocal($userinfo, [
'id' => $userinfo['customerid'],
'new_customer_password' => $new_password
])->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, 'changed password');
if ($old_password == '') {
Response::standardError([
'stringisempty',
'changepassword.old_password'
]);
} elseif ($new_password == '') {
Response::standardError([
'stringisempty',
'changepassword.new_password'
]);
} elseif ($new_password_confirm == '') {
Response::standardError([
'stringisempty',
'changepassword.new_password_confirm'
]);
} elseif ($new_password != $new_password_confirm) {
Response::standardError('newpasswordconfirmerror');
} else {
// Update user password
try {
Customers::getLocal($userinfo, [
'id' => $userinfo['customerid'],
'new_customer_password' => $new_password
])->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, 'changed password');
// Update ftp password
if (isset($_POST['change_main_ftp']) && $_POST['change_main_ftp'] == 'true') {
$cryptPassword = Crypt::makeCryptPassword($new_password);
$stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "`
// Update ftp password
if (isset($_POST['change_main_ftp']) && $_POST['change_main_ftp'] == 'true') {
$cryptPassword = Crypt::makeCryptPassword($new_password);
$stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "`
SET `password` = :password
WHERE `customerid` = :customerid
AND `username` = :username");
$params = [
"password" => $cryptPassword,
"customerid" => $userinfo['customerid'],
"username" => $userinfo['loginname']
];
Database::pexecute($stmt, $params);
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, 'changed main ftp password');
}
$params = [
"password" => $cryptPassword,
"customerid" => $userinfo['customerid'],
"username" => $userinfo['loginname']
];
Database::pexecute($stmt, $params);
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, 'changed main ftp password');
}
// Update statistics password
if (isset($_POST['change_stats']) && $_POST['change_stats'] == 'true') {
$new_stats_password = Crypt::makeCryptPassword($new_password, true);
// Update statistics password
if (isset($_POST['change_stats']) && $_POST['change_stats'] == 'true') {
$new_stats_password = Crypt::makeCryptPassword($new_password, true);
$stmt = Database::prepare("UPDATE `" . TABLE_PANEL_HTPASSWDS . "`
$stmt = Database::prepare("UPDATE `" . TABLE_PANEL_HTPASSWDS . "`
SET `password` = :password
WHERE `customerid` = :customerid
AND `username` = :username");
$params = [
"password" => $new_stats_password,
"customerid" => $userinfo['customerid'],
"username" => $userinfo['loginname']
];
Database::pexecute($stmt, $params);
Cronjob::inserttask(TaskId::REBUILD_VHOST);
}
$params = [
"password" => $new_stats_password,
"customerid" => $userinfo['customerid'],
"username" => $userinfo['loginname']
];
Database::pexecute($stmt, $params);
Cronjob::inserttask(TaskId::REBUILD_VHOST);
}
Response::redirectTo($filename);
}
} elseif ($_POST['send'] == 'changetheme') {
if (Settings::Get('panel.allow_theme_change_customer') == 1) {
$theme = Validate::validate($_POST['theme'], 'theme');
try {
Customers::getLocal($userinfo, [
'id' => $userinfo['customerid'],
'theme' => $theme
])->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "changed default theme to '" . $theme . "'");
}
Response::redirectTo($filename);
} elseif ($_POST['send'] == 'changelanguage') {
$def_language = Validate::validate($_POST['def_language'], 'default language');
if (isset($languages[$def_language])) {
try {
Customers::getLocal($userinfo, [
'id' => $userinfo['customerid'],
'def_language' => $def_language
])->update();
CurrentUser::setField('language', $def_language);
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
}
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "changed default language to '" . $def_language . "'");
Response::redirectTo($filename);
}
} else {
// change theme
$default_theme = Settings::Get('panel.default_theme');
if ($userinfo['theme'] != '') {
$default_theme = $userinfo['theme'];
UI::view('user/change_password.html.twig');
}
} elseif ($page == 'change_language') {
$languages = Language::getLanguages();
if (isset($_POST['send']) && $_POST['send'] == 'send') {
$def_language = Validate::validate($_POST['def_language'], 'default language');
if (isset($languages[$def_language])) {
try {
Customers::getLocal($userinfo, [
'id' => $userinfo['customerid'],
'def_language' => $def_language
])->update();
CurrentUser::setField('language', $def_language);
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
}
$themes_avail = UI::getThemes();
// change language
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "changed default language to '" . $def_language . "'");
Response::redirectTo($filename);
} else {
$default_lang = Settings::Get('panel.standardlanguage');
if ($userinfo['def_language'] != '') {
$default_lang = $userinfo['def_language'];
}
UI::view('user/profile.html.twig', [
'themes' => $themes_avail,
'default_theme' => $default_theme,
UI::view('user/change_language.html.twig', [
'languages' => $languages,
'default_lang' => $default_lang,
'default_lang' => $default_lang
]);
}
} elseif ($page == 'change_theme') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
$theme = Validate::validate($_POST['theme'], 'theme');
try {
Customers::getLocal($userinfo, [
'id' => $userinfo['customerid'],
'theme' => $theme
])->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "changed default theme to '" . $theme . "'");
Response::redirectTo($filename);
} else {
$default_theme = Settings::Get('panel.default_theme');
if ($userinfo['theme'] != '') {
$default_theme = $userinfo['theme'];
}
$themes_avail = UI::getThemes();
UI::view('user/change_theme.html.twig', [
'themes' => $themes_avail,
'default_theme' => $default_theme
]);
}
} elseif ($page == 'send_error_report' && Settings::Get('system.allow_error_report_customer') == '1') {

View File

@@ -28,7 +28,6 @@ require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\Mysqls;
use Froxlor\Api\Commands\MysqlServer;
use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
@@ -38,6 +37,7 @@ use Froxlor\UI\Listing;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\CurrentUser;
// redirect if this customer page is hidden via settings or no resources given
if (Settings::IsInList('panel.customer_hide_options', 'mysql') || $userinfo['mysqls'] == 0) {
@@ -66,21 +66,16 @@ if ($page == 'overview' || $page == 'mysqls') {
Response::dynamicError($e->getMessage());
}
$actions_links = [];
$actions_links = false;
if (CurrentUser::canAddResource('mysqls')) {
$actions_links[] = [
'href' => $linker->getLink(['section' => 'mysql', 'page' => 'mysqls', 'action' => 'add']),
'label' => lng('mysql.database_create')
$actions_links = [
[
'href' => $linker->getLink(['section' => 'mysql', 'page' => 'mysqls', 'action' => 'add']),
'label' => lng('mysql.database_create')
]
];
}
$actions_links[] = [
'href' => \Froxlor\Froxlor::DOCS_URL . 'user-guide/databases/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $mysql_list_data, 'mysql_list'),
'actions_links' => $actions_links,
@@ -184,7 +179,7 @@ if ($page == 'overview' || $page == 'mysqls') {
$result_json = MysqlServer::getLocal($userinfo)->listing();
$result_decoded = json_decode($result_json, true)['data']['list'];
foreach ($result_decoded as $dbserver => $dbdata) {
$mysql_servers[$dbserver] = $dbdata['caption'] . ' (' . $dbdata['host'] . (isset($dbdata['port']) && !empty($dbdata['port']) ? ':' . $dbdata['port'] : '') . ')';
$mysql_servers[$dbserver] = $dbdata['caption'] . ' (' . $dbdata['host'] . (isset($dbdata['port']) && !empty($dbdata['port']) ? ':' . $dbdata['port'] : '').')';
}
} catch (Exception $e) {
/* just none */

View File

@@ -26,7 +26,6 @@
const AREA = 'login';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\FroxlorRPC;
use Froxlor\CurrentUser;
use Froxlor\Customer\Customer;
use Froxlor\Database\Database;
@@ -38,7 +37,6 @@ use Froxlor\PhpHelper;
use Froxlor\Settings;
use Froxlor\System\Crypt;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\User;
use Froxlor\Validate\Validate;
@@ -74,26 +72,27 @@ if ($action == '2fa_entercode') {
$code = isset($_POST['2fa_code']) ? $_POST['2fa_code'] : null;
// verify entered code
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
$result = ($_SESSION['secret_2fa'] == 'email' ? true : $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3));
// get user-data
$table = $_SESSION['uidtable_2fa'];
$field = $_SESSION['uidfield_2fa'];
$uid = $_SESSION['uid_2fa'];
$isadmin = $_SESSION['unfo_2fa'];
if ($_SESSION['secret_2fa'] == 'email') {
// verify code set to user's data_2fa field
$sel_stmt = Database::prepare("SELECT `data_2fa` FROM " . $table . " WHERE `" . $field . "` = :uid");
$userinfo_code = Database::pexecute_first($sel_stmt, ['uid' => $uid]);
$result = $tfa->verifyCode($userinfo_code['data_2fa'], $code);
} else {
$result = $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3);
}
// either the code is valid when using authenticator-app, or we will select userdata by id and entered code
// which is temporarily stored for the customer when using email-2fa
if ($result) {
$sel_param = [
'uid' => $uid
];
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid");
if ($_SESSION['secret_2fa'] == 'email') {
// verify code by selecting user by id and the temp. stored code,
// so only if it's the correct code, we get the user-data
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid AND `data_2fa` = :code");
$sel_param['code'] = $code;
} else {
// Authenticator-verification has already happened at this point, so just get the user-data
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid");
}
$userinfo = Database::pexecute_first($sel_stmt, $sel_param);
// whoops, no (valid) user? Start again
if (empty($userinfo)) {
@@ -326,12 +325,11 @@ if ($action == '2fa_entercode') {
if ($userinfo['type_2fa'] == 1) {
// generate code
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
$secret = $tfa->createSecret();
$code = $tfa->getCode($secret);
$code = $tfa->getCode($tfa->createSecret());
// set code for user
$stmt = Database::prepare("UPDATE $table SET `data_2fa` = :d2fa WHERE `$uid` = :uid");
Database::pexecute($stmt, [
"d2fa" => $secret,
"d2fa" => $code,
"uid" => $userinfo[$uid]
]);
// build up & send email
@@ -736,58 +734,6 @@ if ($action == 'resetpwd') {
}
}
// one-time link login
if ($action == 'll') {
if (!Froxlor::hasUpdates() && !Froxlor::hasDbUpdates()) {
$loginname = Request::get('ln');
$hash = Request::get('h');
if ($loginname && $hash) {
$sel_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_LOGINLINKS . "`
WHERE `loginname` = :loginname AND `hash` = :hash
");
try {
$entry = Database::pexecute_first($sel_stmt, ['loginname' => $loginname, 'hash' => $hash]);
} catch (Exception $e) {
$entry = false;
}
if ($entry) {
// delete entry
$del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_LOGINLINKS . "` WHERE `loginname` = :loginname AND `hash` = :hash");
Database::pexecute($del_stmt, ['loginname' => $loginname, 'hash' => $hash]);
if (time() <= $entry['valid_until']) {
$valid = true;
// validate source ip if specified
if (!empty($entry['allowed_from'])) {
$valid = false;
$ip_list = explode(",", $entry['allowed_from']);
if (FroxlorRPC::validateAllowedFrom($ip_list, $_SERVER['REMOTE_ADDR'])) {
$valid = true;
}
}
if ($valid) {
// login user / select only non-deactivated (in case the user got deactivated after generating the link)
$userinfo_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `loginname`= :loginname AND `deactivated` = 0");
try {
$userinfo = Database::pexecute_first($userinfo_stmt, [
"loginname" => $loginname
]);
} catch (Exception $e) {
$userinfo = false;
}
if ($userinfo) {
$userinfo['userid'] = $userinfo['customerid'];
$userinfo['adminsession'] = 0;
finishLogin($userinfo);
}
}
}
}
}
}
Response::redirectTo('index.php');
}
function finishLogin($userinfo)
{
if (isset($userinfo['userid']) && $userinfo['userid'] != '') {

View File

@@ -157,7 +157,7 @@ CREATE TABLE `panel_admins` (
`api_allowed` tinyint(1) NOT NULL default '1',
PRIMARY KEY (`adminid`),
UNIQUE KEY `loginname` (`loginname`)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC;
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
DROP TABLE IF EXISTS `panel_customers`;
@@ -278,6 +278,7 @@ CREATE TABLE `panel_domains` (
`phpsettingid` INT( 11 ) UNSIGNED NOT NULL DEFAULT '1',
`mod_fcgid_starter` int(4) default '-1',
`mod_fcgid_maxrequests` int(4) default '-1',
`ismainbutsubto` int(11) unsigned NOT NULL default '0',
`letsencrypt` tinyint(1) NOT NULL default '0',
`hsts` varchar(10) NOT NULL default '0',
`hsts_sub` tinyint(1) NOT NULL default '0',
@@ -299,7 +300,7 @@ CREATE TABLE `panel_domains` (
KEY `customerid` (`customerid`),
KEY `parentdomain` (`parentdomainid`),
KEY `domain` (`domain`)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC;
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
DROP TABLE IF EXISTS `panel_ipsandports`;
@@ -356,6 +357,23 @@ CREATE TABLE `panel_htpasswds` (
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
DROP TABLE IF EXISTS `panel_sessions`;
CREATE TABLE `panel_sessions` (
`hash` varchar(32) NOT NULL default '',
`userid` int(11) unsigned NOT NULL default '0',
`ipaddress` varchar(255) NOT NULL default '',
`useragent` varchar(255) NOT NULL default '',
`lastactivity` int(11) unsigned NOT NULL default '0',
`lastpaging` varchar(255) NOT NULL default '',
`formtoken` char(32) NOT NULL default '',
`language` varchar(64) NOT NULL default '',
`adminsession` tinyint(1) unsigned NOT NULL default '0',
`theme` varchar(255) NOT NULL default '',
PRIMARY KEY (`hash`),
KEY `userid` (`userid`)
) ENGINE=HEAP;
DROP TABLE IF EXISTS `panel_settings`;
CREATE TABLE `panel_settings` (
`settingid` int(11) unsigned NOT NULL auto_increment,
@@ -391,7 +409,7 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES
('admin', 'show_version_footer', '0'),
('caa', 'caa_entry', ''),
('spf', 'use_spf', '0'),
('spf', 'spf_entry', 'v=spf1 a mx -all'),
('spf', 'spf_entry', '"v=spf1 a mx -all"'),
('dkim', 'dkim_algorithm', 'all'),
('dkim', 'dkim_keylength', '1024'),
('dkim', 'dkim_servicetype', '0'),
@@ -537,7 +555,7 @@ opcache.validate_timestamps'),
('system', 'defaultip', '1'),
('system', 'defaultsslip', ''),
('system', 'phpappendopenbasedir', '/tmp/'),
('system', 'deactivateddocroot', '/var/www/html/froxlor/templates/misc/deactivated/'),
('system', 'deactivateddocroot', ''),
('system', 'mailpwcleartext', '0'),
('system', 'last_tasks_run', '000000'),
('system', 'nameservers', ''),
@@ -545,7 +563,7 @@ opcache.validate_timestamps'),
('system', 'mod_fcgid', '0'),
('system', 'apacheconf_vhost', '/etc/apache2/sites-enabled/'),
('system', 'apacheconf_diroptions', '/etc/apache2/sites-enabled/'),
('system', 'apacheconf_htpasswddir', '/etc/apache2/froxlor-htpasswd/'),
('system', 'apacheconf_htpasswddir', '/etc/apache2/htpasswd/'),
('system', 'webalizer_quiet', '2'),
('system', 'last_archive_run', '000000'),
('system', 'mod_fcgid_configdir', '/var/www/php-fcgi-scripts'),
@@ -562,6 +580,7 @@ opcache.validate_timestamps'),
('system', 'mod_fcgid_wrapper', '1'),
('system', 'mod_fcgid_starter', '0'),
('system', 'mod_fcgid_peardir', '/usr/share/php/:/usr/share/php5/'),
('system', 'index_file_extension', 'html'),
('system', 'mod_fcgid_maxrequests', '250'),
('system', 'ssl_key_file','/etc/ssl/froxlor_selfsigned.key'),
('system', 'ssl_ca_file', ''),
@@ -628,7 +647,7 @@ opcache.validate_timestamps'),
('system', 'letsencryptreuseold', 0),
('system', 'leenabled', '0'),
('system', 'leapiversion', '2'),
('system', 'exportenabled', '0'),
('system', 'backupenabled', '0'),
('system', 'dnsenabled', '0'),
('system', 'dns_server', 'Bind'),
('system', 'apacheglobaldiropt', ''),
@@ -678,7 +697,7 @@ opcache.validate_timestamps'),
('system', 'distribution', ''),
('system', 'update_channel', 'stable'),
('system', 'updatecheck_data', ''),
('system', 'update_notify_last', ''),
('system', 'update_notify_last', '2.0.24'),
('system', 'traffictool', 'goaccess'),
('system', 'req_limit_per_interval', 60),
('system', 'req_limit_interval', 60),
@@ -725,9 +744,8 @@ opcache.validate_timestamps'),
('panel', 'logo_overridetheme', '0'),
('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'),
('panel', 'menu_collapsed', '1'),
('panel', 'version', '2.1.2'),
('panel', 'db_version', '202312120');
('panel', 'version', '2.0.24'),
('panel', 'db_version', '202304260');
DROP TABLE IF EXISTS `panel_tasks`;
@@ -750,7 +768,6 @@ CREATE TABLE `panel_templates` (
`templategroup` varchar(255) NOT NULL default '',
`varname` varchar(255) NOT NULL default '',
`value` longtext NOT NULL,
`file_extension` varchar(50) NOT NULL default 'html',
PRIMARY KEY (id),
KEY adminid (adminid)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
@@ -897,7 +914,7 @@ INSERT INTO `cronjobs_run` (`id`, `module`, `cronfile`, `cronclass`, `interval`,
(3, 'froxlor/reports', 'usage_report', '\\Froxlor\\Cron\\Traffic\\ReportsCron', '1 DAY', '1', 'cron_usage_report'),
(4, 'froxlor/core', 'mailboxsize', '\\Froxlor\\Cron\\System\\MailboxsizeCron', '6 HOUR', '1', 'cron_mailboxsize'),
(5, 'froxlor/letsencrypt', 'letsencrypt', '\\Froxlor\\Cron\\Http\\LetsEncrypt\\AcmeSh', '5 MINUTE', '0', 'cron_letsencrypt'),
(6, 'froxlor/export', 'export', '\\Froxlor\\Cron\\System\\ExportCron', '1 HOUR', '0', 'cron_export');
(6, 'froxlor/backup', 'backup', '\\Froxlor\\Cron\\System\\BackupCron', '1 DAY', '0', 'cron_backup');
DROP TABLE IF EXISTS `ftp_quotalimits`;
@@ -1035,14 +1052,4 @@ CREATE TABLE `panel_usercolumns` (
KEY adminid (adminid),
KEY customerid (customerid)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
DROP TABLE IF EXISTS `panel_loginlinks`;
CREATE TABLE `panel_loginlinks` (
`hash` varchar(500) NOT NULL,
`loginname` varchar(50) NOT NULL,
`valid_until` int(15) NOT NULL,
`allowed_from` text NOT NULL,
UNIQUE KEY `loginname` (`loginname`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
FROXLORSQL;

View File

@@ -23,11 +23,11 @@
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\Install\Update;
use Froxlor\FileDir;
use Froxlor\Database\Database;
use Froxlor\Settings;
use Froxlor\Install\Update;
use Froxlor\System\Cronjob;
use Froxlor\System\IPTools;

View File

@@ -1,271 +0,0 @@
<?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
*/
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\Install\Update;
use Froxlor\Settings;
if (!defined('_CRON_UPDATE')) {
if (!defined('AREA') || (defined('AREA') && AREA != 'admin') || !isset($userinfo['loginname']) || (isset($userinfo['loginname']) && $userinfo['loginname'] == '')) {
header('Location: ../../../../index.php');
exit();
}
}
if (Froxlor::isFroxlorVersion('2.0.24')) {
Update::showUpdateStep("Cleaning domains table");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` ROW_FORMAT=DYNAMIC;");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` DROP COLUMN `ismainbutsubto`;");
Update::lastStepStatus(0);
Update::showUpdateStep("Creating new tables and fields");
Database::query("DROP TABLE IF EXISTS `panel_loginlinks`;");
$sql = "CREATE TABLE `panel_loginlinks` (
`hash` varchar(500) NOT NULL,
`loginname` varchar(50) NOT NULL,
`valid_until` int(15) NOT NULL,
`allowed_from` text NOT NULL,
UNIQUE KEY `loginname` (`loginname`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
Database::query($sql);
Update::lastStepStatus(0);
Update::showUpdateStep("Adding new settings");
Settings::AddNew('panel.menu_collapsed', 1);
Update::lastStepStatus(0);
Update::showUpdateStep("Adjusting setting for deactivated webroot");
$current_deactivated_webroot = Settings::Get('system.deactivateddocroot');
if (empty($current_deactivated_webroot)) {
Settings::Set('system.deactivateddocroot', FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/templates/misc/deactivated/'));
Update::lastStepStatus(0);
} else {
Update::lastStepStatus(1, 'Customized setting, not changing');
}
Update::showUpdateStep("Adjusting cronjobs");
$cfupd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET
`module`= 'froxlor/export',
`cronfile` = 'export',
`cronclass` = :cc,
`interval` = '1 HOUR',
`desc_lng_key` = 'cron_export'
WHERE `module` = 'froxlor/backup'
");
Database::pexecute($cfupd_stmt, [
'cc' => '\\Froxlor\\Cron\\System\\ExportCron'
]);
Update::lastStepStatus(0);
Update::showUpdateStep("Adjusting system for data-export function");
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "`SET `varname` = 'exportenabled' WHERE `settinggroup`= 'system' AND `varname`= 'backupenabled'");
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "`SET `value` = REPLACE(`value`, 'extras.backup', 'extras.export') WHERE `settinggroup` = 'panel' AND `varname` = 'customer_hide_options'");
Database::query("DELETE FROM `" . TABLE_PANEL_USERCOLUMNS . "` WHERE `section` = 'backup_list'");
Database::query("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202305240');
Froxlor::updateToVersion('2.1.0-dev1');
}
if (Froxlor::isFroxlorVersion('2.1.0-dev1')) {
Update::showUpdateStep("Updating from 2.1.0-dev1 to 2.1.0-beta1", false);
Froxlor::updateToVersion('2.1.0-beta1');
}
if (Froxlor::isFroxlorVersion('2.1.0-beta1')) {
Update::showUpdateStep("Updating from 2.1.0-beta1 to 2.1.0-beta2", false);
Update::showUpdateStep("Removing unused table");
Database::query("DROP TABLE IF EXISTS `panel_sessions`;");
Update::lastStepStatus(0);
Froxlor::updateToVersion('2.1.0-beta2');
}
if (Froxlor::isFroxlorVersion('2.1.0-beta2')) {
Update::showUpdateStep("Updating from 2.1.0-beta2 to 2.1.0-rc1", false);
Froxlor::updateToVersion('2.1.0-rc1');
}
if (Froxlor::isFroxlorVersion('2.1.0-rc1')) {
Update::showUpdateStep("Updating from 2.1.0-rc1 to 2.1.0-rc2", false);
Update::showUpdateStep("Adjusting setting spf_entry");
$spf_entry = Settings::Get('spf.spf_entry');
if (!preg_match('/^v=spf[a-z0-9:~?\s.-]+$/i', $spf_entry)) {
Settings::Set('spf.spf_entry', 'v=spf1 a mx -all');
Update::lastStepStatus(1, 'corrected');
} else {
Update::lastStepStatus(0);
}
Froxlor::updateToVersion('2.1.0-rc2');
}
if (Froxlor::isDatabaseVersion('202305240')) {
Update::showUpdateStep("Adjusting file-template file extension setttings");
$current_fileextension = Settings::Get('system.index_file_extension');
Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup`= 'system' AND `varname`= 'index_file_extension'");
Database::query("ALTER TABLE `" . TABLE_PANEL_TEMPLATES . "` ADD `file_extension` varchar(50) NOT NULL default 'html';");
if (!empty(trim($current_fileextension)) && strtolower(trim($current_fileextension)) != 'html') {
$stmt = Database::prepare("UPDATE `" . TABLE_PANEL_TEMPLATES . "` SET `file_extension` = :ext WHERE `templategroup` = 'files'");
Database::pexecute($stmt, ['ext' => strtolower(trim($current_fileextension))]);
}
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202311260');
}
if (Froxlor::isFroxlorVersion('2.1.0-rc2')) {
Update::showUpdateStep("Updating from 2.1.0-rc2 to 2.1.0-rc3", false);
Froxlor::updateToVersion('2.1.0-rc3');
}
if (Froxlor::isDatabaseVersion('202311260')) {
Update::showUpdateStep("Cleaning up old files");
$to_clean = array(
"install/updates/froxlor/update_2.x.inc.php",
"install/updates/preconfig/preconfig_2.x.inc.php",
"lib/Froxlor/Api/Commands/CustomerBackups.php",
"lib/Froxlor/Cli/Action",
"lib/Froxlor/Cli/Action.php",
"lib/Froxlor/Cli/CmdLineHandler.php",
"lib/Froxlor/Cli/ConfigServicesCmd.php",
"lib/Froxlor/Cli/PhpSessioncleanCmd.php",
"lib/Froxlor/Cli/SwitchServerIpCmd.php",
"lib/Froxlor/Cli/UpdateCliCmd.php",
"lib/Froxlor/Cron/System/BackupCron.php",
"lib/formfields/customer/extras/formfield.backup.php",
"lib/tablelisting/customer/tablelisting.backups.php",
"templates/Froxlor/assets/mix-manifest.json",
"templates/Froxlor/assets/css",
"templates/Froxlor/assets/webfonts",
"templates/Froxlor/assets/js/main.js",
"templates/Froxlor/assets/js/main.js.LICENSE.txt",
"templates/Froxlor/src",
"templates/Froxlor/user/change_language.html.twig",
"templates/Froxlor/user/change_password.html.twig",
"templates/Froxlor/user/change_theme.html.twig",
"tests/Backup/CustomerBackupsTest.php"
);
$disabled = explode(',', ini_get('disable_functions'));
$exec_allowed = !in_array('exec', $disabled);
$del_list = "";
foreach ($to_clean as $filedir) {
$complete_filedir = Froxlor::getInstallDir() . $filedir;
if (file_exists($complete_filedir)) {
if ($exec_allowed) {
FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir));
} else {
$del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL;
}
}
}
if ($exec_allowed) {
Update::lastStepStatus(0);
} else {
if (empty($del_list)) {
// none of the files existed
Update::lastStepStatus(0);
} else {
Update::lastStepStatus(
1,
'manual commands needed',
'Please run the following commands manually:<br><pre>' . $del_list . '</pre>'
);
}
}
Froxlor::updateToDbVersion('202312050');
}
if (Froxlor::isFroxlorVersion('2.1.0-rc3')) {
Update::showUpdateStep("Updating from 2.1.0-rc3 to 2.1.0 stable", false);
Froxlor::updateToVersion('2.1.0');
}
if (Froxlor::isFroxlorVersion('2.1.0')) {
Update::showUpdateStep("Updating from 2.1.0 to 2.1.1", false);
Froxlor::updateToVersion('2.1.1');
}
if (Froxlor::isDatabaseVersion('202312050')) {
Update::showUpdateStep("Cleaning up old files");
$to_clean = array(
"lib/configfiles/centos7.xml",
"lib/configfiles/centos8.xml",
"lib/configfiles/stretch.xml",
"lib/configfiles/xenial.xml",
"lib/configfiles/buster.xml",
"lib/configfiles/bionic.xml",
);
$disabled = explode(',', ini_get('disable_functions'));
$exec_allowed = !in_array('exec', $disabled);
$del_list = "";
foreach ($to_clean as $filedir) {
$complete_filedir = Froxlor::getInstallDir() . $filedir;
if (file_exists($complete_filedir)) {
if ($exec_allowed) {
FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir));
} else {
$del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL;
}
}
}
if ($exec_allowed) {
Update::lastStepStatus(0);
} else {
if (empty($del_list)) {
// none of the files existed
Update::lastStepStatus(0);
} else {
Update::lastStepStatus(
1,
'manual commands needed',
'Please run the following commands manually:<br><pre>' . $del_list . '</pre>'
);
}
}
Froxlor::updateToDbVersion('202312100');
}
if (Froxlor::isDatabaseVersion('202312100')) {
Update::showUpdateStep("Adjusting table row format of larger tables");
Database::query("ALTER TABLE `" . TABLE_PANEL_ADMINS . "` ROW_FORMAT=DYNAMIC;");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` ROW_FORMAT=DYNAMIC;");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202312120');
}
if (Froxlor::isFroxlorVersion('2.1.1')) {
Update::showUpdateStep("Updating from 2.1.1 to 2.1.2", false);
Froxlor::updateToVersion('2.1.2');
}

View File

@@ -1,43 +0,0 @@
<?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
*/
use Froxlor\Froxlor;
use Froxlor\FileDir;
use Froxlor\Config\ConfigParser;
use Froxlor\Install\Update;
use Froxlor\Settings;
$preconfig = [
'title' => '2.1.x updates',
'fields' => []
];
$return = [];
if (Update::versionInUpdate($current_version, '2.1.0-dev1')) {
}
$preconfig['fields'] = $return;
return $preconfig;

View File

@@ -30,7 +30,7 @@ use Froxlor\Install\Update;
use Froxlor\Settings;
$preconfig = [
'title' => '2.0.x updates',
'title' => '2.x updates',
'fields' => []
];
$return = [];

View File

@@ -53,8 +53,7 @@ try {
if (Froxlor::isFroxlor()) {
include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_0.10.inc.php'));
include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_2.0.inc.php'));
include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_2.1.inc.php'));
include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_2.x.inc.php'));
// Check Froxlor - database integrity (only happens after all updates are done, so we know the db-layout is okay)
Update::showUpdateStep("Checking database integrity");

View File

@@ -272,8 +272,7 @@ abstract class ApiCommand extends ApiParameter
$ops = [
'<',
'>',
'=',
'<>'
'='
];
$first = true;
foreach ($search as $field => $valoper) {
@@ -397,7 +396,6 @@ abstract class ApiCommand extends ApiParameter
$nat_fields = [
'`c`.`loginname`',
'`c`.`name`',
'`a`.`loginname`',
'`adminname`',
'`databasename`',

View File

@@ -41,22 +41,20 @@ use PDO;
/**
* @since 0.10.0
*/
class DataDump extends ApiCommand implements ResourceEntity
class CustomerBackups extends ApiCommand implements ResourceEntity
{
/**
* add a new data dump job
* add a new customer backup job
*
* @param string $path
* path to store the dumped data to
* @param string $pgp_public_key
* optional pgp public key to encrypt the archive, default is empty
* @param bool $dump_dbs
* optional whether to include databases, default is 0 (false)
* @param bool $dump_mail
* optional whether to include mail-data, default is 0 (false)
* @param bool $dump_web
* optional whether to incoude web-data, default is 0 (false)
* path to store the backup to
* @param bool $backup_dbs
* optional whether to backup databases, default is 0 (false)
* @param bool $backup_mail
* optional whether to backup mail-data, default is 0 (false)
* @param bool $backup_web
* optional whether to backup web-data, default is 0 (false)
* @param int $customerid
* optional, required when called as admin (if $loginname is not specified)
* @param string $loginname
@@ -74,10 +72,9 @@ class DataDump extends ApiCommand implements ResourceEntity
$path = $this->getParam('path');
// parameter
$pgp_public_key = $this->getParam('pgp_public_key', true, '');
$dump_dbs = $this->getBoolParam('dump_dbs', true, 0);
$dump_mail = $this->getBoolParam('dump_mail', true, 0);
$dump_web = $this->getBoolParam('dump_web', true, 0);
$backup_dbs = $this->getBoolParam('backup_dbs', true, 0);
$backup_mail = $this->getBoolParam('backup_mail', true, 0);
$backup_web = $this->getBoolParam('backup_web', true, 0);
// get customer data
$customer = $this->getCustomerData();
@@ -89,32 +86,19 @@ class DataDump extends ApiCommand implements ResourceEntity
// path cannot be the customers docroot
if ($path == FileDir::makeCorrectDir($customer['documentroot'])) {
Response::standardError('dumpfoldercannotbedocroot', '', true);
Response::standardError('backupfoldercannotbedocroot', '', true);
}
// pgp public key validation
if (!empty($pgp_public_key)) {
// check if gnupg extension is loaded
if (!extension_loaded('gnupg')) {
Response::standardError('gnupgextensionnotavailable', '', true);
}
// check if the pgp public key is a valid key
putenv('GNUPGHOME='.sys_get_temp_dir());
if (gnupg_import(gnupg_init(), $pgp_public_key) === false) {
Response::standardError('invalidpgppublickey', '', true);
}
if ($backup_dbs != '1') {
$backup_dbs = '0';
}
if ($dump_dbs != '1') {
$dump_dbs = '0';
if ($backup_mail != '1') {
$backup_mail = '0';
}
if ($dump_mail != '1') {
$dump_mail = '0';
}
if ($dump_web != '1') {
$dump_web = '0';
if ($backup_web != '1') {
$backup_web = '0';
}
$task_data = [
@@ -123,63 +107,61 @@ class DataDump extends ApiCommand implements ResourceEntity
'gid' => $customer['guid'],
'loginname' => $customer['loginname'],
'destdir' => $path,
'pgp_public_key' => $pgp_public_key,
'dump_dbs' => $dump_dbs,
'dump_mail' => $dump_mail,
'dump_web' => $dump_web
'backup_dbs' => $backup_dbs,
'backup_mail' => $backup_mail,
'backup_web' => $backup_web
];
// schedule backup job
Cronjob::inserttask(TaskId::CREATE_CUSTOMER_BACKUP, $task_data);
// schedule export job
Cronjob::inserttask(TaskId::CREATE_CUSTOMER_DATADUMP, $task_data);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added customer data export job for '" . $customer['loginname'] . "'. Target directory: " . $userpath);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added customer-backup job for '" . $customer['loginname'] . "'. Target directory: " . $userpath);
return $this->response($task_data);
}
/**
* check whether data dump is enabled systemwide and if accessible for customer (hide_options)
* check whether backup is enabled systemwide and if accessible for customer (hide_options)
*
* @throws Exception
*/
private function validateAccess()
{
if (Settings::Get('system.exportenabled') != 1) {
if (Settings::Get('system.backupenabled') != 1) {
throw new Exception("You cannot access this resource", 405);
}
if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras')) {
throw new Exception("You cannot access this resource", 405);
}
if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras.export')) {
if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras.backup')) {
throw new Exception("You cannot access this resource", 405);
}
}
/**
* You cannot get a planned data export.
* Try DataDump.listing()
* You cannot get a planned backup.
* Try CustomerBackups.listing()
*/
public function get()
{
throw new Exception('You cannot get a planned data export. Try DataDump.listing()', 303);
throw new Exception('You cannot get a planned backup. Try CustomerBackups.listing()', 303);
}
/**
* You cannot update a planned data export.
* You cannot update a planned backup.
* You need to delete it and re-add it.
*/
public function update()
{
throw new Exception('You cannot update a planned data export. You need to delete it and re-add it.', 303);
throw new Exception('You cannot update a planned backup. You need to delete it and re-add it.', 303);
}
/**
* list all planned data export jobs, if called from an admin, list all planned data export jobs of all customers you are
* list all planned backup-jobs, if called from an admin, list all planned backup-jobs of all customers you are
* allowed to view, or specify id or loginname for one specific customer
*
* @param int $customerid
* optional, admin-only, select data export jobs of a specific customer by id
* optional, admin-only, select backup-jobs of a specific customer by id
* @param string $loginname
* optional, admin-only, select data export jobs of a specific customer by loginname
* optional, admin-only, select backup-jobs of a specific customer by loginname
* @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
@@ -199,9 +181,9 @@ class DataDump extends ApiCommand implements ResourceEntity
{
$this->validateAccess();
$customer_ids = $this->getAllowedCustomerIds('extras.export');
$customer_ids = $this->getAllowedCustomerIds('extras.backup');
// check whether there is a data export job for this customer
// check whether there is a backup-job for this customer
$query_fields = [];
$sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'" . $this->getSearchWhere($query_fields, true) . $this->getOrderBy() . $this->getLimit());
Database::pexecute($sel_stmt, $query_fields, true, true);
@@ -212,7 +194,7 @@ class DataDump extends ApiCommand implements ResourceEntity
$result[] = $entry;
}
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list customer data dump jobs");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list customer-backups");
return $this->response([
'count' => count($result),
'list' => $result
@@ -220,12 +202,12 @@ class DataDump extends ApiCommand implements ResourceEntity
}
/**
* returns the total number of planned data exports
* returns the total number of planned backups
*
* @param int $customerid
* optional, admin-only, select data export jobs of a specific customer by id
* optional, admin-only, select backup-jobs of a specific customer by id
* @param string $loginname
* optional, admin-only, select data export jobs of a specific customer by loginname
* optional, admin-only, select backup-jobs of a specific customer by loginname
*
* @access admin, customer
* @return string json-encoded response message
@@ -235,9 +217,9 @@ class DataDump extends ApiCommand implements ResourceEntity
{
$this->validateAccess();
$customer_ids = $this->getAllowedCustomerIds('extras.export');
$customer_ids = $this->getAllowedCustomerIds('extras.backup');
// check whether there is a data export job for this customer
// check whether there is a backup-job for this customer
$result_count = 0;
$sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'");
Database::pexecute($sel_stmt, null, true, true);
@@ -251,10 +233,10 @@ class DataDump extends ApiCommand implements ResourceEntity
}
/**
* delete a planned data export jobs by id, if called from an admin you need to specify the customerid/loginname
* delete a planned backup-jobs by id, if called from an admin you need to specify the customerid/loginname
*
* @param int $job_entry
* id of data export job
* @param int $backup_job_entry
* id of backup job
* @param int $customerid
* optional, required when called as admin (if $loginname is not specified)
* @param string $loginname
@@ -266,26 +248,26 @@ class DataDump extends ApiCommand implements ResourceEntity
*/
public function delete()
{
// get planned exports
$result = $this->apiCall('DataDump.listing', $this->getParamList());
// get planned backups
$result = $this->apiCall('CustomerBackups.listing', $this->getParamList());
$entry = $this->getParam('job_entry');
$customer_ids = $this->getAllowedCustomerIds('extras.export');
$entry = $this->getParam('backup_job_entry');
$customer_ids = $this->getAllowedCustomerIds('extras.backup');
if ($result['count'] > 0 && $entry > 0) {
// prepare statement
$del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `id` = :tid");
// check for the correct job
foreach ($result['list'] as $exportjob) {
if ($exportjob['id'] == $entry && in_array($exportjob['data']['customerid'], $customer_ids)) {
foreach ($result['list'] as $backupjob) {
if ($backupjob['id'] == $entry && in_array($backupjob['data']['customerid'], $customer_ids)) {
Database::pexecute($del_stmt, [
'tid' => $entry
], true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] deleted planned customer data export job #" . $entry);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] deleted planned customer-backup #" . $entry);
return $this->response(true);
}
}
}
throw new Exception('Data export job with id #' . $entry . ' could not be found', 404);
throw new Exception('Backup job with id #' . $entry . ' could not be found', 404);
}
}

View File

@@ -100,7 +100,7 @@ class Customers extends ApiCommand implements ResourceEntity
AND `id`<> :stdd
");
$usages_stmt = Database::prepare("
SELECT webspace, mail, mysql FROM `" . TABLE_PANEL_DISKSPACE . "`
SELECT * FROM `" . TABLE_PANEL_DISKSPACE . "`
WHERE `customerid` = :cid
ORDER BY `stamp` DESC LIMIT 1
");
@@ -109,10 +109,11 @@ class Customers extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
if ($show_usages) {
// get number of domains
$domains = Database::pexecute_first($domains_stmt, [
Database::pexecute($domains_stmt, [
'cid' => $row['customerid'],
'stdd' => $row['standardsubdomain']
]);
$domains = $domains_stmt->fetch(PDO::FETCH_ASSOC);
$row['domains'] = intval($domains['domains']);
// get disk-space usages for web, mysql and mail
$usages = Database::pexecute_first($usages_stmt, [
@@ -399,10 +400,6 @@ class Customers extends ApiCommand implements ResourceEntity
}
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
if (empty($allowed_phpconfigs) && $phpenabled == 1) {
Response::standardError('customerphpenabledbutnoconfig', '', true);
}
$allowed_mysqlserver = array();
if (! empty($p_allowed_mysqlserver) && is_array($p_allowed_mysqlserver)) {
foreach ($p_allowed_mysqlserver as $allowed_ms) {
@@ -1053,7 +1050,7 @@ class Customers extends ApiCommand implements ResourceEntity
$email = $this->getParam('email', true, $idna_convert->decode($result['email']));
$name = $this->getParam('name', true, $result['name']);
$firstname = $this->getParam('firstname', true, $result['firstname']);
$company_required = (!empty($name) && empty($firstname)) || (empty($name) && !empty($firstname)) || (empty($name) && empty($firstname));
$company_required = empty($result['company']) && ((!empty($name) && empty($firstname)) || (empty($name) && !empty($firstname)) || (empty($name) && empty($firstname)));
$company = $this->getParam('company', !$company_required, $result['company']);
$street = $this->getParam('street', true, $result['street']);
$zipcode = $this->getParam('zipcode', true, $result['zipcode']);
@@ -1113,9 +1110,6 @@ class Customers extends ApiCommand implements ResourceEntity
if (!empty($allowed_phpconfigs)) {
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
}
if (empty($allowed_phpconfigs) && $phpenabled == 1) {
Response::standardError('customerphpenabledbutnoconfig', '', true);
}
// add permission for allowed mysql usage if customer was not allowed to use mysql prior
if ($result['mysqls'] == 0 && ($mysqls == -1 || $mysqls > 0)) {
@@ -1124,7 +1118,6 @@ class Customers extends ApiCommand implements ResourceEntity
if (! empty($allowed_mysqlserver)) {
$allowed_mysqlserver = array_map('intval', $allowed_mysqlserver);
}
}
$def_language = Validate::validate($def_language, 'default language', '', '', [], true);
$theme = Validate::validate($theme, 'theme', '', '', [], true);

View File

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

View File

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

View File

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

View File

@@ -76,7 +76,7 @@ class Domains extends ApiCommand implements ResourceEntity
$query_fields = [];
$result_stmt = Database::prepare("
SELECT
`d`.*, `c`.`loginname`, `c`.`deactivated` as `customer_deactivated`, `c`.`name`, `c`.`firstname`, `c`.`company`, `c`.`standardsubdomain`, `c`.`adminid` as customeradmin,
`d`.*, `c`.`loginname`, `c`.`deactivated`, `c`.`name`, `c`.`firstname`, `c`.`company`, `c`.`standardsubdomain`, `c`.`adminid` as customeradmin,
`ad`.`id` AS `aliasdomainid`, `ad`.`domain` AS `aliasdomain`
FROM `" . TABLE_PANEL_DOMAINS . "` `d`
LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`)
@@ -110,7 +110,7 @@ class Domains extends ApiCommand implements ResourceEntity
*
* @param number $domain_id
* @param bool $ssl_only
* optional, return only ssl enabled ips, default false
* optional, return only ssl enabled ip's, default false
* @return array
*/
private function getIpsForDomain($domain_id = 0, $ssl_only = false)
@@ -190,6 +190,9 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, whether to create an exclusive web-logfile for this domain, default 0 (false)
* @param int $alias
* optional, domain-id of a domain that the new domain should be an alias of, default 0 (none)
* @param int $issubof
* optional, domain-id of a domain this domain is a subdomain of (required for webserver-cronjob to
* generate the correct order), default 0 (none)
* @param string $registration_date
* optional, date of domain registration in form of YYYY-MM-DD, default empty (none)
* @param string $termination_date
@@ -207,7 +210,7 @@ class Domains extends ApiCommand implements ResourceEntity
* @param string $ssl_specialsettings
* optional, custom webserver vhost-content which is added to the generated ssl-vhost, default empty
* @param bool $include_specialsettings
* optional, whether to include non-ssl specialsettings in the generated ssl-vhost, default false
* optional, whether or not to include non-ssl specialsettings in the generated ssl-vhost, default false
* @param bool $notryfiles
* optional, [nginx only] do not generate the default try-files directive, default 0 (false)
* @param bool $writeaccesslog
@@ -216,7 +219,7 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, Enable writing an error-log file for this domain, default 1 (true)
* @param string $documentroot
* optional, specify homedir of domain by specifying a directory (relative to customer-docroot), be
* aware, if path starts with / it is considered a full path, not relative to customer-docroot. Also
* aware, if path starts with / it it considered a full path, not relative to customer-docroot. Also
* specifying a URL is possible here (redirect), default empty (autogenerated)
* @param bool $phpenabled
* optional, whether php is enabled for this domain, default 0 (false)
@@ -241,7 +244,7 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, do NOT set the systems default ssl ip addresses if none are given via $ssl_ipandport
* parameter
* @param bool $sslenabled
* optional, whether SSL is enabled for this domain, regardless of the assigned ssl-ips, default
* optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default
* 1 (true)
* @param bool $http2
* optional, whether to enable http/2 for this domain (requires to be enabled in the settings), default
@@ -249,9 +252,9 @@ class Domains extends ApiCommand implements ResourceEntity
* @param int $hsts_maxage
* optional max-age value for HSTS header
* @param bool $hsts_sub
* optional whether to add subdomains to the HSTS header
* optional whether or not to add subdomains to the HSTS header
* @param bool $hsts_preload
* optional whether to preload HSTS header value
* optional whether or not to preload HSTS header value
* @param bool $ocsp_stapling
* optional whether to enable ocsp-stapling for this domain. default 0 (false), requires SSL
* @param bool $honorcipherorder
@@ -260,7 +263,7 @@ class Domains extends ApiCommand implements ResourceEntity
* optional whether to enable or disable TLS sessiontickets (RFC 5077) for this domain. default 1
* (true), requires SSL
* @param bool $override_tls
* optional whether to override system-tls settings like protocol, ssl-ciphers and if applicable
* optional whether or not to override system-tls settings like protocol, ssl-ciphers and if applicable
* tls-1.3 ciphers, requires change_serversettings flag for the admin, default false
* @param array $ssl_protocols
* optional list of allowed/used ssl/tls protocols, see system.ssl_protocols setting, only used/required
@@ -295,6 +298,7 @@ class Domains extends ApiCommand implements ResourceEntity
$serveraliasoption = $this->getParam('selectserveralias', true, Settings::Get('system.domaindefaultalias'));
$speciallogfile = $this->getBoolParam('speciallogfile', true, 0);
$aliasdomain = intval($this->getParam('alias', true, 0));
$issubof = $this->getParam('issubof', true, 0);
$registration_date = $this->getParam('registration_date', true, '');
$termination_date = $this->getParam('termination_date', true, '');
$caneditdomain = $this->getBoolParam('caneditdomain', true, 0);
@@ -316,9 +320,9 @@ class Domains extends ApiCommand implements ResourceEntity
$mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, -1);
$ssl_redirect = $this->getBoolParam('ssl_redirect', true, 0);
$letsencrypt = $this->getBoolParam('letsencrypt', true, 0);
$sslenabled = $this->getBoolParam('sslenabled', true, 1);
$dont_use_default_ssl_ipandport_if_empty = $this->getBoolParam('dont_use_default_ssl_ipandport_if_empty', true, 0);
$p_ssl_ipandports = $this->getParam('ssl_ipandport', true, $dont_use_default_ssl_ipandport_if_empty ? [] : explode(',', Settings::Get('system.defaultsslip')));
$sslenabled = $this->getBoolParam('sslenabled', true, 1);
$http2 = $this->getBoolParam('http2', true, 0);
$hsts_maxage = $this->getParam('hsts_maxage', true, 0);
$hsts_sub = $this->getBoolParam('hsts_sub', true, 0);
@@ -349,8 +353,6 @@ class Domains extends ApiCommand implements ResourceEntity
if (substr($p_domain, 0, 4) == 'xn--') {
Response::standardError('domain_nopunycode', '', true);
} elseif (Validate::validate_ip2($p_domain, true, '', true, true)) {
Response::standardError('domain_noipaddress', '', true);
}
$idna_convert = new IdnaWrapper();
@@ -546,10 +548,6 @@ class Domains extends ApiCommand implements ResourceEntity
$ssl_specialsettings = Validate::validate(str_replace("\r\n", "\n", $ssl_specialsettings), 'ssl_specialsettings', '/^[^\0]*$/', '', [], true);
}
}
if (Settings::Get('system.use_ssl') == "1" && $sslenabled == 1 && empty($ssl_ipandports)) {
// enabled ssl for the domain but no ssl ip/port is selected
Response::standardError('nosslippportgiven', '', true);
}
if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) {
$ssl_redirect = 0;
$letsencrypt = 0;
@@ -667,6 +665,10 @@ class Domains extends ApiCommand implements ResourceEntity
$serveraliasoption = '0';
}
if ($issubof <= 0) {
$issubof = '0';
}
$idna_convert = new IdnaWrapper();
if ($domain == '') {
Response::standardError([
@@ -721,6 +723,7 @@ class Domains extends ApiCommand implements ResourceEntity
'phpsettingid' => $phpsettingid,
'mod_fcgid_starter' => $mod_fcgid_starter,
'mod_fcgid_maxrequests' => $mod_fcgid_maxrequests,
'ismainbutsubto' => $issubof,
'letsencrypt' => $letsencrypt,
'http2' => $http2,
'hsts' => $hsts_maxage,
@@ -774,6 +777,7 @@ class Domains extends ApiCommand implements ResourceEntity
`phpsettingid` = :phpsettingid,
`mod_fcgid_starter` = :mod_fcgid_starter,
`mod_fcgid_maxrequests` = :mod_fcgid_maxrequests,
`ismainbutsubto` = :ismainbutsubto,
`letsencrypt` = :letsencrypt,
`http2` = :http2,
`hsts` = :hsts,
@@ -1065,6 +1069,9 @@ class Domains extends ApiCommand implements ResourceEntity
* default 0 (false)
* @param int $alias
* optional, domain-id of a domain that the new domain should be an alias of, default 0 (none)
* @param int $issubof
* optional, domain-id of a domain this domain is a subdomain of (required for webserver-cronjob to
* generate the correct order), default 0 (none)
* @param string $registration_date
* optional, date of domain registration in form of YYYY-MM-DD, default empty (none)
* @param string $termination_date
@@ -1082,7 +1089,7 @@ class Domains extends ApiCommand implements ResourceEntity
* @param string $ssl_specialsettings
* optional, custom webserver vhost-content which is added to the generated ssl-vhost, default empty
* @param bool $include_specialsettings
* optional, whether to include non-ssl specialsettings in the generated ssl-vhost, default false
* optional, whether or not to include non-ssl specialsettings in the generated ssl-vhost, default false
* @param bool $specialsettingsforsubdomains
* optional, whether to apply specialsettings to all subdomains of this domain, default is read from
* setting system.apply_specialsettings_default
@@ -1094,7 +1101,7 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, Enable writing an error-log file for this domain, default 1 (true)
* @param string $documentroot
* optional, specify homedir of domain by specifying a directory (relative to customer-docroot), be
* aware, if path starts with / it is considered a full path, not relative to customer-docroot. Also
* aware, if path starts with / it it considered a full path, not relative to customer-docroot. Also
* specifying a URL is possible here (redirect), default empty (autogenerated)
* @param bool $phpenabled
* optional, whether php is enabled for this domain, default 0 (false)
@@ -1123,7 +1130,7 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, if set to true and no $ssl_ipandport value is given, the ip's get removed, otherwise, the
* currently set value is used, default false
* @param bool $sslenabled
* optional, whether SSL is enabled for this domain, regardless of the assigned ssl-ips, default
* optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default
* 1 (true)
* @param bool $http2
* optional, whether to enable http/2 for this domain (requires to be enabled in the settings), default
@@ -1131,9 +1138,9 @@ class Domains extends ApiCommand implements ResourceEntity
* @param int $hsts_maxage
* optional max-age value for HSTS header
* @param bool $hsts_sub
* optional whether to add subdomains to the HSTS header
* optional whether or not to add subdomains to the HSTS header
* @param bool $hsts_preload
* optional whether to preload HSTS header value
* optional whether or not to preload HSTS header value
* @param bool $ocsp_stapling
* optional whether to enable ocsp-stapling for this domain. default 0 (false), requires SSL
* @param bool $honorcipherorder
@@ -1143,8 +1150,6 @@ class Domains extends ApiCommand implements ResourceEntity
* (true), requires SSL
* @param string $description
* optional custom description (currently not used/shown in the frontend), default empty
* @param bool $deactivated
* optional, if 1 (true) the domain can be deactivated/suspended
*
* @access admin
* @return string json-encoded array
@@ -1186,6 +1191,7 @@ class Domains extends ApiCommand implements ResourceEntity
$speciallogfile = $this->getBoolParam('speciallogfile', true, $result['speciallogfile']);
$speciallogverified = $this->getBoolParam('speciallogverified', true, 0);
$aliasdomain = intval($this->getParam('alias', true, $result['aliasdomain']));
$issubof = $this->getParam('issubof', true, $result['ismainbutsubto']);
$registration_date = $this->getParam('registration_date', true, $result['registration_date']);
$termination_date = $this->getParam('termination_date', true, $result['termination_date']);
$caneditdomain = $this->getBoolParam('caneditdomain', true, $result['caneditdomain']);
@@ -1213,7 +1219,7 @@ class Domains extends ApiCommand implements ResourceEntity
$p_ssl_ipandports = $this->getParam('ssl_ipandport', true, $remove_ssl_ipandport ? [
-1
] : null);
$sslenabled = $remove_ssl_ipandport ? false : $this->getBoolParam('sslenabled', true, $result['ssl_enabled']);
$sslenabled = $this->getBoolParam('sslenabled', true, $result['ssl_enabled']);
$http2 = $this->getBoolParam('http2', true, $result['http2']);
$hsts_maxage = $this->getParam('hsts_maxage', true, $result['hsts']);
$hsts_sub = $this->getBoolParam('hsts_sub', true, $result['hsts_sub']);
@@ -1240,7 +1246,6 @@ class Domains extends ApiCommand implements ResourceEntity
$tlsv13_cipher_list = $result['tlsv13_cipher_list'];
}
$description = $this->getParam('description', true, $result['description']);
$deactivated = $this->getBoolParam('deactivated', true, $result['deactivated']);
// count subdomain usage of source-domain
$subdomains_stmt = Database::prepare("
@@ -1523,10 +1528,6 @@ class Domains extends ApiCommand implements ResourceEntity
if ($remove_ssl_ipandport || (!empty($p_ssl_ipandports) && $p_ssl_ipandports[0] == -1)) {
$ssl_ipandports = [];
}
if (Settings::Get('system.use_ssl') == "1" && $sslenabled && empty($ssl_ipandports)) {
// enabled ssl for the domain but no ssl ip/port is selected
Response::standardError('nosslippportgiven', '', true);
}
if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) {
$ssl_redirect = 0;
$letsencrypt = 0;
@@ -1563,7 +1564,7 @@ class Domains extends ApiCommand implements ResourceEntity
}
// Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated
if (($result['letsencrypt'] != $letsencrypt || $result['ssl_redirect'] != $ssl_redirect) && $ssl_redirect > 0 && $letsencrypt == 1) {
if ($ssl_redirect > 0 && $letsencrypt == 1 && $result['letsencrypt'] != $letsencrypt) {
$ssl_redirect = 2;
}
@@ -1639,6 +1640,10 @@ class Domains extends ApiCommand implements ResourceEntity
Response::standardError('domainisaliasorothercustomer', '', true);
}
if ($issubof <= 0) {
$issubof = '0';
}
if ($serveraliasoption != '1' && $serveraliasoption != '2') {
$serveraliasoption = '0';
}
@@ -1652,7 +1657,6 @@ class Domains extends ApiCommand implements ResourceEntity
|| $iswildcarddomain != $result['iswildcarddomain']
|| $phpenabled != $result['phpenabled']
|| $openbasedir != $result['openbasedir']
|| $openbasedir_path != $result['openbasedir_path']
|| $phpsettingid != $result['phpsettingid']
|| $mod_fcgid_starter != $result['mod_fcgid_starter']
|| $mod_fcgid_maxrequests != $result['mod_fcgid_maxrequests']
@@ -1662,6 +1666,7 @@ class Domains extends ApiCommand implements ResourceEntity
|| $writeaccesslog != $result['writeaccesslog']
|| $writeerrorlog != $result['writeerrorlog']
|| $aliasdomain != $result['aliasdomain']
|| $issubof != $result['ismainbutsubto']
|| $email_only != $result['email_only']
|| ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1')
|| $letsencrypt != $result['letsencrypt']
@@ -1670,7 +1675,6 @@ class Domains extends ApiCommand implements ResourceEntity
|| $hsts_sub != $result['hsts_sub']
|| $hsts_preload != $result['hsts_preload']
|| $ocsp_stapling != $result['ocsp_stapling']
|| $sslenabled != $result['ssl_enabled']
) {
Cronjob::inserttask(TaskId::REBUILD_VHOST);
}
@@ -1819,7 +1823,7 @@ class Domains extends ApiCommand implements ResourceEntity
$update_data['wwwserveralias'] = $wwwserveralias;
$update_data['iswildcarddomain'] = $iswildcarddomain;
$update_data['phpenabled'] = $phpenabled;
$update_data['openbasedir'] = $openbasedir;
$update_data['openbasedir'] = $openbasedir;;
$update_data['openbasedir_path'] = $openbasedir_path;
$update_data['speciallogfile'] = $speciallogfile;
$update_data['phpsettingid'] = $phpsettingid;
@@ -1833,6 +1837,7 @@ class Domains extends ApiCommand implements ResourceEntity
$update_data['writeerrorlog'] = $writeerrorlog;
$update_data['registration_date'] = $registration_date;
$update_data['termination_date'] = $termination_date;
$update_data['ismainbutsubto'] = $issubof;
$update_data['letsencrypt'] = $letsencrypt;
$update_data['http2'] = $http2;
$update_data['hsts'] = $hsts_maxage;
@@ -1847,7 +1852,6 @@ class Domains extends ApiCommand implements ResourceEntity
$update_data['honorcipherorder'] = $honorcipherorder;
$update_data['sessiontickets'] = $sessiontickets;
$update_data['description'] = $description;
$update_data['deactivated'] = $deactivated;
$update_data['id'] = $id;
$update_stmt = Database::prepare("
@@ -1881,6 +1885,7 @@ class Domains extends ApiCommand implements ResourceEntity
`writeerrorlog` = :writeerrorlog,
`registration_date` = :registration_date,
`termination_date` = :termination_date,
`ismainbutsubto` = :ismainbutsubto,
`letsencrypt` = :letsencrypt,
`http2` = :http2,
`hsts` = :hsts,
@@ -1894,36 +1899,11 @@ class Domains extends ApiCommand implements ResourceEntity
`ssl_enabled` = :sslenabled,
`ssl_honorcipherorder` = :honorcipherorder,
`ssl_sessiontickets` = :sessiontickets,
`description` = :description,
`deactivated` = :deactivated
`description` = :description
WHERE `id` = :id
");
Database::pexecute($update_stmt, $update_data, true, true);
// activate/deactivate domain-based services
if ($deactivated != $result['deactivated']) {
// deactivate email accounts
$yesno = ($deactivated ? 'N' : 'Y');
$pop3 = ($deactivated ? '0' : (int)$customer['pop3']);
$imap = ($deactivated ? '0' : (int)$customer['imap']);
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_MAIL_USERS . "`
SET `postfix`= :yesno, `pop3` = :pop3, `imap` = :imap
WHERE `customerid` = :customerid AND `domainid` = :domainid
");
Database::pexecute($upd_stmt, [
'yesno' => $yesno,
'pop3' => $pop3,
'imap' => $imap,
'customerid' => $customerid,
'domainid' => $id
]);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] " . ($deactivated ? 'deactivated' : 'reactivated') . " domain '" . $result['domain'] . "'");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
}
$_update_data['customerid'] = $customerid;
$_update_data['adminid'] = $adminid;
$_update_data['phpenabled'] = $phpenabled;
@@ -1941,7 +1921,6 @@ class Domains extends ApiCommand implements ResourceEntity
$_update_data['honorcipherorder'] = $honorcipherorder;
$_update_data['sessiontickets'] = $sessiontickets;
$_update_data['parentdomainid'] = $id;
$_update_data['deactivated'] = $deactivated;
// if php config is to be set for all subdomains, check here
$update_phpconfig = '';
@@ -1974,8 +1953,7 @@ class Domains extends ApiCommand implements ResourceEntity
`ssl_cipher_list` = :ssl_cipher_list,
`tlsv13_cipher_list` = :tlsv13_cipher_list,
`ssl_honorcipherorder` = :honorcipherorder,
`ssl_sessiontickets` = :sessiontickets,
`deactivated` = :deactivated
`ssl_sessiontickets` = :sessiontickets
" . $update_phpconfig . $upd_specialsettings . $updatechildren . $update_sslredirect . "
WHERE `parentdomainid` = :parentdomainid
");
@@ -2095,6 +2073,9 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, the domain-id
* @param string $domainname
* optional, the domainname
* @param bool $delete_mainsubdomains
* optional, remove also domains that are subdomains of this domain but added as main domains; default
* false
* @param bool $is_stdsubdomain
* optional, default false, specify whether it's a std-subdomain you are deleting as it does not count
* as subdomain-resource
@@ -2110,6 +2091,7 @@ class Domains extends ApiCommand implements ResourceEntity
$dn_optional = $id > 0;
$domainname = $this->getParam('domainname', $dn_optional, '');
$is_stdsubdomain = $this->getParam('is_stdsubdomain', true, 0);
$remove_subbutmain_domains = $this->getParam('delete_mainsubdomains', true, 0);
$result = $this->apiCall('Domains.get', [
'id' => $id,
@@ -2117,10 +2099,15 @@ class Domains extends ApiCommand implements ResourceEntity
]);
$id = $result['id'];
// check for deletion of main-domains which are logically subdomains, #329
$rsd_sql = '';
if ($remove_subbutmain_domains) {
$rsd_sql .= " OR `ismainbutsubto` = :id";
}
$subresult_stmt = Database::prepare("
SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE (`id` = :id OR `parentdomainid` = :id)
");
SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE (`id` = :id OR `parentdomainid` = :id " . $rsd_sql . ")");
Database::pexecute($subresult_stmt, [
'id' => $id
], true, true);
@@ -2142,10 +2129,23 @@ class Domains extends ApiCommand implements ResourceEntity
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] deleted domain/s from mail-tables");
}
// if mainbutsubto-domains are not to be deleted, re-assign the (ismainbutsubto value of the main
// domain which is being deleted) as their new ismainbutsubto value
if ($remove_subbutmain_domains !== 1) {
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_DOMAINS . "` SET
`ismainbutsubto` = :newIsMainButSubtoValue
WHERE `ismainbutsubto` = :deletedMainDomainId
");
Database::pexecute($upd_stmt, [
'newIsMainButSubtoValue' => $result['ismainbutsubto'],
'deletedMainDomainId' => $id
], true, true);
}
$del_stmt = Database::prepare("
DELETE FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE `id` = :id OR `parentdomainid` = :id
");
DELETE FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE `id` = :id OR `parentdomainid` = :id " . $rsd_sql);
Database::pexecute($del_stmt, [
'id' => $id
], true, true);
@@ -2230,118 +2230,4 @@ class Domains extends ApiCommand implements ResourceEntity
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* duplicate domain entry by either id or domainname. All parameters from Domains.add() can be used
* to overwrite source entity values if necessary.
*
* @param int $id
* optional, the domain-id
* @param string $domainname
* optional, the domainname
* @param string $domain
* required, name of the new domain to be added
*
* @access admin
* @return string json-encoded array
* @throws Exception
*/
public function duplicate()
{
if ($this->isAdmin()) {
// parameters
$id = $this->getParam('id', true, 0);
$dn_optional = $id > 0;
$domainname = $this->getParam('domainname', $dn_optional, '');
$p_domain = $this->getParam('domain');
// get requested domain
$result = $this->apiCall('Domains.get', [
'id' => $id,
'domainname' => $domainname,
]);
// clear some defaults
unset($result['domain_ace']);
unset($result['adminid']);
unset($result['documentroot']);
unset($result['registration_date']);
unset($result['termination_date']);
unset($result['zonefile']);
// clear auto-generated values
unset($result['bindserial']);
unset($result['dkim_privkey']);
unset($result['dkim_pubkey']);
// clear api-call generated fields
unset($result['domain_hascert']);
// set correct ip/port information
$domain_ips = $result['ipsandports'];
unset($result['ipsandports']);
$result['ipandport'] = [];
$result['ssl_ipandport'] = [];
foreach ($domain_ips as $dip) {
if ($dip['ssl'] == 1) {
$result['ssl_ipandport'][] = $dip['id'];
} else {
$result['ipandport'][] = $dip['id'];
}
}
// check whether we are changing the customer/owner
if ($this->getParam('customerid', true, 0) == 0 && $this->getParam('loginname', true, '') == '') {
$customerid = $result['customerid'];
} else {
$customer = $this->getCustomerData();
$customerid = $customer['customerid'];
}
// check for alias-domain and whether it belongs to the target user
if (!empty($result['aliasdomain']) && $customerid == $result['customerid']) {
// duplicate alias entry
$result['alias'] = $result['aliasdomain'];
}
unset($result['aliasdomain']);
// validate possible fpm configs and whether the customer is allowed to use them
if ($customerid != $result['customerid']) {
$allowed_phpconfigs = json_decode($customer['allowed_phpconfigs'] ?? '[]', true);
if (empty($allowed_phpconfigs)) {
// system defaults
unset($result['phpsettingid']);
} elseif (!in_array($result['phpsettingid'], $allowed_phpconfigs)) {
// use the first customer allowed config
$result['phpsettingid'] = array_shift($allowed_phpconfigs);
}
}
// translate serveralias values
$result['selectserveralias'] = 2;
if ((int)$result['wwwserveralias'] == 1) {
$result['selectserveralias'] = 1;
} elseif ((int)$result['iswildcarddomain'] == 1) {
$result['selectserveralias'] = 0;
}
unset($result['wwwserveralias']);
unset($result['iswildcarddomain']);
// translate sslenabled flag
$result['sslenabled'] = $result['ssl_enabled'];
unset($result['ssl_enabled']);
$additional_params = $this->getParamList();
// unset unneeded params from this call
unset($additional_params['id']);
unset($additional_params['domainname']);
unset($additional_params['domain']);
// set new values and merge with optional add() parameters
$new_domain = array_merge($result, $additional_params);
$new_domain['domain'] = $p_domain;
$result_new = $this->apiCall('Domains.add', $new_domain);
return $this->response($result_new);
}
throw new Exception("Not allowed to execute given command.", 403);
}
}

View File

@@ -95,13 +95,9 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$customer = $this->getCustomerData('email_accounts');
// check for imap||pop3 == 1, see #1298
// d00p, 6.5.2023 @revert this - if a customer has resources which allow email accounts
// it implicitly allowed SMTP, e.g. sending of emails which also requires an account to exist
/*
if ($customer['imap'] != '1' && $customer['pop3'] != '1') {
Response::standardError('notallowedtouseaccounts', '', true);
}
*/
if (!empty($emailaddr)) {
$idna_convert = new IdnaWrapper();

View File

@@ -75,6 +75,7 @@ class Emails extends ApiCommand implements ResourceEntity
// parameters
$iscatchall = $this->getBoolParam('iscatchall', true, 0);
$disablegreylist = $this->getBoolParam('disablegreylist', true, 0);
$description = $this->getParam('description', true, '');
// validation
@@ -88,12 +89,9 @@ class Emails extends ApiCommand implements ResourceEntity
$domain_check = $this->apiCall('SubDomains.get', [
'domainname' => $domain
], true);
if ((int)$domain_check['isemaildomain'] == 0) {
if ($domain_check['isemaildomain'] == 0) {
Response::standardError('maindomainnonexist', $domain, true);
}
if ((int)$domain_check['deactivated'] == 1) {
Response::standardError('maindomaindeactivated', $domain, true);
}
if (Settings::Get('catchall.catchall_enabled') != '1') {
$iscatchall = 0;
@@ -121,7 +119,7 @@ class Emails extends ApiCommand implements ResourceEntity
// duplicate check
$stmt = Database::prepare("
SELECT `id`, `email`, `email_full`, `iscatchall`, `destination`, `customerid` FROM `" . TABLE_MAIL_VIRTUAL . "`
SELECT `id`, `email`, `email_full`, `iscatchall`, `destination`, `customerid`, `disablegreylist` FROM `" . TABLE_MAIL_VIRTUAL . "`
WHERE (`email` = :email OR `email_full` = :emailfull )
AND `customerid`= :cid
");
@@ -147,7 +145,8 @@ class Emails extends ApiCommand implements ResourceEntity
`email_full` = :email_full,
`iscatchall` = :iscatchall,
`domainid` = :domainid,
`description` = :description
`description` = :description,
`disablegreylist` = :disablegreylist
");
$params = [
"cid" => $customer['customerid'],
@@ -155,7 +154,8 @@ class Emails extends ApiCommand implements ResourceEntity
"email_full" => $email_full,
"iscatchall" => $iscatchall,
"domainid" => $domain_check['id'],
"description" => $description
"description" => $description,
"disablegreylist" => $disablegreylist
];
Database::pexecute($stmt, $params, true, true);
@@ -194,7 +194,7 @@ class Emails extends ApiCommand implements ResourceEntity
$customer_ids = $this->getAllowedCustomerIds('email');
$params['idea'] = ($id <= 0 ? $emailaddr : $id);
$result_stmt = Database::prepare("SELECT v.`id`, v.`email`, v.`email_full`, v.`iscatchall`, v.`destination`, v.`customerid`, v.`popaccountid`, v.`domainid`, v.`description`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
$result_stmt = Database::prepare("SELECT v.`id`, v.`email`, v.`email_full`, v.`iscatchall`, v.`disablegreylist`, v.`destination`, v.`customerid`, v.`popaccountid`, v.`domainid`, v.`description`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
FROM `" . TABLE_MAIL_VIRTUAL . "` v
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON v.`popaccountid` = u.`id`
WHERE v.`customerid` IN (" . implode(", ", $customer_ids) . ")
@@ -305,6 +305,81 @@ class Emails extends ApiCommand implements ResourceEntity
return $this->response($result);
}
/**
* toggle greylist flag of given email address either by id or email-address
*
* @param int $id
* optional, the email-address-id
* @param string $emailaddr
* optional, the email-address
* @param int $customerid
* optional, required when called as admin (if $loginname is not specified)
* @param string $loginname
* optional, required when called as admin (if $customerid is not specified)
* @param boolean $greylist
* optional
* @param string $description
* optional custom description (currently not used/shown in the frontend), default empty
*
* @access admin, customer
* @return string json-encoded array
* @throws Exception
*/
public function updateGreylist()
{
if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'email')) {
throw new Exception("You cannot access this resource", 405);
}
// if enabling catchall is not allowed by settings, we do not need
// to run update()
/** if (Settings::Get('catchall.catchall_enabled') != '1') {
Response::standardError([
'operationnotpermitted',
'featureisdisabled'
], 'catchall', true);
} */
$id = $this->getParam('id', true, 0);
$ea_optional = $id > 0;
$emailaddr = $this->getParam('emailaddr', $ea_optional, '');
$result = $this->apiCall('Emails.get', [
'id' => $id,
'emailaddr' => $emailaddr
]);
$id = $result['id'];
$email = $result['email'];
// parameters
$disablegreylist = $this->getBoolParam('disablegreylist', true, $result['disablegreylist']);
$description = $this->getParam('description', true, $result['description']);
// get needed customer info to reduce the email-address-counter by one
$customer = $this->getCustomerData();
// check for catchall-flag
$stmt = Database::prepare("
UPDATE `" . TABLE_MAIL_VIRTUAL . "`
SET `email` = :email , `disablegreylist` = :grflag, `description` = :description
WHERE `customerid`= :cid AND `id`= :id
");
$params = [
"email" => $email,
"grflag" => $disablegreylist,
"description" => $description,
"cid" => $customer['customerid'],
"id" => $id
];
Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] toggled greylist-flag for email address '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [
'emailaddr' => $result['email_full']
]);
return $this->response($result);
}
/**
* list all email addresses, if called from an admin, list all email addresses of all customers you are allowed to
* view, or specify id or loginname for one specific customer
@@ -334,7 +409,7 @@ class Emails extends ApiCommand implements ResourceEntity
$result = [];
$query_fields = [];
$result_stmt = Database::prepare("
SELECT m.`id`, m.`domainid`, m.`email`, m.`email_full`, m.`iscatchall`, m.`destination`, m.`popaccountid`, d.`domain`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
SELECT m.`id`, m.`domainid`, m.`email`, m.`email_full`, m.`iscatchall`, m.`disablegreylist`, m.`destination`, m.`popaccountid`, d.`domain`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
FROM `" . TABLE_MAIL_VIRTUAL . "` m
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` d ON (m.`domainid` = d.`id`)
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON (m.`popaccountid` = u.`id`)

View File

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

View File

@@ -37,7 +37,6 @@ use Froxlor\Settings;
use Froxlor\SImExporter;
use Froxlor\System\Cronjob;
use Froxlor\System\Crypt;
use Froxlor\Validate\Validate;
use PDO;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
@@ -82,7 +81,7 @@ class Froxlor extends ApiCommand
if ($aucheck == 1) {
// anzeige über version-status mit ggfls. formular
// zum update schritt #1 -> download
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel').' ' : ''), AutoUpdate::getFromResult('version'), $this->version]);
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') == 'testing' ? 'testing ' : ''), AutoUpdate::getFromResult('version'), $this->version]);
$response = [
'isnewerversion' => (int) !AutoUpdate::getFromResult('has_latest'),
'version' => $this->version,
@@ -91,7 +90,7 @@ class Froxlor extends ApiCommand
'additional_info' => AutoUpdate::getFromResult('info'),
'aucheck' => $aucheck
];
} elseif ($aucheck < 0 || $aucheck > 1) {
} else if ($aucheck < 0 || $aucheck > 1) {
// errors
if ($aucheck < 0) {
$errmsg = AutoUpdate::getLastError();
@@ -270,79 +269,6 @@ class Froxlor extends ApiCommand
return $this->response(Crypt::generatePassword($length));
}
/**
* return a one-time login link URL for a given user
*
* @param int $customerid optional, required if $loginname is not specified, user to create link for
* @param string $loginname optional, required if $customerid is not specified, user to create link for
* @param int $valid_time optional, value in seconds how long the link will be valid, default is 10 seconds, valid values are numbers from 10 to 120
* @param string $allowed_from optional, comma separated list of ip addresses or networks to allow login from via this link
*
* @access admin
* @return string json-encoded array [base => domain, uri => relative link]
* @throws Exception
*/
public function generateLoginLink()
{
if ($this->isAdmin()) {
$customer = $this->getCustomerData();
// cannot create link for deactivated users
if ((int)$customer['deactivated'] == 1) {
throw new Exception("Cannot generate link for deactivated user", 406);
}
$valid_time = (int)$this->getParam('valid_time', true, 10);
$allowed_from = $this->getParam('allowed_from', true, '');
$valid_time = Validate::validate($valid_time, 'valid time', '/^(1[0-1][0-9]|120|[1-9][0-9])$/', 'invalid_validtime', [10], true);
// validate allowed_from
if (!empty($allowed_from)) {
$ip_list = array_map('trim', explode(",", $allowed_from));
$_check_list = $ip_list;
foreach ($_check_list as $idx => $ip) {
if (Validate::validate_ip2($ip, true, 'invalidip', true, true, true) == false) {
throw new Exception('Invalid ip address', 406);
}
// check for cidr
if (strpos($ip, '/') !== false) {
$ipparts = explode("/", $ip);
// shorten IP
$ip = inet_ntop(inet_pton($ipparts[0]));
// re-add cidr
$ip .= '/' . $ipparts[1];
} else {
// shorten IP
$ip = inet_ntop(inet_pton($ip));
}
$ip_list[$idx] = $ip;
}
$allowed_from = implode(",", array_unique($ip_list));
}
$hash = hash('sha256', openssl_random_pseudo_bytes(64 * 64));
$ins_stmt = Database::prepare("
INSERT INTO `" . TABLE_PANEL_LOGINLINKS . "`
SET `hash` = :hash, `loginname` = :loginname, `valid_until` = :validuntil, `allowed_from` = :allowedfrom
ON DUPLICATE KEY UPDATE `hash` = :hash, `valid_until` = :validuntil, `allowed_from` = :allowedfrom
");
Database::pexecute($ins_stmt, [
'hash' => $hash,
'loginname' => $customer['loginname'],
'validuntil' => time() + $valid_time,
'allowedfrom' => $allowed_from
]);
return $this->response([
'base' => 'https://' . Settings::Get('system.hostname') . '/' . (Settings::Get('system.froxlordirectlyviahostname') != 1 ? basename(\Froxlor\Froxlor::getInstallDir()) . '/' : ''),
'uri' => 'index.php?action=ll&ln=' . $customer['loginname'] . '&h=' . $hash
]);
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* can be used to remotely run the integritiy checks froxlor implements
*

View File

@@ -72,8 +72,6 @@ class Ftps extends ApiCommand implements ResourceEntity
* optional whether to add additional usernames to the group
* @param bool $is_defaultuser
* optional whether this is the standard default ftp user which is being added so no usage is decreased
* @param bool $login_enabled
* optional whether to allow login (default) or not
*
* @access admin, customer
* @return string json-encoded array
@@ -86,7 +84,6 @@ class Ftps extends ApiCommand implements ResourceEntity
}
$is_defaultuser = $this->getBoolParam('is_defaultuser', true, 0);
$login_enabled = $this->getBoolParam('login_enabled', true, 1);
if (($this->getUserDetail('ftps_used') < $this->getUserDetail('ftps') || $this->getUserDetail('ftps') == '-1') || $this->isAdmin() && $is_defaultuser == 1) {
// required parameters
@@ -174,19 +171,18 @@ class Ftps extends ApiCommand implements ResourceEntity
} elseif ($username == $password) {
Response::standardError('passwordshouldnotbeusername', '', true);
} else {
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
$cryptPassword = Crypt::makeCryptPassword($password, false, true);
$stmt = Database::prepare("INSERT INTO `" . TABLE_FTP_USERS . "`
(`customerid`, `username`, `description`, `password`, `homedir`, `login_enabled`, `uid`, `gid`, `shell`)
VALUES (:customerid, :username, :description, :password, :homedir, :loginenabled, :guid, :guid, :shell)");
VALUES (:customerid, :username, :description, :password, :homedir, 'y', :guid, :guid, :shell)");
$params = [
"customerid" => $customer['customerid'],
"username" => $username,
"description" => $description,
"password" => $cryptPassword,
"homedir" => $path,
"loginenabled" => $login_enabled ? 'Y' : 'N',
"guid" => $customer['guid'],
"shell" => $shell
];
@@ -393,8 +389,6 @@ class Ftps extends ApiCommand implements ResourceEntity
* optional, description for ftp-user
* @param string $shell
* optional, default /bin/false (not changeable when deactivated)
* @param bool $login_enabled
* optional whether to allow login (default) or not
* @param int $customerid
* optional, required when called as admin (if $loginname is not specified)
* @param string $loginname
@@ -425,7 +419,6 @@ class Ftps extends ApiCommand implements ResourceEntity
$password = $this->getParam('ftp_password', true, '');
$description = $this->getParam('ftp_description', true, $result['description']);
$shell = $this->getParam('shell', true, $result['shell']);
$login_enabled = $this->getBoolParam('login_enabled', true, ($result['login_enabled'] == 'Y' ? 1 : 0));
// validation
$password = Validate::validate($password, 'password', '', '', [], true);
@@ -437,10 +430,6 @@ class Ftps extends ApiCommand implements ResourceEntity
$shell = "/bin/false";
}
if ($login_enabled != 1) {
$login_enabled = 0;
}
// get needed customer info to reduce the ftp-user-counter by one
$customer = $this->getCustomerData();
@@ -469,7 +458,7 @@ class Ftps extends ApiCommand implements ResourceEntity
// path update?
if ($path != '') {
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
if ($path != $result['homedir']) {
$stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "`
@@ -491,14 +480,13 @@ class Ftps extends ApiCommand implements ResourceEntity
$stmt = Database::prepare("
UPDATE `" . TABLE_FTP_USERS . "`
SET `description` = :desc, `shell` = :shell, `login_enabled` = :loginenabled
SET `description` = :desc, `shell` = :shell
WHERE `customerid` = :customerid
AND `id` = :id
");
Database::pexecute($stmt, [
"desc" => $description,
"shell" => $shell,
"loginenabled" => $login_enabled ? 'Y' : 'N',
"customerid" => $customer['customerid'],
"id" => $id
], true, true);

View File

@@ -201,7 +201,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
// validation
$name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_CONF_TEXT);
if (Settings::Get('system.mail_quota_enabled') != '1') {
$value_arr['email_quota'] = -1;
@@ -383,7 +383,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
// validation
$name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_CONF_TEXT);
if (Settings::Get('system.mail_quota_enabled') != '1') {
$value_arr['email_quota'] = -1;

View File

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

View File

@@ -67,8 +67,6 @@ class SubDomains extends ApiCommand implements ResourceEntity
* optional, php-settings-id, if empty the $domain value is used
* @param int $redirectcode
* optional, redirect-code-id from TABLE_PANEL_REDIRECTCODES
* @param int $speciallogfile
* optional, whether to create an exclusive web-logfile for this domain (1) or not (0) or inherit value from parentdomain (2, default)
* @param bool $sslenabled
* optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default
* 1 (true)
@@ -109,7 +107,6 @@ class SubDomains extends ApiCommand implements ResourceEntity
$openbasedir_path = $this->getParam('openbasedir_path', true, 0);
$phpsettingid = $this->getParam('phpsettingid', true, 0);
$redirectcode = $this->getParam('redirectcode', true, Settings::Get('customredirect.default'));
$speciallogfile = intval($this->getParam('speciallogfile', true, 2));
$isemaildomain = $this->getParam('isemaildomain', true, 0);
if (Settings::Get('system.use_ssl')) {
$sslenabled = $this->getBoolParam('sslenabled', true, 1);
@@ -232,9 +229,6 @@ class SubDomains extends ApiCommand implements ResourceEntity
} elseif ($completedomain_check && strtolower($completedomain_check['domain']) == strtolower($completedomain)) {
// the domain does already exist as main-domain
Response::standardError('domainexistalready', $completedomain, true);
} elseif ((int)$domain_check['deactivated'] == 1) {
// main domain is deactivated
Response::standardError('maindomaindeactivated', $domain, true);
}
// if allowed, check for 'is email domain'-flag
@@ -279,11 +273,6 @@ class SubDomains extends ApiCommand implements ResourceEntity
$ssl_redirect = 2;
}
// validate speciallogfile value
if ($speciallogfile < 0 || $speciallogfile > 2) {
$speciallogfile = 2; // inherit from parent-domain
}
// get the phpsettingid from parentdomain, #107
$phpsid_stmt = Database::prepare("
SELECT `phpsettingid` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `id` = :id
@@ -362,7 +351,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
"openbasedir" => $domain_check['openbasedir'],
"openbasedir_path" => $openbasedir_path,
"phpenabled" => $domain_check['phpenabled'],
"speciallogfile" => $speciallogfile == 2 ? $domain_check['speciallogfile'] : $speciallogfile,
"speciallogfile" => $domain_check['speciallogfile'],
"specialsettings" => $domain_check['specialsettings'],
"ssl_specialsettings" => $domain_check['ssl_specialsettings'],
"include_specialsettings" => $domain_check['include_specialsettings'],
@@ -564,9 +553,9 @@ class SubDomains extends ApiCommand implements ResourceEntity
// If path is empty or '/' and 'Use domain name as default value for DocumentRoot path' is enabled in settings,
// set default path to subdomain or domain name
if ((($path == '') || ($path == '/')) && Settings::Get('system.documentroot_use_default_value') == 1) {
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $completedomain, $customer['documentroot']);
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $completedomain);
} else {
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
}
} else {
// no it's not, create a redirect
@@ -599,11 +588,6 @@ class SubDomains extends ApiCommand implements ResourceEntity
* optional, php-settings-id, if empty the $domain value is used
* @param int $redirectcode
* optional, redirect-code-id from TABLE_PANEL_REDIRECTCODES
* @param bool $speciallogfile
* optional, whether to create an exclusive web-logfile for this domain
* @param bool $speciallogverified
* optional, when setting $speciallogfile to false, this needs to be set to true to confirm the action,
* default 0 (false)
* @param bool $sslenabled
* optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default
* 1 (true)
@@ -661,8 +645,6 @@ class SubDomains extends ApiCommand implements ResourceEntity
$openbasedir_path = $this->getParam('openbasedir_path', true, $result['openbasedir_path']);
$phpsettingid = $this->getParam('phpsettingid', true, $result['phpsettingid']);
$redirectcode = $this->getParam('redirectcode', true, Domain::getDomainRedirectId($id));
$speciallogfile = $this->getBoolParam('speciallogfile', true, $result['speciallogfile']);
$speciallogverified = $this->getBoolParam('speciallogverified', true, 0);
if (Settings::Get('system.use_ssl')) {
$sslenabled = $this->getBoolParam('sslenabled', true, $result['ssl_enabled']);
$ssl_redirect = $this->getBoolParam('ssl_redirect', true, $result['ssl_redirect']);
@@ -772,10 +754,6 @@ class SubDomains extends ApiCommand implements ResourceEntity
$ssl_redirect = 2;
}
if ($speciallogfile != $result['speciallogfile'] && $speciallogverified != '1') {
$speciallogfile = $result['speciallogfile'];
}
// is-email-domain flag changed - remove mail accounts and mail-addresses
if (($result['isemaildomain'] == '1') && $isemaildomain == '0') {
$params = [
@@ -808,21 +786,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
Domain::updateRedirectOfDomain($id, $redirectcode);
}
if ($path != $result['documentroot']
|| $isemaildomain != $result['isemaildomain']
|| $wwwserveralias != $result['wwwserveralias']
|| $iswildcarddomain != $result['iswildcarddomain']
|| $aliasdomain != (int)$result['aliasdomain']
|| $openbasedir_path != $result['openbasedir_path']
|| $ssl_redirect != $result['ssl_redirect']
|| $letsencrypt != $result['letsencrypt']
|| $hsts_maxage != $result['hsts']
|| $hsts_sub != $result['hsts_sub']
|| $hsts_preload != $result['hsts_preload']
|| $phpsettingid != $result['phpsettingid']
|| $http2 != $result['http2']
|| ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1')
) {
if ($path != $result['documentroot'] || $isemaildomain != $result['isemaildomain'] || $wwwserveralias != $result['wwwserveralias'] || $iswildcarddomain != $result['iswildcarddomain'] || $aliasdomain != (int)$result['aliasdomain'] || $openbasedir_path != $result['openbasedir_path'] || $ssl_redirect != $result['ssl_redirect'] || $letsencrypt != $result['letsencrypt'] || $hsts_maxage != $result['hsts'] || $hsts_sub != $result['hsts_sub'] || $hsts_preload != $result['hsts_preload'] || $phpsettingid != $result['phpsettingid'] || $http2 != $result['http2']) {
$stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_DOMAINS . "` SET
`documentroot` = :documentroot,
@@ -838,8 +802,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
`hsts` = :hsts,
`hsts_sub` = :hsts_sub,
`hsts_preload` = :hsts_preload,
`phpsettingid` = :phpsettingid,
`speciallogfile` = :speciallogfile
`phpsettingid` = :phpsettingid
WHERE `customerid`= :customerid AND `id`= :id
");
$params = [
@@ -857,7 +820,6 @@ class SubDomains extends ApiCommand implements ResourceEntity
"hsts_sub" => $hsts_sub,
"hsts_preload" => $hsts_preload,
"phpsettingid" => $phpsettingid,
"speciallogfile" => $speciallogfile,
"customerid" => $customer['customerid'],
"id" => $id
];
@@ -903,7 +865,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
}
/**
* lists all customer domain/subdomain entries
* lists all subdomain entries
*
* @param bool $with_ips
* optional, default true
@@ -948,12 +910,17 @@ class SubDomains extends ApiCommand implements ResourceEntity
$custom_list_result = $_custom_list_result['list'];
}
$customer_ids = [];
$customer_stdsubs = [];
foreach ($custom_list_result as $customer) {
$customer_ids[] = $customer['customerid'];
$customer_stdsubs[$customer['customerid']] = $customer['standardsubdomain'];
}
if (empty($customer_ids)) {
throw new Exception("Required resource unsatisfied.", 405);
}
if (empty($customer_stdsubs)) {
throw new Exception("Required resource unsatisfied.", 405);
}
$select_fields = [
'`d`.*'
@@ -965,6 +932,9 @@ class SubDomains extends ApiCommand implements ResourceEntity
$customer_ids = [
$this->getUserDetail('customerid')
];
$customer_stdsubs = [
$this->getUserDetail('customerid') => $this->getUserDetail('standardsubdomain')
];
$select_fields = [
'`d`.`id`',
@@ -979,8 +949,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
'`d`.`parentdomainid`',
'`d`.`letsencrypt`',
'`d`.`registration_date`',
'`d`.`termination_date`',
'`d`.`deactivated`'
'`d`.`termination_date`'
];
}
$query_fields = [];
@@ -994,7 +963,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `pd` ON `pd`.`id`=`d`.`parentdomainid`
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
AND `d`.`email_only` = '0'
" . $this->getSearchWhere($query_fields, true) . " GROUP BY `d`.`id` ORDER BY `parentdomainname` ASC, `d`.`parentdomainid` ASC " . $this->getOrderBy(true) . $this->getLimit());
AND `d`.`id` NOT IN (" . implode(', ', $customer_stdsubs) . ")" . $this->getSearchWhere($query_fields, true) . " GROUP BY `d`.`id` ORDER BY `parentdomainname` ASC, `d`.`parentdomainid` ASC " . $this->getOrderBy(true) . $this->getLimit());
$result = [];
Database::pexecute($domains_stmt, $query_fields, true, true);
@@ -1078,8 +1047,10 @@ class SubDomains extends ApiCommand implements ResourceEntity
$custom_list_result = $_custom_list_result['list'];
}
$customer_ids = [];
$customer_stdsubs = [];
foreach ($custom_list_result as $customer) {
$customer_ids[] = $customer['customerid'];
$customer_stdsubs[$customer['customerid']] = $customer['standardsubdomain'];
}
} else {
if (Settings::IsInList('panel.customer_hide_options', 'domains')) {
@@ -1088,19 +1059,21 @@ class SubDomains extends ApiCommand implements ResourceEntity
$customer_ids = [
$this->getUserDetail('customerid')
];
$customer_stdsubs = [
$this->getUserDetail('customerid') => $this->getUserDetail('standardsubdomain')
];
}
if (!empty($customer_ids)) {
// prepare select statement
$domains_stmt = Database::prepare("
SELECT COUNT(*) as num_subdom
FROM `" . TABLE_PANEL_DOMAINS . "` `d`
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
AND `d`.`email_only` = '0'
");
$result = Database::pexecute_first($domains_stmt, null, true, true);
if ($result) {
return $this->response($result['num_subdom']);
}
// prepare select statement
$domains_stmt = Database::prepare("
SELECT COUNT(*) as num_subdom
FROM `" . TABLE_PANEL_DOMAINS . "` `d`
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
AND `d`.`email_only` = '0'
AND `d`.`id` NOT IN (" . implode(', ', $customer_stdsubs) . ")
");
$result = Database::pexecute_first($domains_stmt, null, true, true);
if ($result) {
return $this->response($result['num_subdom']);
}
return $this->response(0);
}

View File

@@ -112,11 +112,11 @@ class FroxlorRPC
*
* @return bool
*/
public static function validateAllowedFrom(array $allowed_from, string $remote_addr): bool
private static function validateAllowedFrom(array $allowed_from, string $remote_addr): bool
{
// shorten IP for comparison
$remote_addr = inet_ntop(inet_pton($remote_addr));
// check for direct matches
// check for diret matches
if (in_array($remote_addr, $allowed_from)) {
return true;
}

View File

@@ -25,18 +25,19 @@
namespace Froxlor\Cli;
use PDO;
use Exception;
use Froxlor\Database\Database;
use Froxlor\Froxlor;
use Froxlor\Settings;
use PDO;
use Froxlor\Database\Database;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class CliCommand extends Command
{
protected function validateRequirements(OutputInterface $output, bool $ignore_has_updates = false): int
protected function validateRequirements(InputInterface $input, OutputInterface $output, bool $ignore_has_updates = false): int
{
if (!file_exists(Froxlor::getInstallDir() . '/lib/userdata.inc.php')) {
$output->writeln("<error>Could not find froxlor's userdata.inc.php file. You should use this script only with an installed froxlor system.</>");
@@ -115,11 +116,9 @@ class CliCommand extends Command
return $userinfo;
}
protected function runUpdate(OutputInterface $output, bool $manual = false): int
private function runUpdate(OutputInterface $output): int
{
if (!$manual) {
$output->writeln('<comment>Automatic update is activated and we are going to proceed without any notices</>');
}
$output->writeln('<comment>Automatic update is activated and we are going to proceed without any notices</>');
include_once Froxlor::getInstallDir() . '/lib/tables.inc.php';
define('_CRON_UPDATE', 1);
ob_start([
@@ -128,11 +127,11 @@ class CliCommand extends Command
]);
include_once Froxlor::getInstallDir() . '/install/updatesql.php';
ob_end_flush();
$output->writeln('<info>' . ($manual ? 'Database' : 'Automatic') . ' update done - you should check your settings to be sure everything is fine</>');
$output->writeln('<info>Automatic update done - you should check your settings to be sure everything is fine</>');
return self::SUCCESS;
}
private function cleanUpdateOutput($buffer): string
private function cleanUpdateOutput($buffer)
{
return strip_tags(preg_replace("/<br\W*?\/>/", "\n", $buffer));
}

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

View File

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

View File

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

View File

@@ -25,20 +25,19 @@
namespace Froxlor\Cli;
use Exception;
use PDO;
use Froxlor\Froxlor;
use Froxlor\FileDir;
use Froxlor\Settings;
use Froxlor\FroxlorLogger;
use Froxlor\Database\Database;
use Froxlor\System\Cronjob;
use Froxlor\Cron\TaskId;
use Froxlor\Cron\CronConfig;
use Froxlor\Cron\System\Extrausers;
use Froxlor\Cron\TaskId;
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
use Froxlor\System\Cronjob;
use PDO;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
final class MasterCron extends CliCommand
@@ -53,17 +52,15 @@ final class MasterCron extends CliCommand
$this->setDescription('Regulary perform tasks created by froxlor');
$this->addArgument('job', InputArgument::IS_ARRAY, 'Job(s) to run');
$this->addOption('run-task', 'r', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Run a specific task [1 = re-generate configs, 4 = re-generate dns zones, 10 = re-set quotas, 99 = re-create cron.d-file]')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces given job or, if none given, forces re-generating of config-files (webserver, nameserver, etc.)')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces re-generating of config-files (webserver, nameserver, etc.)')
->addOption('debug', 'd', InputOption::VALUE_NONE, 'Output debug information about what is going on to STDOUT.')
->addOption('no-fork', 'N', InputOption::VALUE_NONE, 'Do not fork to background (traffic cron only).');
}
/**
* @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int
protected function execute(InputInterface $input, OutputInterface $output)
{
$result = $this->validateRequirements($output);
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
if ($result != self::SUCCESS) {
// requirements failed, exit
@@ -74,13 +71,12 @@ final class MasterCron extends CliCommand
// handle force option
if ($input->getOption('force')) {
if (empty($jobs) || in_array('tasks', $jobs)) {
Cronjob::inserttask(TaskId::REBUILD_VHOST);
Cronjob::inserttask(TaskId::REBUILD_DNS);
Cronjob::inserttask(TaskId::CREATE_QUOTA);
Cronjob::inserttask(TaskId::REBUILD_CRON);
$jobs[] = 'tasks';
}
// rebuild all config files
Cronjob::inserttask(TaskId::REBUILD_VHOST);
Cronjob::inserttask(TaskId::REBUILD_DNS);
Cronjob::inserttask(TaskId::CREATE_QUOTA);
Cronjob::inserttask(TaskId::REBUILD_CRON);
array_push($jobs, 'tasks');
define('CRON_IS_FORCED', 1);
}
// handle debug option
@@ -95,9 +91,9 @@ final class MasterCron extends CliCommand
if ($input->getOption('run-task')) {
$tasks_to_run = $input->getOption('run-task');
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, [1, 4, 10, 99])) {
Cronjob::inserttask($ttr);
$jobs[] = 'tasks';
array_push($jobs, 'tasks');
} else {
$output->writeln('<comment>Unknown task number "' . $ttr . '"</>');
}
@@ -143,12 +139,12 @@ final class MasterCron extends CliCommand
$cronfile::run();
}
// free the lockfile
$this->unlockJob();
$this->unlockJob($job);
}
}
// regenerate nss-extrausers files / invalidate nscd cache (if used)
$this->refreshUsers((int)$tasks_cnt['jobcnt']);
$this->refreshUsers((int) $tasks_cnt['jobcnt']);
// we have to check the system's last guid with every cron run
// in case the admin installed new software which added a new user
@@ -160,25 +156,40 @@ final class MasterCron extends CliCommand
CronConfig::checkCrondConfigurationFile();
// check for old/compatibility cronjob file
if (file_exists(Froxlor::getInstallDir() . '/scripts/froxlor_master_cronjob.php')) {
@unlink(Froxlor::getInstallDir() . '/scripts/froxlor_master_cronjob.php');
@rmdir(Froxlor::getInstallDir() . '/scripts');
if (file_exists(Froxlor::getInstallDir().'/scripts/froxlor_master_cronjob.php')) {
@unlink(Froxlor::getInstallDir().'/scripts/froxlor_master_cronjob.php');
@rmdir(Froxlor::getInstallDir().'/scripts');
}
// reset cronlog-flag if set to "once"
if ((int)Settings::Get('logger.log_cron') == 1) {
if ((int) Settings::Get('logger.log_cron') == 1) {
FroxlorLogger::getInstanceOf()->setCronLog(0);
}
// clean up possible old login-links
Database::query("DELETE FROM `" . TABLE_PANEL_LOGINLINKS . "` WHERE `valid_until` < UNIX_TIMESTAMP()");
return $result;
}
/**
* @throws Exception
*/
private function refreshUsers(int $jobcount = 0)
{
if ($jobcount > 0) {
if (Settings::Get('system.nssextrausers') == 1) {
Extrausers::generateFiles($this->cronLog);
return;
}
// clear NSCD cache if using fcgid or fpm, #1570 - not needed for nss-extrausers
if ((Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && Settings::Get('system.nssextrausers') == 0) {
$false_val = false;
FileDir::safe_exec('nscd -i passwd 1> /dev/null', $false_val, [
'>'
]);
FileDir::safe_exec('nscd -i group 1> /dev/null', $false_val, [
'>'
]);
}
}
}
private function validateOwnership(OutputInterface $output)
{
// when using fcgid or fpm for froxlor-vhost itself, we have to check
@@ -205,44 +216,6 @@ final class MasterCron extends CliCommand
$output->writeln('OK');
}
private function lockJob(string $job, OutputInterface $output): bool
{
$this->lockFile = '/run/lock/froxlor_' . $job . '.lock';
if (file_exists($this->lockFile)) {
$jobinfo = json_decode(file_get_contents($this->lockFile), true);
$check_pid_return = null;
// get status of process
system("kill -CHLD " . (int)$jobinfo['pid'] . " 1> /dev/null 2> /dev/null", $check_pid_return);
if ($check_pid_return == 1) {
// Process does not seem to run, most likely it has died
$this->unlockJob();
} else {
// cronjob still running, output info and stop
$output->writeln([
'<comment>Job "' . $jobinfo['job'] . '" is currently running.',
'Started: ' . date('d.m.Y H:i', (int)$jobinfo['startts']),
'PID: ' . $jobinfo['pid'] . '</>'
]);
return false;
}
}
$jobinfo = [
'job' => $job,
'startts' => time(),
'pid' => getmypid()
];
file_put_contents($this->lockFile, json_encode($jobinfo));
return true;
}
private function unlockJob(): bool
{
return @unlink($this->lockFile);
}
private function getCronModule(string $cronname, OutputInterface $output)
{
$upd_stmt = Database::prepare("
@@ -258,24 +231,41 @@ final class MasterCron extends CliCommand
return false;
}
private function refreshUsers(int $jobcount = 0)
private function lockJob(string $job, OutputInterface $output): bool
{
if ($jobcount > 0) {
if (Settings::Get('system.nssextrausers') == 1) {
Extrausers::generateFiles($this->cronLog);
return;
}
// clear NSCD cache if using fcgid or fpm, #1570 - not needed for nss-extrausers
if ((Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && Settings::Get('system.nssextrausers') == 0) {
$false_val = false;
FileDir::safe_exec('nscd -i passwd 1> /dev/null', $false_val, [
'>'
]);
FileDir::safe_exec('nscd -i group 1> /dev/null', $false_val, [
'>'
$this->lockFile = '/run/lock/froxlor_' . $job . '.lock';
if (file_exists($this->lockFile)) {
$jobinfo = json_decode(file_get_contents($this->lockFile), true);
$check_pid_return = null;
// get status of process
system("kill -CHLD " . (int)$jobinfo['pid'] . " 1> /dev/null 2> /dev/null", $check_pid_return);
if ($check_pid_return == 1) {
// Process does not seem to run, most likely it has died
$this->unlockJob($job);
} else {
// cronjob still running, output info and stop
$output->writeln([
'<comment>Job "' . $jobinfo['job'] . '" is currently running.',
'Started: ' . date('d.m.Y H:i', (int) $jobinfo['startts']),
'PID: ' . $jobinfo['pid'] . '</>'
]);
return false;
}
}
$jobinfo = [
'job' => $job,
'startts' => time(),
'pid' => getmypid()
];
file_put_contents($this->lockFile, json_encode($jobinfo));
return true;
}
private function unlockJob(string $job): bool
{
return @unlink($this->lockFile);
}
}

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"');
}
protected function execute(InputInterface $input, OutputInterface $output): int
protected function execute(InputInterface $input, OutputInterface $output)
{
$result = $this->validateRequirements($output);
$result = $this->validateRequirements($input, $output);
if ($result == self::SUCCESS) {
if ((int)Settings::Get('phpfpm.enabled') == 1) {
@@ -89,7 +89,7 @@ final class PhpSessionclean extends CliCommand
if (count($paths_to_clean) > 0) {
foreach ($paths_to_clean as $ptc) {
// find all files older than maxlifetime and delete them
// find all files older then maxlifetime and delete them
FileDir::safe_exec("find -O3 \"" . $ptc . "\" -ignore_readdir_race -depth -mindepth 1 -name 'sess_*' -type f -cmin \"+" . $maxlifetime . "\" -delete");
}
}

View File

@@ -26,12 +26,14 @@
namespace Froxlor\Cli;
use Exception;
use Froxlor\Froxlor;
use Symfony\Component\Console\Input\InputArgument;
use PDO;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Froxlor\Database\Database;
use Froxlor\Froxlor;
final class RunApiCommand extends CliCommand
{
@@ -46,9 +48,11 @@ final class RunApiCommand extends CliCommand
$this->addOption('show-params', 's', InputOption::VALUE_NONE, 'Show possible parameters for given api-command (given command will *not* be called)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
protected function execute(InputInterface $input, OutputInterface $output)
{
$result = $this->validateRequirements($output);
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
require Froxlor::getInstallDir() . '/lib/functions.php';
@@ -106,9 +110,6 @@ final class RunApiCommand extends CliCommand
return self::SUCCESS;
}
/**
* @throws Exception
*/
private function validateCommand(string $command): array
{
$command = explode(".", $command);

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');
}
protected function execute(InputInterface $input, OutputInterface $output): int
protected function execute(InputInterface $input, OutputInterface $output)
{
$result = $this->validateRequirements($output);
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
if ($result == self::SUCCESS && $input->getOption('list') == false && $input->getOption('switch') == false) {
$output->writeln('<error>Either --list or --switch option must be provided. Nothing to do, exiting.</>');
@@ -81,7 +83,6 @@ final class SwitchServerIp extends CliCommand
$ip_list = $input->getOption('switch');
$has_error = false;
$ips_to_switch = [];
foreach ($ip_list as $ips_combo) {
$ip_pair = explode(",", $ips_combo);
if (count($ip_pair) != 2) {

View File

@@ -27,9 +27,9 @@ namespace Froxlor\Cli;
use Exception;
use Froxlor\Froxlor;
use Froxlor\Install\AutoUpdate;
use Froxlor\Install\Update;
use Froxlor\Settings;
use Froxlor\Install\Update;
use Froxlor\Install\AutoUpdate;
use Froxlor\System\Mailer;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -44,7 +44,6 @@ final class UpdateCommand extends CliCommand
$this->setName('froxlor:update');
$this->setDescription('Check for newer version and update froxlor');
$this->addOption('check-only', 'c', InputOption::VALUE_NONE, 'Only check for newer version and exit')
->addOption('database', 'd', InputOption::VALUE_NONE, 'Only run database updates in case updates are done via apt or manually.')
->addOption('mail-notify', 'm', InputOption::VALUE_NONE, 'Additionally inform administrator via email if a newer version was found')
->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for download, extract and database-update, just do it (if not --check-only is set)')
->addOption('integer-return', 'i', InputOption::VALUE_NONE, 'Return integer whether a new version is available or not (implies --check-only). Useful for programmatic use.');
@@ -54,35 +53,7 @@ final class UpdateCommand extends CliCommand
{
$result = self::SUCCESS;
// database update only
if ($input->getOption('database')) {
$result = $this->validateRequirements($output, true);
if ($result == self::SUCCESS) {
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
$output->writeln('<info>' . lng('updates.dbupdate_required') . '</>');
if ($input->getOption('check-only')) {
$output->writeln('<comment>Doing nothing because of "check-only" flag.</>');
} else {
$yestoall = $input->getOption('yes-to-all') !== false;
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
if ($yestoall || $helper->ask($input, $output, $question)) {
$result = $this->runUpdate($output, true);
}
}
return $result;
}
$output->writeln('<info>' . lng('update.noupdatesavail', (Settings::Get('system.update_channel') == 'testing' ? lng('serversettings.uc_testing') . ' ' : '')) . '</>');
}
return $result;
}
$result = $this->validateRequirements($output);
if ($result != self::SUCCESS) {
// requirements failed, exit
return $result;
}
$result = $this->validateRequirements($input, $output);
require Froxlor::getInstallDir() . '/lib/functions.php';
@@ -100,7 +71,7 @@ final class UpdateCommand extends CliCommand
}
// there is a new version
if ($input->getOption('check-only')) {
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel').' ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') == 'testing' ? 'testing ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
} else {
$text = lng('admin.newerversionavailable') . ' ' . lng('admin.newerversiondetails', [AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
}
@@ -175,7 +146,7 @@ final class UpdateCommand extends CliCommand
$result = self::SUCCESS;
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
if ($yestoall || $helper->ask($input, $output, $question)) {
$result = $this->runUpdate($output, true);
$result = $this->updateDatabase();
}
} else {
$errmsg = 'error.autoupdate_' . $auex;
@@ -199,7 +170,7 @@ final class UpdateCommand extends CliCommand
if ($input->getOption('mail-notify')) {
$last_check_version = Settings::Get('system.update_notify_last');
if (Update::versionInUpdate($last_check_version, AutoUpdate::getFromResult('version'))) {
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel').' ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') == 'testing' ? 'testing ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
$mail = new Mailer(true);
$mail->Body = $text;
$mail->Subject = "[froxlor] " . lng('update.notify_subject');
@@ -211,4 +182,22 @@ final class UpdateCommand extends CliCommand
}
}
}
private function updateDatabase()
{
include_once Froxlor::getInstallDir() . '/lib/tables.inc.php';
define('_CRON_UPDATE', 1);
ob_start([
$this,
'cleanUpdateOutput'
]);
include_once Froxlor::getInstallDir() . '/install/updatesql.php';
ob_end_flush();
return self::SUCCESS;
}
private function cleanUpdateOutput($buffer)
{
return strip_tags(preg_replace("/<br\W*?\/>/", "\n", $buffer));
}
}

View File

@@ -26,15 +26,15 @@
namespace Froxlor\Cli;
use Exception;
use Froxlor\Api\Commands\Admins;
use Froxlor\Api\Commands\Customers;
use Froxlor\Froxlor;
use Froxlor\System\Crypt;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Froxlor\Api\Commands\Admins;
use Froxlor\Api\Commands\Customers;
use Froxlor\System\Crypt;
use Froxlor\Froxlor;
final class UserCommand extends CliCommand
{
@@ -50,11 +50,11 @@ final class UserCommand extends CliCommand
->addOption('show-info', 's', InputOption::VALUE_NONE, 'Output information details of given user');
}
protected function execute(InputInterface $input, OutputInterface $output): int
protected function execute(InputInterface $input, OutputInterface $output)
{
$result = self::SUCCESS;
$result = $this->validateRequirements($output);
$result = $this->validateRequirements($input, $output);
require Froxlor::getInstallDir() . '/lib/functions.php';

View File

@@ -48,16 +48,15 @@ final class ValidateAcmeWebroot extends CliCommand
$this->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for confirmation, update files if necessary');
}
/**
* @throws \Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int
protected function execute(InputInterface $input, OutputInterface $output)
{
$result = $this->validateRequirements($output, true);
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output, true);
$io = new SymfonyStyle($input, $output);
if ((int)Settings::Get('system.leenabled') == 0) {
if ((int) Settings::Get('system.leenabled') == 0) {
$io->info("Let's Encrypt not activated in froxlor settings.");
$result = self::INVALID;
}
@@ -95,7 +94,7 @@ final class ValidateAcmeWebroot extends CliCommand
$acmesh_challenge_dir = $recommended;
// need to update the corresponding acme-alias config-file
$acme_alias_file = Settings::Get('system.letsencryptacmeconf');
$sed_params = "s@" . $former_value . "@" . $acmesh_challenge_dir . "@";
$sed_params = "s@".$former_value."@" . $acmesh_challenge_dir . "@";
FileDir::safe_exec('sed -i -e "' . $sed_params . '" ' . escapeshellarg($acme_alias_file));
$count_changes++;
}
@@ -139,6 +138,8 @@ final class ValidateAcmeWebroot extends CliCommand
$io->info("Domain '" . $domain . "' Le_Webroot value is correct");
}
break;
} else {
continue;
}
}
}

View File

@@ -62,8 +62,8 @@ class Bind extends DnsBase
$this->bindconf_file = '# ' . Settings::Get('system.bindconf_directory') . 'froxlor_bind.conf' . "\n" . '# Created ' . date('d.m.Y H:i') . "\n" . '# Do NOT manually edit this file, all changes will be deleted after the next domain change at the panel.' . "\n\n";
foreach ($domains as $domain) {
if ($domain['is_child']) {
// domains that are subdomains to other main domains are handled by recursion within walkDomainList()
if ($domain['ismainbutsubto'] > 0) {
// domains with ismainbutsubto>0 are handled by recursion within walkDomainList()
continue;
}
$this->walkDomainList($domain, $domains);
@@ -114,7 +114,7 @@ class Bind extends DnsBase
$isFroxlorHostname = true;
}
if (!$domain['is_child']) {
if ($domain['ismainbutsubto'] == 0) {
$zoneContent = (string)Dns::createDomainZone(($domain['id'] == 'none') ? $domain : $domain['id'], $isFroxlorHostname);
$domain['zonefile'] = 'domains/' . $domain['domain'] . '.zone';
$zonefile_name = FileDir::makeCorrectFile(Settings::Get('system.bindconf_directory') . '/' . $domain['zonefile']);

View File

@@ -26,7 +26,6 @@
namespace Froxlor\Cron\Dns;
use Froxlor\Database\Database;
use Froxlor\Domain\Domain;
use Froxlor\FileDir;
use Froxlor\FroxlorLogger;
use Froxlor\PhpHelper;
@@ -133,18 +132,16 @@ abstract class DnsBase
");
while ($domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) {
$privkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim' . $domain['dkim_id'] . Settings::Get('dkim.privkeysuffix'));
$pubkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim' . $domain['dkim_id'] . '.public');
$privkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/mx.' . $domain['domain'] . '.' . Settings::Get('dkim.privkeysuffix'));
$pubkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/mx.' . $domain['domain'] . '.public');
if ($domain['dkim_privkey'] == '' || $domain['dkim_pubkey'] == '') {
$max_dkim_id_stmt = Database::query("SELECT MAX(`dkim_id`) as `max_dkim_id` FROM `" . TABLE_PANEL_DOMAINS . "`");
$max_dkim_id = $max_dkim_id_stmt->fetch(PDO::FETCH_ASSOC);
$domain['dkim_id'] = (int)$max_dkim_id['max_dkim_id'] + 1;
$privkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim' . $domain['dkim_id'] . Settings::Get('dkim.privkeysuffix'));
FileDir::safe_exec('openssl genrsa -out ' . escapeshellarg($privkey_filename) . ' ' . Settings::Get('dkim.dkim_keylength'));
$domain['dkim_privkey'] = file_get_contents($privkey_filename);
FileDir::safe_exec("chmod 0640 " . escapeshellarg($privkey_filename));
$pubkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim' . $domain['dkim_id'] . '.public');
FileDir::safe_exec('openssl rsa -in ' . escapeshellarg($privkey_filename) . ' -pubout -outform pem -out ' . escapeshellarg($pubkey_filename));
$domain['dkim_pubkey'] = file_get_contents($pubkey_filename);
FileDir::safe_exec("chmod 0664 " . escapeshellarg($pubkey_filename));
@@ -211,15 +208,16 @@ abstract class DnsBase
`d`.`dkim`,
`d`.`dkim_id`,
`d`.`dkim_pubkey`,
`d`.`ismainbutsubto`,
`c`.`loginname`,
`c`.`guid`
FROM
`" . TABLE_PANEL_DOMAINS . "` `d`
LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`)
WHERE
`d`.`isbinddomain` = '1'
`d`.`isbinddomain` = '1' aND `d`.`deactivated` = '0'
ORDER BY
LENGTH(`d`.`domain`), `d`.`domain` ASC
`d`.`domain` ASC
");
$domains = [];
@@ -241,10 +239,11 @@ abstract class DnsBase
'bindserial' => date('Ymd') . '00',
'dkim' => '0',
'iswildcarddomain' => '1',
'ismainbutsubto' => '0',
'zonefile' => '',
'froxlorhost' => '1'
];
$domains[0] = $hostname_arr;
$domains['none'] = $hostname_arr;
}
if (empty($domains)) {
@@ -256,23 +255,18 @@ abstract class DnsBase
if (!isset($domains[$key]['children'])) {
$domains[$key]['children'] = [];
}
if (!isset($domains[$key]['is_child'])) {
$domains[$key]['is_child'] = false;
}
$children = Domain::getMainSubdomainIds($key);
if (count($children) > 0) {
foreach ($children as $child) {
if (isset($domains[$child])) {
$domains[$key]['children'][] = $domains[$child]['id'];
$domains[$child]['is_child'] = true;
}
if ($domains[$key]['ismainbutsubto'] > 0) {
if (isset($domains[$domains[$key]['ismainbutsubto']])) {
$domains[$domains[$key]['ismainbutsubto']]['children'][] = $domains[$key]['id'];
} else {
$domains[$key]['ismainbutsubto'] = 0;
}
}
}
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, str_pad('domId', 9, ' ') . str_pad('domain', 40, ' ') . "list of child domain ids");
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, str_pad('domId', 9, ' ') . str_pad('domain', 40, ' ') . 'ismainbutsubto ' . str_pad('parent domain', 40, ' ') . "list of child domain ids");
foreach ($domains as $domain) {
$logLine = str_pad($domain['id'], 9, ' ') . str_pad($domain['domain'], 40, ' ') . join(', ', $domain['children']);
$logLine = str_pad($domain['id'], 9, ' ') . str_pad($domain['domain'], 40, ' ') . str_pad($domain['ismainbutsubto'], 15, ' ') . str_pad(((isset($domains[$domain['ismainbutsubto']])) ? $domains[$domain['ismainbutsubto']]['domain'] : '-'), 40, ' ') . join(', ', $domain['children']);
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, $logLine);
}

View File

@@ -50,8 +50,8 @@ class PowerDNS extends DnsBase
}
foreach ($domains as $domain) {
if ($domain['is_child']) {
// domains that are subdomains to other main domains are handled by recursion within walkDomainList()
if ($domain['ismainbutsubto'] > 0) {
// domains with ismainbutsubto>0 are handled by recursion within walkDomainList()
continue;
}
$this->walkDomainList($domain, $domains);
@@ -108,7 +108,7 @@ class PowerDNS extends DnsBase
$isFroxlorHostname = true;
}
if (!$domain['is_child']) {
if ($domain['ismainbutsubto'] == 0) {
$zoneContent = Dns::createDomainZone(($domain['id'] == 'none') ? $domain : $domain['id'], $isFroxlorHostname);
if (count($subzones)) {
foreach ($subzones as $subzone) {

View File

@@ -1,57 +0,0 @@
<?php
namespace Froxlor\Cron;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
trait Forkable
{
public static function runFork($closure, array $attributes = [], int $concurrentChildren = 3)
{
$childrenPids = [];
// We only fork if pcntl_fork is available and nofork flag is not set
if (function_exists('pcntl_fork') && !defined('CRON_NOFORK_FLAG')) {
foreach ($attributes as $closureAttributes) {
// We close the database - connection before we fork, so we don't share resources with the child
Database::needRoot(false); // this forces the connection to be set to null
$pid = pcntl_fork();
if ($pid == -1) {
exit("Error forking...\n");
} elseif ($pid == 0) {
// re-create db
Database::needRoot(false);
$closure($closureAttributes);
exit();
} else {
$childrenPids[] = $pid;
while (count($childrenPids) >= $concurrentChildren) {
foreach ($childrenPids as $key => $pid) {
$res = pcntl_waitpid($pid, $status, WNOHANG);
// If the process has already exited
if ($res == -1 || $res > 0) {
unset($childrenPids[$key]);
}
}
sleep(1);
}
}
}
while (pcntl_waitpid(0, $status) != -1);
} else {
if (!defined('CRON_NOFORK_FLAG')) {
if (extension_loaded('pcntl')) {
$msg = "PHP compiled with pcntl but pcntl_fork function is not available.";
} else {
$msg = "PHP compiled without pcntl.";
}
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, $msg . " Not forking " . self::class . ", this may take a long time!");
}
foreach ($attributes as $closureAttributes) {
$closure($closureAttributes);
}
}
}
}

View File

@@ -129,7 +129,7 @@ class Apache extends HttpConfigBase
if ($row_ipsandports['ssl'] == '0' && Settings::Get('system.le_froxlor_redirect') == '1') {
$is_redirect = true;
// check whether froxlor uses Let's Encrypt and not cert is being generated yet
// or a renewal is ongoing - disable redirect
// or a renew is ongoing - disable redirect
if (Settings::Get('system.le_froxlor_enabled') && ($this->froxlorVhostHasLetsEncryptCert() == false || $this->froxlorVhostLetsEncryptNeedsRenew())) {
$this->virtualhosts_data[$vhosts_filename] .= '# temp. disabled ssl-redirect due to Let\'s Encrypt certificate generation.' . PHP_EOL;
$is_redirect = false;
@@ -515,7 +515,13 @@ class Apache extends HttpConfigBase
*/
private function createStandardDirectoryEntry()
{
$vhosts_filename = $this->getCustomVhostFilename('05_froxlor_dirfix_nofcgid.conf');
$vhosts_folder = '';
if (is_dir(Settings::Get('system.apacheconf_vhost'))) {
$vhosts_folder = FileDir::makeCorrectDir(Settings::Get('system.apacheconf_vhost'));
} else {
$vhosts_folder = FileDir::makeCorrectDir(dirname(Settings::Get('system.apacheconf_vhost')));
}
$vhosts_filename = FileDir::makeCorrectFile($vhosts_folder . '/05_froxlor_dirfix_nofcgid.conf');
if (!isset($this->virtualhosts_data[$vhosts_filename])) {
$this->virtualhosts_data[$vhosts_filename] = '';
@@ -539,7 +545,7 @@ class Apache extends HttpConfigBase
}
$this->virtualhosts_data[$vhosts_filename] .= ' </Directory>' . "\n";
$ocsp_cache_filename = $this->getCustomVhostFilename('03_froxlor_ocsp_cache.conf');
$ocsp_cache_filename = FileDir::makeCorrectFile($vhosts_folder . '/03_froxlor_ocsp_cache.conf');
if (Settings::Get('system.use_ssl') == '1' && Settings::Get('system.apache24') == 1) {
$this->virtualhosts_data[$ocsp_cache_filename] = 'SSLStaplingCache ' . Settings::Get('system.apache24_ocsp_cache_path') . "\n";
} else {
@@ -556,7 +562,14 @@ class Apache extends HttpConfigBase
private function createStandardErrorHandler()
{
if (Settings::Get('defaultwebsrverrhandler.enabled') == '1' && (Settings::Get('defaultwebsrverrhandler.err401') != '' || Settings::Get('defaultwebsrverrhandler.err403') != '' || Settings::Get('defaultwebsrverrhandler.err404') != '' || Settings::Get('defaultwebsrverrhandler.err500') != '')) {
$vhosts_filename = $this->getCustomVhostFilename('05_froxlor_default_errorhandler.conf');
$vhosts_folder = '';
if (is_dir(Settings::Get('system.apacheconf_vhost'))) {
$vhosts_folder = FileDir::makeCorrectDir(Settings::Get('system.apacheconf_vhost'));
} else {
$vhosts_folder = FileDir::makeCorrectDir(dirname(Settings::Get('system.apacheconf_vhost')));
}
$vhosts_filename = FileDir::makeCorrectFile($vhosts_folder . '/05_froxlor_default_errorhandler.conf');
if (!isset($this->virtualhosts_data[$vhosts_filename])) {
$this->virtualhosts_data[$vhosts_filename] = '';
@@ -617,6 +630,29 @@ class Apache extends HttpConfigBase
}
}
/**
* Get the filename for the virtualhost
*/
protected function getVhostFilename($domain, $ssl_vhost = false)
{
if ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && ((int)$domain['ismainbutsubto'] == 0 || Domain::domainMainToSubExists($domain['ismainbutsubto']) == false)) {
$vhost_no = '35';
} elseif ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && (int)$domain['ismainbutsubto'] > 0) {
$vhost_no = '30';
} else {
// number of dots in a domain specifies it's position (and depth of subdomain) starting at 29 going downwards on higher depth
$vhost_no = (string)(30 - substr_count($domain['domain'], ".") + 1);
}
if ($ssl_vhost === true) {
$vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $vhost_no . '_froxlor_ssl_vhost_' . $domain['domain'] . '.conf');
} else {
$vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $vhost_no . '_froxlor_normal_vhost_' . $domain['domain'] . '.conf');
}
return $vhost_filename;
}
/**
* We compose the virtualhost entry for one domain
*/
@@ -1242,7 +1278,7 @@ class Apache extends HttpConfigBase
// >=apache-2.4 enabled?
if (Settings::Get('system.apache24') == '1') {
$mypath_dir = new Directory($row_diroptions['path']);
// only create the' require all granted' if there is no active directory-protection
// only create the require all granted if there is not active directory-protection
// for this path, as this would be the first require and therefore grant all access
if ($mypath_dir->isUserProtected() == false) {
$this->diroptions_data[$diroptions_filename] .= ' Require all granted' . "\n";

View File

@@ -28,7 +28,6 @@ namespace Froxlor\Cron\Http;
use Froxlor\Cron\Http\LetsEncrypt\AcmeSh;
use Froxlor\Cron\Http\Php\Fpm;
use Froxlor\Database\Database;
use Froxlor\Domain\Domain;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\FroxlorLogger;
@@ -188,27 +187,4 @@ class HttpConfigBase
}
return false;
}
/**
* Get the filename for the virtualhost
*/
protected function getVhostFilename(array $domain, bool $ssl_vhost = false, bool $filename_only = false)
{
// number of dots in a domain specifies its position (and depth of subdomain) starting at 35 going downwards on higher depth
$vhost_no = (string)(35 - substr_count($domain['domain'], ".") + 1);
$filename = $vhost_no . '_froxlor_' . ($ssl_vhost ? 'ssl' : 'normal') . '_vhost_' . $domain['domain'] . '.conf';
if ($filename_only) {
return $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;
use Froxlor\Cron\FroxlorCron;
use Froxlor\Cron\TaskId;
use Froxlor\Database\Database;
use Froxlor\Domain\Domain;
use Froxlor\FileDir;
@@ -84,7 +83,7 @@ class AcmeSh extends FroxlorCron
$renew_domains = self::renewDomains(true);
if ($issue_froxlor || !empty($issue_domains) || !empty($renew_froxlor) || $renew_domains) {
// insert task to generate certificates and vhost-configs
Cronjob::inserttask(TaskId::REBUILD_VHOST);
Cronjob::inserttask(1);
}
return 0;
}
@@ -204,7 +203,7 @@ class AcmeSh extends FroxlorCron
// This is easiest done by just creating a new task ;)
if ($changedetected) {
if (self::$no_inserttask == false) {
Cronjob::inserttask(TaskId::REBUILD_VHOST);
Cronjob::inserttask(1);
}
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Let's Encrypt certificates have been updated");
} else {
@@ -557,10 +556,6 @@ EOC;
Settings::Set('system.le_froxlor_enabled', 0);
}
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Let's Encrypt deactivated for domain " . $domain);
if (!defined('CRON_IS_FORCED') && !defined('CRON_DEBUG_FLAG')) {
// email info to admin that lets encrypt has been disabled for this domain
Cronjob::notifyMailToAdmin("Let's Encrypt has been deactivated for domain '" . $domain . "' due to failed dns validation (wrong or no IP address)");
}
}
}
}
@@ -591,20 +586,11 @@ EOC;
$acmesh_cmd .= " --debug";
}
$exit_code = null;
$acme_result = FileDir::safe_exec($acmesh_cmd, $exit_code);
$acme_result = FileDir::safe_exec($acmesh_cmd);
// debug output of acme.sh run
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, implode("\n", $acme_result));
if ($exit_code != 0) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Non-successful exit-code returned :(");
if (!defined('CRON_IS_FORCED') && !defined('CRON_DEBUG_FLAG')) {
Cronjob::notifyMailToAdmin("Let's Encrypt certificate could not be obtained for: " . implode(", ", $domains) . "\n\n" . implode("\n", $acme_result));
}
} else {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Successful exit-code returned - storing certificate");
self::certToDb($certrow, $cronlog, $acme_result);
}
self::certToDb($certrow, $cronlog, $acme_result);
}
}

View File

@@ -336,9 +336,24 @@ class Lighttpd extends HttpConfigBase
$_pos = strrpos($_tmp_path, '/');
$_inc_path = substr($_tmp_path, $_pos + 1);
$filename = self::getVhostFilename($domain, ($ssl == '1'), true);
$vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/vhosts/' . $filename);
$included_vhosts[] = $_inc_path . '/vhosts/' . $filename;
// maindomain
if ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && ((int)$domain['ismainbutsubto'] == 0 || Domain::domainMainToSubExists($domain['ismainbutsubto']) == false)) {
$vhost_no = '50';
} elseif ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && (int)$domain['ismainbutsubto'] > 0) {
// sub-but-main-domain
$vhost_no = '51';
} else {
// subdomains
// number of dots in a domain specifies it's position (and depth of subdomain) starting at 89 going downwards on higher depth
$vhost_no = (string)(90 - substr_count($domain['domain'], ".") + 1);
}
if ($ssl == '1') {
$vhost_no = (int)$vhost_no += 10;
}
$vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/vhosts/' . $vhost_no . '_' . $domain['domain'] . '.conf');
$included_vhosts[] = $_inc_path . '/vhosts/' . $vhost_no . '_' . $domain['domain'] . '.conf';
}
if (!isset($this->lighttpd_data[$vhost_filename])) {

View File

@@ -467,6 +467,26 @@ class Nginx extends HttpConfigBase
}
}
protected function getVhostFilename($domain, $ssl_vhost = false)
{
if ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && ((int)$domain['ismainbutsubto'] == 0 || Domain::domainMainToSubExists($domain['ismainbutsubto']) == false)) {
$vhost_no = '35';
} elseif ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && (int)$domain['ismainbutsubto'] > 0) {
$vhost_no = '30';
} else {
// number of dots in a domain specifies it's position (and depth of subdomain) starting at 29 going downwards on higher depth
$vhost_no = (string)(30 - substr_count($domain['domain'], ".") + 1);
}
if ($ssl_vhost === true) {
$vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $vhost_no . '_froxlor_ssl_vhost_' . $domain['domain'] . '.conf');
} else {
$vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $vhost_no . '_froxlor_normal_vhost_' . $domain['domain'] . '.conf');
}
return $vhost_filename;
}
protected function getVhostContent($domain, $ssl_vhost = false)
{
if ($ssl_vhost === true && $domain['ssl'] != '1' && $domain['ssl_redirect'] != '1') {
@@ -1161,7 +1181,14 @@ class Nginx extends HttpConfigBase
private function createStandardErrorHandler()
{
if (Settings::Get('defaultwebsrverrhandler.enabled') == '1' && (Settings::Get('defaultwebsrverrhandler.err401') != '' || Settings::Get('defaultwebsrverrhandler.err403') != '' || Settings::Get('defaultwebsrverrhandler.err404') != '' || Settings::Get('defaultwebsrverrhandler.err500') != '')) {
$vhosts_filename = $this->getCustomVhostFilename('05_froxlor_default_errorhandler.conf');
$vhosts_folder = '';
if (is_dir(Settings::Get('system.apacheconf_vhost'))) {
$vhosts_folder = FileDir::makeCorrectDir(Settings::Get('system.apacheconf_vhost'));
} else {
$vhosts_folder = FileDir::makeCorrectDir(dirname(Settings::Get('system.apacheconf_vhost')));
}
$vhosts_filename = FileDir::makeCorrectFile($vhosts_folder . '/05_froxlor_default_errorhandler.conf');
if (!isset($this->nginx_data[$vhosts_filename])) {
$this->nginx_data[$vhosts_filename] = '';

View File

@@ -289,7 +289,7 @@ pm.max_children = 1
}
}
// now check if 'sendmail_path' has not been set in the custom-php.ini
// now check if 'sendmail_path' has not beed set in the custom-php.ini
// if not we use our fallback-default as usual
if (strpos($fpm_config, 'php_admin_value[sendmail_path]') === false) {
$fpm_config .= 'php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f ' . $this->domain['email'] . "\n";

View File

@@ -25,84 +25,122 @@
namespace Froxlor\Cron\System;
use Exception;
use Froxlor\Cron\Forkable;
use Froxlor\Cron\FroxlorCron;
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
class ExportCron extends FroxlorCron
class BackupCron extends FroxlorCron
{
use Forkable;
public static function run()
{
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'ExportCron: started - creating customer data export');
// Check Traffic-Lock
if (function_exists('pcntl_fork')) {
$BackupLock = FileDir::makeCorrectFile(dirname(self::getLockfile()) . "/froxlor_cron_backup.lock");
if (file_exists($BackupLock) && is_numeric($BackupPid = file_get_contents($BackupLock))) {
if (function_exists('posix_kill')) {
$BackupPidStatus = @posix_kill($BackupPid, 0);
} else {
system("kill -CHLD " . $BackupPid . " 1> /dev/null 2> /dev/null", $BackupPidStatus);
$BackupPidStatus = !$BackupPidStatus;
}
if ($BackupPidStatus) {
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Backup run already in progress');
return 1;
}
}
// Create Backup Log and Fork
// We close the database - connection before we fork, so we don't share resources with the child
Database::needRoot(false); // this forces the connection to be set to null
$BackupPid = pcntl_fork();
// Parent
if ($BackupPid) {
file_put_contents($BackupLock, $BackupPid);
// unnecessary to recreate database connection here
return 0;
} elseif ($BackupPid == 0) {
// Child
posix_setsid();
// re-create db
Database::needRoot(false);
} else {
// Fork failed
return 1;
}
} else {
if (extension_loaded('pcntl')) {
$msg = "PHP compiled with pcntl but pcntl_fork function is not available.";
} else {
$msg = "PHP compiled without pcntl.";
}
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, $msg . " Not forking backup-cron, this may take a long time!");
}
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'BackupCron: started - creating customer backup');
$result_tasks_stmt = Database::query("
SELECT * FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20' ORDER BY `id` ASC
");
$all_jobs = $result_tasks_stmt->fetchAll();
if (!empty($all_jobs)) {
self::runFork([self::class, 'handle'], $all_jobs);
}
}
public static function handle(array $row)
{
$del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `id` = :id");
$cronlog = FroxlorLogger::getInstanceOf();
if ($row['data'] != '') {
$row['data'] = json_decode($row['data'], true);
}
if (is_array($row['data'])) {
if (isset($row['data']['customerid']) && isset($row['data']['loginname']) && isset($row['data']['destdir'])) {
$row['data']['destdir'] = FileDir::makeCorrectDir($row['data']['destdir']);
$customerdocroot = FileDir::makeCorrectDir(Settings::Get('system.documentroot_prefix') . '/' . $row['data']['loginname'] . '/');
// create folder if not exists
if (!file_exists($row['data']['destdir']) && $row['data']['destdir'] != '/' && $row['data']['destdir'] != Settings::Get('system.documentroot_prefix') && $row['data']['destdir'] != $customerdocroot) {
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating data export destination path for customer: ' . escapeshellarg($row['data']['destdir']));
FileDir::safe_exec('mkdir -p ' . escapeshellarg($row['data']['destdir']));
}
self::createCustomerExport($row['data'], $customerdocroot, $cronlog);
$all_jobs = $result_tasks_stmt->fetchAll();
foreach ($all_jobs as $row) {
if ($row['data'] != '') {
$row['data'] = json_decode($row['data'], true);
}
if (is_array($row['data'])) {
if (isset($row['data']['customerid']) && isset($row['data']['loginname']) && isset($row['data']['destdir'])) {
$row['data']['destdir'] = FileDir::makeCorrectDir($row['data']['destdir']);
$customerdocroot = FileDir::makeCorrectDir(Settings::Get('system.documentroot_prefix') . '/' . $row['data']['loginname'] . '/');
// create folder if not exists
if (!file_exists($row['data']['destdir']) && $row['data']['destdir'] != '/' && $row['data']['destdir'] != Settings::Get('system.documentroot_prefix') && $row['data']['destdir'] != $customerdocroot) {
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'Creating backup-destination path for customer: ' . escapeshellarg($row['data']['destdir']));
FileDir::safe_exec('mkdir -p ' . escapeshellarg($row['data']['destdir']));
}
self::createCustomerBackup($row['data'], $customerdocroot, $cronlog);
}
}
// remove entry
Database::pexecute($del_stmt, [
'id' => $row['id']
]);
}
// remove entry
Database::pexecute($del_stmt, [
'id' => $row['id']
]);
if (function_exists('pcntl_fork')) {
@unlink($BackupLock);
die();
}
}
/**
* depending on the give choice, the customers web-data, email-data and databases are being exported
* depending on the give choice, the customers web-data, email-data and databases are being backup'ed
*
* @param array $data
*
* @return void
*
* @throws Exception
*/
private static function createCustomerExport($data = null, $customerdocroot = null, &$cronlog = null)
private static function createCustomerBackup($data = null, $customerdocroot = null, &$cronlog = null)
{
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'Creating data export for user "' . $data['loginname'] . '"');
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating Backup for user "' . $data['loginname'] . '"');
// create tmp folder
$tmpdir = FileDir::makeCorrectDir($data['destdir'] . '/.tmp/');
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating tmp-folder "' . $tmpdir . '"');
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mkdir -p ' . escapeshellarg($tmpdir));
FileDir::safe_exec('mkdir -p ' . escapeshellarg($tmpdir));
$create_export_tar_data = "";
$create_backup_tar_data = "";
// MySQL databases
if ($data['dump_dbs'] == 1) {
if ($data['backup_dbs'] == 1) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating mysql-folder "' . FileDir::makeCorrectDir($tmpdir . '/mysql') . '"');
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/mysql')));
FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/mysql')));
@@ -114,7 +152,7 @@ class ExportCron extends FroxlorCron
]);
$has_dbs = false;
$current_dbserver = -1;
$current_dbserver = null;
while ($row = $sel_stmt->fetch()) {
// Get sql_root data for the specific database-server the database resides on
if ($current_dbserver != $row['dbserver']) {
@@ -142,18 +180,16 @@ class ExportCron extends FroxlorCron
}
if ($has_dbs) {
$create_export_tar_data .= './mysql ';
$create_backup_tar_data .= './mysql ';
}
if (file_exists($mysqlcnf_file)) {
unlink($mysqlcnf_file);
}
unlink($mysqlcnf_file);
unset($sql_root);
}
// E-mail data
if ($data['dump_mail'] == 1) {
if ($data['backup_mail'] == 1) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating mail-folder "' . FileDir::makeCorrectDir($tmpdir . '/mail') . '"');
FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/mail')));
@@ -173,41 +209,28 @@ class ExportCron extends FroxlorCron
if (!empty($tar_file_list)) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfvz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/mail/' . $data['loginname'] . '-mail.tar.gz')) . ' -C ' . escapeshellarg($mail_homedir) . ' ' . trim($tar_file_list));
FileDir::safe_exec('tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/mail/' . $data['loginname'] . '-mail.tar.gz')) . ' -C ' . escapeshellarg($mail_homedir) . ' ' . trim($tar_file_list));
$create_export_tar_data .= './mail ';
$create_backup_tar_data .= './mail ';
}
}
// Web data
if ($data['dump_web'] == 1) {
if ($data['backup_web'] == 1) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating web-folder "' . FileDir::makeCorrectDir($tmpdir . '/web') . '"');
FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/web')));
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/web/' . $data['loginname'] . '-web.tar.gz')) . ' --exclude=' . escapeshellarg(str_replace($customerdocroot, "./", FileDir::makeCorrectFile($tmpdir . '/*'))) . ' --exclude=' . escapeshellarg(str_replace($customerdocroot, "./", substr(FileDir::makeCorrectDir($tmpdir), 0, -1))) . ' -C ' . escapeshellarg($customerdocroot) . ' .');
FileDir::safe_exec('tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/web/' . $data['loginname'] . '-web.tar.gz')) . ' --exclude=' . escapeshellarg(str_replace($customerdocroot, "./", FileDir::makeCorrectFile($tmpdir . '/*'))) . ' --exclude=' . escapeshellarg(str_replace($customerdocroot, "./", substr(FileDir::makeCorrectFile($tmpdir), 0, -1))) . ' -C ' . escapeshellarg($customerdocroot) . ' .');
$create_export_tar_data .= './web ';
$create_backup_tar_data .= './web ';
}
if (!empty($create_export_tar_data)) {
// set owner to customer
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> chown -R ' . (int)$data['uid'] . ':' . (int)$data['gid'] . ' ' . escapeshellarg($tmpdir));
FileDir::safe_exec('chown -R ' . (int)$data['uid'] . ':' . (int)$data['gid'] . ' ' . escapeshellarg($tmpdir));
// create tar-file
$export_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-export_' . date('YmdHi', time()) . '.tar.gz' . (!empty($data['pgp_public_key']) ? '.gpg' : ''));
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating export-file "' . $export_file . '"');
if (!empty($data['pgp_public_key'])) {
// pack all archives in tmp-dir to one archive and encrypt it with gpg
$recipient_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-recipients.gpg');
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating recipient-file "' . $recipient_file . '"');
file_put_contents($recipient_file, $data['pgp_public_key']);
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data) . ' | gpg --encrypt --recipient-file ' . escapeshellarg($recipient_file) . ' --output ' . escapeshellarg($export_file) . ' --trust-model always --batch --yes');
FileDir::safe_exec('tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data) . ' | gpg --encrypt --recipient-file ' . escapeshellarg($recipient_file) . ' --output ' . escapeshellarg($export_file) . ' --trust-model always --batch --yes', $return_value, ['|']);
} else {
// pack all archives in tmp-dir to one archive
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg($export_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data));
FileDir::safe_exec('tar cfz ' . escapeshellarg($export_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data));
}
if (!empty($create_backup_tar_data)) {
$backup_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-backup_' . date('YmdHi', time()) . '.tar.gz');
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating backup-file "' . $backup_file . '"');
// pack all archives in tmp-dir to one
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data));
FileDir::safe_exec('tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data));
// move to destination directory
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mv ' . escapeshellarg($export_file) . ' ' . escapeshellarg($data['destdir']));
FileDir::safe_exec('mv ' . escapeshellarg($export_file) . ' ' . escapeshellarg($data['destdir']));
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mv ' . escapeshellarg($backup_file) . ' ' . escapeshellarg($data['destdir']));
FileDir::safe_exec('mv ' . escapeshellarg($backup_file) . ' ' . escapeshellarg($data['destdir']));
// remove tmp-files
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> rm -rf ' . escapeshellarg($tmpdir));
FileDir::safe_exec('rm -rf ' . escapeshellarg($tmpdir));

View File

@@ -46,7 +46,7 @@ class TasksCron extends FroxlorCron
* LOOK INTO TASKS TABLE TO SEE IF THERE ARE ANY UNDONE JOBS
*/
self::$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "TasksCron: Searching for tasks to do");
// no type 99 (regenerate cron.d-file) and no type 20 (customer data export)
// no type 99 (regenerate cron.d-file) and no type 20 (customer backup)
// order by type descending to re-create bind and then webserver at the end
$result_tasks_stmt = Database::query("
SELECT `id`, `type`, `data` FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` <> '99' AND `type` <> '20' ORDER BY `type` DESC, `id` ASC

View File

@@ -82,9 +82,9 @@ final class TaskId
const DELETE_DOMAIN_SSL = 12;
/**
* TYPE=20 CUSTUMER DATA DUMP
* TYPE=20 COSTUMERBACKUP
*/
const CREATE_CUSTOMER_DATADUMP = 20;
const CREATE_CUSTOMER_BACKUP = 20;
/**
* TYPE=99 REGENERATE CRON

View File

@@ -30,7 +30,6 @@ namespace Froxlor\Cron\Traffic;
* @author Froxlor team <team@froxlor.org> (2010-)
*/
use Froxlor\Cron\Forkable;
use Froxlor\Cron\FroxlorCron;
use Froxlor\Database\Database;
use Froxlor\FileDir;
@@ -43,15 +42,51 @@ use PDO;
class TrafficCron extends FroxlorCron
{
use Forkable;
public static function run()
{
self::runFork([self::class, 'handle']);
}
// Check Traffic-Lock
if (function_exists('pcntl_fork') && !defined('CRON_NOFORK_FLAG')) {
$TrafficLock = FileDir::makeCorrectFile("/var/run/froxlor_cron_traffic.lock");
if (file_exists($TrafficLock) && is_numeric($TrafficPid = file_get_contents($TrafficLock))) {
if (function_exists('posix_kill')) {
$TrafficPidStatus = @posix_kill($TrafficPid, 0);
} else {
system("kill -CHLD " . $TrafficPid . " 1> /dev/null 2> /dev/null", $TrafficPidStatus);
$TrafficPidStatus = !$TrafficPidStatus;
}
if ($TrafficPidStatus) {
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Traffic Run already in progress');
return 1;
}
}
// Create Traffic Log and Fork
// We close the database - connection before we fork, so we don't share resources with the child
Database::needRoot(false); // this forces the connection to be set to null
$TrafficPid = pcntl_fork();
// Parent
if ($TrafficPid) {
file_put_contents($TrafficLock, $TrafficPid);
// unnecessary to recreate database connection here
return 0;
} elseif ($TrafficPid == 0) {
// Child
posix_setsid();
// re-create db
Database::needRoot(false);
} else {
// Fork failed
return 1;
}
} elseif (!defined('CRON_NOFORK_FLAG')) {
if (extension_loaded('pcntl')) {
$msg = "PHP compiled with pcntl but pcntl_fork function is not available.";
} else {
$msg = "PHP compiled without pcntl.";
}
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, $msg . " Not forking traffic-cron, this may take a long time!");
}
public static function handle()
{
/**
* TRAFFIC AND DISKUSAGE MEASURE
*/
@@ -576,6 +611,11 @@ class TrafficCron extends FroxlorCron
}
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `value` = UNIX_TIMESTAMP() WHERE `settinggroup` = 'system' AND `varname` = 'last_traffic_run'");
if (function_exists('pcntl_fork') && !defined('CRON_NOFORK_FLAG')) {
@unlink($TrafficLock);
die();
}
}
/**

View File

@@ -25,13 +25,10 @@
namespace Froxlor;
use Exception;
use Froxlor\Api\Commands\Customers;
use Froxlor\Api\Commands\SubDomains;
use Froxlor\Database\Database;
use Froxlor\UI\Collection;
use Froxlor\UI\Response;
use RobThree\Auth\TwoFactorAuthException;
/**
* Class to manage the current user / session
@@ -147,7 +144,7 @@ class CurrentUser
$result_stmt = Database::prepare("
SELECT COUNT(`id`) as emaildomains
FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE `customerid`= :cid AND `isemaildomain` = '1' AND `deactivated` = '0'
WHERE `customerid`= :cid AND `isemaildomain` = '1'
");
$result = Database::pexecute_first($result_stmt, [
"cid" => $_SESSION['userinfo']['customerid']
@@ -157,85 +154,16 @@ class CurrentUser
if (Settings::IsInList('panel.customer_hide_options', 'domains')) {
$addition = false;
} else {
$parentDomainCollection = (new Collection(
SubDomains::class,
$_SESSION['userinfo'],
['sql_search' => [
'd.parentdomainid' => 0,
'd.deactivated' => 0,
'd.id' => ['op' => '<>', 'value' => $_SESSION['userinfo']['standardsubdomain']]
]
]
));
$parentDomainCollection = (new Collection(SubDomains::class, $_SESSION['userinfo'],
['sql_search' => ['d.parentdomainid' => 0]]));
$addition = $parentDomainCollection->count() != 0;
}
} elseif ($resource == 'domains') {
$customerCollection = (new Collection(Customers::class, $_SESSION['userinfo']));
$addition = $customerCollection->count() != 0;
$addition = $customerCollection != 0;
}
return ($_SESSION['userinfo'][$resource . '_used'] < $_SESSION['userinfo'][$resource] || $_SESSION['userinfo'][$resource] == '-1') && $addition;
}
/**
* @throws TwoFactorAuthException
*/
public static function sendOtpEmail()
{
global $mail;
if (self::getField('type_2fa') == 1) {
// generate code
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
$secret = $tfa->createSecret();
$code = $tfa->getCode($secret);
// set code for user
$table = TABLE_PANEL_CUSTOMERS;
$uid = 'customerid';
if (self::isAdmin()) {
$table = TABLE_PANEL_ADMINS;
$uid = 'adminid';
}
$stmt = Database::prepare("UPDATE $table SET `data_2fa` = :d2fa WHERE `$uid` = :uid");
Database::pexecute($stmt, [
"d2fa" => $secret,
"uid" => self::getField($uid)
]);
// build up & send email
$_mailerror = false;
$mailerr_msg = "";
$replace_arr = [
'CODE' => $code
];
$mail_body = html_entity_decode(PhpHelper::replaceVariables(lng('mails.2fa.mailbody'), $replace_arr));
try {
$mail->Subject = lng('mails.2fa.subject');
$mail->AltBody = $mail_body;
$mail->MsgHTML(str_replace("\n", "<br />", $mail_body));
$mail->AddAddress(self::getField('email'), User::getCorrectUserSalutation(self::getData()));
$mail->Send();
} catch (\PHPMailer\PHPMailer\Exception $e) {
$mailerr_msg = $e->errorMessage();
$_mailerror = true;
} catch (Exception $e) {
$mailerr_msg = $e->getMessage();
$_mailerror = true;
}
if ($_mailerror) {
$rstlog = FroxlorLogger::getInstanceOf([
'loginname' => '2fa code-sending'
]);
$rstlog->logAction(FroxlorLogger::ADM_ACTION, LOG_ERR, "Error sending mail: " . $mailerr_msg);
Response::redirectTo('index.php', [
'showmessage' => '4',
'customermail' => self::getField('email')
]);
exit();
}
$mail->ClearAddresses();
}
}
}

View File

@@ -22,7 +22,6 @@
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor\Dns;
use Froxlor\Database\Database;
@@ -183,7 +182,10 @@ class Dns
}
if (Settings::Get('dkim.use_dkim') == '1') {
// check for DKIM content later
self::addRequiredEntry('dkim' . $domain['dkim_id'] . '._domainkey.' . $sub_record, 'TXT', $required_entries);
//self::addRequiredEntry('dkim' . $domain['dkim_id'] . '._domainkey.' . $sub_record, 'TXT', $required_entries);
self::addRequiredEntry('mx._domainkey.' . $sub_record, 'TXT', $required_entries);
//Also add dmarc
self::addRequiredEntry('_dmarc' . $sub_record, 'TXT', $required_entries);
}
}
}
@@ -220,7 +222,10 @@ class Dns
}
if (Settings::Get('dkim.use_dkim') == '1') {
// check for DKIM content later
self::addRequiredEntry('dkim' . $domain['dkim_id'] . '._domainkey', 'TXT', $required_entries);
//self::addRequiredEntry('dkim' . $domain['dkim_id'] . '._domainkey', 'TXT', $required_entries);
self::addRequiredEntry('mx._domainkey', 'TXT', $required_entries);
//Also add dmarc
self::addRequiredEntry('_dmarc', 'TXT', $required_entries);
}
}
@@ -378,10 +383,13 @@ class Dns
if (array_key_exists("TXT", $required_entries)) {
if (Settings::Get('dkim.use_dkim') == '1') {
$dkim_entries = self::generateDkimEntries($domain);
$dmarc_entries = self::generateDmarcEntries($domain);
}
foreach ($required_entries as $type => $records) {
if ($type == 'TXT') {
//$dkim_record = 'dkim' . $domain['dkim_id'] . '._domainkey';
$dkim_record = 'mx._domainkey';
foreach ($records as $record) {
if ($record == '@SPF@') {
// spf for main-domain
@@ -392,9 +400,8 @@ class Dns
$txt_content = Settings::Get('spf.spf_entry');
$sub_record = substr($record, 6);
$zonerecords[] = new DnsEntry($sub_record, 'TXT', self::encloseTXTContent($txt_content));
} elseif (!empty($dkim_entries)) {
} elseif (!empty($dkim_entries) && $record == $dkim_record ) {
// DKIM entries
$dkim_record = 'dkim' . $domain['dkim_id'] . '._domainkey';
if ($record == $dkim_record) {
// dkim for main-domain
// check for multiline entry
@@ -412,7 +419,10 @@ class Dns
}
$zonerecords[] = new DnsEntry($record, 'TXT', self::encloseTXTContent($dkim_entries[0], $multiline));
}
} elseif ($record == '_dmarc' && !empty($dmarc_entries) && $domain['isemaildomain'] == '1') {
$zonerecords[] = new DnsEntry($record, 'TXT', self::encloseTXTContent($dmarc_entries[0]));
}
}
}
}
@@ -523,7 +533,7 @@ class Dns
* @param array $domain
* @return array
*/
private static function generateDkimEntries(array $domain): array
/** private static function generateDkimEntries(array $domain): array
{
$zone_dkim = [];
@@ -569,43 +579,61 @@ class Dns
}
return $zone_dkim;
}
} */
private static function generateDkimEntries(array $domain): array
{
$zone_dkim = [];
if (Settings::Get('dkim.use_dkim') == '1' && $domain['dkim'] == '1' && $domain['dkim_pubkey'] != '') {
// start
$dkim_txt = 'v=DKIM1;k=rsa;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAosq0CmLqEzJJxIHkQwG1Xwk6CSyHHWSDXL9BHCKzY9lJXH7a23PogVlLvUBYaAgBtFOpsKuUCBl+/g6rOqgVXKg0OpYdpgTxZyz1i4NcubGFLifQGnF8ZKpIEDqIzmLI6SbH+9DKwYA319sXAR6feZI4g5bWqF07t/kzA5LN+2V5QnDQ3th++GPRl5rmWF6uoidIRD85UZVEX4s3J1hce0k6tRb2aEozCJaSXHUwyarmbbX/5rky467QQ+45Uy0q9CNaMMu1IX5eybhLRxYXK1k0TfIRJv4FH1UFLlq2QoGC7d+KvLrUabhzQ5wbdZkWuVgLFZ7CL2NegfzO6YeEcQIDAQAB';
$zone_dkim[] = $dkim_txt;
}
return $zone_dkim;
}
private static function generateDmarcEntries(array $domain): array
{
$zone_dmarc = [];
if (Settings::Get('dkim.use_dkim') == '1' && $domain['dkim'] == '1' ){
$dmarc_txt = 'v=DMARC1; p=reject; ruf=mailto:dmarc@'. $domain['domain'] . '; rua=mailto:dmarc@'. $domain['domain'] . '; fo=1; adkim=r; aspf=r; pct=100; rf=afrf; ri=345600;';
$zone_dmarc[] = $dmarc_txt;
}
return $zone_dmarc;
}
/**
* @param string $txt_content
* @param bool $isMultiLine
* @return string
*/
public static function encloseTXTContent(string $txt_content, bool $isMultiLine = false): string
{
// check that TXT content is enclosed in " "
if (!$isMultiLine && Settings::Get('system.dns_server') != 'PowerDNS') {
if (substr($txt_content, 0, 1) != '"') {
$txt_content = '"' . $txt_content;
}
if (substr($txt_content, -1) != '"') {
$txt_content .= '"';
}
}
if (Settings::Get('system.dns_server') == 'PowerDNS') {
// no quotation for PowerDNS
if (substr($txt_content, 0, 1) == '"') {
$txt_content = substr($txt_content, 1);
}
if (substr($txt_content, -1) == '"') {
$txt_content = substr($txt_content, 0, -1);
}
}
return $txt_content;
}
{
// check that TXT content is enclosed in " "
if (! $isMultiLine && Settings::Get('system.dns_server') != 'PowerDNS') {
if (substr($txt_content, 0, 1) != '"') {
$txt_content = '"' . $txt_content;
}
if (substr($txt_content, - 1) != '"') {
$txt_content .= '"';
}
}
if (Settings::Get('system.dns_server') == 'PowerDNS') {
// no quotation for PowerDNS
if (substr($txt_content, 0, 1) == '"') {
$txt_content = substr($txt_content, 1);
}
if (substr($txt_content, - 1) == '"') {
$txt_content = substr($txt_content, 0, - 1);
}
}
return $txt_content;
}
/**
* @param string $email
* @return string
*/
private static function escapeSoaAdminMail(string $email): string
{
$mail_parts = explode("@", $email);
return str_replace(".", "\.", $mail_parts[0]) . "." . $mail_parts[1] . ".";
}
{
$mail_parts = explode("@", $email);
return str_replace(".", "\.", $mail_parts[0]) . "." . $mail_parts[1] . ".";
}
}

View File

@@ -235,30 +235,51 @@ class Domain
}
/**
* get ids of domains that are main domains but a subdomain of another main domain (for DNS)
* check whether a domain has subdomains added as full-domains
* #329
*
* @param int $id main-domain to check
* @param int $id domain-id
*
* @return array
* @return bool
* @throws \Exception
*/
public static function getMainSubdomainIds(int $id): array
public static function domainHasMainSubDomains(int $id): bool
{
$result_stmt = Database::prepare("
SELECT id
FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE
isbinddomain = 1 AND
domain LIKE CONCAT('%.', ( SELECT d.domain FROM `" . TABLE_PANEL_DOMAINS . "` AS d WHERE d.id = :id ))
");
SELECT COUNT(`id`) as `mainsubs` FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE `ismainbutsubto` = :id");
$result = Database::pexecute_first($result_stmt, [
'id' => $id
]);
if ($result && isset($result['mainsubs'])) {
return $result['mainsubs'] > 0;
}
return false;
}
/**
* check whether a subof-domain exists
* #329
*
* @param int $id subof-domain-id
*
* @return bool
* @throws \Exception
*/
public static function domainMainToSubExists(int $id): bool
{
$result_stmt = Database::prepare("
SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `id` = :id");
Database::pexecute($result_stmt, [
'id' => $id
]);
$result = [];
while ($entry = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $entry['id'];
$result = $result_stmt->fetch(PDO::FETCH_ASSOC);
if ($result && isset($result['id'])) {
return $result['id'] > 0;
}
return $result;
return false;
}
/**
@@ -320,15 +341,12 @@ class Domain
* @throws \Exception
*/
public static function triggerLetsEncryptCSRForAliasDestinationDomain(
int $aliasDestinationDomainID,
int $aliasDestinationDomainID,
FroxlorLogger $log
) {
if ($aliasDestinationDomainID > 0) {
$log->logAction(
FroxlorLogger::ADM_ACTION,
LOG_INFO,
"LetsEncrypt CSR triggered for domain ID " . $aliasDestinationDomainID
);
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO,
"LetsEncrypt CSR triggered for domain ID " . $aliasDestinationDomainID);
$upd_stmt = Database::prepare("UPDATE
`" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`
SET
@@ -352,20 +370,15 @@ class Domain
$acmesh = AcmeSh::getAcmeSh();
if (file_exists($acmesh)) {
$certificate_folder = AcmeSh::getWorkingDirFromEnv($domainname);
$certificate_ecc_folder = AcmeSh::getWorkingDirFromEnv($domainname, true);
if (file_exists($certificate_folder) || file_exists($certificate_ecc_folder)) {
if (file_exists($certificate_folder)) {
$params = " --remove -d " . $domainname;
if (file_exists($certificate_ecc_folder)) {
if (Settings::Get('system.leecc') > 0) {
$params .= " --ecc";
}
// run remove command
FileDir::safe_exec($acmesh . $params);
// remove certificates directory
if (file_exists($certificate_folder)) {
FileDir::safe_exec('rm -rf ' . $certificate_folder);
} elseif (file_exists($certificate_ecc_folder)) {
FileDir::safe_exec('rm -rf ' . $certificate_ecc_folder);
}
FileDir::safe_exec('rm -rf ' . $certificate_folder);
}
}
return true;

View File

@@ -26,10 +26,10 @@
namespace Froxlor;
use Exception;
use Froxlor\Customer\Customer;
use Froxlor\Database\Database;
use PDO;
use RecursiveCallbackFilterIterator;
use Froxlor\Customer\Customer;
use Froxlor\Database\Database;
class FileDir
{
@@ -51,12 +51,11 @@ class FileDir
public static function mkDirWithCorrectOwnership(
string $homeDir,
string $dirToCreate,
int $uid,
int $gid,
bool $placeindex = false,
bool $allow_notwithinhomedir = false
): bool
{
int $uid,
int $gid,
bool $placeindex = false,
bool $allow_notwithinhomedir = false
): bool {
if ($homeDir != '' && $dirToCreate != '') {
$homeDir = self::makeCorrectDir($homeDir);
$dirToCreate = self::makeCorrectDir($dirToCreate);
@@ -108,16 +107,15 @@ class FileDir
}
/**
* Returns a correct/secure dirname, means to add slashes at the beginning and at the end if there weren't
* some. If $fixes_homedir is specified,
*
* Function which returns a correct dirname, means to add slashes at the beginning and at the end if there weren't
* some
*
* @param string $dir the path to correct
*
* @return string the corrected path
* @throws Exception
*/
public static function makeCorrectDir(string $dir, string $fixed_homedir = ""): string
public static function makeCorrectDir(string $dir): string
{
if (strlen($dir) > 0) {
$dir = trim($dir);
@@ -127,30 +125,6 @@ class FileDir
if (substr($dir, 0, 1) != '/') {
$dir = '/' . $dir;
}
// if given, check that the target path is within the $fixed_homedir
// by checking each folder for being a symlink and whether it targets
// the customers homedir or points outside of it
if (!empty($fixed_homedir)) {
$to_check = explode("/", substr($dir, strlen($fixed_homedir) + 1), -1);
$check_dir = substr($fixed_homedir, 0, -1);
// Symlink check
foreach ($to_check as $sub_dir) {
$check_dir .= '/' . $sub_dir;
if (is_link($check_dir)) {
$original_target = $check_dir;
$check_dir = readlink($check_dir);
if (substr($check_dir, 0, strlen($fixed_homedir)) != $fixed_homedir) {
throw new Exception("Found symlink pointing outside of customer home directory: " . substr($original_target, strlen($fixed_homedir)));
}
}
}
// check for the path to be within the given homedir
if (substr($dir, 0, strlen($fixed_homedir)) != $fixed_homedir) {
throw new Exception("Target path not within the required customer home directory");
}
}
return self::makeSecurePath($dir);
}
throw new Exception("Cannot validate directory in " . __FUNCTION__ . " which is very dangerous.");
@@ -245,7 +219,7 @@ class FileDir
}
// execute the command and return output
$return = [];
$return = '';
// -------------------------------------------------------------------------------
if ($return_value == false) {
@@ -257,41 +231,6 @@ class FileDir
return $return;
}
/**
* Read unconfigured-domain template from database if exists or fallback to default
*
* @param string $servername
*
* @return string
* @throws Exception
*/
public static function getUnknownDomainTemplate(string $servername = "")
{
$result_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_TEMPLATES . "` WHERE `templategroup` = 'files' AND `varname` = 'unconfigured_html'
");
Database::pexecute($result_stmt);
if (Database::num_rows() > 0) {
$template = $result_stmt->fetch(PDO::FETCH_ASSOC);
$replace_arr = [
'SERVERNAME' => $servername,
];
$tpl_content = PhpHelper::replaceVariables($template['value'], $replace_arr);
$tpl_ext = $template['file_extension'];
} else {
$tpl_ext = 'html';
$unconfiguredPath = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/templates/misc/unconfigured/index.html');
if (file_exists($unconfiguredPath)) {
$tpl_content = file_get_contents($unconfiguredPath);
} else {
$tpl_content = lng('admin.templates.unconfigured_content_fallback');
}
}
$redirect_file = FileDir::makeCorrectFile(Froxlor::getInstallDir().'/notice.'.$tpl_ext);
file_put_contents($redirect_file, $tpl_content);
return basename($redirect_file);
}
/**
* store the default index-file in a given destination folder
*
@@ -306,13 +245,12 @@ class FileDir
public static function storeDefaultIndex(
string $loginname,
string $destination,
$logger = null,
bool $force = false
)
{
$logger = null,
bool $force = false
) {
if ($force || (int)Settings::Get('system.store_index_file_subs') == 1) {
$result_stmt = Database::prepare("
SELECT `t`.`value`, `t`.`file_extension`, `c`.`email` AS `customer_email`, `a`.`email` AS `admin_email`, `c`.`loginname` AS `customer_login`, `a`.`loginname` AS `admin_login`
SELECT `t`.`value`, `c`.`email` AS `customer_email`, `a`.`email` AS `admin_email`, `c`.`loginname` AS `customer_login`, `a`.`loginname` AS `admin_login`
FROM `" . TABLE_PANEL_CUSTOMERS . "` AS `c` INNER JOIN `" . TABLE_PANEL_ADMINS . "` AS `a`
ON `c`.`adminid` = `a`.`adminid`
INNER JOIN `" . TABLE_PANEL_TEMPLATES . "` AS `t`
@@ -335,7 +273,7 @@ class FileDir
// replaceVariables
$htmlcontent = PhpHelper::replaceVariables($template['value'], $replace_arr);
$indexhtmlpath = self::makeCorrectFile($destination . '/index.' . $template['file_extension']);
$indexhtmlpath = self::makeCorrectFile($destination . '/index.' . Settings::Get('system.index_file_extension'));
$index_html_handler = fopen($indexhtmlpath, 'w');
fwrite($index_html_handler, $htmlcontent);
fclose($index_html_handler);
@@ -343,7 +281,7 @@ class FileDir
$logger->logAction(
FroxlorLogger::CRON_ACTION,
LOG_NOTICE,
'Creating \'index.' . $template['file_extension'] . '\' for Customer \'' . $template['customer_login'] . '\' based on template in directory ' . escapeshellarg($indexhtmlpath)
'Creating \'index.' . Settings::Get('system.index_file_extension') . '\' for Customer \'' . $template['customer_login'] . '\' based on template in directory ' . escapeshellarg($indexhtmlpath)
);
}
} else {

View File

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

View File

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

View File

@@ -64,7 +64,7 @@ class IdnaWrapper
*/
public function encode(string $to_encode): string
{
$to_encode = $this->isUtf8($to_encode) ? $to_encode : mb_convert_encoding($to_encode, 'UTF-8');
$to_encode = $this->isUtf8($to_encode) ? $to_encode : utf8_encode($to_encode);
try {
return $this->idna_converter->encode($to_encode);
} catch (InvalidArgumentException $iae) {

View File

@@ -68,12 +68,6 @@ class AutoUpdate
$channel = '';
if (Settings::Get('system.update_channel') == 'testing') {
$channel = '/testing';
} elseif (Settings::Get('system.update_channel') == 'nightly') {
if (empty(Froxlor::BRANDING)) {
$channel = '/nightly.0000000';
} else {
$channel = '/' . substr(Froxlor::BRANDING, 1);
}
}
$latestversion = HttpClient::urlGet(self::UPDATE_URI . Froxlor::VERSION . $channel, true, 3);
} catch (Exception $e) {
@@ -87,7 +81,7 @@ class AutoUpdate
if (!empty(self::$latestversion['error']) && self::$latestversion['error']) {
$result = -1;
self::$lasterror = self::$latestversion['message'];
} elseif (isset(self::$latestversion['has_latest']) && self::$latestversion['has_latest'] == false) {
} else if (isset(self::$latestversion['has_latest']) && self::$latestversion['has_latest'] == false) {
$result = 1;
}
}

View File

@@ -26,14 +26,13 @@
namespace Froxlor\Install;
use Exception;
use Froxlor\Config\ConfigParser;
use Froxlor\Froxlor;
use PDO;
use Froxlor\Install\Install\Core;
use Froxlor\System\IPTools;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\Config\ConfigParser;
use Froxlor\Validate\Validate;
use PDO;
use Froxlor\System\IPTools;
class Install
{
@@ -42,37 +41,34 @@ class Install
public $maxSteps;
public $phpVersion;
public $formfield;
public string $requiredVersion = '7.4.0';
public array $requiredExtensions = ['session', 'ctype', 'xml', 'filter', 'posix', 'mbstring', 'curl', 'gmp', 'json', 'gd'];
public array $suggestedExtensions = ['bcmath', 'zip'];
public array $suggestions = [];
public array $criticals = [];
public array $loadedExtensions;
public array $supportedOS = [];
public array $webserverBackend = [
'php-fpm' => 'PHP-FPM',
'fcgid' => 'FCGID (apache2 only)',
'fcgid' => 'FCGID',
'mod_php' => 'mod_php (not recommended)',
];
public function __construct(array $cliData = [])
{
// set actual php version and extensions
$this->phpVersion = phpversion();
$this->loadedExtensions = get_loaded_extensions();
// get all supported OS
// show list of available distro's
$distros = glob(dirname(__DIR__, 3) . '/lib/configfiles/*.xml');
$distributions_select[''] = '-';
if (in_array('xml', $this->loadedExtensions)) {
// read in all the distros
foreach ($distros as $distribution) {
// get configparser object
$dist = new ConfigParser($distribution);
// store in tmp array
$this->supportedOS[str_replace(".xml", "", strtolower(basename($distribution)))] = $dist->getCompleteDistroName();
}
// sort by distribution name
asort($this->supportedOS);
// read in all the distros
foreach ($distros as $distribution) {
// get configparser object
$dist = new ConfigParser($distribution);
// store in tmp array
$this->supportedOS[str_replace(".xml", "", strtolower(basename($distribution)))] = $dist->getCompleteDistroName();
}
// sort by distribution name
asort($this->supportedOS);
// guess distribution and webserver to preselect in formfield
$webserverBackend = $this->webserverBackend;
@@ -88,6 +84,10 @@ class Install
$this->extendedView = $cliData['extended'] ?? Request::any('extended', 0);
$this->maxSteps = count($this->formfield['install']['sections']);
// set actual php version and extensions
$this->phpVersion = phpversion();
$this->loadedExtensions = get_loaded_extensions();
if (empty($cliData)) {
// set global variables
UI::twig()->addGlobal('install_mode', true);
@@ -99,7 +99,7 @@ class Install
}
// check for url manipulation or wrong step
if ((isset($_SESSION['installation']['stepCompleted']) && ($this->currentStep + 1) > $_SESSION['installation']['stepCompleted'])
if ((isset($_SESSION['installation']['stepCompleted']) && ($this->currentStep + 1) > ($_SESSION['installation']['stepCompleted'] ?? 0))
|| (!isset($_SESSION['installation']['stepCompleted']) && $this->currentStep > 0)
) {
$this->currentStep = isset($_SESSION['installation']['stepCompleted']) ? $_SESSION['installation']['stepCompleted'] + 1 : 1;
@@ -136,7 +136,6 @@ class Install
'section' => $this->formfield['install']['sections']['step' . $this->currentStep] ?? [],
'error' => $error ?? null,
'extended' => $this->extendedView,
'csrf_token' => Froxlor::genSessionId(20),
]);
// output view
@@ -152,14 +151,16 @@ class Install
if ($this->currentStep <= $this->maxSteps) {
// Validate user data
$validatedData = $this->validateRequest($formfield['sections']['step' . $this->currentStep]['fields']);
// Check database connection (
if ($this->currentStep == 1) {
// Check database connection
$this->checkDatabase($validatedData);
} elseif ($this->currentStep == 2) {
// Check validity of admin user data
}
// Check validity of admin user data
elseif ($this->currentStep == 2) {
$this->checkAdminUser($validatedData);
} elseif ($this->currentStep == 3) {
// Check validity of system data
}
// Check validity of system data
elseif ($this->currentStep == 3) {
$this->checkSystem($validatedData);
}
$validatedData['stepCompleted'] = ($this->currentStep < $this->maxSteps) ? $this->currentStep : ($this->maxSteps - 1);
@@ -191,7 +192,7 @@ class Install
private function checkInstallStateFinished(): bool
{
$core = new Core($_SESSION['installation']);
if (isset($_SESSION['installation']['manual_config']) && (int)$_SESSION['installation']['manual_config'] == 1) {
if (isset($_SESSION['installation']['manual_config']) && (int) $_SESSION['installation']['manual_config'] == 1) {
$core->createUserdataConf();
return true;
}
@@ -199,7 +200,7 @@ class Install
$stmt = $pdo->prepare("SELECT `value` FROM `panel_settings` WHERE `settinggroup` = 'panel' AND `varname` = 'is_configured'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result && (int)$result['value'] == 1) {
if ($result && (int) $result['value'] == 1) {
$core->createUserdataConf();
return true;
}
@@ -222,7 +223,7 @@ class Install
}
// check for required extensions
foreach (Requirements::REQUIRED_EXTENSIONS as $requiredExtension) {
foreach ($this->requiredExtensions as $requiredExtension) {
if (in_array($requiredExtension, $this->loadedExtensions)) {
continue;
}
@@ -230,7 +231,7 @@ class Install
}
// check for suggested extensions
foreach (Requirements::SUGGESTED_EXTENSIONS as $suggestedExtension) {
foreach ($this->suggestedExtensions as $suggestedExtension) {
if (in_array($suggestedExtension, $this->loadedExtensions)) {
continue;
}
@@ -249,11 +250,11 @@ class Install
*/
private function getInformationText(): string
{
if (version_compare(Requirements::REQUIRED_VERSION, PHP_VERSION, "<")) {
if (version_compare($this->requiredVersion, PHP_VERSION, "<")) {
$text = lng('install.phpinfosuccess', [$this->phpVersion]);
} else {
$text = lng('install.phpinfowarn', [Requirements::REQUIRED_VERSION]);
$this->criticals[] = lng('install.phpinfoupdate', [$this->phpVersion, Requirements::REQUIRED_VERSION]);
$text = lng('install.phpinfowarn', [$this->requiredVersion]);
$this->criticals[] = lng('install.phpinfoupdate', [$this->phpVersion, $this->requiredVersion]);
}
return $text;
}
@@ -301,9 +302,9 @@ class Install
throw new Exception(lng('install.errors.nov4andnov6ip'));
} elseif (!empty($serveripv4) && (!Validate::validate_ip2($serveripv4, true, '', false, true) || IPTools::is_ipv6($serveripv4))) {
throw new Exception(lng('error.invalidip', [$serveripv4]));
} elseif (!empty($serveripv6) && (!Validate::validate_ip2($serveripv6, true, '', false, true) || !IPTools::is_ipv6($serveripv6))) {
} elseif (!empty($serveripv6) && (!Validate::validate_ip2($serveripv6, true, '', false, true) || IPTools::is_ipv6($serveripv6) == false)) {
throw new Exception(lng('error.invalidip', [$serveripv6]));
} elseif (!Validate::validateDomain($servername)) {
} elseif (!Validate::validateDomain($servername) && !Validate::validateLocalHostname($servername)) {
throw new Exception(lng('install.errors.servernameneedstobevalid'));
} elseif (posix_getpwnam($httpuser) === false) {
throw new Exception(lng('install.errors.websrvuserdoesnotexist'));
@@ -409,7 +410,7 @@ class Install
} else {
$osrf = explode("\n", file_get_contents('/etc/os-release'));
foreach ($osrf as $line) {
$osrfline = explode("=", $line);
$osrfline = explode("\n", $line);
if ($osrfline[0] == 'VERSION_CODENAME') {
$os_dist['VERSION_CODENAME'] = $osrfline[1];
} else if ($osrfline[0] == 'ID') {

View File

@@ -421,7 +421,6 @@ class Core
$this->updateSetting($upd_stmt, $this->validatedData['activate_newsfeed'], 'admin', 'show_news_feed');
$this->updateSetting($upd_stmt, dirname(__FILE__, 5), 'system', 'letsencryptchallengepath');
$this->updateSetting($upd_stmt, dirname(__FILE__, 5) . '/templates/misc/deactivated/', 'system', 'deactivateddocroot');
// insert the lastcronrun to be the installation date
$this->updateSetting($upd_stmt, time(), 'system', 'lastcronrun');

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

@@ -85,7 +85,7 @@ class Update
self::$update_tasks[self::$task_counter]['result'] = 1;
break;
default:
self::$update_tasks[self::$task_counter]['result'] = -1;
self::$update_tasks[self::$task_counter]['result'] = -1;
break;
}

View File

@@ -220,11 +220,8 @@ class PhpHelper
if (is_dir($data_dirname)) {
$data_dirhandle = opendir($data_dirname);
while (false !== ($data_filename = readdir($data_dirhandle))) {
if ($data_filename != '.'
&& $data_filename != '..'
&& $data_filename != ''
&& substr($data_filename, -4) == '.php'
) {
if ($data_filename != '.' && $data_filename != '..' && $data_filename != '' && substr($data_filename,
-4) == '.php') {
$data_files[] = $data_dirname . $data_filename;
}
}
@@ -418,8 +415,8 @@ class PhpHelper
*/
public static function recursive_array_search(
string $needle,
array $haystack,
array &$keys = [],
array $haystack,
array &$keys = [],
string $currentKey = ''
): bool {
foreach ($haystack as $key => $value) {
@@ -461,10 +458,6 @@ class PhpHelper
'directory_password',
'ftp_password',
'mysql_password',
'mysql_root_pass',
'mysql_unprivileged_pass',
'admin_pass',
'admin_pass_confirm',
];
if (!empty($global)) {
$tmp = $global;
@@ -564,17 +557,4 @@ class PhpHelper
}
return $tab . $str;
}
public static function array_merge_recursive_distinct(array &$array1, array &$array2)
{
$merged = $array1;
foreach ($array2 as $key => &$value) {
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
$merged[$key] = self::array_merge_recursive_distinct($merged[$key], $value);
} else {
$merged[$key] = $value;
}
}
return $merged;
}
}

View File

@@ -65,7 +65,7 @@ class SImExporter
public static function export()
{
$settings_definitions = [];
foreach (PhpHelper::loadConfigArrayDir(Froxlor::getInstallDir() . '/actions/admin/settings/')['groups'] as $group) {
foreach (PhpHelper::loadConfigArrayDir('./actions/admin/settings/')['groups'] as $group) {
foreach ($group['fields'] as $field) {
$settings_definitions[$field['settinggroup']][$field['varname']] = $field;
}

View File

@@ -129,8 +129,7 @@ class Settings
{
// set defaults
self::$conf = [
'enable_webupdate' => false,
'disable_otp_security_check' => false,
'enable_webupdate' => false
];
$configfile = Froxlor::getInstallDir() . '/lib/config.inc.php';

View File

@@ -211,10 +211,10 @@ class Cronjob
'type' => TaskId::DELETE_DOMAIN_SSL,
'data' => $data
]);
} elseif ($type == TaskId::CREATE_CUSTOMER_DATADUMP && isset($params[0]) && is_array($params[0])) {
} elseif ($type == TaskId::CREATE_CUSTOMER_BACKUP && isset($params[0]) && is_array($params[0])) {
$data = json_encode($params[0]);
Database::pexecute($ins_stmt, [
'type' => TaskId::CREATE_CUSTOMER_DATADUMP,
'type' => TaskId::CREATE_CUSTOMER_BACKUP,
'data' => $data
]);
}
@@ -310,37 +310,42 @@ class Cronjob
}
/**
* Send notification to system admin via email
* Cronjob function to end a cronjob in a critical condition
* but not without sending a notification mail to the admin
*
* @param string $message
* @param string $subject
*
* @return void
*/
public static function notifyMailToAdmin(string $message, string $subject = "[froxlor] Important notice")
public static function dieWithMail(string $message, string $subject = "[froxlor] Cronjob error")
{
$mail = new Mailer(true);
$mailerror = false;
$mailerr_msg = "";
try {
$mail->Subject = $subject;
$mail->AltBody = $message;
$mail->MsgHTML(nl2br($message));
$mail->AddAddress(Settings::Get('panel.adminmail'), Settings::Get('panel.adminmail_defname'));
$mail->Send();
} catch (\PHPMailer\PHPMailer\Exception $e) {
$mailerr_msg = $e->errorMessage();
$mailerror = true;
} catch (Exception $e) {
$mailerr_msg = $e->getMessage();
$mailerror = true;
if (Settings::Get('system.send_cron_errors') == '1') {
$_mail = new Mailer(true);
$_mailerror = false;
$mailerr_msg = "";
try {
$_mail->Subject = $subject;
$_mail->AltBody = $message;
$_mail->MsgHTML(nl2br($message));
$_mail->AddAddress(Settings::Get('panel.adminmail'), Settings::Get('panel.adminmail_defname'));
$_mail->Send();
} catch (\PHPMailer\PHPMailer\Exception $e) {
$mailerr_msg = $e->errorMessage();
$_mailerror = true;
} catch (Exception $e) {
$mailerr_msg = $e->getMessage();
$_mailerror = true;
}
$_mail->ClearAddresses();
if ($_mailerror) {
echo 'Error sending mail: ' . $mailerr_msg . "\n";
}
}
$mail->ClearAddresses();
if ($mailerror) {
echo 'Error sending mail: ' . $mailerr_msg . "\n";
}
die($message);
}
/**

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