Compare commits

...

67 Commits

Author SHA1 Message Date
Michael Kaufmann
38d94698ce set version to 2.0.12 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-17 20:47:17 +01:00
Michael Kaufmann
5ba28ef599 fix wrong request-parameter reading for table-column mangement
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-17 19:32:27 +01:00
Michael Kaufmann
a3486cc5b3 updated workflow for building/deploying documentation; added missing api-method-description for EmailDomains-API
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-17 17:53:32 +01:00
Michael Kaufmann
5ab322ab1d remove unused required function parameter in nginx cron; set default value for function parameter in lighttpd cron
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-17 13:19:48 +01:00
Michael Kaufmann
4f26bdd535 set version to 2.0.11 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-17 10:26:32 +01:00
Michael Kaufmann
88f76e4355 use bcrypt hash algorithms for htpasswd password hashing instead of the old SHA1
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-17 10:00:08 +01:00
Oliver Rahner
a464d8cb19 fixed duplicated column heading (#1100) 2023-02-15 20:44:57 +01:00
Michael Kaufmann
0f596dce8b fix api parameter issue when empty values are passed
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-15 08:33:49 +01:00
Michael Kaufmann
60270b20b3 backup possible remote-db-server databases in backup-cron
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-14 11:36:39 +01:00
Michael Kaufmann
4003a8d2b6 check for existing fields when setting/updating tablelisting-columns
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-14 11:36:11 +01:00
Michael Kaufmann
89843d6f37 fix referenced quota field for searching/sorting, fixes #1099
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-12 10:08:41 +01:00
Michael Kaufmann
256a52a5da fix setting incorrect acme-challenge path on installation; fixes #1097
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-11 08:54:09 +01:00
Michael Kaufmann
c9b2bfe53c fix pagination for entity-listings with extra parameters
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-11 08:52:43 +01:00
Michael Kaufmann
98cb36327e add SPF/DKIM to Subdomain DNS Zone separately if isemaildomain = 1
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-08 12:37:20 +01:00
Michael Kaufmann
7d23e4882d fix '0 illegal offset type' when changing mysql-access-host setting
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-08 12:36:38 +01:00
Michael Kaufmann
1cc3a1d066 re-add special image_data import for exported custom-logos
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 17:21:39 +01:00
Michael Kaufmann
de0f7d2f01 generalize array-index name of settings to be settingsgroup_varname
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 13:39:09 +01:00
Michael Kaufmann
aa48ffca2b run Form::processForm() when importing settings so the same validations apply if the import file has malicious content
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 13:02:11 +01:00
Michael Kaufmann
802168cb5b forgot to add Validate/Validate to the last commit
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 11:45:57 +01:00
Michael Kaufmann
6ace2e9f3d corrected call to Domain::triggerLetsEncryptCSRForAliasDestinationDomain only if aliasdomain is a valid id; validate registration-date and termination-date only if given
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 11:44:07 +01:00
Michael Kaufmann
0bff360d22 another type fix
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 11:26:13 +01:00
Michael Kaufmann
e300acf109 corrected return type of pexecute_first
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 11:23:21 +01:00
Michael Kaufmann
14d8e12cdc honor deactivated flag for redirects and prepare to use domain.deactivated for domain-specific deactivation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 11:19:31 +01:00
Michael Kaufmann
d29411dba6 backup nginx.conf when configuring service
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 11:15:56 +01:00
Michael Kaufmann
464663877c cleanup function/parameters and add type declarations where possible
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 11:15:19 +01:00
Michael Kaufmann
c3f769d48b remove robots.txt to actually make meta-tag robots work; fixes #1096
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-06 14:27:23 +01:00
Michael Kaufmann
f97536ed02 minor adjustments in customer-email-domain-overview
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-05 18:30:29 +01:00
Michael Kaufmann
7686effc8c new setting to select default value of 'allow api access' for new customers; fixes #1087
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-03 14:16:37 +01:00
Michael Kaufmann
ee8385467b add fallback to system-hostname for faulty http-clients not setting 'Host' in the request
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-03 13:59:12 +01:00
Michael Kaufmann
0a51d97684 add translation for new email domain overview
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-03 13:57:28 +01:00
Michael Kaufmann
67fc762eef fix let's encrypt dns validation check caused by issue in PhpHelper::gethostbynamel6()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-31 17:32:56 +01:00
Michael Kaufmann
8378795f5d Merge branch 'main' of github.com:Froxlor/Froxlor 2023-01-31 09:33:02 +01:00
Michael Kaufmann
98e6f1df4a Merge branch 'main' of github.com:Froxlor/Froxlor 2023-01-31 09:32:21 +01:00
Michael Kaufmann
674e35e5c5 add new EmailDomains API Commands for listing domain/email-usage information; show email-domain overview when customer has >1 domains with email addresses; add EmailDomains to GlobalSearch
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-31 09:31:58 +01:00
Michael Kaufmann
b24ca44e6f fix typos
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-30 08:47:11 +01:00
Michael Kaufmann
e0f7fcd2ef fix awstats path in generated vhost config if speciallogfile=0
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-29 15:58:49 +01:00
Michael Kaufmann
c5bece64ce set version to 2.0.10 for security release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-28 20:07:15 +01:00
Michael Kaufmann
0034681412 fix possible privilege escalation from customer to root when specifying custom error documents in directory-options
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-28 20:00:24 +01:00
Michael Kaufmann
bd5b99dc1c verify cronjob interval is one of the fixed available values
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-28 13:06:44 +01:00
Michael Kaufmann
2feb802094 validate existence of language in admin-templates
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-28 12:16:40 +01:00
Michael Kaufmann
7b08a71c59 add missing use statement for error-reporting to include the dbms version
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-28 11:57:43 +01:00
Michael Kaufmann
2a84e9c120 enforce password requirements set in settings for directory-protection
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-28 11:40:07 +01:00
Michael Kaufmann
d854e8e991 Merge branch 'main' of github.com:Froxlor/Froxlor 2023-01-26 15:23:03 +01:00
Michael Kaufmann
0a363910d6 fix potential infinite loop on errors in cli-installation; fixes #1092
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-26 15:22:39 +01:00
Maurice Preuß (envoyr)
b23d5cd909 merge branch 'main' of github.com:Froxlor/Froxlor 2023-01-25 18:51:03 +01:00
Maurice Preuß (envoyr)
3b753aa69d change session/cookie domain value, this prevents using the _ server_name when using nginx
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-01-25 18:50:49 +01:00
Michael Kaufmann
492cd288bc enhanced themefile validation for non-default themes
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-25 13:09:06 +01:00
Marvin Stark
47938c5082 Update README.md (#1090)
Fixed typo.
2023-01-24 18:56:29 +01:00
Michael Kaufmann
97c4c9a366 Merge branch 'main' of github.com:Froxlor/Froxlor 2023-01-23 09:00:21 +01:00
Michael Kaufmann
d090e48544 validate result of Net_DNS2_Resolver::query (CNAME's are being resolved to their corresponding target A/AAAA addresses); fixes #1089
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-23 08:58:42 +01:00
Michael Kaufmann
314e4407a0 add lasst successful login to table-columns for customer overview
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-22 15:04:19 +01:00
Michael Kaufmann
ed50e03957 Merge remote-tracking branch 'origin/main' into customeremail-overview 2023-01-22 14:03:07 +01:00
Michael Kaufmann
dff7530cc5 include froxlor-vhost in validate-acme-webroot command; fixes #1088
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-22 13:01:20 +01:00
Maurice Preuß (envoyr)
19423c9644 normalize (compress) ip addresses
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-01-20 21:26:24 +01:00
Michael Kaufmann
42b3f1e59d set version to 2.0.9
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-20 18:01:28 +01:00
Michael Kaufmann
1b77632fa8 correctly display config-services command in updater if manual commands are needed
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-19 20:19:43 +01:00
Maurice Preuß (envoyr)
867b7b1390 fix domain variable for gethostbynamel6 function
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-01-18 14:47:25 +01:00
Maurice Preuß (envoyr)
4c6ebde58c adding new dns resolver setting for let's encrypt
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
Co-authored-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-18 13:57:47 +01:00
Michael Kaufmann
1e013d9e9a enhance information on updater regarding acme-challenge (if lets encrypt is enabled and applicable)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-18 11:47:55 +01:00
Michael Kaufmann
c56bc651b9 allow hiding documentation menu for customers via customers-hide-option; use --staging for acme.sh for every test-CA
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-18 08:59:59 +01:00
aPollO2k
6cbdf45a7c Typo fixed in update_2.x.inc.php (#1082)
PHO_EOL => PHP_EOL
2023-01-16 21:32:56 +01:00
Michael Kaufmann
715667e227 Merge branch 'main' of github.com:Froxlor/Froxlor 2023-01-15 23:49:09 +01:00
Michael Kaufmann
41de161555 show exact froxlor:config-services parameter for updater; better checks for changed acme-challenge paths; fix typo in PHP_EOL statement; remove crsf token from config-apply-parameter generation from within the ui
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-15 23:48:37 +01:00
Maurice Preuß (envoyr)
1f1ea370c0 add version to mix-manifest.json and add mix function
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-01-14 21:14:55 +01:00
Michael Kaufmann
ea88d53e39 Merge remote-tracking branch 'origin/main' into customeremail-overview 2023-01-12 09:59:22 +01:00
Michael Kaufmann
61f6a474e4 add emails-overview tablelisting
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 09:59:19 +01:00
Michael Kaufmann
82af9af1e1 group email-domains in overview if there are email addresses for multiple domains
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-11 22:22:39 +01:00
118 changed files with 2233 additions and 1064 deletions

View File

@@ -1,4 +1,4 @@
name: build-docs
name: build-documentation
on:
release:
@@ -11,4 +11,4 @@ jobs:
- env:
GITHUB_TOKEN: ${{ secrets.ORG_GITHUB_TOKEN }}
run: |
gh workflow run --repo Froxlor/Documentation build-docs -f ref=${{github.ref_name}}
gh workflow run --repo Froxlor/Documentation build-and-deploy -f type=tags ref=${{github.ref_name}}

View File

@@ -57,7 +57,7 @@ May be found in [COPYING](COPYING)
### Tarball
https://files.froxlor.org/releases/froxlor-latest.tar.gz [MD5](https://files.froxlor.org/releases/froxlor-latest.tar.gz.md5) [SHA1](https://files.froxlor.org/releases/froxlor-latest.tar.gz.sha1)
### Debian / Ubutnu repository
### Debian / Ubuntu repository
[HowTo](https://docs.froxlor.org/latest/general/installation/apt-package.html)

View File

@@ -269,7 +269,8 @@ return [
'traffic' => lng('menue.traffic.traffic'),
'traffic.http' => lng('menue.traffic.traffic') . " / HTTP",
'traffic.ftp' => lng('menue.traffic.traffic') . " / FTP",
'traffic.mail' => lng('menue.traffic.traffic') . " / Mail"
'traffic.mail' => lng('menue.traffic.traffic') . " / Mail",
'misc.documentation' => lng('admin.documentation'),
],
'save_method' => 'storeSettingField',
'advanced_mode' => true

View File

@@ -109,7 +109,19 @@ return [
'default' => false,
'save_method' => 'storeSettingField'
],
'update_channel' => [
'api_customer_default' => [
'label' => lng('serversettings.api_customer_default'),
'settinggroup' => 'api',
'varname' => 'customer_default',
'type' => 'select',
'default' => 1,
'select_var' => [
1 => lng('panel.yes'),
0 => lng('panel.no')
],
'save_method' => 'storeSettingField'
],
'system_update_channel' => [
'label' => lng('serversettings.update_channel'),
'settinggroup' => 'system',
'varname' => 'update_channel',
@@ -122,7 +134,7 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_validatedomain' => [
'system_validate_domain' => [
'label' => lng('serversettings.validate_domain'),
'settinggroup' => 'system',
'varname' => 'validate_domain',
@@ -307,7 +319,7 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'hide_incompatible_settings' => [
'system_hide_incompatible_settings' => [
'label' => lng('serversettings.hide_incompatible_settings'),
'settinggroup' => 'system',
'varname' => 'hide_incompatible_settings',

View File

@@ -154,7 +154,7 @@ return [
/**
* FCGID
*/
'system_mod_fcgid_enabled_ownvhost' => [
'system_mod_fcgid_ownvhost' => [
'label' => lng('serversettings.mod_fcgid_ownvhost'),
'settinggroup' => 'system',
'varname' => 'mod_fcgid_ownvhost',
@@ -224,7 +224,7 @@ return [
/**
* php-fpm
*/
'system_phpfpm_enabled_ownvhost' => [
'phpfpm_enabled_ownvhost' => [
'label' => lng('phpfpm.ownvhost'),
'settinggroup' => 'phpfpm',
'varname' => 'enabled_ownvhost',
@@ -237,7 +237,7 @@ return [
]),
'requires_reconf' => ['system:php-fpm']
],
'system_phpfpm_httpuser' => [
'phpfpm_vhost_httpuser' => [
'label' => lng('phpfpm.vhost_httpuser'),
'settinggroup' => 'phpfpm',
'varname' => 'vhost_httpuser',
@@ -250,7 +250,7 @@ return [
]),
'requires_reconf' => ['system:php-fpm']
],
'system_phpfpm_httpgroup' => [
'phpfpm_vhost_httpgroup' => [
'label' => lng('phpfpm.vhost_httpgroup'),
'settinggroup' => 'phpfpm',
'varname' => 'vhost_httpgroup',
@@ -263,7 +263,7 @@ return [
]),
'requires_reconf' => ['system:php-fpm']
],
'system_phpfpm_defaultini_ownvhost' => [
'phpfpm_vhost_defaultini' => [
'label' => lng('serversettings.mod_fcgid.defaultini_ownvhost'),
'settinggroup' => 'phpfpm',
'varname' => 'vhost_defaultini',

View File

@@ -60,7 +60,7 @@ return [
'apache2'
]
],
'system_apache_itksupport' => [
'system_apacheitksupport' => [
'label' => lng('serversettings.apache_itksupport'),
'settinggroup' => 'system',
'varname' => 'apacheitksupport',
@@ -229,7 +229,7 @@ return [
'nginx'
]
],
'system_customersslpath' => [
'system_customer_ssl_path' => [
'label' => lng('serversettings.customerssl_directory'),
'settinggroup' => 'system',
'varname' => 'customer_ssl_path',
@@ -287,7 +287,7 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_apache_globaldiropt' => [
'system_apacheglobaldiropt' => [
'label' => lng('serversettings.apache_globaldiropt'),
'settinggroup' => 'system',
'varname' => 'apacheglobaldiropt',

View File

@@ -32,7 +32,7 @@ return [
'title' => lng('admin.sslsettings'),
'icon' => 'fa-solid fa-shield',
'fields' => [
'system_ssl_enabled' => [
'system_use_ssl' => [
'label' => lng('serversettings.ssl.use_ssl'),
'settinggroup' => 'system',
'varname' => 'use_ssl',
@@ -241,6 +241,16 @@ return [
'type' => 'checkbox',
'default' => true,
'save_method' => 'storeSettingField'
],
'system_le_domain_dnscheck_resolver' => [
'label' => lng('serversettings.le_domain_dnscheck_resolver'),
'settinggroup' => 'system',
'varname' => 'le_domain_dnscheck_resolver',
'type' => 'text',
'string_regexp' => '/^(([0-9]+ [a-z0-9\-\._]+, ?)*[0-9]+ [a-z0-9\-\._]+)?$/i',
'string_emptyallowed' => true,
'default' => '',
'save_method' => 'storeSettingField'
]
]
]

View File

@@ -33,7 +33,7 @@ return [
'lighttpd'
],
'fields' => [
'system_mod_fcgid_enabled' => [
'system_mod_fcgid' => [
'label' => lng('serversettings.mod_fcgid'),
'settinggroup' => 'system',
'varname' => 'mod_fcgid',

View File

@@ -31,7 +31,7 @@ return [
'title' => lng('admin.phpfpm_settings'),
'icon' => 'fa-brands fa-php',
'fields' => [
'system_phpfpm_enabled' => [
'phpfpm_enabled' => [
'label' => lng('serversettings.phpfpm'),
'settinggroup' => 'phpfpm',
'varname' => 'enabled',
@@ -45,7 +45,7 @@ return [
'overview_option' => true,
'requires_reconf' => ['http', 'system:php-fpm']
],
'system_phpfpm_defaultini' => [
'phpfpm_defaultini' => [
'label' => lng('serversettings.mod_fcgid.defaultini'),
'settinggroup' => 'phpfpm',
'varname' => 'defaultini',
@@ -57,7 +57,7 @@ return [
],
'save_method' => 'storeSettingField'
],
'system_phpfpm_aliasconfigdir' => [
'phpfpm_aliasconfigdir' => [
'label' => lng('serversettings.phpfpm_settings.aliasconfigdir'),
'settinggroup' => 'phpfpm',
'varname' => 'aliasconfigdir',
@@ -67,7 +67,7 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_phpfpm_tmpdir' => [
'phpfpm_tmpdir' => [
'label' => lng('serversettings.mod_fcgid.tmpdir'),
'settinggroup' => 'phpfpm',
'varname' => 'tmpdir',
@@ -76,7 +76,7 @@ return [
'default' => '/var/customers/tmp/',
'save_method' => 'storeSettingField'
],
'system_phpfpm_peardir' => [
'phpfpm_peardir' => [
'label' => lng('serversettings.mod_fcgid.peardir'),
'settinggroup' => 'phpfpm',
'varname' => 'peardir',
@@ -88,7 +88,7 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_phpfpm_envpath' => [
'phpfpm_envpath' => [
'label' => lng('serversettings.phpfpm_settings.envpath'),
'settinggroup' => 'phpfpm',
'varname' => 'envpath',
@@ -100,7 +100,7 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_phpfpm_fastcgi_ipcdir' => [
'phpfpm_fastcgi_ipcdir' => [
'label' => lng('serversettings.phpfpm_settings.ipcdir'),
'settinggroup' => 'phpfpm',
'varname' => 'fastcgi_ipcdir',
@@ -110,7 +110,7 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_phpfpm_use_mod_proxy' => [
'phpfpm_use_mod_proxy' => [
'label' => lng('phpfpm.use_mod_proxy'),
'settinggroup' => 'phpfpm',
'varname' => 'use_mod_proxy',
@@ -119,7 +119,7 @@ return [
'visible' => Settings::Get('system.apache24'),
'save_method' => 'storeSettingField'
],
'system_phpfpm_ini_flags' => [
'phpfpm_ini_flags' => [
'label' => lng('phpfpm.ini_flags'),
'settinggroup' => 'phpfpm',
'varname' => 'ini_flags',
@@ -128,7 +128,7 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_phpfpm_ini_values' => [
'phpfpm_ini_values' => [
'label' => lng('phpfpm.ini_values'),
'settinggroup' => 'phpfpm',
'varname' => 'ini_values',
@@ -137,7 +137,7 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_phpfpm_ini_admin_flags' => [
'phpfpm_ini_admin_flags' => [
'label' => lng('phpfpm.ini_admin_flags'),
'settinggroup' => 'phpfpm',
'varname' => 'ini_admin_flags',
@@ -146,7 +146,7 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_phpfpm_ini_admin_values' => [
'phpfpm_ini_admin_values' => [
'label' => lng('phpfpm.ini_admin_values'),
'settinggroup' => 'phpfpm',
'varname' => 'ini_admin_values',

View File

@@ -29,7 +29,7 @@ return [
'title' => lng('admin.perl_settings'),
'icon' => 'fa-solid fa-code',
'fields' => [
'perl_path' => [
'system_perl_path' => [
'label' => lng('serversettings.perl_path'),
'settinggroup' => 'system',
'varname' => 'perl_path',
@@ -40,7 +40,7 @@ return [
'lighttpd'
]
],
'system_perl_suexecworkaround' => [
'perl_suexecworkaround' => [
'label' => lng('serversettings.perl.suexecworkaround'),
'settinggroup' => 'perl',
'varname' => 'suexecworkaround',
@@ -51,7 +51,7 @@ return [
'apache2'
]
],
'system_perl_suexeccgipath' => [
'perl_suexecpath' => [
'label' => lng('serversettings.perl.suexeccgipath'),
'settinggroup' => 'perl',
'varname' => 'suexecpath',
@@ -63,7 +63,7 @@ return [
'apache2'
]
],
'perl_server' => [
'serversettings_perl_server' => [
'label' => lng('serversettings.perl_server'),
'settinggroup' => 'serversettings',
'varname' => 'perl_server',

View File

@@ -98,7 +98,7 @@ return [
'default' => 100,
'save_method' => 'storeSettingField'
],
'system_catchall_enabled' => [
'catchall_catchall_enabled' => [
'label' => lng('serversettings.catchall_enabled'),
'settinggroup' => 'catchall',
'varname' => 'catchall_enabled',

View File

@@ -29,7 +29,7 @@ return [
'title' => lng('admin.ftpserversettings'),
'icon' => 'fa-solid fa-arrow-right-arrow-left',
'fields' => [
'ftpserver' => [
'system_ftpserver' => [
'label' => lng('admin.ftpserver'),
'settinggroup' => 'system',
'varname' => 'ftpserver',

View File

@@ -31,7 +31,7 @@ return [
'title' => lng('admin.nameserversettings'),
'icon' => 'fa-solid fa-globe',
'fields' => [
'nameserver_enable' => [
'system_bind_enable' => [
'label' => lng('serversettings.bindenable'),
'settinggroup' => 'system',
'varname' => 'bind_enable',

View File

@@ -31,7 +31,7 @@ return [
'title' => lng('admin.dkimsettings'),
'icon' => 'fa-solid fa-fingerprint',
'fields' => [
'dkim_enabled' => [
'dkim_use_dkim' => [
'label' => lng('dkim.use_dkim'),
'settinggroup' => 'dkim',
'varname' => 'use_dkim',
@@ -40,7 +40,7 @@ return [
'save_method' => 'storeSettingFieldInsertBindTask',
'overview_option' => true
],
'dkim_prefix' => [
'dkim_dkim_prefix' => [
'label' => lng('dkim.dkim_prefix'),
'settinggroup' => 'dkim',
'varname' => 'dkim_prefix',
@@ -59,7 +59,7 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'dkim_domains' => [
'dkim_dkim_domains' => [
'label' => lng('dkim.dkim_domains'),
'settinggroup' => 'dkim',
'varname' => 'dkim_domains',
@@ -68,7 +68,7 @@ return [
'default' => 'domains',
'save_method' => 'storeSettingField'
],
'dkim_dkimkeys' => [
'dkim_dkim_dkimkeys' => [
'label' => lng('dkim.dkim_dkimkeys'),
'settinggroup' => 'dkim',
'varname' => 'dkim_dkimkeys',
@@ -77,7 +77,7 @@ return [
'default' => 'dkim-keys.conf',
'save_method' => 'storeSettingField'
],
'dkim_algorithm' => [
'dkim_dkim_algorithm' => [
'label' => lng('dkim.dkim_algorithm'),
'settinggroup' => 'dkim',
'varname' => 'dkim_algorithm',
@@ -92,7 +92,7 @@ return [
'save_method' => 'storeSettingFieldInsertBindTask',
'advanced_mode' => true
],
'dkim_servicetype' => [
'dkim_dkim_servicetype' => [
'label' => lng('dkim.dkim_servicetype'),
'settinggroup' => 'dkim',
'varname' => 'dkim_servicetype',
@@ -105,7 +105,7 @@ return [
'save_method' => 'storeSettingFieldInsertBindTask',
'advanced_mode' => true
],
'dkim_keylength' => [
'dkim_dkim_keylength' => [
'label' => [
'title' => lng('dkim.dkim_keylength.title'),
'description' => lng('dkim.dkim_keylength.description', [Settings::Get('dkim.dkim_prefix')])
@@ -120,7 +120,7 @@ return [
],
'save_method' => 'storeSettingFieldInsertBindTask'
],
'dkim_notes' => [
'dkim_dkim_notes' => [
'label' => lng('dkim.dkim_notes'),
'settinggroup' => 'dkim',
'varname' => 'dkim_notes',
@@ -130,7 +130,7 @@ return [
'save_method' => 'storeSettingFieldInsertBindTask',
'advanced_mode' => true
],
'dkimrestart_command' => [
'dkim_dkimrestart_command' => [
'label' => lng('dkim.dkimrestart_command'),
'settinggroup' => 'dkim',
'varname' => 'dkimrestart_command',

View File

@@ -29,7 +29,7 @@ return [
'title' => lng('admin.spfsettings'),
'icon' => 'fa-solid fa-clipboard-check',
'fields' => [
'use_spf' => [
'spf_use_spf' => [
'label' => lng('spf.use_spf'),
'settinggroup' => 'spf',
'varname' => 'use_spf',
@@ -38,7 +38,7 @@ return [
'save_method' => 'storeSettingField',
'overview_option' => true
],
'spf_entry' => [
'spf_spf_entry' => [
'label' => lng('spf.spf_entry'),
'settinggroup' => 'spf',
'varname' => 'spf_entry',

View File

@@ -30,7 +30,7 @@ return [
'icon' => 'fa-solid fa-sliders',
'advanced_mode' => true,
'fields' => [
'diskquota_enabled' => [
'system_diskquota_enabled' => [
'label' => lng('serversettings.diskquota_enabled'),
'settinggroup' => 'system',
'varname' => 'diskquota_enabled',
@@ -39,7 +39,7 @@ return [
'save_method' => 'storeSettingField',
'overview_option' => true
],
'diskquota_repquota_path' => [
'system_diskquota_repquota_path' => [
'label' => lng('serversettings.diskquota_repquota_path.description'),
'settinggroup' => 'system',
'varname' => 'diskquota_repquota_path',
@@ -47,7 +47,7 @@ return [
'default' => '/usr/sbin/repquota',
'save_method' => 'storeSettingField'
],
'diskquota_quotatool_path' => [
'system_diskquota_quotatool_path' => [
'label' => lng('serversettings.diskquota_quotatool_path.description'),
'settinggroup' => 'system',
'varname' => 'diskquota_quotatool_path',
@@ -55,7 +55,7 @@ return [
'default' => '/usr/bin/quotatool',
'save_method' => 'storeSettingField'
],
'diskquota_customer_partition' => [
'system_diskquota_customer_partition' => [
'label' => lng('serversettings.diskquota_customer_partition.description'),
'settinggroup' => 'system',
'varname' => 'diskquota_customer_partition',

View File

@@ -92,6 +92,7 @@ if ($userinfo['change_serversettings'] == '1') {
if ($distribution != "" && isset($_POST['finish'])) {
unset($_POST['finish']);
unset($_POST['csrf_token']);
$params = $_POST;
$params['distro'] = $distribution;
$params['system'] = [];
@@ -121,8 +122,6 @@ if ($userinfo['change_serversettings'] == '1') {
'distribution' => $distribution
]);
} else {
// @fixme check set distribution from settings
$cfg_formfield = [
'config' => [
'title' => lng('admin.configfiles.serverconfiguration'),

View File

@@ -253,6 +253,9 @@ if ($action == '') {
if (isset($_POST['prepare']) && $_POST['prepare'] == 'prepare') {
// email templates
$language = htmlentities(Validate::validate($_POST['language'], 'language', '/^[^\r\n\0"\']+$/', 'nolanguageselect'));
if (!array_key_exists($language, $languages)) {
Response::standardError('templatelanguageinvalid');
}
$template = Validate::validate($_POST['template'], 'template');
$result_stmt = Database::prepare("
@@ -288,6 +291,9 @@ if ($action == '') {
} elseif (isset($_POST['send']) && $_POST['send'] == 'send' && !isset($_POST['filesend'])) {
// email templates
$language = htmlentities(Validate::validate($_POST['language'], 'language', '/^[^\r\n\0"\']+$/', 'nolanguageselect'));
if (!array_key_exists($language, $languages)) {
Response::standardError('templatelanguageinvalid');
}
$template = Validate::validate($_POST['template'], 'template');
$subject = Validate::validate($_POST['subject'], 'subject', '/^[^\r\n\0]+$/', 'nosubjectcreate');
$mailbody = Validate::validate($_POST['mailbody'], 'mailbody', '/^[^\0]+$/', 'nomailbodycreate');

View File

@@ -52,7 +52,8 @@
"voku/anti-xss": "^4.1",
"twig/twig": "^3.3",
"erusev/parsedown": "^1.7",
"symfony/console": "^5.4"
"symfony/console": "^5.4",
"pear/net_dns2": "^1.5"
},
"require-dev": {
"phpunit/phpunit": "^9",

53
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "f8370edea3c85bcb7b681926a1fff04e",
"content-hash": "41e7a3bc0e13b47c4f245334b113c3be",
"packages": [
{
"name": "erusev/parsedown",
@@ -198,6 +198,57 @@
],
"time": "2022-06-09T08:53:42+00:00"
},
{
"name": "pear/net_dns2",
"version": "v1.5.3",
"source": {
"type": "git",
"url": "https://github.com/mikepultz/netdns2.git",
"reference": "dc8053772132a855b8bb6193422a959995f3a773"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mikepultz/netdns2/zipball/dc8053772132a855b8bb6193422a959995f3a773",
"reference": "dc8053772132a855b8bb6193422a959995f3a773",
"shasum": ""
},
"require": {
"php": ">=5.4"
},
"require-dev": {
"phpunit/phpunit": "^9"
},
"type": "library",
"autoload": {
"psr-0": {
"Net_DNS2": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Mike Pultz",
"email": "mike@mikepultz.com",
"homepage": "https://mikepultz.com/",
"role": "lead"
}
],
"description": "Native PHP DNS Resolver and Updater Library",
"homepage": "https://netdns2.com/",
"keywords": [
"PEAR",
"dns",
"network"
],
"support": {
"issues": "https://github.com/mikepultz/netdns2/issues",
"source": "https://github.com/mikepultz/netdns2"
},
"time": "2022-11-28T19:16:31+00:00"
},
{
"name": "phpmailer/phpmailer",
"version": "v6.6.3",

View File

@@ -26,9 +26,10 @@
const AREA = 'customer';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\EmailAccounts as EmailAccounts;
use Froxlor\Api\Commands\EmailForwarders as EmailForwarders;
use Froxlor\Api\Commands\Emails as Emails;
use Froxlor\Api\Commands\EmailAccounts;
use Froxlor\Api\Commands\EmailForwarders;
use Froxlor\Api\Commands\Emails;
use Froxlor\Api\Commands\EmailDomains;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
use Froxlor\PhpHelper;
@@ -50,13 +51,50 @@ if (Settings::IsInList('panel.customer_hide_options', 'email') || $userinfo['ema
$id = (int)Request::any('id');
if ($page == 'overview' || $page == 'emails') {
$result_stmt = Database::prepare("
SELECT COUNT(DISTINCT `domainid`) as maildomains FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `customerid`= :cid
");
$domain_count = Database::pexecute_first($result_stmt, [
"cid" => $userinfo['customerid']
]);
if ($domain_count['maildomains'] && $domain_count['maildomains'] > 1) {
try {
$emaildomain_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.emails_overview.php';
$collection = (new Collection(EmailDomains::class, $userinfo))
->withPagination($emaildomain_list_data['emaildomain_list']['columns'],
$emaildomain_list_data['emaildomain_list']['default_sorting']);
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $emaildomain_list_data, 'emaildomain_list'),
'actions_links' => CurrentUser::canAddResource('emails') ? [
[
'href' => $linker->getLink(['section' => 'email', 'page' => $page, 'action' => 'add']),
'label' => lng('emails.emails_add')
]
] : null,
]);
} else {
// only emails for one domain -> show email address listing directly
$page = 'email_domain';
}
}
if ($page == 'email_domain') {
$email_domainid = Request::any('domainid', 0);
if ($action == '') {
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_email::emails");
$sql_search = [];
if ($email_domainid > 0) {
$sql_search = ['sql_search' => ['m.domainid' => ['op' => '=', 'value' => $email_domainid]]];
}
try {
$email_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.emails.php';
$collection = (new Collection(Emails::class, $userinfo))
->withPagination($email_list_data['email_list']['columns'], $email_list_data['email_list']['default_sorting']);
$collection = (new Collection(Emails::class, $userinfo, $sql_search))
->withPagination($email_list_data['email_list']['columns'],
$email_list_data['email_list']['default_sorting']);
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -71,13 +109,22 @@ if ($page == 'overview' || $page == 'emails') {
]);
$emaildomains_count = $result2['emaildomains'];
$actions_links = false;
$actions_links = [];
if ($email_domainid > 0) {
$actions_links[] = [
'class' => 'btn-outline-primary',
'href' => $linker->getLink([
'section' => 'email',
'page' => 'emails',
]),
'label' => lng('emails.back_to_overview'),
'icon' => 'fa-solid fa-reply'
];
}
if (CurrentUser::canAddResource('emails')) {
$actions_links = [
[
'href' => $linker->getLink(['section' => 'email', 'page' => $page, 'action' => 'add']),
'label' => lng('emails.emails_add')
]
$actions_links[] = [
'href' => $linker->getLink(['section' => 'email', 'page' => 'email_domain', 'action' => 'add']),
'label' => lng('emails.emails_add')
];
}
@@ -244,11 +291,13 @@ if ($page == 'overview' || $page == 'emails') {
}
Response::redirectTo($filename, [
'page' => $page,
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id
'id' => $id,
]);
}
} elseif ($page == 'accounts') {
$email_domainid = Request::any('domainid', 0);
if ($action == 'add' && $id != 0) {
if ($userinfo['email_accounts'] == '-1' || ($userinfo['email_accounts_used'] < $userinfo['email_accounts'])) {
try {
@@ -267,7 +316,8 @@ if ($page == 'overview' || $page == 'emails') {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => 'emails',
'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id
]);
@@ -292,7 +342,8 @@ if ($page == 'overview' || $page == 'emails') {
'class' => 'btn-secondary',
'href' => $linker->getLink([
'section' => 'email',
'page' => 'emails',
'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id
]),
@@ -301,7 +352,11 @@ if ($page == 'overview' || $page == 'emails') {
],
[
'class' => 'btn-secondary',
'href' => $linker->getLink(['section' => 'email', 'page' => 'emails']),
'href' => $linker->getLink([
'section' => 'email',
'page' => 'email_domain',
'domainid' => $email_domainid
]),
'label' => lng('menue.email.emails'),
'icon' => 'fa-solid fa-envelope'
]
@@ -332,7 +387,8 @@ if ($page == 'overview' || $page == 'emails') {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => 'emails',
'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id
]);
@@ -350,7 +406,8 @@ if ($page == 'overview' || $page == 'emails') {
'class' => 'btn-secondary',
'href' => $linker->getLink([
'section' => 'email',
'page' => 'emails',
'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id
]),
@@ -359,7 +416,11 @@ if ($page == 'overview' || $page == 'emails') {
],
[
'class' => 'btn-secondary',
'href' => $linker->getLink(['section' => 'email', 'page' => 'emails']),
'href' => $linker->getLink([
'section' => 'email',
'page' => 'email_domain',
'domainid' => $email_domainid
]),
'label' => lng('menue.email.emails'),
'icon' => 'fa-solid fa-envelope'
]
@@ -385,7 +446,8 @@ if ($page == 'overview' || $page == 'emails') {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => 'emails',
'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id
]);
@@ -403,7 +465,8 @@ if ($page == 'overview' || $page == 'emails') {
'class' => 'btn-secondary',
'href' => $linker->getLink([
'section' => 'email',
'page' => 'emails',
'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id
]),
@@ -412,7 +475,11 @@ if ($page == 'overview' || $page == 'emails') {
],
[
'class' => 'btn-secondary',
'href' => $linker->getLink(['section' => 'email', 'page' => 'emails']),
'href' => $linker->getLink([
'section' => 'email',
'page' => 'email_domain',
'domainid' => $email_domainid
]),
'label' => lng('menue.email.emails'),
'icon' => 'fa-solid fa-envelope'
]
@@ -438,7 +505,8 @@ if ($page == 'overview' || $page == 'emails') {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => 'emails',
'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id
]);
@@ -446,12 +514,14 @@ if ($page == 'overview' || $page == 'emails') {
HTML::askYesNoWithCheckbox('email_reallydelete_account', 'admin_customer_alsoremovemail', $filename, [
'id' => $id,
'page' => $page,
'domainid' => $email_domainid,
'action' => $action
], $idna_convert->decode($result['email_full']));
}
}
}
} elseif ($page == 'forwarders') {
$email_domainid = Request::any('domainid', 0);
if ($action == 'add' && $id != 0) {
if ($userinfo['email_forwarders_used'] < $userinfo['email_forwarders'] || $userinfo['email_forwarders'] == '-1') {
try {
@@ -471,7 +541,8 @@ if ($page == 'overview' || $page == 'emails') {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => 'emails',
'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id
]);
@@ -489,7 +560,8 @@ if ($page == 'overview' || $page == 'emails') {
'class' => 'btn-secondary',
'href' => $linker->getLink([
'section' => 'email',
'page' => 'emails',
'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id
]),
@@ -498,7 +570,11 @@ if ($page == 'overview' || $page == 'emails') {
],
[
'class' => 'btn-secondary',
'href' => $linker->getLink(['section' => 'email', 'page' => 'emails']),
'href' => $linker->getLink([
'section' => 'email',
'page' => 'email_domain',
'domainid' => $email_domainid
]),
'label' => lng('menue.email.emails'),
'icon' => 'fa-solid fa-envelope'
]
@@ -540,7 +616,8 @@ if ($page == 'overview' || $page == 'emails') {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => 'emails',
'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id
]);
@@ -549,6 +626,7 @@ if ($page == 'overview' || $page == 'emails') {
'id' => $id,
'forwarderid' => $forwarderid,
'page' => $page,
'domainid' => $email_domainid,
'action' => $action
], $idna_convert->decode($result['email_full']) . ' -> ' . $idna_convert->decode($forwarder));
}

View File

@@ -104,7 +104,7 @@ if ($action == 'add_record' && !empty($_POST)) {
try {
$dns_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/tablelisting.dns.php';
$collection = (new Collection(DomainZones::class, $userinfo, ['id' => $domain_id]))
->withPagination($dns_list_data['dns_list']['columns'], $dns_list_data['dns_list']['default_sorting']);
->withPagination($dns_list_data['dns_list']['columns'], $dns_list_data['dns_list']['default_sorting'], ['domain_id='.$domain_id]);
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}

View File

@@ -33,6 +33,7 @@ use Froxlor\Froxlor;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\Database\Database;
// This file is being included in admin_domains and customer_domains
// and therefore does not need to require lib/init.php

View File

@@ -670,6 +670,7 @@ opcache.validate_timestamps'),
('system', 'leaccount', ''),
('system', 'nssextrausers', '1'),
('system', 'le_domain_dnscheck', '1'),
('system', 'le_domain_dnscheck_resolver', '1.1.1.1'),
('system', 'ssl_protocols', 'TLSv1.2'),
('system', 'tlsv13_cipher_list', ''),
('system', 'honorcipherorder', '0'),
@@ -696,9 +697,10 @@ opcache.validate_timestamps'),
('system', 'distribution', ''),
('system', 'update_channel', 'stable'),
('system', 'updatecheck_data', ''),
('system', 'update_notify_last', '2.0.8'),
('system', 'update_notify_last', '2.0.12'),
('system', 'traffictool', 'goaccess'),
('api', 'enabled', '0'),
('api', 'customer_default', '1'),
('2fa', 'enabled', '1'),
('panel', 'decimal_places', '4'),
('panel', 'adminmail', 'admin@SERVERNAME'),
@@ -740,8 +742,8 @@ opcache.validate_timestamps'),
('panel', 'logo_overridetheme', '0'),
('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'),
('panel', 'version', '2.0.8'),
('panel', 'db_version', '202301120');
('panel', 'version', '2.0.12'),
('panel', 'db_version', '202302030');
DROP TABLE IF EXISTS `panel_tasks`;

View File

@@ -222,7 +222,7 @@ EOF;
file_put_contents($complete_filedir . '/froxlor_master_cronjob.php', $compCron);
Update::lastStepStatus(0);
} else {
$cron_run_cmd = 'chmod +x ' . FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . PHO_EOL;
$cron_run_cmd = 'chmod +x ' . FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . PHP_EOL;
$cron_run_cmd .= FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . ' froxlor:cron -r 99';
Update::lastStepStatus(1, 'manual commands needed', 'Please run the following commands manually:<br><pre>' . $cron_run_cmd . '</pre>');
}
@@ -281,7 +281,7 @@ EOF;
file_put_contents($complete_filedir . '/froxlor_master_cronjob.php', $compCron);
Update::lastStepStatus(0);
} else {
$cron_run_cmd = 'chmod +x ' . FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . PHO_EOL;
$cron_run_cmd = 'chmod +x ' . FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . PHP_EOL;
$cron_run_cmd .= FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . ' froxlor:cron -r 99';
Update::lastStepStatus(1, 'manual commands needed', 'Please run the following commands manually:<br><pre>' . $cron_run_cmd . '</pre>');
}
@@ -323,7 +323,26 @@ if (Froxlor::isDatabaseVersion('202212060')) {
$system_letsencryptchallengepath_upd = isset($_POST['system_letsencryptchallengepath_upd']) ? $_POST['system_letsencryptchallengepath_upd'] : $acmesh_challenge_dir;
if ($acmesh_challenge_dir != $system_letsencryptchallengepath_upd) {
Settings::Set('system.letsencryptchallengepath', $system_letsencryptchallengepath_upd);
Update::lastStepStatus(1, 'manual commands needed', 'Please reconfigure webserver service using <pre>bin/froxlor-cli froxlor:config-services</pre> or adjust the path manually in <pre>' . Settings::Get('system.letsencryptacmeconf') . '</pre>');
if ((int) Settings::Get('system.leenabled') == 1) {
// create JSON string for --apply
$dist = Settings::Get('system.distribution');
$webserver = Settings::Get('system.webserver');
if ($webserver == 'apache2') {
$webserver = 'apache22';
if (Settings::Get('system.apache24')) {
$webserver = 'apache24';
}
}
$apply_json = '{"http":"' . $webserver . '","dns":"x","smtp":"x","mail":"x","ftp":"x","distro":"' . $dist . '","system":[]}';
Update::lastStepStatus(1, 'manual commands needed',
"Please reconfigure webserver service using <pre>bin/froxlor-cli froxlor:config-services --apply='" . $apply_json . "'</pre>" .
'<br>or adjust the path manually in <pre>' . Settings::Get('system.letsencryptacmeconf') . '</pre>' .
'<br><br>In case you already have certificates issued, run the following command to validate and correct the webroot used for renewal:<br>' .
'<pre>bin/froxlor-cli froxlor:validate-acme-webroot</pre><br>'
);
} else {
Update::lastStepStatus(0);
}
} else {
Update::lastStepStatus(0);
}
@@ -344,3 +363,40 @@ if (Froxlor::isFroxlorVersion('2.0.7')) {
Froxlor::updateToVersion('2.0.8');
}
if (Froxlor::isDatabaseVersion('202301120')) {
Update::showUpdateStep("Adding new setting for DNS resolver when using Let's Encrypt");
$system_le_domain_dnscheck_resolver = isset($_POST['system_le_domain_dnscheck_resolver']) ? $_POST['system_le_domain_dnscheck_resolver'] : '1.1.1.1';
Settings::AddNew("system.le_domain_dnscheck_resolver", $system_le_domain_dnscheck_resolver);
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202301180');
}
if (Froxlor::isFroxlorVersion('2.0.8')) {
Update::showUpdateStep("Updating from 2.0.8 to 2.0.9", false);
Froxlor::updateToVersion('2.0.9');
}
if (Froxlor::isFroxlorVersion('2.0.9')) {
Update::showUpdateStep("Updating from 2.0.9 to 2.0.10", false);
Froxlor::updateToVersion('2.0.10');
}
if (Froxlor::isDatabaseVersion('202301180')) {
Update::showUpdateStep("Adding new setting for 'Allow API access' default value for new customers");
Settings::AddNew("api.customer_default", "1");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202302030');
}
if (Froxlor::isFroxlorVersion('2.0.10')) {
Update::showUpdateStep("Updating from 2.0.10 to 2.0.11", false);
Froxlor::updateToVersion('2.0.11');
}
if (Froxlor::isFroxlorVersion('2.0.11')) {
Update::showUpdateStep("Updating from 2.0.11 to 2.0.12", false);
Froxlor::updateToVersion('2.0.12');
}

View File

@@ -74,17 +74,35 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
}
if (Update::versionInUpdate($current_db_version, '202301120')) {
$acmesh_challenge_dir = Settings::Get('system.letsencryptchallengepath');
if ($acmesh_challenge_dir != Froxlor::getInstallDir()) {
$acmesh_challenge_dir = rtrim(FileDir::makeCorrectDir(Settings::Get('system.letsencryptchallengepath')), "/");
$recommended = rtrim(FileDir::makeCorrectDir(Froxlor::getInstallDir()), "/");
if ((int) Settings::Get('system.leenabled') == 1 && $acmesh_challenge_dir != $recommended) {
$has_preconfig = true;
$description = 'ACME challenge docroot from settings differs from the current installation directory.';
$question = '<strong>Validate Let\'s Encrypt challenge path&nbsp;';
$question = '<strong>Validate Let\'s Encrypt challenge path (recommended value: ' . $recommended . ')</strong>';
$return['system_letsencryptchallengepath_upd'] = [
'type' => 'text',
'value' => $acmesh_challenge_dir,
'placeholder' => Froxlor::getInstallDir(),
'value' => $recommended,
'placeholder' => $acmesh_challenge_dir,
'label' => $question,
'prior_infotext' => $description
'prior_infotext' => $description,
'mandatory' => true,
];
}
}
if (Update::versionInUpdate($current_db_version, '202301180')) {
if ((int) Settings::Get('system.leenabled') == 1) {
$has_preconfig = true;
$description = 'Froxlor now supports to set an external DNS resolver for the Let\'s Encrypt pre-check.';
$question = '<strong>Specify a DNS resolver IP (recommended value: 1.1.1.1 or similar)</strong>';
$return['system_le_domain_dnscheck_resolver'] = [
'type' => 'text',
'pattern' => '^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$|^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$|^\s*$',
'value' => '1.1.1.1',
'placeholder' => '1.1.1.1',
'label' => $question,
'prior_infotext' => $description,
];
}
}

View File

@@ -162,7 +162,7 @@ class Ajax
$content = preg_replace("/[\r\n]+/", " ", strip_tags($item->description));
$content = substr($content, 0, 150) . "...";
$items .= UI::twig()->render($this->theme . '/user/newsfeeditem.html.twig', [
$items .= UI::twig()->render(UI::validateThemeTemplate('/user/newsfeeditem.html.twig', $this->theme), [
'link' => $link,
'title' => $title,
'date' => $date,
@@ -201,7 +201,7 @@ class Ajax
$result['last_update_check'] = $uc_data['ts'];
$result['channel'] = Settings::Get('system.update_channel');
$result_rendered = UI::twig()->render($this->theme . '/misc/version_top.html.twig', $result);
$result_rendered = UI::twig()->render(UI::validateThemeTemplate('/misc/version_top.html.twig', $this->theme), $result);
return $this->jsonResponse($result_rendered);
} catch (Exception $e) {
// don't display anything if just not allowed due to permissions
@@ -237,11 +237,11 @@ class Ajax
private function updateTablelisting()
{
$columns = [];
foreach ((Request::any('columns') ?? []) as $value) {
foreach ((Request::post('columns') ?? []) as $value) {
$columns[] = $value;
}
if (!empty($columns)) {
Listing::storeColumnListingForUser([Request::any('listing') => $columns]);
$columns = Listing::storeColumnListingForUser([Request::get('listing') => $columns]);
return $this->jsonResponse($columns);
}
return $this->errorResponse('At least one column must be selected', 406);
@@ -249,7 +249,7 @@ class Ajax
private function resetTablelisting()
{
Listing::deleteColumnListingForUser([Request::any('listing') => []]);
Listing::deleteColumnListingForUser([Request::get('listing') => []]);
return $this->jsonResponse([]);
}

View File

@@ -28,6 +28,7 @@ namespace Froxlor\Ajax;
use Froxlor\Api\Commands\Admins;
use Froxlor\Api\Commands\Customers;
use Froxlor\Api\Commands\Domains;
use Froxlor\Api\Commands\EmailDomains;
use Froxlor\Api\Commands\Emails;
use Froxlor\Api\Commands\FpmDaemons;
use Froxlor\Api\Commands\Ftps;
@@ -267,7 +268,20 @@ class GlobalSearch
'result_format' => [
'title' => ['self', 'getFieldFromResult'],
'title_args' => 'email',
'href' => 'customer_email.php?page=emails&searchfield=m.email&searchtext='
'href' => 'customer_email.php?page=email_domain&domainid={domainid}&searchfield=m.email&searchtext='
]
],
// email-domains
'email_domains' => [
'class' => EmailDomains::class,
'searchfields' => [
'd.domain',
],
'result_key' => 'domain',
'result_format' => [
'title' => ['self', 'getFieldFromResult'],
'title_args' => 'domain',
'href' => 'customer_email.php?page=emails&searchfield=d.domain&searchtext='
]
],
// databases
@@ -326,6 +340,14 @@ class GlobalSearch
if (!isset($result[$entity])) {
$result[$entity] = [];
}
// replacer from result in href
$href_replacer = [];
if (preg_match_all('/\{([a-z]+)\}/', $edata['result_format']['href'], $href_replacer) !== false) {
foreach ($href_replacer[1] as $href_field) {
$href_field_value = self::getFieldFromResult($cresult, $href_field);
$edata['result_format']['href'] = str_replace('{'.$href_field.'}', $href_field_value, $edata['result_format']['href']);
}
}
$result[$entity][] = [
'title' => call_user_func($edata['result_format']['title'], $cresult, ($edata['result_format']['title_args'] ?? null)),
'href' => $edata['result_format']['href'] . $cresult[$edata['result_key']]
@@ -335,7 +357,7 @@ class GlobalSearch
}
} // foreach entity
} // foreach splitted search-term
} // foreach split search-term
}
return $result;
}

View File

@@ -117,6 +117,6 @@ class Api
private function stripcslashesDeep($value)
{
return is_array($value) ? array_map([$this, 'stripcslashesDeep'], $value) : stripcslashes($value);
return is_array($value) ? array_map([$this, 'stripcslashesDeep'], $value) : (!empty($value) ? stripcslashes($value) : $value);
}
}

View File

@@ -32,6 +32,7 @@ use Froxlor\Cron\TaskId;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
use Froxlor\System\Cronjob;
use Froxlor\UI\Response;
use Froxlor\Validate\Validate;
use PDO;
@@ -41,6 +42,14 @@ use PDO;
class Cronjobs extends ApiCommand implements ResourceEntity
{
private array $allowed_intervals = [
'MINUTE',
'HOUR',
'DAY',
'WEEK',
'MONTH'
];
/**
* You cannot add new cronjobs yet.
*/
@@ -118,6 +127,10 @@ class Cronjobs extends ApiCommand implements ResourceEntity
$interval_value = Validate::validate($interval_value, 'interval_value', '/^([0-9]+)$/Di', 'stringisempty', [], true);
$interval_interval = Validate::validate($interval_interval, 'interval_interval', '', '', [], true);
if (!in_array(strtoupper($interval_interval), $this->allowed_intervals)) {
Response::standardError('invalidcronjobintervalvalue', implode(", ", $this->allowed_intervals), true);
}
// put together interval value
$interval = $interval_value . ' ' . strtoupper($interval_interval);

View File

@@ -298,7 +298,7 @@ class Customers extends ApiCommand implements ResourceEntity
$fax = $this->getParam('fax', true, '');
$customernumber = $this->getParam('customernumber', true, '');
$def_language = $this->getParam('def_language', true, Settings::Get('panel.standardlanguage'));
$api_allowed = $this->getBoolParam('api_allowed', true, Settings::Get('api.enabled'));
$api_allowed = $this->getBoolParam('api_allowed', true, (Settings::Get('api.enabled') && Settings::Get('api.customer_default')));
$gender = (int)$this->getParam('gender', true, 0);
$custom_notes = $this->getParam('custom_notes', true, '');
$custom_notes_show = $this->getBoolParam('custom_notes_show', true, 0);

View File

@@ -157,16 +157,15 @@ class DirOptions extends ApiCommand implements ResourceEntity
* this functions validates a given value as ErrorDocument
* refs #267
*
* @param
* string error-document-string
* @param string $errdoc
* @param bool $throw_exception
*
* @return string error-document-string
*
*/
private function correctErrorDocument($errdoc = null, $throw_exception = false)
private function correctErrorDocument(string $errdoc, $throw_exception = false)
{
if ($errdoc !== null && $errdoc != '') {
if (trim($errdoc) != '') {
// not a URL
if ((strtoupper(substr($errdoc, 0, 5)) != 'HTTP:' && strtoupper(substr($errdoc, 0, 6)) != 'HTTPS:') || !Validate::validateUrl($errdoc)) {
// a file
@@ -176,14 +175,14 @@ class DirOptions extends ApiCommand implements ResourceEntity
if (!substr($errdoc, 0, 1) == '/') {
$errdoc = '/' . $errdoc;
}
} else {
} elseif (preg_match('/^"([^\r\n\t\f\0"]+)"$/', $errdoc)) {
// a string (check for ending ")
// string won't work for lighty
if (Settings::Get('system.webserver') == 'lighttpd') {
Response::standardError('stringerrordocumentnotvalidforlighty', '', $throw_exception);
} elseif (substr($errdoc, -1) != '"') {
$errdoc .= '"';
}
} else {
Response::standardError('invaliderrordocumentvalue', '', $throw_exception);
}
} else {
if (Settings::Get('system.webserver') == 'lighttpd') {
@@ -191,7 +190,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
}
}
}
return $errdoc;
return trim($errdoc);
}
/**

View File

@@ -87,7 +87,8 @@ class DirProtections extends ApiCommand implements ResourceEntity
$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);
Validate::validate($password, 'password', '', '', [], true);
$password = Validate::validate($password, 'password', '', '', [], true);
$password = Crypt::validatePassword($password, true);
// check for duplicate usernames for the path
$username_path_check_stmt = Database::prepare("
@@ -244,7 +245,8 @@ class DirProtections extends ApiCommand implements ResourceEntity
// validation
$authname = Validate::validate($authname, 'directory_authname', '/^[a-zA-Z0-9][a-zA-Z0-9\-_ ]+\$?$/', '', [], true);
Validate::validate($password, 'password', '', '', [], true);
$password = Validate::validate($password, 'password', '', '', [], true);
$password = Crypt::validatePassword($password, true);
$upd_query = "";
$upd_params = [

View File

@@ -404,20 +404,25 @@ class Domains extends ApiCommand implements ResourceEntity
$documentroot = $_documentroot;
}
$registration_date = Validate::validate($registration_date, 'registration_date', Validate::REGEX_YYYY_MM_DD, '', [
'0000-00-00',
'0',
''
], true);
if (!is_null($registration_date)) {
$registration_date = Validate::validate($registration_date, 'registration_date',
Validate::REGEX_YYYY_MM_DD, '', [
'0000-00-00',
'0',
''
], true);
}
if ($registration_date == '0000-00-00' || empty($registration_date)) {
$registration_date = null;
}
$termination_date = Validate::validate($termination_date, 'termination_date', Validate::REGEX_YYYY_MM_DD, '', [
'0000-00-00',
'0',
''
], true);
if (!is_null($termination_date)) {
$termination_date = Validate::validate($termination_date, 'termination_date',
Validate::REGEX_YYYY_MM_DD, '', [
'0000-00-00',
'0',
''
], true);
}
if ($termination_date == '0000-00-00' || empty($termination_date)) {
$termination_date = null;
}
@@ -559,7 +564,7 @@ class Domains extends ApiCommand implements ResourceEntity
// validate dns if lets encrypt is enabled to check whether we can use it at all
if ($letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
$domain_ips = PhpHelper::gethostbynamel6($domain);
$domain_ips = PhpHelper::gethostbynamel6($domain, true, Settings::Get('system.le_domain_dnscheck_resolver'));
$selected_ips = $this->getIpsFromIdArray($ssl_ipandports);
if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true);
@@ -1322,19 +1327,25 @@ class Domains extends ApiCommand implements ResourceEntity
$adminid = $result['adminid'];
}
$registration_date = Validate::validate($registration_date, 'registration_date', Validate::REGEX_YYYY_MM_DD, '', [
'0000-00-00',
'0',
''
], true);
if (!is_null($registration_date)) {
$registration_date = Validate::validate($registration_date, 'registration_date',
Validate::REGEX_YYYY_MM_DD, '', [
'0000-00-00',
'0',
''
], true);
}
if ($registration_date == '0000-00-00' || empty($registration_date)) {
$registration_date = null;
}
$termination_date = Validate::validate($termination_date, 'termination_date', Validate::REGEX_YYYY_MM_DD, '', [
'0000-00-00',
'0',
''
], true);
if (!is_null($termination_date)) {
$termination_date = Validate::validate($termination_date, 'termination_date',
Validate::REGEX_YYYY_MM_DD, '', [
'0000-00-00',
'0',
''
], true);
}
if ($termination_date == '0000-00-00' || empty($termination_date)) {
$termination_date = null;
}
@@ -1523,7 +1534,7 @@ class Domains extends ApiCommand implements ResourceEntity
// validate dns if lets encrypt is enabled to check whether we can use it at all
if ($letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
$domain_ips = PhpHelper::gethostbynamel6($result['domain']);
$domain_ips = PhpHelper::gethostbynamel6($result['domain'], true, Settings::Get('system.le_domain_dnscheck_resolver'));
$selected_ips = $this->getIpsFromIdArray($ssl_ipandports);
if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true);
@@ -1974,12 +1985,11 @@ class Domains extends ApiCommand implements ResourceEntity
}
if ($result['wwwserveralias'] != $wwwserveralias || $result['letsencrypt'] != $letsencrypt) {
// or when wwwserveralias or letsencrypt was changed
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger());
if ((int)$aliasdomain === 0) {
// in case the wwwserveralias is set on a main domain, $aliasdomain is 0
// --> the call just above to triggerLetsEncryptCSRForAliasDestinationDomain
// is a noop...let's repeat it with the domain id of the main domain
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($id, $this->logger());
} else {
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger());
}
}
@@ -2138,7 +2148,9 @@ class Domains extends ApiCommand implements ResourceEntity
'domainid' => $id
], true, true);
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger());
if ((int)$result['aliasdomain'] !== 0) {
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger());
}
// remove domains DNS from powerDNS if used, #581
Cronjob::inserttask(TaskId::DELETE_DOMAIN_PDNS, $result['domain']);

View File

@@ -0,0 +1,188 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor\Api\Commands;
use Exception;
use Froxlor\Api\ApiCommand;
use Froxlor\Api\ResourceEntity;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
use PDO;
/**
* @since 2.0
*/
class EmailDomains extends ApiCommand implements ResourceEntity
{
/**
* list all domains with email addresses connected to it.
* If called from an admin, list all domains with email addresses
* connected to it from all customers you are allowed to view, or
* specify id or loginname for one specific customer
*
* @param int $customerid
* optional, admin-only, select email addresses of a specific customer by id
* @param string $loginname
* optional, admin-only, select email addresses 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
* @param int $sql_limit
* optional specify number of results to be returned
* @param int $sql_offset
* optional specify offset for resultset
* @param array $sql_orderby
* optional array with index = fieldname and value = ASC|DESC to order the resultset by one or more
* fields
*
* @access admin, customer
* @return string json-encoded array count|list
* @throws Exception
*/
public function listing()
{
$customer_ids = $this->getAllowedCustomerIds('email');
$result = [];
$query_fields = [];
$result_stmt = Database::prepare("
SELECT DISTINCT d.domain, e.domainid,
COUNT(e.email) as addresses,
IFNULL(SUM(CASE WHEN e.popaccountid > 0 THEN 1 ELSE 0 END), 0) as accounts,
IFNULL(SUM(
CASE
WHEN LENGTH(REPLACE(e.destination, CONCAT(e.email_full, ' '), '')) - LENGTH(REPLACE(REPLACE(e.destination, CONCAT(e.email_full, ' '), ''), ' ', '')) > 0
THEN LENGTH(REPLACE(e.destination, CONCAT(e.email_full, ' '), '')) - LENGTH(REPLACE(REPLACE(e.destination, CONCAT(e.email_full, ' '), ''), ' ', ''))
WHEN e.destination <> e.email_full THEN 1
ELSE 0
END
), 0) as forwarder
FROM `" . TABLE_MAIL_VIRTUAL . "` e
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` d ON d.id = e.domainid
WHERE e.customerid IN (" . implode(", ", $customer_ids) . ") AND d.domain IS NOT NULL " .
$this->getSearchWhere($query_fields,
true) . " GROUP BY e.domainid " . $this->getOrderBy() . $this->getLimit());
Database::pexecute($result_stmt, $query_fields, true, true);
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $row;
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE,
"[API] list email-domains");
return $this->response([
'count' => count($result),
'list' => $result
]);
}
/**
* returns the total number of accessible domains with email addresses connected to
*
* @param int $customerid
* optional, admin-only, select email addresses of a specific customer by id
* @param string $loginname
* optional, admin-only, select email addresses of a specific customer by loginname
*
* @access admin, customer
* @return string json-encoded response message
* @throws Exception
*/
public function listingCount()
{
$customer_ids = $this->getAllowedCustomerIds('email');
$result_stmt = Database::prepare("
SELECT COUNT(DISTINCT d.domain) as num_emaildomains
FROM `" . TABLE_MAIL_VIRTUAL . "` e
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` d ON d.id = e.domainid
WHERE e.customerid IN (" . implode(", ", $customer_ids) . ") AND d.domain IS NOT NULL
");
$result = Database::pexecute_first($result_stmt, null, true, true);
if ($result) {
return $this->response($result['num_emaildomains']);
}
return $this->response(0);
}
/**
* You cannot directly access email-domains
*
* @access admin, customer
* @return string json-encoded array
* @throws Exception
*/
public function get()
{
if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'email')) {
throw new Exception("You cannot access this resource", 405);
}
throw new Exception('You cannot directly access this resource.', 303);
}
/**
* You cannot directly add email-domains
*
* @access admin, customer
* @return string json-encoded array
* @throws Exception
*/
public function add()
{
if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'email')) {
throw new Exception("You cannot access this resource", 405);
}
throw new Exception('You cannot directly add this resource.', 303);
}
/**
* toggle catchall flag of given email address either by id or email-address
*
* @access admin, customer
* @return string json-encoded array
* @throws Exception
*/
public function update()
{
if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'email')) {
throw new Exception("You cannot access this resource", 405);
}
throw new Exception('You cannot directly update this resource.', 303);
}
/**
* You cannot directly delete email-domains
*
* @access admin, customer
* @return string json-encoded array
* @throws Exception
*/
public function delete()
{
if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'email')) {
throw new Exception("You cannot access this resource", 405);
}
throw new Exception('You cannot directly delete this resource.', 303);
}
}

View File

@@ -262,7 +262,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
// validate dns if lets encrypt is enabled to check whether we can use it at all
if ($letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
$our_ips = Domain::getIpsOfDomain($domain_check['id']);
$domain_ips = PhpHelper::gethostbynamel6($completedomain);
$domain_ips = PhpHelper::gethostbynamel6($completedomain, true, Settings::Get('system.le_domain_dnscheck_resolver'));
if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true);
}
@@ -738,7 +738,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
// validate dns if lets encrypt is enabled to check whether we can use it at all
if ($result['letsencrypt'] != $letsencrypt && $letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
$our_ips = Domain::getIpsOfDomain($result['parentdomainid']);
$domain_ips = PhpHelper::gethostbynamel6($result['domain']);
$domain_ips = PhpHelper::gethostbynamel6($result['domain'], true, Settings::Get('system.le_domain_dnscheck_resolver'));
if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true);
}
@@ -1133,7 +1133,9 @@ class SubDomains extends ApiCommand implements ResourceEntity
}
}
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger());
if ((int)$result['aliasdomain'] !== 0) {
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger());
}
// delete domain from table
$stmt = Database::prepare("

View File

@@ -246,7 +246,10 @@ final class InstallCommand extends Command
}
} catch (Exception $e) {
$this->io->error($e->getMessage());
return $this->showStep($step, $extended, $decoded_input);
if ($this->io->confirm('Retry?', empty($decoded_input))) {
return $this->showStep($step, $extended, $decoded_input);
}
return self::FAILURE;
}
if ($step == 3) {
// do actual install with data from $this->formfielddata
@@ -297,7 +300,7 @@ final class InstallCommand extends Command
$json_output = [];
foreach ($fields['install']['sections'] as $section => $section_fields) {
foreach ($section_fields['fields'] as $name => $field) {
if ($name == 'system' || $name == 'manual_config') {
if ($name == 'system' || $name == 'manual_config' || $name == 'target_servername') {
continue;
}
if ($field['type'] == 'text' || $field['type'] == 'email') {

View File

@@ -44,7 +44,7 @@ final class ValidateAcmeWebroot extends CliCommand
protected function configure()
{
$this->setName('froxlor:validate-acme-webroot');
$this->setDescription('Validates the Le_Webroot value is correct for froxlor managed domains with Let\s Encrypt certificate.');
$this->setDescription('Validates the Le_Webroot value is correct for froxlor managed domains with Let\'s Encrypt certificate.');
$this->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for confirmation, update files if necessary');
}
@@ -69,21 +69,29 @@ final class ValidateAcmeWebroot extends CliCommand
$sel_stmt = Database::prepare("SELECT id, domain FROM panel_domains WHERE `letsencrypt` = '1' AND aliasdomain IS NULL ORDER BY id ASC");
Database::pexecute($sel_stmt);
$domains = $sel_stmt->fetchAll(PDO::FETCH_ASSOC);
// check for froxlor-vhost
if (Settings::Get('system.le_froxlor_enabled') == '1') {
$domains[] = [
'id' => 0,
'domain' => Settings::Get('system.hostname')
];
}
$upd_stmt = Database::prepare("UPDATE domain_ssl_settings SET expirationdate=NULL WHERE `domainid` = :did");
$acmesh_dir = dirname(Settings::Get('system.acmeshpath'));
$acmesh_challenge_dir = Settings::Get('system.letsencryptchallengepath');
$acmesh_challenge_dir = rtrim(FileDir::makeCorrectDir(Settings::Get('system.letsencryptchallengepath')), "/");
$recommended = rtrim(FileDir::makeCorrectDir(Froxlor::getInstallDir()), "/");
if ($acmesh_challenge_dir != Froxlor::getInstallDir()) {
if ($acmesh_challenge_dir != $recommended) {
$io->warning([
"ACME challenge docroot from settings differs from the current installation directory.",
"Settings: '" . $acmesh_challenge_dir . "'",
"Default/recommended value: '" . Froxlor::getInstallDir() . "'",
"Default/recommended value: '" . $recommended . "'",
]);
$question = new ConfirmationQuestion('Fix ACME challenge docroot setting? [yes] ', true, '/^(y|j)/i');
if ($yestoall || $helper->ask($input, $output, $question)) {
Settings::Set('system.letsencryptchallengepath', Froxlor::getInstallDir());
Settings::Set('system.letsencryptchallengepath', $recommended);
$former_value = $acmesh_challenge_dir;
$acmesh_challenge_dir = Froxlor::getInstallDir();
$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 . "@";
@@ -138,6 +146,7 @@ final class ValidateAcmeWebroot extends CliCommand
}
if ($count_changes > 0) {
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
$io->info("Changes detected but froxlor has been updated. Inserting task to rebuild vhosts after update.");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
} else {
$question = new ConfirmationQuestion('Changes detected. Force cronjob to refresh certificates? [yes] ', true, '/^(y|j)/i');
@@ -145,6 +154,8 @@ final class ValidateAcmeWebroot extends CliCommand
passthru(FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . ' froxlor:cron -f -d');
}
}
} else {
$io->success("No changes necessary.");
}
}

View File

@@ -148,7 +148,7 @@ class ConfigDisplay
if ($lasttype != '' && $lasttype != $_action['type']) {
$commands = trim($commands);
$numbrows = count(explode("\n", $commands));
$configpage .= UI::twig()->render(self::$theme . '/settings/conf/command.html.twig', [
$configpage .= UI::twig()->render(UI::validateThemeTemplate('/settings/conf/command.html.twig', self::$theme), [
'commands' => $commands,
'numbrows' => $numbrows
]);
@@ -182,7 +182,7 @@ class ConfigDisplay
$commands = trim($commands_pre);
if ($commands != "") {
$numbrows = count(explode("\n", $commands));
$commands_pre = UI::twig()->render(self::$theme . '/settings/conf/command.html.twig', [
$commands_pre = UI::twig()->render(UI::validateThemeTemplate('/settings/conf/command.html.twig', self::$theme), [
'commands' => $commands,
'numbrows' => $numbrows
]);
@@ -190,12 +190,12 @@ class ConfigDisplay
$commands = trim($commands_post);
if ($commands != "") {
$numbrows = count(explode("\n", $commands));
$commands_post = UI::twig()->render(self::$theme . '/settings/conf/command.html.twig', [
$commands_post = UI::twig()->render(UI::validateThemeTemplate('/settings/conf/command.html.twig', self::$theme), [
'commands' => $commands,
'numbrows' => $numbrows
]);
}
$configpage .= UI::twig()->render(self::$theme . '/settings/conf/fileblock.html.twig', [
$configpage .= UI::twig()->render(UI::validateThemeTemplate('/settings/conf/fileblock.html.twig', self::$theme), [
'realname' => $realname,
'commands_pre' => $commands_pre,
'commands_file' => $commands_file,
@@ -210,7 +210,7 @@ class ConfigDisplay
$commands = trim($commands);
if ($commands != '') {
$numbrows = count(explode("\n", $commands));
$configpage .= UI::twig()->render(self::$theme . '/settings/conf/command.html.twig', [
$configpage .= UI::twig()->render(UI::validateThemeTemplate('/settings/conf/command.html.twig', self::$theme), [
'commands' => $commands,
'numbrows' => $numbrows
]);
@@ -233,7 +233,7 @@ class ConfigDisplay
$file_content = htmlspecialchars($file_content);
$numbrows = count(explode("\n", $file_content));
//eval("\$files=\"" . \Froxlor\UI\Template::getTemplate("configfiles/configfiles_file") . "\";");
$files = UI::twig()->render(self::$theme . '/settings/conf/file.html.twig', [
$files = UI::twig()->render(UI::validateThemeTemplate('/settings/conf/file.html.twig', self::$theme), [
'distro_editor' => self::$editor,
'realname' => $realname,
'numbrows' => $numbrows,

View File

@@ -613,7 +613,10 @@ class Apache extends HttpConfigBase
// Apply header
$this->virtualhosts_data[$vhosts_filename] = '# Domain ID: ' . $domain['id'] . ' - CustomerID: ' . $domain['customerid'] . ' - CustomerLogin: ' . $domain['loginname'] . "\n";
if ($domain['deactivated'] != '1' || Settings::Get('system.deactivateddocroot') != '') {
$ddr = Settings::Get('system.deactivateddocroot');
if (($domain['deactivated'] == '1' || $domain['customer_deactivated'] == '1') && empty($ddr)) {
$this->virtualhosts_data[$vhosts_filename] .= '# Customer/domain deactivated and a docroot for deactivated users hasn\'t been set.' . "\n";
} else {
// Create vhost without ssl
$this->virtualhosts_data[$vhosts_filename] .= $this->getVhostContent($domain, false);
@@ -623,8 +626,6 @@ class Apache extends HttpConfigBase
$this->virtualhosts_data[$vhosts_filename_ssl] = '# Domain ID: ' . $domain['id'] . ' (SSL) - CustomerID: ' . $domain['customerid'] . ' - CustomerLogin: ' . $domain['loginname'] . "\n";
$this->virtualhosts_data[$vhosts_filename_ssl] .= $this->getVhostContent($domain, true);
}
} else {
$this->virtualhosts_data[$vhosts_filename] .= '# Customer deactivated and a docroot for deactivated users hasn\'t been set.' . "\n";
}
}
}
@@ -840,29 +841,34 @@ class Apache extends HttpConfigBase
$domain['documentroot'] = trim($domain['documentroot']);
if (preg_match('/^https?\:\/\//', $domain['documentroot'])) {
$corrected_docroot = $domain['documentroot'];
$possible_deactivated_webroot = $this->getWebroot($domain);
if ($this->deactivated == false) {
$corrected_docroot = $domain['documentroot'];
// Get domain's redirect code
$code = Domain::getDomainRedirectCode($domain['id']);
$modrew_red = '';
if ($code != '') {
$modrew_red = ' [R=' . $code . ';L,NE]';
}
// Get domain's redirect code
$code = Domain::getDomainRedirectCode($domain['id']);
$modrew_red = '';
if ($code != '') {
$modrew_red = ' [R=' . $code . ';L,NE]';
}
// redirect everything, not only root-directory, #541
$vhost_content .= ' <IfModule mod_rewrite.c>' . "\n";
$vhost_content .= ' RewriteEngine On' . "\n";
if (!$ssl_vhost) {
$vhost_content .= ' RewriteCond %{HTTPS} off' . "\n";
// redirect everything, not only root-directory, #541
$vhost_content .= ' <IfModule mod_rewrite.c>' . "\n";
$vhost_content .= ' RewriteEngine On' . "\n";
if (!$ssl_vhost) {
$vhost_content .= ' RewriteCond %{HTTPS} off' . "\n";
}
if ($domain['letsencrypt'] == '1') {
$vhost_content .= ' RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge' . "\n";
}
$vhost_content .= ' RewriteRule ^/(.*) ' . $corrected_docroot . '$1' . $modrew_red . "\n";
$vhost_content .= ' </IfModule>' . "\n";
$vhost_content .= ' <IfModule !mod_rewrite.c>' . "\n";
$vhost_content .= ' Redirect ' . $code . ' / ' . $domain['documentroot_norewrite'] . "\n";
$vhost_content .= ' </IfModule>' . "\n";
} elseif (Settings::Get('system.deactivateddocroot') != '') {
$vhost_content .= $possible_deactivated_webroot;
}
if ($domain['letsencrypt'] == '1') {
$vhost_content .= ' RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge' . "\n";
}
$vhost_content .= ' RewriteRule ^/(.*) ' . $corrected_docroot . '$1' . $modrew_red . "\n";
$vhost_content .= ' </IfModule>' . "\n";
$vhost_content .= ' <IfModule !mod_rewrite.c>' . "\n";
$vhost_content .= ' Redirect ' . $code . ' / ' . $domain['documentroot_norewrite'] . "\n";
$vhost_content .= ' </IfModule>' . "\n";
} else {
FileDir::mkDirWithCorrectOwnership($domain['customerroot'], $domain['documentroot'], $domain['guid'], $domain['guid'], true, true);
$vhost_content .= $this->getWebroot($domain);
@@ -952,8 +958,8 @@ class Apache extends HttpConfigBase
$domain['customerroot'] = FileDir::makeCorrectDir($domain['customerroot']);
$domain['documentroot'] = FileDir::makeCorrectDir($domain['documentroot']);
if ($domain['deactivated'] == '1' && Settings::Get('system.deactivateddocroot') != '') {
$webroot_text .= ' # Using docroot for deactivated users...' . "\n";
if (($domain['deactivated'] == '1' || $domain['customer_deactivated'] == '1') && Settings::Get('system.deactivateddocroot') != '') {
$webroot_text .= ' # Using docroot for deactivated users/domains...' . "\n";
$webroot_text .= ' DocumentRoot "' . rtrim(FileDir::makeCorrectDir(Settings::Get('system.deactivateddocroot')), "/") . "\"\n";
$webroot_text .= ' <Directory "' . FileDir::makeCorrectDir(Settings::Get('system.deactivateddocroot')) . '">' . "\n";
// >=apache-2.4 enabled?
@@ -1034,6 +1040,10 @@ class Apache extends HttpConfigBase
$statTool = Settings::Get('system.traffictool');
$statDomain = "";
if ($statTool == 'awstats') {
// awstats generates for each domain regardless of speciallogfile
$statDomain = "/" . $domain['domain'];
}
if ($domain['speciallogfile'] == '1') {
$statDomain = "/" . (($domain['parentdomainid'] == '0') ? $domain['domain'] : $domain['parentdomain']);
}

View File

@@ -521,7 +521,7 @@ EOC;
foreach ($loop_domains as $idx => $domain) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Validating DNS of " . $domain);
// ips according to NS
$domain_ips = PhpHelper::gethostbynamel6($domain);
$domain_ips = PhpHelper::gethostbynamel6($domain, true, Settings::Get('system.le_domain_dnscheck_resolver'));
if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
// no common ips...
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Skipping Let's Encrypt generation for " . $domain . " due to no system known IP address via DNS check");
@@ -557,7 +557,7 @@ EOC;
if (Settings::Get('system.letsencryptreuseold') != '1') {
$acmesh_cmd .= " --always-force-new-domain-key";
}
if (Settings::Get('system.letsencryptca') == 'letsencrypt_test') {
if (substr(Settings::Get('system.letsencryptca'), -5) == '_test') {
$acmesh_cmd .= " --staging";
}
if ($force) {

View File

@@ -414,15 +414,20 @@ class Lighttpd extends HttpConfigBase
$domain['documentroot'] = trim($domain['documentroot']);
if (preg_match('/^https?\:\/\//', $domain['documentroot'])) {
$uri = $domain['documentroot'];
$possible_deactivated_webroot = $this->getWebroot($domain);
if ($this->deactivated == false) {
$uri = $domain['documentroot'];
// Get domain's redirect code
$code = Domain::getDomainRedirectCode($domain['id']);
// Get domain's redirect code
$code = Domain::getDomainRedirectCode($domain['id']);
$vhost_content .= ' url.redirect-code = ' . $code . "\n";
$vhost_content .= ' url.redirect = (' . "\n";
$vhost_content .= ' "^/(.*)$" => "' . $uri . '$1"' . "\n";
$vhost_content .= ' )' . "\n";
$vhost_content .= ' url.redirect-code = ' . $code . "\n";
$vhost_content .= ' url.redirect = (' . "\n";
$vhost_content .= ' "^/(.*)$" => "' . $uri . '$1"' . "\n";
$vhost_content .= ' )' . "\n";
} elseif (Settings::Get('system.deactivateddocroot') != '') {
$vhost_content .= $possible_deactivated_webroot;
}
} else {
FileDir::mkDirWithCorrectOwnership($domain['customerroot'], $domain['documentroot'], $domain['guid'], $domain['guid'], true, true);
@@ -562,12 +567,12 @@ class Lighttpd extends HttpConfigBase
return $servernames_text;
}
protected function getWebroot($domain, $ssl)
protected function getWebroot($domain, bool $ssl = false)
{
$webroot_text = '';
if ($domain['deactivated'] == '1' && Settings::Get('system.deactivateddocroot') != '') {
$webroot_text .= ' # Using docroot for deactivated users...' . "\n";
if (($domain['deactivated'] == '1' || $domain['customer_deactivated'] == '1') && Settings::Get('system.deactivateddocroot') != '') {
$webroot_text .= ' # Using docroot for deactivated users/domains...' . "\n";
$webroot_text .= ' server.document-root = "' . FileDir::makeCorrectDir(Settings::Get('system.deactivateddocroot')) . "\"\n";
$this->deactivated = true;
} else {
@@ -719,6 +724,10 @@ class Lighttpd extends HttpConfigBase
$statTool = Settings::Get('system.traffictool');
$statDomain = "";
if ($statTool == 'awstats') {
// awstats generates for each domain regardless of speciallogfile
$statDomain = "/" . $domain['domain'];
}
if ($domain['speciallogfile'] == '1') {
$statDomain = "/" . (($domain['parentdomainid'] == '0') ? $domain['domain'] : $domain['parentdomain']);
}

View File

@@ -493,10 +493,10 @@ class Nginx extends HttpConfigBase
return '';
}
// check whether the customer is deactivated and NO docroot for deactivated users has been set#
// check whether the customer/domain is deactivated and NO docroot for deactivated users has been set#
$ddr = Settings::Get('system.deactivateddocroot');
if ($domain['deactivated'] == '1' && empty($ddr)) {
return '# Customer deactivated and a docroot for deactivated users hasn\'t been set.' . "\n";
if (($domain['deactivated'] == '1' || $domain['customer_deactivated'] == '1') && empty($ddr)) {
return '# Customer deactivated and a docroot for deactivated users/domains hasn\'t been set.' . "\n";
}
$vhost_content = '';
@@ -596,22 +596,27 @@ class Nginx extends HttpConfigBase
// if the documentroot is an URL we just redirect
if (preg_match('/^https?\:\/\//', $domain['documentroot'])) {
$uri = $domain['documentroot'];
if (substr($uri, -1) == '/') {
$uri = substr($uri, 0, -1);
$possible_deactivated_webroot = $this->getWebroot($domain);
if ($this->deactivated == false) {
$uri = $domain['documentroot'];
if (substr($uri, -1) == '/') {
$uri = substr($uri, 0, -1);
}
// Get domain's redirect code
$code = Domain::getDomainRedirectCode($domain['id']);
$vhost_content .= "\t" . 'location / {' . "\n";
$vhost_content .= "\t\t" . 'return ' . $code . ' ' . $uri . '$request_uri;' . "\n";
$vhost_content .= "\t" . '}' . "\n";
} elseif (Settings::Get('system.deactivateddocroot') != '') {
$vhost_content .= $possible_deactivated_webroot;
}
// Get domain's redirect code
$code = Domain::getDomainRedirectCode($domain['id']);
$vhost_content .= "\t" . 'location / {' . "\n";
$vhost_content .= "\t\t" . 'return ' . $code . ' ' . $uri . '$request_uri;' . "\n";
$vhost_content .= "\t" . '}' . "\n";
} else {
FileDir::mkDirWithCorrectOwnership($domain['customerroot'], $domain['documentroot'], $domain['guid'], $domain['guid'], true);
$vhost_content .= $this->getLogFiles($domain);
$vhost_content .= $this->getWebroot($domain, $ssl_vhost);
$vhost_content .= $this->getWebroot($domain);
if ($this->deactivated == false) {
$vhost_content = $this->mergeVhostCustom($vhost_content, $this->createPathOptions($domain)) . "\n";
@@ -770,12 +775,12 @@ class Nginx extends HttpConfigBase
return $logfiles_text;
}
protected function getWebroot($domain, $ssl)
protected function getWebroot($domain)
{
$webroot_text = '';
if ($domain['deactivated'] == '1' && Settings::Get('system.deactivateddocroot') != '') {
$webroot_text .= "\t" . '# Using docroot for deactivated users...' . "\n";
if (($domain['deactivated'] == '1' || $domain['customer_deactivated'] == '1' ) && Settings::Get('system.deactivateddocroot') != '') {
$webroot_text .= "\t" . '# Using docroot for deactivated users/domains...' . "\n";
$webroot_text .= "\t" . 'root ' . FileDir::makeCorrectDir(Settings::Get('system.deactivateddocroot')) . ';' . "\n";
$this->deactivated = true;
} else {
@@ -1120,6 +1125,10 @@ class Nginx extends HttpConfigBase
$statTool = Settings::Get('system.traffictool');
$statDomain = "";
if ($statTool == 'awstats') {
// awstats generates for each domain regardless of speciallogfile
$statDomain = "/" . $domain['domain'];
}
if ($domain['speciallogfile'] == '1') {
$statDomain = "/" . (($domain['parentdomainid'] == '0') ? $domain['domain'] : $domain['parentdomain']);
}

View File

@@ -43,7 +43,7 @@ class WebserverBase
{
$query = "SELECT `d`.*, `pd`.`domain` AS `parentdomain`, `c`.`loginname`,
`d`.`phpsettingid`, `c`.`adminid`, `c`.`guid`, `c`.`email`,
`c`.`documentroot` AS `customerroot`, `c`.`deactivated`,
`c`.`documentroot` AS `customerroot`, `c`.`deactivated` as `customer_deactivated`,
`c`.`phpenabled` AS `phpenabled_customer`,
`d`.`phpenabled` AS `phpenabled_vhost`
FROM `" . TABLE_PANEL_DOMAINS . "` `d`

View File

@@ -146,29 +146,37 @@ class BackupCron extends FroxlorCron
FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/mysql')));
// get all customer database-names
// @fixme respect multiple dbservers
$sel_stmt = Database::prepare("SELECT `databasename` FROM `" . TABLE_PANEL_DATABASES . "` WHERE `customerid` = :cid");
$sel_stmt = Database::prepare("SELECT `databasename`, `dbserver` FROM `" . TABLE_PANEL_DATABASES . "` WHERE `customerid` = :cid ORDER BY `dbserver`");
Database::pexecute($sel_stmt, [
'cid' => $data['customerid']
]);
Database::needRoot(true);
Database::needSqlData();
$sql_root = Database::getSqlData();
Database::needRoot(false);
$mysqlcnf_file = tempnam("/tmp", "frx");
$mysqlcnf = "[mysqldump]\npassword=".$sql_root['passwd']."\n";
file_put_contents($mysqlcnf_file, $mysqlcnf);
$has_dbs = false;
$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']) {
Database::needRoot(true, $row['dbserver']);
Database::needSqlData();
$sql_root = Database::getSqlData();
Database::needRoot(false);
// create temporary mysql-defaults file for the connection-credentials/details
$mysqlcnf_file = tempnam("/tmp", "frx");
$mysqlcnf = "[mysqldump]\npassword=" . $sql_root['passwd'] . "\nhost=" . $sql_root['host'] . "\n";
if (!empty($sql_root['port'])) {
$mysqlcnf .= "port=" . $sql_root['port'] . "\n";
} elseif (!empty($sql_root['socket'])) {
$mysqlcnf .= "socket=" . $sql_root['socket'] . "\n";
}
file_put_contents($mysqlcnf_file, $mysqlcnf);
}
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mysqldump -u ' . escapeshellarg($sql_root['user']) . ' -pXXXXX ' . $row['databasename'] . ' > ' . FileDir::makeCorrectFile($tmpdir . '/mysql/' . $row['databasename'] . '_' . date('YmdHi', time()) . '.sql'));
$bool_false = false;
FileDir::safe_exec('mysqldump --defaults-file=' . escapeshellarg($mysqlcnf_file) .' -u ' . escapeshellarg($sql_root['user']) . ' ' . $row['databasename'] . ' > ' . FileDir::makeCorrectFile($tmpdir . '/mysql/' . $row['databasename'] . '_' . date('YmdHi', time()) . '.sql'), $bool_false, [
FileDir::safe_exec('mysqldump --defaults-file=' . escapeshellarg($mysqlcnf_file) . ' -u ' . escapeshellarg($sql_root['user']) . ' ' . $row['databasename'] . ' > ' . FileDir::makeCorrectFile($tmpdir . '/mysql/' . $row['databasename'] . '_' . date('YmdHi', time()) . '.sql'), $bool_false, [
'>'
]);
$has_dbs = true;
$current_dbserver = $row['dbserver'];
}
if ($has_dbs) {

View File

@@ -63,9 +63,10 @@ class CurrentUser
/**
* re-read in the user data if a valid session exists
*
* @return boolean
* @return bool
* @throws \Exception
*/
public static function reReadUserData()
public static function reReadUserData(): bool
{
$table = self::isAdmin() ? TABLE_PANEL_ADMINS : TABLE_PANEL_CUSTOMERS;
$userinfo_stmt = Database::prepare("
@@ -75,7 +76,7 @@ class CurrentUser
"loginname" => self::getField('loginname')
]);
if ($userinfo) {
// dont just set the data, we need to merge with current data
// don't just set the data, we need to merge with current data
// array_merge is a right-reduction - value existing in getData() will be overwritten with $userinfo,
// other than the union-operator (+) which would keep the values already existing from getData()
$newuserinfo = array_merge(self::getData(), $userinfo);
@@ -107,7 +108,7 @@ class CurrentUser
*/
public static function getField(string $index)
{
return isset($_SESSION['userinfo'][$index]) ? $_SESSION['userinfo'][$index] : "";
return $_SESSION['userinfo'][$index] ?? "";
}
/**
@@ -130,6 +131,11 @@ class CurrentUser
$_SESSION['userinfo'] = $data;
}
/**
* @param string $resource
* @return bool
* @throws \Exception
*/
public static function canAddResource(string $resource): bool
{
$addition = true;
@@ -145,14 +151,15 @@ class CurrentUser
]);
$addition = $result['emaildomains'] != 0;
} elseif ($resource == 'subdomains') {
$parentDomainCollection = (new Collection(SubDomains::class, $_SESSION['userinfo'], ['sql_search' => ['d.parentdomainid' => 0]]));
$parentDomainCollection = (new Collection(SubDomains::class, $_SESSION['userinfo'],
['sql_search' => ['d.parentdomainid' => 0]]));
$addition = $parentDomainCollection != 0;
} elseif ($resource == 'domains') {
$customerCollection = (new Collection(Customers::class, $_SESSION['userinfo']));
$addition = $customerCollection != 0;
}
return ($_SESSION['userinfo'][$resource.'_used'] < $_SESSION['userinfo'][$resource] || $_SESSION['userinfo'][$resource] == '-1') && $addition;
return ($_SESSION['userinfo'][$resource . '_used'] < $_SESSION['userinfo'][$resource] || $_SESSION['userinfo'][$resource] == '-1') && $addition;
}
}

View File

@@ -31,7 +31,15 @@ use PDO;
class Customer
{
public static function getCustomerDetail($customerid, $varname)
/**
* Get value of a a specific field from a given customer
*
* @param int $customerid
* @param string $varname
* @return false|mixed
* @throws \Exception
*/
public static function getCustomerDetail(int $customerid, string $varname)
{
$customer_stmt = Database::prepare("
SELECT `" . $varname . "` FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `customerid` = :customerid
@@ -42,20 +50,19 @@ class Customer
if (isset($customer[$varname])) {
return $customer[$varname];
} else {
return false;
}
return false;
}
/**
* returns the loginname of a customer by given uid
*
* @param int $uid
* uid of customer
* @param int $uid uid of customer
*
* @return string customers loginname
* @throws \Exception
*/
public static function getLoginNameByUid($uid = null)
public static function getLoginNameByUid(int $uid)
{
$result_stmt = Database::prepare("
SELECT `loginname` FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `guid` = :guid
@@ -64,7 +71,7 @@ class Customer
'guid' => $uid
]);
if (is_array($result) && isset($result['loginname'])) {
if ($result && isset($result['loginname'])) {
return $result['loginname'];
}
return false;
@@ -76,23 +83,22 @@ class Customer
* returns true or false whether perl is
* enabled for the given customer
*
* @param
* int customer-id
* @param int $cid customer-id
*
* @return boolean
* @return bool
* @throws \Exception
*/
public static function customerHasPerlEnabled($cid = 0)
public static function customerHasPerlEnabled(int $cid = 0)
{
if ($cid > 0) {
$result_stmt = Database::prepare("
SELECT `perlenabled` FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `customerid` = :cid");
Database::pexecute($result_stmt, [
$result = Database::pexecute_first($result_stmt, [
'cid' => $cid
]);
$result = $result_stmt->fetch(PDO::FETCH_ASSOC);
if (is_array($result) && isset($result['perlenabled'])) {
return $result['perlenabled'] == '1';
if ($result && isset($result['perlenabled'])) {
return (bool)$result['perlenabled'];
}
}
return false;

View File

@@ -61,41 +61,43 @@ class Database
/**
* indicator whether to use root-connection or not
*/
private static $needroot = false;
private static bool $needroot = false;
/**
* indicator which database-server we're on (not really used)
*/
private static $dbserver = 0;
private static int $dbserver = 0;
/**
* used database-name
*/
private static $dbname = null;
private static ?string $dbname = null;
/**
* sql-access data
*/
private static $needsqldata = false;
private static bool $needsqldata = false;
private static $sqldata = null;
private static $need_dbname = true;
private static bool $need_dbname = true;
/**
* Wrapper for PDOStatement::execute so we can catch the PDOException
* Wrapper for PDOStatement::execute, so we can catch the PDOException
* and display the error nicely on the panel - also fetches the
* result from the statement and returns the resulting array
*
* @param PDOStatement $stmt
* @param array $params
* @param array|null $params
* (optional)
* @param bool $showerror
* suppress errordisplay (default true)
* suppress error display (default true)
* @param bool $json_response
*
* @return array
* @return mixed
* @throws Exception
*/
public static function pexecute_first(&$stmt, $params = null, $showerror = true, $json_response = false)
public static function pexecute_first(PDOStatement &$stmt, $params = null, bool $showerror = true, bool $json_response = false)
{
self::pexecute($stmt, $params, $showerror, $json_response);
return $stmt->fetch(PDO::FETCH_ASSOC);
@@ -106,12 +108,15 @@ class Database
* and display the error nicely on the panel
*
* @param PDOStatement $stmt
* @param array $params
* @param array|null $params
* (optional)
* @param bool $showerror
* suppress errordisplay (default true)
* suppress error display (default true)
* @param bool $json_response
*
* @throws Exception
*/
public static function pexecute(&$stmt, $params = null, $showerror = true, $json_response = false)
public static function pexecute(PDOStatement &$stmt, $params = null, bool $showerror = true, bool $json_response = false)
{
try {
$stmt->execute($params);
@@ -125,9 +130,10 @@ class Database
*
* @param PDOException $error
* @param bool $showerror
* if set to false, the error will be logged but we go on
* if set to false, the error will be logged, but we go on
* @throws Exception
*/
private static function showerror($error, $showerror = true, $json_response = false, PDOStatement $stmt = null)
private static function showerror(Exception $error, bool $showerror = true, bool $json_response = false, PDOStatement $stmt = null)
{
global $userinfo, $theme, $linker;
@@ -143,7 +149,7 @@ class Database
0 => [
'caption' => 'Default',
'host' => $sql['host'],
'socket' => (isset($sql['socket']) ? $sql['socket'] : null),
'socket' => ($sql['socket'] ?? null),
'user' => $sql['root_user'],
'password' => $sql['root_password']
]
@@ -159,8 +165,8 @@ class Database
$substitutions = [
$sql['password'] => 'DB_UNPRIV_PWD',
];
foreach ($sql_root as $dbserver => $sql_root_data) {
$substitutions[$sql_root_data[$dbserver]]['password'] = 'DB_ROOT_PWD';
foreach ($sql_root as $sql_root_data) {
$substitutions[$sql_root_data['password']] = 'DB_ROOT_PWD';
}
// hide username/password in messages
@@ -254,7 +260,7 @@ class Database
* @param int $minLength
* @return string
*/
private static function substitute($content, array $substitutions, $minLength = 6)
private static function substitute(string $content, array $substitutions, int $minLength = 6): string
{
$replacements = [];
@@ -262,9 +268,7 @@ class Database
$replacements += self::createShiftedSubstitutions($search, $replace, $minLength);
}
$content = str_replace(array_keys($replacements), array_values($replacements), $content);
return $content;
return str_replace(array_keys($replacements), array_values($replacements), $content);
}
/**
@@ -284,7 +288,7 @@ class Database
* @param int $minLength
* @return array
*/
private static function createShiftedSubstitutions($search, $replace, $minLength)
private static function createShiftedSubstitutions(string $search, string $replace, int $minLength): array
{
$substitutions = [];
$length = strlen($search);
@@ -303,8 +307,9 @@ class Database
*
* @param int $length
* @return string
* @throws Exception
*/
private static function genUniqueToken(int $length = 16)
private static function genUniqueToken(int $length = 16): string
{
if (intval($length) <= 8) {
$length = 16;
@@ -327,7 +332,7 @@ class Database
*
* @return int
*/
public static function num_rows()
public static function num_rows(): int
{
return Database::query("SELECT FOUND_ROWS()")->fetchColumn();
}
@@ -337,7 +342,7 @@ class Database
*
* @return string
*/
public static function getDbName()
public static function getDbName(): ?string
{
return self::$dbname;
}
@@ -349,8 +354,8 @@ class Database
* the 'normal' database-connection
*
* @param bool $needroot
* @param int $dbserver
* optional
* @param int $dbserver optional
* @param bool $need_db
*/
public static function needRoot(bool $needroot = false, int $dbserver = 0, bool $need_db = true)
{
@@ -366,7 +371,7 @@ class Database
*
* @param int $dbserver
*/
private static function setServer($dbserver = 0)
private static function setServer(int $dbserver = 0)
{
self::$dbserver = $dbserver;
self::$link = null;
@@ -397,17 +402,16 @@ class Database
* function that will be called on every static call
* which connects to the database if necessary
*
* @param bool $root
*
* @return object
* @throws Exception
*/
private static function getDB()
{
if (!extension_loaded('pdo') || in_array("mysql", PDO::getAvailableDrivers()) == false) {
if (!extension_loaded('pdo') || !in_array("mysql", PDO::getAvailableDrivers())) {
self::showerror(new Exception("The php PDO extension or PDO-MySQL driver is not available"));
}
// do we got a connection already?
// do we have a connection already?
if (self::$link) {
// return it
return self::$link;
@@ -422,7 +426,7 @@ class Database
0 => [
'caption' => 'Default',
'host' => $sql['host'],
'socket' => (isset($sql['socket']) ? $sql['socket'] : null),
'socket' => ($sql['socket'] ?? null),
'user' => $sql['root_user'],
'password' => $sql['root_password']
]
@@ -441,8 +445,8 @@ class Database
$user = $sql_root[self::$dbserver]['user'];
$password = $sql_root[self::$dbserver]['password'];
$host = $sql_root[self::$dbserver]['host'];
$socket = isset($sql_root[self::$dbserver]['socket']) ? $sql_root[self::$dbserver]['socket'] : null;
$port = isset($sql_root[self::$dbserver]['port']) ? $sql_root[self::$dbserver]['port'] : '3306';
$socket = $sql_root[self::$dbserver]['socket'] ?? null;
$port = $sql_root[self::$dbserver]['port'] ?? '3306';
$sslCAFile = $sql_root[self::$dbserver]['ssl']['caFile'] ?? "";
$sslVerifyServerCertificate = $sql_root[self::$dbserver]['ssl']['verifyServerCertificate'] ?? false;
} else {
@@ -450,8 +454,8 @@ class Database
$user = $sql["user"];
$password = $sql["password"];
$host = $sql["host"];
$socket = isset($sql['socket']) ? $sql['socket'] : null;
$port = isset($sql['port']) ? $sql['port'] : '3306';
$socket = $sql['socket'] ?? null;
$port = $sql['port'] ?? '3306';
$sslCAFile = $sql['ssl']['caFile'] ?? "";
$sslVerifyServerCertificate = $sql['ssl']['verifyServerCertificate'] ?? false;
}
@@ -556,14 +560,14 @@ class Database
*
* @return int
*/
public static function getSqlUsernameLength()
public static function getSqlUsernameLength(): int
{
// MariaDB supports up to 80 characters but only 64 for databases and as we use the loginname also for
// MariaDB supports up to 80 characters but only 64 for databases and as we use the login-name also for
// database names, we set the limit to 64 here
if (strpos(strtolower(Database::getAttribute(\PDO::ATTR_SERVER_VERSION)), "mariadb") !== false) {
$mysql_max = 64;
} else {
// MySQL user names can be up to 32 characters long (16 characters before MySQL 5.7.8).
// MySQL user-names can be up to 32 characters long (16 characters before MySQL 5.7.8).
$mysql_max = 32;
if (version_compare(Database::getAttribute(\PDO::ATTR_SERVER_VERSION), '5.7.8', '<')) {
$mysql_max = 16;
@@ -573,15 +577,16 @@ class Database
}
/**
* let's us interact with the PDO-Object by using static
* Lets us interact with the PDO-Object by using static
* call like "Database::function()"
*
* @param string $name
* @param mixed $args
*
* @return mixed
* @throws Exception
*/
public static function __callStatic($name, $args)
public static function __callStatic(string $name, $args)
{
$callback = [
self::getDB(),

View File

@@ -77,7 +77,15 @@ class DbManager
$this->manager = new DbManagerMySQL($this->log);
}
public static function correctMysqlUsers($mysql_access_host_array)
/**
* function called when the mysql-access-host setting changes
*
* @param array $mysql_access_host_array
*
* @return void
* @throws \Exception
*/
public static function correctMysqlUsers(array $mysql_access_host_array)
{
// get all databases for all dbservers
$databases = [];
@@ -101,7 +109,7 @@ class DbManager
$dbm = new DbManager(FroxlorLogger::getInstanceOf());
$users = $dbm->getManager()->getAllSqlUsers(false);
foreach ($databases[$dbserver] as $username) {
foreach ($databases[$dbserver['dbserver']] as $username) {
if (isset($users[$username]) && is_array($users[$username]) && isset($users[$username]['hosts']) && is_array($users[$username]['hosts'])) {
$password = [
@@ -136,13 +144,14 @@ class DbManager
* DB-name and user-name are being generated and
* the password for the user will be set
*
* @param string $loginname
* @param string $password
* @param ?string $loginname
* @param ?string $password
* @param int $dbserver
* @param int $last_accnumber
*
* @return string|bool $username if successful or false of username is equal to the password
*/
public function createDatabase($loginname = null, $password = null, int $dbserver = 0, $last_accnumber = 0)
public function createDatabase(string $loginname = null, string $password = null, int $dbserver = 0, int $last_accnumber = 0)
{
Database::needRoot(true, $dbserver, false);

View File

@@ -85,21 +85,22 @@ class IntegrityCheck
/**
* check whether the froxlor database and its tables are in utf-8 character-set
*
* @param bool $fix
* fix db charset/collation if not utf8
* @param bool $fix fix db charset/collation if not utf8
*
* @return boolean
* @return bool
* @throws \Exception
*/
public function databaseCharset($fix = false)
public function databaseCharset(bool $fix = false): bool
{
// get characterset
// get character-set
$cs_stmt = Database::prepare('SELECT default_character_set_name FROM information_schema.SCHEMATA WHERE schema_name = :dbname');
$resp = Database::pexecute_first($cs_stmt, [
'dbname' => Database::getDbName()
]);
$charset = isset($resp['default_character_set_name']) ? $resp['default_character_set_name'] : null;
$charset = $resp['default_character_set_name'] ?? null;
if (!empty($charset) && substr(strtolower($charset), 0, 4) != 'utf8') {
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "database charset seems to be different from UTF-8, integrity-check can fix that");
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE,
"database charset seems to be different from UTF-8, integrity-check can fix that");
if ($fix) {
// fix database
Database::query('ALTER DATABASE `' . Database::getDbName() . '` CHARACTER SET utf8 COLLATE utf8_general_ci');
@@ -109,7 +110,8 @@ class IntegrityCheck
$table = $row[0];
Database::query('ALTER TABLE `' . $table . '` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;');
}
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "database charset was different from UTF-8, integrity-check fixed that");
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING,
"database charset was different from UTF-8, integrity-check fixed that");
} else {
return false;
}
@@ -124,10 +126,12 @@ class IntegrityCheck
/**
* Check the integrity of the domain to ip/port - association
*
* @param bool $fix
* Fix everything found directly
* @param bool $fix fix everything found directly
*
* @return bool
* @throws \Exception
*/
public function domainIpTable($fix = false)
public function domainIpTable(bool $fix = false): bool
{
$ips = [];
$domains = [];
@@ -184,9 +188,11 @@ class IntegrityCheck
'domainid' => $row['id_domain'],
'ipandportid' => $row['id_ipandports']
]);
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "found an ip/port-id in domain <> ip table which does not exist, integrity check fixed this");
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING,
"found an ip/port-id in domain <> ip table which does not exist, integrity check fixed this");
} else {
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "found an ip/port-id in domain <> ip table which does not exist, integrity check can fix this");
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE,
"found an ip/port-id in domain <> ip table which does not exist, integrity check can fix this");
return false;
}
}
@@ -196,9 +202,11 @@ class IntegrityCheck
'domainid' => $row['id_domain'],
'ipandportid' => $row['id_ipandports']
]);
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "found a domain-id in domain <> ip table which does not exist, integrity check fixed this");
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING,
"found a domain-id in domain <> ip table which does not exist, integrity check fixed this");
} else {
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "found a domain-id in domain <> ip table which does not exist, integrity check can fix this");
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE,
"found a domain-id in domain <> ip table which does not exist, integrity check can fix this");
return false;
}
}
@@ -216,9 +224,11 @@ class IntegrityCheck
'ipandportid' => $defaultip
]);
}
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "found a domain-id with no entry in domain <> ip table, integrity check fixed this");
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING,
"found a domain-id with no entry in domain <> ip table, integrity check fixed this");
} else {
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "found a domain-id with no entry in domain <> ip table, integrity check can fix this");
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE,
"found a domain-id with no entry in domain <> ip table, integrity check can fix this");
return false;
}
}
@@ -226,18 +236,19 @@ class IntegrityCheck
if ($fix) {
return $this->domainIpTable();
} else {
return true;
}
return true;
}
/**
* Check if all subdomains have ssl-redirect = 0 if domain has no ssl-port
*
* @param bool $fix
* Fix everything found directly
* @param bool $fix fix everything found directly
*
* @return bool
* @throws \Exception
*/
public function subdomainSslRedirect($fix = false)
public function subdomainSslRedirect(bool $fix = false): bool
{
$ips = [];
$parentdomains = [];
@@ -300,28 +311,31 @@ class IntegrityCheck
Database::pexecute($upd_stmt, [
'domainid' => $id
]);
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "found a subdomain with ssl_redirect=1 but parent-domain has ssl=0, integrity check fixed this");
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING,
"found a subdomain with ssl_redirect=1 but parent-domain has ssl=0, integrity check fixed this");
} else {
// It's just the check, let the function fail
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "found a subdomain with ssl_redirect=1 but parent-domain has ssl=0, integrity check can fix this");
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE,
"found a subdomain with ssl_redirect=1 but parent-domain has ssl=0, integrity check can fix this");
return false;
}
}
if ($fix) {
return $this->subdomainSslRedirect();
} else {
return true;
}
return true;
}
/**
* Check if all subdomain have letsencrypt = 0 if domain has no ssl-port
*
* @param bool $fix
* Fix everything found directly
* @param bool $fix fix everything found directly
*
* @return bool
* @throws \Exception
*/
public function subdomainLetsencrypt($fix = false)
public function subdomainLetsencrypt(bool $fix = false): bool
{
$ips = [];
$parentdomains = [];
@@ -384,31 +398,32 @@ class IntegrityCheck
Database::pexecute($upd_stmt, [
'domainid' => $id
]);
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "found a subdomain with letsencrypt=1 but parent-domain has ssl=0, integrity check fixed this");
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING,
"found a subdomain with letsencrypt=1 but parent-domain has ssl=0, integrity check fixed this");
} else {
// It's just the check, let the function fail
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "found a subdomain with letsencrypt=1 but parent-domain has ssl=0, integrity check can fix this");
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE,
"found a subdomain with letsencrypt=1 but parent-domain has ssl=0, integrity check can fix this");
return false;
}
}
if ($fix) {
return $this->subdomainLetsencrypt();
} else {
return true;
}
return true;
}
/**
* check whether the webserveruser is in
* the customers groups when fcgid / php-fpm is used
*
* @param bool $fix
* fix member/groups
* @param bool $fix fix member/groups
*
* @return boolean
* @return bool
* @throws \Exception
*/
public function webserverGroupMemberForFcgidPhpFpm($fix = false)
public function webserverGroupMemberForFcgidPhpFpm(bool $fix = false): bool
{
if (Settings::Get('system.mod_fcgid') == 0 && Settings::Get('phpfpm.enabled') == 0) {
return true;
@@ -423,7 +438,8 @@ class IntegrityCheck
]);
if ($cwg_stmt->rowCount() > 0) {
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "Customers are missing the webserver-user as group-member, integrity-check can fix that");
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE,
"Customers are missing the webserver-user as group-member, integrity-check can fix that");
if ($fix) {
// prepare update statement
$upd_stmt = Database::prepare("
@@ -438,7 +454,8 @@ class IntegrityCheck
$upd_data['id'] = $cwg_row['id'];
Database::pexecute($upd_stmt, $upd_data);
}
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "Customers were missing the webserver-user as group-member, integrity-check fixed that");
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE,
"Customers were missing the webserver-user as group-member, integrity-check fixed that");
} else {
return false;
}
@@ -455,12 +472,12 @@ class IntegrityCheck
* the customers groups when fcgid / php-fpm and
* fcgid/fpm in froxlor vhost is used
*
* @param bool $fix
* fix member/groups
* @param bool $fix fix member/groups
*
* @return boolean
* @return bool
* @throws \Exception
*/
public function froxlorLocalGroupMemberForFcgidPhpFpm($fix = false)
public function froxlorLocalGroupMemberForFcgidPhpFpm(bool $fix = false): bool
{
if (Settings::Get('system.mod_fcgid') == 0 && Settings::Get('phpfpm.enabled') == 0) {
return true;
@@ -491,7 +508,8 @@ class IntegrityCheck
]);
if ($cwg_stmt->rowCount() > 0) {
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "Customers are missing the local froxlor-user as group-member, integrity-check can fix that");
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE,
"Customers are missing the local froxlor-user as group-member, integrity-check can fix that");
if ($fix) {
// prepare update statement
$upd_stmt = Database::prepare("
@@ -506,7 +524,8 @@ class IntegrityCheck
$upd_data['id'] = $cwg_row['id'];
Database::pexecute($upd_stmt, $upd_data);
}
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "Customers were missing the local froxlor-user as group-member, integrity-check fixed that");
$this->log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE,
"Customers were missing the local froxlor-user as group-member, integrity-check fixed that");
} else {
return false;
}

View File

@@ -48,7 +48,7 @@ class DbManagerMySQL
/**
* main constructor
*
* @param FroxlorLogger $log
* @param FroxlorLogger|null $log
*/
public function __construct(&$log = null)
{
@@ -58,9 +58,9 @@ class DbManagerMySQL
/**
* creates a database
*
* @param string $dbname
* @param string|null $dbname
*/
public function createDatabase($dbname = null)
public function createDatabase(string $dbname = null)
{
Database::query("CREATE DATABASE `" . $dbname . "`");
}
@@ -71,13 +71,14 @@ class DbManagerMySQL
*
* @param string $username
* @param string|array $password
* @param string $access_host
* @param ?string $access_host
* @param bool $p_encrypted
* optional, whether the password is encrypted or not, default false
* @param bool $update
* optional, whether to update the password only (not create user)
* @throws \Exception
*/
public function grantPrivilegesTo($username = null, $password = null, $access_host = null, $p_encrypted = false, $update = false)
public function grantPrivilegesTo(string $username, $password, string $access_host = null, bool $p_encrypted = false, bool $update = false)
{
$pwd_plugin = 'mysql_native_password';
if (is_array($password) && count($password) == 2) {
@@ -141,8 +142,9 @@ class DbManagerMySQL
* takes away any privileges from a user to that db
*
* @param string $dbname
* @throws \Exception
*/
public function deleteDatabase($dbname = null)
public function deleteDatabase(string $dbname)
{
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.0.2', '<')) {
// failsafe if user has been deleted manually (requires MySQL 4.1.2+)
@@ -178,8 +180,9 @@ class DbManagerMySQL
*
* @param string $username
* @param string $host
* @throws \Exception
*/
public function deleteUser($username = null, $host = null)
public function deleteUser(string $username, string $host)
{
if (Database::getAttribute(PDO::ATTR_SERVER_VERSION) < '5.0.2') {
// Revoke privileges (only required for MySQL 4.1.2 - 5.0.1)
@@ -203,9 +206,9 @@ class DbManagerMySQL
*
* @param string $username
* @param string $host
* (unused in mysql)
* @throws \Exception
*/
public function disableUser($username = null, $host = null)
public function disableUser(string $username, string $host)
{
$stmt = Database::prepare('REVOKE ALL PRIVILEGES, GRANT OPTION FROM `' . $username . '`@`' . $host . '`');
Database::pexecute($stmt, [], false);
@@ -216,8 +219,9 @@ class DbManagerMySQL
*
* @param string $username
* @param string $host
* @throws \Exception
*/
public function enableUser($username = null, $host = null)
public function enableUser(string $username, string $host)
{
// check whether user exists to avoid errors
$exist_check_stmt = Database::prepare("SELECT EXISTS(SELECT 1 FROM mysql.user WHERE user = '" . $username . "' AND host = '" . $host . "')");
@@ -239,14 +243,14 @@ class DbManagerMySQL
/**
* return an array of all usernames used in that DBMS
*
* @param bool $user_only
* if false, * will be selected from mysql.user and slightly different array will be generated
* @param bool $user_only if false, will be selected from mysql.user and slightly different array will be generated
*
* @return array
* @throws \Exception
*/
public function getAllSqlUsers($user_only = true)
public function getAllSqlUsers(bool $user_only = true): array
{
if ($user_only == false) {
if (!$user_only) {
$result_stmt = Database::prepare('SELECT * FROM mysql.user');
} else {
$result_stmt = Database::prepare('SELECT `User` FROM mysql.user');

View File

@@ -33,7 +33,15 @@ use PDO;
class Dns
{
public static function getAllowedDomainEntry($domain_id, $area = 'customer', $userinfo = [])
/**
* @param int $domain_id
* @param string $area
* @param array $userinfo
*
* @return string|void
* @throws \Exception
*/
public static function getAllowedDomainEntry(int $domain_id, string $area = 'customer', array $userinfo = [])
{
$dom_data = [
'did' => $domain_id
@@ -67,7 +75,15 @@ class Dns
Response::standardError('dns_notfoundorallowed');
}
public static function createDomainZone($domain_id, $froxlorhostname = false, $isMainButSubTo = false)
/**
* @param int|array $domain_id id of domain or in case of froxlorhostname, a domain-array with the needed data
* @param bool $froxlorhostname
* @param bool $isMainButSubTo
*
* @return DnsZone|void
* @throws \Exception
*/
public static function createDomainZone($domain_id, bool $froxlorhostname = false, bool $isMainButSubTo = false)
{
if (!$froxlorhostname) {
// get domain-name
@@ -136,7 +152,7 @@ class Dns
if (!$froxlorhostname) {
// additional required records for subdomains
$subdomains_stmt = Database::prepare("
SELECT `domain`, `iswildcarddomain`, `wwwserveralias` FROM `" . TABLE_PANEL_DOMAINS . "`
SELECT `domain`, `iswildcarddomain`, `wwwserveralias`, `isemaildomain` FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE `parentdomainid` = :domainid
");
Database::pexecute($subdomains_stmt, [
@@ -144,18 +160,31 @@ class Dns
]);
while ($subdomain = $subdomains_stmt->fetch(PDO::FETCH_ASSOC)) {
$sub_record = str_replace('.' . $domain['domain'], '', $subdomain['domain']);
// Listing domains is enough as there currently is no support for choosing
// different ips for a subdomain => use same IPs as toplevel
self::addRequiredEntry(str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'A', $required_entries);
self::addRequiredEntry(str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'AAAA', $required_entries);
self::addRequiredEntry($sub_record, 'A',$required_entries);
self::addRequiredEntry($sub_record, 'AAAA', $required_entries);
// Check whether to add a www.-prefix
if ($subdomain['iswildcarddomain'] == '1') {
self::addRequiredEntry('*.' . str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'A', $required_entries);
self::addRequiredEntry('*.' . str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'AAAA', $required_entries);
self::addRequiredEntry('*.' . $sub_record, 'A', $required_entries);
self::addRequiredEntry('*.' . $sub_record, 'AAAA', $required_entries);
} elseif ($subdomain['wwwserveralias'] == '1') {
self::addRequiredEntry('www.' . str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'A', $required_entries);
self::addRequiredEntry('www.' . str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'AAAA', $required_entries);
self::addRequiredEntry('www.' . $sub_record, 'A', $required_entries);
self::addRequiredEntry('www.' . $sub_record, 'AAAA', $required_entries);
}
// check for email ability
if ($subdomain['isemaildomain'] == '1') {
if (Settings::Get('spf.use_spf') == '1') {
// check for SPF content later
self::addRequiredEntry('@SPF@.' . $sub_record, 'TXT', $required_entries);
}
if (Settings::Get('dkim.use_dkim') == '1') {
// check for DKIM content later
self::addRequiredEntry('dkim' . $domain['dkim_id'] . '._domainkey.' . $sub_record, 'TXT', $required_entries);
}
}
}
}
@@ -200,14 +229,17 @@ class Dns
// now generate all records and unset the required entries we have
foreach ($dom_entries as $entry) {
if (array_key_exists($entry['type'], $required_entries) && array_key_exists(md5($entry['record']), $required_entries[$entry['type']])) {
if (array_key_exists($entry['type'], $required_entries) && array_key_exists(md5($entry['record']),
$required_entries[$entry['type']])) {
unset($required_entries[$entry['type']][md5($entry['record'])]);
}
if (Settings::Get('system.dns_createcaaentry') == '1' && $entry['type'] == 'CAA' && strtolower(substr($entry['content'], 0, 7)) == '"v=caa1') {
if (Settings::Get('system.dns_createcaaentry') == '1' && $entry['type'] == 'CAA' && strtolower(substr($entry['content'],
0, 7)) == '"v=caa1') {
// unset special CAA required-entry
unset($required_entries[$entry['type']][md5("@CAA@")]);
}
if (Settings::Get('spf.use_spf') == '1' && $entry['type'] == 'TXT' && $entry['record'] == '@' && (strtolower(substr($entry['content'], 0, 7)) == '"v=spf1' || strtolower(substr($entry['content'], 0, 6)) == 'v=spf1')) {
if (Settings::Get('spf.use_spf') == '1' && $entry['type'] == 'TXT' && $entry['record'] == '@' && (strtolower(substr($entry['content'],
0, 7)) == '"v=spf1' || strtolower(substr($entry['content'], 0, 6)) == 'v=spf1')) {
// unset special spf required-entry
unset($required_entries[$entry['type']][md5("@SPF@")]);
}
@@ -223,7 +255,8 @@ class Dns
'*'
] as $crecord
) {
if ($entry['type'] == 'CNAME' && $entry['record'] == '@' && (array_key_exists(md5($crecord), $required_entries['A']) || array_key_exists(md5($crecord), $required_entries['AAAA']))) {
if ($entry['type'] == 'CNAME' && $entry['record'] == '@' && (array_key_exists(md5($crecord),
$required_entries['A']) || array_key_exists(md5($crecord), $required_entries['AAAA']))) {
unset($required_entries['A'][md5($crecord)]);
unset($required_entries['AAAA'][md5($crecord)]);
}
@@ -238,13 +271,16 @@ class Dns
'smtp'
] as $crecord
) {
if ($entry['type'] == 'CNAME' && $entry['record'] == $crecord && (array_key_exists(md5($crecord), $required_entries['A']) || array_key_exists(md5($crecord), $required_entries['AAAA']))) {
if ($entry['type'] == 'CNAME' && $entry['record'] == $crecord && (array_key_exists(md5($crecord),
$required_entries['A']) || array_key_exists(md5($crecord),
$required_entries['AAAA']))) {
unset($required_entries['A'][md5($crecord)]);
unset($required_entries['AAAA'][md5($crecord)]);
}
}
}
$zonerecords[] = new DnsEntry($entry['record'], $entry['type'], $entry['content'], $entry['prio'], $entry['ttl']);
$zonerecords[] = new DnsEntry($entry['record'], $entry['type'], $entry['content'], $entry['prio'],
$entry['ttl']);
}
// add missing required entries
@@ -275,7 +311,8 @@ class Dns
foreach ($records as $record) {
if ($type == 'A' && filter_var($ip['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) {
$zonerecords[] = new DnsEntry($record, 'A', $ip['ip']);
} elseif ($type == 'AAAA' && filter_var($ip['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) {
} elseif ($type == 'AAAA' && filter_var($ip['ip'], FILTER_VALIDATE_IP,
FILTER_FLAG_IPV6) !== false) {
$zonerecords[] = new DnsEntry($record, 'AAAA', $ip['ip']);
}
}
@@ -348,15 +385,34 @@ class Dns
if ($type == 'TXT') {
foreach ($records as $record) {
if ($record == '@SPF@') {
// spf for main-domain
$txt_content = Settings::Get('spf.spf_entry');
$zonerecords[] = new DnsEntry('@', 'TXT', self::encloseTXTContent($txt_content));
} elseif ($record == 'dkim' . $domain['dkim_id'] . '._domainkey' && !empty($dkim_entries)) {
// check for multiline entry
$multiline = false;
if (substr($dkim_entries[0], 0, 1) == '(') {
$multiline = true;
} elseif (strlen($record) > 6 && substr($record, 0, 6) == '@SPF@.') {
// spf for subdomain
$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)) {
// DKIM entries
$dkim_record = 'dkim' . $domain['dkim_id'] . '._domainkey';
if ($record == $dkim_record) {
// dkim for main-domain
// check for multiline entry
$multiline = false;
if (substr($dkim_entries[0], 0, 1) == '(') {
$multiline = true;
}
$zonerecords[] = new DnsEntry($record, 'TXT', self::encloseTXTContent($dkim_entries[0], $multiline));
} elseif (strlen($record) > strlen($dkim_record) && substr($record, 0, strlen($dkim_record)+1) == $dkim_record . '.') {
// dkim for subdomain-domain
// check for multiline entry
$multiline = false;
if (substr($dkim_entries[0], 0, 1) == '(') {
$multiline = true;
}
$zonerecords[] = new DnsEntry($record, 'TXT', self::encloseTXTContent($dkim_entries[0], $multiline));
}
$zonerecords[] = new DnsEntry($record, 'TXT', self::encloseTXTContent($dkim_entries[0], $multiline));
}
}
}
@@ -416,7 +472,8 @@ class Dns
if (!$isMainButSubTo) {
$date = date('Ymd');
$domain['bindserial'] = (preg_match('/^' . $date . '/', $domain['bindserial']) ? $domain['bindserial'] + 1 : $date . '00');
$domain['bindserial'] = (preg_match('/^' . $date . '/',
$domain['bindserial']) ? $domain['bindserial'] + 1 : $date . '00');
if (!$froxlorhostname) {
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_DOMAINS . "` SET
@@ -443,12 +500,19 @@ class Dns
array_unshift($zonerecords, $soa_record);
}
$zone = new DnsZone((int)Settings::Get('system.defaultttl'), $domain['domain'], $domain['bindserial'], $zonerecords);
$zone = new DnsZone((int)Settings::Get('system.defaultttl'), $domain['domain'], $domain['bindserial'],
$zonerecords);
return $zone;
}
private static function addRequiredEntry($record = '@', $type = 'A', &$required = [])
/**
* @param string $record
* @param string $type
* @param array $required
* @return void
*/
private static function addRequiredEntry(string $record = '@', string $type = 'A', array &$required = [])
{
if (!isset($required[$type])) {
$required[$type] = [];
@@ -456,7 +520,11 @@ class Dns
$required[$type][md5($record)] = $record;
}
private static function generateDkimEntries($domain)
/**
* @param array $domain
* @return array
*/
private static function generateDkimEntries(array $domain): array
{
$zone_dkim = [];
@@ -486,7 +554,8 @@ class Dns
}
// key
$dkim_txt .= 'k=rsa;p=' . trim(preg_replace('/-----BEGIN PUBLIC KEY-----(.+)-----END PUBLIC KEY-----/s', '$1', str_replace("\n", '', $domain['dkim_pubkey']))) . ';';
$dkim_txt .= 'k=rsa;p=' . trim(preg_replace('/-----BEGIN PUBLIC KEY-----(.+)-----END PUBLIC KEY-----/s',
'$1', str_replace("\n", '', $domain['dkim_pubkey']))) . ';';
// service-type
if (Settings::Get('dkim.dkim_servicetype') == '1') {
@@ -503,10 +572,15 @@ class Dns
return $zone_dkim;
}
public static function encloseTXTContent($txt_content, $isMultiLine = false)
/**
* @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 == false && Settings::Get('system.dns_server') != 'PowerDNS') {
if (!$isMultiLine && Settings::Get('system.dns_server') != 'PowerDNS') {
if (substr($txt_content, 0, 1) != '"') {
$txt_content = '"' . $txt_content;
}
@@ -526,10 +600,13 @@ class Dns
return $txt_content;
}
private static function escapeSoaAdminMail($email)
/**
* @param string $email
* @return string
*/
private static function escapeSoaAdminMail(string $email): string
{
$mail_parts = explode("@", $email);
$escpd_mail = str_replace(".", "\.", $mail_parts[0]) . "." . $mail_parts[1] . ".";
return $escpd_mail;
return str_replace(".", "\.", $mail_parts[0]) . "." . $mail_parts[1] . ".";
}
}

View File

@@ -29,14 +29,22 @@ use Froxlor\Settings;
class DnsEntry
{
public $record;
public $ttl;
public $class = 'IN';
public $type;
public $priority;
public $content;
public string $record;
public int $ttl;
public string $class = 'IN';
public string $type;
public int $priority;
public ?string $content;
public function __construct($record = '', $type = 'A', $content = null, $prio = 0, $ttl = 0, $class = 'IN')
/**
* @param string $record
* @param string $type
* @param string|null $content
* @param int $prio
* @param int $ttl
* @param string $class
*/
public function __construct(string $record = '', string $type = 'A', string $content = null, int $prio = 0, int $ttl = 0, string $class = 'IN')
{
$this->record = $record;
$this->type = $type;
@@ -72,7 +80,6 @@ class DnsEntry
// last line
$_content .= "\t\t\t\t" . '"' . $_l . '")';
}
$result = $this->record . "\t" . $this->ttl . "\t" . $this->class . "\t" . $this->type . "\t" . (($this->priority >= 0 && ($this->type == 'MX' || $this->type == 'SRV')) ? $this->priority . "\t" : "") . $_content . PHP_EOL;
return $result;
return $this->record . "\t" . $this->ttl . "\t" . $this->class . "\t" . $this->type . "\t" . (($this->priority >= 0 && ($this->type == 'MX' || $this->type == 'SRV')) ? $this->priority . "\t" : "") . $_content . PHP_EOL;
}
}

View File

@@ -29,12 +29,18 @@ use Froxlor\Settings;
class DnsZone
{
public $ttl;
public $origin;
public $serial;
public $records;
public int $ttl;
public string $origin;
public string $serial;
public ?array $records;
public function __construct($ttl = 0, $origin = '', $serial = '', $records = null)
/**
* @param int $ttl
* @param string $origin
* @param string $serial
* @param array|null $records
*/
public function __construct(int $ttl = 0, string $origin = '', string $serial = '', array $records = null)
{
$this->ttl = ($ttl <= 0 ? Settings::Get('system.defaultttl') : $ttl);
$this->origin = $origin;
@@ -44,13 +50,13 @@ class DnsZone
public function __toString()
{
$_zonefile = "\$TTL " . $this->ttl . PHP_EOL;
$_zonefile .= "\$ORIGIN " . $this->origin . "." . PHP_EOL;
$zone_file = "\$TTL " . $this->ttl . PHP_EOL;
$zone_file .= "\$ORIGIN " . $this->origin . "." . PHP_EOL;
if (!empty($this->records)) {
foreach ($this->records as $record) {
$_zonefile .= (string)$record;
$zone_file .= (string)$record;
}
}
return $_zonefile;
return $zone_file;
}
}

View File

@@ -37,9 +37,9 @@ class PowerDNS
/**
* remove all records and entries of a given domain
*
* @param array $domain
* @param array|null $domain
*/
public static function cleanDomainZone($domain = null)
public static function cleanDomainZone(array $domain = null)
{
if (is_array($domain) && isset($domain['domain'])) {
$pdns_domains_stmt = self::getDB()->prepare("SELECT `id`, `name` FROM `domains` WHERE `name` = :domain");
@@ -67,16 +67,19 @@ class PowerDNS
/**
* get pdo database connection to powerdns database
*
* @return PDO
* @return \PDO
*/
public static function getDB()
public static function getDB(): \PDO
{
if (!isset(self::$pdns_db) || (self::$pdns_db instanceof PDO) == false) {
if (!isset(self::$pdns_db) || !(self::$pdns_db instanceof PDO)) {
self::connectToPdnsDb();
}
return self::$pdns_db;
}
/**
* @return void
*/
private static function connectToPdnsDb()
{
// get froxlor pdns config

View File

@@ -41,8 +41,9 @@ class Domain
*
* @param int $domain_id
* @return array
* @throws \Exception
*/
public static function getIpsOfDomain($domain_id)
public static function getIpsOfDomain(int $domain_id = 0): array
{
if ($domain_id > 0) {
$sel_stmt = Database::prepare("
@@ -75,7 +76,7 @@ class Domain
*
* @return array array of enabled redirect-codes
*/
public static function getRedirectCodesArray()
public static function getRedirectCodesArray(): array
{
$sql = "SELECT * FROM `" . TABLE_PANEL_REDIRECTCODES . "` WHERE `enabled` = '1' ORDER BY `id` ASC";
$result_stmt = Database::query($sql);
@@ -92,12 +93,12 @@ class Domain
* returns the redirect-code for a given
* domain-id
*
* @param integer $domainid
* id of the domain
* @param int $domainid id of the domain
*
* @return string redirect-code
* @throws \Exception
*/
public static function getDomainRedirectCode($domainid = 0)
public static function getDomainRedirectCode(int $domainid = 0): string
{
// get system default
$default = '301';
@@ -128,12 +129,11 @@ class Domain
* return an array of all enabled redirect-codes
* for the settings form
*
* @param bool $add_desc
* optional, default true, add the code-description
* @param bool $add_desc optional, default true, add the code-description
*
* @return array array of enabled redirect-codes
*/
public static function getRedirectCodes($add_desc = true)
public static function getRedirectCodes(bool $add_desc = true): array
{
$sql = "SELECT * FROM `" . TABLE_PANEL_REDIRECTCODES . "` WHERE `enabled` = '1' ORDER BY `id` ASC";
$result_stmt = Database::query($sql);
@@ -153,12 +153,12 @@ class Domain
* returns the redirect-id for a given
* domain-id
*
* @param integer $domainid
* id of the domain
* @param int $domainid id of the domain
*
* @return integer redirect-code-id
* @return int redirect-code-id
* @throws \Exception
*/
public static function getDomainRedirectId($domainid = 0)
public static function getDomainRedirectId(int $domainid = 0): int
{
$code = 1;
if ($domainid > 0) {
@@ -171,7 +171,7 @@ class Domain
'domainid' => $domainid
]);
if (is_array($result) && isset($result['redirect'])) {
if ($result && isset($result['redirect'])) {
$code = (int)$result['redirect'];
}
}
@@ -179,16 +179,15 @@ class Domain
}
/**
* adds a redirectcode for a domain
* adds a redirect-code for a domain
*
* @param integer $domainid
* id of the domain to add the code for
* @param integer $redirect
* selected redirect-id
* @param int $domainid id of the domain to add the code for
* @param int $redirect selected redirect-id
*
* @return null
* @throws \Exception
*/
public static function addRedirectToDomain($domainid = 0, $redirect = 1)
public static function addRedirectToDomain(int $domainid = 0, int $redirect = 1)
{
if ($domainid > 0) {
$ins_stmt = Database::prepare("
@@ -202,19 +201,18 @@ class Domain
}
/**
* updates the redirectcode of a domain
* updates the redirect-code of a domain
* if redirect-code is false, nothing happens
*
* @param integer $domainid
* id of the domain to update
* @param integer $redirect
* selected redirect-id or false
* @param int $domainid id of the domain to update
* @param int $redirect selected redirect-id
*
* @return null
* @throws \Exception
*/
public static function updateRedirectOfDomain($domainid = 0, $redirect = false)
public static function updateRedirectOfDomain(int $domainid = 0, int $redirect = 0)
{
if ($redirect == false) {
if (!$redirect) {
return;
}
@@ -240,12 +238,12 @@ class Domain
* check whether a domain has subdomains added as full-domains
* #329
*
* @param int $id
* domain-id
* @param int $id domain-id
*
* @return boolean
* @return bool
* @throws \Exception
*/
public static function domainHasMainSubDomains($id = 0)
public static function domainHasMainSubDomains(int $id): bool
{
$result_stmt = Database::prepare("
SELECT COUNT(`id`) as `mainsubs` FROM `" . TABLE_PANEL_DOMAINS . "`
@@ -254,8 +252,8 @@ class Domain
'id' => $id
]);
if (isset($result['mainsubs']) && $result['mainsubs'] > 0) {
return true;
if ($result && isset($result['mainsubs'])) {
return $result['mainsubs'] > 0;
}
return false;
}
@@ -264,12 +262,12 @@ class Domain
* check whether a subof-domain exists
* #329
*
* @param int $id
* subof-domain-id
* @param int $id subof-domain-id
*
* @return boolean
* @return bool
* @throws \Exception
*/
public static function domainMainToSubExists($id = 0)
public static function domainMainToSubExists(int $id): bool
{
$result_stmt = Database::prepare("
SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `id` = :id");
@@ -278,8 +276,8 @@ class Domain
]);
$result = $result_stmt->fetch(PDO::FETCH_ASSOC);
if (isset($result['id']) && $result['id'] > 0) {
return true;
if ($result && isset($result['id'])) {
return $result['id'] > 0;
}
return false;
}
@@ -289,9 +287,10 @@ class Domain
*
* @param int $domainid
*
* @return boolean
* @return bool
* @throws \Exception
*/
public static function domainHasSslIpPort($domainid = 0)
public static function domainHasSslIpPort(int $domainid): bool
{
$result_stmt = Database::prepare("
SELECT `dt`.* FROM `" . TABLE_DOMAINTOIP . "` `dt`, `" . TABLE_PANEL_IPSANDPORTS . "` `iap`
@@ -301,7 +300,7 @@ class Domain
]);
$result = $result_stmt->fetch(PDO::FETCH_ASSOC);
if (is_array($result) && isset($result['id_ipandports'])) {
if ($result && isset($result['id_ipandports'])) {
return true;
}
return false;
@@ -311,12 +310,12 @@ class Domain
* returns true or false whether a given domain id
* is the std-subdomain of a customer
*
* @param
* int domain-id
* @param int $did domain-id
*
* @return boolean
* @return bool
* @throws \Exception
*/
public static function isCustomerStdSubdomain($did = 0)
public static function isCustomerStdSubdomain(int $did): bool
{
if ($did > 0) {
$result_stmt = Database::prepare("
@@ -327,17 +326,27 @@ class Domain
'did' => $did
]);
if (is_array($result) && isset($result['customerid']) && $result['customerid'] > 0) {
return true;
if ($result && isset($result['customerid'])) {
return $result['customerid'] > 0;
}
}
return false;
}
public static function triggerLetsEncryptCSRForAliasDestinationDomain($aliasDestinationDomainID, $log)
{
if (isset($aliasDestinationDomainID) && $aliasDestinationDomainID > 0) {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "LetsEncrypt CSR triggered for domain ID " . $aliasDestinationDomainID);
/**
* @param int $aliasDestinationDomainID
* @param FroxlorLogger $log
*
* @return void
* @throws \Exception
*/
public static function triggerLetsEncryptCSRForAliasDestinationDomain(
int $aliasDestinationDomainID,
FroxlorLogger $log
) {
if ($aliasDestinationDomainID > 0) {
$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
@@ -351,7 +360,11 @@ class Domain
}
}
public static function doLetsEncryptCleanUp($domainname = null)
/**
* @param string $domainname
* @return true
*/
public static function doLetsEncryptCleanUp(string $domainname): bool
{
// @ see \Froxlor\Cron\Http\LetsEncrypt\AcmeSh.php
$acmesh = AcmeSh::getAcmeSh();
@@ -374,18 +387,19 @@ class Domain
/**
* checks give path for security issues
* and returns a string that can be appended
* to a line for a open_basedir directive
* to a line for an open_basedir directive
*
* @param string $path
* the path to check and append
* @param boolean $first
* if true, no ':' will be prefixed to the path
* @param string $path the path to check and append
* @param bool $first if true, no ':' will be prefixed to the path
*
* @return string
* @throws \Exception
*/
public static function appendOpenBasedirPath($path = '', $first = false)
public static function appendOpenBasedirPath(string $path = '', bool $first = false): string
{
if ($path != '' && $path != '/' && (!preg_match("#^/dev#i", $path) || preg_match("#^/dev/urandom#i", $path)) && !preg_match("#^/proc#i", $path) && !preg_match("#^/etc#i", $path) && !preg_match("#^/sys#i", $path) && !preg_match("#:#", $path)) {
if ($path != '' && $path != '/' && (!preg_match("#^/dev#i", $path) || preg_match("#^/dev/urandom#i",
$path)) && !preg_match("#^/proc#i", $path) && !preg_match("#^/etc#i",
$path) && !preg_match("#^/sys#i", $path) && !preg_match("#:#", $path)) {
if (preg_match("#^/dev/urandom#i", $path)) {
$path = FileDir::makeCorrectFile($path);
} else {
@@ -394,7 +408,7 @@ class Domain
// check for php-version that requires the trailing
// slash to be removed as it does not allow the usage
// of the subfolders within the given folder, fixes #797
// of the sub-folders within the given folder, fixes #797
if ((PHP_MINOR_VERSION == 2 && PHP_VERSION_ID >= 50216) || PHP_VERSION_ID >= 50304) {
// check trailing slash
if (substr($path, -1, 1) == '/') {

View File

@@ -30,8 +30,10 @@ use PDO;
class IpAddr
{
public static function getIpAddresses()
/**
* @return array
*/
public static function getIpAddresses(): array
{
$result_stmt = Database::query("
SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` ORDER BY `ip` ASC, `port` ASC
@@ -51,14 +53,22 @@ class IpAddr
return $system_ipaddress_array;
}
public static function getSslIpPortCombinations()
/**
* @return array
*/
public static function getSslIpPortCombinations(): array
{
return [
'' => lng('panel.none_value')
] + self::getIpPortCombinations(true);
}
public static function getIpPortCombinations($ssl = false)
/**
* @param bool $ssl
* @return array
* @throws \Exception
*/
public static function getIpPortCombinations(bool $ssl = false): array
{
global $userinfo;

View File

@@ -38,30 +38,24 @@ class FileDir
* which had to be created below with correct Owner/Group
* (Copied from cron_tasks.php:rev1189 as we'll need this more often in future)
*
* @param string $homeDir
* The homedir of the user
* @param string $dirToCreate
* The dir which should be created
* @param int $uid
* The uid of the user
* @param int $gid
* The gid of the user
* @param bool $placeindex
* Place standard-index.html into the new folder
* @param bool $allow_notwithinhomedir
* Allow creating a directory out of the customers docroot
* @param string $homeDir The homedir of the user
* @param string $dirToCreate The dir which should be created
* @param int $uid The uid of the user
* @param int $gid The gid of the user
* @param bool $placeindex Place standard-index.html into the new folder
* @param bool $allow_notwithinhomedir Allow creating a directory out of the customers docroot
*
* @return bool true if everything went okay, false if something went wrong
* @throws Exception
*/
public static function mkDirWithCorrectOwnership(
$homeDir,
$dirToCreate,
$uid,
$gid,
$placeindex = false,
$allow_notwithinhomedir = false
) {
string $homeDir,
string $dirToCreate,
int $uid,
int $gid,
bool $placeindex = false,
bool $allow_notwithinhomedir = false
): bool {
if ($homeDir != '' && $dirToCreate != '') {
$homeDir = self::makeCorrectDir($homeDir);
$dirToCreate = self::makeCorrectDir($dirToCreate);
@@ -116,15 +110,14 @@ class FileDir
* Function which returns a correct dirname, means to add slashes at the beginning and at the end if there weren't
* some
*
* @param string $path
* the path to correct
* @param string $dir the path to correct
*
* @return string the corrected path
* @throws Exception
*/
public static function makeCorrectDir($dir)
public static function makeCorrectDir(string $dir): string
{
if (is_string($dir) && strlen($dir) > 0) {
if (strlen($dir) > 0) {
$dir = trim($dir);
if (substr($dir, -1, 1) != '/') {
$dir .= '/';
@@ -140,16 +133,15 @@ class FileDir
/**
* Function which returns a secure path, means to remove all multiple dots and slashes
*
* @param string $path
* the path to secure
* @param string $path the path to secure
*
* @return string the corrected path
*/
public static function makeSecurePath($path)
public static function makeSecurePath(string $path): string
{
// check for bad characters, some are allowed with escaping
// check for bad characters, some are allowed with escaping,
// but we generally don't want them in our directory-names,
// thx to aaronmueller for this snipped
// thx to aaronmueller for this snippet
$badchars = [
':',
';',
@@ -161,7 +153,11 @@ class FileDir
'$',
'~',
'?',
"\0"
"\0",
"\n",
"\r",
"\t",
"\f"
];
foreach ($badchars as $bc) {
$path = str_replace($bc, "", $path);
@@ -187,16 +183,13 @@ class FileDir
/**
* Wrapper around the exec command.
*
* @param string $exec_string
* command to be executed
* @param string $return_value
* referenced variable where the output is stored
* @param array $allowedChars
* optional array of allowed characters in path/command
* @param string $exec_string command to be executed
* @param mixed $return_value referenced variable where the output is stored
* @param ?array $allowedChars optional array of allowed characters in path/command
*
* @return array result of exec()
*/
public static function safe_exec($exec_string, &$return_value = false, $allowedChars = null)
public static function safe_exec(string $exec_string, &$return_value = false, $allowedChars = null)
{
$disallowed = [
';',
@@ -241,19 +234,20 @@ class FileDir
/**
* store the default index-file in a given destination folder
*
* @param string $loginname
* customers loginname
* @param string $destination
* path where to create the file
* @param object $logger
* FroxlorLogger object
* @param boolean $force
* force creation whatever the settings say (needed for task #2, create new user)
* @param string $loginname customers loginname
* @param string $destination path where to create the file
* @param object $logger FroxlorLogger object
* @param bool $force force creation whatever the settings say (needed for task #2, create new user)
*
* @return null
* @return void
* @throws Exception
*/
public static function storeDefaultIndex($loginname = null, $destination = null, $logger = null, $force = false)
{
public static function storeDefaultIndex(
string $loginname,
string $destination,
$logger = null,
bool $force = false
) {
if ($force || (int)Settings::Get('system.store_index_file_subs') == 1) {
$result_stmt = Database::prepare("
SELECT `t`.`value`, `c`.`email` AS `customer_email`, `a`.`email` AS `admin_email`, `c`.`loginname` AS `customer_login`, `a`.`loginname` AS `admin_login`
@@ -302,18 +296,16 @@ class FileDir
self::safe_exec('cp -a ' . Froxlor::getInstallDir() . '/templates/misc/standardcustomer/* ' . escapeshellarg($destination));
}
}
return;
}
/**
* Function which returns a correct filename, means to add a slash at the beginning if there wasn't one
*
* @param string $filename
* the filename
* @param string $filename the filename
*
* @return string the corrected filename
*/
public static function makeCorrectFile(string $filename)
public static function makeCorrectFile(string $filename): string
{
if (trim($filename) == '') {
$error = 'Given filename for function ' . __FUNCTION__ . ' is empty.' . "\n";
@@ -329,21 +321,19 @@ class FileDir
$filename = '/' . $filename;
}
$filename = self::makeSecurePath($filename);
return $filename;
return self::makeSecurePath($filename);
}
/**
* checks a directory against disallowed paths which could
* lead to a damaged system if you use them
*
* @param string $fieldname
* @param array $fielddata
* @param mixed $newfieldvalue
* @param string|null $path
*
* @return boolean|array
* @return bool
* @throws Exception
*/
public static function checkDisallowedPaths($path = null)
public static function checkDisallowedPaths(string $path): bool
{
/*
* disallow base-directories and /
@@ -381,12 +371,11 @@ class FileDir
/**
* Function which returns a correct destination for Postfix Virtual Table
*
* @param
* string The destinations
* @param string $destination The destinations
*
* @return string the corrected destinations
* @author Florian Lippert <flo@syscp.org> (2003-2009)
*/
public static function makeCorrectDestination($destination)
public static function makeCorrectDestination(string $destination): string
{
$search = '/ +/';
$replace = ' ';
@@ -406,27 +395,25 @@ class FileDir
/**
* Returns a valid html tag for the chosen $fieldType for paths
*
* @param
* string path The path to start searching in
* @param
* integer uid The uid which must match the found directories
* @param
* integer gid The gid which must match the found directories
* @param
* string value the value for the input-field
* @param string $path The path to start searching in
* @param int $uid The uid which must match the found directories
* @param int $gid The gid which must match the found directories
* @param string $value the value for the input-field
* @param bool $dom
*
* @return string The html tag for the chosen $fieldType
* @return array
*
* @author Martin Burchert <martin.burchert@syscp.de>
* @throws Exception
* @author Manuel Bernhardt <manuel.bernhardt@syscp.de>
* @author Martin Burchert <martin.burchert@syscp.de>
*/
public static function makePathfield($path, $uid, $gid, $value = '', $dom = false)
public static function makePathfield(string $path, int $uid, int $gid, string $value = '', bool $dom = false): array
{
$value = str_replace($path, '', $value);
$field = [];
// path is given without starting slash
// but dirList holds the paths with starting slash
// but dirList holds the paths with starting slash,
// so we just add one here to get the correct
// default path selected, #225
if (substr($value, 0, 1) != '/' && !$dom) {
@@ -480,16 +467,14 @@ class FileDir
* This function checks every found directory if they match either $uid or $gid, if they do
* the found directory is valid. It uses recursive-iterators to find subdirectories.
*
* @param string $path
* the path to start searching in
* @param int $uid
* the uid which must match the found directories
* @param int $gid
* the gid which must match the found directories
* @param string $path the path to start searching in
* @param int $uid the uid which must match the found directories
* @param int $gid the gid which must match the found directories
*
* @return array Array of found valid paths
* @throws Exception
*/
private static function findDirs($path, $uid, $gid)
private static function findDirs(string $path, int $uid, int $gid): array
{
$_fileList = [];
$path = self::makeCorrectDir($path);
@@ -499,7 +484,8 @@ class FileDir
// Will exclude everything under these directories
$exclude = [
'awstats',
'webalizer'
'webalizer',
'goaccess'
];
/**
@@ -565,7 +551,7 @@ class FileDir
*
* @return string functionname + parameter (not the file)
*/
private static function getImmutableFunction(bool $remove = false)
private static function getImmutableFunction(bool $remove = false): string
{
if (self::isFreeBSD()) {
// FreeBSD style
@@ -585,7 +571,7 @@ class FileDir
*
* @return bool
*/
public static function isFreeBSD(bool $exact = false)
public static function isFreeBSD(bool $exact = false): bool
{
if (($exact && PHP_OS == 'FreeBSD') || (!$exact && stristr(PHP_OS, 'BSD'))) {
return true;
@@ -606,7 +592,7 @@ class FileDir
}
/**
*
*
* @return array|false
*/
public static function getFilesystemQuota()

View File

@@ -31,10 +31,10 @@ final class Froxlor
{
// Main version variable
const VERSION = '2.0.8';
const VERSION = '2.0.12';
// Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '202301120';
const DBVERSION = '202302030';
// Distribution branding-tag (used for Debian etc.)
const BRANDING = '';
@@ -45,7 +45,7 @@ final class Froxlor
*
* @return string
*/
public static function getInstallDir()
public static function getInstallDir(): string
{
return dirname(__DIR__, 2) . '/';
}
@@ -55,7 +55,7 @@ final class Froxlor
*
* @return string
*/
public static function getVersion()
public static function getVersion(): string
{
return self::VERSION;
}
@@ -65,7 +65,7 @@ final class Froxlor
*
* @return string
*/
public static function getVersionString()
public static function getVersionString(): string
{
return self::getFullVersion() . ' (' . self::DBVERSION . ')';
}
@@ -75,7 +75,7 @@ final class Froxlor
*
* @return string
*/
public static function getFullVersion()
public static function getFullVersion(): string
{
return self::VERSION . self::BRANDING;
}
@@ -85,12 +85,11 @@ final class Froxlor
*
* checks if a given version is not equal the current one
*
* @param string $to_check
* version to check, if empty current version is used
* @param string $to_check version to check, if empty current version is used
*
* @return bool true if version to check does not match, else false
*/
public static function hasUpdates($to_check = null)
public static function hasUpdates(string $to_check = ''): bool
{
if (empty($to_check)) {
$to_check = self::VERSION;
@@ -102,16 +101,15 @@ final class Froxlor
}
/**
* Function hasUpdates
* Function hasDbUpdates
*
* checks if a given database-version is not equal the current one
*
* @param int $to_check
* version to check, if empty current dbversion is used
* @param string $to_check version to check, if empty current dbversion is used
*
* @return bool true if version to check does not match, else false
*/
public static function hasDbUpdates($to_check = null)
public static function hasDbUpdates(string $to_check = ''): bool
{
if (empty($to_check)) {
$to_check = self::DBVERSION;
@@ -127,12 +125,11 @@ final class Froxlor
*
* checks if a given database-version is the current one
*
* @param int $to_check
* version to check
* @param string $to_check version to check
*
* @return bool true if version to check matches, else false
*/
public static function isDatabaseVersion($to_check = null)
public static function isDatabaseVersion(string $to_check): bool
{
if (Settings::Get('panel.frontend') == 'froxlor' && Settings::Get('panel.db_version') == $to_check) {
return true;
@@ -146,14 +143,14 @@ final class Froxlor
* updates the panel.version field
* to the given value (no checks here!)
*
* @param string $new_version
* new-version
* @param string $new_version new-version
*
* @return bool true on success, else false
* @throws \Exception
*/
public static function updateToDbVersion($new_version = null)
public static function updateToDbVersion(string $new_version): bool
{
if ($new_version !== null && $new_version != '') {
if ($new_version != '') {
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `value` = :newversion
WHERE `settinggroup` = 'panel' AND `varname` = 'db_version'");
@@ -172,14 +169,14 @@ final class Froxlor
* updates the panel.version field
* to the given value (no checks here!)
*
* @param string $new_version
* new-version
* @param string $new_version new-version
*
* @return bool true on success, else false
* @throws \Exception
*/
public static function updateToVersion($new_version = null)
public static function updateToVersion(string $new_version): bool
{
if ($new_version !== null && $new_version != '') {
if ($new_version != '') {
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `value` = :newversion
WHERE `settinggroup` = 'panel' AND `varname` = 'version'");
@@ -199,7 +196,7 @@ final class Froxlor
*
* @return bool true if panel is froxlor, else false
*/
public static function isFroxlor()
public static function isFroxlor(): bool
{
if (Settings::Get('panel.frontend') !== null && Settings::Get('panel.frontend') == 'froxlor') {
return true;
@@ -213,12 +210,11 @@ final class Froxlor
* checks if a given version is the
* current one (and panel is froxlor)
*
* @param string $to_check
* version to check
* @param string $to_check version to check
*
* @return bool true if version to check matches, else false
*/
public static function isFroxlorVersion($to_check = null)
public static function isFroxlorVersion(string $to_check): bool
{
if (Settings::Get('panel.frontend') == 'froxlor' && Settings::Get('panel.version') == $to_check) {
return true;
@@ -231,10 +227,11 @@ final class Froxlor
*
* @param int $length
* @return string
* @throws \Exception
*/
public static function genSessionId(int $length = 16)
public static function genSessionId(int $length = 16): string
{
if (intval($length) <= 8) {
if ($length <= 8) {
$length = 16;
}
if (function_exists('random_bytes')) {
@@ -256,9 +253,9 @@ final class Froxlor
* @param string $a
* @param string $b
*
* @return integer 0 if equal, 1 if a>b and -1 if b>a
* @return int 0 if equal, 1 if a>b and -1 if b>a
*/
public static function versionCompare2($a, $b)
public static function versionCompare2(string $a, string $b): int
{
// split version into pieces and remove trailing .0
$a = explode(".", $a);
@@ -295,7 +292,11 @@ final class Froxlor
return (count($a) < count($b)) ? -1 : 0;
}
private static function parseVersionArray(&$arr = null)
/**
* @param array|null $arr
* @return void
*/
private static function parseVersionArray(array &$arr = null)
{
// -dev or -beta or -rc ?
if (stripos($arr[count($arr) - 1], '-') !== false) {
@@ -306,16 +307,20 @@ final class Froxlor
$arr[] = '2'; // dev < beta < rc
// number of rc
$arr[] = substr($x[1], 2);
} else if (stripos($x[1], 'beta') !== false) {
$arr[] = '-1';
$arr[] = '1'; // dev < beta < rc
// number of beta
$arr[] = substr($x[1], 3);
} else if (stripos($x[1], 'dev') !== false) {
$arr[] = '-1';
$arr[] = '0'; // dev < beta < rc
// number of dev
$arr[] = substr($x[1], 3);
} else {
if (stripos($x[1], 'beta') !== false) {
$arr[] = '-1';
$arr[] = '1'; // dev < beta < rc
// number of beta
$arr[] = substr($x[1], 3);
} else {
if (stripos($x[1], 'dev') !== false) {
$arr[] = '-1';
$arr[] = '0'; // dev < beta < rc
// number of dev
$arr[] = substr($x[1], 3);
}
}
}
}
}

View File

@@ -45,38 +45,42 @@ class FroxlorLogger
/**
* current \Monolog\Logger object
*
* @var Logger
* @var ?Logger
*/
private static $ml = null;
private static ?Logger $ml = null;
/**
* LogTypes Array
*
* @var array
* @var ?array
*/
private static $logtypes = null;
private static ?array $logtypes = null;
/**
* whether to output log-messages to STDOUT (cron)
*
* @var bool
*/
private static $crondebug_flag = false;
private static bool $crondebug_flag = false;
/**
* user info of logged in user
* user info of logged-in user
*
* @var array
*/
private static $userinfo = [];
private static array $userinfo = [];
/**
* whether the logger object has already been initialized
*
* @var bool
*/
private static $is_initialized = false;
private static bool $is_initialized = false;
/**
* Class constructor.
*
* @param array $userinfo
*
* @throws \Exception
*/
protected function __construct($userinfo = [])
protected function __construct(array $userinfo = [])
{
$this->initMonolog();
self::$userinfo = $userinfo;
@@ -143,8 +147,9 @@ class FroxlorLogger
* @param array $userinfo
*
* @return FroxlorLogger
* @throws \Exception
*/
public static function getInstanceOf($userinfo = [])
public static function getInstanceOf(array $userinfo = [])
{
if (empty($userinfo)) {
$userinfo = [
@@ -159,9 +164,9 @@ class FroxlorLogger
*
* @param int $action
* @param int $type
* @param string $text
* @param ?string $text
*/
public function logAction($action = FroxlorLogger::USR_ACTION, $type = LOG_NOTICE, $text = null)
public function logAction($action = FroxlorLogger::USR_ACTION, int $type = LOG_NOTICE, string $text = null)
{
// not logging normal stuff if not set to "paranoid" logging
if (!self::$crondebug_flag && Settings::Get('logger.severity') == '1' && $type > LOG_NOTICE) {
@@ -208,7 +213,11 @@ class FroxlorLogger
}
}
public function getLogLevelDesc($type)
/**
* @param int $type
* @return string
*/
public function getLogLevelDesc(int $type): string
{
switch ($type) {
case LOG_INFO:
@@ -236,7 +245,11 @@ class FroxlorLogger
return $_type;
}
private function getActionTypeDesc($action)
/**
* @param $action
* @return string
*/
private function getActionTypeDesc($action): string
{
switch ($action) {
case FroxlorLogger::USR_ACTION:
@@ -268,7 +281,7 @@ class FroxlorLogger
*
* @return int
*/
public function setCronLog(int $cronlog = 0)
public function setCronLog(int $cronlog = 0): int
{
if ($cronlog < 0 || $cronlog > 2) {
$cronlog = 0;

View File

@@ -47,15 +47,17 @@ class Directory
*
* @param string $dir
*/
public function __construct($dir = null)
public function __construct(string $dir = null)
{
$this->dir = $dir;
}
/**
* check whether the directory has options set in panel_htaccess
*
* @return bool
*/
public function hasUserOptions()
public function hasUserOptions(): bool
{
$uo_stmt = Database::prepare("
SELECT COUNT(`id`) as `usropts` FROM `" . TABLE_PANEL_HTACCESS . "` WHERE `path` = :dir
@@ -63,7 +65,7 @@ class Directory
$uo_res = Database::pexecute_first($uo_stmt, [
'dir' => FileDir::makeCorrectDir($this->dir)
]);
if ($uo_res != false && isset($uo_res['usropts'])) {
if ($uo_res && isset($uo_res['usropts'])) {
return $uo_res['usropts'] > 0;
}
return false;
@@ -71,8 +73,10 @@ class Directory
/**
* check whether the directory is protected using panel_htpasswd
*
* @return bool
*/
public function isUserProtected()
public function isUserProtected(): bool
{
$up_stmt = Database::prepare("
SELECT COUNT(`id`) as `usrprot` FROM `" . TABLE_PANEL_HTPASSWDS . "` WHERE `path` = :dir
@@ -80,7 +84,7 @@ class Directory
$up_res = Database::pexecute_first($up_stmt, [
'dir' => FileDir::makeCorrectDir($this->dir)
]);
if ($up_res != false && isset($up_res['usrprot'])) {
if ($up_res && isset($up_res['usrprot'])) {
return $up_res['usrprot'] > 0;
}
return false;
@@ -90,12 +94,11 @@ class Directory
* Checks if a given directory is valid for multiple configurations
* or should rather be used as a single file
*
* @param bool $ifexists
* also check whether file/dir exists
* @param bool $ifexists also check whether file/dir exists
*
* @return bool true if usable as dir, false otherwise
*/
public function isConfigDir($ifexists = false)
public function isConfigDir(bool $ifexists = false): bool
{
if (is_null($this->dir)) {
trigger_error(__CLASS__ . '::' . __FUNCTION__ . ' has been called with a null value', E_USER_WARNING);

View File

@@ -33,6 +33,10 @@ class HttpClient
/**
* Executes simple GET request
*
* @param string $url
* @param bool $follow_location
* @param int $timeout
*
* @return bool|string
* @throws Exception
*/
@@ -59,6 +63,10 @@ class HttpClient
/**
* Downloads and stores a file from an url
*
* @param string $url
* @param string $target
*
* @return bool|string
* @throws Exception
*/
public static function fileGet(string $url, string $target)

View File

@@ -37,7 +37,7 @@ class PhpConfig
*
* @return array
*/
public static function getPhpConfigs()
public static function getPhpConfigs(): array
{
$configs_array = [];

View File

@@ -36,16 +36,22 @@ class Statistics
* Create or modify the AWStats configuration file for the given domain.
* Modified by Berend Dekens to allow custom configurations.
*
* @param
* logFile
* @param
* siteDomain
* @param
* hostAliases
* @param string $logFile
* @param string $siteDomain
* @param string $hostAliases
* @param string $customerDocroot
* @param array $domain_data
*
* @return null
* @throws \Exception
*/
public static function createAWStatsConf($logFile, $siteDomain, $hostAliases, $customerDocroot, $awstats_params = [])
{
public static function createAWStatsConf(
string $logFile,
string $siteDomain,
string $hostAliases,
string $customerDocroot,
array $domain_data = []
) {
// Generation header
$header = "## GENERATED BY FROXLOR\n";
$header2 = "## Do not remove the line above! This tells Froxlor to update this configuration\n## If you wish to manually change this configuration file, remove the first line to make sure Froxlor won't rebuild this file\n## Generated for domain {SITE_DOMAIN} on " . date('l dS \of F Y h:i:s A') . "\n";
@@ -55,7 +61,7 @@ class Statistics
FileDir::safe_exec('mkdir -p ' . escapeshellarg($awstats_dir));
}
// chown created folder, #258
self::makeChownWithNewStats($awstats_params);
self::makeChownWithNewStats($domain_data);
// weird but could happen...
if (!is_dir(Settings::Get('system.awstats_conf'))) {
@@ -127,16 +133,15 @@ class Statistics
}
/**
* chowns either awstats or webalizer folder,
* either with webserver-user or - if fcgid
* is used - the customers name, #258
* chowns stats-tools folder, either with webserver-user or
* if fcgid/php-fpm is used, the customers name, #258
*
* @param array $row
* array if panel_customers
* @param array $row array of panel_customers
*
* @return void
* @throws \Exception
*/
public static function makeChownWithNewStats($row)
public static function makeChownWithNewStats(array $row)
{
// get correct user
if ((Settings::Get('system.mod_fcgid') == '1' || Settings::Get('phpfpm.enabled') == '1') && isset($row['deactivated']) && $row['deactivated'] == '0') {

View File

@@ -54,16 +54,15 @@ class IdnaWrapper
}
/**
* Encode a domain name, a email address or a list of one of both.
* Encode a domain name, an email address or a list of one of both.
*
* @param
* string May be either a single domain name, e single email address or a list of one
* @param string $to_encode May be either a single domain name, e single email address or a list of one
* separated either by ',', ';' or ' '.
*
* @return string Returns either a single domain name, a single email address or a list of one of
* both separated by the same string as the input.
*/
public function encode($to_encode)
public function encode(string $to_encode): string
{
$to_encode = $this->isUtf8($to_encode) ? $to_encode : utf8_encode($to_encode);
try {
@@ -83,7 +82,7 @@ class IdnaWrapper
*
* @return boolean
*/
private function isUtf8($string = null)
private function isUtf8(string $string)
{
if (function_exists("mb_detect_encoding")) {
if (mb_detect_encoding($string, 'UTF-8, ISO-8859-1') === 'UTF-8') {
@@ -119,16 +118,15 @@ class IdnaWrapper
}
/**
* Decode a domain name, a email address or a list of one of both.
* Decode a domain name, an email address or a list of one of both.
*
* @param
* string May be either a single domain name, e single email address or a list of one
* @param string $to_decode May be either a single domain name, e single email address or a list of one
* separated either by ',', ';' or ' '.
*
* @return string Returns either a single domain name, a single email address or a list of one of
* both separated by the same string as the input.
*/
public function decode($to_decode)
public function decode(string $to_decode): string
{
return $this->idna_converter->decode($to_decode);
}

View File

@@ -420,7 +420,7 @@ class Core
}
$this->updateSetting($upd_stmt, $this->validatedData['activate_newsfeed'], 'admin', 'show_news_feed');
$this->updateSetting($upd_stmt, dirname(__FILE__, 3), 'system', 'letsencryptchallengepath');
$this->updateSetting($upd_stmt, dirname(__FILE__, 5), 'system', 'letsencryptchallengepath');
// insert the lastcronrun to be the installation date
$this->updateSetting($upd_stmt, time(), 'system', 'lastcronrun');

View File

@@ -101,7 +101,7 @@ class Preconfig
$agree = [
'title' => 'Check',
'fields' => [
'update_changesagreed' => ['type' => 'checkbox', 'value' => 1, 'label' => '<strong>I have read the update notifications above and I am aware of the changes made to my system.</strong>'],
'update_changesagreed' => ['mandatory' => true, 'type' => 'checkrequired', 'value' => 1, 'label' => '<strong>I have read the update notifications above and I am aware of the changes made to my system.</strong>'],
'update_preconfig' => ['type' => 'hidden', 'value' => 1]
]
];

View File

@@ -27,6 +27,8 @@ namespace Froxlor;
use Exception;
use Froxlor\UI\Panel\UI;
use Net_DNS2_Exception;
use Net_DNS2_Resolver;
use Throwable;
use voku\helper\AntiXSS;
@@ -41,9 +43,9 @@ class PhpHelper
* @param array $list
* @param string $key
*
* @return boolean
* @return bool
*/
public static function sortListBy(&$list, $key = 'id')
public static function sortListBy(array &$list, string $key = 'id'): bool
{
self::$sort_type = Settings::Get('panel.natsorting') == 1 ? SORT_NATURAL : SORT_STRING;
self::$sort_key = $key;
@@ -57,14 +59,10 @@ class PhpHelper
* Wrapper around htmlentities to handle arrays, with the advantage that you
* can select which fields should be handled by htmlentities
*
* @param array|string $subject
* The subject array
* @param string $fields
* The fields which should be checked for, separated by spaces
* @param int $quote_style
* See php documentation about this
* @param string $charset
* See php documentation about this
* @param array|string $subject The subject array
* @param array|string $fields The fields which should be checked for, separated by spaces
* @param int $quote_style See php documentation about this
* @param string $charset See php documentation about this
*
* @return array|string The string or an array with htmlentities converted strings
* @author Florian Lippert <flo@syscp.org> (2003-2009)
@@ -77,7 +75,7 @@ class PhpHelper
}
foreach ($subject as $field => $value) {
if ((!is_array($fields) || empty($fields)) || (is_array($fields) && !empty($fields) && in_array($field, $fields))) {
if ((!is_array($fields) || empty($fields)) || (in_array($field, $fields))) {
// Just call ourselve to manage multi-dimensional arrays
$subject[$field] = self::htmlentitiesArray($value, $fields, $quote_style, $charset);
}
@@ -92,45 +90,36 @@ class PhpHelper
/**
* Returns array with all empty-values removed
*
* @param array $source
* The array to trim
* @param array $source The array to trim
* @return array The trim'med array
*/
public static function arrayTrim($source)
public static function arrayTrim(array $source): array
{
$returnval = [];
if (is_array($source)) {
$source = array_map('trim', $source);
$returnval = array_filter($source, function ($value) {
return $value !== '';
});
} else {
$returnval = $source;
}
return $returnval;
$source = array_map('trim', $source);
return array_filter($source, function ($value) {
return $value !== '';
});
}
/**
* Replaces Strings in an array, with the advantage that you
* can select which fields should be str_replace'd
*
* @param string|array $search
* String or array of strings to search for
* @param string|array $reaplce
* String or array to replace with
* @param string|array $subject
* String or array The subject array
* @param string $fields
* string The fields which should be checked for, separated by spaces
* @param string|array $search String or array of strings to search for
* @param string|array $replace String or array to replace with
* @param string|array $subject String or array The subject array
* @param string|array $fields string The fields which should be checked for, separated by spaces
*
* @return string|array The str_replace'd array
* @author Florian Lippert <flo@syscp.org> (2003-2009)
*/
public static function strReplaceArray($search, $replace, $subject, $fields = '')
{
if (is_array($subject)) {
$fields = self::arrayTrim(explode(' ', $fields));
if (!is_array($fields)) {
$fields = self::arrayTrim(explode(' ', $fields));
}
foreach ($subject as $field => $value) {
if ((!is_array($fields) || empty($fields)) || (is_array($fields) && !empty($fields) && in_array($field, $fields))) {
if ((!is_array($fields) || empty($fields)) || (in_array($field, $fields))) {
$subject[$field] = str_replace($search, $replace, $value);
}
}
@@ -175,7 +164,8 @@ class PhpHelper
$err_display .= '<br><p><pre>';
$debug = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
foreach ($debug as $dline) {
$err_display .= $dline['function'] . '() called at [' . str_replace(Froxlor::getInstallDir(), '', ($dline['file'] ?? 'unknown')) . ':' . ($dline['line'] ?? 0) . ']<br>';
$err_display .= $dline['function'] . '() called at [' . str_replace(Froxlor::getInstallDir(), '',
($dline['file'] ?? 'unknown')) . ':' . ($dline['line'] ?? 0) . ']<br>';
}
$err_display .= '</pre></p>';
// end later
@@ -191,9 +181,13 @@ class PhpHelper
return false;
}
/**
* @param Throwable $exception
* @return void
*/
public static function phpExceptionHandler(Throwable $exception)
{
if (!isset($_SERVER['SHELL']) || (isset($_SERVER['SHELL']) && $_SERVER['SHELL'] == '')) {
if (!isset($_SERVER['SHELL']) || $_SERVER['SHELL'] == '') {
// show
UI::initTwig(true);
UI::twig()->addGlobal('install_mode', '1');
@@ -208,6 +202,10 @@ class PhpHelper
}
}
/**
* @param ...$configdirs
* @return array|null
*/
public static function loadConfigArrayDir(...$configdirs)
{
if (count($configdirs) <= 0) {
@@ -222,7 +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;
}
}
@@ -244,45 +243,64 @@ class PhpHelper
* ipv6 aware gethostbynamel function
*
* @param string $host
* @param boolean $try_a
* default true
* @return boolean|array
* @param boolean $try_a default true
* @param string|null $nameserver set additional resolver nameserver to use (e.g. 1.1.1.1)
* @return bool|array
*/
public static function gethostbynamel6($host, $try_a = true)
public static function gethostbynamel6(string $host, bool $try_a = true, string $nameserver = null)
{
$dns6 = @dns_get_record($host, DNS_AAAA);
if (!is_array($dns6)) {
// no record or failed to check
$dns6 = [];
}
if ($try_a == true) {
$dns4 = @dns_get_record($host, DNS_A);
if (!is_array($dns4)) {
// no record or failed to check
$dns4 = [];
}
$dns = array_merge($dns4, $dns6);
} else {
$dns = $dns6;
}
$ips = [];
foreach ($dns as $record) {
if ($record["type"] == "A") {
// always use compressed ipv6 format
$ip = inet_ntop(inet_pton($record["ip"]));
$ips[] = $ip;
try {
// set the default nameservers to use, use the system default if none are provided
$resolver = new Net_DNS2_Resolver($nameserver ? ['nameservers' => [$nameserver]] : []);
// get all ip addresses from the A record and normalize them
if ($try_a) {
try {
$answer = $resolver->query($host, 'A')->answer;
foreach ($answer as $rr) {
if ($rr instanceof \Net_DNS2_RR_A) {
$ips[] = inet_ntop(inet_pton($rr->address));
}
}
} catch (Net_DNS2_Exception $e) {
// we can't do anything here, just continue
}
}
if ($record["type"] == "AAAA") {
// always use compressed ipv6 format
$ip = inet_ntop(inet_pton($record["ipv6"]));
$ips[] = $ip;
// get all ip addresses from the AAAA record and normalize them
try {
$answer = $resolver->query($host, 'AAAA')->answer;
foreach ($answer as $rr) {
if ($rr instanceof \Net_DNS2_RR_AAAA) {
$ips[] = inet_ntop(inet_pton($rr->address));
}
}
} catch (Net_DNS2_Exception $e) {
// we can't do anything here, just continue
}
} catch (Net_DNS2_Exception $e) {
// fallback to php's dns_get_record if Net_DNS2 has no resolver available, but this may cause
// problems if the system's dns is not configured correctly; for example, the acme pre-check
// will fail because some providers put a local ip in /etc/hosts
// get all ip addresses from the A record and normalize them
if ($try_a) {
$answer = @dns_get_record($host, DNS_A);
foreach ($answer as $rr) {
$ips[] = inet_ntop(inet_pton($rr['ip']));
}
}
// get all ip addresses from the AAAA record and normalize them
$answer = @dns_get_record($host, DNS_AAAA);
foreach ($answer as $rr) {
$ips[] = inet_ntop(inet_pton($rr['ipv6']));
}
}
if (count($ips) < 1) {
return false;
} else {
return $ips;
}
return count($ips) > 0 ? $ips : false;
}
/**
@@ -294,7 +312,7 @@ class PhpHelper
* @return string
* @throws Exception
*/
public static function randomStr($length)
public static function randomStr(int $length): string
{
if (function_exists('openssl_random_pseudo_bytes')) {
return openssl_random_pseudo_bytes($length);
@@ -303,20 +321,21 @@ class PhpHelper
}
/**
* Return human readable sizes
* Return human-readable sizes
*
* @param int $size
* size in bytes
* @param string $max
* maximum unit
* @param string $system
* 'si' for SI, 'bi' for binary prefixes
* @param int $size size in bytes
* @param ?string $max maximum unit
* @param string $system 'si' for SI, 'bi' for binary prefixes
* @param string $retstring string-format
*
* @param string $retstring
* string
* @return string
*/
public static function sizeReadable($size, $max = null, $system = 'si', $retstring = '%01.2f %s')
{
public static function sizeReadable(
int $size,
?string $max = '',
string $system = 'si',
string $retstring = '%01.2f %s'
): string {
// Pick units
$systems = [
'si' => [
@@ -342,7 +361,7 @@ class PhpHelper
'size' => 1024
]
];
$sys = isset($systems[$system]) ? $systems[$system] : $systems['si'];
$sys = $systems[$system] ?? $systems['si'];
// Max unit to display
$depth = count($sys['prefix']) - 1;
@@ -363,14 +382,12 @@ class PhpHelper
* Replaces all occurrences of variables defined in the second argument
* in the first argument with their values.
*
* @param string $text
* The string that should be searched for variables
* @param array $vars
* The array containing the variables with their values
* @param string $text The string that should be searched for variables
* @param array $vars The array containing the variables with their values
*
* @return string The submitted string with the variables replaced.
*/
public static function replaceVariables($text, $vars)
public static function replaceVariables(string $text, array $vars): string
{
$pattern = "/\{([a-zA-Z0-9\-_]+)\}/";
$matches = [];
@@ -386,12 +403,22 @@ class PhpHelper
}
}
$text = str_replace('\n', "\n", $text);
return $text;
return str_replace('\n', "\n", $text);
}
public static function recursive_array_search($needle, $haystack, &$keys = [], $currentKey = '')
{
/**
* @param string $needle
* @param array $haystack
* @param array $keys
* @param string $currentKey
* @return true
*/
public static function recursive_array_search(
string $needle,
array $haystack,
array &$keys = [],
string $currentKey = ''
): bool {
foreach ($haystack as $key => $value) {
$pathkey = empty($currentKey) ? $key : $currentKey . '.' . $key;
if (is_array($value)) {
@@ -406,13 +433,13 @@ class PhpHelper
}
/**
* function to check a super-global passed by reference
* function to check a super-global passed by reference,
* so it gets automatically updated
*
* @param array $global
* @param AntiXSS $antiXss
*/
public static function cleanGlobal(&$global, &$antiXss)
public static function cleanGlobal(array &$global, AntiXSS &$antiXss)
{
$ignored_fields = [
'system_default_vhostconf',
@@ -434,7 +461,12 @@ class PhpHelper
}
}
private static function sortListByGivenKey($a, $b): int
/**
* @param array $a
* @param array $b
* @return int
*/
private static function sortListByGivenKey(array $a, array $b): int
{
if (self::$sort_type == SORT_NATURAL) {
return strnatcasecmp($a[self::$sort_key], $b[self::$sort_key]);
@@ -467,35 +499,33 @@ class PhpHelper
/**
* Parse array to array string.
*
* @param $array
* @param $key
* @param array $array
* @param ?string $key
* @param int $depth
* @return string
*/
public static function parseArrayToString($array, $key = null, int $depth = 1): string
public static function parseArrayToString(array $array, string $key = null, int $depth = 1): string
{
$str = '';
if (is_array($array)) {
if (!is_null($key)) {
$str .= self::tabPrefix(($depth-1), "'{$key}' => [\n");
} else {
$str .= self::tabPrefix(($depth-1), "[\n");
}
foreach ($array as $key => $value) {
if (!is_array($value)) {
if (is_bool($value)) {
$str .= self::tabPrefix($depth, sprintf("'%s' => %s,\n", $key, $value ? 'true' : 'false'));
} elseif (is_int($value)) {
$str .= self::tabPrefix($depth, "'{$key}' => $value,\n");
} else {
$str .= self::tabPrefix($depth, "'{$key}' => '{$value}',\n");
}
} elseif (is_array($value)) {
$str .= self::parseArrayToString($value, $key, ($depth + 1));
}
}
$str .= self::tabPrefix(($depth-1), "],\n");
if (!is_null($key)) {
$str .= self::tabPrefix(($depth - 1), "'{$key}' => [\n");
} else {
$str .= self::tabPrefix(($depth - 1), "[\n");
}
foreach ($array as $key => $value) {
if (!is_array($value)) {
if (is_bool($value)) {
$str .= self::tabPrefix($depth, sprintf("'%s' => %s,\n", $key, $value ? 'true' : 'false'));
} elseif (is_int($value)) {
$str .= self::tabPrefix($depth, "'{$key}' => $value,\n");
} else {
$str .= self::tabPrefix($depth, "'{$key}' => '{$value}',\n");
}
} else {
$str .= self::parseArrayToString($value, $key, ($depth + 1));
}
}
$str .= self::tabPrefix(($depth - 1), "],\n");
return $str;
}

View File

@@ -27,6 +27,7 @@ namespace Froxlor;
use Exception;
use Froxlor\Database\Database;
use Froxlor\UI\Form;
use PDO;
/**
@@ -79,14 +80,16 @@ class SImExporter
$_data[$index] = $row['value'];
}
if (array_key_exists($row['settinggroup'], $settings_definitions) && array_key_exists($row['varname'], $settings_definitions[$row['settinggroup']])) {
if (array_key_exists($row['settinggroup'], $settings_definitions) && array_key_exists($row['varname'],
$settings_definitions[$row['settinggroup']])) {
// Export image file
if ($settings_definitions[$row['settinggroup']][$row['varname']]['type'] === "image") {
if ($row['value'] === "") {
continue;
}
$_data[$index . '.image_data'] = base64_encode(file_get_contents(explode('?', $row['value'], 2)[0]));
$_data[$index . '.image_data'] = base64_encode(file_get_contents(explode('?', $row['value'],
2)[0]));
}
}
}
@@ -140,66 +143,96 @@ class SImExporter
$_data['system.le_froxlor_redirect'] = 0;
}
}
// store new data
foreach ($_data as $index => $value) {
$index_split = explode('.', $index, 3);
// Catch image_data and save it
if (isset($index_split[2]) && $index_split[2] === 'image_data' && !empty($_data[$index_split[0] . '.' . $index_split[1]])) {
$path = Froxlor::getInstallDir() . '/img/';
if (!is_dir($path) && !mkdir($path, 0775)) {
throw new Exception("img directory does not exist and cannot be created");
$form_data = [];
$image_data = [];
// read in all current settings
$current_settings = Settings::getAll();
foreach ($current_settings as $setting_group => $setting) {
foreach ($setting as $varname => $value) {
// set all group/varname:values which are not in the import file
if (!array_key_exists($setting_group . '.' . $varname, $_data)) {
$_data[$setting_group . '.' . $varname] = $value;
}
// Make sure we can write to the upload directory
if (!is_writable($path)) {
if (!chmod($path, 0775)) {
throw new Exception("Cannot write to img directory");
}
}
$img_data = base64_decode($value);
$img_filename = Froxlor::getInstallDir() . '/' . str_replace('../', '', explode('?', $_data[$index_split[0] . '.' . $index_split[1]], 2)[0]);
file_put_contents($img_filename, $img_data);
if (function_exists('finfo_open')) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimetype = finfo_file($finfo, $img_filename);
finfo_close($finfo);
} else {
$mimetype = mime_content_type($img_filename);
}
if (empty($mimetype)) {
$mimetype = 'application/octet-stream';
}
if (!in_array($mimetype, ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'])) {
@unlink($img_filename);
throw new Exception("Uploaded file is not a valid image");
}
$spl = explode('.', $img_filename);
$file_extension = strtolower(array_pop($spl));
unset($spl);
if (!in_array($file_extension, [
'jpeg',
'jpg',
'png',
'gif'
])) {
@unlink($img_filename);
throw new Exception("Invalid file-extension, use one of: jpeg, jpg, png, gif");
}
continue;
}
Settings::Set($index, $value);
}
// save to DB
Settings::Flush();
// all good
return true;
// re-format the array-key for Form::processForm
foreach ($_data as $key => $value) {
$index_split = explode('.', $key, 3);
if (isset($index_split[2]) && $index_split[2] === 'image_data' && !empty($_data[$index_split[0] . '.' . $index_split[1]])) {
$image_data[$key] = $value;
} else {
$form_data[str_replace(".", "_", $key)] = $value;
}
}
// store new data
$settings_data = PhpHelper::loadConfigArrayDir(Froxlor::getInstallDir() . '/actions/admin/settings/');
Settings::loadSettingsInto($settings_data);
if (Form::processForm($settings_data, $form_data, [], null, true)) {
// save to DB
Settings::Flush();
// Process image_data and save it
if (count($image_data) > 0) {
foreach ($image_data as $index => $value) {
$index_split = explode('.', $index, 3);
$path = Froxlor::getInstallDir() . '/img/';
if (!is_dir($path) && !mkdir($path, 0775)) {
throw new Exception("img directory does not exist and cannot be created");
}
// Make sure we can write to the upload directory
if (!is_writable($path)) {
if (!chmod($path, 0775)) {
throw new Exception("Cannot write to img directory");
}
}
$img_data = base64_decode($value);
$img_filename = Froxlor::getInstallDir() . '/' . str_replace('../', '',
explode('?', $_data[$index_split[0] . '.' . $index_split[1]], 2)[0]);
file_put_contents($img_filename, $img_data);
if (function_exists('finfo_open')) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimetype = finfo_file($finfo, $img_filename);
finfo_close($finfo);
} else {
$mimetype = mime_content_type($img_filename);
}
if (empty($mimetype)) {
$mimetype = 'application/octet-stream';
}
if (!in_array($mimetype, ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'])) {
@unlink($img_filename);
throw new Exception("Uploaded file is not a valid image");
}
$spl = explode('.', $img_filename);
$file_extension = strtolower(array_pop($spl));
unset($spl);
if (!in_array($file_extension, [
'jpeg',
'jpg',
'png',
'gif'
])) {
@unlink($img_filename);
throw new Exception("Invalid file-extension, use one of: jpeg, jpg, png, gif");
}
Settings::Set($index, $value);
}
}
// all good
return true;
} else {
throw new Exception("Importing settings failed");
}
}
throw new Exception("Invalid JSON data: " . json_last_error_msg());
}

View File

@@ -329,6 +329,12 @@ class Settings
}
}
public static function getAll() : array
{
self::init();
return self::$data;
}
/**
* get value from config by identifier
*/

View File

@@ -30,10 +30,19 @@ use Froxlor\Database\Database;
class FroxlorVhostSettings
{
public static function hasVhostContainerEnabled($need_ssl = false)
/**
* @param bool $need_ssl
*
* @return bool
* @throws \Exception
*/
public static function hasVhostContainerEnabled(bool $need_ssl = false): bool
{
$sel_stmt = Database::prepare("SELECT COUNT(*) as vcentries FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `vhostcontainer`= '1'" . ($need_ssl ? " AND `ssl` = '1'" : ""));
$result = Database::pexecute_first($sel_stmt);
return $result['vcentries'] > 0;
if ($result) {
return $result['vcentries'] > 0;
}
return false;
}
}

View File

@@ -100,32 +100,34 @@ class Cronjob
// now check if it differs from our settings
if ($update_to_guid != Settings::Get('system.lastguid')) {
$mylog->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'Updating froxlor last guid to ' . $update_to_guid);
$mylog->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE,
'Updating froxlor last guid to ' . $update_to_guid);
Settings::Set('system.lastguid', $update_to_guid);
}
} else {
$mylog->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'File /etc/group not readable; cannot check for latest guid');
$mylog->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE,
'File /etc/group not readable; cannot check for latest guid');
}
} else {
$mylog->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'File /etc/group not readable; cannot check for latest guid');
$mylog->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE,
'File /etc/group not readable; cannot check for latest guid');
}
} else {
$mylog->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'File /etc/group does not exist; cannot check for latest guid');
$mylog->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE,
'File /etc/group does not exist; cannot check for latest guid');
}
}
/**
* Inserts a task into the PANEL_TASKS-Table
*
* @param
* int Type of task
* @param
* string Parameter (possible to pass multiple times)
* @param int $type Type of task
* @param string $params Parameter (possible to pass multiple times)
*
* @author Florian Lippert <flo@syscp.org> (2003-2009)
* @throws Exception
* @author Froxlor team <team@froxlor.org> (2010-)
*/
public static function inserttask($type, ...$params)
public static function inserttask(int $type, ...$params)
{
// prepare the insert-statement
$ins_stmt = Database::prepare("
@@ -223,7 +225,7 @@ class Cronjob
*
* @return array
*/
public static function getCronjobsLastRun()
public static function getCronjobsLastRun(): array
{
$query = "SELECT `lastrun`, `desc_lng_key` FROM `" . TABLE_PANEL_CRONRUNS . "` WHERE `isactive` = '1' ORDER BY `cronfile` ASC";
$result = Database::query($query);
@@ -238,14 +240,21 @@ class Cronjob
return $cronjobs_last_run;
}
public static function toggleCronStatus($module = null, $isactive = 0)
/**
* @param string $module
* @param int $isactive
* @return void
* @throws Exception
*/
public static function toggleCronStatus(string $module, int $isactive = 0)
{
if ($isactive != 1) {
$isactive = 0;
}
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET `isactive` = :active WHERE `module` = :module");
UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET `isactive` = :active WHERE `module` = :module
");
Database::pexecute($upd_stmt, [
'active' => $isactive,
'module' => $module
@@ -257,7 +266,7 @@ class Cronjob
*
* @return array
*/
public static function getOutstandingTasks()
public static function getOutstandingTasks(): array
{
$query = "SELECT * FROM `" . TABLE_PANEL_TASKS . "` ORDER BY `type` ASC";
$result = Database::query($query);
@@ -309,7 +318,7 @@ class Cronjob
*
* @return void
*/
public static function dieWithMail($message, $subject = "[froxlor] Cronjob error")
public static function dieWithMail(string $message, string $subject = "[froxlor] Cronjob error")
{
if (Settings::Get('system.send_cron_errors') == '1') {
$_mail = new Mailer(true);
@@ -339,7 +348,12 @@ class Cronjob
die($message);
}
public static function updateLastRunOfCron($cronname)
/**
* @param string $cronname
* @return void
* @throws Exception
*/
public static function updateLastRunOfCron(string $cronname)
{
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET `lastrun` = UNIX_TIMESTAMP() WHERE `cronfile` = :cron;

View File

@@ -41,7 +41,7 @@ class Crypt
*
* @return string
*/
public static function generatePassword(int $length = 0, bool $isSalt = false)
public static function generatePassword(int $length = 0, bool $isSalt = false): string
{
$alpha_lower = 'abcdefghijklmnopqrstuvwxyz';
$alpha_upper = strtoupper($alpha_lower);
@@ -78,7 +78,7 @@ class Crypt
*
* @return string
*/
private static function specialShuffle($str = null)
private static function specialShuffle(string $str): string
{
$len = mb_strlen($str);
$sploded = [];
@@ -94,7 +94,7 @@ class Crypt
*
* @return array
*/
public static function getAvailablePasswordHashes()
public static function getAvailablePasswordHashes(): array
{
// get available pwd-hases
$available_pwdhashes = [
@@ -120,31 +120,40 @@ class Crypt
* we check against the length, if not matched
* an error message will be output and 'exit' is called
*
* @param string $password
* the password to validate
* @param string $password the password to validate
* @param bool $json_response
*
* @return string either the password or an errormessage+exit
*/
public static function validatePassword($password = null, $json_response = false)
public static function validatePassword(string $password, bool $json_response = false): string
{
if (Settings::Get('panel.password_min_length') > 0) {
$password = Validate::validate($password, Settings::Get('panel.password_min_length'), '/^.{' . (int)Settings::Get('panel.password_min_length') . ',}$/D', 'notrequiredpasswordlength', [], $json_response);
$password = Validate::validate($password, Settings::Get('panel.password_min_length'),
'/^.{' . (int)Settings::Get('panel.password_min_length') . ',}$/D', 'notrequiredpasswordlength', [],
$json_response);
}
if (Settings::Get('panel.password_regex') != '') {
$password = Validate::validate($password, Settings::Get('panel.password_regex'), Settings::Get('panel.password_regex'), 'notrequiredpasswordcomplexity', [], $json_response);
$password = Validate::validate($password, Settings::Get('panel.password_regex'),
Settings::Get('panel.password_regex'), 'notrequiredpasswordcomplexity', [], $json_response);
} else {
if (Settings::Get('panel.password_alpha_lower')) {
$password = Validate::validate($password, '/.*[a-z]+.*/', '/.*[a-z]+.*/', 'notrequiredpasswordcomplexity', [], $json_response);
$password = Validate::validate($password, '/.*[a-z]+.*/', '/.*[a-z]+.*/',
'notrequiredpasswordcomplexity', [], $json_response);
}
if (Settings::Get('panel.password_alpha_upper')) {
$password = Validate::validate($password, '/.*[A-Z]+.*/', '/.*[A-Z]+.*/', 'notrequiredpasswordcomplexity', [], $json_response);
$password = Validate::validate($password, '/.*[A-Z]+.*/', '/.*[A-Z]+.*/',
'notrequiredpasswordcomplexity', [], $json_response);
}
if (Settings::Get('panel.password_numeric')) {
$password = Validate::validate($password, '/.*[0-9]+.*/', '/.*[0-9]+.*/', 'notrequiredpasswordcomplexity', [], $json_response);
$password = Validate::validate($password, '/.*[0-9]+.*/', '/.*[0-9]+.*/',
'notrequiredpasswordcomplexity', [], $json_response);
}
if (Settings::Get('panel.password_special_char_required')) {
$password = Validate::validate($password, '/.*[' . preg_quote(Settings::Get('panel.password_special_char'), '/') . ']+.*/', '/.*[' . preg_quote(Settings::Get('panel.password_special_char'), '/') . ']+.*/', 'notrequiredpasswordcomplexity', [], $json_response);
$password = Validate::validate($password,
'/.*[' . preg_quote(Settings::Get('panel.password_special_char'), '/') . ']+.*/',
'/.*[' . preg_quote(Settings::Get('panel.password_special_char'), '/') . ']+.*/',
'notrequiredpasswordcomplexity', [], $json_response);
}
}
@@ -159,19 +168,20 @@ class Crypt
* additionally it updates the hash if the system settings changed
* or if the very old md5() sum is used
*
* @param array $userinfo
* user-data from table
* @param string $password
* the password to validate
* @param string $table
* either panel_customers or panel_admins
* @param string $uid
* user-id-field in $table
* @param array $userinfo user-data from table
* @param string $password the password to validate
* @param string $table either panel_customers or panel_admins
* @param string $uid user-id-field in $table
*
* @return boolean
* @return bool
* @throws \Exception
*/
public static function validatePasswordLogin($userinfo = null, $password = null, $table = 'panel_customers', $uid = 'customerid')
{
public static function validatePasswordLogin(
array $userinfo,
string $password,
string $table = 'panel_customers',
string $uid = 'customerid'
): bool {
$algo = Settings::Get('system.passwordcryptfunc') !== null ? Settings::Get('system.passwordcryptfunc') : PASSWORD_DEFAULT;
if (is_numeric($algo)) {
// old setting format
@@ -209,24 +219,21 @@ class Crypt
/**
* Make encrypted password from clear text password
*
* @param string $password
* Password to be encrypted
* @param bool $htpasswd
* optional whether to generate a SHA1 password for directory protection
* @param bool $ftpd
* optional generates sha256 password strings for proftpd/pureftpd
* @param string $password Password to be encrypted
* @param bool $htpasswd optional whether to generate a bcrypt password for directory protection
* @param bool $ftpd optional generates sha256 password strings for proftpd/pureftpd
*
* @return string encrypted password
*/
public static function makeCryptPassword(string $password, bool $htpasswd = false, bool $ftpd = false)
public static function makeCryptPassword(string $password, bool $htpasswd = false, bool $ftpd = false): string
{
if ($htpasswd || $ftpd) {
if ($ftpd) {
// sha256 compatible for proftpd and pure-ftpd
return crypt($password, '$5$' . self::generatePassword(16, true) . '$');
}
// sha1 hash for dir-protection
return '{SHA}' . base64_encode(sha1($password, true));
// bcrypt hash for dir-protection
return password_hash($password, PASSWORD_BCRYPT);
}
// crypt using the specified crypt-algorithm or system default
$algo = Settings::Get('system.passwordcryptfunc') !== null ? Settings::Get('system.passwordcryptfunc') : PASSWORD_DEFAULT;

View File

@@ -36,7 +36,7 @@ class IPTools
*
* @return string
*/
public static function cidr2NetmaskAddr($cidr)
public static function cidr2NetmaskAddr(string $cidr): string
{
$ta = substr($cidr, strpos($cidr, '/') + 1) * 1;
$netmask = str_split(str_pad(str_pad('', $ta, '1'), 32, '0'), 8);
@@ -52,7 +52,7 @@ class IPTools
* Checks whether the given $ip is in range of given ip/cidr range
*
* @param array $ip_cidr 0 => ip, 1 => netmask in decimal, e.g. [0 => '123.123.123.123', 1 => 24]
* @param string $ip ip-address to check
* @param string $ip ip-address to check
*
* @return bool
*/
@@ -77,7 +77,7 @@ class IPTools
*
* @return string|bool ip address on success, false on failure
*/
public static function is_ipv6($address)
public static function is_ipv6(string $address)
{
return filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
}
@@ -86,7 +86,7 @@ class IPTools
* Checks whether the given ipv6 $ip is in range of given ip/cidr range
*
* @param array $ip_cidr 0 => ip, 1 => netmask in decimal, e.g. [0 => '123:123::1', 1 => 64]
* @param string $ip ip-address to check
* @param string $ip ip-address to check
*
* @return bool
*/
@@ -130,6 +130,10 @@ class IPTools
return $in_range;
}
/**
* @param string $addr
* @return false|string
*/
private static function inet6_expand(string $addr)
{
// Check if there are segments missing, insert if necessary
@@ -139,7 +143,7 @@ class IPTools
$part[1] = explode(':', $part[1]);
$missing = [];
for ($i = 0; $i < (8 - (count($part[0]) + count($part[1]))); $i++) {
array_push($missing, '0000');
$missing[] = '0000';
}
$missing = array_merge($part[0], $missing);
$part = array_merge($missing, $part[1]);
@@ -163,17 +167,18 @@ class IPTools
}
}
private static function inet6_prefix_to_mask($prefix)
/**
* @param int $prefix
* @return false|string
*/
private static function inet6_prefix_to_mask(int $prefix)
{
/* Make sure the prefix is a number between 1 and 127 (inclusive) */
$prefix = intval($prefix);
if ($prefix < 0 || $prefix > 128) {
return false;
}
$mask = '0b';
for ($i = 0; $i < $prefix; $i++) {
$mask .= '1';
}
$mask .= str_repeat('1', $prefix);
for ($i = strlen($mask) - 2; $i < 128; $i++) {
$mask .= '0';
}
@@ -188,7 +193,11 @@ class IPTools
return inet_ntop(inet_pton($result));
}
private static function ip2long6($ip)
/**
* @param string $ip
* @return string
*/
private static function ip2long6(string $ip): string
{
$ip_n = inet_pton($ip);
$bits = 15; // 16 x 8 bit = 128bit

View File

@@ -26,6 +26,7 @@
namespace Froxlor\System;
use Froxlor\Settings;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\PHPMailer;
class Mailer extends PHPMailer
@@ -34,9 +35,9 @@ class Mailer extends PHPMailer
/**
* class constructor
*
* @param bool $exceptions
* whether to throw exceptions or not
* @param bool $exceptions whether to throw exceptions or not
*
* @throws Exception
*/
public function __construct(bool $exceptions = false)
{

View File

@@ -32,7 +32,7 @@ use Monolog\Logger;
class MysqlHandler extends AbstractProcessingHandler
{
protected static $froxlorLevels = [
protected static array $froxlorLevels = [
Logger::DEBUG => LOG_DEBUG,
Logger::INFO => LOG_INFO,
Logger::NOTICE => LOG_NOTICE,
@@ -47,11 +47,10 @@ class MysqlHandler extends AbstractProcessingHandler
/**
* Constructor
*
* @param bool|int $level
* Debug level which this handler should store
* @param bool|int $level Debug level which this handler should store
* @param bool $bubble
*/
public function __construct($level = Logger::DEBUG, $bubble = true)
public function __construct($level = Logger::DEBUG, bool $bubble = true)
{
parent::__construct($level, $bubble);
}
@@ -66,8 +65,8 @@ class MysqlHandler extends AbstractProcessingHandler
{
$this->insert([
':message' => $record['message'],
':contextUser' => (isset($record['context']['user']) ? $record['context']['user'] : 'unknown'),
':contextAction' => (isset($record['context']['action']) ? $record['context']['action'] : '0'),
':contextUser' => ($record['context']['user'] ?? 'unknown'),
':contextAction' => ($record['context']['action'] ?? '0'),
':level' => self::$froxlorLevels[$record['level']],
':datetime' => $record['datetime']->format('U')
]);
@@ -79,7 +78,7 @@ class MysqlHandler extends AbstractProcessingHandler
* @param array $data
* @return bool
*/
protected function insert(array $data)
protected function insert(array $data): bool
{
if ($this->pdoStatement === null) {
$sql = "INSERT INTO `panel_syslog` SET `text` = :message, `user` = :contextUser, `action` = :contextAction, `type` = :level, `date` = :datetime";

View File

@@ -32,9 +32,16 @@ use Froxlor\Api\Commands\Traffic as TrafficAPI;
class Traffic
{
public static function getCustomerStats($userinfo, $range = null): array
/**
* @param array $userinfo
* @param ?string $range
* @return array
* @throws \Exception
*/
public static function getCustomerStats(array $userinfo, string $range = null): array
{
$trafficCollectionObj = (new Collection(TrafficAPI::class, $userinfo, self::getParamsByRange($range, ['customer_traffic' => true,])));
$trafficCollectionObj = (new Collection(TrafficAPI::class, $userinfo,
self::getParamsByRange($range, ['customer_traffic' => true,])));
if ($userinfo['adminsession'] == 1) {
$trafficCollectionObj->has('customer', Customers::class, 'customerid', 'customerid');
}
@@ -58,15 +65,15 @@ class Traffic
$years[$item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
$years[$item['year']]['mail'] += $item['mail'];
// per month
$months[$item['month'].'/'.$item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
$months[$item['month'].'/'.$item['year']]['http'] += $item['http'];
$months[$item['month'].'/'.$item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
$months[$item['month'].'/'.$item['year']]['mail'] += $item['mail'];
$months[$item['month'] . '/' . $item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
$months[$item['month'] . '/' . $item['year']]['http'] += $item['http'];
$months[$item['month'] . '/' . $item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
$months[$item['month'] . '/' . $item['year']]['mail'] += $item['mail'];
// per day
$days[$item['day'].'.'.$item['month'].'.'.$item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
$days[$item['day'].'.'.$item['month'].'.'.$item['year']]['http'] += $item['http'];
$days[$item['day'].'.'.$item['month'].'.'.$item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
$days[$item['day'].'.'.$item['month'].'.'.$item['year']]['mail'] += $item['mail'];
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['http'] += $item['http'];
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['mail'] += $item['mail'];
}
// calculate overview for given range from users
@@ -94,7 +101,13 @@ class Traffic
];
}
private static function getParamsByRange($range = null, array $params = [])
/**
* @param ?string $range
* @param array $params
* @return array
* @throws \Exception
*/
private static function getParamsByRange(string $range = null, array $params = []): array
{
$dateParams = [];

View File

@@ -92,7 +92,7 @@ class Text
$result = $attributes['fields'];
$apikey_data = include Froxlor::getInstallDir() . '/lib/formfields/formfield.api_key.php';
$body = UI::twig()->render(UI::getTheme() . '/user/inline-form.html.twig', [
$body = UI::twig()->render(UI::validateThemeTemplate('/user/inline-form.html.twig'), [
'formaction' => $linker->getLink(['section' => 'index', 'page' => 'apikeys']),
'formdata' => $apikey_data['apikey'],
'editid' => $attributes['fields']['id']

View File

@@ -110,7 +110,7 @@ class Collection
return $this;
}
public function withPagination(array $columns, array $default_sorting = []): Collection
public function withPagination(array $columns, array $default_sorting = [], array $pagination_additional_params = []): Collection
{
// Get only searchable columns
$sortableColumns = [];
@@ -121,7 +121,7 @@ class Collection
}
// Prepare pagination
$this->pagination = new Pagination($sortableColumns, $this->count(), (int)Settings::Get('panel.paging'), $default_sorting);
$this->pagination = new Pagination($sortableColumns, $this->count(), (int)Settings::Get('panel.paging'), $default_sorting, $pagination_additional_params);
$this->params = array_merge($this->params, $this->pagination->getApiCommandParams());
return $this;

View File

@@ -203,7 +203,7 @@ class Form
return $returnvalue;
}
public static function processForm(&$form, &$input, $url_params = [], $part = null, $settings_all = [], $settings_part = null, $only_enabledisable = false)
public static function processForm(&$form, &$input, $url_params = [], $part = null, bool $settings_all = false, $settings_part = null, bool $only_enabledisable = false)
{
if (\Froxlor\Validate\Form::validateFormDefinition($form)) {
$submitted_fields = [];

View File

@@ -27,6 +27,7 @@ namespace Froxlor\UI;
use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\Froxlor;
use Froxlor\UI\Panel\UI;
use InvalidArgumentException;
@@ -247,9 +248,9 @@ class Listing
* ]
*
* @param array $tabellisting
* @return bool
* @return array
*/
public static function storeColumnListingForUser(array $tabellisting): bool
public static function storeColumnListingForUser(array $tabellisting): array
{
$section = array_key_first($tabellisting);
if (empty($section) || !is_array($tabellisting[$section]) || empty($tabellisting[$section])) {
@@ -259,6 +260,22 @@ class Listing
if (CurrentUser::isAdmin()) {
$userid = 'adminid';
}
// include all possible tablelisting-definitions to check for the right section
foreach(glob(Froxlor::getInstallDir().'lib/tablelisting/{,*/}*.php', GLOB_BRACE) as $tbl_file) {
$table_listings = include $tbl_file;
if (!isset($table_listings[$section])) {
continue;
} else {
break;
}
}
$columns_available = array_keys($table_listings[$section]['columns']);
// filter out unknown columns
foreach ($tabellisting[$section] as $index => $column_changed) {
if (!in_array($column_changed, $columns_available)) {
unset($tabellisting[$section][$index]);
}
}
// delete possible existing entry
self::deleteColumnListingForUser($tabellisting);
// add new entry
@@ -273,7 +290,7 @@ class Listing
'section' => $section,
'cols' => json_encode($tabellisting[$section])
]);
return true;
return $tabellisting[$section];
}
/**

View File

@@ -48,6 +48,8 @@ class Pagination
private int $perPage;
private string $paginationAdditional = "";
/**
* Create new pagination object to search/filter, limit and sort Api-listing() calls
*
@@ -55,9 +57,15 @@ class Pagination
* @param int $total_entries
* @param int $perPage
* @param array $default_sorting array of key=sortfield,value=sortorder for default sorting
* @param array $pagination_additional_params
*/
public function __construct(array $fields = [], int $total_entries = 0, int $perPage = 20, array $default_sorting = [])
{
public function __construct(
array $fields = [],
int $total_entries = 0,
int $perPage = 20,
array $default_sorting = [],
array $pagination_additional_params = []
) {
$this->fields = $fields;
$this->entries = $total_entries;
$this->perPage = $perPage;
@@ -72,7 +80,8 @@ class Pagination
$orderfields = array_keys($fields);
$this->searchfield = $orderfields[0];
}
if (isset($_REQUEST['searchtext']) && (preg_match('/[-_@\p{L}\p{N}*.]+$/u', $_REQUEST['searchtext']) || $_REQUEST['searchtext'] === '')) {
if (isset($_REQUEST['searchtext']) && (preg_match('/[-_@\p{L}\p{N}*.]+$/u',
$_REQUEST['searchtext']) || $_REQUEST['searchtext'] === '')) {
$this->searchtext = trim($_REQUEST['searchtext']);
}
if (isset($_REQUEST['searchfield']) && isset($fields[$_REQUEST['searchfield']])) {
@@ -114,6 +123,13 @@ class Pagination
$this->pageno = 1;
}
$this->addOffset(($this->pageno - 1) * Settings::Get('panel.paging'));
// pagination additional parameters for url
if (!empty($pagination_additional_params)) {
foreach ($pagination_additional_params as $pap) {
$this->paginationAdditional .= "&" . $pap;
}
}
}
/**
@@ -210,7 +226,8 @@ class Pagination
"last_page" => (Settings::Get('panel.paging') > 0) ? ceil($this->entries / $this->perPage) : -1,
"from" => $this->data['sql_offset'] ?? null,
"to" => min($this->data['sql_offset'] + $this->perPage, $this->entries),
'sortfields' => array_keys($this->fields),
"sortfields" => array_keys($this->fields),
"link_additions" => $this->paginationAdditional,
]
];
}

View File

@@ -87,6 +87,10 @@ class FroxlorTwig extends AbstractExtension
new TwigFunction('linker', [
$this,
'getLink'
]),
new TwigFunction('mix', [
$this,
'getMix'
])
];
}
@@ -158,4 +162,9 @@ class FroxlorTwig extends AbstractExtension
{
return 'froxlortwig';
}
public function getMix($mix = '')
{
return mix($mix);
}
}

View File

@@ -92,10 +92,21 @@ class UI
*/
public static function sendHeaders()
{
if (empty($_SERVER['HTTP_HOST'])) {
if (!self::$install_mode) {
// fallback to set hostname in settings
$_SERVER['HTTP_HOST'] = Settings::Get('system.hostname');
} else {
// bad request
http_response_code(400);
exit();
}
}
session_set_cookie_params([
'lifetime' => self::$install_mode ? 7200 : 600, // will be renewed based on settings in lib/init.php
'path' => '/',
'domain' => $_SERVER['SERVER_NAME'],
'domain' => explode(':', $_SERVER['HTTP_HOST'])[0],
'secure' => self::requestIsHttps(),
'httponly' => true,
'samesite' => 'Strict'
@@ -260,7 +271,18 @@ class UI
*/
public static function twigBuffer($name, array $context = [])
{
$template_file = self::getTheme() . '/' . $name;
$template_file = self::validateThemeTemplate($name);
self::$twigbuf[] = [
$template_file => $context
];
}
public static function validateThemeTemplate(string $name, string $theme = "") {
if (empty(trim($theme))) {
$theme = self::getTheme();
}
$template_file = $theme . '/' . $name;
if (!file_exists(Froxlor::getInstallDir() . '/templates/' . $template_file)) {
PhpHelper::phpErrHandler(E_USER_WARNING, "Template '" . $template_file . "' could not be found, trying fallback theme", __FILE__, __LINE__);
$template_file = self::$default_theme . '/'. $name;
@@ -268,10 +290,7 @@ class UI
PhpHelper::phpErrHandler(E_USER_ERROR, "Unknown template '" . $template_file . "'", __FILE__, __LINE__);
}
}
self::$twigbuf[] = [
$template_file => $context
];
return $template_file;
}
public static function getTheme()

View File

@@ -38,6 +38,13 @@ class Check
const FORMFIELDS_PLAUSIBILITY_CHECK_QUESTION = 2;
/**
* @param $fieldname
* @param $fielddata
* @param $newfieldvalue
* @param $allnewfieldvalues
* @return array|int[]
*/
public static function checkFcgidPhpFpm($fieldname, $fielddata, $newfieldvalue, $allnewfieldvalues)
{
$returnvalue = [
@@ -76,7 +83,8 @@ class Check
} else {
// fcgid is being validated before fpm -> "ask" fpm about its state
if ($fieldname == 'system_mod_fcgid_enabled') {
$returnvalue = self::checkFcgidPhpFpm('system_phpfpm_enabled', null, $check_array[$fieldname]['other_post_field'], null);
$returnvalue = self::checkFcgidPhpFpm('system_phpfpm_enabled', null,
$check_array[$fieldname]['other_post_field'], null);
} else {
// not, bot are nogo
$returnvalue = $returnvalue = [
@@ -109,7 +117,8 @@ class Check
$mysql_access_host_array = array_unique(array_map('trim', explode(',', $newfieldvalue)));
foreach ($mysql_access_host_array as $host_entry) {
if (Validate::validate_ip2($host_entry, true, 'invalidip', true, true, true, true, false) == false && Validate::validateDomain($host_entry) == false && Validate::validateLocalHostname($host_entry) == false && $host_entry != '%') {
if (Validate::validate_ip2($host_entry, true, 'invalidip', true, true, true, true,
false) == false && Validate::validateDomain($host_entry) == false && Validate::validateLocalHostname($host_entry) == false && $host_entry != '%') {
return [
self::FORMFIELDS_PLAUSIBILITY_CHECK_ERROR,
'invalidmysqlhost',
@@ -123,6 +132,13 @@ class Check
];
}
/**
* @param $fieldname
* @param $fielddata
* @param $newfieldvalue
* @param $allnewfieldvalues
* @return array|int[]
*/
public static function checkHostname($fieldname, $fielddata, $newfieldvalue, $allnewfieldvalues)
{
if (0 == strlen(trim($newfieldvalue)) || Validate::validateDomain($newfieldvalue) === false) {
@@ -141,10 +157,12 @@ class Check
* check whether an email account is to be deleted
* reference: #1519
*
* @return bool true if the domain is to be deleted, false otherwise
* @param string $email_addr
*
* @return bool true if the domain is to be deleted, false otherwise
* @throws \Exception
*/
public static function checkMailAccDeletionState($email_addr = null)
public static function checkMailAccDeletionState(string $email_addr): bool
{
// example data of task 7: a:2:{s:9:"loginname";s:4:"webX";s:5:"email";s:20:"deleteme@example.tld";}
@@ -164,6 +182,14 @@ class Check
return false;
}
/**
* @param $fieldname
* @param $fielddata
* @param $newfieldvalue
* @param $allnewfieldvalues
* @return array|int[]
* @throws \Exception
*/
public static function checkPathConflicts($fieldname, $fielddata, $newfieldvalue, $allnewfieldvalues)
{
if ((int)Settings::Get('system.mod_fcgid') == 1) {
@@ -178,7 +204,8 @@ class Check
}
// neither dir can be within the other nor can they be equal
if (substr($newdir, 0, strlen($cdir)) == $cdir || substr($cdir, 0, strlen($newdir)) == $newdir || $newdir == $cdir) {
if (substr($newdir, 0, strlen($cdir)) == $cdir || substr($cdir, 0,
strlen($newdir)) == $newdir || $newdir == $cdir) {
$returnvalue = [
self::FORMFIELDS_PLAUSIBILITY_CHECK_ERROR,
'fcgidpathcannotbeincustomerdoc'
@@ -197,6 +224,13 @@ class Check
return $returnvalue;
}
/**
* @param $fieldname
* @param $fielddata
* @param $newfieldvalue
* @param $allnewfieldvalues
* @return array|int[]
*/
public static function checkPhpInterfaceSetting($fieldname, $fielddata, $newfieldvalue, $allnewfieldvalues)
{
$returnvalue = [
@@ -216,6 +250,13 @@ class Check
return $returnvalue;
}
/**
* @param $fieldname
* @param $fielddata
* @param $newfieldvalue
* @param $allnewfieldvalues
* @return array|int[]
*/
public static function checkUsername($fieldname, $fielddata, $newfieldvalue, $allnewfieldvalues)
{
if (!isset($allnewfieldvalues['customer_mysqlprefix'])) {
@@ -223,7 +264,8 @@ class Check
}
$returnvalue = [];
if (Validate::validateUsername($newfieldvalue, Settings::Get('panel.unix_names'), Database::getSqlUsernameLength() - strlen($allnewfieldvalues['customer_mysqlprefix'])) === true) {
if (Validate::validateUsername($newfieldvalue, Settings::Get('panel.unix_names'),
Database::getSqlUsernameLength() - strlen($allnewfieldvalues['customer_mysqlprefix'])) === true) {
$returnvalue = [
self::FORMFIELDS_PLAUSIBILITY_CHECK_OK
];
@@ -240,6 +282,13 @@ class Check
return $returnvalue;
}
/**
* @param $fieldname
* @param $fielddata
* @param $newfieldvalue
* @param $allnewfieldvalues
* @return array|int[]
*/
public static function checkLocalGroup($fieldname, $fielddata, $newfieldvalue, $allnewfieldvalues)
{
if (empty($newfieldvalue) || $fielddata == $newfieldvalue) {

View File

@@ -28,32 +28,40 @@ namespace Froxlor\Validate;
class Form
{
public static function validateFormDefinition($form)
/**
* @param array $form
* @return bool
*/
public static function validateFormDefinition(array $form): bool
{
$returnvalue = false;
if (is_array($form) && !empty($form) && isset($form['groups']) && is_array($form['groups']) && !empty($form['groups'])) {
$returnvalue = true;
if (!empty($form) && isset($form['groups']) && is_array($form['groups']) && !empty($form['groups'])) {
return true;
}
return $returnvalue;
return false;
}
public static function validateFieldDefinition($field)
/**
* @param array $field
* @return bool
*/
public static function validateFieldDefinition(array $field): bool
{
$returnvalue = false;
if (is_array($field) && !empty($field) && isset($field['fields']) && is_array($field['fields']) && !empty($field['fields'])) {
$returnvalue = true;
if (!empty($field) && isset($field['fields']) && is_array($field['fields']) && !empty($field['fields'])) {
return true;
}
return $returnvalue;
return false;
}
public static function validateFormField($fieldname, $fielddata, $newfieldvalue)
/**
* @param $fieldname
* @param array $fielddata
* @param $newfieldvalue
* @return mixed|string
*/
public static function validateFormField($fieldname, array $fielddata, $newfieldvalue)
{
$returnvalue = '';
if (is_array($fielddata) && isset($fielddata['type']) && $fielddata['type'] != '' && method_exists('\\Froxlor\\Validate\\Form\\Data', 'validateFormField' . ucfirst($fielddata['type']))) {
if (isset($fielddata['type']) && $fielddata['type'] != '' && method_exists('\\Froxlor\\Validate\\Form\\Data', 'validateFormField' . ucfirst($fielddata['type']))) {
$returnvalue = call_user_func([
'\\Froxlor\\Validate\\Form\\Data',
'validateFormField' . ucfirst($fielddata['type'])

View File

@@ -57,9 +57,16 @@ class Validate
* @param bool $throw_exception whether to display error or throw an exception, default false
*
* @return string|void the clean string or error
* @throws Exception
*/
public static function validate($str, string $fieldname, string $pattern = '', $lng = '', $emptydefault = [], bool $throw_exception = false)
{
public static function validate(
string $str,
string $fieldname,
string $pattern = '',
$lng = '',
$emptydefault = [],
bool $throw_exception = false
) {
if (!is_array($emptydefault)) {
$emptydefault_array = [
$emptydefault
@@ -101,27 +108,28 @@ class Validate
/**
* Checks whether it is a valid ip
*
* @param string $ip
* ip-address to check
* @param bool $return_bool
* whether to return bool or call \Froxlor\UI\Response::standard_error()
* @param string $lng
* index for error-message (if $return_bool is false)
* @param bool $allow_localhost
* whether to allow 127.0.0.1
* @param bool $allow_priv
* whether to allow private network addresses
* @param bool $allow_cidr
* whether to allow CIDR values e.g. 10.10.10.10/16
* @param bool $cidr_as_netmask
* whether to format CIDR nodation to netmask notation
* @param bool $throw_exception
* whether to throw an exception on failure
* @param string $ip ip-address to check
* @param bool $return_bool whether to return bool or call \Froxlor\UI\Response::standard_error()
* @param string $lng index for error-message (if $return_bool is false)
* @param bool $allow_localhost whether to allow 127.0.0.1
* @param bool $allow_priv whether to allow private network addresses
* @param bool $allow_cidr whether to allow CIDR values e.g. 10.10.10.10/16
* @param bool $cidr_as_netmask whether to format CIDR notation to netmask notation
* @param bool $throw_exception whether to throw an exception on failure
*
* @return string|bool|void ip address on success, false on failure (or nothing if error is displayed)
* @throws Exception
*/
public static function validate_ip2($ip, bool $return_bool = false, string $lng = 'invalidip', bool $allow_localhost = false, bool $allow_priv = false, bool $allow_cidr = false, bool $cidr_as_netmask = false, bool $throw_exception = false)
{
public static function validate_ip2(
string $ip,
bool $return_bool = false,
string $lng = 'invalidip',
bool $allow_localhost = false,
bool $allow_priv = false,
bool $allow_cidr = false,
bool $cidr_as_netmask = false,
bool $throw_exception = false
) {
$cidr = "";
if ($allow_cidr) {
$org_ip = $ip;
@@ -131,7 +139,8 @@ class Validate
if (IPTools::is_ipv6($ip_cidr[0])) {
$cidr_range_max = 128;
}
if (strlen($ip_cidr[1]) <= 3 && in_array((int)$ip_cidr[1], array_values(range(1, $cidr_range_max)), true) === false) {
if (strlen($ip_cidr[1]) <= 3 && in_array((int)$ip_cidr[1], array_values(range(1, $cidr_range_max)),
true) === false) {
if ($return_bool) {
return false;
}
@@ -161,7 +170,8 @@ class Validate
$filter_lan = $allow_priv ? FILTER_FLAG_NO_RES_RANGE : (FILTER_FLAG_NO_RES_RANGE | FILTER_FLAG_NO_PRIV_RANGE);
if ((filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) || filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) && filter_var($ip, FILTER_VALIDATE_IP, $filter_lan)) {
if ((filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) || filter_var($ip, FILTER_VALIDATE_IP,
FILTER_FLAG_IPV4)) && filter_var($ip, FILTER_VALIDATE_IP, $filter_lan)) {
return $ip . $cidr;
}
@@ -179,14 +189,12 @@ class Validate
/**
* Returns whether a URL is in a correct format or not
*
* @param string $url
* URL to be tested
* @param bool $allow_private_ip
* optional, default is false
* @param string $url URL to be tested
* @param bool $allow_private_ip optional, default is false
*
* @return bool
*/
public static function validateUrl(string $url, bool $allow_private_ip = false)
public static function validateUrl(string $url, bool $allow_private_ip = false): bool
{
if (strtolower(substr($url, 0, 7)) != "http://" && strtolower(substr($url, 0, 8)) != "https://") {
$url = 'http://' . $url;
@@ -215,25 +223,22 @@ class Validate
/**
* Check if the submitted string is a valid domainname
*
* @param string $domainname
* The domainname which should be checked.
* @param bool $allow_underscore
* optional if true, allowes the underscore character in a domain label (DKIM etc.)
* @param string $domainname The domainname which should be checked.
* @param bool $allow_underscore optional if true, allowes the underscore character in a domain label (DKIM etc.)
*
* @return string|boolean the domain-name if the domain is valid, false otherwise
*/
public static function validateDomain($domainname, $allow_underscore = false)
public static function validateDomain(string $domainname, bool $allow_underscore = false)
{
if (is_string($domainname)) {
$char_validation = '([a-z\d](-*[a-z\d])*)(\.?([a-z\d](-*[a-z\d])*))*\.(xn\-\-)?([a-z\d])+';
if ($allow_underscore) {
$char_validation = '([a-z\d\_](-*[a-z\d\_])*)(\.([a-z\d\_](-*[a-z\d])*))*(\.?([a-z\d](-*[a-z\d])*))+\.(xn\-\-)?([a-z\d])+';
}
$char_validation = '([a-z\d](-*[a-z\d])*)(\.?([a-z\d](-*[a-z\d])*))*\.(xn\-\-)?([a-z\d])+';
if ($allow_underscore) {
$char_validation = '([a-z\d\_](-*[a-z\d\_])*)(\.([a-z\d\_](-*[a-z\d])*))*(\.?([a-z\d](-*[a-z\d])*))+\.(xn\-\-)?([a-z\d])+';
}
// valid chars check && overall length check && length of each label
if (preg_match("/^" . $char_validation . "$/i", $domainname) && preg_match("/^.{1,253}$/", $domainname) && preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domainname)) {
return $domainname;
}
// valid chars check && overall length check && length of each label
if (preg_match("/^" . $char_validation . "$/i", $domainname) && preg_match("/^.{1,253}$/",
$domainname) && preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domainname)) {
return $domainname;
}
return false;
}
@@ -245,7 +250,7 @@ class Validate
*
* @return string|boolean hostname on success, else false
*/
public static function validateLocalHostname($hostname)
public static function validateLocalHostname(string $hostname)
{
$pattern = '/^[a-z0-9][a-z0-9\-]{0,62}$/i';
if (preg_match($pattern, $hostname)) {
@@ -257,11 +262,11 @@ class Validate
/**
* Returns if an emailaddress is in correct format or not
*
* @param string $email
* The email address to check
* @return bool Correct or not
* @param string $email The email address to check
*
* @return mixed
*/
public static function validateEmail($email)
public static function validateEmail(string $email)
{
$email = strtolower($email);
// as of php-7.1
@@ -272,25 +277,22 @@ class Validate
}
/**
* Returns if an username is in correct format or not.
* Returns if a username is in correct format or not.
*
* @param string $username
* The username to check
* @param bool $unix_names
* optional, default true, checks whether it must be UNIX compatible
* @param int $mysql_max
* optional, number of max mysql username characters, default empty
* @param string $username The username to check
* @param bool $unix_names optional, default true, checks whether it must be UNIX compatible
* @param int $mysql_max optional, number of max mysql username characters, default empty
*
* @return bool
*/
public static function validateUsername($username, $unix_names = 1, $mysql_max = '')
public static function validateUsername(string $username, bool $unix_names = true, int $mysql_max = 0): bool
{
if (empty($mysql_max) || !is_numeric($mysql_max) || $mysql_max <= 0) {
if (empty($mysql_max) || $mysql_max <= 0) {
$mysql_max = Database::getSqlUsernameLength() - 1;
} else {
$mysql_max--;
}
if ($unix_names == 0) {
if (!$unix_names) {
if (strpos($username, '--') === false) {
return (preg_match('/^[a-z][a-z0-9\-_]{0,' . $mysql_max . '}[a-z0-9]{1}$/Di', $username) != false);
}
@@ -304,9 +306,9 @@ class Validate
*
* @param string $interval
*
* @return boolean
* @return bool
*/
public static function validateSqlInterval($interval = null)
public static function validateSqlInterval(string $interval = ''): bool
{
if (!empty($interval) && strstr($interval, ' ') !== false) {
/*
@@ -325,7 +327,8 @@ class Validate
$interval_parts = explode(' ', $interval);
if (count($interval_parts) == 2 && preg_match('/[0-9]+/', $interval_parts[0]) && in_array(strtoupper($interval_parts[1]), $valid_expr)) {
if (count($interval_parts) == 2 && preg_match('/[0-9]+/',
$interval_parts[0]) && in_array(strtoupper($interval_parts[1]), $valid_expr)) {
return true;
}
}

View File

@@ -4,5 +4,11 @@
* change the options below to either true or false
*/
return [
'enable_webupdate' => false
/**
* enable/disable the possibility to update froxlor from within the web-interface,
* recommended value for debian/ubuntu package users is false to rely on apt and not have version mixup.
* This is also useful for providers that manage the servers but give admin access to froxlor to handle
* updates the way the providers does it (e.g. automation, etc.)
*/
'enable_webupdate' => false,
];

View File

@@ -174,7 +174,7 @@ include_shell "/usr/share/lighttpd/include-conf-enabled.pl"
</visibility>
<content><![CDATA[apt-get install php-cgi]]></content>
</install>
<file name="/etc/nginx/nginx.conf">
<file name="/etc/nginx/nginx.conf" backup="true">
<content><![CDATA[
user www-data;
worker_processes 4;
@@ -274,7 +274,7 @@ http {
]]>
</content>
</file>
<file name="/etc/nginx/fastcgi_params">
<file name="/etc/nginx/fastcgi_params" backup="true">
<content><![CDATA[
fastcgi_connect_timeout 65;
fastcgi_send_timeout 180;
@@ -320,7 +320,7 @@ location /.well-known/acme-challenge {
]]>
</content>
</file>
<file name="/etc/init.d/php-fcgi" chmod="u+x">
<file name="/etc/init.d/php-fcgi" backup="true" chmod="u+x">
<visibility mode="false">{{settings.phpfpm.enabled}}
</visibility>
<visibility mode="false">{{settings.system.mod_fcgid}}

View File

@@ -163,7 +163,7 @@ include_shell "/usr/share/lighttpd/include-conf-enabled.pl"
</visibility>
<content><![CDATA[apt-get install php-cgi]]></content>
</install>
<file name="/etc/nginx/nginx.conf">
<file name="/etc/nginx/nginx.conf" backup="true">
<content><![CDATA[
user www-data;
worker_processes 4;
@@ -263,7 +263,7 @@ http {
]]>
</content>
</file>
<file name="/etc/nginx/fastcgi_params">
<file name="/etc/nginx/fastcgi_params" backup="true">
<content><![CDATA[
fastcgi_connect_timeout 65;
fastcgi_send_timeout 180;
@@ -309,7 +309,7 @@ location /.well-known/acme-challenge {
]]>
</content>
</file>
<file name="/etc/init.d/php-fcgi" chmod="u+x">
<file name="/etc/init.d/php-fcgi" backup="true" chmod="u+x">
<visibility mode="false">{{settings.phpfpm.enabled}}
</visibility>
<visibility mode="false">{{settings.system.mod_fcgid}}

View File

@@ -163,7 +163,7 @@ include_shell "/usr/share/lighttpd/include-conf-enabled.pl"
</visibility>
<content><![CDATA[apt-get install php-cgi]]></content>
</install>
<file name="/etc/nginx/nginx.conf">
<file name="/etc/nginx/nginx.conf" backup="true">
<content><![CDATA[
user www-data;
worker_processes 4;
@@ -263,7 +263,7 @@ http {
]]>
</content>
</file>
<file name="/etc/nginx/fastcgi_params">
<file name="/etc/nginx/fastcgi_params" backup="true">
<content><![CDATA[
fastcgi_connect_timeout 65;
fastcgi_send_timeout 180;
@@ -309,7 +309,7 @@ location /.well-known/acme-challenge {
]]>
</content>
</file>
<file name="/etc/init.d/php-fcgi" chmod="u+x">
<file name="/etc/init.d/php-fcgi" backup="true" chmod="u+x">
<visibility mode="false">{{settings.phpfpm.enabled}}
</visibility>
<visibility mode="false">{{settings.system.mod_fcgid}}

View File

@@ -163,7 +163,7 @@ include_shell "/usr/share/lighttpd/include-conf-enabled.pl"
</visibility>
<content><![CDATA[apt-get install php-cgi]]></content>
</install>
<file name="/etc/nginx/nginx.conf">
<file name="/etc/nginx/nginx.conf" backup="true">
<content><![CDATA[
user www-data;
worker_processes 4;
@@ -263,7 +263,7 @@ http {
]]>
</content>
</file>
<file name="/etc/nginx/fastcgi_params">
<file name="/etc/nginx/fastcgi_params" backup="true">
<content><![CDATA[
fastcgi_connect_timeout 65;
fastcgi_send_timeout 180;
@@ -309,7 +309,7 @@ location /.well-known/acme-challenge {
]]>
</content>
</file>
<file name="/etc/init.d/php-fcgi" chmod="u+x">
<file name="/etc/init.d/php-fcgi" backup="true" chmod="u+x">
<visibility mode="false">{{settings.phpfpm.enabled}}
</visibility>
<visibility mode="false">{{settings.system.mod_fcgid}}

View File

@@ -162,7 +162,7 @@ include_shell "/usr/share/lighttpd/include-conf-enabled.pl"
</visibility>
<content><![CDATA[apt-get install php-cgi]]></content>
</install>
<file name="/etc/nginx/nginx.conf">
<file name="/etc/nginx/nginx.conf" backup="true">
<content><![CDATA[
user www-data;
worker_processes 4;
@@ -262,7 +262,7 @@ http {
]]>
</content>
</file>
<file name="/etc/nginx/fastcgi_params">
<file name="/etc/nginx/fastcgi_params" backup="true">
<content><![CDATA[
fastcgi_connect_timeout 65;
fastcgi_send_timeout 180;
@@ -308,7 +308,7 @@ location /.well-known/acme-challenge {
]]>
</content>
</file>
<file name="/etc/init.d/php-fcgi" chmod="u+x">
<file name="/etc/init.d/php-fcgi" backup="true" chmod="u+x">
<visibility mode="false">{{settings.phpfpm.enabled}}
</visibility>
<visibility mode="false">{{settings.system.mod_fcgid}}

View File

@@ -192,7 +192,7 @@ alias.url += ("/.well-known/acme-challenge/" => "{{settings.system.let
<!-- HTTP Nginx -->
<daemon name="nginx" title="nginx">
<install><![CDATA[emerge www-servers/nginx]]></install>
<file name="/etc/nginx/nginx.conf">
<file name="/etc/nginx/nginx.conf" backup="true">
<content><![CDATA[
user nginx;
worker_processes 4;
@@ -222,7 +222,7 @@ http {
]]>
</content>
</file>
<file name="/etc/nginx/fastcgi_params">
<file name="/etc/nginx/fastcgi_params" backup="true">
<content><![CDATA[
fastcgi_connect_timeout 65;
fastcgi_send_timeout 180;
@@ -268,7 +268,7 @@ location /.well-known/acme-challenge {
]]>
</content>
</file>
<file name="/etc/init.d/php-fcgi" chmod="u+x">
<file name="/etc/init.d/php-fcgi" backup="true" chmod="u+x">
<visibility mode="false">{{settings.phpfpm.enabled}}
</visibility>
<visibility mode="false">{{settings.system.mod_fcgid}}

View File

@@ -162,7 +162,7 @@ include_shell "/usr/share/lighttpd/include-conf-enabled.pl"
</visibility>
<content><![CDATA[apt-get install php-cgi]]></content>
</install>
<file name="/etc/nginx/nginx.conf">
<file name="/etc/nginx/nginx.conf" backup="true">
<content><![CDATA[
user www-data;
worker_processes 4;
@@ -262,7 +262,7 @@ http {
]]>
</content>
</file>
<file name="/etc/nginx/fastcgi_params">
<file name="/etc/nginx/fastcgi_params" backup="true">
<content><![CDATA[
fastcgi_connect_timeout 65;
fastcgi_send_timeout 180;
@@ -308,7 +308,7 @@ location /.well-known/acme-challenge {
]]>
</content>
</file>
<file name="/etc/init.d/php-fcgi" chmod="u+x">
<file name="/etc/init.d/php-fcgi" chmod="u+x" backup="true">
<visibility mode="false">{{settings.phpfpm.enabled}}
</visibility>
<visibility mode="false">{{settings.system.mod_fcgid}}

View File

@@ -87,7 +87,7 @@ return [
'desc' => lng('usersettings.api_allowed.description'),
'type' => 'checkbox',
'value' => '1',
'checked' => Settings::Get('api.enabled') == '1',
'checked' => Settings::Get('api.enabled') == '1' && Settings::Get('api.customer_default'),
'visible' => Settings::Get('api.enabled') == '1'
]
]

View File

@@ -27,7 +27,7 @@ return [
'emails_add' => [
'title' => lng('emails.emails_add'),
'image' => 'fa-solid fa-plus',
'self_overview' => ['section' => 'email', 'page' => 'emails'],
'self_overview' => ['section' => 'email', 'page' => 'email_domain', 'domainid' => $email_domainid ?: 0],
'sections' => [
'section_a' => [
'title' => lng('emails.emails_add'),

View File

@@ -29,7 +29,7 @@ return [
'emails_edit' => [
'title' => lng('emails.emails_edit'),
'image' => 'fa-solid fa-pen',
'self_overview' => ['section' => 'email', 'page' => 'emails'],
'self_overview' => ['section' => 'email', 'page' => 'email_domain', 'domainid' => $result['domainid']],
'sections' => [
'section_a' => [
'title' => lng('emails.emails_edit'),
@@ -48,13 +48,13 @@ return [
'next_to' => [
'edit_link' => [
'type' => 'link',
'href' => $filename . '?page=accounts&amp;action=changepw&amp;id=' . $result['id'],
'href' => $filename . '?page=accounts&amp;domainid=' . $result['domainid'] . '&amp;action=changepw&amp;id=' . $result['id'],
'label' => lng('menue.main.changepassword'),
'classes' => 'btn btn-sm btn-secondary'
],
'del_link' => [
'type' => 'link',
'href' => $filename . '?page=accounts&amp;action=delete&amp;id=' . $result['id'],
'href' => $filename . '?page=accounts&amp;domainid=' . $result['domainid'] . '&amp;action=delete&amp;id=' . $result['id'],
'label' => lng('emails.account_delete'),
'classes' => 'btn btn-sm btn-danger'
]
@@ -68,7 +68,7 @@ return [
'next_to' => [
'add_link' => [
'type' => 'link',
'href' => $filename . '?page=accounts&amp;action=add&amp;id=' . $result['id'],
'href' => $filename . '?page=accounts&amp;domainid=' . $result['domainid'] . '&amp;action=add&amp;id=' . $result['id'],
'label' => lng('emails.account_add'),
'classes' => 'btn btn-sm btn-primary'
]
@@ -83,7 +83,7 @@ return [
'add_link' => [
'visible' => ((int)$result['popaccountid'] != 0 && Settings::Get('system.mail_quota_enabled')),
'type' => 'link',
'href' => $filename . '?page=accounts&amp;action=changequota&amp;id=' . $result['id'],
'href' => $filename . '?page=accounts&amp;domainid=' . $result['domainid'] . '&amp;action=changequota&amp;id=' . $result['id'],
'label' => lng('emails.quota_edit'),
'classes' => 'btn btn-sm btn-secondary'
]
@@ -96,7 +96,7 @@ return [
'next_to' => [
'add_link' => [
'type' => 'link',
'href' => $filename . '?page=' . $page . '&amp;action=togglecatchall&amp;id=' . $result['id'],
'href' => $filename . '?page=' . $page . '&amp;domainid=' . $result['domainid'] . '&amp;action=togglecatchall&amp;id=' . $result['id'],
'label' => '<i class="fa-solid fa-arrow-right-arrow-left"></i> ' . lng('panel.toggle'),
'classes' => 'btn btn-sm btn-secondary'
]
@@ -109,7 +109,7 @@ return [
'next_to' => [
'add_link' => [
'type' => 'link',
'href' => $filename . '?page=forwarders&amp;action=add&amp;id=' . $result['id'],
'href' => $filename . '?page=forwarders&amp;domainid=' . $result['domainid'] . '&amp;action=add&amp;id=' . $result['id'],
'label' => lng('emails.forwarder_add'),
'classes' => 'btn btn-sm btn-primary'
]

View File

@@ -26,6 +26,14 @@
use Froxlor\Language;
use Froxlor\UI\Request;
/**
* Render a template with the given data.
* Mostly used if we have no template-engine (twig).
*
* @param $template
* @param $attributes
* @return array|false|string|string[]
*/
function view($template, $attributes)
{
$view = file_get_contents(dirname(__DIR__) . '/templates/' . $template);
@@ -33,11 +41,26 @@ function view($template, $attributes)
return str_replace(array_keys($attributes), array_values($attributes), $view);
}
/**
* Get the current translation for a given string.
*
* @param string $identifier
* @param array $arguments
* @return array|string
*/
function lng(string $identifier, array $arguments = [])
{
return Language::getTranslation($identifier, $arguments);
}
/**
* Get the value of a request variable.
*
* @param string $identifier
* @param string|null $default
* @param string|null $session
* @return mixed|string|null
*/
function old(string $identifier, string $default = null, string $session = null)
{
if ($session && isset($_SESSION[$session])) {
@@ -45,3 +68,26 @@ function old(string $identifier, string $default = null, string $session = null)
}
return Request::any($identifier, $default);
}
/**
* Loading the mix manifest file from given theme.
* This file contains the hashed filenames of the assets.
* It must be always placed in the theme assets folder.
*
* @param $filename
* @return mixed|string
*/
function mix($filename)
{
if (preg_match('/templates\/(.+)\/assets\/(.+)\/(.+)/', $filename, $matches)) {
$mixManifest = dirname(__DIR__) . '/templates/' . $matches[1] . '/assets/mix-manifest.json';
if (file_exists($mixManifest)) {
$manifest = json_decode(file_get_contents($mixManifest), true);
$key = '/' . $matches[2] . '/' . $matches[3];
if ($manifest && !empty($manifest[$key])) {
$filename = 'templates/' . $matches[1] . '/assets' . $manifest[$key];
}
}
}
return $filename;
}

View File

@@ -277,14 +277,14 @@ if (is_array($_themeoptions) && array_key_exists('js', $_themeoptions['variants'
if (is_array($_themeoptions['variants'][$themevariant]['js'])) {
foreach ($_themeoptions['variants'][$themevariant]['js'] as $jsfile) {
if (file_exists('templates/' . $theme . '/assets/js/' . $jsfile)) {
$js .= '<script type="text/javascript" src="templates/' . $theme . '/assets/js/' . $jsfile . '"></script>' . "\n";
$js .= '<script type="text/javascript" src="' . mix('templates/' . $theme . '/assets/js/' . $jsfile) . '"></script>' . "\n";
}
}
}
if (is_array($_themeoptions['variants'][$themevariant]['css'])) {
foreach ($_themeoptions['variants'][$themevariant]['css'] as $cssfile) {
if (file_exists('templates/' . $theme . '/assets/css/' . $cssfile)) {
$css .= '<link href="templates/' . $theme . '/assets/css/' . $cssfile . '" rel="stylesheet" type="text/css" />' . "\n";
$css .= '<link href="' . mix('templates/' . $theme . '/assets/css/' . $cssfile) . '" rel="stylesheet" type="text/css" />' . "\n";
}
}
}
@@ -332,7 +332,7 @@ if (CurrentUser::hasSession()) {
$cookie_params = [
'expires' => time() + Settings::Get('session.sessiontimeout'),
'path' => '/',
'domain' => $_SERVER['SERVER_NAME'],
'domain' => explode(':', $_SERVER['HTTP_HOST'])[0],
'secure' => UI::requestIsHttps(),
'httponly' => true,
'samesite' => 'Strict'

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