Compare commits

...

159 Commits

Author SHA1 Message Date
Michael Kaufmann
c38b90deef Merge branch 'main' of github.com:Froxlor/Froxlor 2023-07-07 09:52:37 +02:00
Michael Kaufmann
13daa7d6fa set version to 2.0.21 for upcoming maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-07 09:50:50 +02:00
Michael Kaufmann
b0e43d332d validate generated config-json parameter string
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-07 09:47:17 +02:00
jabertwo
75c8754fb4 Fix typo in pathDescriptionSubdomain (#1156) 2023-06-26 11:03:48 +02:00
Michael Kaufmann
e0fa64f897 fix update-check unit-tests now that the current testing version is at 2.1.0-dev1
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-08 12:33:48 +02:00
Michael Kaufmann
ed72fd1766 exclude password fields from being filtered/escaped by AntiXSS, fixes #1150
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-08 12:14:13 +02:00
Michael Kaufmann
826ae36647 adjust log-levels in API methods
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-05 12:13:38 +02:00
Michael Kaufmann
9ddf24539e remove hidden fields from login/passwd-reset; refs #1102
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-05 12:10:39 +02:00
Grigory Morozov
3940c1429d Correcting Nginx location match, fixes #1153 2023-06-05 08:06:44 +02:00
Michael Kaufmann
c236d9eaab set version to 2.0.20 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-02 20:13:36 +02:00
Michael Kaufmann
688994e40c idna encode umlaut-emailaddresses when adding email-forwarder
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-29 20:52:57 +02:00
Michael Kaufmann
9facaee809 re-enable fcgid/php-fpm activation-validate-check
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-28 15:49:06 +02:00
Michael Kaufmann
a7dd5f4685 show 0 value of resource-fields if value is empty, fixes #1149
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-28 10:46:28 +02:00
Michael Kaufmann
da810ea953 secure filename of local-archive in webupdate
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-25 09:51:55 +02:00
Michael Kaufmann
51b6e067e8 idna encode umlaut-emailaddresses when adding/editing email-account; use correct password-suggestion-layout in change-email-account formfield
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-25 08:26:39 +02:00
Michael Kaufmann
34cf6698bc remove superfluous try_files in nginx config if php-backend (non-fastcgi) is used
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-15 20:14:26 +02:00
Michael Kaufmann
4642160724 add same loginfail restrictions for entering 2fa code as for user/pwd login
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-12 10:36:27 +02:00
Nicolas Thumann
78a259ef3b Fix IPv6 address in cookie domain (#1137)
* Implement getCookieHost to extract cookie host from HTTP_HOST
2023-05-10 08:26:08 +02:00
Nicolas Thumann
68cf4ab69a Fix typo in English privileged_passwd (#1136) 2023-05-09 18:52:43 +02:00
Michael Kaufmann
d5661d492d set version to 2.0.19 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-07 11:07:31 +02:00
Michael Kaufmann
6900898ae1 typo in updater
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-07 11:03:21 +02:00
Michael Kaufmann
d90fb7fa68 fix mysql-pdo check on installation, set version to 2.0.18 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-07 10:54:47 +02:00
Michael Kaufmann
4ea8629fcc set version to 2.0.17 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-06 22:08:43 +02:00
Michael Kaufmann
9d4ff8698d fix ratelimiting when settings do not exist (yet)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-06 22:00:19 +02:00
Michael Kaufmann
b164038846 set version to 2.0.16 for upcoming maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-05 21:10:01 +02:00
Michael Kaufmann
5c46960734 fix language mixup for rate-limit-interval setting
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-05 13:21:12 +02:00
Michael Kaufmann
a7f4f0c737 output nicer message when hitting rate limit
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-04 10:55:34 +02:00
Michael Kaufmann
b64dd501dd fix missing use-statement
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-02 10:27:28 +02:00
Michael Kaufmann
1679675aa1 introduce http-request rate-limit; smaller fixes
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-02 10:19:53 +02:00
sro0
640466f301 Disable autocomplete on 2FA input element (#1133)
2FA codes change every login. So there is no need to save entered values in browser and suggest them again during future logins.

Co-authored-by: sro0 <>
2023-04-29 09:56:15 +02:00
Michael Kaufmann
9c9771a371 fix generation of current_ips array in Domains-API
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-25 20:09:18 +02:00
Michael Kaufmann
1922b3ce65 set default value for email_quota to settings-default in EmailAccounts.add(); fixes #1132
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-25 19:50:41 +02:00
Michael Kaufmann
83e819908a set default value of 'openbasedir_path' to 0 in SubDomain.add() like we do in Domains.add()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-25 19:49:09 +02:00
Michael Kaufmann
0924aa644b update dependencies
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-25 19:48:29 +02:00
Lukas Bableck
7711ce1d66 Allow admins to edit openbasedir_path for domains (#1125)
* Add openbasedir_path formfield
* Add openbasedir_path field values to admin_domains page
2023-04-25 19:42:27 +02:00
Michael Kaufmann
7dae63e586 Merge branch 'main' of github.com:Froxlor/Froxlor 2023-04-25 19:40:22 +02:00
Michael Kaufmann
1bcaa45492 add copy-system-details-to-clipboard button on admin dashboard; fixes #1126
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-25 19:36:46 +02:00
Michael Kaufmann
66cb114f0d trigger rebuild of config files after changing only ip-settings in domains
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-25 19:33:52 +02:00
Michael Kaufmann
1c5d60dcfd Add mysql to required extensions 2023-04-23 13:28:33 +02:00
Michael Kaufmann
b6da6356fc Update build-docs.yml 2023-04-23 12:08:19 +02:00
Michael Kaufmann
c09670cc45 make it clearer that the finishing commands have to be exectuted as 'root'; fixes #1128
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-23 11:56:20 +02:00
Michael Kaufmann
464f5b7bed fix adding mysql-server to customers without any prior assigned mysql-server, fixes #1123; fix issues with displaying set value if path-mode is 'dropdown'
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-19 20:58:48 +02:00
Michael Kaufmann
c799235c24 corrected display of special-case titles of settings
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-14 08:23:37 +02:00
Michael Kaufmann
a2860e70a5 strictly check whether field to select is the id or the email-address b/c is cases of email-addresses starting with a digit this is somehow used as value for the id field and return the wrong entity
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-14 08:22:31 +02:00
Michael Kaufmann
95a96d46a6 put php-fpm directives in Directory-directive in apache2; fixes #1120
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-11 09:48:26 +02:00
Michael Kaufmann
81f3dbda31 respect no-try_files setting also in protected directories
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-10 09:33:43 +02:00
Michael Kaufmann
4eb4191843 don't run cron tasks if requirements return non-success; fixes #1122
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-08 10:49:59 +02:00
Michael Kaufmann
ca433d8a61 set version to 2.0.15 for update-bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-07 18:58:19 +02:00
Michael Kaufmann
8f4dfe1514 Fix the fix... 2023-04-07 11:51:44 +02:00
Michael Kaufmann
ee42f5168e Use correct SQL Syntax for older versions 2023-04-07 11:48:43 +02:00
Michael Kaufmann
fc8ca57f8c set version to 2.0.14 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-04-07 08:52:12 +02:00
Michael Kaufmann
7e4bba2d55 corrected mail-log parsing, refs #1119
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-27 12:53:21 +02:00
Michael Kaufmann
7e635f9be4 correctly retriggered certificate issue on froxlor-vhost alias-domain changes, fixes #1115
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-24 09:17:04 +01:00
Michael Kaufmann
e9406a20f2 readd php interpretation to php-enabled customers/domains in directory protection, fixes #1118
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-23 19:18:39 +01:00
Michael Kaufmann
de7729cec8 add certificate metadata to db table
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-23 12:40:01 +01:00
Michael Kaufmann
d60e48849b correct languages for mail/file templates
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-23 08:56:45 +01:00
Michael Kaufmann
908df5a7bb remove sorting from ssl 'issuer' as this data is being read from the certificate content and not the database/table and therefore cannot be sorted using the API, fixes #1116
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-21 14:22:34 +01:00
Michael Kaufmann
c1952afb94 dont sort indexed array as the keys get lost; fixes #1114
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-20 11:12:30 +01:00
Michael Kaufmann
7a22e8f4dd open newsfeed-links in a new tab, fixes #1112
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-18 20:04:02 +01:00
Michael Kaufmann
3ac0da2cdd corrected checkLocalGroup() validation if setting did not change, fixes #1111
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-17 08:27:31 +01:00
dependabot[bot]
eb816c4cc6 Bump webpack from 5.75.0 to 5.76.1 (#1109)
Bumps [webpack](https://github.com/webpack/webpack) from 5.75.0 to 5.76.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.75.0...v5.76.1)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-15 13:21:01 +01:00
Michael Kaufmann
64d8bf4fba avoid socket length limitations leading to cut-off/invalid filename for very long domain and/or loginnames, fixes #1108
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-15 13:18:43 +01:00
Michael Kaufmann
ae6ee95973 avoid using posix-extension function before requirement-check can test for it and inform user
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-15 08:54:50 +01:00
Michael Kaufmann
e9051dc30a add spanish language translation reference to german language file
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-08 09:48:13 +01:00
scramatte
b6c7c53c3a Add Spanish language (#1105)
* Add Spanish localization
* add spanish to languages list
2023-03-08 09:43:35 +01:00
Michael Kaufmann
f36bc61fc7 better validation for uploaded/imported image files
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-08 09:33:30 +01:00
Michael Kaufmann
c56e0b9dac add 'Passing HTTP AUTH BASIC' header option when using FCGID; fix typeerror in parameter for Froxlor\Dns\Dns; require php-gd extension for validating uploaded images
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-08 09:33:02 +01:00
Michael Kaufmann
1deb08bf75 use correct parameter in PowerDNS::cleanDomainZone(), fixes #1104
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-04 14:42:31 +01:00
Michael Kaufmann
b30d7a8252 set version to 2.0.13 for maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-03 13:05:09 +01:00
Michael Kaufmann
b03e11c18d fix email-domain navigation and descriptions
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-03 13:03:12 +01:00
Michael Kaufmann
bf7d22a794 typecast parameter values for sizeReadable(), fixes #1103
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-28 08:15:31 +01:00
Michael Kaufmann
fb57a8a3b5 update dependencies
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-26 10:24:44 +01:00
Mickey
0d625797b0 Add command to remove debians prerotate script (#1101)
Co-authored-by: Mickey Knox <mickey@netfreaks.org>
2023-02-22 10:01:25 +01:00
Michael Kaufmann
6777fbf229 type-safe comparsion of md5-compatibility hash-validation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-22 09:35:27 +01:00
Michael Kaufmann
23f1f79eff specify clearly which tls settings are being overwritten/ignored depending on the 'Override system TLS settings' flag when adding/updating Domains
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-19 12:27:56 +01:00
Michael Kaufmann
a5af104d53 keep search-fields/text in pagination links of displaying a search-result
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-19 12:06:47 +01:00
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
090cfc26f2 set file-log (if enabled) to be in froxlor/logs/ folder; fix ssl param directive for dovecot in Ubuntu Bionic; set version to 2.0.8
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-14 13:09:42 +01:00
Michael Kaufmann
529890b5d2 fix typo in langauge-definition
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 22:27:45 +01:00
Michael Kaufmann
d4a6ab146d re-add total-disspace dashboard-display on customer dashboard
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 16:52:14 +01:00
Michael Kaufmann
e3f02879cf restore mandatory field on domain-formfields; add translated require message and correct error-placement of the message
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 15:16:42 +01:00
Michael Kaufmann
b52d6df777 [UI] change require of ipandport field in domains.add and domains.delete to one-of instead of all; fixes #1078
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 14:53:05 +01:00
Michael Kaufmann
9e671100ae acme-challenge path adjustments if docroot changed after update from 0.10.x (via apt)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 14:21:14 +01:00
Michael Kaufmann
7e801ea502 Merge branch 'main' of github.com:Froxlor/Froxlor 2023-01-12 12:20:23 +01:00
Daniel
b68522f7d5 Fix formfield image preview path (#1077) 2023-01-12 12:19:31 +01:00
Michael Kaufmann
86852942e0 add missing language-strings for traffic page
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 11:30:52 +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
ec05c84f4d check whether let's encrypt is enabled at all and correct acme-alias configuration file if necessary/selected
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 09:40:35 +01:00
Michael Kaufmann
9e13c077e9 show command to regenerate cron.d-file if previous deletion of old files could not be done automatically, fixes #1076
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 08:33:19 +01:00
Maurice Preuß (envoyr)
da8d315e77 remove hardcoded logo height
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-01-11 22:43:00 +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
Michael Kaufmann
cb67e3ae63 continue checking domains even if no config was found, thx knox
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-11 21:07:00 +01:00
Michael Kaufmann
82d15c4dc2 fixes for ValidateAcmeWebroot command
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-11 20:47:07 +01:00
Michael Kaufmann
6d048e2cee fix default mysql-dbserver for customers if not allowed to use the default (id=0) one; fixes #1075
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-11 19:41:24 +01:00
Michael Kaufmann
87bd80eea1 reenable access to ftp view for customers with ftps=0 because the main account is always being created
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-11 14:58:18 +01:00
184 changed files with 6842 additions and 9427 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.yml -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

@@ -138,6 +138,26 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_req_limit_per_interval' => [
'label' => lng('serversettings.req_limit_per_interval'),
'settinggroup' => 'system',
'varname' => 'req_limit_per_interval',
'type' => 'number',
'min' => 30,
'default' => 60,
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_req_limit_interval' => [
'label' => lng('serversettings.req_limit_interval'),
'settinggroup' => 'system',
'varname' => 'req_limit_interval',
'type' => 'number',
'min' => 5,
'default' => 60,
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'customer_accountprefix' => [
'label' => lng('serversettings.accountprefix'),
'settinggroup' => 'customer',

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

@@ -53,7 +53,7 @@ return [
'string_regexp' => '/^(([a-z0-9\-\._]+, ?)*[a-z0-9\-\._]+)?$/i',
'string_emptyallowed' => true,
'default' => '',
'save_method' => 'storeSettingField',
'save_method' => 'storeSettingClearCertificates',
'advanced_mode' => true
],
/**
@@ -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

@@ -28,7 +28,7 @@ require __DIR__ . '/lib/init.php';
use Froxlor\Froxlor;
use Froxlor\FroxlorLogger;
use Froxlor\Http\HttpClient;
use Froxlor\FileDir;
use Froxlor\Install\AutoUpdate;
use Froxlor\Settings;
use Froxlor\UI\Panel\UI;
@@ -132,7 +132,7 @@ elseif ($page == 'getdownload') {
elseif ($page == 'extract') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
$toExtract = isset($_POST['archive']) ? $_POST['archive'] : null;
$localArchive = Froxlor::getInstallDir() . '/updates/' . $toExtract;
$localArchive = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/updates/' . $toExtract);
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "Extracting " . $localArchive . " to " . Froxlor::getInstallDir());
$result = AutoUpdate::extractZip($localArchive);
if ($result > 0) {
@@ -146,7 +146,7 @@ elseif ($page == 'extract') {
Response::redirectTo('admin_updates.php');
} else {
$toExtract = isset($_GET['archive']) ? $_GET['archive'] : null;
$localArchive = Froxlor::getInstallDir() . '/updates/' . $toExtract;
$localArchive = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/updates/' . $toExtract);
}
if (!file_exists($localArchive)) {

View File

@@ -33,6 +33,7 @@ use Froxlor\Settings;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\Validate\Validate;
if ($userinfo['change_serversettings'] == '1') {
if ($action == 'setconfigured') {
@@ -91,13 +92,29 @@ if ($userinfo['change_serversettings'] == '1') {
}
if ($distribution != "" && isset($_POST['finish'])) {
$valid_keys = ['http', 'dns', 'smtp', 'mail', 'ftp', 'system', 'distro'];
unset($_POST['finish']);
unset($_POST['csrf_token']);
$params = $_POST;
$params['distro'] = $distribution;
$params['system'] = [];
foreach ($_POST['system'] as $sysdaemon) {
$params['system'][] = $sysdaemon;
}
// validate params
foreach ($params as $key => $value) {
if (!in_array($key, $valid_keys)) {
unset($params[$key]);
continue;
}
if (!is_array($value)) {
$params[$key] = Validate::validate($value, $key);
} else {
foreach ($value as $subkey => $subvalue) {
$params[$key][$subkey] = Validate::validate($subvalue, $key.'.'.$subkey);
}
}
}
$params_content = json_encode($params);
$params_filename = FileDir::makeCorrectFile(Froxlor::getInstallDir() . 'install/' . Froxlor::genSessionId() . '.json');
file_put_contents($params_filename, $params_content);
@@ -121,8 +138,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

@@ -282,6 +282,12 @@ if ($page == 'domains' || $page == 'overview') {
}
}
$openbasedir = [
0 => lng('domain.docroot'),
1 => lng('domain.homedir'),
2 => lng('domain.docparent')
];
// create serveralias options
$serveraliasoptions = [
0 => lng('domains.serveraliasoption_wildcard'),
@@ -545,6 +551,12 @@ if ($page == 'domains' || $page == 'overview') {
$result['temporary_ssl_redirect'] = $result['ssl_redirect'];
$result['ssl_redirect'] = ($result['ssl_redirect'] == 0 ? 0 : 1);
$openbasedir = [
0 => lng('domain.docroot'),
1 => lng('domain.homedir'),
2 => lng('domain.docparent')
];
$serveraliasoptions = [
0 => lng('domains.serveraliasoption_wildcard'),
1 => lng('domains.serveraliasoption_www'),

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

@@ -35,6 +35,7 @@ use Froxlor\Cli\UpdateCommand;
use Froxlor\Cli\InstallCommand;
use Froxlor\Cli\MasterCron;
use Froxlor\Cli\UserCommand;
use Froxlor\Cli\ValidateAcmeWebroot;
use Froxlor\Froxlor;
// validate correct php version
@@ -59,4 +60,5 @@ $application->add(new UpdateCommand());
$application->add(new InstallCommand());
$application->add(new MasterCron());
$application->add(new UserCommand());
$application->add(new ValidateAcmeWebroot());
$application->run();

View File

@@ -45,6 +45,7 @@
"ext-openssl": "*",
"ext-fileinfo": "*",
"ext-gmp": "*",
"ext-gd": "*",
"phpmailer/phpmailer": "~6.0",
"monolog/monolog": "^1.24",
"robthree/twofactorauth": "^1.6",
@@ -52,7 +53,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",

836
composer.lock generated

File diff suppressed because it is too large Load Diff

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' => 'email_domain', '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']),
$actions_links[] = [
'href' => $linker->getLink(['section' => 'email', 'page' => 'email_domain', 'action' => 'add', 'domainid' => $email_domainid]),
'label' => lng('emails.emails_add')
]
];
}
@@ -145,7 +192,11 @@ if ($page == 'overview' || $page == 'emails') {
"cid" => $userinfo['customerid']
]);
$domains = [];
$selected_domain = "";
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
if ($email_domainid == $row['id']) {
$selected_domain = $row['domain'];
}
$domains[$row['domain']] = $idna_convert->decode($row['domain']);
}
@@ -244,11 +295,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 +320,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 +346,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 +356,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 +391,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 +410,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 +420,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 +450,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 +469,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 +479,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 +509,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 +518,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 +545,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 +564,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 +574,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 +620,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 +630,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

@@ -40,7 +40,7 @@ use Froxlor\UI\Response;
use Froxlor\CurrentUser;
// redirect if this customer page is hidden via settings
if (Settings::IsInList('panel.customer_hide_options', 'ftp') || $userinfo['ftps'] == 0) {
if (Settings::IsInList('panel.customer_hide_options', 'ftp')) {
Response::redirectTo('customer_index.php');
}
@@ -119,7 +119,7 @@ if ($page == 'overview' || $page == 'accounts') {
if (Settings::Get('customer.ftpatdomain') == '1') {
$domainlist = [];
$result_domains_stmt = Database::prepare("SELECT `domain` FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE `customerid`= :customerid");
WHERE `customerid`= :customerid ORDER BY `domain` ASC");
Database::pexecute($result_domains_stmt, [
"customerid" => $userinfo['customerid']
]);
@@ -127,7 +127,6 @@ if ($page == 'overview' || $page == 'accounts') {
while ($row_domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) {
$domainlist[$row_domain['domain']] = $idna_convert->decode($row_domain['domain']);
}
sort($domainlist);
}
if (Settings::Get('system.allow_customer_shell') == '1') {

View File

@@ -115,10 +115,14 @@ if ($page == 'overview') {
if ($usages) {
$userinfo['diskspace_bytes_used'] = $usages['webspace'] * 1024;
$userinfo['mailspace_used'] = $usages['mail'] * 1024;
$userinfo['dbspace_used'] = $usages['mysql'] * 1024;
$userinfo['total_bytes_used'] = ($usages['webspace'] + $usages['mail'] + $usages['mysql']) * 1024;
} else {
$userinfo['diskspace_bytes_used'] = 0;
$userinfo['total_bytes_used'] = 0;
$userinfo['mailspace_used'] = 0;
$userinfo['dbspace_used'] = 0;
}
UI::twig()->addGlobal('userinfo', $userinfo);

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

@@ -53,9 +53,15 @@ if ($action == '2fa_entercode') {
Response::redirectTo('index.php');
exit();
}
$smessage = isset($_GET['showmessage']) ? (int)$_GET['showmessage'] : 0;
$message = "";
if ($smessage > 0) {
$message = lng('error.2fa_wrongcode');
}
// show template to enter code
UI::view('login/enter2fa.html.twig', [
'pagetitle' => lng('login.2fa')
'pagetitle' => lng('login.2fa'),
'message' => $message
]);
} elseif ($action == '2fa_verify') {
// verify code from 2fa code-enter form
@@ -68,25 +74,25 @@ if ($action == '2fa_entercode') {
// verify entered code
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
$result = ($_SESSION['secret_2fa'] == 'email' ? true : $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3));
// either the code is valid when using authenticator-app, or we will select userdata by id and entered code
// which is temporarily stored for the customer when using email-2fa
if ($result) {
// get user-data
$table = $_SESSION['uidtable_2fa'];
$field = $_SESSION['uidfield_2fa'];
$uid = $_SESSION['uid_2fa'];
$isadmin = $_SESSION['unfo_2fa'];
// either the code is valid when using authenticator-app, or we will select userdata by id and entered code
// which is temporarily stored for the customer when using email-2fa
if ($result) {
$sel_param = [
'uid' => $uid
];
if ($_SESSION['secret_2fa'] == 'email') {
// verify code by selecting user by id and the temp. stored code,
// so only if it's the correct code, we get the user-data
$sel_stmt = Database::prepare("SELECT * FROM $table WHERE `" . $field . "` = :uid AND `data_2fa` = :code");
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid AND `data_2fa` = :code");
$sel_param['code'] = $code;
} else {
// Authenticator-verification has already happened at this point, so just get the user-data
$sel_stmt = Database::prepare("SELECT * FROM $table WHERE `" . $field . "` = :uid");
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid");
}
$userinfo = Database::pexecute_first($sel_stmt, $sel_param);
// whoops, no (valid) user? Start again
@@ -108,19 +114,54 @@ if ($action == '2fa_entercode') {
// when using email-2fa, remove the one-time-code
if ($userinfo['type_2fa'] == '1') {
$del_stmt = Database::prepare("UPDATE $table SET `data_2fa` = '' WHERE `" . $field . "` = :uid");
$del_stmt = Database::prepare("UPDATE " . $table . " SET `data_2fa` = '' WHERE `" . $field . "` = :uid");
$userinfo = Database::pexecute_first($del_stmt, [
'uid' => $uid
]);
}
exit();
}
// wrong 2fa code - treat like "wrong password"
$stmt = Database::prepare("
UPDATE " . $table . "
SET `lastlogin_fail`= :lastlogin_fail, `loginfail_count`=`loginfail_count`+1
WHERE `" . $field . "`= :uid
");
Database::pexecute($stmt, [
"lastlogin_fail" => time(),
"uid" => $uid
]);
// get data for processing further
$stmt = Database::prepare("
SELECT `loginname`, `loginfail_count`, `lastlogin_fail` FROM " . $table . "
WHERE `" . $field . "`= :uid
");
$fail_user = Database::pexecute_first($stmt, [
"uid" => $uid
]);
if ($fail_user['loginfail_count'] >= Settings::Get('login.maxloginattempts') && $fail_user['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime'))) {
// Log failed login
$rstlog = FroxlorLogger::getInstanceOf([
'loginname' => $_SERVER['REMOTE_ADDR']
]);
$rstlog->logAction(FroxlorLogger::LOGIN_ACTION, LOG_WARNING, "User '" . $fail_user['loginname'] . "' entered wrong 2fa code too often.");
unset($fail_user);
Response::redirectTo('index.php', [
'showmessage' => '2'
'showmessage' => '3'
]);
exit();
}
unset($fail_user);
// back to form
Response::redirectTo('index.php', [
'action' => '2fa_entercode',
'showmessage' => '1'
]);
exit();
} elseif ($action == 'login') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (!empty($_POST)) {
$loginname = Validate::validate($_POST['loginname'], 'loginname');
$password = Validate::validate($_POST['password'], 'password');
@@ -390,13 +431,13 @@ if ($action == '2fa_entercode') {
}
$lastqrystr = "";
if (isset($_REQUEST['qrystr']) && $_REQUEST['qrystr'] != "") {
$lastqrystr = htmlspecialchars($_REQUEST['qrystr'], ENT_QUOTES);
$lastqrystr = urlencode($_REQUEST['qrystr']);
}
$_SESSION['lastscript'] = $lastscript;
$_SESSION['lastqrystr'] = $lastqrystr;
UI::view('login/login.html.twig', [
'pagetitle' => 'Login',
'lastscript' => $lastscript,
'lastqrystr' => $lastqrystr,
'upd_in_progress' => $update_in_progress,
'message' => $message,
'successmsg' => $successmessage
@@ -408,7 +449,7 @@ if ($action == 'forgotpwd') {
$adminchecked = false;
$message = '';
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (!empty($_POST)) {
$loginname = Validate::validate($_POST['loginname'], 'loginname');
$email = Validate::validateEmail($_POST['loginemail']);
$result_stmt = Database::prepare("SELECT `adminid`, `customerid`, `customernumber`, `firstname`, `name`, `company`, `email`, `loginname`, `def_language`, `deactivated` FROM `" . TABLE_PANEL_CUSTOMERS . "`
@@ -592,7 +633,7 @@ if ($action == 'forgotpwd') {
UI::view('login/fpwd.html.twig', [
'pagetitle' => lng('login.presend'),
'action' => $action,
'formaction' => 'index.php?action='.$action,
'message' => $message,
]);
}
@@ -615,7 +656,7 @@ if ($action == 'resetpwd') {
$check = substr($activationcode, 40, 10);
if (substr(md5($third . $timestamp), 0, 10) == $check && $timestamp >= time() - 86400) {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (!empty($_POST)) {
$stmt = Database::prepare("SELECT `userid`, `admin` FROM `" . TABLE_PANEL_ACTIVATION . "`
WHERE `activationcode` = :activationcode");
$result = Database::pexecute_first($stmt, [
@@ -705,29 +746,34 @@ function finishLogin($userinfo)
}
$qryparams = [];
if (isset($_POST['qrystr']) && $_POST['qrystr'] != "") {
parse_str(urldecode($_POST['qrystr']), $qryparams);
if (isset($_SESSION['lastqrystr']) && !empty($_SESSION['lastqrystr'])) {
parse_str(urldecode($_SESSION['lastqrystr']), $qryparams);
unset($_SESSION['lastqrystr']);
}
if ($userinfo['adminsession'] == '1') {
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
Response::redirectTo('admin_updates.php?page=overview');
} else {
if (isset($_POST['script']) && $_POST['script'] != "") {
if (preg_match("/customer\_/", $_POST['script']) === 1) {
if (isset($_SESSION['lastscript']) && !empty($_SESSION['lastscript'])) {
$lastscript = $_SESSION['lastscript'];
unset($_SESSION['lastscript']);
if (preg_match("/customer\_/", $lastscript) === 1) {
Response::redirectTo('admin_customers.php', [
"page" => "customers"
]);
} else {
Response::redirectTo($_POST['script'], $qryparams);
Response::redirectTo($lastscript, $qryparams);
}
} else {
Response::redirectTo('admin_index.php', $qryparams);
}
}
} else {
if (isset($_POST['script']) && $_POST['script'] != "") {
Response::redirectTo($_POST['script'], $qryparams);
if (isset($_SESSION['lastscript']) && !empty($_SESSION['lastscript'])) {
$lastscript = $_SESSION['lastscript'];
unset($_SESSION['lastscript']);
Response::redirectTo($lastscript, $qryparams);
} else {
Response::redirectTo('customer_index.php', $qryparams);
}

View File

@@ -642,7 +642,7 @@ opcache.validate_timestamps'),
('system', 'leprivatekey', 'unset'),
('system', 'lepublickey', 'unset'),
('system', 'letsencryptca', 'letsencrypt'),
('system', 'letsencryptchallengepath', '/var/www/froxlor'),
('system', 'letsencryptchallengepath', '/var/www/html/froxlor'),
('system', 'letsencryptkeysize', '4096'),
('system', 'letsencryptreuseold', 0),
('system', 'leenabled', '0'),
@@ -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,12 @@ opcache.validate_timestamps'),
('system', 'distribution', ''),
('system', 'update_channel', 'stable'),
('system', 'updatecheck_data', ''),
('system', 'update_notify_last', '2.0.7'),
('system', 'update_notify_last', '2.0.21'),
('system', 'traffictool', 'goaccess'),
('system', 'req_limit_per_interval', 60),
('system', 'req_limit_interval', 60),
('api', 'enabled', '0'),
('api', 'customer_default', '1'),
('2fa', 'enabled', '1'),
('panel', 'decimal_places', '4'),
('panel', 'adminmail', 'admin@SERVERNAME'),
@@ -740,8 +744,8 @@ opcache.validate_timestamps'),
('panel', 'logo_overridetheme', '0'),
('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'),
('panel', 'version', '2.0.7'),
('panel', 'db_version', '202212060');
('panel', 'version', '2.0.21'),
('panel', 'db_version', '202304260');
DROP TABLE IF EXISTS `panel_tasks`;
@@ -981,7 +985,9 @@ CREATE TABLE IF NOT EXISTS `domain_ssl_settings` (
`ssl_cert_chainfile` mediumtext,
`ssl_csr_file` mediumtext,
`ssl_fullchain_file` mediumtext,
`expirationdate` datetime DEFAULT NULL,
`validfromdate` datetime DEFAULT NULL,
`validtodate` datetime DEFAULT NULL,
`issuer` varchar(255) NOT NULL default '',
PRIMARY KEY (`id`),
UNIQUE KEY (`domainid`)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;

View File

@@ -23,6 +23,7 @@
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
use Froxlor\Http\RateLimiter;
use Froxlor\UI\Panel\UI;
use Froxlor\Install\Install;
@@ -62,6 +63,7 @@ require dirname(__DIR__) . '/lib/tables.inc.php';
// init twig
UI::initTwig(true);
UI::sendHeaders();
RateLimiter::run(true);
$installer = new Install();
$installer->handle();

View File

@@ -23,11 +23,11 @@
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
use Froxlor\Froxlor;
use Froxlor\FileDir;
use Froxlor\Database\Database;
use Froxlor\Settings;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\Install\Update;
use Froxlor\Settings;
if (!defined('_CRON_UPDATE')) {
if (!defined('AREA') || (defined('AREA') && AREA != 'admin') || !isset($userinfo['loginname']) || (isset($userinfo['loginname']) && $userinfo['loginname'] == '')) {
@@ -38,7 +38,6 @@ if (!defined('_CRON_UPDATE')) {
// last 0.10.x release
if (Froxlor::isFroxlorVersion('0.10.38.3')) {
$update_to = '2.0.0-beta1';
Update::showUpdateStep("Updating from 0.10.38.3 to " . $update_to, false);
@@ -83,7 +82,7 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
Database::query("ALTER TABLE `" . TABLE_PANEL_ADMINS . "` DROP COLUMN `domains_see_all`;");
Update::lastStepStatus(0);
Update::showUpdateStep("Checking for multiple mysql-servers to allow acccess to customers for existing databases");
Update::showUpdateStep("Checking for multiple mysql-servers to allow access to customers for existing databases");
$dbservers_stmt = Database::query("
SELECT `customerid`,
GROUP_CONCAT(DISTINCT `dbserver` SEPARATOR ',') as allowed_mysqlserver
@@ -94,7 +93,8 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
while ($dbserver = $dbservers_stmt->fetch(PDO::FETCH_ASSOC)) {
if (isset($dbserver['allowed_mysqlserver']) && !empty($dbserver['allowed_mysqlserver'])) {
$allowed_mysqlserver = json_encode(explode(",", $dbserver['allowed_mysqlserver']));
Database::pexecute($upd_stmt, ['allowed_mysql_server' => $allowed_mysqlserver, 'customerid' => $dbserver['customerid']]);
Database::pexecute($upd_stmt,
['allowed_mysql_server' => $allowed_mysqlserver, 'customerid' => $dbserver['customerid']]);
}
}
Update::lastStepStatus(0);
@@ -141,14 +141,15 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
// none of the files existed
Update::lastStepStatus(0);
} else {
Update::lastStepStatus(1, 'manual commands needed', 'Please run the following commands manually:<br><pre>' . $del_list . '</pre>');
Update::lastStepStatus(1, 'manual commands needed',
'Please run the following commands manually:<br><pre>' . $del_list . '</pre>');
}
}
Update::showUpdateStep("Adding new settings");
$panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int)$_POST['panel_settings_mode'] : 0;
Settings::AddNew("panel.settings_mode", $panel_settings_mode);
$system_distribution = isset($_POST['system_distribution']) ? $_POST['system_distribution'] : '';
$system_distribution = isset($_POST['system_distribution']) ? $_POST['system_distribution'] : 'bullseye';
Settings::AddNew("system.distribution", $system_distribution);
Settings::AddNew("system.update_channel", 'stable');
Settings::AddNew("system.updatecheck_data", '');
@@ -193,7 +194,6 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
}
if (Froxlor::isDatabaseVersion('202112310')) {
Update::showUpdateStep("Adjusting traffic tool settings");
$traffic_tool = Settings::Get('system.awstats_enabled') == 1 ? 'awstats' : 'webalizer';
Settings::AddNew("system.traffictool", $traffic_tool);
@@ -204,8 +204,13 @@ if (Froxlor::isDatabaseVersion('202112310')) {
}
if (Froxlor::isDatabaseVersion('202211030')) {
Update::showUpdateStep("Creating backward compatibility for cronjob");
$disabled = explode(',', ini_get('disable_functions'));
$exec_allowed = !in_array('exec', $disabled);
// check whether old files could be deleted in previous updates and if not,
// user should run cron to regenerate cron.d-file manually as he will run
// the other commands manually only after the update so this file would be deleted too
if ($exec_allowed) {
$complete_filedir = Froxlor::getInstallDir() . '/scripts';
mkdir($complete_filedir, 0750, true);
$newCronBin = Froxlor::getInstallDir() . '/bin/froxlor-cli';
@@ -218,6 +223,12 @@ exit;
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') . 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>');
}
Froxlor::updateToDbVersion('202212060');
}
@@ -259,6 +270,9 @@ if (Froxlor::isFroxlorVersion('2.0.3')) {
// check if compat. cronjob still exists (most likely didn't run successfully b/c of error from former 2.0 release)
if (@file_exists($complete_filedir . '/froxlor_master_cronjob.php')) {
Update::showUpdateStep("Adjusting backward compatibility for cronjob");
$disabled = explode(',', ini_get('disable_functions'));
$exec_allowed = !in_array('exec', $disabled);
if ($exec_allowed) {
$newCronBin = Froxlor::getInstallDir() . '/bin/froxlor-cli';
$compCron = <<<EOF
<?php
@@ -269,8 +283,13 @@ exit;
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') . 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>');
}
}
Froxlor::updateToVersion('2.0.4');
}
@@ -301,3 +320,185 @@ if (Froxlor::isFroxlorVersion('2.0.6')) {
Froxlor::updateToVersion('2.0.7');
}
if (Froxlor::isDatabaseVersion('202212060')) {
Update::showUpdateStep("Validating acme.sh challenge path");
$acmesh_challenge_dir = Settings::Get('system.letsencryptchallengepath');
$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);
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);
}
Froxlor::updateToDbVersion('202301120');
}
if (Froxlor::isFroxlorVersion('2.0.7')) {
Update::showUpdateStep("Updating from 2.0.7 to 2.0.8", false);
// adjust file-logging to be set to froxlor/logs/
$logtypes = explode(',', Settings::Get('logger.logtypes'));
if (in_array('file', $logtypes)) {
Update::showUpdateStep("Adjusting froxlor logfile for system-logging to be stored in logs/froxlor.log");
Settings::Set('logger.logfile', 'froxlor.log');
Update::lastStepStatus(0);
}
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');
}
if (Froxlor::isFroxlorVersion('2.0.12')) {
Update::showUpdateStep("Updating from 2.0.12 to 2.0.13", false);
Froxlor::updateToVersion('2.0.13');
}
if (Froxlor::isDatabaseVersion('202302030')) {
Update::showUpdateStep("Correcting language mapping of templates created pre 2.0.x");
// languages from 0.10.x
$language_mapping_comp = [
'de' => 'Deutsch',
'en' => 'English',
'fr' => 'Fran&ccedil;ais',
'pt' => 'Portugu&ecirc;s',
'it' => 'Italiano',
'nl' => 'Nederlands',
'se' => 'Svenska',
'cz' => '&#268;esk&aacute; republika'
];
$upd_tpl_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_TEMPLATES . "` SET `language` = :iso WHERE `language` = :lng");
foreach ($language_mapping_comp as $iso => $lang) {
Database::pexecute($upd_tpl_stmt, ['iso' => $iso, 'lng' => $lang]);
}
Update::lastStepStatus(0);
Update::showUpdateStep("Enhancing ssl data table");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` CHANGE `expirationdate` `validtodate` datetime DEFAULT NULL;");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` ADD `validfromdate` datetime DEFAULT NULL AFTER `ssl_fullchain_file`;");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` ADD `issuer` varchar(255) NOT NULL default '' AFTER `validtodate`;");
Update::lastStepStatus(0);
Update::showUpdateStep("Filling new ssl data fields with existing certificate data");
$crt_upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` SET `validfromdate` = :validfromdate, `issuer` = :issuer WHERE `id` = :id");
$crt_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`");
Database::pexecute($crt_stmt);
while ($cert = $crt_stmt->fetch(\PDO::FETCH_ASSOC)) {
$cert_content = openssl_x509_parse($cert['ssl_cert_file']);
if (is_array($cert_content)) {
$validfromdate = empty($cert_content['validFrom_time_t']) ? null : date("Y-m-d H:i:s", $cert_content['validFrom_time_t']);
$issuer = $cert_content['issuer']['O'] ?? "";
Database::pexecute($crt_upd_stmt, ['validfromdate' => $validfromdate, 'issuer' => $issuer, 'id' => $cert['id']]);
}
}
// clear possible user customized columns
Database::query("DELETE FROM `" . TABLE_PANEL_USERCOLUMNS . "` WHERE `section` = 'sslcertificates_list'");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202303150');
}
if (Froxlor::isFroxlorVersion('2.0.13')) {
Update::showUpdateStep("Updating from 2.0.13 to 2.0.14", false);
Froxlor::updateToVersion('2.0.14');
}
if (Froxlor::isFroxlorVersion('2.0.14')) {
Update::showUpdateStep("Updating from 2.0.14 to 2.0.15", false);
Froxlor::updateToVersion('2.0.15');
}
if (Froxlor::isDatabaseVersion('202303150')) {
Update::showUpdateStep("Adding new request rate limit settings");
Settings::AddNew("system.req_limit_per_interval", "60");
Settings::AddNew("system.req_limit_interval", "60");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202304260');
}
if (Froxlor::isFroxlorVersion('2.0.15')) {
Update::showUpdateStep("Updating from 2.0.15 to 2.0.16", false);
Froxlor::updateToVersion('2.0.16');
}
if (Froxlor::isFroxlorVersion('2.0.16')) {
Update::showUpdateStep("Updating from 2.0.16 to 2.0.17", false);
Froxlor::updateToVersion('2.0.17');
}
if (Froxlor::isFroxlorVersion('2.0.17')) {
Update::showUpdateStep("Updating from 2.0.17 to 2.0.18", false);
Froxlor::updateToVersion('2.0.18');
}
if (Froxlor::isFroxlorVersion('2.0.18')) {
Update::showUpdateStep("Updating from 2.0.18 to 2.0.19", false);
Froxlor::updateToVersion('2.0.19');
}
if (Froxlor::isFroxlorVersion('2.0.19')) {
Update::showUpdateStep("Updating from 2.0.19 to 2.0.20", false);
Froxlor::updateToVersion('2.0.20');
}
if (Froxlor::isFroxlorVersion('2.0.20')) {
Update::showUpdateStep("Updating from 2.0.20 to 2.0.21", false);
Froxlor::updateToVersion('2.0.21');
}

View File

@@ -34,9 +34,14 @@ $return = [];
if (Update::versionInUpdate($current_db_version, '202004140')) {
$has_preconfig = true;
$description = 'Froxlor can now optionally validate the dns entries of domains that request Lets Encrypt certificates to reduce dns-related problems (e.g. freshly registered domain or updated a-record).';
$return['system_le_domain_dnscheck_note'] = ['type' => 'infotext', 'value' => $description];
$question = '<strong>Validate DNS of domains when using Lets Encrypt&nbsp;';
$return['system_le_domain_dnscheck'] = ['type' => 'checkbox', 'value' => 1, 'checked' => 1, 'label' => $question];
$return['system_le_domain_dnscheck'] = [
'type' => 'checkbox',
'value' => 1,
'checked' => 1,
'label' => $question,
'prior_infotext' => $description
];
}
$preconfig['fields'] = $return;

View File

@@ -27,6 +27,7 @@ use Froxlor\Froxlor;
use Froxlor\FileDir;
use Froxlor\Config\ConfigParser;
use Froxlor\Install\Update;
use Froxlor\Settings;
$preconfig = [
'title' => '2.x updates',
@@ -36,7 +37,6 @@ $return = [];
if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
$description = 'We have rearranged the settings and split them into basic and advanced categories. This makes it easier for users who do not need all the detailed or very specific settings and options and gives a better overview of the basic/mostly used settings.';
$return['panel_settings_mode_note'] = ['type' => 'infotext', 'value' => $description];
$question = '<strong>Chose settings mode (you can change that at any time)</strong>';
$return['panel_settings_mode'] = [
'type' => 'select',
@@ -45,16 +45,16 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
1 => 'Advanced'
],
'selected' => 1,
'label' => $question
'label' => $question,
'prior_infotext' => $description
];
$description = 'The configuration page now can preselect a distribution, please select your current distribution';
$return['system_distribution_note'] = ['type' => 'infotext', 'value' => $description];
$question = '<strong>Select distribution</strong>';
$config_dir = FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/lib/configfiles/');
// show list of available distro's
$distros = glob($config_dir . '*.xml');
$distributions_select[''] = '-';
// selection is required $distributions_select[''] = '-';
// read in all the distros
foreach ($distros as $_distribution) {
// get configparser object
@@ -68,9 +68,44 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
'type' => 'select',
'select_var' => $distributions_select,
'selected' => '',
'label' => $question
'label' => $question,
'prior_infotext' => $description
];
}
if (Update::versionInUpdate($current_db_version, '202301120')) {
$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 (recommended value: ' . $recommended . ')</strong>';
$return['system_letsencryptchallengepath_upd'] = [
'type' => 'text',
'value' => $recommended,
'placeholder' => $acmesh_challenge_dir,
'label' => $question,
'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,
];
}
}
$preconfig['fields'] = $return;
return $preconfig;

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

@@ -26,6 +26,7 @@
namespace Froxlor\Api;
use Exception;
use Froxlor\Http\RateLimiter;
use Froxlor\Settings;
use voku\helper\AntiXSS;
@@ -52,6 +53,8 @@ class Api
if (Settings::Get('api.enabled') != 1) {
throw new Exception('API is not enabled. Please contact the administrator if you think this is wrong.', 400);
}
RateLimiter::run();
}
/**
@@ -117,6 +120,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

@@ -95,7 +95,7 @@ class Admins extends ApiCommand implements ResourceEntity
public function listing()
{
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list admins");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list admins");
$query_fields = [];
$result_stmt = Database::prepare("
SELECT *
@@ -407,7 +407,7 @@ class Admins extends ApiCommand implements ResourceEntity
];
$result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] get admin '" . $result['loginname'] . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get admin '" . $result['loginname'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "loginname '" . $loginname . "'");
@@ -705,7 +705,7 @@ class Admins extends ApiCommand implements ResourceEntity
WHERE `adminid` = :adminid
");
Database::pexecute($upd_stmt, $upd_data, true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] edited admin '" . $result['loginname'] . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] edited admin '" . $result['loginname'] . "'");
// get all admin-data for return-array
$result = $this->apiCall('Admins.get', [

View File

@@ -97,7 +97,7 @@ class Certificates extends ApiCommand implements ResourceEntity
}
if (!$has_cert) {
$this->addOrUpdateCertificate($domain['id'], $ssl_cert_file, $ssl_key_file, $ssl_ca_file, $ssl_cert_chainfile, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added ssl-certificate for '" . $domain['domain'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added ssl-certificate for '" . $domain['domain'] . "'");
$result = $this->apiCall('Certificates.get', [
'id' => $domain['id']
]);
@@ -127,7 +127,9 @@ class Certificates extends ApiCommand implements ResourceEntity
}
$do_verify = true;
$expirationdate = null;
$validtodate = null;
$validtodate = null;
$issuer = "";
// no cert-file given -> forget everything
if ($ssl_cert_file == '') {
$ssl_key_file = '';
@@ -168,7 +170,10 @@ class Certificates extends ApiCommand implements ResourceEntity
} else {
Response::standardError('sslcertificateinvalidcert', '', true);
}
$expirationdate = empty($cert_content['validTo_time_t']) ? null : date("Y-m-d H:i:s", $cert_content['validTo_time_t']);
// get data from certificate to store in the table
$validfromdate = empty($cert_content['validFrom_time_t']) ? null : date("Y-m-d H:i:s", $cert_content['validFrom_time_t']);
$validtodate = empty($cert_content['validTo_time_t']) ? null : date("Y-m-d H:i:s", $cert_content['validTo_time_t']);
$issuer = $cert_content['issuer']['O'] ?? "";
}
// Add/Update database entry
@@ -183,7 +188,9 @@ class Certificates extends ApiCommand implements ResourceEntity
`ssl_key_file` = :ssl_key_file,
`ssl_ca_file` = :ssl_ca_file,
`ssl_cert_chainfile` = :ssl_cert_chainfile,
`expirationdate` = :expirationdate
`validfromdate` = :validfromdate,
`validtodate` = :validtodate,
`issuer` = :issuer
" . $qrywhere . " `domainid`= :domainid
");
$params = [
@@ -191,7 +198,9 @@ class Certificates extends ApiCommand implements ResourceEntity
"ssl_key_file" => $ssl_key_file,
"ssl_ca_file" => $ssl_ca_file,
"ssl_cert_chainfile" => $ssl_cert_chainfile,
"expirationdate" => $expirationdate,
"validfromdate" => $validfromdate,
"validtodate" => $validtodate,
"issuer" => $issuer,
"domainid" => $domainid
];
Database::pexecute($stmt, $params, true, true);
@@ -239,7 +248,7 @@ class Certificates extends ApiCommand implements ResourceEntity
$ssl_ca_file = $this->getParam('ssl_ca_file', true, '');
$ssl_cert_chainfile = $this->getParam('ssl_cert_chainfile', true, '');
$this->addOrUpdateCertificate($domain['id'], $ssl_cert_file, $ssl_key_file, $ssl_ca_file, $ssl_cert_chainfile, false);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated ssl-certificate for '" . $domain['domain'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated ssl-certificate for '" . $domain['domain'] . "'");
$result = $this->apiCall('Certificates.get', [
'id' => $domain['id']
]);
@@ -299,16 +308,12 @@ class Certificates extends ApiCommand implements ResourceEntity
}
// Set data from certificate
$cert['isvalid'] = false;
$cert['san'] = null;
$cert_data = openssl_x509_parse($cert['ssl_cert_file']);
if ($cert_data) {
$cert['validfromdate'] = date('Y-m-d H:i:s', $cert_data['validFrom_time_t']);
$cert['validtodate'] = date('Y-m-d H:i:s', $cert_data['validTo_time_t']);
$cert['isvalid'] = (bool)$cert_data['validTo_time_t'] > time();
$cert['issuer'] = $cert_data['issuer']['O'] ?? null;
}
// Set subject alt names from certificate
$cert['san'] = null;
if (isset($cert_data['extensions']['subjectAltName']) && !empty($cert_data['extensions']['subjectAltName'])) {
$SANs = explode(",", $cert_data['extensions']['subjectAltName']);
$SANs = array_map('trim', $SANs);
@@ -319,7 +324,7 @@ class Certificates extends ApiCommand implements ResourceEntity
}
}
}
}
$result[] = $cert;
}
return $this->response([
@@ -465,7 +470,7 @@ class Certificates extends ApiCommand implements ResourceEntity
if ($chk['letsencrypt'] == '1') {
Cronjob::inserttask(TaskId::DELETE_DOMAIN_SSL, $chk['domain']);
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] removed ssl-certificate for '" . $chk['domain'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] removed ssl-certificate for '" . $chk['domain'] . "'");
return $this->response($result);
}
throw new Exception("Unable to determine SSL certificate. Maybe no access?", 406);

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);
@@ -134,7 +147,7 @@ class Cronjobs extends ApiCommand implements ResourceEntity
// insert task to re-generate the cron.d-file
Cronjob::inserttask(TaskId::REBUILD_CRON);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] cronjob with description '" . $result['module'] . '/' . $result['cronfile'] . "' has been updated by '" . $this->getUserDetail('loginname') . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] cronjob with description '" . $result['module'] . '/' . $result['cronfile'] . "' has been updated by '" . $this->getUserDetail('loginname') . "'");
$result = $this->apiCall('Cronjobs.get', [
'id' => $id
]);
@@ -164,7 +177,7 @@ class Cronjobs extends ApiCommand implements ResourceEntity
public function listing()
{
if ($this->isAdmin()) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list cronjobs");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list cronjobs");
$query_fields = [];
$result_stmt = Database::prepare("
SELECT `c`.* FROM `" . TABLE_PANEL_CRONRUNS . "` `c` " . $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit());

View File

@@ -194,7 +194,7 @@ class CustomerBackups extends ApiCommand implements ResourceEntity
$result[] = $entry;
}
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list customer-backups");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list customer-backups");
return $this->response([
'count' => count($result),
'list' => $result

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);
@@ -895,7 +895,7 @@ class Customers extends ApiCommand implements ResourceEntity
$result['dbspace_used'] = 0;
}
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get customer '" . $result['loginname'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get customer '" . $result['loginname'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "loginname '" . $loginname . "'");
@@ -1327,7 +1327,7 @@ class Customers extends ApiCommand implements ResourceEntity
'vu' => $valid_until
], true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] " . ($deactivated ? 'deactivated' : 'reactivated') . " user '" . $result['loginname'] . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] " . ($deactivated ? 'deactivated' : 'reactivated') . " user '" . $result['loginname'] . "'");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
}
@@ -1538,7 +1538,7 @@ class Customers extends ApiCommand implements ResourceEntity
Database::query($admin_update_query);
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] edited user '" . $result['loginname'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] edited user '" . $result['loginname'] . "'");
/*
* move customer to another admin/reseller; #1166
@@ -1911,7 +1911,7 @@ class Customers extends ApiCommand implements ResourceEntity
// now, recalculate the resource-usage for the old and the new admin
User::updateCounters(false);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] moved user '" . $c_result['loginname'] . "' from admin/reseller '" . $c_result['adminname'] . " to admin/reseller '" . $a_result['loginname'] . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] moved user '" . $c_result['loginname'] . "' from admin/reseller '" . $c_result['adminname'] . " to admin/reseller '" . $a_result['loginname'] . "'");
$result = $this->apiCall('Customers.get', [
'id' => $c_result['customerid']

View File

@@ -144,7 +144,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
];
Database::pexecute($stmt, $params, true, true);
$id = Database::lastInsertId();
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added directory-option for '" . $userpath . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added directory-option for '" . $userpath . "'");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
$result = $this->apiCall('DirOptions.get', [
@@ -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);
}
/**
@@ -248,7 +247,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
$params['id'] = $id;
$result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) {
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get directory options for '" . $result['path'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get directory options for '" . $result['path'] . "'");
return $this->response($result);
}
$key = "id #" . $id;
@@ -332,7 +331,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
"id" => $id
];
Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] edited directory options for '" . str_replace($customer['documentroot'], '/', $result['path']) . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] edited directory options for '" . str_replace($customer['documentroot'], '/', $result['path']) . "'");
}
$result = $this->apiCall('DirOptions.get', [
@@ -380,7 +379,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
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 directory-options");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list directory-options");
return $this->response([
'count' => count($result),
'list' => $result
@@ -479,7 +478,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
"customerid" => $customer_data['customerid'],
"id" => $id
], true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted directory-option for '" . str_replace($customer_data['documentroot'], '/', $result['path']) . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] deleted directory-option for '" . str_replace($customer_data['documentroot'], '/', $result['path']) . "'");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
return $this->response($result);
}

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("
@@ -128,7 +129,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
];
Database::pexecute($stmt, $params, true, true);
$id = Database::lastInsertId();
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added directory-protection for '" . $username . " (" . $path . ")'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added directory-protection for '" . $username . " (" . $path . ")'");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
$result = $this->apiCall('DirProtections.get', [
@@ -195,7 +196,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
$params['idun'] = ($id <= 0 ? $username : $id);
$result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) {
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get directory protection for '" . $result['path'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get directory protection for '" . $result['path'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "username '" . $username . "'");
@@ -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 = [
@@ -277,7 +279,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
Cronjob::inserttask(TaskId::REBUILD_VHOST);
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated directory-protection '" . $result['username'] . " (" . $result['path'] . ")'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated directory-protection '" . $result['username'] . " (" . $result['path'] . ")'");
$result = $this->apiCall('DirProtections.get', [
'id' => $result['id']
]);
@@ -323,7 +325,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
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 directory-protections");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list directory-protections");
return $this->response([
'count' => count($result),
'list' => $result
@@ -411,7 +413,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
"id" => $id
]);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted htpasswd for '" . $result['username'] . " (" . $result['path'] . ")'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] deleted htpasswd for '" . $result['username'] . " (" . $result['path'] . ")'");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
return $this->response($result);
}

View File

@@ -413,7 +413,7 @@ class DomainZones extends ApiCommand implements ResourceEntity
$zone = Dns::createDomainZone($id);
$zonefile = (string)$zone;
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get dns-zone for '" . $result['domain'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get dns-zone for '" . $result['domain'] . "'");
return $this->response(explode("\n", $zonefile));
}

View File

@@ -225,6 +225,8 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, whether php is enabled for this domain, default 0 (false)
* @param bool $openbasedir
* optional, whether to activate openbasedir restriction for this domain, default 0 (false)
* @param int $openbasedir_path
* optional, either 0 for domains-docroot, 1 for customers-homedir or 2 for parent-directory of domains-docroot
* @param int $phpsettingid
* optional, specify php-configuration that is being used by id, default 1 (system-default)
* @param int $mod_fcgid_starter
@@ -312,6 +314,7 @@ class Domains extends ApiCommand implements ResourceEntity
$documentroot = $this->getParam('documentroot', true, '');
$phpenabled = $this->getBoolParam('phpenabled', true, 0);
$openbasedir = $this->getBoolParam('openbasedir', true, 0);
$openbasedir_path = $this->getParam('openbasedir_path', true, 0);
$phpsettingid = $this->getParam('phpsettingid', true, 1);
$mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, -1);
$mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, -1);
@@ -404,20 +407,25 @@ class Domains extends ApiCommand implements ResourceEntity
$documentroot = $_documentroot;
}
$registration_date = Validate::validate($registration_date, 'registration_date', Validate::REGEX_YYYY_MM_DD, '', [
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, '', [
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;
}
@@ -525,6 +533,10 @@ class Domains extends ApiCommand implements ResourceEntity
$mod_fcgid_maxrequests = '-1';
}
if ($openbasedir_path > 2 && $openbasedir_path < 0) {
$openbasedir_path = 0;
}
// check non-ssl IP
$ipandports = $this->validateIpAddresses($p_ipandports);
// check ssl IP
@@ -559,7 +571,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);
@@ -696,6 +708,7 @@ class Domains extends ApiCommand implements ResourceEntity
'caneditdomain' => $caneditdomain,
'phpenabled' => $phpenabled,
'openbasedir' => $openbasedir,
'openbasedir_path' => $openbasedir_path,
'speciallogfile' => $speciallogfile,
'specialsettings' => $specialsettings,
'ssl_specialsettings' => $ssl_specialsettings,
@@ -749,6 +762,7 @@ class Domains extends ApiCommand implements ResourceEntity
`caneditdomain` = :caneditdomain,
`phpenabled` = :phpenabled,
`openbasedir` = :openbasedir,
`openbasedir_path` = :openbasedir_path,
`speciallogfile` = :speciallogfile,
`specialsettings` = :specialsettings,
`ssl_specialsettings` = :ssl_specialsettings,
@@ -884,7 +898,7 @@ class Domains extends ApiCommand implements ResourceEntity
$result['ipsandports'] = $this->getIpsForDomain($result['id']);
}
$result['domain_hascert'] = $this->getHasCertValueForDomain((int)$result['id'], (int)$result['parentdomainid']);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] get domain '" . $result['domain'] . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get domain '" . $result['domain'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "domainname '" . $domainname . "'");
@@ -1096,6 +1110,8 @@ class Domains extends ApiCommand implements ResourceEntity
* from setting system.apply_phpconfigs_default
* @param bool $openbasedir
* optional, whether to activate openbasedir restriction for this domain, default 0 (false)
* @param int $openbasedir_path
* optional, either 0 for domains-docroot, 1 for customers-homedir or 2 for parent-directory of domains-docroot
* @param int $phpsettingid
* optional, specify php-configuration that is being used by id, default 1 (system-default)
* @param int $mod_fcgid_starter
@@ -1193,6 +1209,7 @@ class Domains extends ApiCommand implements ResourceEntity
$phpenabled = $this->getBoolParam('phpenabled', true, $result['phpenabled']);
$phpfs = $this->getBoolParam('phpsettingsforsubdomains', true, Settings::Get('system.apply_phpconfigs_default'));
$openbasedir = $this->getBoolParam('openbasedir', true, $result['openbasedir']);
$openbasedir_path = $this->getParam('openbasedir_path', true, $result['openbasedir_path']);
$phpsettingid = $this->getParam('phpsettingid', true, $result['phpsettingid']);
$mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, $result['mod_fcgid_starter']);
$mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, $result['mod_fcgid_maxrequests']);
@@ -1322,19 +1339,25 @@ class Domains extends ApiCommand implements ResourceEntity
$adminid = $result['adminid'];
}
$registration_date = Validate::validate($registration_date, 'registration_date', Validate::REGEX_YYYY_MM_DD, '', [
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, '', [
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;
}
@@ -1478,6 +1501,11 @@ class Domains extends ApiCommand implements ResourceEntity
$mod_fcgid_maxrequests = $result['mod_fcgid_maxrequests'];
}
// check changes of openbasedir-path variable
if ($openbasedir_path > 2 && $openbasedir_path < 0) {
$openbasedir_path = 0;
}
// check non-ssl IP
$ipandports = $this->validateIpAddresses($p_ipandports, false, $result['id']);
// check ssl IP
@@ -1523,7 +1551,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);
@@ -1623,7 +1651,31 @@ class Domains extends ApiCommand implements ResourceEntity
$wwwserveralias = ($serveraliasoption == '1') ? '1' : '0';
$iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0';
if ($documentroot != $result['documentroot'] || $ssl_redirect != $result['ssl_redirect'] || $wwwserveralias != $result['wwwserveralias'] || $iswildcarddomain != $result['iswildcarddomain'] || $phpenabled != $result['phpenabled'] || $openbasedir != $result['openbasedir'] || $phpsettingid != $result['phpsettingid'] || $mod_fcgid_starter != $result['mod_fcgid_starter'] || $mod_fcgid_maxrequests != $result['mod_fcgid_maxrequests'] || $specialsettings != $result['specialsettings'] || $ssl_specialsettings != $result['ssl_specialsettings'] || $notryfiles != $result['notryfiles'] || $writeaccesslog != $result['writeaccesslog'] || $writeerrorlog != $result['writeerrorlog'] || $aliasdomain != $result['aliasdomain'] || $issubof != $result['ismainbutsubto'] || $email_only != $result['email_only'] || ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1') || $letsencrypt != $result['letsencrypt'] || $http2 != $result['http2'] || $hsts_maxage != $result['hsts'] || $hsts_sub != $result['hsts_sub'] || $hsts_preload != $result['hsts_preload'] || $ocsp_stapling != $result['ocsp_stapling']) {
if ($documentroot != $result['documentroot']
|| $ssl_redirect != $result['ssl_redirect']
|| $wwwserveralias != $result['wwwserveralias']
|| $iswildcarddomain != $result['iswildcarddomain']
|| $phpenabled != $result['phpenabled']
|| $openbasedir != $result['openbasedir']
|| $phpsettingid != $result['phpsettingid']
|| $mod_fcgid_starter != $result['mod_fcgid_starter']
|| $mod_fcgid_maxrequests != $result['mod_fcgid_maxrequests']
|| $specialsettings != $result['specialsettings']
|| $ssl_specialsettings != $result['ssl_specialsettings']
|| $notryfiles != $result['notryfiles']
|| $writeaccesslog != $result['writeaccesslog']
|| $writeerrorlog != $result['writeerrorlog']
|| $aliasdomain != $result['aliasdomain']
|| $issubof != $result['ismainbutsubto']
|| $email_only != $result['email_only']
|| ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1')
|| $letsencrypt != $result['letsencrypt']
|| $http2 != $result['http2']
|| $hsts_maxage != $result['hsts']
|| $hsts_sub != $result['hsts_sub']
|| $hsts_preload != $result['hsts_preload']
|| $ocsp_stapling != $result['ocsp_stapling']
) {
Cronjob::inserttask(TaskId::REBUILD_VHOST);
}
@@ -1749,7 +1801,7 @@ class Domains extends ApiCommand implements ResourceEntity
Database::pexecute($upd_stmt, [
'id' => $id
], true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] removed specialsettings on all subdomains of domain #" . $id);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] removed specialsettings on all subdomains of domain #" . $id);
}
$wwwserveralias = ($serveraliasoption == '1') ? '1' : '0';
@@ -1771,7 +1823,8 @@ class Domains extends ApiCommand implements ResourceEntity
$update_data['wwwserveralias'] = $wwwserveralias;
$update_data['iswildcarddomain'] = $iswildcarddomain;
$update_data['phpenabled'] = $phpenabled;
$update_data['openbasedir'] = $openbasedir;
$update_data['openbasedir'] = $openbasedir;;
$update_data['openbasedir_path'] = $openbasedir_path;
$update_data['speciallogfile'] = $speciallogfile;
$update_data['phpsettingid'] = $phpsettingid;
$update_data['mod_fcgid_starter'] = $mod_fcgid_starter;
@@ -1819,6 +1872,7 @@ class Domains extends ApiCommand implements ResourceEntity
`iswildcarddomain` = :iswildcarddomain,
`phpenabled` = :phpenabled,
`openbasedir` = :openbasedir,
`openbasedir_path` = :openbasedir_path,
`speciallogfile` = :speciallogfile,
`phpsettingid` = :phpsettingid,
`mod_fcgid_starter` = :mod_fcgid_starter,
@@ -1854,6 +1908,7 @@ class Domains extends ApiCommand implements ResourceEntity
$_update_data['adminid'] = $adminid;
$_update_data['phpenabled'] = $phpenabled;
$_update_data['openbasedir'] = $openbasedir;
$_update_data['openbasedir_path'] = $openbasedir_path;
$_update_data['mod_fcgid_starter'] = $mod_fcgid_starter;
$_update_data['mod_fcgid_maxrequests'] = $mod_fcgid_maxrequests;
$_update_data['notryfiles'] = $notryfiles;
@@ -1887,6 +1942,7 @@ class Domains extends ApiCommand implements ResourceEntity
`adminid` = :adminid,
`phpenabled` = :phpenabled,
`openbasedir` = :openbasedir,
`openbasedir_path` = :openbasedir_path,
`mod_fcgid_starter` = :mod_fcgid_starter,
`mod_fcgid_maxrequests` = :mod_fcgid_maxrequests,
`notryfiles` = :notryfiles,
@@ -1903,6 +1959,18 @@ class Domains extends ApiCommand implements ResourceEntity
");
Database::pexecute($_update_stmt, $_update_data, true, true);
// get current ip<>domain entries
$ip_sel_stmt = Database::prepare("
SELECT id_ipandports FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id
");
Database::pexecute($ip_sel_stmt, [
'id' => $id
], true, true);
$current_ips = [];
while ($cIP = $ip_sel_stmt->fetch(\PDO::FETCH_ASSOC)) {
$current_ips[] = $cIP['id_ipandports'];
}
// Cleanup domain <-> ip mapping
$del_stmt = Database::prepare("
DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id
@@ -1930,6 +1998,12 @@ class Domains extends ApiCommand implements ResourceEntity
}
}
// check ip changes
$all_new_ips = array_merge($ipandports, $ssl_ipandports);
if (count(array_diff($current_ips, $all_new_ips)) != 0 || count(array_diff($all_new_ips, $current_ips)) != 0) {
Cronjob::inserttask(TaskId::REBUILD_VHOST);
}
// Cleanup domain <-> ip mapping for subdomains
$domainidsresult_stmt = Database::prepare("
SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `parentdomainid` = :id
@@ -1974,12 +2048,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 +2211,9 @@ class Domains extends ApiCommand implements ResourceEntity
'domainid' => $id
], true, true);
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']);
@@ -2146,7 +2221,7 @@ class Domains extends ApiCommand implements ResourceEntity
// remove domain from acme.sh / lets encrypt if used
Cronjob::inserttask(TaskId::DELETE_DOMAIN_SSL, $result['domain']);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] deleted domain/subdomains (#" . $result['id'] . ")");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] deleted domain/subdomains (#" . $result['id'] . ")");
User::updateCounters();
Cronjob::inserttask(TaskId::REBUILD_VHOST);
// Using nameserver, insert a task which rebuilds the server config

View File

@@ -63,7 +63,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
* @param string $alternative_email
* optional email address to send account information to, default is the account that is being created
* @param int $email_quota
* optional quota if enabled in MB, default 0
* optional quota if enabled in MB, default setting: system.mail_quota
* @param bool $sendinfomail
* optional, sends the welcome message to the new account (needed for creation, without the user won't
* be able to login before any mail is received), default 1 (true)
@@ -85,7 +85,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$emailaddr = $this->getParam('emailaddr', $ea_optional, '');
$email_password = $this->getParam('email_password');
$alternative_email = $this->getParam('alternative_email', true, '');
$quota = $this->getParam('email_quota', true, 0);
$quota = $this->getParam('email_quota', true, Settings::Get('system.mail_quota') ?? 0);
$sendinfomail = $this->getBoolParam('sendinfomail', true, 1);
// validation
@@ -99,6 +99,11 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
Response::standardError('notallowedtouseaccounts', '', true);
}
if (!empty($emailaddr)) {
$idna_convert = new IdnaWrapper();
$emailaddr = $idna_convert->encode($emailaddr);
}
// get email address
$result = $this->apiCall('Emails.get', [
'id' => $id,
@@ -306,7 +311,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
}
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added email account for '" . $result['email_full'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added email account for '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [
'emailaddr' => $result['email_full']
]);
@@ -357,6 +362,11 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$ea_optional = $id > 0;
$emailaddr = $this->getParam('emailaddr', $ea_optional, '');
if (!empty($emailaddr)) {
$idna_convert = new IdnaWrapper();
$emailaddr = $idna_convert->encode($emailaddr);
}
// validation
$result = $this->apiCall('Emails.get', [
'id' => $id,
@@ -450,7 +460,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
Admins::increaseUsage($customer['adminid'], 'email_quota_used', '', ($quota - $result['quota']));
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated email account '" . $result['email_full'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated email account '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [
'emailaddr' => $result['email_full']
]);
@@ -556,7 +566,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
Customers::decreaseUsage($customer['customerid'], 'email_accounts_used');
Customers::decreaseUsage($customer['customerid'], 'email_quota_used', '', $quota);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted email account for '" . $result['email_full'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] deleted email account for '" . $result['email_full'] . "'");
return $this->response($result);
}
}

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_INFO,
"[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

@@ -77,6 +77,11 @@ class EmailForwarders extends ApiCommand implements ResourceEntity
$idna_convert = new IdnaWrapper();
$destination = $idna_convert->encode($destination);
if (!empty($emailaddr)) {
$idna_convert = new IdnaWrapper();
$emailaddr = $idna_convert->encode($emailaddr);
}
$result = $this->apiCall('Emails.get', [
'id' => $id,
'emailaddr' => $emailaddr
@@ -116,7 +121,7 @@ class EmailForwarders extends ApiCommand implements ResourceEntity
// update customer usage
Customers::increaseUsage($customer['customerid'], 'email_forwarders_used');
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added email forwarder for '" . $result['email_full'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added email forwarder for '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [
'emailaddr' => $result['email_full']
@@ -293,7 +298,7 @@ class EmailForwarders extends ApiCommand implements ResourceEntity
// update customer usage
Customers::decreaseUsage($customer['customerid'], 'email_forwarders_used');
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted email forwarder for '" . $result['email_full'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] deleted email forwarder for '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [
'emailaddr' => $result['email_full']

View File

@@ -159,7 +159,7 @@ class Emails extends ApiCommand implements ResourceEntity
// update customer usage
Customers::increaseUsage($customer['customerid'], 'emails_used');
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added email address '" . $email_full . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added email address '" . $email_full . "'");
$result = $this->apiCall('Emails.get', [
'emailaddr' => $email_full
@@ -195,11 +195,11 @@ class Emails extends ApiCommand implements ResourceEntity
FROM `" . TABLE_MAIL_VIRTUAL . "` v
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON v.`popaccountid` = u.`id`
WHERE v.`customerid` IN (" . implode(", ", $customer_ids) . ")
AND (v.`id`= :idea OR (v.`email` = :idea OR v.`email_full` = :idea))
");
AND " . (is_numeric($params['idea']) ? "v.`id`= :idea" : "(v.`email` = :idea OR v.`email_full` = :idea)")
);
$result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) {
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get email address '" . $result['email_full'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get email address '" . $result['email_full'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "emailaddr '" . $emailaddr . "'");
@@ -294,7 +294,7 @@ class Emails extends ApiCommand implements ResourceEntity
"id" => $id
];
Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] toggled catchall-flag for email address '" . $result['email_full'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] toggled catchall-flag for email address '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [
'emailaddr' => $result['email_full']
@@ -340,7 +340,7 @@ class Emails extends ApiCommand implements ResourceEntity
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-addresses");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list email-addresses");
return $this->response([
'count' => count($result),
'list' => $result
@@ -445,7 +445,7 @@ class Emails extends ApiCommand implements ResourceEntity
], true, true);
Customers::decreaseUsage($customer['customerid'], 'emails_used');
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted email address '" . $result['email_full'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] deleted email address '" . $result['email_full'] . "'");
return $this->response($result);
}
}

View File

@@ -64,7 +64,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
public function listing()
{
if ($this->isAdmin()) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list fpm-daemons");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list fpm-daemons");
$query_fields = [];
$result_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_FPMDAEMONS . "`" . $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit());
@@ -258,7 +258,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
$id = Database::lastInsertId();
Cronjob::inserttask(TaskId::REBUILD_VHOST);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] fpm-daemon with description '" . $description . "' has been created by '" . $this->getUserDetail('loginname') . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] fpm-daemon with description '" . $description . "' has been created by '" . $this->getUserDetail('loginname') . "'");
$result = $this->apiCall('FpmDaemons.get', [
'id' => $id
]);
@@ -384,7 +384,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
Database::pexecute($upd_stmt, $upd_data, true, true);
Cronjob::inserttask(TaskId::REBUILD_VHOST);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] fpm-daemon with description '" . $description . "' has been updated by '" . $this->getUserDetail('loginname') . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] fpm-daemon with description '" . $description . "' has been updated by '" . $this->getUserDetail('loginname') . "'");
$result = $this->apiCall('FpmDaemons.get', [
'id' => $id
]);
@@ -433,7 +433,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
], true, true);
Cronjob::inserttask(TaskId::REBUILD_VHOST);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] fpm-daemon setting '" . $result['description'] . "' has been deleted by '" . $this->getUserDetail('loginname') . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] fpm-daemon setting '" . $result['description'] . "' has been deleted by '" . $this->getUserDetail('loginname') . "'");
return $this->response($result);
}
throw new Exception("Not allowed to execute given command.", 403);

View File

@@ -72,7 +72,7 @@ class Froxlor extends ApiCommand
if (empty($uc_data) || empty($response) || $uc_data['ts'] + self::UPDATE_CHECK_INTERVAL < time() || $uc_data['channel'] != Settings::Get('system.update_channel') || $force_ucheck) {
// log our actions
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] checking for updates");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] checking for updates");
// check for new version
$aucheck = AutoUpdate::checkVersion();
@@ -142,7 +142,7 @@ class Froxlor extends ApiCommand
{
if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) {
$json_str = $this->getParam('json_str');
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "User " . $this->getUserDetail('loginname') . " imported settings");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "User " . $this->getUserDetail('loginname') . " imported settings");
try {
SImExporter::import($json_str);
Cronjob::inserttask(TaskId::REBUILD_VHOST);

View File

@@ -257,7 +257,7 @@ class Ftps extends ApiCommand implements ResourceEntity
Customers::increaseUsage($customer['customerid'], 'ftp_lastaccountnumber');
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added ftp-account '" . $username . " (" . $path . ")'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added ftp-account '" . $username . " (" . $path . ")'");
Cronjob::inserttask(TaskId::CREATE_FTP);
if ($sendinfomail == 1) {
@@ -302,7 +302,7 @@ class Ftps extends ApiCommand implements ResourceEntity
$this->mailer()->clearAddresses();
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] added ftp-user '" . $username . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added ftp-user '" . $username . "'");
$result = $this->apiCall('Ftps.get', [
'username' => $username
@@ -367,7 +367,7 @@ class Ftps extends ApiCommand implements ResourceEntity
$params['idun'] = ($id <= 0 ? $username : $id);
$result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) {
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get ftp-user '" . $result['username'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get ftp-user '" . $result['username'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "username '" . $username . "'");
@@ -453,7 +453,7 @@ class Ftps extends ApiCommand implements ResourceEntity
"id" => $id,
"password" => $cryptPassword
], true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated ftp-account password for '" . $result['username'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated ftp-account password for '" . $result['username'] . "'");
}
// path update?
@@ -471,7 +471,7 @@ class Ftps extends ApiCommand implements ResourceEntity
"customerid" => $customer['customerid'],
"id" => $id
], true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated ftp-account homdir for '" . $result['username'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated ftp-account homdir for '" . $result['username'] . "'");
}
}
// it's the task for "new ftp" but that will
@@ -533,7 +533,7 @@ class Ftps extends ApiCommand implements ResourceEntity
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 ftp-users");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list ftp-users");
return $this->response([
'count' => count($result),
'list' => $result

View File

@@ -61,7 +61,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
public function listing()
{
if ($this->isAdmin()) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list hosting-plans");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list hosting-plans");
$query_fields = [];
$result_stmt = Database::prepare("
SELECT p.*, a.loginname as adminname
@@ -227,7 +227,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
'valuearr' => json_encode($value_arr)
];
Database::pexecute($ins_stmt, $ins_data, true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] added hosting-plan '" . $name . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] added hosting-plan '" . $name . "'");
$result = $this->apiCall('HostingPlans.get', [
'planname' => $name
]);
@@ -264,7 +264,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
}
$result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] get hosting-plan '" . $result['name'] . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get hosting-plan '" . $result['name'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "planname '" . $planname . "'");
@@ -414,7 +414,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
'id' => $id
];
Database::pexecute($upd_stmt, $update_data, true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] updated hosting-plan '" . $result['name'] . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] updated hosting-plan '" . $result['name'] . "'");
return $this->response($update_data);
}
throw new Exception("Not allowed to execute given command.", 403);

View File

@@ -65,7 +65,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
public function listing()
{
if ($this->isAdmin() && ($this->getUserDetail('change_serversettings') || !empty($this->getUserDetail('ip')))) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list ips and ports");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list ips and ports");
$ip_where = "";
$append_where = false;
if (!empty($this->getUserDetail('ip')) && $this->getUserDetail('ip') != -1) {
@@ -335,7 +335,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
'id' => $id
], true, true);
if ($result) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] get ip " . $result['ip'] . " " . $result['port']);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get ip " . $result['ip'] . " " . $result['port']);
return $this->response($result);
}
throw new Exception("IP/port with id #" . $id . " could not be found", 404);

View File

@@ -26,14 +26,15 @@
namespace Froxlor\Api\Commands;
use Exception;
use PDO;
use PDOException;
use Froxlor\Froxlor;
use Froxlor\PhpHelper;
use Froxlor\Api\ApiCommand;
use Froxlor\Api\ResourceEntity;
use Froxlor\Database\Database;
use Froxlor\Froxlor;
use Froxlor\FroxlorLogger;
use Froxlor\PhpHelper;
use Froxlor\Validate\Validate;
use PDO;
use PDOException;
class MysqlServer extends ApiCommand implements ResourceEntity
{
@@ -73,8 +74,8 @@ class MysqlServer extends ApiCommand implements ResourceEntity
* optional, test connection with given credentials, default is true (yes)
*
* @access admin
* @throws Exception
* @return string json-encoded array
* @throws Exception
*/
public function add()
{
@@ -167,6 +168,8 @@ class MysqlServer extends ApiCommand implements ResourceEntity
$this->addDatabaseFromCustomerAllowedList($newdbserver);
}
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] added new database server '" . $description . "' (" . $mysql_host . ")");
return $this->response(['dbserver' => $newdbserver]);
}
@@ -179,8 +182,8 @@ class MysqlServer extends ApiCommand implements ResourceEntity
* optional the number of the mysql server (either id or dbserver must be set)
*
* @access admin
* @throws Exception
* @return string json-encoded array
* @throws Exception
*/
public function delete()
{
@@ -212,8 +215,12 @@ class MysqlServer extends ApiCommand implements ResourceEntity
// when removing, remove from list of allowed_mysqlservers from any customers
$this->removeDatabaseFromCustomerAllowedList($dbserver);
$description = $sql_root[$dbserver]['caption'] ?? "unknown";
$mysql_host = $sql_root[$dbserver]['host'] ?? "unknown";
unset($sql_root[$dbserver]);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] removed database server '" . $description . "' (" . $mysql_host . ")");
$this->generateNewUserData($sql, $sql_root);
return $this->response(['true']);
}
@@ -287,8 +294,8 @@ class MysqlServer extends ApiCommand implements ResourceEntity
* optional the number of the mysql server (either id or dbserver must be set)
*
* @access admin, customer
* @throws Exception
* @return string json-encoded array
* @throws Exception
*/
public function get()
{
@@ -317,6 +324,7 @@ class MysqlServer extends ApiCommand implements ResourceEntity
unset($sql_root[$dbserver]['password']);
$sql_root[$dbserver]['id'] = $dbserver;
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get database-server '" . $sql_root[$dbserver]['caption'] . "'");
return $this->response($sql_root[$dbserver]);
}
@@ -347,8 +355,8 @@ class MysqlServer extends ApiCommand implements ResourceEntity
* optional, test connection with given credentials, default is true (yes)
*
* @access admin
* @throws Exception
* @return string json-encoded array
* @throws Exception
*/
public function update()
{
@@ -448,6 +456,8 @@ class MysqlServer extends ApiCommand implements ResourceEntity
$this->addDatabaseFromCustomerAllowedList($dbserver);
}
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] edited database server '" . $description . "' (" . $mysql_host . ")");
return $this->response(['true']);
}
@@ -516,7 +526,7 @@ class MysqlServer extends ApiCommand implements ResourceEntity
`allowed_mysqlserver` = :am WHERE `customerid` = :cid
");
while ($customer = $sel_stmt->fetch(PDO::FETCH_ASSOC)) {
$allowed_mysqls = json_decode(($customer['allowed_mysqlserver'] ?? '[]'), true);
$allowed_mysqls = json_decode(($customer['allowed_mysqlserver'] ?: '[]'), true);
if (!in_array($dbserver, $allowed_mysqls)) {
$allowed_mysqls[] = $dbserver;
$allowed_mysqls = json_encode($allowed_mysqls);

View File

@@ -73,12 +73,12 @@ class Mysqls extends ApiCommand implements ResourceEntity
$password = $this->getParam('mysql_password');
// parameters
$dbserver = $this->getParam('mysql_server', true, 0);
$databasedescription = $this->getParam('description', true, '');
$databasename = $this->getParam('custom_suffix', true, '');
$sendinfomail = $this->getBoolParam('sendinfomail', true, 0);
// get needed customer info to reduce the mysql-usage-counter by one
$customer = $this->getCustomerData('mysqls');
$dbserver = $this->getParam('mysql_server', true, $this->getDefaultMySqlServer($customer));
// validation
$password = Validate::validate($password, 'password', '', '', [], true);
@@ -199,7 +199,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
$this->mailer()->clearAddresses();
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] added mysql-database '" . $username . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added mysql-database '" . $username . "'");
$result = $this->apiCall('Mysqls.get', [
'dbname' => $username,
@@ -299,7 +299,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
$mbdata = $mbdata_stmt->fetch(PDO::FETCH_ASSOC);
Database::needRoot(false);
$result['size'] = $mbdata['MB'] ?? 0;
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get database '" . $result['databasename'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get database '" . $result['databasename'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "dbname '" . $dbname . "'");
@@ -388,7 +388,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
];
Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] updated mysql-database '" . $result['databasename'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated mysql-database '" . $result['databasename'] . "'");
$result = $this->apiCall('Mysqls.get', [
'dbname' => $result['databasename']
]);
@@ -558,4 +558,13 @@ class Mysqls extends ApiCommand implements ResourceEntity
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] deleted database '" . $result['databasename'] . "'");
return $this->response($result);
}
private function getDefaultMySqlServer(array $customer) {
$allowed_mysqlservers = json_decode($customer['allowed_mysqlserver'] ?? '[]', true);
asort($allowed_mysqlservers, SORT_NUMERIC);
if (count($allowed_mysqlservers) == 1 && $allowed_mysqlservers[0] != 0) {
return (int) $allowed_mysqlservers[0];
}
return (int) array_shift($allowed_mysqlservers);
}
}

View File

@@ -67,7 +67,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
public function listing()
{
if ($this->isAdmin()) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list php-configs");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list php-configs");
$with_subdomains = $this->getBoolParam('with_subdomains', true, false);
$query_fields = [];
@@ -392,7 +392,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
$ins_data['id'] = Database::lastInsertId();
Cronjob::inserttask(TaskId::REBUILD_VHOST);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] php setting with description '" . $description . "' has been created by '" . $this->getUserDetail('loginname') . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] php setting with description '" . $description . "' has been created by '" . $this->getUserDetail('loginname') . "'");
$result = $this->apiCall('PhpSettings.get', [
'id' => $ins_data['id']
@@ -629,7 +629,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
Database::pexecute($upd_stmt, $upd_data, true, true);
Cronjob::inserttask(TaskId::REBUILD_VHOST);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] php setting with description '" . $description . "' has been updated by '" . $this->getUserDetail('loginname') . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] php setting with description '" . $description . "' has been updated by '" . $this->getUserDetail('loginname') . "'");
$result = $this->apiCall('PhpSettings.get', [
'id' => $id
@@ -686,7 +686,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
], true, true);
Cronjob::inserttask(TaskId::REBUILD_VHOST);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] php setting '" . $result['description'] . "' has been deleted by '" . $this->getUserDetail('loginname') . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] php setting '" . $result['description'] . "' has been deleted by '" . $this->getUserDetail('loginname') . "'");
return $this->response($result);
}
throw new Exception("Not allowed to execute given command.", 403);

View File

@@ -62,7 +62,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
* optional, overwrites path value with an URL to generate a redirect, alternatively use the path
* parameter also for URLs
* @param int $openbasedir_path
* optional, either 0 for domains-docroot, 1 for customers-homedir or 2 for parent-directory of domains-docroot
* optional, either 0 for domains-docroot [default], 1 for customers-homedir or 2 for parent-directory of domains-docroot
* @param int $phpsettingid
* optional, php-settings-id, if empty the $domain value is used
* @param int $redirectcode
@@ -104,7 +104,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
$aliasdomain = $this->getParam('alias', true, 0);
$path = $this->getParam('path', true, '');
$url = $this->getParam('url', true, '');
$openbasedir_path = $this->getParam('openbasedir_path', true, 1);
$openbasedir_path = $this->getParam('openbasedir_path', true, 0);
$phpsettingid = $this->getParam('phpsettingid', true, 0);
$redirectcode = $this->getParam('redirectcode', true, Settings::Get('customredirect.default'));
$isemaildomain = $this->getParam('isemaildomain', true, 0);
@@ -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);
}
@@ -486,7 +486,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
$result['ipsandports'] = $this->getIpsForDomain($result['id']);
}
$result['domain_hascert'] = $this->getHasCertValueForDomain((int)$result['id'], (int)$result['parentdomainid']);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get subdomain '" . $result['domain'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get subdomain '" . $result['domain'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "domainname '" . $domainname . "'");
@@ -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);
}
@@ -856,7 +856,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
Cronjob::inserttask(TaskId::REBUILD_VHOST);
Cronjob::inserttask(TaskId::REBUILD_DNS);
$idna_convert = new IdnaWrapper();
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] edited domain '" . $idna_convert->decode($result['domain']) . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] edited domain '" . $idna_convert->decode($result['domain']) . "'");
}
$result = $this->apiCall('SubDomains.get', [
'id' => $id
@@ -1133,7 +1133,9 @@ class SubDomains extends ApiCommand implements ResourceEntity
}
}
if ((int)$result['aliasdomain'] !== 0) {
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger());
}
// delete domain from table
$stmt = Database::prepare("

View File

@@ -92,7 +92,7 @@ class SysLog extends ApiCommand implements ResourceEntity
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 log-entries");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list log-entries");
return $this->response([
'count' => count($result),
'list' => $result

View File

@@ -166,7 +166,7 @@ class Traffic extends ApiCommand implements ResourceEntity
$row['mail'] *= 1024;
$result[] = $row;
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list traffic");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list traffic");
return $this->response([
'count' => count($result),
'list' => $result

View File

@@ -37,7 +37,7 @@ use Symfony\Component\Console\Output\OutputInterface;
class CliCommand extends Command
{
protected function validateRequirements(InputInterface $input, OutputInterface $output): int
protected function validateRequirements(InputInterface $input, OutputInterface $output, bool $ignore_has_updates = false): int
{
if (!file_exists(Froxlor::getInstallDir() . '/lib/userdata.inc.php')) {
$output->writeln("<error>Could not find froxlor's userdata.inc.php file. You should use this script only with an installed froxlor system.</>");
@@ -51,7 +51,7 @@ class CliCommand extends Command
$output->writeln("<error>" . $e->getMessage() . "</>");
return self::INVALID;
}
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
if (!$ignore_has_updates && (Froxlor::hasUpdates() || Froxlor::hasDbUpdates())) {
if ((int)Settings::Get('system.cron_allowautoupdate') == 1) {
return $this->runUpdate($output);
} else {
@@ -122,7 +122,7 @@ class CliCommand extends Command
include_once Froxlor::getInstallDir() . '/lib/tables.inc.php';
define('_CRON_UPDATE', 1);
ob_start([
'this',
$this,
'cleanUpdateOutput'
]);
include_once Froxlor::getInstallDir() . '/install/updatesql.php';

View File

@@ -80,7 +80,7 @@ final class InstallCommand extends Command
$_SERVER['SERVER_NAME'] = $host[0] ?? '';
$ips = [];
exec('hostname -I', $ips);
$ips = explode(" ", $ips[0]);
$ips = explode(" ", $ips[0] ?? "");
// ipv4 address?
$_SERVER['SERVER_ADDR'] = filter_var($ips[0] ?? "", FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? ($ips[0] ?? '') : '';
if (empty($_SERVER['SERVER_ADDR'])) {
@@ -246,8 +246,11 @@ final class InstallCommand extends Command
}
} catch (Exception $e) {
$this->io->error($e->getMessage());
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
$core = new Core($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

@@ -62,6 +62,11 @@ final class MasterCron extends CliCommand
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
if ($result != self::SUCCESS) {
// requirements failed, exit
return $result;
}
$jobs = $input->getArgument('job');
// handle force option
@@ -111,8 +116,8 @@ final class MasterCron extends CliCommand
]);
$this->cronLog->setCronDebugFlag(defined('CRON_DEBUG_FLAG'));
// check whether there are actual tasks to perform by 'tasks'-cron so
// we dont regenerate files unnecessarily
// check whether there are actual tasks to perform by 'tasks'-cron, so
// we don't regenerate files unnecessarily
$tasks_cnt_stmt = Database::query("SELECT COUNT(*) as jobcnt FROM `panel_tasks`");
$tasks_cnt = $tasks_cnt_stmt->fetch(PDO::FETCH_ASSOC);

View File

@@ -0,0 +1,165 @@
<?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\Cli;
use Froxlor\Cron\TaskId;
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\Settings;
use Froxlor\System\Cronjob;
use PDO;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Style\SymfonyStyle;
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->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for confirmation, update files if necessary');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output, true);
$io = new SymfonyStyle($input, $output);
if ((int) Settings::Get('system.leenabled') == 0) {
$io->info("Let's Encrypt not activated in froxlor settings.");
$result = self::INVALID;
}
if ($result == self::SUCCESS) {
$yestoall = $input->getOption('yes-to-all') !== false;
$helper = $this->getHelper('question');
$count_changes = 0;
// get all Let's Encrypt enabled domains
$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 `validtodate`=NULL WHERE `domainid` = :did");
$acmesh_dir = dirname(Settings::Get('system.acmeshpath'));
$acmesh_challenge_dir = rtrim(FileDir::makeCorrectDir(Settings::Get('system.letsencryptchallengepath')), "/");
$recommended = rtrim(FileDir::makeCorrectDir(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: '" . $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', $recommended);
$former_value = $acmesh_challenge_dir;
$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 . "@";
FileDir::safe_exec('sed -i -e "' . $sed_params . '" ' . escapeshellarg($acme_alias_file));
$count_changes++;
}
}
foreach ($domains as $domain_arr) {
$domain = $domain_arr['domain'];
$acme_domain_conf = FileDir::makeCorrectFile($acmesh_dir . '/' . $domain . '/' . $domain . '.conf');
if (file_exists($acme_domain_conf)) {
$io->text("Getting info from " . $acme_domain_conf);
$conf_content = file_get_contents($acme_domain_conf);
} else {
$acme_domain_conf = FileDir::makeCorrectFile($acmesh_dir . '/' . $domain . '_ecc/' . $domain . '.conf');
if (file_exists($acme_domain_conf)) {
$io->text("Getting info from " . $acme_domain_conf);
$conf_content = file_get_contents($acme_domain_conf);
} else {
$io->info("No domain configuration file found in '" . $acmesh_dir . "'");
continue;
}
}
if (!empty($conf_content)) {
$lines = explode("\n", $conf_content);
foreach ($lines as $line) {
$val_key = explode("=", $line);
if ($val_key[0] == 'Le_Webroot') {
$domain_webroot = trim(trim($val_key[1], "'"), '"');
if ($domain_webroot != $acmesh_challenge_dir) {
$io->warning("Domain '" . $domain . "' has old/wrong Le_Webroot setting: '" . $domain_webroot . ' <> ' . $acmesh_challenge_dir . "'");
$question = new ConfirmationQuestion('Fix Le_Webroot? [yes] ', true, '/^(y|j)/i');
if ($yestoall || $helper->ask($input, $output, $question)) {
$sed_params = "s@Le_Webroot=.*@Le_Webroot='" . $acmesh_challenge_dir . "'@";
FileDir::safe_exec('sed -i -e "' . $sed_params . '" ' . escapeshellarg($acme_domain_conf));
Database::pexecute($upd_stmt, ['did' => $domain_arr['id']]);
$io->success("Correction of Le_Webroot successful");
$count_changes++;
} else {
continue;
}
} else {
$io->info("Domain '" . $domain . "' Le_Webroot value is correct");
}
break;
} else {
continue;
}
}
}
}
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');
if ($yestoall || $helper->ask($input, $output, $question)) {
passthru(FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . ' froxlor:cron -f -d');
}
}
} else {
$io->success("No changes necessary.");
}
}
return $result;
}
}

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,6 +841,8 @@ class Apache extends HttpConfigBase
$domain['documentroot'] = trim($domain['documentroot']);
if (preg_match('/^https?\:\/\//', $domain['documentroot'])) {
$possible_deactivated_webroot = $this->getWebroot($domain);
if ($this->deactivated == false) {
$corrected_docroot = $domain['documentroot'];
// Get domain's redirect code
@@ -863,6 +866,9 @@ class Apache extends HttpConfigBase
$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;
}
} 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

@@ -126,6 +126,9 @@ class ApacheFcgi extends Apache
// mod_proxy stuff for apache-2.4
if (Settings::Get('system.apache24') == '1' && Settings::Get('phpfpm.use_mod_proxy') == '1') {
$php_options_text .= ' <Directory "' . FileDir::makeCorrectDir($domain['documentroot']) . '">' . "\n";
$filesmatch = $phpconfig['fpm_settings']['limit_extensions'];
$extensions = explode(" ", $filesmatch);
$filesmatch = "";
@@ -141,23 +144,19 @@ class ApacheFcgi extends Apache
$php_options_text .= ' </FilesMatch>' . "\n";
$mypath_dir = new Directory($domain['documentroot']);
// only create the require all granted if there is not active directory-protection
// only create the "require all granted" directive if there is no active directory-protection
// for this path, as this would be the first require and therefore grant all access
if ($mypath_dir->isUserProtected() == false) {
$php_options_text .= ' <Directory "' . FileDir::makeCorrectDir($domain['documentroot']) . '">' . "\n";
if ($phpconfig['pass_authorizationheader'] == '1') {
$php_options_text .= ' CGIPassAuth On' . "\n";
}
$php_options_text .= ' Require all granted' . "\n";
$php_options_text .= ' AllowOverride All' . "\n";
$php_options_text .= ' </Directory>' . "\n";
} elseif ($phpconfig['pass_authorizationheader'] == '1') {
// allow Pass of Authorization header
$php_options_text .= ' <Directory "' . FileDir::makeCorrectDir($domain['documentroot']) . '">' . "\n";
$php_options_text .= ' CGIPassAuth On' . "\n";
$php_options_text .= ' </Directory>' . "\n";
}
$php_options_text .= ' </Directory>' . "\n";
} else {
$addheader = "";
if ($phpconfig['pass_authorizationheader'] == '1') {
@@ -196,6 +195,9 @@ class ApacheFcgi extends Apache
}
} else {
$php_options_text .= ' FcgidIdleTimeout ' . Settings::Get('system.mod_fcgid_idle_timeout') . "\n";
if ($phpconfig['pass_authorizationheader'] == '1') {
$php_options_text .= ' FcgidPassHeader Authorization' . "\n";
}
if ((int)Settings::Get('system.mod_fcgid_wrapper') == 0) {
$php_options_text .= ' SuexecUserGroup "' . $domain['loginname'] . '" "' . $domain['loginname'] . '"' . "\n";
$php_options_text .= ' ScriptAlias /php/ ' . $php->getInterface()->getConfigDir() . "\n";

View File

@@ -179,7 +179,7 @@ class HttpConfigBase
$froxlor_ssl_settings_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`
WHERE `domainid` = '0' AND
(`expirationdate` < DATE_ADD(NOW(), INTERVAL 30 DAY) OR `expirationdate` IS NULL)
(`validtodate` < DATE_ADD(NOW(), INTERVAL 30 DAY) OR `validtodate` IS NULL)
");
$froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt);
if ($froxlor_ssl && !empty($froxlor_ssl['ssl_cert_file'])) {

View File

@@ -114,7 +114,9 @@ class AcmeSh extends FroxlorCron
`ssl_cert_chainfile` = :chain,
`ssl_csr_file` = :csr,
`ssl_fullchain_file` = :fullchain,
`expirationdate` = :expirationdate
`validfromdate` = :validfromdate,
`validtodate` = :validtodate,
`issuer` = :issuer
");
// prepare domain update sql
@@ -136,7 +138,9 @@ class AcmeSh extends FroxlorCron
'lepublickey' => Settings::Get('system.lepublickey'),
'leregistered' => Settings::Get('system.leregistered'),
'ssl_redirect' => Settings::Get('system.le_froxlor_redirect'),
'expirationdate' => null,
'validfromdate' => null,
'validtodate' => null,
'issuer' => "",
'ssl_cert_file' => null,
'ssl_key_file' => null,
'ssl_ca_file' => null,
@@ -171,7 +175,9 @@ class AcmeSh extends FroxlorCron
'lepublickey' => Settings::Get('system.lepublickey'),
'leregistered' => Settings::Get('system.leregistered'),
'ssl_redirect' => Settings::Get('system.le_froxlor_redirect'),
'expirationdate' => is_array($renew_froxlor) ? $renew_froxlor['expirationdate'] : date('Y-m-d H:i:s', 0),
'validfromdate' => is_array($renew_froxlor) ? $renew_froxlor['validfromdate'] : date('Y-m-d H:i:s', 0),
'validtodate' => is_array($renew_froxlor) ? $renew_froxlor['validtodate'] : date('Y-m-d H:i:s', 0),
'issuer' => is_array($renew_froxlor) ? $renew_froxlor['issuer'] : "",
'ssl_cert_file' => is_array($renew_froxlor) ? $renew_froxlor['ssl_cert_file'] : null,
'ssl_key_file' => is_array($renew_froxlor) ? $renew_froxlor['ssl_key_file'] : null,
'ssl_ca_file' => is_array($renew_froxlor) ? $renew_froxlor['ssl_ca_file'] : null,
@@ -187,7 +193,7 @@ class AcmeSh extends FroxlorCron
'loginname' => $domain['loginname'],
'adminsession' => 0
]);
if (defined('CRON_IS_FORCED') || self::checkFsFilesAreNewer($domain['domain'], $domain['expirationdate'])) {
if (defined('CRON_IS_FORCED') || self::checkFsFilesAreNewer($domain['domain'], $domain['validtodate'])) {
self::certToDb($domain, $cronlog, []);
$changedetected = 1;
}
@@ -221,7 +227,9 @@ class AcmeSh extends FroxlorCron
");
$froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt);
// also check for possible existing certificate
if (!$froxlor_ssl && !self::checkFsFilesAreNewer(Settings::Get('system.hostname'), date('Y-m-d H:i:s'))) {
if (($froxlor_ssl && empty($froxlor_ssl['validtodate']))
|| (!$froxlor_ssl && !self::checkFsFilesAreNewer(Settings::Get('system.hostname'), date('Y-m-d H:i:s')))
) {
return true;
}
}
@@ -279,7 +287,9 @@ EOC;
SELECT
domssl.`id`,
domssl.`domainid`,
domssl.`expirationdate`,
domssl.`validfromdate`,
domssl.`validtodate`,
domssl.`issuer`,
domssl.`ssl_cert_file`,
domssl.`ssl_key_file`,
domssl.`ssl_ca_file`,
@@ -306,7 +316,7 @@ EOC;
AND dom.`letsencrypt` = 1
AND dom.`aliasdomain` IS NULL
AND dom.`iswildcarddomain` = 0
AND domssl.`expirationdate` IS NULL
AND domssl.`validtodate` IS NULL
");
$customer_ssl = $certificates_stmt->fetchAll(PDO::FETCH_ASSOC);
if ($customer_ssl) {
@@ -330,7 +340,7 @@ EOC;
");
$froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt);
// also check for possible existing certificate
if ($froxlor_ssl && self::checkFsFilesAreNewer(Settings::Get('system.hostname'), $froxlor_ssl['expirationdate'])) {
if ($froxlor_ssl && self::checkFsFilesAreNewer(Settings::Get('system.hostname'), $froxlor_ssl['validtodate'])) {
return $froxlor_ssl;
}
}
@@ -346,7 +356,9 @@ EOC;
SELECT
domssl.`id`,
domssl.`domainid`,
domssl.`expirationdate`,
domssl.`validfromdate`,
domssl.`validtodate`,
domssl.`issuer`,
domssl.`ssl_cert_file`,
domssl.`ssl_key_file`,
dom.`domain`,
@@ -370,7 +382,7 @@ EOC;
if ($renew_certs) {
if ($check) {
foreach ($renew_certs as $cert) {
if (self::checkFsFilesAreNewer($cert['domain'], $cert['expirationdate'])) {
if (self::checkFsFilesAreNewer($cert['domain'], $cert['validtodate'])) {
return true;
}
}
@@ -453,7 +465,7 @@ EOC;
// Only issue let's encrypt certificate if no broken ssl_redirect is enabled
if ($certrow['ssl_redirect'] != 2) {
$do_force = false;
if (!empty($certrow['ssl_cert_file']) && empty($certrow['expirationdate'])) {
if (!empty($certrow['ssl_cert_file']) && empty($certrow['validtodate'])) {
// domain changed (SAN or similar)
$do_force = true;
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Re-creating certificate for " . $certrow['domain']);
@@ -521,7 +533,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 +569,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) {
@@ -594,7 +606,9 @@ EOC;
'chain' => $return['chain'],
'csr' => $return['csr'],
'fullchain' => $return['fullchain'],
'expirationdate' => date('Y-m-d H:i:s', $newcert['validTo_time_t'])
'validfromdate' => date('Y-m-d H:i:s', $newcert['validFrom_time_t']),
'validtodate' => date('Y-m-d H:i:s', $newcert['validTo_time_t']),
'issuer' => $newcert['issuer']['O'] ?? ""
]);
if ($certrow['ssl_redirect'] == 3) {

View File

@@ -414,6 +414,8 @@ class Lighttpd extends HttpConfigBase
$domain['documentroot'] = trim($domain['documentroot']);
if (preg_match('/^https?\:\/\//', $domain['documentroot'])) {
$possible_deactivated_webroot = $this->getWebroot($domain);
if ($this->deactivated == false) {
$uri = $domain['documentroot'];
// Get domain's redirect code
@@ -423,6 +425,9 @@ class Lighttpd extends HttpConfigBase
$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

@@ -225,7 +225,7 @@ class Nginx extends HttpConfigBase
$this->nginx_data[$vhost_filename] .= "\t" . '}' . "\n";
// protect bin/
$this->nginx_data[$vhost_filename] .= "\t" . 'location ~ ' . rtrim($relpath, "/") . '/(bin|cache|logs|tests|vendor) {' . "\n";
$this->nginx_data[$vhost_filename] .= "\t" . 'location ~ ^' . rtrim($relpath, "/") . '/(bin|cache|logs|tests|vendor) {' . "\n";
$this->nginx_data[$vhost_filename] .= "\t" . ' deny all;' . "\n";
$this->nginx_data[$vhost_filename] .= "\t" . '}' . "\n";
}
@@ -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,6 +596,8 @@ class Nginx extends HttpConfigBase
// if the documentroot is an URL we just redirect
if (preg_match('/^https?\:\/\//', $domain['documentroot'])) {
$possible_deactivated_webroot = $this->getWebroot($domain);
if ($this->deactivated == false) {
$uri = $domain['documentroot'];
if (substr($uri, -1) == '/') {
$uri = substr($uri, 0, -1);
@@ -607,11 +609,14 @@ class Nginx extends HttpConfigBase
$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;
}
} 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 {
@@ -1035,6 +1040,11 @@ class Nginx extends HttpConfigBase
$path_options .= "\t\t" . 'auth_basic_user_file ' . FileDir::makeCorrectFile($single['usrf']) . ';' . "\n";
if ($domain['phpenabled_customer'] == 1 && $domain['phpenabled_vhost'] == '1') {
$path_options .= "\t\t" . 'index index.php index.html index.htm;' . "\n";
if ($domain['notryfiles'] != 1) {
$path_options .= "\t\t" . 'location ~ ^(.+?\.php)(/.*)?$ {' . "\n";
$path_options .= "\t\t\t" . 'try_files ' . $domain['nonexistinguri'] . ' @php;' . "\n";
$path_options .= "\t\t" . '}' . "\n\n";
}
} else {
$path_options .= "\t\t" . 'index index.html index.htm;' . "\n";
}
@@ -1120,6 +1130,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']);
}
@@ -1157,7 +1171,6 @@ class Nginx extends HttpConfigBase
$phpopts .= "\t\tinclude " . Settings::Get('nginx.fastcgiparams') . ";\n";
$phpopts .= "\t\tfastcgi_param SCRIPT_FILENAME \$request_filename;\n";
$phpopts .= "\t\tfastcgi_param PATH_INFO \$fastcgi_path_info;\n";
$phpopts .= "\t\ttry_files \$fastcgi_script_name =404;\n";
$phpopts .= "\t\tfastcgi_pass " . Settings::Get('system.nginx_php_backend') . ";\n";
$phpopts .= "\t\tfastcgi_index index.php;\n";
if ($domain['ssl'] == '1' && $ssl_vhost) {

View File

@@ -342,8 +342,17 @@ pm.max_children = 1
public function getSocketFile($createifnotexists = true)
{
$socketdir = FileDir::makeCorrectDir(Settings::Get('phpfpm.fastcgi_ipcdir'));
// add fpm-config-id to filename so it's unique for the fpm-daemon and doesn't interfere with running configs when reuilding
$socket = strtolower(FileDir::makeCorrectFile($socketdir . '/' . $this->domain['fpm_config_id'] . '-' . $this->domain['loginname'] . '-' . $this->domain['domain'] . '-php-fpm.socket'));
// add fpm-config-id to filename, so it's unique for the fpm-daemon and doesn't interfere with running configs when reuilding
$socket_filename = $socketdir . '/' . $this->domain['fpm_config_id'] . '-' . $this->domain['loginname'] . '-' . $this->domain['domain'] . '-php-fpm.socket';
if (strlen($socket_filename) > 100) {
// respect the unix socket-length limitation
$socket_filename = $socketdir . '/' . $this->domain['fpm_config_id'] . '-' . $this->domain['loginname'] . '-' . $this->domain['id'] . '-php-fpm.socket';
if (strlen($socket_filename) > 100) {
// even a long loginname it seems
$socket_filename = $socketdir . '/' . $this->domain['fpm_config_id'] . '-' . $this->domain['guid'] . '-' . $this->domain['id'] . '-php-fpm.socket';
}
}
$socket = strtolower(FileDir::makeCorrectFile($socket_filename));
if (!is_dir($socketdir) && $createifnotexists) {
FileDir::safe_exec('mkdir -p ' . escapeshellarg($socketdir));

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);
$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']."\n";
$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);
$has_dbs = false;
while ($row = $sel_stmt->fetch()) {
}
$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, [
'>'
]);
$has_dbs = true;
$current_dbserver = $row['dbserver'];
}
if ($has_dbs) {

View File

@@ -33,8 +33,6 @@ namespace Froxlor\Cron\Traffic;
use Exception;
use Froxlor\Cron\FroxlorCron;
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\FroxlorLogger;
use Froxlor\PhpHelper;
use Froxlor\Settings;
@@ -99,8 +97,8 @@ class ReportsCron extends FroxlorCron
'COMPANY' => $rep_userinfo['company'],
'USERNAME' => $rep_userinfo['loginname'],
'CUSTOMER_NO' => $rep_userinfo['customernumber'],
'TRAFFIC' => PhpHelper::sizeReadable($row['traffic'], null, 'bi'),
'TRAFFICUSED' => PhpHelper::sizeReadable($row['traffic_used'], null, 'bi'),
'TRAFFIC' => PhpHelper::sizeReadable((int)$row['traffic'], null, 'bi'),
'TRAFFICUSED' => PhpHelper::sizeReadable((int)$row['traffic_used'], null, 'bi'),
'USAGE_PERCENT' => round(($row['traffic_used'] * 100) / $row['traffic'], 2),
'MAX_PERCENT' => Settings::Get('system.report_trafficmax')
];
@@ -182,8 +180,8 @@ class ReportsCron extends FroxlorCron
if (isset($row['traffic']) && $row['traffic'] > 0 && (($row['traffic_used_total'] * 100) / ($row['traffic'])) >= (int)Settings::Get('system.report_trafficmax')) {
$replace_arr = [
'NAME' => $row['name'],
'TRAFFIC' => PhpHelper::sizeReadable($row['traffic'], null, 'bi'),
'TRAFFICUSED' => PhpHelper::sizeReadable($row['traffic_used_total'], null, 'bi'),
'TRAFFIC' => PhpHelper::sizeReadable((int)$row['traffic'], null, 'bi'),
'TRAFFICUSED' => PhpHelper::sizeReadable((int)$row['traffic_used_total'], null, 'bi'),
'USAGE_PERCENT' => round(($row['traffic_used_total'] * 100) / $row['traffic'], 2),
'MAX_PERCENT' => Settings::Get('system.report_trafficmax')
];
@@ -265,10 +263,10 @@ class ReportsCron extends FroxlorCron
while ($customer = $customers_stmt->fetch(PDO::FETCH_ASSOC)) {
$customer['traffic'] *= 1024;
$t = $customer['traffic_used_total'] * 1024;
$t = (int) $customer['traffic_used_total'] * 1024;
if ($customer['traffic'] > 0) {
$p = (($t * 100) / $customer['traffic']);
$tg = $customer['traffic'];
$tg = (int) $customer['traffic'];
$str = sprintf('%s ( %00.1f %% )', PhpHelper::sizeReadable($t, null, 'bi'), $p);
$mail_body .= sprintf('%-15s', $customer['loginname']) . ' ' . sprintf('%-25s', $str) . ' ' . sprintf('%s', PhpHelper::sizeReadable($tg, null, 'bi')) . "\n";
} elseif ($customer['traffic'] == 0) {
@@ -282,10 +280,10 @@ class ReportsCron extends FroxlorCron
$mail_body .= '---------------------------------------------------------------' . "\n";
$t = $row['traffic_used_total'];
$t = (int) $row['traffic_used_total'];
if ($row['traffic'] > 0) {
$p = (($t * 100) / $row['traffic']);
$tg = $row['traffic'];
$tg = (int) $row['traffic'];
$str = sprintf('%s ( %00.1f %% )', PhpHelper::sizeReadable($t, null, 'bi'), $p);
$mail_body .= sprintf('%-15s', $row['loginname']) . ' ' . sprintf('%-25s', $str) . ' ' . sprintf('%s', PhpHelper::sizeReadable($tg, null, 'bi')) . "\n";
} elseif ($row['traffic'] == 0) {
@@ -369,8 +367,8 @@ class ReportsCron extends FroxlorCron
'COMPANY' => $rep_userinfo['company'],
'USERNAME' => $rep_userinfo['loginname'],
'CUSTOMER_NO' => $rep_userinfo['customernumber'],
'DISKAVAILABLE' => PhpHelper::sizeReadable($row['diskspace'], null, 'bi'),
'DISKUSED' => PhpHelper::sizeReadable($row['diskspace_used'], null, 'bi'),
'DISKAVAILABLE' => PhpHelper::sizeReadable((int)$row['diskspace'], null, 'bi'),
'DISKUSED' => PhpHelper::sizeReadable((int)$row['diskspace_used'], null, 'bi'),
'USAGE_PERCENT' => round(($row['diskspace_used'] * 100) / $row['diskspace'], 2),
'MAX_PERCENT' => Settings::Get('system.report_webmax')
];
@@ -443,8 +441,8 @@ class ReportsCron extends FroxlorCron
if (isset($row['diskspace']) && $row['diskspace_used'] != null && $row['diskspace_used'] > 0 && (($row['diskspace_used'] * 100) / $row['diskspace']) >= (int)Settings::Get('system.report_webmax')) {
$replace_arr = [
'NAME' => $row['name'],
'DISKAVAILABLE' => PhpHelper::sizeReadable($row['diskspace'], null, 'bi'),
'DISKUSED' => PhpHelper::sizeReadable($row['diskspace_used'], null, 'bi'),
'DISKAVAILABLE' => PhpHelper::sizeReadable((int)$row['diskspace'], null, 'bi'),
'DISKUSED' => PhpHelper::sizeReadable((int)$row['diskspace_used'], null, 'bi'),
'USAGE_PERCENT' => ($row['diskspace_used'] * 100) / $row['diskspace'],
'MAX_PERCENT' => Settings::Get('system.report_webmax')
];

View File

@@ -406,7 +406,7 @@ class TrafficCron extends FroxlorCron
} else {
// Use the old fashioned way with "du"
if (file_exists($row['documentroot']) && is_dir($row['documentroot'])) {
$back = FileDir::safe_exec('du -sk ' . escapeshellarg($row['documentroot']) . '');
$back = FileDir::safe_exec('du -sk ' . escapeshellarg($row['documentroot']));
foreach ($back as $backrow) {
$webspaceusage = explode(' ', $backrow);
}
@@ -426,7 +426,7 @@ class TrafficCron extends FroxlorCron
$maildir = FileDir::makeCorrectDir(Settings::Get('system.vmail_homedir') . $row['loginname']);
if (file_exists($maildir) && is_dir($maildir)) {
$back = FileDir::safe_exec('du -sk ' . escapeshellarg($maildir) . '');
$back = FileDir::safe_exec('du -sk ' . escapeshellarg($maildir));
foreach ($back as $backrow) {
$emailusage = explode(' ', $backrow);
}

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,7 +151,8 @@ 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']));

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 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 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,15 @@ 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'] ?? 0, $entry['ttl']);
}
// add missing required entries
@@ -275,7 +310,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 +384,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)) {
} 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));
}
}
}
}
@@ -416,7 +471,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 +499,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 +519,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 +553,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 +571,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 +599,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,18 +37,18 @@ class PowerDNS
/**
* remove all records and entries of a given domain
*
* @param array $domain
* @param string|null $domain
*/
public static function cleanDomainZone($domain = null)
public static function cleanDomainZone(string $domain = null)
{
if (is_array($domain) && isset($domain['domain'])) {
if (!empty($domain)) {
$pdns_domains_stmt = self::getDB()->prepare("SELECT `id`, `name` FROM `domains` WHERE `name` = :domain");
$del_rec_stmt = self::getDB()->prepare("DELETE FROM `records` WHERE `domain_id` = :did");
$del_meta_stmt = self::getDB()->prepare("DELETE FROM `domainmetadata` WHERE `domain_id` = :did");
$del_dom_stmt = self::getDB()->prepare("DELETE FROM `domains` WHERE `id` = :did");
$pdns_domains_stmt->execute([
'domain' => $domain['domain']
'domain' => $domain
]);
$pdns_domain = $pdns_domains_stmt->fetch(PDO::FETCH_ASSOC);
@@ -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,21 +326,31 @@ 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
`expirationdate` = null
`validtodate` = null
WHERE
domainid = :domainid
");
@@ -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) {
@@ -460,7 +447,8 @@ class FileDir
$field = [
'type' => 'select',
'select_var' => $_field,
'selected' => $value
'selected' => $value,
'value' => $value
];
} else {
$field = [
@@ -480,16 +468,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 +485,8 @@ class FileDir
// Will exclude everything under these directories
$exclude = [
'awstats',
'webalizer'
'webalizer',
'goaccess'
];
/**
@@ -565,7 +552,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 +572,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;

View File

@@ -31,10 +31,10 @@ final class Froxlor
{
// Main version variable
const VERSION = '2.0.7';
const VERSION = '2.0.21';
// Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '202212060';
const DBVERSION = '202304260';
// 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,12 +307,14 @@ final class Froxlor
$arr[] = '2'; // dev < beta < rc
// number of rc
$arr[] = substr($x[1], 2);
} else if (stripos($x[1], 'beta') !== false) {
} 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) {
} else {
if (stripos($x[1], 'dev') !== false) {
$arr[] = '-1';
$arr[] = '0'; // dev < beta < rc
// number of dev
@@ -320,3 +323,5 @@ final class Froxlor
}
}
}
}
}

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

@@ -0,0 +1,55 @@
<?php
namespace Froxlor\Http;
use Froxlor\Froxlor;
use Froxlor\Settings;
use Froxlor\UI\Panel\UI;
class RateLimiter
{
private static int $limit_per_interval = 60;
private static int $reset_time = 0;
public static function run(bool $install_mode = false)
{
// default interval = 60 sec
self::$reset_time = time() + 60;
if (!$install_mode) {
self::$limit_per_interval = Settings::Get('system.req_limit_per_interval') ?? 60;
self::$reset_time = time() + Settings::Get('system.req_limit_interval') ?? 60;
}
// Get the remaining requests and reset time from the headers
$remaining = isset($_SESSION['HTTP_X_RATELIMIT_REMAINING']) ? (int)$_SESSION['HTTP_X_RATELIMIT_REMAINING'] : self::$limit_per_interval;
$reset = isset($_SESSION['HTTP_X_RATELIMIT_RESET']) ? (int)$_SESSION['HTTP_X_RATELIMIT_RESET'] : self::$reset_time;
// check if reset time is due
if (time() > $reset) {
$remaining = self::$limit_per_interval;
$reset = self::$reset_time;
}
// If we've hit the limit, return an error
if ($remaining <= 0) {
header('HTTP/1.1 429 Too Many Requests');
header("Retry-After: $reset");
UI::twig()->addGlobal('install_mode', '1');
echo UI::twig()->render('Froxlor/misc/ratelimithint.html.twig', [
'retry' => $reset,
'installdir' => Froxlor::getInstallDir()
]);
die();
}
// Decrement the remaining requests and update the headers
$remaining--;
$_SESSION['HTTP_X_RATELIMIT_REMAINING'] = $remaining;
$_SESSION['HTTP_X_RATELIMIT_RESET'] = $reset;
header("X-RateLimit-Limit: " . self::$limit_per_interval);
header("X-RateLimit-Remaining: " . $remaining);
header("X-RateLimit-Reset: " . $reset);
}
}

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

@@ -42,7 +42,7 @@ class Install
public $phpVersion;
public $formfield;
public string $requiredVersion = '7.4.0';
public array $requiredExtensions = ['session', 'ctype', 'xml', 'filter', 'posix', 'mbstring', 'curl', 'gmp', 'json'];
public array $requiredExtensions = ['session', 'ctype', 'xml', 'filter', 'posix', 'mbstring', 'curl', 'gmp', 'json', 'gd'];
public array $suggestedExtensions = ['bcmath', 'zip'];
public array $suggestions = [];
public array $criticals = [];

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