Compare commits

...

149 Commits

Author SHA1 Message Date
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
Michael Kaufmann
80e442e396 set version to 2.0.7
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-10 22:15:57 +01:00
Michael Kaufmann
489ad375bd ensure latest userdata.inc.php layout for updaters/users of old format
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-10 16:54:20 +01:00
Michael Kaufmann
c420196e73 check explicitly for template existence and try to use default theme as fallback; fixes #1071
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-10 16:53:36 +01:00
Michael Kaufmann
cc6d8d5f8b fix login if non-standard ports are used for froxlor vhost
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-10 12:43:04 +01:00
Michael Kaufmann
24f47bc58b set version to 2.0.6
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-09 10:09:15 +01:00
Michael Kaufmann
c769c074e0 add Google CA to available acme.sh providers; fixes #1065
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-09 10:00:08 +01:00
dependabot[bot]
2ecb8eb034 Bump json5 from 1.0.1 to 1.0.2 (#1069)
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-09 09:51:52 +01:00
Michael Kaufmann
6827c100c3 fix updating email account password-hashes in updater
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-09 09:50:51 +01:00
Michael Kaufmann
c402acd1bd disable correct mod_php in bionic-config-templates when fcgid/php-fpm is selected
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-09 09:25:29 +01:00
Michael Kaufmann
c4ec2509fa fix resetting of isemaildomain-flag of subdomains when nothing changed; fixes #1067
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-09 09:24:22 +01:00
Michael Kaufmann
0f382586ce set version to 2.0.5
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-08 23:24:43 +01:00
Michael Kaufmann
9c2f12ecb1 mysql-remote-server fixes
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-08 23:20:31 +01:00
170 changed files with 6676 additions and 9326 deletions

View File

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

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@ install/update.log
install/*.json install/*.json
lib/userdata.inc.php lib/userdata.inc.php
lib/userdata.inc.php.bak lib/userdata.inc.php.bak
lib/config.inc.php
logs/* logs/*
!logs/index.html !logs/index.html
.buildpath .buildpath

View File

@@ -57,7 +57,7 @@ May be found in [COPYING](COPYING)
### Tarball ### 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) 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) [HowTo](https://docs.froxlor.org/latest/general/installation/apt-package.html)

View File

@@ -269,7 +269,8 @@ return [
'traffic' => lng('menue.traffic.traffic'), 'traffic' => lng('menue.traffic.traffic'),
'traffic.http' => lng('menue.traffic.traffic') . " / HTTP", 'traffic.http' => lng('menue.traffic.traffic') . " / HTTP",
'traffic.ftp' => lng('menue.traffic.traffic') . " / FTP", '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', 'save_method' => 'storeSettingField',
'advanced_mode' => true 'advanced_mode' => true

View File

@@ -138,6 +138,26 @@ return [
'save_method' => 'storeSettingField', 'save_method' => 'storeSettingField',
'advanced_mode' => true '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' => [ 'customer_accountprefix' => [
'label' => lng('serversettings.accountprefix'), 'label' => lng('serversettings.accountprefix'),
'settinggroup' => 'customer', 'settinggroup' => 'customer',

View File

@@ -109,7 +109,19 @@ return [
'default' => false, 'default' => false,
'save_method' => 'storeSettingField' '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'), 'label' => lng('serversettings.update_channel'),
'settinggroup' => 'system', 'settinggroup' => 'system',
'varname' => 'update_channel', 'varname' => 'update_channel',
@@ -122,7 +134,7 @@ return [
'save_method' => 'storeSettingField', 'save_method' => 'storeSettingField',
'advanced_mode' => true 'advanced_mode' => true
], ],
'system_validatedomain' => [ 'system_validate_domain' => [
'label' => lng('serversettings.validate_domain'), 'label' => lng('serversettings.validate_domain'),
'settinggroup' => 'system', 'settinggroup' => 'system',
'varname' => 'validate_domain', 'varname' => 'validate_domain',
@@ -307,7 +319,7 @@ return [
'save_method' => 'storeSettingField', 'save_method' => 'storeSettingField',
'advanced_mode' => true 'advanced_mode' => true
], ],
'hide_incompatible_settings' => [ 'system_hide_incompatible_settings' => [
'label' => lng('serversettings.hide_incompatible_settings'), 'label' => lng('serversettings.hide_incompatible_settings'),
'settinggroup' => 'system', 'settinggroup' => 'system',
'varname' => 'hide_incompatible_settings', 'varname' => 'hide_incompatible_settings',

View File

@@ -53,7 +53,7 @@ return [
'string_regexp' => '/^(([a-z0-9\-\._]+, ?)*[a-z0-9\-\._]+)?$/i', 'string_regexp' => '/^(([a-z0-9\-\._]+, ?)*[a-z0-9\-\._]+)?$/i',
'string_emptyallowed' => true, 'string_emptyallowed' => true,
'default' => '', 'default' => '',
'save_method' => 'storeSettingField', 'save_method' => 'storeSettingClearCertificates',
'advanced_mode' => true 'advanced_mode' => true
], ],
/** /**
@@ -154,7 +154,7 @@ return [
/** /**
* FCGID * FCGID
*/ */
'system_mod_fcgid_enabled_ownvhost' => [ 'system_mod_fcgid_ownvhost' => [
'label' => lng('serversettings.mod_fcgid_ownvhost'), 'label' => lng('serversettings.mod_fcgid_ownvhost'),
'settinggroup' => 'system', 'settinggroup' => 'system',
'varname' => 'mod_fcgid_ownvhost', 'varname' => 'mod_fcgid_ownvhost',
@@ -224,7 +224,7 @@ return [
/** /**
* php-fpm * php-fpm
*/ */
'system_phpfpm_enabled_ownvhost' => [ 'phpfpm_enabled_ownvhost' => [
'label' => lng('phpfpm.ownvhost'), 'label' => lng('phpfpm.ownvhost'),
'settinggroup' => 'phpfpm', 'settinggroup' => 'phpfpm',
'varname' => 'enabled_ownvhost', 'varname' => 'enabled_ownvhost',
@@ -237,7 +237,7 @@ return [
]), ]),
'requires_reconf' => ['system:php-fpm'] 'requires_reconf' => ['system:php-fpm']
], ],
'system_phpfpm_httpuser' => [ 'phpfpm_vhost_httpuser' => [
'label' => lng('phpfpm.vhost_httpuser'), 'label' => lng('phpfpm.vhost_httpuser'),
'settinggroup' => 'phpfpm', 'settinggroup' => 'phpfpm',
'varname' => 'vhost_httpuser', 'varname' => 'vhost_httpuser',
@@ -250,7 +250,7 @@ return [
]), ]),
'requires_reconf' => ['system:php-fpm'] 'requires_reconf' => ['system:php-fpm']
], ],
'system_phpfpm_httpgroup' => [ 'phpfpm_vhost_httpgroup' => [
'label' => lng('phpfpm.vhost_httpgroup'), 'label' => lng('phpfpm.vhost_httpgroup'),
'settinggroup' => 'phpfpm', 'settinggroup' => 'phpfpm',
'varname' => 'vhost_httpgroup', 'varname' => 'vhost_httpgroup',
@@ -263,7 +263,7 @@ return [
]), ]),
'requires_reconf' => ['system:php-fpm'] 'requires_reconf' => ['system:php-fpm']
], ],
'system_phpfpm_defaultini_ownvhost' => [ 'phpfpm_vhost_defaultini' => [
'label' => lng('serversettings.mod_fcgid.defaultini_ownvhost'), 'label' => lng('serversettings.mod_fcgid.defaultini_ownvhost'),
'settinggroup' => 'phpfpm', 'settinggroup' => 'phpfpm',
'varname' => 'vhost_defaultini', 'varname' => 'vhost_defaultini',

View File

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

View File

@@ -32,7 +32,7 @@ return [
'title' => lng('admin.sslsettings'), 'title' => lng('admin.sslsettings'),
'icon' => 'fa-solid fa-shield', 'icon' => 'fa-solid fa-shield',
'fields' => [ 'fields' => [
'system_ssl_enabled' => [ 'system_use_ssl' => [
'label' => lng('serversettings.ssl.use_ssl'), 'label' => lng('serversettings.ssl.use_ssl'),
'settinggroup' => 'system', 'settinggroup' => 'system',
'varname' => 'use_ssl', 'varname' => 'use_ssl',
@@ -180,7 +180,9 @@ return [
'letsencrypt' => 'Let\'s Encrypt (Live)', 'letsencrypt' => 'Let\'s Encrypt (Live)',
'buypass_test' => 'Buypass (Test / Staging)', 'buypass_test' => 'Buypass (Test / Staging)',
'buypass' => 'Buypass (Live)', 'buypass' => 'Buypass (Live)',
'zerossl' => 'ZeroSSL (Live)' 'zerossl' => 'ZeroSSL (Live)',
'google' => 'Google (Live)',
'google_test' => 'Google (Test / Staging)',
], ],
'save_method' => 'storeSettingField' 'save_method' => 'storeSettingField'
], ],
@@ -239,6 +241,16 @@ return [
'type' => 'checkbox', 'type' => 'checkbox',
'default' => true, 'default' => true,
'save_method' => 'storeSettingField' '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' 'lighttpd'
], ],
'fields' => [ 'fields' => [
'system_mod_fcgid_enabled' => [ 'system_mod_fcgid' => [
'label' => lng('serversettings.mod_fcgid'), 'label' => lng('serversettings.mod_fcgid'),
'settinggroup' => 'system', 'settinggroup' => 'system',
'varname' => 'mod_fcgid', 'varname' => 'mod_fcgid',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -92,6 +92,7 @@ if ($userinfo['change_serversettings'] == '1') {
if ($distribution != "" && isset($_POST['finish'])) { if ($distribution != "" && isset($_POST['finish'])) {
unset($_POST['finish']); unset($_POST['finish']);
unset($_POST['csrf_token']);
$params = $_POST; $params = $_POST;
$params['distro'] = $distribution; $params['distro'] = $distribution;
$params['system'] = []; $params['system'] = [];
@@ -121,8 +122,6 @@ if ($userinfo['change_serversettings'] == '1') {
'distribution' => $distribution 'distribution' => $distribution
]); ]);
} else { } else {
// @fixme check set distribution from settings
$cfg_formfield = [ $cfg_formfield = [
'config' => [ 'config' => [
'title' => lng('admin.configfiles.serverconfiguration'), '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 // create serveralias options
$serveraliasoptions = [ $serveraliasoptions = [
0 => lng('domains.serveraliasoption_wildcard'), 0 => lng('domains.serveraliasoption_wildcard'),
@@ -545,6 +551,12 @@ if ($page == 'domains' || $page == 'overview') {
$result['temporary_ssl_redirect'] = $result['ssl_redirect']; $result['temporary_ssl_redirect'] = $result['ssl_redirect'];
$result['ssl_redirect'] = ($result['ssl_redirect'] == 0 ? 0 : 1); $result['ssl_redirect'] = ($result['ssl_redirect'] == 0 ? 0 : 1);
$openbasedir = [
0 => lng('domain.docroot'),
1 => lng('domain.homedir'),
2 => lng('domain.docparent')
];
$serveraliasoptions = [ $serveraliasoptions = [
0 => lng('domains.serveraliasoption_wildcard'), 0 => lng('domains.serveraliasoption_wildcard'),
1 => lng('domains.serveraliasoption_www'), 1 => lng('domains.serveraliasoption_www'),

View File

@@ -43,12 +43,6 @@ use PHPMailer\PHPMailer\PHPMailer;
const AREA = 'admin'; const AREA = 'admin';
require __DIR__ . '/lib/init.php'; require __DIR__ . '/lib/init.php';
// get sql-root access data
Database::needRoot(true);
Database::needSqlData();
$sql_root = Database::getSqlData();
Database::needRoot(false);
if ($page == 'overview' && $userinfo['change_serversettings'] == '1') { if ($page == 'overview' && $userinfo['change_serversettings'] == '1') {
$settings_data = PhpHelper::loadConfigArrayDir('./actions/admin/settings/'); $settings_data = PhpHelper::loadConfigArrayDir('./actions/admin/settings/');
Settings::loadSettingsInto($settings_data); Settings::loadSettingsInto($settings_data);

View File

@@ -253,6 +253,9 @@ if ($action == '') {
if (isset($_POST['prepare']) && $_POST['prepare'] == 'prepare') { if (isset($_POST['prepare']) && $_POST['prepare'] == 'prepare') {
// email templates // email templates
$language = htmlentities(Validate::validate($_POST['language'], 'language', '/^[^\r\n\0"\']+$/', 'nolanguageselect')); $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'); $template = Validate::validate($_POST['template'], 'template');
$result_stmt = Database::prepare(" $result_stmt = Database::prepare("
@@ -288,6 +291,9 @@ if ($action == '') {
} elseif (isset($_POST['send']) && $_POST['send'] == 'send' && !isset($_POST['filesend'])) { } elseif (isset($_POST['send']) && $_POST['send'] == 'send' && !isset($_POST['filesend'])) {
// email templates // email templates
$language = htmlentities(Validate::validate($_POST['language'], 'language', '/^[^\r\n\0"\']+$/', 'nolanguageselect')); $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'); $template = Validate::validate($_POST['template'], 'template');
$subject = Validate::validate($_POST['subject'], 'subject', '/^[^\r\n\0]+$/', 'nosubjectcreate'); $subject = Validate::validate($_POST['subject'], 'subject', '/^[^\r\n\0]+$/', 'nosubjectcreate');
$mailbody = Validate::validate($_POST['mailbody'], 'mailbody', '/^[^\0]+$/', 'nomailbodycreate'); $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\InstallCommand;
use Froxlor\Cli\MasterCron; use Froxlor\Cli\MasterCron;
use Froxlor\Cli\UserCommand; use Froxlor\Cli\UserCommand;
use Froxlor\Cli\ValidateAcmeWebroot;
use Froxlor\Froxlor; use Froxlor\Froxlor;
// validate correct php version // validate correct php version
@@ -59,4 +60,5 @@ $application->add(new UpdateCommand());
$application->add(new InstallCommand()); $application->add(new InstallCommand());
$application->add(new MasterCron()); $application->add(new MasterCron());
$application->add(new UserCommand()); $application->add(new UserCommand());
$application->add(new ValidateAcmeWebroot());
$application->run(); $application->run();

View File

@@ -45,6 +45,7 @@
"ext-openssl": "*", "ext-openssl": "*",
"ext-fileinfo": "*", "ext-fileinfo": "*",
"ext-gmp": "*", "ext-gmp": "*",
"ext-gd": "*",
"phpmailer/phpmailer": "~6.0", "phpmailer/phpmailer": "~6.0",
"monolog/monolog": "^1.24", "monolog/monolog": "^1.24",
"robthree/twofactorauth": "^1.6", "robthree/twofactorauth": "^1.6",
@@ -52,7 +53,8 @@
"voku/anti-xss": "^4.1", "voku/anti-xss": "^4.1",
"twig/twig": "^3.3", "twig/twig": "^3.3",
"erusev/parsedown": "^1.7", "erusev/parsedown": "^1.7",
"symfony/console": "^5.4" "symfony/console": "^5.4",
"pear/net_dns2": "^1.5"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9", "phpunit/phpunit": "^9",

836
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -59,7 +59,6 @@ if ($page == 'overview' || $page == 'domains') {
$domain_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.domains.php'; $domain_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.domains.php';
$collection = (new Collection(SubDomains::class, $userinfo)) $collection = (new Collection(SubDomains::class, $userinfo))
->withPagination($domain_list_data['domain_list']['columns'], $domain_list_data['domain_list']['default_sorting']); ->withPagination($domain_list_data['domain_list']['columns'], $domain_list_data['domain_list']['default_sorting']);
$parentDomainCollection = (new Collection(SubDomains::class, $userinfo, ['sql_search' => ['d.parentdomainid' => 0]]));
} catch (Exception $e) { } catch (Exception $e) {
Response::dynamicError($e->getMessage()); Response::dynamicError($e->getMessage());
} }

View File

@@ -26,9 +26,10 @@
const AREA = 'customer'; const AREA = 'customer';
require __DIR__ . '/lib/init.php'; require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\EmailAccounts as EmailAccounts; use Froxlor\Api\Commands\EmailAccounts;
use Froxlor\Api\Commands\EmailForwarders as EmailForwarders; use Froxlor\Api\Commands\EmailForwarders;
use Froxlor\Api\Commands\Emails as Emails; use Froxlor\Api\Commands\Emails;
use Froxlor\Api\Commands\EmailDomains;
use Froxlor\Database\Database; use Froxlor\Database\Database;
use Froxlor\FroxlorLogger; use Froxlor\FroxlorLogger;
use Froxlor\PhpHelper; use Froxlor\PhpHelper;
@@ -50,13 +51,50 @@ if (Settings::IsInList('panel.customer_hide_options', 'email') || $userinfo['ema
$id = (int)Request::any('id'); $id = (int)Request::any('id');
if ($page == 'overview' || $page == 'emails') { 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 == '') { if ($action == '') {
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_email::emails"); $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 { try {
$email_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.emails.php'; $email_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.emails.php';
$collection = (new Collection(Emails::class, $userinfo)) $collection = (new Collection(Emails::class, $userinfo, $sql_search))
->withPagination($email_list_data['email_list']['columns'], $email_list_data['email_list']['default_sorting']); ->withPagination($email_list_data['email_list']['columns'],
$email_list_data['email_list']['default_sorting']);
} catch (Exception $e) { } catch (Exception $e) {
Response::dynamicError($e->getMessage()); Response::dynamicError($e->getMessage());
} }
@@ -71,13 +109,22 @@ if ($page == 'overview' || $page == 'emails') {
]); ]);
$emaildomains_count = $result2['emaildomains']; $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')) { if (CurrentUser::canAddResource('emails')) {
$actions_links = [ $actions_links[] = [
[ 'href' => $linker->getLink(['section' => 'email', 'page' => 'email_domain', 'action' => 'add', 'domainid' => $email_domainid]),
'href' => $linker->getLink(['section' => 'email', 'page' => $page, 'action' => 'add']), 'label' => lng('emails.emails_add')
'label' => lng('emails.emails_add')
]
]; ];
} }
@@ -145,7 +192,11 @@ if ($page == 'overview' || $page == 'emails') {
"cid" => $userinfo['customerid'] "cid" => $userinfo['customerid']
]); ]);
$domains = []; $domains = [];
$selected_domain = "";
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { 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']); $domains[$row['domain']] = $idna_convert->decode($row['domain']);
} }
@@ -244,11 +295,13 @@ if ($page == 'overview' || $page == 'emails') {
} }
Response::redirectTo($filename, [ Response::redirectTo($filename, [
'page' => $page, 'page' => $page,
'domainid' => $email_domainid,
'action' => 'edit', 'action' => 'edit',
'id' => $id 'id' => $id,
]); ]);
} }
} elseif ($page == 'accounts') { } elseif ($page == 'accounts') {
$email_domainid = Request::any('domainid', 0);
if ($action == 'add' && $id != 0) { if ($action == 'add' && $id != 0) {
if ($userinfo['email_accounts'] == '-1' || ($userinfo['email_accounts_used'] < $userinfo['email_accounts'])) { if ($userinfo['email_accounts'] == '-1' || ($userinfo['email_accounts_used'] < $userinfo['email_accounts'])) {
try { try {
@@ -267,7 +320,8 @@ if ($page == 'overview' || $page == 'emails') {
Response::dynamicError($e->getMessage()); Response::dynamicError($e->getMessage());
} }
Response::redirectTo($filename, [ Response::redirectTo($filename, [
'page' => 'emails', 'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit', 'action' => 'edit',
'id' => $id 'id' => $id
]); ]);
@@ -292,7 +346,8 @@ if ($page == 'overview' || $page == 'emails') {
'class' => 'btn-secondary', 'class' => 'btn-secondary',
'href' => $linker->getLink([ 'href' => $linker->getLink([
'section' => 'email', 'section' => 'email',
'page' => 'emails', 'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit', 'action' => 'edit',
'id' => $id 'id' => $id
]), ]),
@@ -301,7 +356,11 @@ if ($page == 'overview' || $page == 'emails') {
], ],
[ [
'class' => 'btn-secondary', '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'), 'label' => lng('menue.email.emails'),
'icon' => 'fa-solid fa-envelope' 'icon' => 'fa-solid fa-envelope'
] ]
@@ -332,7 +391,8 @@ if ($page == 'overview' || $page == 'emails') {
Response::dynamicError($e->getMessage()); Response::dynamicError($e->getMessage());
} }
Response::redirectTo($filename, [ Response::redirectTo($filename, [
'page' => 'emails', 'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit', 'action' => 'edit',
'id' => $id 'id' => $id
]); ]);
@@ -350,7 +410,8 @@ if ($page == 'overview' || $page == 'emails') {
'class' => 'btn-secondary', 'class' => 'btn-secondary',
'href' => $linker->getLink([ 'href' => $linker->getLink([
'section' => 'email', 'section' => 'email',
'page' => 'emails', 'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit', 'action' => 'edit',
'id' => $id 'id' => $id
]), ]),
@@ -359,7 +420,11 @@ if ($page == 'overview' || $page == 'emails') {
], ],
[ [
'class' => 'btn-secondary', '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'), 'label' => lng('menue.email.emails'),
'icon' => 'fa-solid fa-envelope' 'icon' => 'fa-solid fa-envelope'
] ]
@@ -385,7 +450,8 @@ if ($page == 'overview' || $page == 'emails') {
Response::dynamicError($e->getMessage()); Response::dynamicError($e->getMessage());
} }
Response::redirectTo($filename, [ Response::redirectTo($filename, [
'page' => 'emails', 'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit', 'action' => 'edit',
'id' => $id 'id' => $id
]); ]);
@@ -403,7 +469,8 @@ if ($page == 'overview' || $page == 'emails') {
'class' => 'btn-secondary', 'class' => 'btn-secondary',
'href' => $linker->getLink([ 'href' => $linker->getLink([
'section' => 'email', 'section' => 'email',
'page' => 'emails', 'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit', 'action' => 'edit',
'id' => $id 'id' => $id
]), ]),
@@ -412,7 +479,11 @@ if ($page == 'overview' || $page == 'emails') {
], ],
[ [
'class' => 'btn-secondary', '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'), 'label' => lng('menue.email.emails'),
'icon' => 'fa-solid fa-envelope' 'icon' => 'fa-solid fa-envelope'
] ]
@@ -438,7 +509,8 @@ if ($page == 'overview' || $page == 'emails') {
Response::dynamicError($e->getMessage()); Response::dynamicError($e->getMessage());
} }
Response::redirectTo($filename, [ Response::redirectTo($filename, [
'page' => 'emails', 'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit', 'action' => 'edit',
'id' => $id 'id' => $id
]); ]);
@@ -446,12 +518,14 @@ if ($page == 'overview' || $page == 'emails') {
HTML::askYesNoWithCheckbox('email_reallydelete_account', 'admin_customer_alsoremovemail', $filename, [ HTML::askYesNoWithCheckbox('email_reallydelete_account', 'admin_customer_alsoremovemail', $filename, [
'id' => $id, 'id' => $id,
'page' => $page, 'page' => $page,
'domainid' => $email_domainid,
'action' => $action 'action' => $action
], $idna_convert->decode($result['email_full'])); ], $idna_convert->decode($result['email_full']));
} }
} }
} }
} elseif ($page == 'forwarders') { } elseif ($page == 'forwarders') {
$email_domainid = Request::any('domainid', 0);
if ($action == 'add' && $id != 0) { if ($action == 'add' && $id != 0) {
if ($userinfo['email_forwarders_used'] < $userinfo['email_forwarders'] || $userinfo['email_forwarders'] == '-1') { if ($userinfo['email_forwarders_used'] < $userinfo['email_forwarders'] || $userinfo['email_forwarders'] == '-1') {
try { try {
@@ -471,7 +545,8 @@ if ($page == 'overview' || $page == 'emails') {
Response::dynamicError($e->getMessage()); Response::dynamicError($e->getMessage());
} }
Response::redirectTo($filename, [ Response::redirectTo($filename, [
'page' => 'emails', 'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit', 'action' => 'edit',
'id' => $id 'id' => $id
]); ]);
@@ -489,7 +564,8 @@ if ($page == 'overview' || $page == 'emails') {
'class' => 'btn-secondary', 'class' => 'btn-secondary',
'href' => $linker->getLink([ 'href' => $linker->getLink([
'section' => 'email', 'section' => 'email',
'page' => 'emails', 'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit', 'action' => 'edit',
'id' => $id 'id' => $id
]), ]),
@@ -498,7 +574,11 @@ if ($page == 'overview' || $page == 'emails') {
], ],
[ [
'class' => 'btn-secondary', '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'), 'label' => lng('menue.email.emails'),
'icon' => 'fa-solid fa-envelope' 'icon' => 'fa-solid fa-envelope'
] ]
@@ -540,7 +620,8 @@ if ($page == 'overview' || $page == 'emails') {
Response::dynamicError($e->getMessage()); Response::dynamicError($e->getMessage());
} }
Response::redirectTo($filename, [ Response::redirectTo($filename, [
'page' => 'emails', 'page' => 'email_domain',
'domainid' => $email_domainid,
'action' => 'edit', 'action' => 'edit',
'id' => $id 'id' => $id
]); ]);
@@ -549,6 +630,7 @@ if ($page == 'overview' || $page == 'emails') {
'id' => $id, 'id' => $id,
'forwarderid' => $forwarderid, 'forwarderid' => $forwarderid,
'page' => $page, 'page' => $page,
'domainid' => $email_domainid,
'action' => $action 'action' => $action
], $idna_convert->decode($result['email_full']) . ' -> ' . $idna_convert->decode($forwarder)); ], $idna_convert->decode($result['email_full']) . ' -> ' . $idna_convert->decode($forwarder));
} }

View File

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

View File

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

View File

@@ -92,7 +92,7 @@ if ($page == 'overview' || $page == 'mysqls') {
$result = json_decode($json_result, true)['data']; $result = json_decode($json_result, true)['data'];
if (isset($result['databasename']) && $result['databasename'] != '') { if (isset($result['databasename']) && $result['databasename'] != '') {
Database::needRoot(true, $result['dbserver']); Database::needRoot(true, $result['dbserver'], false);
Database::needSqlData(); Database::needSqlData();
$sql_root = Database::getSqlData(); $sql_root = Database::getSqlData();
Database::needRoot(false); Database::needRoot(false);

View File

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

View File

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

View File

@@ -225,7 +225,7 @@ CREATE TABLE `panel_customers` (
`allowed_mysqlserver` text NOT NULL, `allowed_mysqlserver` text NOT NULL,
PRIMARY KEY (`customerid`), PRIMARY KEY (`customerid`),
UNIQUE KEY `loginname` (`loginname`) UNIQUE KEY `loginname` (`loginname`)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci; ) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC;
DROP TABLE IF EXISTS `panel_databases`; DROP TABLE IF EXISTS `panel_databases`;
@@ -642,7 +642,7 @@ opcache.validate_timestamps'),
('system', 'leprivatekey', 'unset'), ('system', 'leprivatekey', 'unset'),
('system', 'lepublickey', 'unset'), ('system', 'lepublickey', 'unset'),
('system', 'letsencryptca', 'letsencrypt'), ('system', 'letsencryptca', 'letsencrypt'),
('system', 'letsencryptchallengepath', '/var/www/froxlor'), ('system', 'letsencryptchallengepath', '/var/www/html/froxlor'),
('system', 'letsencryptkeysize', '4096'), ('system', 'letsencryptkeysize', '4096'),
('system', 'letsencryptreuseold', 0), ('system', 'letsencryptreuseold', 0),
('system', 'leenabled', '0'), ('system', 'leenabled', '0'),
@@ -670,6 +670,7 @@ opcache.validate_timestamps'),
('system', 'leaccount', ''), ('system', 'leaccount', ''),
('system', 'nssextrausers', '1'), ('system', 'nssextrausers', '1'),
('system', 'le_domain_dnscheck', '1'), ('system', 'le_domain_dnscheck', '1'),
('system', 'le_domain_dnscheck_resolver', '1.1.1.1'),
('system', 'ssl_protocols', 'TLSv1.2'), ('system', 'ssl_protocols', 'TLSv1.2'),
('system', 'tlsv13_cipher_list', ''), ('system', 'tlsv13_cipher_list', ''),
('system', 'honorcipherorder', '0'), ('system', 'honorcipherorder', '0'),
@@ -696,9 +697,12 @@ opcache.validate_timestamps'),
('system', 'distribution', ''), ('system', 'distribution', ''),
('system', 'update_channel', 'stable'), ('system', 'update_channel', 'stable'),
('system', 'updatecheck_data', ''), ('system', 'updatecheck_data', ''),
('system', 'update_notify_last', '2.0.4'), ('system', 'update_notify_last', '2.0.17'),
('system', 'traffictool', 'goaccess'), ('system', 'traffictool', 'goaccess'),
('system', 'req_limit_per_interval', 60),
('system', 'req_limit_interval', 60),
('api', 'enabled', '0'), ('api', 'enabled', '0'),
('api', 'customer_default', '1'),
('2fa', 'enabled', '1'), ('2fa', 'enabled', '1'),
('panel', 'decimal_places', '4'), ('panel', 'decimal_places', '4'),
('panel', 'adminmail', 'admin@SERVERNAME'), ('panel', 'adminmail', 'admin@SERVERNAME'),
@@ -740,8 +744,8 @@ opcache.validate_timestamps'),
('panel', 'logo_overridetheme', '0'), ('panel', 'logo_overridetheme', '0'),
('panel', 'logo_overridecustom', '0'), ('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'), ('panel', 'settings_mode', '0'),
('panel', 'version', '2.0.4'), ('panel', 'version', '2.0.17'),
('panel', 'db_version', '202212060'); ('panel', 'db_version', '202304260');
DROP TABLE IF EXISTS `panel_tasks`; DROP TABLE IF EXISTS `panel_tasks`;
@@ -981,7 +985,9 @@ CREATE TABLE IF NOT EXISTS `domain_ssl_settings` (
`ssl_cert_chainfile` mediumtext, `ssl_cert_chainfile` mediumtext,
`ssl_csr_file` mediumtext, `ssl_csr_file` mediumtext,
`ssl_fullchain_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`), PRIMARY KEY (`id`),
UNIQUE KEY (`domainid`) UNIQUE KEY (`domainid`)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci; ) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;

View File

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

View File

@@ -23,11 +23,11 @@
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2 * @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/ */
use Froxlor\Froxlor;
use Froxlor\FileDir;
use Froxlor\Database\Database; use Froxlor\Database\Database;
use Froxlor\Settings; use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\Install\Update; use Froxlor\Install\Update;
use Froxlor\Settings;
if (!defined('_CRON_UPDATE')) { if (!defined('_CRON_UPDATE')) {
if (!defined('AREA') || (defined('AREA') && AREA != 'admin') || !isset($userinfo['loginname']) || (isset($userinfo['loginname']) && $userinfo['loginname'] == '')) { if (!defined('AREA') || (defined('AREA') && AREA != 'admin') || !isset($userinfo['loginname']) || (isset($userinfo['loginname']) && $userinfo['loginname'] == '')) {
@@ -38,10 +38,9 @@ if (!defined('_CRON_UPDATE')) {
// last 0.10.x release // last 0.10.x release
if (Froxlor::isFroxlorVersion('0.10.38.3')) { if (Froxlor::isFroxlorVersion('0.10.38.3')) {
$update_to = '2.0.0-beta1'; $update_to = '2.0.0-beta1';
Update::showUpdateStep("Updating from 0.10.38.3 to ".$update_to, false); Update::showUpdateStep("Updating from 0.10.38.3 to " . $update_to, false);
Update::showUpdateStep("Removing unused table"); Update::showUpdateStep("Removing unused table");
Database::query("DROP TABLE IF EXISTS `panel_sessions`;"); Database::query("DROP TABLE IF EXISTS `panel_sessions`;");
@@ -66,8 +65,8 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
KEY customerid (customerid) KEY customerid (customerid)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;"; ) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;";
Database::query($sql); Database::query($sql);
Database::query("SET SESSION innodb_strict_mode=OFF;");
// new customer allowed_mysqlserver field // new customer allowed_mysqlserver field
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` ROW_FORMAT=DYNAMIC;");
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` CHANGE COLUMN `customernumber` `customernumber` varchar(100) NOT NULL default '';"); Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` CHANGE COLUMN `customernumber` `customernumber` varchar(100) NOT NULL default '';");
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` CHANGE COLUMN `allowed_phpconfigs` `allowed_phpconfigs` text NOT NULL;"); Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` CHANGE COLUMN `allowed_phpconfigs` `allowed_phpconfigs` text NOT NULL;");
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` ADD `allowed_mysqlserver` text NOT NULL;"); Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` ADD `allowed_mysqlserver` text NOT NULL;");
@@ -83,7 +82,7 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
Database::query("ALTER TABLE `" . TABLE_PANEL_ADMINS . "` DROP COLUMN `domains_see_all`;"); Database::query("ALTER TABLE `" . TABLE_PANEL_ADMINS . "` DROP COLUMN `domains_see_all`;");
Update::lastStepStatus(0); 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(" $dbservers_stmt = Database::query("
SELECT `customerid`, SELECT `customerid`,
GROUP_CONCAT(DISTINCT `dbserver` SEPARATOR ',') as allowed_mysqlserver 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)) { while ($dbserver = $dbservers_stmt->fetch(PDO::FETCH_ASSOC)) {
if (isset($dbserver['allowed_mysqlserver']) && !empty($dbserver['allowed_mysqlserver'])) { if (isset($dbserver['allowed_mysqlserver']) && !empty($dbserver['allowed_mysqlserver'])) {
$allowed_mysqlserver = json_encode(explode(",", $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); Update::lastStepStatus(0);
@@ -141,12 +141,13 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
// none of the files existed // none of the files existed
Update::lastStepStatus(0); Update::lastStepStatus(0);
} else { } 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"); Update::showUpdateStep("Adding new settings");
$panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int) $_POST['panel_settings_mode'] : 0; $panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int)$_POST['panel_settings_mode'] : 0;
Settings::AddNew("panel.settings_mode", $panel_settings_mode); Settings::AddNew("panel.settings_mode", $panel_settings_mode);
$system_distribution = isset($_POST['system_distribution']) ? $_POST['system_distribution'] : ''; $system_distribution = isset($_POST['system_distribution']) ? $_POST['system_distribution'] : '';
Settings::AddNew("system.distribution", $system_distribution); Settings::AddNew("system.distribution", $system_distribution);
@@ -183,17 +184,16 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
Update::lastStepStatus(0); Update::lastStepStatus(0);
Update::showUpdateStep("Updating email account password-hashes"); Update::showUpdateStep("Updating email account password-hashes");
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password` = REPLACE(`password`, '$1$', '{MD5-CRYPT}$1$') WHERE SUBSTRING(`password`, 1, 3) = '$1$'"); Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password_enc` = REPLACE(`password_enc`, '$1$', '{MD5-CRYPT}$1$') WHERE SUBSTRING(`password_enc`, 1, 3) = '$1$'");
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password` = REPLACE(`password`, '$5$', '{SHA256-CRYPT}$5$') WHERE SUBSTRING(`password`, 1, 3) = '$5$'"); Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password_enc` = REPLACE(`password_enc`, '$5$', '{SHA256-CRYPT}$5$') WHERE SUBSTRING(`password_enc`, 1, 3) = '$5$'");
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password` = REPLACE(`password`, '$6$', '{SHA512-CRYPT}$6$') WHERE SUBSTRING(`password`, 1, 3) = '$6$'"); Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password_enc` = REPLACE(`password_enc`, '$6$', '{SHA512-CRYPT}$6$') WHERE SUBSTRING(`password_enc`, 1, 3) = '$6$'");
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password` = REPLACE(`password`, '$2y$', '{BLF-CRYPT}$2y$') WHERE SUBSTRING(`password`, 1, 4) = '$2y$'"); Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password_enc` = REPLACE(`password_enc`, '$2y$', '{BLF-CRYPT}$2y$') WHERE SUBSTRING(`password_enc`, 1, 4) = '$2y$'");
Update::lastStepStatus(0); Update::lastStepStatus(0);
Froxlor::updateToVersion($update_to); Froxlor::updateToVersion($update_to);
} }
if (Froxlor::isDatabaseVersion('202112310')) { if (Froxlor::isDatabaseVersion('202112310')) {
Update::showUpdateStep("Adjusting traffic tool settings"); Update::showUpdateStep("Adjusting traffic tool settings");
$traffic_tool = Settings::Get('system.awstats_enabled') == 1 ? 'awstats' : 'webalizer'; $traffic_tool = Settings::Get('system.awstats_enabled') == 1 ? 'awstats' : 'webalizer';
Settings::AddNew("system.traffictool", $traffic_tool); Settings::AddNew("system.traffictool", $traffic_tool);
@@ -204,20 +204,31 @@ if (Froxlor::isDatabaseVersion('202112310')) {
} }
if (Froxlor::isDatabaseVersion('202211030')) { if (Froxlor::isDatabaseVersion('202211030')) {
Update::showUpdateStep("Creating backward compatibility for cronjob"); Update::showUpdateStep("Creating backward compatibility for cronjob");
$complete_filedir = Froxlor::getInstallDir() . '/scripts'; $disabled = explode(',', ini_get('disable_functions'));
mkdir($complete_filedir, 0750, true); $exec_allowed = !in_array('exec', $disabled);
$newCronBin = Froxlor::getInstallDir().'/bin/froxlor-cli'; // check whether old files could be deleted in previous updates and if not,
$compCron = <<<EOF // 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';
$compCron = <<<EOF
<?php <?php
chmod('$newCronBin', 0755); chmod('$newCronBin', 0755);
// re-create cron.d configuration file // re-create cron.d configuration file
exec('$newCronBin froxlor:cron -r 99'); exec('$newCronBin froxlor:cron -r 99');
exit; exit;
EOF; EOF;
file_put_contents($complete_filedir.'/froxlor_master_cronjob.php', $compCron); file_put_contents($complete_filedir . '/froxlor_master_cronjob.php', $compCron);
Update::lastStepStatus(0); 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'); Froxlor::updateToDbVersion('202212060');
} }
@@ -257,19 +268,217 @@ if (Froxlor::isFroxlorVersion('2.0.3')) {
$complete_filedir = Froxlor::getInstallDir() . '/scripts'; $complete_filedir = Froxlor::getInstallDir() . '/scripts';
// check if compat. cronjob still exists (most likely didn't run successfully b/c of error from former 2.0 release) // 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')) { if (@file_exists($complete_filedir . '/froxlor_master_cronjob.php')) {
Update::showUpdateStep("Adjusting backward compatibility for cronjob"); Update::showUpdateStep("Adjusting backward compatibility for cronjob");
$newCronBin = Froxlor::getInstallDir() . '/bin/froxlor-cli'; $disabled = explode(',', ini_get('disable_functions'));
$compCron = <<<EOF $exec_allowed = !in_array('exec', $disabled);
if ($exec_allowed) {
$newCronBin = Froxlor::getInstallDir() . '/bin/froxlor-cli';
$compCron = <<<EOF
<?php <?php
chmod('$newCronBin', 0755); chmod('$newCronBin', 0755);
// re-create cron.d configuration file // re-create cron.d configuration file
exec('$newCronBin froxlor:cron -r 99'); exec('$newCronBin froxlor:cron -r 99');
exit; exit;
EOF; EOF;
file_put_contents($complete_filedir . '/froxlor_master_cronjob.php', $compCron); 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');
}
if (Froxlor::isFroxlorVersion('2.0.4')) {
Update::showUpdateStep("Updating from 2.0.4 to 2.0.5", false);
Froxlor::updateToVersion('2.0.5');
}
if (Froxlor::isFroxlorVersion('2.0.5')) {
Update::showUpdateStep("Updating from 2.0.5 to 2.0.6", false);
Update::showUpdateStep("Updating possible missing email account password-hashes");
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password_enc` = REPLACE(`password_enc`, '$1$', '{MD5-CRYPT}$1$') WHERE SUBSTRING(`password_enc`, 1, 3) = '$1$'");
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password_enc` = REPLACE(`password_enc`, '$5$', '{SHA256-CRYPT}$5$') WHERE SUBSTRING(`password_enc`, 1, 3) = '$5$'");
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password_enc` = REPLACE(`password_enc`, '$6$', '{SHA512-CRYPT}$6$') WHERE SUBSTRING(`password_enc`, 1, 3) = '$6$'");
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password_enc` = REPLACE(`password_enc`, '$2y$', '{BLF-CRYPT}$2y$') WHERE SUBSTRING(`password_enc`, 1, 4) = '$2y$'");
Update::lastStepStatus(0);
Froxlor::updateToVersion('2.0.6');
}
if (Froxlor::isFroxlorVersion('2.0.6')) {
Update::showUpdateStep("Updating from 2.0.6 to 2.0.7", false);
Update::showUpdateStep("Correcting allowed_mysqlserver for customers");
Database::query("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `allowed_mysqlserver` = '[0]' WHERE `allowed_mysqlserver` = ''");
Update::lastStepStatus(0);
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); Update::lastStepStatus(0);
} }
Froxlor::updateToVersion('2.0.4'); 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');
} }

View File

@@ -34,9 +34,14 @@ $return = [];
if (Update::versionInUpdate($current_db_version, '202004140')) { if (Update::versionInUpdate($current_db_version, '202004140')) {
$has_preconfig = true; $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).'; $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;'; $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; $preconfig['fields'] = $return;

View File

@@ -27,6 +27,7 @@ use Froxlor\Froxlor;
use Froxlor\FileDir; use Froxlor\FileDir;
use Froxlor\Config\ConfigParser; use Froxlor\Config\ConfigParser;
use Froxlor\Install\Update; use Froxlor\Install\Update;
use Froxlor\Settings;
$preconfig = [ $preconfig = [
'title' => '2.x updates', 'title' => '2.x updates',
@@ -36,7 +37,6 @@ $return = [];
if (Update::versionInUpdate($current_version, '2.0.0-beta1')) { 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.'; $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>'; $question = '<strong>Chose settings mode (you can change that at any time)</strong>';
$return['panel_settings_mode'] = [ $return['panel_settings_mode'] = [
'type' => 'select', 'type' => 'select',
@@ -45,11 +45,11 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
1 => 'Advanced' 1 => 'Advanced'
], ],
'selected' => 1, 'selected' => 1,
'label' => $question 'label' => $question,
'prior_infotext' => $description
]; ];
$description = 'The configuration page now can preselect a distribution, please select your current distribution'; $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>'; $question = '<strong>Select distribution</strong>';
$config_dir = FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/lib/configfiles/'); $config_dir = FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/lib/configfiles/');
// show list of available distro's // show list of available distro's
@@ -68,9 +68,44 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
'type' => 'select', 'type' => 'select',
'select_var' => $distributions_select, 'select_var' => $distributions_select,
'selected' => '', '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; $preconfig['fields'] = $return;
return $preconfig; return $preconfig;

View File

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

View File

@@ -28,6 +28,7 @@ namespace Froxlor\Ajax;
use Froxlor\Api\Commands\Admins; use Froxlor\Api\Commands\Admins;
use Froxlor\Api\Commands\Customers; use Froxlor\Api\Commands\Customers;
use Froxlor\Api\Commands\Domains; use Froxlor\Api\Commands\Domains;
use Froxlor\Api\Commands\EmailDomains;
use Froxlor\Api\Commands\Emails; use Froxlor\Api\Commands\Emails;
use Froxlor\Api\Commands\FpmDaemons; use Froxlor\Api\Commands\FpmDaemons;
use Froxlor\Api\Commands\Ftps; use Froxlor\Api\Commands\Ftps;
@@ -267,7 +268,20 @@ class GlobalSearch
'result_format' => [ 'result_format' => [
'title' => ['self', 'getFieldFromResult'], 'title' => ['self', 'getFieldFromResult'],
'title_args' => 'email', '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 // databases
@@ -326,6 +340,14 @@ class GlobalSearch
if (!isset($result[$entity])) { if (!isset($result[$entity])) {
$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][] = [ $result[$entity][] = [
'title' => call_user_func($edata['result_format']['title'], $cresult, ($edata['result_format']['title_args'] ?? null)), 'title' => call_user_func($edata['result_format']['title'], $cresult, ($edata['result_format']['title_args'] ?? null)),
'href' => $edata['result_format']['href'] . $cresult[$edata['result_key']] 'href' => $edata['result_format']['href'] . $cresult[$edata['result_key']]
@@ -335,7 +357,7 @@ class GlobalSearch
} }
} // foreach entity } // foreach entity
} // foreach splitted search-term } // foreach split search-term
} }
return $result; return $result;
} }

View File

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

@@ -127,7 +127,9 @@ class Certificates extends ApiCommand implements ResourceEntity
} }
$do_verify = true; $do_verify = true;
$expirationdate = null; $validtodate = null;
$validtodate = null;
$issuer = "";
// no cert-file given -> forget everything // no cert-file given -> forget everything
if ($ssl_cert_file == '') { if ($ssl_cert_file == '') {
$ssl_key_file = ''; $ssl_key_file = '';
@@ -168,7 +170,10 @@ class Certificates extends ApiCommand implements ResourceEntity
} else { } else {
Response::standardError('sslcertificateinvalidcert', '', true); 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 // Add/Update database entry
@@ -183,7 +188,9 @@ class Certificates extends ApiCommand implements ResourceEntity
`ssl_key_file` = :ssl_key_file, `ssl_key_file` = :ssl_key_file,
`ssl_ca_file` = :ssl_ca_file, `ssl_ca_file` = :ssl_ca_file,
`ssl_cert_chainfile` = :ssl_cert_chainfile, `ssl_cert_chainfile` = :ssl_cert_chainfile,
`expirationdate` = :expirationdate `validfromdate` = :validfromdate,
`validtodate` = :validtodate,
`issuer` = :issuer
" . $qrywhere . " `domainid`= :domainid " . $qrywhere . " `domainid`= :domainid
"); ");
$params = [ $params = [
@@ -191,7 +198,9 @@ class Certificates extends ApiCommand implements ResourceEntity
"ssl_key_file" => $ssl_key_file, "ssl_key_file" => $ssl_key_file,
"ssl_ca_file" => $ssl_ca_file, "ssl_ca_file" => $ssl_ca_file,
"ssl_cert_chainfile" => $ssl_cert_chainfile, "ssl_cert_chainfile" => $ssl_cert_chainfile,
"expirationdate" => $expirationdate, "validfromdate" => $validfromdate,
"validtodate" => $validtodate,
"issuer" => $issuer,
"domainid" => $domainid "domainid" => $domainid
]; ];
Database::pexecute($stmt, $params, true, true); Database::pexecute($stmt, $params, true, true);
@@ -299,27 +308,23 @@ class Certificates extends ApiCommand implements ResourceEntity
} }
// Set data from certificate // Set data from certificate
$cert['isvalid'] = false;
$cert['san'] = null;
$cert_data = openssl_x509_parse($cert['ssl_cert_file']); $cert_data = openssl_x509_parse($cert['ssl_cert_file']);
if ($cert_data) { 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['isvalid'] = (bool)$cert_data['validTo_time_t'] > time();
$cert['issuer'] = $cert_data['issuer']['O'] ?? null; // Set subject alt names from certificate
} if (isset($cert_data['extensions']['subjectAltName']) && !empty($cert_data['extensions']['subjectAltName'])) {
$SANs = explode(",", $cert_data['extensions']['subjectAltName']);
// Set subject alt names from certificate $SANs = array_map('trim', $SANs);
$cert['san'] = null; foreach ($SANs as $san) {
if (isset($cert_data['extensions']['subjectAltName']) && !empty($cert_data['extensions']['subjectAltName'])) { $san = str_replace("DNS:", "", $san);
$SANs = explode(",", $cert_data['extensions']['subjectAltName']); if ($san != $cert_data['subject']['CN'] && strpos($san, "othername:") === false) {
$SANs = array_map('trim', $SANs); $cert['san'][] = $san;
foreach ($SANs as $san) { }
$san = str_replace("DNS:", "", $san);
if ($san != $cert_data['subject']['CN'] && strpos($san, "othername:") === false) {
$cert['san'][] = $san;
} }
} }
} }
$result[] = $cert; $result[] = $cert;
} }
return $this->response([ return $this->response([

View File

@@ -32,6 +32,7 @@ use Froxlor\Cron\TaskId;
use Froxlor\Database\Database; use Froxlor\Database\Database;
use Froxlor\FroxlorLogger; use Froxlor\FroxlorLogger;
use Froxlor\System\Cronjob; use Froxlor\System\Cronjob;
use Froxlor\UI\Response;
use Froxlor\Validate\Validate; use Froxlor\Validate\Validate;
use PDO; use PDO;
@@ -41,6 +42,14 @@ use PDO;
class Cronjobs extends ApiCommand implements ResourceEntity class Cronjobs extends ApiCommand implements ResourceEntity
{ {
private array $allowed_intervals = [
'MINUTE',
'HOUR',
'DAY',
'WEEK',
'MONTH'
];
/** /**
* You cannot add new cronjobs yet. * 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_value = Validate::validate($interval_value, 'interval_value', '/^([0-9]+)$/Di', 'stringisempty', [], true);
$interval_interval = Validate::validate($interval_interval, 'interval_interval', '', '', [], 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 // put together interval value
$interval = $interval_value . ' ' . strtoupper($interval_interval); $interval = $interval_value . ' ' . strtoupper($interval_interval);

View File

@@ -298,7 +298,7 @@ class Customers extends ApiCommand implements ResourceEntity
$fax = $this->getParam('fax', true, ''); $fax = $this->getParam('fax', true, '');
$customernumber = $this->getParam('customernumber', true, ''); $customernumber = $this->getParam('customernumber', true, '');
$def_language = $this->getParam('def_language', true, Settings::Get('panel.standardlanguage')); $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); $gender = (int)$this->getParam('gender', true, 0);
$custom_notes = $this->getParam('custom_notes', true, ''); $custom_notes = $this->getParam('custom_notes', true, '');
$custom_notes_show = $this->getBoolParam('custom_notes_show', true, 0); $custom_notes_show = $this->getBoolParam('custom_notes_show', true, 0);

View File

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

View File

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

View File

@@ -225,6 +225,8 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, whether php is enabled for this domain, default 0 (false) * optional, whether php is enabled for this domain, default 0 (false)
* @param bool $openbasedir * @param bool $openbasedir
* optional, whether to activate openbasedir restriction for this domain, default 0 (false) * 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 * @param int $phpsettingid
* optional, specify php-configuration that is being used by id, default 1 (system-default) * optional, specify php-configuration that is being used by id, default 1 (system-default)
* @param int $mod_fcgid_starter * @param int $mod_fcgid_starter
@@ -312,6 +314,7 @@ class Domains extends ApiCommand implements ResourceEntity
$documentroot = $this->getParam('documentroot', true, ''); $documentroot = $this->getParam('documentroot', true, '');
$phpenabled = $this->getBoolParam('phpenabled', true, 0); $phpenabled = $this->getBoolParam('phpenabled', true, 0);
$openbasedir = $this->getBoolParam('openbasedir', true, 0); $openbasedir = $this->getBoolParam('openbasedir', true, 0);
$openbasedir_path = $this->getParam('openbasedir_path', true, 0);
$phpsettingid = $this->getParam('phpsettingid', true, 1); $phpsettingid = $this->getParam('phpsettingid', true, 1);
$mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, -1); $mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, -1);
$mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, -1); $mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, -1);
@@ -404,20 +407,25 @@ class Domains extends ApiCommand implements ResourceEntity
$documentroot = $_documentroot; $documentroot = $_documentroot;
} }
$registration_date = Validate::validate($registration_date, 'registration_date', Validate::REGEX_YYYY_MM_DD, '', [ if (!is_null($registration_date)) {
'0000-00-00', $registration_date = Validate::validate($registration_date, 'registration_date',
'0', Validate::REGEX_YYYY_MM_DD, '', [
'' '0000-00-00',
], true); '0',
''
], true);
}
if ($registration_date == '0000-00-00' || empty($registration_date)) { if ($registration_date == '0000-00-00' || empty($registration_date)) {
$registration_date = null; $registration_date = null;
} }
if (!is_null($termination_date)) {
$termination_date = Validate::validate($termination_date, 'termination_date', Validate::REGEX_YYYY_MM_DD, '', [ $termination_date = Validate::validate($termination_date, 'termination_date',
'0000-00-00', Validate::REGEX_YYYY_MM_DD, '', [
'0', '0000-00-00',
'' '0',
], true); ''
], true);
}
if ($termination_date == '0000-00-00' || empty($termination_date)) { if ($termination_date == '0000-00-00' || empty($termination_date)) {
$termination_date = null; $termination_date = null;
} }
@@ -525,6 +533,10 @@ class Domains extends ApiCommand implements ResourceEntity
$mod_fcgid_maxrequests = '-1'; $mod_fcgid_maxrequests = '-1';
} }
if ($openbasedir_path > 2 && $openbasedir_path < 0) {
$openbasedir_path = 0;
}
// check non-ssl IP // check non-ssl IP
$ipandports = $this->validateIpAddresses($p_ipandports); $ipandports = $this->validateIpAddresses($p_ipandports);
// check ssl IP // 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 // 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') { 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); $selected_ips = $this->getIpsFromIdArray($ssl_ipandports);
if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) { if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true); Response::standardError('invaliddnsforletsencrypt', '', true);
@@ -696,6 +708,7 @@ class Domains extends ApiCommand implements ResourceEntity
'caneditdomain' => $caneditdomain, 'caneditdomain' => $caneditdomain,
'phpenabled' => $phpenabled, 'phpenabled' => $phpenabled,
'openbasedir' => $openbasedir, 'openbasedir' => $openbasedir,
'openbasedir_path' => $openbasedir_path,
'speciallogfile' => $speciallogfile, 'speciallogfile' => $speciallogfile,
'specialsettings' => $specialsettings, 'specialsettings' => $specialsettings,
'ssl_specialsettings' => $ssl_specialsettings, 'ssl_specialsettings' => $ssl_specialsettings,
@@ -749,6 +762,7 @@ class Domains extends ApiCommand implements ResourceEntity
`caneditdomain` = :caneditdomain, `caneditdomain` = :caneditdomain,
`phpenabled` = :phpenabled, `phpenabled` = :phpenabled,
`openbasedir` = :openbasedir, `openbasedir` = :openbasedir,
`openbasedir_path` = :openbasedir_path,
`speciallogfile` = :speciallogfile, `speciallogfile` = :speciallogfile,
`specialsettings` = :specialsettings, `specialsettings` = :specialsettings,
`ssl_specialsettings` = :ssl_specialsettings, `ssl_specialsettings` = :ssl_specialsettings,
@@ -1096,6 +1110,8 @@ class Domains extends ApiCommand implements ResourceEntity
* from setting system.apply_phpconfigs_default * from setting system.apply_phpconfigs_default
* @param bool $openbasedir * @param bool $openbasedir
* optional, whether to activate openbasedir restriction for this domain, default 0 (false) * 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 * @param int $phpsettingid
* optional, specify php-configuration that is being used by id, default 1 (system-default) * optional, specify php-configuration that is being used by id, default 1 (system-default)
* @param int $mod_fcgid_starter * @param int $mod_fcgid_starter
@@ -1193,6 +1209,7 @@ class Domains extends ApiCommand implements ResourceEntity
$phpenabled = $this->getBoolParam('phpenabled', true, $result['phpenabled']); $phpenabled = $this->getBoolParam('phpenabled', true, $result['phpenabled']);
$phpfs = $this->getBoolParam('phpsettingsforsubdomains', true, Settings::Get('system.apply_phpconfigs_default')); $phpfs = $this->getBoolParam('phpsettingsforsubdomains', true, Settings::Get('system.apply_phpconfigs_default'));
$openbasedir = $this->getBoolParam('openbasedir', true, $result['openbasedir']); $openbasedir = $this->getBoolParam('openbasedir', true, $result['openbasedir']);
$openbasedir_path = $this->getParam('openbasedir_path', true, $result['openbasedir_path']);
$phpsettingid = $this->getParam('phpsettingid', true, $result['phpsettingid']); $phpsettingid = $this->getParam('phpsettingid', true, $result['phpsettingid']);
$mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, $result['mod_fcgid_starter']); $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']); $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']; $adminid = $result['adminid'];
} }
$registration_date = Validate::validate($registration_date, 'registration_date', Validate::REGEX_YYYY_MM_DD, '', [ if (!is_null($registration_date)) {
'0000-00-00', $registration_date = Validate::validate($registration_date, 'registration_date',
'0', Validate::REGEX_YYYY_MM_DD, '', [
'' '0000-00-00',
], true); '0',
''
], true);
}
if ($registration_date == '0000-00-00' || empty($registration_date)) { if ($registration_date == '0000-00-00' || empty($registration_date)) {
$registration_date = null; $registration_date = null;
} }
$termination_date = Validate::validate($termination_date, 'termination_date', Validate::REGEX_YYYY_MM_DD, '', [ if (!is_null($termination_date)) {
'0000-00-00', $termination_date = Validate::validate($termination_date, 'termination_date',
'0', Validate::REGEX_YYYY_MM_DD, '', [
'' '0000-00-00',
], true); '0',
''
], true);
}
if ($termination_date == '0000-00-00' || empty($termination_date)) { if ($termination_date == '0000-00-00' || empty($termination_date)) {
$termination_date = null; $termination_date = null;
} }
@@ -1478,6 +1501,11 @@ class Domains extends ApiCommand implements ResourceEntity
$mod_fcgid_maxrequests = $result['mod_fcgid_maxrequests']; $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 // check non-ssl IP
$ipandports = $this->validateIpAddresses($p_ipandports, false, $result['id']); $ipandports = $this->validateIpAddresses($p_ipandports, false, $result['id']);
// check ssl IP // 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 // 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') { 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); $selected_ips = $this->getIpsFromIdArray($ssl_ipandports);
if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) { if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true); Response::standardError('invaliddnsforletsencrypt', '', true);
@@ -1623,7 +1651,31 @@ class Domains extends ApiCommand implements ResourceEntity
$wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0';
$iswildcarddomain = ($serveraliasoption == '0') ? '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); Cronjob::inserttask(TaskId::REBUILD_VHOST);
} }
@@ -1771,7 +1823,8 @@ class Domains extends ApiCommand implements ResourceEntity
$update_data['wwwserveralias'] = $wwwserveralias; $update_data['wwwserveralias'] = $wwwserveralias;
$update_data['iswildcarddomain'] = $iswildcarddomain; $update_data['iswildcarddomain'] = $iswildcarddomain;
$update_data['phpenabled'] = $phpenabled; $update_data['phpenabled'] = $phpenabled;
$update_data['openbasedir'] = $openbasedir; $update_data['openbasedir'] = $openbasedir;;
$update_data['openbasedir_path'] = $openbasedir_path;
$update_data['speciallogfile'] = $speciallogfile; $update_data['speciallogfile'] = $speciallogfile;
$update_data['phpsettingid'] = $phpsettingid; $update_data['phpsettingid'] = $phpsettingid;
$update_data['mod_fcgid_starter'] = $mod_fcgid_starter; $update_data['mod_fcgid_starter'] = $mod_fcgid_starter;
@@ -1819,6 +1872,7 @@ class Domains extends ApiCommand implements ResourceEntity
`iswildcarddomain` = :iswildcarddomain, `iswildcarddomain` = :iswildcarddomain,
`phpenabled` = :phpenabled, `phpenabled` = :phpenabled,
`openbasedir` = :openbasedir, `openbasedir` = :openbasedir,
`openbasedir_path` = :openbasedir_path,
`speciallogfile` = :speciallogfile, `speciallogfile` = :speciallogfile,
`phpsettingid` = :phpsettingid, `phpsettingid` = :phpsettingid,
`mod_fcgid_starter` = :mod_fcgid_starter, `mod_fcgid_starter` = :mod_fcgid_starter,
@@ -1854,6 +1908,7 @@ class Domains extends ApiCommand implements ResourceEntity
$_update_data['adminid'] = $adminid; $_update_data['adminid'] = $adminid;
$_update_data['phpenabled'] = $phpenabled; $_update_data['phpenabled'] = $phpenabled;
$_update_data['openbasedir'] = $openbasedir; $_update_data['openbasedir'] = $openbasedir;
$_update_data['openbasedir_path'] = $openbasedir_path;
$_update_data['mod_fcgid_starter'] = $mod_fcgid_starter; $_update_data['mod_fcgid_starter'] = $mod_fcgid_starter;
$_update_data['mod_fcgid_maxrequests'] = $mod_fcgid_maxrequests; $_update_data['mod_fcgid_maxrequests'] = $mod_fcgid_maxrequests;
$_update_data['notryfiles'] = $notryfiles; $_update_data['notryfiles'] = $notryfiles;
@@ -1887,6 +1942,7 @@ class Domains extends ApiCommand implements ResourceEntity
`adminid` = :adminid, `adminid` = :adminid,
`phpenabled` = :phpenabled, `phpenabled` = :phpenabled,
`openbasedir` = :openbasedir, `openbasedir` = :openbasedir,
`openbasedir_path` = :openbasedir_path,
`mod_fcgid_starter` = :mod_fcgid_starter, `mod_fcgid_starter` = :mod_fcgid_starter,
`mod_fcgid_maxrequests` = :mod_fcgid_maxrequests, `mod_fcgid_maxrequests` = :mod_fcgid_maxrequests,
`notryfiles` = :notryfiles, `notryfiles` = :notryfiles,
@@ -1903,6 +1959,18 @@ class Domains extends ApiCommand implements ResourceEntity
"); ");
Database::pexecute($_update_stmt, $_update_data, true, true); 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 // Cleanup domain <-> ip mapping
$del_stmt = Database::prepare(" $del_stmt = Database::prepare("
DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id 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 // Cleanup domain <-> ip mapping for subdomains
$domainidsresult_stmt = Database::prepare(" $domainidsresult_stmt = Database::prepare("
SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `parentdomainid` = :id 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) { if ($result['wwwserveralias'] != $wwwserveralias || $result['letsencrypt'] != $letsencrypt) {
// or when wwwserveralias or letsencrypt was changed // or when wwwserveralias or letsencrypt was changed
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger());
if ((int)$aliasdomain === 0) { if ((int)$aliasdomain === 0) {
// in case the wwwserveralias is set on a main domain, $aliasdomain is 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()); Domain::triggerLetsEncryptCSRForAliasDestinationDomain($id, $this->logger());
} else {
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger());
} }
} }
@@ -2138,7 +2211,9 @@ class Domains extends ApiCommand implements ResourceEntity
'domainid' => $id 'domainid' => $id
], true, true); ], true, true);
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger()); if ((int)$result['aliasdomain'] !== 0) {
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger());
}
// remove domains DNS from powerDNS if used, #581 // remove domains DNS from powerDNS if used, #581
Cronjob::inserttask(TaskId::DELETE_DOMAIN_PDNS, $result['domain']); Cronjob::inserttask(TaskId::DELETE_DOMAIN_PDNS, $result['domain']);

View File

@@ -63,7 +63,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
* @param string $alternative_email * @param string $alternative_email
* optional email address to send account information to, default is the account that is being created * optional email address to send account information to, default is the account that is being created
* @param int $email_quota * @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 * @param bool $sendinfomail
* optional, sends the welcome message to the new account (needed for creation, without the user won't * 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) * 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, ''); $emailaddr = $this->getParam('emailaddr', $ea_optional, '');
$email_password = $this->getParam('email_password'); $email_password = $this->getParam('email_password');
$alternative_email = $this->getParam('alternative_email', true, ''); $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); $sendinfomail = $this->getBoolParam('sendinfomail', true, 1);
// validation // validation

View File

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

View File

@@ -195,8 +195,8 @@ class Emails extends ApiCommand implements ResourceEntity
FROM `" . TABLE_MAIL_VIRTUAL . "` v FROM `" . TABLE_MAIL_VIRTUAL . "` v
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON v.`popaccountid` = u.`id` LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON v.`popaccountid` = u.`id`
WHERE v.`customerid` IN (" . implode(", ", $customer_ids) . ") 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); $result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) { 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_NOTICE, "[API] get email address '" . $result['email_full'] . "'");

View File

@@ -516,7 +516,7 @@ class MysqlServer extends ApiCommand implements ResourceEntity
`allowed_mysqlserver` = :am WHERE `customerid` = :cid `allowed_mysqlserver` = :am WHERE `customerid` = :cid
"); ");
while ($customer = $sel_stmt->fetch(PDO::FETCH_ASSOC)) { 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)) { if (!in_array($dbserver, $allowed_mysqls)) {
$allowed_mysqls[] = $dbserver; $allowed_mysqls[] = $dbserver;
$allowed_mysqls = json_encode($allowed_mysqls); $allowed_mysqls = json_encode($allowed_mysqls);

View File

@@ -73,12 +73,12 @@ class Mysqls extends ApiCommand implements ResourceEntity
$password = $this->getParam('mysql_password'); $password = $this->getParam('mysql_password');
// parameters // parameters
$dbserver = $this->getParam('mysql_server', true, 0);
$databasedescription = $this->getParam('description', true, ''); $databasedescription = $this->getParam('description', true, '');
$databasename = $this->getParam('custom_suffix', true, ''); $databasename = $this->getParam('custom_suffix', true, '');
$sendinfomail = $this->getBoolParam('sendinfomail', true, 0); $sendinfomail = $this->getBoolParam('sendinfomail', true, 0);
// get needed customer info to reduce the mysql-usage-counter by one // get needed customer info to reduce the mysql-usage-counter by one
$customer = $this->getCustomerData('mysqls'); $customer = $this->getCustomerData('mysqls');
$dbserver = $this->getParam('mysql_server', true, $this->getDefaultMySqlServer($customer));
// validation // validation
$password = Validate::validate($password, 'password', '', '', [], true); $password = Validate::validate($password, 'password', '', '', [], true);
@@ -90,7 +90,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
// validate whether the dbserver exists // validate whether the dbserver exists
$dbserver = Validate::validate($dbserver, html_entity_decode(lng('mysql.mysql_server')), '/^[0-9]+$/', '', 0, true); $dbserver = Validate::validate($dbserver, html_entity_decode(lng('mysql.mysql_server')), '/^[0-9]+$/', '', 0, true);
Database::needRoot(true, $dbserver); Database::needRoot(true, $dbserver, false);
Database::needSqlData(); Database::needSqlData();
$sql_root = Database::getSqlData(); $sql_root = Database::getSqlData();
Database::needRoot(false); Database::needRoot(false);
@@ -150,7 +150,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
$pma = Settings::Get('panel.phpmyadmin_url'); $pma = Settings::Get('panel.phpmyadmin_url');
} }
Database::needRoot(true, $dbserver); Database::needRoot(true, $dbserver, false);
Database::needSqlData(); Database::needSqlData();
$sql_root = Database::getSqlData(); $sql_root = Database::getSqlData();
Database::needRoot(false); Database::needRoot(false);
@@ -287,7 +287,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
} }
$result = Database::pexecute_first($result_stmt, $params, true, true); $result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) { if ($result) {
Database::needRoot(true, $result['dbserver']); Database::needRoot(true, $result['dbserver'], false);
$mbdata_stmt = Database::prepare(" $mbdata_stmt = Database::prepare("
SELECT SUM(data_length + index_length) as MB FROM information_schema.TABLES SELECT SUM(data_length + index_length) as MB FROM information_schema.TABLES
WHERE table_schema = :table_schema WHERE table_schema = :table_schema
@@ -364,7 +364,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
} }
// Begin root-session // Begin root-session
Database::needRoot(true, $result['dbserver']); Database::needRoot(true, $result['dbserver'], false);
$dbmgr = new DbManager($this->logger()); $dbmgr = new DbManager($this->logger());
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) { foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
$dbmgr->getManager()->grantPrivilegesTo($result['databasename'], $password, $mysql_access_host, false, true); $dbmgr->getManager()->grantPrivilegesTo($result['databasename'], $password, $mysql_access_host, false, true);
@@ -449,7 +449,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
'dbserver' => $_dbserver['dbserver'] 'dbserver' => $_dbserver['dbserver']
], $query_fields), true, true); ], $query_fields), true, true);
// Begin root-session // Begin root-session
Database::needRoot(true, $_dbserver['dbserver']); Database::needRoot(true, $_dbserver['dbserver'], false);
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$mbdata_stmt = Database::prepare(" $mbdata_stmt = Database::prepare("
SELECT SUM(data_length + index_length) as MB FROM information_schema.TABLES SELECT SUM(data_length + index_length) as MB FROM information_schema.TABLES
@@ -536,7 +536,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
$id = $result['id']; $id = $result['id'];
// Begin root-session // Begin root-session
Database::needRoot(true, $result['dbserver']); Database::needRoot(true, $result['dbserver'], false);
$dbm = new DbManager($this->logger()); $dbm = new DbManager($this->logger());
$dbm->getManager()->deleteDatabase($result['databasename']); $dbm->getManager()->deleteDatabase($result['databasename']);
Database::needRoot(false); Database::needRoot(false);
@@ -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'] . "'"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] deleted database '" . $result['databasename'] . "'");
return $this->response($result); 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

@@ -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 * optional, overwrites path value with an URL to generate a redirect, alternatively use the path
* parameter also for URLs * parameter also for URLs
* @param int $openbasedir_path * @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 * @param int $phpsettingid
* optional, php-settings-id, if empty the $domain value is used * optional, php-settings-id, if empty the $domain value is used
* @param int $redirectcode * @param int $redirectcode
@@ -104,7 +104,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
$aliasdomain = $this->getParam('alias', true, 0); $aliasdomain = $this->getParam('alias', true, 0);
$path = $this->getParam('path', true, ''); $path = $this->getParam('path', true, '');
$url = $this->getParam('url', 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); $phpsettingid = $this->getParam('phpsettingid', true, 0);
$redirectcode = $this->getParam('redirectcode', true, Settings::Get('customredirect.default')); $redirectcode = $this->getParam('redirectcode', true, Settings::Get('customredirect.default'));
$isemaildomain = $this->getParam('isemaildomain', true, 0); $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 // 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') { if ($letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
$our_ips = Domain::getIpsOfDomain($domain_check['id']); $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) { if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true); Response::standardError('invaliddnsforletsencrypt', '', true);
} }
@@ -701,10 +701,12 @@ class SubDomains extends ApiCommand implements ResourceEntity
$wwwserveralias = ($selectserveralias == '1') ? '1' : '0'; $wwwserveralias = ($selectserveralias == '1') ? '1' : '0';
// if allowed, check for 'is email domain'-flag // if allowed, check for 'is email domain'-flag
if ($result['parentdomainid'] != '0' && ($result['subcanemaildomain'] == '1' || $result['subcanemaildomain'] == '2') && $isemaildomain != $result['isemaildomain']) { if ($isemaildomain != $result['isemaildomain']) {
$isemaildomain = intval($isemaildomain); if ($result['parentdomainid'] != '0' && ($result['subcanemaildomain'] == '1' || $result['subcanemaildomain'] == '2')) {
} elseif ($result['parentdomainid'] != '0') { $isemaildomain = intval($isemaildomain);
$isemaildomain = $result['subcanemaildomain'] == '3' ? 1 : 0; } elseif ($result['parentdomainid'] != '0') {
$isemaildomain = $result['subcanemaildomain'] == '3' ? 1 : 0;
}
} }
// check changes of openbasedir-path variable // check changes of openbasedir-path variable
@@ -736,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 // 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') { if ($result['letsencrypt'] != $letsencrypt && $letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
$our_ips = Domain::getIpsOfDomain($result['parentdomainid']); $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) { if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true); Response::standardError('invaliddnsforletsencrypt', '', true);
} }
@@ -1131,7 +1133,9 @@ class SubDomains extends ApiCommand implements ResourceEntity
} }
} }
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger()); if ((int)$result['aliasdomain'] !== 0) {
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger());
}
// delete domain from table // delete domain from table
$stmt = Database::prepare(" $stmt = Database::prepare("

View File

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

View File

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

View File

@@ -62,6 +62,11 @@ final class MasterCron extends CliCommand
$result = self::SUCCESS; $result = self::SUCCESS;
$result = $this->validateRequirements($input, $output); $result = $this->validateRequirements($input, $output);
if ($result != self::SUCCESS) {
// requirements failed, exit
return $result;
}
$jobs = $input->getArgument('job'); $jobs = $input->getArgument('job');
// handle force option // handle force option
@@ -111,8 +116,8 @@ final class MasterCron extends CliCommand
]); ]);
$this->cronLog->setCronDebugFlag(defined('CRON_DEBUG_FLAG')); $this->cronLog->setCronDebugFlag(defined('CRON_DEBUG_FLAG'));
// check whether there are actual tasks to perform by 'tasks'-cron so // check whether there are actual tasks to perform by 'tasks'-cron, so
// we dont regenerate files unnecessarily // we don't regenerate files unnecessarily
$tasks_cnt_stmt = Database::query("SELECT COUNT(*) as jobcnt FROM `panel_tasks`"); $tasks_cnt_stmt = Database::query("SELECT COUNT(*) as jobcnt FROM `panel_tasks`");
$tasks_cnt = $tasks_cnt_stmt->fetch(PDO::FETCH_ASSOC); $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']) { if ($lasttype != '' && $lasttype != $_action['type']) {
$commands = trim($commands); $commands = trim($commands);
$numbrows = count(explode("\n", $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, 'commands' => $commands,
'numbrows' => $numbrows 'numbrows' => $numbrows
]); ]);
@@ -182,7 +182,7 @@ class ConfigDisplay
$commands = trim($commands_pre); $commands = trim($commands_pre);
if ($commands != "") { if ($commands != "") {
$numbrows = count(explode("\n", $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, 'commands' => $commands,
'numbrows' => $numbrows 'numbrows' => $numbrows
]); ]);
@@ -190,12 +190,12 @@ class ConfigDisplay
$commands = trim($commands_post); $commands = trim($commands_post);
if ($commands != "") { if ($commands != "") {
$numbrows = count(explode("\n", $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, 'commands' => $commands,
'numbrows' => $numbrows '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, 'realname' => $realname,
'commands_pre' => $commands_pre, 'commands_pre' => $commands_pre,
'commands_file' => $commands_file, 'commands_file' => $commands_file,
@@ -210,7 +210,7 @@ class ConfigDisplay
$commands = trim($commands); $commands = trim($commands);
if ($commands != '') { if ($commands != '') {
$numbrows = count(explode("\n", $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, 'commands' => $commands,
'numbrows' => $numbrows 'numbrows' => $numbrows
]); ]);
@@ -233,7 +233,7 @@ class ConfigDisplay
$file_content = htmlspecialchars($file_content); $file_content = htmlspecialchars($file_content);
$numbrows = count(explode("\n", $file_content)); $numbrows = count(explode("\n", $file_content));
//eval("\$files=\"" . \Froxlor\UI\Template::getTemplate("configfiles/configfiles_file") . "\";"); //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, 'distro_editor' => self::$editor,
'realname' => $realname, 'realname' => $realname,
'numbrows' => $numbrows, 'numbrows' => $numbrows,

View File

@@ -613,7 +613,10 @@ class Apache extends HttpConfigBase
// Apply header // Apply header
$this->virtualhosts_data[$vhosts_filename] = '# Domain ID: ' . $domain['id'] . ' - CustomerID: ' . $domain['customerid'] . ' - CustomerLogin: ' . $domain['loginname'] . "\n"; $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 // Create vhost without ssl
$this->virtualhosts_data[$vhosts_filename] .= $this->getVhostContent($domain, false); $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] = '# Domain ID: ' . $domain['id'] . ' (SSL) - CustomerID: ' . $domain['customerid'] . ' - CustomerLogin: ' . $domain['loginname'] . "\n";
$this->virtualhosts_data[$vhosts_filename_ssl] .= $this->getVhostContent($domain, true); $this->virtualhosts_data[$vhosts_filename_ssl] .= $this->getVhostContent($domain, true);
} }
} else {
$this->virtualhosts_data[$vhosts_filename] .= '# Customer deactivated and a docroot for deactivated users hasn\'t been set.' . "\n";
} }
} }
} }
@@ -840,29 +841,34 @@ class Apache extends HttpConfigBase
$domain['documentroot'] = trim($domain['documentroot']); $domain['documentroot'] = trim($domain['documentroot']);
if (preg_match('/^https?\:\/\//', $domain['documentroot'])) { if (preg_match('/^https?\:\/\//', $domain['documentroot'])) {
$corrected_docroot = $domain['documentroot']; $possible_deactivated_webroot = $this->getWebroot($domain);
if ($this->deactivated == false) {
$corrected_docroot = $domain['documentroot'];
// Get domain's redirect code // Get domain's redirect code
$code = Domain::getDomainRedirectCode($domain['id']); $code = Domain::getDomainRedirectCode($domain['id']);
$modrew_red = ''; $modrew_red = '';
if ($code != '') { if ($code != '') {
$modrew_red = ' [R=' . $code . ';L,NE]'; $modrew_red = ' [R=' . $code . ';L,NE]';
} }
// redirect everything, not only root-directory, #541 // redirect everything, not only root-directory, #541
$vhost_content .= ' <IfModule mod_rewrite.c>' . "\n"; $vhost_content .= ' <IfModule mod_rewrite.c>' . "\n";
$vhost_content .= ' RewriteEngine On' . "\n"; $vhost_content .= ' RewriteEngine On' . "\n";
if (!$ssl_vhost) { if (!$ssl_vhost) {
$vhost_content .= ' RewriteCond %{HTTPS} off' . "\n"; $vhost_content .= ' RewriteCond %{HTTPS} off' . "\n";
}
if ($domain['letsencrypt'] == '1') {
$vhost_content .= ' RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge' . "\n";
}
$vhost_content .= ' RewriteRule ^/(.*) ' . $corrected_docroot . '$1' . $modrew_red . "\n";
$vhost_content .= ' </IfModule>' . "\n";
$vhost_content .= ' <IfModule !mod_rewrite.c>' . "\n";
$vhost_content .= ' Redirect ' . $code . ' / ' . $domain['documentroot_norewrite'] . "\n";
$vhost_content .= ' </IfModule>' . "\n";
} elseif (Settings::Get('system.deactivateddocroot') != '') {
$vhost_content .= $possible_deactivated_webroot;
} }
if ($domain['letsencrypt'] == '1') {
$vhost_content .= ' RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge' . "\n";
}
$vhost_content .= ' RewriteRule ^/(.*) ' . $corrected_docroot . '$1' . $modrew_red . "\n";
$vhost_content .= ' </IfModule>' . "\n";
$vhost_content .= ' <IfModule !mod_rewrite.c>' . "\n";
$vhost_content .= ' Redirect ' . $code . ' / ' . $domain['documentroot_norewrite'] . "\n";
$vhost_content .= ' </IfModule>' . "\n";
} else { } else {
FileDir::mkDirWithCorrectOwnership($domain['customerroot'], $domain['documentroot'], $domain['guid'], $domain['guid'], true, true); FileDir::mkDirWithCorrectOwnership($domain['customerroot'], $domain['documentroot'], $domain['guid'], $domain['guid'], true, true);
$vhost_content .= $this->getWebroot($domain); $vhost_content .= $this->getWebroot($domain);
@@ -952,8 +958,8 @@ class Apache extends HttpConfigBase
$domain['customerroot'] = FileDir::makeCorrectDir($domain['customerroot']); $domain['customerroot'] = FileDir::makeCorrectDir($domain['customerroot']);
$domain['documentroot'] = FileDir::makeCorrectDir($domain['documentroot']); $domain['documentroot'] = FileDir::makeCorrectDir($domain['documentroot']);
if ($domain['deactivated'] == '1' && Settings::Get('system.deactivateddocroot') != '') { if (($domain['deactivated'] == '1' || $domain['customer_deactivated'] == '1') && Settings::Get('system.deactivateddocroot') != '') {
$webroot_text .= ' # Using docroot for deactivated users...' . "\n"; $webroot_text .= ' # Using docroot for deactivated users/domains...' . "\n";
$webroot_text .= ' DocumentRoot "' . rtrim(FileDir::makeCorrectDir(Settings::Get('system.deactivateddocroot')), "/") . "\"\n"; $webroot_text .= ' DocumentRoot "' . rtrim(FileDir::makeCorrectDir(Settings::Get('system.deactivateddocroot')), "/") . "\"\n";
$webroot_text .= ' <Directory "' . FileDir::makeCorrectDir(Settings::Get('system.deactivateddocroot')) . '">' . "\n"; $webroot_text .= ' <Directory "' . FileDir::makeCorrectDir(Settings::Get('system.deactivateddocroot')) . '">' . "\n";
// >=apache-2.4 enabled? // >=apache-2.4 enabled?
@@ -1034,6 +1040,10 @@ class Apache extends HttpConfigBase
$statTool = Settings::Get('system.traffictool'); $statTool = Settings::Get('system.traffictool');
$statDomain = ""; $statDomain = "";
if ($statTool == 'awstats') {
// awstats generates for each domain regardless of speciallogfile
$statDomain = "/" . $domain['domain'];
}
if ($domain['speciallogfile'] == '1') { if ($domain['speciallogfile'] == '1') {
$statDomain = "/" . (($domain['parentdomainid'] == '0') ? $domain['domain'] : $domain['parentdomain']); $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 // mod_proxy stuff for apache-2.4
if (Settings::Get('system.apache24') == '1' && Settings::Get('phpfpm.use_mod_proxy') == '1') { 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']; $filesmatch = $phpconfig['fpm_settings']['limit_extensions'];
$extensions = explode(" ", $filesmatch); $extensions = explode(" ", $filesmatch);
$filesmatch = ""; $filesmatch = "";
@@ -141,23 +144,19 @@ class ApacheFcgi extends Apache
$php_options_text .= ' </FilesMatch>' . "\n"; $php_options_text .= ' </FilesMatch>' . "\n";
$mypath_dir = new Directory($domain['documentroot']); $mypath_dir = new Directory($domain['documentroot']);
// only create the "require all granted" directive if there is no active directory-protection
// only create the require all granted if there is not active directory-protection
// for this path, as this would be the first require and therefore grant all access // for this path, as this would be the first require and therefore grant all access
if ($mypath_dir->isUserProtected() == false) { if ($mypath_dir->isUserProtected() == false) {
$php_options_text .= ' <Directory "' . FileDir::makeCorrectDir($domain['documentroot']) . '">' . "\n";
if ($phpconfig['pass_authorizationheader'] == '1') { if ($phpconfig['pass_authorizationheader'] == '1') {
$php_options_text .= ' CGIPassAuth On' . "\n"; $php_options_text .= ' CGIPassAuth On' . "\n";
} }
$php_options_text .= ' Require all granted' . "\n"; $php_options_text .= ' Require all granted' . "\n";
$php_options_text .= ' AllowOverride All' . "\n"; $php_options_text .= ' AllowOverride All' . "\n";
$php_options_text .= ' </Directory>' . "\n";
} elseif ($phpconfig['pass_authorizationheader'] == '1') { } elseif ($phpconfig['pass_authorizationheader'] == '1') {
// allow Pass of Authorization header // allow Pass of Authorization header
$php_options_text .= ' <Directory "' . FileDir::makeCorrectDir($domain['documentroot']) . '">' . "\n";
$php_options_text .= ' CGIPassAuth On' . "\n"; $php_options_text .= ' CGIPassAuth On' . "\n";
$php_options_text .= ' </Directory>' . "\n";
} }
$php_options_text .= ' </Directory>' . "\n";
} else { } else {
$addheader = ""; $addheader = "";
if ($phpconfig['pass_authorizationheader'] == '1') { if ($phpconfig['pass_authorizationheader'] == '1') {
@@ -196,6 +195,9 @@ class ApacheFcgi extends Apache
} }
} else { } else {
$php_options_text .= ' FcgidIdleTimeout ' . Settings::Get('system.mod_fcgid_idle_timeout') . "\n"; $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) { if ((int)Settings::Get('system.mod_fcgid_wrapper') == 0) {
$php_options_text .= ' SuexecUserGroup "' . $domain['loginname'] . '" "' . $domain['loginname'] . '"' . "\n"; $php_options_text .= ' SuexecUserGroup "' . $domain['loginname'] . '" "' . $domain['loginname'] . '"' . "\n";
$php_options_text .= ' ScriptAlias /php/ ' . $php->getInterface()->getConfigDir() . "\n"; $php_options_text .= ' ScriptAlias /php/ ' . $php->getInterface()->getConfigDir() . "\n";

View File

@@ -179,7 +179,7 @@ class HttpConfigBase
$froxlor_ssl_settings_stmt = Database::prepare(" $froxlor_ssl_settings_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`
WHERE `domainid` = '0' AND 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); $froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt);
if ($froxlor_ssl && !empty($froxlor_ssl['ssl_cert_file'])) { if ($froxlor_ssl && !empty($froxlor_ssl['ssl_cert_file'])) {

View File

@@ -46,7 +46,9 @@ class AcmeSh extends FroxlorCron
'letsencrypt_test' => "https://acme-staging-v02.api.letsencrypt.org/directory", 'letsencrypt_test' => "https://acme-staging-v02.api.letsencrypt.org/directory",
'buypass' => "https://api.buypass.com/acme/directory", 'buypass' => "https://api.buypass.com/acme/directory",
'buypass_test' => "https://api.test4.buypass.no/acme/directory", 'buypass_test' => "https://api.test4.buypass.no/acme/directory",
'zerossl' => "https://acme.zerossl.com/v2/DV90" 'zerossl' => "https://acme.zerossl.com/v2/DV90",
'google' => "https://dv.acme-v02.api.pki.goog/directory",
'google_test' => "https://dv.acme-v02.test-api.pki.goog/directory",
]; ];
public static $no_inserttask = false; public static $no_inserttask = false;
private static $apiserver = ""; private static $apiserver = "";
@@ -112,7 +114,9 @@ class AcmeSh extends FroxlorCron
`ssl_cert_chainfile` = :chain, `ssl_cert_chainfile` = :chain,
`ssl_csr_file` = :csr, `ssl_csr_file` = :csr,
`ssl_fullchain_file` = :fullchain, `ssl_fullchain_file` = :fullchain,
`expirationdate` = :expirationdate `validfromdate` = :validfromdate,
`validtodate` = :validtodate,
`issuer` = :issuer
"); ");
// prepare domain update sql // prepare domain update sql
@@ -134,7 +138,9 @@ class AcmeSh extends FroxlorCron
'lepublickey' => Settings::Get('system.lepublickey'), 'lepublickey' => Settings::Get('system.lepublickey'),
'leregistered' => Settings::Get('system.leregistered'), 'leregistered' => Settings::Get('system.leregistered'),
'ssl_redirect' => Settings::Get('system.le_froxlor_redirect'), 'ssl_redirect' => Settings::Get('system.le_froxlor_redirect'),
'expirationdate' => null, 'validfromdate' => null,
'validtodate' => null,
'issuer' => "",
'ssl_cert_file' => null, 'ssl_cert_file' => null,
'ssl_key_file' => null, 'ssl_key_file' => null,
'ssl_ca_file' => null, 'ssl_ca_file' => null,
@@ -169,7 +175,9 @@ class AcmeSh extends FroxlorCron
'lepublickey' => Settings::Get('system.lepublickey'), 'lepublickey' => Settings::Get('system.lepublickey'),
'leregistered' => Settings::Get('system.leregistered'), 'leregistered' => Settings::Get('system.leregistered'),
'ssl_redirect' => Settings::Get('system.le_froxlor_redirect'), '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_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_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, 'ssl_ca_file' => is_array($renew_froxlor) ? $renew_froxlor['ssl_ca_file'] : null,
@@ -185,7 +193,7 @@ class AcmeSh extends FroxlorCron
'loginname' => $domain['loginname'], 'loginname' => $domain['loginname'],
'adminsession' => 0 '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, []); self::certToDb($domain, $cronlog, []);
$changedetected = 1; $changedetected = 1;
} }
@@ -219,7 +227,9 @@ class AcmeSh extends FroxlorCron
"); ");
$froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt); $froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt);
// also check for possible existing certificate // 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; return true;
} }
} }
@@ -277,7 +287,9 @@ EOC;
SELECT SELECT
domssl.`id`, domssl.`id`,
domssl.`domainid`, domssl.`domainid`,
domssl.`expirationdate`, domssl.`validfromdate`,
domssl.`validtodate`,
domssl.`issuer`,
domssl.`ssl_cert_file`, domssl.`ssl_cert_file`,
domssl.`ssl_key_file`, domssl.`ssl_key_file`,
domssl.`ssl_ca_file`, domssl.`ssl_ca_file`,
@@ -304,7 +316,7 @@ EOC;
AND dom.`letsencrypt` = 1 AND dom.`letsencrypt` = 1
AND dom.`aliasdomain` IS NULL AND dom.`aliasdomain` IS NULL
AND dom.`iswildcarddomain` = 0 AND dom.`iswildcarddomain` = 0
AND domssl.`expirationdate` IS NULL AND domssl.`validtodate` IS NULL
"); ");
$customer_ssl = $certificates_stmt->fetchAll(PDO::FETCH_ASSOC); $customer_ssl = $certificates_stmt->fetchAll(PDO::FETCH_ASSOC);
if ($customer_ssl) { if ($customer_ssl) {
@@ -328,7 +340,7 @@ EOC;
"); ");
$froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt); $froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt);
// also check for possible existing certificate // 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; return $froxlor_ssl;
} }
} }
@@ -344,7 +356,9 @@ EOC;
SELECT SELECT
domssl.`id`, domssl.`id`,
domssl.`domainid`, domssl.`domainid`,
domssl.`expirationdate`, domssl.`validfromdate`,
domssl.`validtodate`,
domssl.`issuer`,
domssl.`ssl_cert_file`, domssl.`ssl_cert_file`,
domssl.`ssl_key_file`, domssl.`ssl_key_file`,
dom.`domain`, dom.`domain`,
@@ -368,7 +382,7 @@ EOC;
if ($renew_certs) { if ($renew_certs) {
if ($check) { if ($check) {
foreach ($renew_certs as $cert) { foreach ($renew_certs as $cert) {
if (self::checkFsFilesAreNewer($cert['domain'], $cert['expirationdate'])) { if (self::checkFsFilesAreNewer($cert['domain'], $cert['validtodate'])) {
return true; return true;
} }
} }
@@ -451,7 +465,7 @@ EOC;
// Only issue let's encrypt certificate if no broken ssl_redirect is enabled // Only issue let's encrypt certificate if no broken ssl_redirect is enabled
if ($certrow['ssl_redirect'] != 2) { if ($certrow['ssl_redirect'] != 2) {
$do_force = false; $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) // domain changed (SAN or similar)
$do_force = true; $do_force = true;
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Re-creating certificate for " . $certrow['domain']); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Re-creating certificate for " . $certrow['domain']);
@@ -519,7 +533,7 @@ EOC;
foreach ($loop_domains as $idx => $domain) { foreach ($loop_domains as $idx => $domain) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Validating DNS of " . $domain); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Validating DNS of " . $domain);
// ips according to NS // 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) { if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
// no common ips... // 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"); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Skipping Let's Encrypt generation for " . $domain . " due to no system known IP address via DNS check");
@@ -555,7 +569,7 @@ EOC;
if (Settings::Get('system.letsencryptreuseold') != '1') { if (Settings::Get('system.letsencryptreuseold') != '1') {
$acmesh_cmd .= " --always-force-new-domain-key"; $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"; $acmesh_cmd .= " --staging";
} }
if ($force) { if ($force) {
@@ -592,7 +606,9 @@ EOC;
'chain' => $return['chain'], 'chain' => $return['chain'],
'csr' => $return['csr'], 'csr' => $return['csr'],
'fullchain' => $return['fullchain'], '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) { if ($certrow['ssl_redirect'] == 3) {

View File

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

View File

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

View File

@@ -342,8 +342,17 @@ pm.max_children = 1
public function getSocketFile($createifnotexists = true) public function getSocketFile($createifnotexists = true)
{ {
$socketdir = FileDir::makeCorrectDir(Settings::Get('phpfpm.fastcgi_ipcdir')); $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 // 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')); $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) { if (!is_dir($socketdir) && $createifnotexists) {
FileDir::safe_exec('mkdir -p ' . escapeshellarg($socketdir)); FileDir::safe_exec('mkdir -p ' . escapeshellarg($socketdir));

View File

@@ -43,7 +43,7 @@ class WebserverBase
{ {
$query = "SELECT `d`.*, `pd`.`domain` AS `parentdomain`, `c`.`loginname`, $query = "SELECT `d`.*, `pd`.`domain` AS `parentdomain`, `c`.`loginname`,
`d`.`phpsettingid`, `c`.`adminid`, `c`.`guid`, `c`.`email`, `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`, `c`.`phpenabled` AS `phpenabled_customer`,
`d`.`phpenabled` AS `phpenabled_vhost` `d`.`phpenabled` AS `phpenabled_vhost`
FROM `" . TABLE_PANEL_DOMAINS . "` `d` FROM `" . TABLE_PANEL_DOMAINS . "` `d`

View File

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

View File

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

View File

@@ -78,7 +78,7 @@ class TrafficCron extends FroxlorCron
// Fork failed // Fork failed
return 1; return 1;
} }
} else if (!defined('CRON_NOFORK_FLAG')) { } elseif (!defined('CRON_NOFORK_FLAG')) {
if (extension_loaded('pcntl')) { if (extension_loaded('pcntl')) {
$msg = "PHP compiled with pcntl but pcntl_fork function is not available."; $msg = "PHP compiled with pcntl but pcntl_fork function is not available.";
} else { } else {
@@ -127,7 +127,7 @@ class TrafficCron extends FroxlorCron
while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) { while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) {
if ($last_dbserver != $row_database['dbserver']) { if ($last_dbserver != $row_database['dbserver']) {
Database::needRoot(true, $row_database['dbserver']); Database::needRoot(true, $row_database['dbserver'], true);
$last_dbserver = $row_database['dbserver']; $last_dbserver = $row_database['dbserver'];
$databases_list = []; $databases_list = [];
@@ -406,7 +406,7 @@ class TrafficCron extends FroxlorCron
} else { } else {
// Use the old fashioned way with "du" // Use the old fashioned way with "du"
if (file_exists($row['documentroot']) && is_dir($row['documentroot'])) { 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) { foreach ($back as $backrow) {
$webspaceusage = explode(' ', $backrow); $webspaceusage = explode(' ', $backrow);
} }
@@ -426,7 +426,7 @@ class TrafficCron extends FroxlorCron
$maildir = FileDir::makeCorrectDir(Settings::Get('system.vmail_homedir') . $row['loginname']); $maildir = FileDir::makeCorrectDir(Settings::Get('system.vmail_homedir') . $row['loginname']);
if (file_exists($maildir) && is_dir($maildir)) { 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) { foreach ($back as $backrow) {
$emailusage = explode(' ', $backrow); $emailusage = explode(' ', $backrow);
} }
@@ -627,7 +627,7 @@ class TrafficCron extends FroxlorCron
* @param string $caption Caption for webalizer output * @param string $caption Caption for webalizer output
* @param array $monthyear_arr * @param array $monthyear_arr
* @param int $current_stamp * @param int $current_stamp
* *
* @return int Used traffic * @return int Used traffic
*/ */
private static function callGoaccessGetTraffic($customerid, $logfile, $outputdir, $caption, array $monthyear_arr = [], int $current_stamp = 0) private static function callGoaccessGetTraffic($customerid, $logfile, $outputdir, $caption, array $monthyear_arr = [], int $current_stamp = 0)
@@ -705,7 +705,7 @@ class TrafficCron extends FroxlorCron
* @param string $outputdir Place where stats should be build * @param string $outputdir Place where stats should be build
* @param string $caption Caption for webalizer output * @param string $caption Caption for webalizer output
* @param array $usersdomainlist * @param array $usersdomainlist
* *
* @return float Used traffic * @return float Used traffic
*/ */
private static function callWebalizerGetTraffic($logfile, $outputdir, $caption, array $usersdomainlist = []) private static function callWebalizerGetTraffic($logfile, $outputdir, $caption, array $usersdomainlist = [])

View File

@@ -63,9 +63,10 @@ class CurrentUser
/** /**
* re-read in the user data if a valid session exists * 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; $table = self::isAdmin() ? TABLE_PANEL_ADMINS : TABLE_PANEL_CUSTOMERS;
$userinfo_stmt = Database::prepare(" $userinfo_stmt = Database::prepare("
@@ -75,7 +76,7 @@ class CurrentUser
"loginname" => self::getField('loginname') "loginname" => self::getField('loginname')
]); ]);
if ($userinfo) { 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, // 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() // other than the union-operator (+) which would keep the values already existing from getData()
$newuserinfo = array_merge(self::getData(), $userinfo); $newuserinfo = array_merge(self::getData(), $userinfo);
@@ -107,7 +108,7 @@ class CurrentUser
*/ */
public static function getField(string $index) 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; $_SESSION['userinfo'] = $data;
} }
/**
* @param string $resource
* @return bool
* @throws \Exception
*/
public static function canAddResource(string $resource): bool public static function canAddResource(string $resource): bool
{ {
$addition = true; $addition = true;
@@ -145,14 +151,15 @@ class CurrentUser
]); ]);
$addition = $result['emaildomains'] != 0; $addition = $result['emaildomains'] != 0;
} elseif ($resource == 'subdomains') { } 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; $addition = $parentDomainCollection != 0;
} elseif ($resource == 'domains') { } elseif ($resource == 'domains') {
$customerCollection = (new Collection(Customers::class, $_SESSION['userinfo'])); $customerCollection = (new Collection(Customers::class, $_SESSION['userinfo']));
$addition = $customerCollection != 0; $addition = $customerCollection != 0;
} }
return ($_SESSION['userinfo'][$resource.'_used'] < $_SESSION['userinfo'][$resource] || $_SESSION['userinfo'][$resource] == '-1') && $addition; return ($_SESSION['userinfo'][$resource . '_used'] < $_SESSION['userinfo'][$resource] || $_SESSION['userinfo'][$resource] == '-1') && $addition;
} }
} }

View File

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

View File

@@ -28,6 +28,7 @@ namespace Froxlor\Database;
use Exception; use Exception;
use Froxlor\FileDir; use Froxlor\FileDir;
use Froxlor\Froxlor; use Froxlor\Froxlor;
use Froxlor\PhpHelper;
use Froxlor\Settings; use Froxlor\Settings;
use Froxlor\UI\Panel\UI; use Froxlor\UI\Panel\UI;
use PDO; use PDO;
@@ -60,39 +61,43 @@ class Database
/** /**
* indicator whether to use root-connection or not * 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) * indicator which database-server we're on (not really used)
*/ */
private static $dbserver = 0; private static int $dbserver = 0;
/** /**
* used database-name * used database-name
*/ */
private static $dbname = null; private static ?string $dbname = null;
/** /**
* sql-access data * sql-access data
*/ */
private static $needsqldata = false; private static bool $needsqldata = false;
private static $sqldata = null; private static $sqldata = null;
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 * and display the error nicely on the panel - also fetches the
* result from the statement and returns the resulting array * result from the statement and returns the resulting array
* *
* @param PDOStatement $stmt * @param PDOStatement $stmt
* @param array $params * @param array|null $params
* (optional) * (optional)
* @param bool $showerror * @param bool $showerror
* suppress errordisplay (default true) * suppress error display (default true)
* @param bool $json_response
* *
* @return array * @return mixed
* @throws Exception
*/ */
public static function pexecute_first(&$stmt, $params = null, $showerror = true, $json_response = false) public static function pexecute_first(PDOStatement &$stmt, $params = null, bool $showerror = true, bool $json_response = false)
{ {
self::pexecute($stmt, $params, $showerror, $json_response); self::pexecute($stmt, $params, $showerror, $json_response);
return $stmt->fetch(PDO::FETCH_ASSOC); return $stmt->fetch(PDO::FETCH_ASSOC);
@@ -103,12 +108,15 @@ class Database
* and display the error nicely on the panel * and display the error nicely on the panel
* *
* @param PDOStatement $stmt * @param PDOStatement $stmt
* @param array $params * @param array|null $params
* (optional) * (optional)
* @param bool $showerror * @param bool $showerror
* suppress errordisplay (default true) * suppress error display (default true)
* @param bool $json_response
*
* @throws Exception
*/ */
public static function pexecute(&$stmt, $params = null, $showerror = true, $json_response = false) public static function pexecute(PDOStatement &$stmt, $params = null, bool $showerror = true, bool $json_response = false)
{ {
try { try {
$stmt->execute($params); $stmt->execute($params);
@@ -122,9 +130,10 @@ class Database
* *
* @param PDOException $error * @param PDOException $error
* @param bool $showerror * @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; global $userinfo, $theme, $linker;
@@ -135,22 +144,30 @@ class Database
require Froxlor::getInstallDir() . "/lib/userdata.inc.php"; require Froxlor::getInstallDir() . "/lib/userdata.inc.php";
// le format // le format
if (isset($sql['root_user']) && isset($sql['root_password']) && !is_array($sql_root)) { if (isset($sql['root_user']) && isset($sql['root_password']) && empty($sql_root)) {
$sql_root = [ $sql_root = [
0 => [ 0 => [
'caption' => 'Default', 'caption' => 'Default',
'host' => $sql['host'], 'host' => $sql['host'],
'socket' => (isset($sql['socket']) ? $sql['socket'] : null), 'socket' => ($sql['socket'] ?? null),
'user' => $sql['root_user'], 'user' => $sql['root_user'],
'password' => $sql['root_password'] 'password' => $sql['root_password']
] ]
]; ];
unset($sql['root_user']);
unset($sql['root_password']);
// write new layout so this won't happen again
self::generateNewUserData($sql, $sql_root);
// re-read file
require Froxlor::getInstallDir() . "/lib/userdata.inc.php";
} }
$substitutions = [ $substitutions = [
$sql['password'] => 'DB_UNPRIV_PWD', $sql['password'] => 'DB_UNPRIV_PWD',
$sql_root[0]['password'] => 'DB_ROOT_PWD'
]; ];
foreach ($sql_root as $sql_root_data) {
$substitutions[$sql_root_data['password']] = 'DB_ROOT_PWD';
}
// hide username/password in messages // hide username/password in messages
$error_message = $error->getMessage(); $error_message = $error->getMessage();
@@ -243,7 +260,7 @@ class Database
* @param int $minLength * @param int $minLength
* @return string * @return string
*/ */
private static function substitute($content, array $substitutions, $minLength = 6) private static function substitute(string $content, array $substitutions, int $minLength = 6): string
{ {
$replacements = []; $replacements = [];
@@ -251,9 +268,7 @@ class Database
$replacements += self::createShiftedSubstitutions($search, $replace, $minLength); $replacements += self::createShiftedSubstitutions($search, $replace, $minLength);
} }
$content = str_replace(array_keys($replacements), array_values($replacements), $content); return str_replace(array_keys($replacements), array_values($replacements), $content);
return $content;
} }
/** /**
@@ -273,7 +288,7 @@ class Database
* @param int $minLength * @param int $minLength
* @return array * @return array
*/ */
private static function createShiftedSubstitutions($search, $replace, $minLength) private static function createShiftedSubstitutions(string $search, string $replace, int $minLength): array
{ {
$substitutions = []; $substitutions = [];
$length = strlen($search); $length = strlen($search);
@@ -292,8 +307,9 @@ class Database
* *
* @param int $length * @param int $length
* @return string * @return string
* @throws Exception
*/ */
private static function genUniqueToken(int $length = 16) private static function genUniqueToken(int $length = 16): string
{ {
if (intval($length) <= 8) { if (intval($length) <= 8) {
$length = 16; $length = 16;
@@ -316,7 +332,7 @@ class Database
* *
* @return int * @return int
*/ */
public static function num_rows() public static function num_rows(): int
{ {
return Database::query("SELECT FOUND_ROWS()")->fetchColumn(); return Database::query("SELECT FOUND_ROWS()")->fetchColumn();
} }
@@ -326,7 +342,7 @@ class Database
* *
* @return string * @return string
*/ */
public static function getDbName() public static function getDbName(): ?string
{ {
return self::$dbname; return self::$dbname;
} }
@@ -338,15 +354,16 @@ class Database
* the 'normal' database-connection * the 'normal' database-connection
* *
* @param bool $needroot * @param bool $needroot
* @param int $dbserver * @param int $dbserver optional
* optional * @param bool $need_db
*/ */
public static function needRoot($needroot = false, $dbserver = 0) public static function needRoot(bool $needroot = false, int $dbserver = 0, bool $need_db = true)
{ {
// force re-connecting to the db with corresponding user // force re-connecting to the db with corresponding user
// and set the $dbserver (mostly to 0 = default) // and set the $dbserver (mostly to 0 = default)
self::setServer($dbserver); self::setServer($dbserver);
self::$needroot = $needroot; self::$needroot = $needroot;
self::$need_dbname = $need_db;
} }
/** /**
@@ -354,7 +371,7 @@ class Database
* *
* @param int $dbserver * @param int $dbserver
*/ */
private static function setServer($dbserver = 0) private static function setServer(int $dbserver = 0)
{ {
self::$dbserver = $dbserver; self::$dbserver = $dbserver;
self::$link = null; self::$link = null;
@@ -385,17 +402,16 @@ class Database
* function that will be called on every static call * function that will be called on every static call
* which connects to the database if necessary * which connects to the database if necessary
* *
* @param bool $root
*
* @return object * @return object
* @throws Exception
*/ */
private static function getDB() 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")); 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) { if (self::$link) {
// return it // return it
return self::$link; return self::$link;
@@ -405,18 +421,22 @@ class Database
require Froxlor::getInstallDir() . "/lib/userdata.inc.php"; require Froxlor::getInstallDir() . "/lib/userdata.inc.php";
// le format // le format
if (self::$needroot == true && isset($sql['root_user']) && isset($sql['root_password']) && (!isset($sql_root) || !is_array($sql_root))) { if (isset($sql['root_user']) && isset($sql['root_password']) && (!isset($sql_root) || !is_array($sql_root))) {
$sql_root = [ $sql_root = [
0 => [ 0 => [
'caption' => 'Default', 'caption' => 'Default',
'host' => $sql['host'], 'host' => $sql['host'],
'socket' => (isset($sql['socket']) ? $sql['socket'] : null), 'socket' => ($sql['socket'] ?? null),
'user' => $sql['root_user'], 'user' => $sql['root_user'],
'password' => $sql['root_password'] 'password' => $sql['root_password']
] ]
]; ];
unset($sql['root_user']); unset($sql['root_user']);
unset($sql['root_password']); unset($sql['root_password']);
// write new layout so this won't happen again
self::generateNewUserData($sql, $sql_root);
// re-read file
require Froxlor::getInstallDir() . "/lib/userdata.inc.php";
} }
// either root or unprivileged user // either root or unprivileged user
@@ -425,8 +445,8 @@ class Database
$user = $sql_root[self::$dbserver]['user']; $user = $sql_root[self::$dbserver]['user'];
$password = $sql_root[self::$dbserver]['password']; $password = $sql_root[self::$dbserver]['password'];
$host = $sql_root[self::$dbserver]['host']; $host = $sql_root[self::$dbserver]['host'];
$socket = isset($sql_root[self::$dbserver]['socket']) ? $sql_root[self::$dbserver]['socket'] : null; $socket = $sql_root[self::$dbserver]['socket'] ?? null;
$port = isset($sql_root[self::$dbserver]['port']) ? $sql_root[self::$dbserver]['port'] : '3306'; $port = $sql_root[self::$dbserver]['port'] ?? '3306';
$sslCAFile = $sql_root[self::$dbserver]['ssl']['caFile'] ?? ""; $sslCAFile = $sql_root[self::$dbserver]['ssl']['caFile'] ?? "";
$sslVerifyServerCertificate = $sql_root[self::$dbserver]['ssl']['verifyServerCertificate'] ?? false; $sslVerifyServerCertificate = $sql_root[self::$dbserver]['ssl']['verifyServerCertificate'] ?? false;
} else { } else {
@@ -434,8 +454,8 @@ class Database
$user = $sql["user"]; $user = $sql["user"];
$password = $sql["password"]; $password = $sql["password"];
$host = $sql["host"]; $host = $sql["host"];
$socket = isset($sql['socket']) ? $sql['socket'] : null; $socket = $sql['socket'] ?? null;
$port = isset($sql['port']) ? $sql['port'] : '3306'; $port = $sql['port'] ?? '3306';
$sslCAFile = $sql['ssl']['caFile'] ?? ""; $sslCAFile = $sql['ssl']['caFile'] ?? "";
$sslVerifyServerCertificate = $sql['ssl']['verifyServerCertificate'] ?? false; $sslVerifyServerCertificate = $sql['ssl']['verifyServerCertificate'] ?? false;
} }
@@ -465,10 +485,11 @@ class Database
'ATTR_ERRMODE' => 'ERRMODE_EXCEPTION' 'ATTR_ERRMODE' => 'ERRMODE_EXCEPTION'
]; ];
$dbconf["dsn"] = [ $dbconf["dsn"] = ['charset' => 'utf8'];
'dbname' => $sql["db"],
'charset' => 'utf8' if (self::$need_dbname) {
]; $dbconf["dsn"]['dbname'] = $sql["db"];
}
if ($socket != null) { if ($socket != null) {
$dbconf["dsn"]['unix_socket'] = FileDir::makeCorrectFile($socket); $dbconf["dsn"]['unix_socket'] = FileDir::makeCorrectFile($socket);
@@ -539,14 +560,14 @@ class Database
* *
* @return int * @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 // database names, we set the limit to 64 here
if (strpos(strtolower(Database::getAttribute(\PDO::ATTR_SERVER_VERSION)), "mariadb") !== false) { if (strpos(strtolower(Database::getAttribute(\PDO::ATTR_SERVER_VERSION)), "mariadb") !== false) {
$mysql_max = 64; $mysql_max = 64;
} else { } 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; $mysql_max = 32;
if (version_compare(Database::getAttribute(\PDO::ATTR_SERVER_VERSION), '5.7.8', '<')) { if (version_compare(Database::getAttribute(\PDO::ATTR_SERVER_VERSION), '5.7.8', '<')) {
$mysql_max = 16; $mysql_max = 16;
@@ -556,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()" * call like "Database::function()"
* *
* @param string $name * @param string $name
* @param mixed $args * @param mixed $args
* *
* @return mixed * @return mixed
* @throws Exception
*/ */
public static function __callStatic($name, $args) public static function __callStatic(string $name, $args)
{ {
$callback = [ $callback = [
self::getDB(), self::getDB(),
@@ -578,4 +600,22 @@ class Database
} }
return $result; return $result;
} }
/**
* write new userdata.inc.php file
*/
private static function generateNewUserData(array $sql, array $sql_root)
{
$content = PhpHelper::parseArrayToPhpFile(
['sql' => $sql, 'sql_root' => $sql_root],
'automatically generated userdata.inc.php for froxlor'
);
chmod(Froxlor::getInstallDir() . "/lib/userdata.inc.php", 0700);
file_put_contents(Froxlor::getInstallDir() . "/lib/userdata.inc.php", $content);
chmod(Froxlor::getInstallDir() . "/lib/userdata.inc.php", 0400);
clearstatcache();
if (function_exists('opcache_invalidate')) {
@opcache_invalidate(Froxlor::getInstallDir() . "/lib/userdata.inc.php", true);
}
}
} }

View File

@@ -77,7 +77,15 @@ class DbManager
$this->manager = new DbManagerMySQL($this->log); $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 // get all databases for all dbservers
$databases = []; $databases = [];
@@ -96,12 +104,12 @@ class DbManager
$dbservers_stmt = Database::query("SELECT DISTINCT `dbserver` FROM `" . TABLE_PANEL_DATABASES . "`"); $dbservers_stmt = Database::query("SELECT DISTINCT `dbserver` FROM `" . TABLE_PANEL_DATABASES . "`");
while ($dbserver = $dbservers_stmt->fetch(PDO::FETCH_ASSOC)) { while ($dbserver = $dbservers_stmt->fetch(PDO::FETCH_ASSOC)) {
// require privileged access for target db-server // require privileged access for target db-server
Database::needRoot(true, $dbserver['dbserver']); Database::needRoot(true, $dbserver['dbserver'], false);
$dbm = new DbManager(FroxlorLogger::getInstanceOf()); $dbm = new DbManager(FroxlorLogger::getInstanceOf());
$users = $dbm->getManager()->getAllSqlUsers(false); $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'])) { if (isset($users[$username]) && is_array($users[$username]) && isset($users[$username]['hosts']) && is_array($users[$username]['hosts'])) {
$password = [ $password = [
@@ -136,15 +144,16 @@ class DbManager
* DB-name and user-name are being generated and * DB-name and user-name are being generated and
* the password for the user will be set * the password for the user will be set
* *
* @param string $loginname * @param ?string $loginname
* @param string $password * @param ?string $password
* @param int $dbserver
* @param int $last_accnumber * @param int $last_accnumber
* *
* @return string|bool $username if successful or false of username is equal to the password * @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); Database::needRoot(true, $dbserver, false);
// check whether we shall create a random username // check whether we shall create a random username
if (strtoupper(Settings::Get('customer.mysqlprefix')) == 'RANDOM') { if (strtoupper(Settings::Get('customer.mysqlprefix')) == 'RANDOM') {
@@ -169,18 +178,17 @@ class DbManager
// now create the database itself // now create the database itself
$this->getManager()->createDatabase($username); $this->getManager()->createDatabase($username);
$this->log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "created database '" . $username . "'");
// and give permission to the user on every access-host we have // and give permission to the user on every access-host we have
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) { foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
$this->getManager()->grantPrivilegesTo($username, $password, $mysql_access_host); $this->getManager()->grantPrivilegesTo($username, $password, $mysql_access_host);
$this->log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "grant all privileges for '" . $username . "'@'" . $mysql_access_host . "'");
} }
$this->getManager()->flushPrivileges(); $this->getManager()->flushPrivileges();
Database::needRoot(false); Database::needRoot(false);
$this->log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "created database '" . $username . "'");
return $username; return $username;
} }

View File

@@ -85,21 +85,22 @@ class IntegrityCheck
/** /**
* check whether the froxlor database and its tables are in utf-8 character-set * check whether the froxlor database and its tables are in utf-8 character-set
* *
* @param bool $fix * @param bool $fix fix db charset/collation if not utf8
* 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'); $cs_stmt = Database::prepare('SELECT default_character_set_name FROM information_schema.SCHEMATA WHERE schema_name = :dbname');
$resp = Database::pexecute_first($cs_stmt, [ $resp = Database::pexecute_first($cs_stmt, [
'dbname' => Database::getDbName() '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') { 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) { if ($fix) {
// fix database // fix database
Database::query('ALTER DATABASE `' . Database::getDbName() . '` CHARACTER SET utf8 COLLATE utf8_general_ci'); Database::query('ALTER DATABASE `' . Database::getDbName() . '` CHARACTER SET utf8 COLLATE utf8_general_ci');
@@ -109,7 +110,8 @@ class IntegrityCheck
$table = $row[0]; $table = $row[0];
Database::query('ALTER TABLE `' . $table . '` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;'); 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 { } else {
return false; return false;
} }
@@ -124,10 +126,12 @@ class IntegrityCheck
/** /**
* Check the integrity of the domain to ip/port - association * Check the integrity of the domain to ip/port - association
* *
* @param bool $fix * @param bool $fix fix everything found directly
* Fix everything found directly *
* @return bool
* @throws \Exception
*/ */
public function domainIpTable($fix = false) public function domainIpTable(bool $fix = false): bool
{ {
$ips = []; $ips = [];
$domains = []; $domains = [];
@@ -184,9 +188,11 @@ class IntegrityCheck
'domainid' => $row['id_domain'], 'domainid' => $row['id_domain'],
'ipandportid' => $row['id_ipandports'] '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 { } 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; return false;
} }
} }
@@ -196,9 +202,11 @@ class IntegrityCheck
'domainid' => $row['id_domain'], 'domainid' => $row['id_domain'],
'ipandportid' => $row['id_ipandports'] '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 { } 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; return false;
} }
} }
@@ -216,9 +224,11 @@ class IntegrityCheck
'ipandportid' => $defaultip '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 { } 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; return false;
} }
} }
@@ -226,18 +236,19 @@ class IntegrityCheck
if ($fix) { if ($fix) {
return $this->domainIpTable(); return $this->domainIpTable();
} else {
return true;
} }
return true;
} }
/** /**
* Check if all subdomains have ssl-redirect = 0 if domain has no ssl-port * Check if all subdomains have ssl-redirect = 0 if domain has no ssl-port
* *
* @param bool $fix * @param bool $fix fix everything found directly
* Fix everything found directly *
* @return bool
* @throws \Exception
*/ */
public function subdomainSslRedirect($fix = false) public function subdomainSslRedirect(bool $fix = false): bool
{ {
$ips = []; $ips = [];
$parentdomains = []; $parentdomains = [];
@@ -300,28 +311,31 @@ class IntegrityCheck
Database::pexecute($upd_stmt, [ Database::pexecute($upd_stmt, [
'domainid' => $id '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 { } else {
// It's just the check, let the function fail // 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; return false;
} }
} }
if ($fix) { if ($fix) {
return $this->subdomainSslRedirect(); return $this->subdomainSslRedirect();
} else {
return true;
} }
return true;
} }
/** /**
* Check if all subdomain have letsencrypt = 0 if domain has no ssl-port * Check if all subdomain have letsencrypt = 0 if domain has no ssl-port
* *
* @param bool $fix * @param bool $fix fix everything found directly
* Fix everything found directly *
* @return bool
* @throws \Exception
*/ */
public function subdomainLetsencrypt($fix = false) public function subdomainLetsencrypt(bool $fix = false): bool
{ {
$ips = []; $ips = [];
$parentdomains = []; $parentdomains = [];
@@ -384,31 +398,32 @@ class IntegrityCheck
Database::pexecute($upd_stmt, [ Database::pexecute($upd_stmt, [
'domainid' => $id '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 { } else {
// It's just the check, let the function fail // 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; return false;
} }
} }
if ($fix) { if ($fix) {
return $this->subdomainLetsencrypt(); return $this->subdomainLetsencrypt();
} else {
return true;
} }
return true;
} }
/** /**
* check whether the webserveruser is in * check whether the webserveruser is in
* the customers groups when fcgid / php-fpm is used * the customers groups when fcgid / php-fpm is used
* *
* @param bool $fix * @param bool $fix fix member/groups
* 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) { if (Settings::Get('system.mod_fcgid') == 0 && Settings::Get('phpfpm.enabled') == 0) {
return true; return true;
@@ -423,7 +438,8 @@ class IntegrityCheck
]); ]);
if ($cwg_stmt->rowCount() > 0) { 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) { if ($fix) {
// prepare update statement // prepare update statement
$upd_stmt = Database::prepare(" $upd_stmt = Database::prepare("
@@ -438,7 +454,8 @@ class IntegrityCheck
$upd_data['id'] = $cwg_row['id']; $upd_data['id'] = $cwg_row['id'];
Database::pexecute($upd_stmt, $upd_data); 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 { } else {
return false; return false;
} }
@@ -455,12 +472,12 @@ class IntegrityCheck
* the customers groups when fcgid / php-fpm and * the customers groups when fcgid / php-fpm and
* fcgid/fpm in froxlor vhost is used * fcgid/fpm in froxlor vhost is used
* *
* @param bool $fix * @param bool $fix fix member/groups
* 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) { if (Settings::Get('system.mod_fcgid') == 0 && Settings::Get('phpfpm.enabled') == 0) {
return true; return true;
@@ -491,7 +508,8 @@ class IntegrityCheck
]); ]);
if ($cwg_stmt->rowCount() > 0) { 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) { if ($fix) {
// prepare update statement // prepare update statement
$upd_stmt = Database::prepare(" $upd_stmt = Database::prepare("
@@ -506,7 +524,8 @@ class IntegrityCheck
$upd_data['id'] = $cwg_row['id']; $upd_data['id'] = $cwg_row['id'];
Database::pexecute($upd_stmt, $upd_data); 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 { } else {
return false; return false;
} }

View File

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

View File

@@ -33,7 +33,15 @@ use PDO;
class Dns 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 = [ $dom_data = [
'did' => $domain_id 'did' => $domain_id
@@ -67,7 +75,15 @@ class Dns
Response::standardError('dns_notfoundorallowed'); 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) { if (!$froxlorhostname) {
// get domain-name // get domain-name
@@ -136,7 +152,7 @@ class Dns
if (!$froxlorhostname) { if (!$froxlorhostname) {
// additional required records for subdomains // additional required records for subdomains
$subdomains_stmt = Database::prepare(" $subdomains_stmt = Database::prepare("
SELECT `domain`, `iswildcarddomain`, `wwwserveralias` FROM `" . TABLE_PANEL_DOMAINS . "` SELECT `domain`, `iswildcarddomain`, `wwwserveralias`, `isemaildomain` FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE `parentdomainid` = :domainid WHERE `parentdomainid` = :domainid
"); ");
Database::pexecute($subdomains_stmt, [ Database::pexecute($subdomains_stmt, [
@@ -144,18 +160,31 @@ class Dns
]); ]);
while ($subdomain = $subdomains_stmt->fetch(PDO::FETCH_ASSOC)) { 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 // Listing domains is enough as there currently is no support for choosing
// different ips for a subdomain => use same IPs as toplevel // different ips for a subdomain => use same IPs as toplevel
self::addRequiredEntry(str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'A', $required_entries); self::addRequiredEntry($sub_record, 'A',$required_entries);
self::addRequiredEntry(str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'AAAA', $required_entries); self::addRequiredEntry($sub_record, 'AAAA', $required_entries);
// Check whether to add a www.-prefix // Check whether to add a www.-prefix
if ($subdomain['iswildcarddomain'] == '1') { if ($subdomain['iswildcarddomain'] == '1') {
self::addRequiredEntry('*.' . str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'A', $required_entries); self::addRequiredEntry('*.' . $sub_record, 'A', $required_entries);
self::addRequiredEntry('*.' . str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'AAAA', $required_entries); self::addRequiredEntry('*.' . $sub_record, 'AAAA', $required_entries);
} elseif ($subdomain['wwwserveralias'] == '1') { } elseif ($subdomain['wwwserveralias'] == '1') {
self::addRequiredEntry('www.' . str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'A', $required_entries); self::addRequiredEntry('www.' . $sub_record, 'A', $required_entries);
self::addRequiredEntry('www.' . str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'AAAA', $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 // now generate all records and unset the required entries we have
foreach ($dom_entries as $entry) { 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'])]); 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 special CAA required-entry
unset($required_entries[$entry['type']][md5("@CAA@")]); 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 special spf required-entry
unset($required_entries[$entry['type']][md5("@SPF@")]); unset($required_entries[$entry['type']][md5("@SPF@")]);
} }
@@ -223,7 +255,8 @@ class Dns
'*' '*'
] as $crecord ] 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['A'][md5($crecord)]);
unset($required_entries['AAAA'][md5($crecord)]); unset($required_entries['AAAA'][md5($crecord)]);
} }
@@ -238,13 +271,15 @@ class Dns
'smtp' 'smtp'
] as $crecord ] 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['A'][md5($crecord)]);
unset($required_entries['AAAA'][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 // add missing required entries
@@ -275,7 +310,8 @@ class Dns
foreach ($records as $record) { foreach ($records as $record) {
if ($type == 'A' && filter_var($ip['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) { if ($type == 'A' && filter_var($ip['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) {
$zonerecords[] = new DnsEntry($record, 'A', $ip['ip']); $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']); $zonerecords[] = new DnsEntry($record, 'AAAA', $ip['ip']);
} }
} }
@@ -348,15 +384,34 @@ class Dns
if ($type == 'TXT') { if ($type == 'TXT') {
foreach ($records as $record) { foreach ($records as $record) {
if ($record == '@SPF@') { if ($record == '@SPF@') {
// spf for main-domain
$txt_content = Settings::Get('spf.spf_entry'); $txt_content = Settings::Get('spf.spf_entry');
$zonerecords[] = new DnsEntry('@', 'TXT', self::encloseTXTContent($txt_content)); $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@.') {
// check for multiline entry // spf for subdomain
$multiline = false; $txt_content = Settings::Get('spf.spf_entry');
if (substr($dkim_entries[0], 0, 1) == '(') { $sub_record = substr($record, 6);
$multiline = true; $zonerecords[] = new DnsEntry($sub_record, 'TXT', self::encloseTXTContent($txt_content));
} elseif (!empty($dkim_entries)) {
// DKIM entries
$dkim_record = 'dkim' . $domain['dkim_id'] . '._domainkey';
if ($record == $dkim_record) {
// dkim for main-domain
// check for multiline entry
$multiline = false;
if (substr($dkim_entries[0], 0, 1) == '(') {
$multiline = true;
}
$zonerecords[] = new DnsEntry($record, 'TXT', self::encloseTXTContent($dkim_entries[0], $multiline));
} elseif (strlen($record) > strlen($dkim_record) && substr($record, 0, strlen($dkim_record)+1) == $dkim_record . '.') {
// dkim for subdomain-domain
// check for multiline entry
$multiline = false;
if (substr($dkim_entries[0], 0, 1) == '(') {
$multiline = true;
}
$zonerecords[] = new DnsEntry($record, 'TXT', self::encloseTXTContent($dkim_entries[0], $multiline));
} }
$zonerecords[] = new DnsEntry($record, 'TXT', self::encloseTXTContent($dkim_entries[0], $multiline));
} }
} }
} }
@@ -416,7 +471,8 @@ class Dns
if (!$isMainButSubTo) { if (!$isMainButSubTo) {
$date = date('Ymd'); $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) { if (!$froxlorhostname) {
$upd_stmt = Database::prepare(" $upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_DOMAINS . "` SET UPDATE `" . TABLE_PANEL_DOMAINS . "` SET
@@ -443,12 +499,19 @@ class Dns
array_unshift($zonerecords, $soa_record); 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; 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])) { if (!isset($required[$type])) {
$required[$type] = []; $required[$type] = [];
@@ -456,7 +519,11 @@ class Dns
$required[$type][md5($record)] = $record; $required[$type][md5($record)] = $record;
} }
private static function generateDkimEntries($domain) /**
* @param array $domain
* @return array
*/
private static function generateDkimEntries(array $domain): array
{ {
$zone_dkim = []; $zone_dkim = [];
@@ -486,7 +553,8 @@ class Dns
} }
// key // 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 // service-type
if (Settings::Get('dkim.dkim_servicetype') == '1') { if (Settings::Get('dkim.dkim_servicetype') == '1') {
@@ -503,10 +571,15 @@ class Dns
return $zone_dkim; 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 " " // 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) != '"') { if (substr($txt_content, 0, 1) != '"') {
$txt_content = '"' . $txt_content; $txt_content = '"' . $txt_content;
} }
@@ -526,10 +599,13 @@ class Dns
return $txt_content; return $txt_content;
} }
private static function escapeSoaAdminMail($email) /**
* @param string $email
* @return string
*/
private static function escapeSoaAdminMail(string $email): string
{ {
$mail_parts = explode("@", $email); $mail_parts = explode("@", $email);
$escpd_mail = str_replace(".", "\.", $mail_parts[0]) . "." . $mail_parts[1] . "."; return str_replace(".", "\.", $mail_parts[0]) . "." . $mail_parts[1] . ".";
return $escpd_mail;
} }
} }

View File

@@ -29,14 +29,22 @@ use Froxlor\Settings;
class DnsEntry class DnsEntry
{ {
public $record; public string $record;
public $ttl; public int $ttl;
public $class = 'IN'; public string $class = 'IN';
public $type; public string $type;
public $priority; public int $priority;
public $content; 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->record = $record;
$this->type = $type; $this->type = $type;
@@ -72,7 +80,6 @@ class DnsEntry
// last line // last line
$_content .= "\t\t\t\t" . '"' . $_l . '")'; $_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 $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;
} }
} }

View File

@@ -29,12 +29,18 @@ use Froxlor\Settings;
class DnsZone class DnsZone
{ {
public $ttl; public int $ttl;
public $origin; public string $origin;
public $serial; public string $serial;
public $records; 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->ttl = ($ttl <= 0 ? Settings::Get('system.defaultttl') : $ttl);
$this->origin = $origin; $this->origin = $origin;
@@ -44,13 +50,13 @@ class DnsZone
public function __toString() public function __toString()
{ {
$_zonefile = "\$TTL " . $this->ttl . PHP_EOL; $zone_file = "\$TTL " . $this->ttl . PHP_EOL;
$_zonefile .= "\$ORIGIN " . $this->origin . "." . PHP_EOL; $zone_file .= "\$ORIGIN " . $this->origin . "." . PHP_EOL;
if (!empty($this->records)) { if (!empty($this->records)) {
foreach ($this->records as $record) { 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 * 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"); $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_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_meta_stmt = self::getDB()->prepare("DELETE FROM `domainmetadata` WHERE `domain_id` = :did");
$del_dom_stmt = self::getDB()->prepare("DELETE FROM `domains` WHERE `id` = :did"); $del_dom_stmt = self::getDB()->prepare("DELETE FROM `domains` WHERE `id` = :did");
$pdns_domains_stmt->execute([ $pdns_domains_stmt->execute([
'domain' => $domain['domain'] 'domain' => $domain
]); ]);
$pdns_domain = $pdns_domains_stmt->fetch(PDO::FETCH_ASSOC); $pdns_domain = $pdns_domains_stmt->fetch(PDO::FETCH_ASSOC);
@@ -67,16 +67,19 @@ class PowerDNS
/** /**
* get pdo database connection to powerdns database * 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(); self::connectToPdnsDb();
} }
return self::$pdns_db; return self::$pdns_db;
} }
/**
* @return void
*/
private static function connectToPdnsDb() private static function connectToPdnsDb()
{ {
// get froxlor pdns config // get froxlor pdns config

View File

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

View File

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

View File

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

View File

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

View File

@@ -45,38 +45,42 @@ class FroxlorLogger
/** /**
* current \Monolog\Logger object * current \Monolog\Logger object
* *
* @var Logger * @var ?Logger
*/ */
private static $ml = null; private static ?Logger $ml = null;
/** /**
* LogTypes Array * LogTypes Array
* *
* @var array * @var ?array
*/ */
private static $logtypes = null; private static ?array $logtypes = null;
/** /**
* whether to output log-messages to STDOUT (cron) * whether to output log-messages to STDOUT (cron)
* *
* @var bool * @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 * @var array
*/ */
private static $userinfo = []; private static array $userinfo = [];
/** /**
* whether the logger object has already been initialized * whether the logger object has already been initialized
* *
* @var bool * @var bool
*/ */
private static $is_initialized = false; private static bool $is_initialized = false;
/** /**
* Class constructor. * Class constructor.
*
* @param array $userinfo
*
* @throws \Exception
*/ */
protected function __construct($userinfo = []) protected function __construct(array $userinfo = [])
{ {
$this->initMonolog(); $this->initMonolog();
self::$userinfo = $userinfo; self::$userinfo = $userinfo;
@@ -100,11 +104,17 @@ class FroxlorLogger
self::$ml->pushHandler(new SyslogHandler('froxlor', LOG_USER, Logger::DEBUG)); self::$ml->pushHandler(new SyslogHandler('froxlor', LOG_USER, Logger::DEBUG));
break; break;
case 'file': case 'file':
$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 // is_writable needs an existing file to check if it's actually writable
@touch($logger_logfile); @touch($logger_logfile);
if (empty($logger_logfile) || !is_writable($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)); self::$ml->pushHandler(new StreamHandler($logger_logfile, Logger::DEBUG));
break; break;
@@ -137,8 +147,9 @@ class FroxlorLogger
* @param array $userinfo * @param array $userinfo
* *
* @return FroxlorLogger * @return FroxlorLogger
* @throws \Exception
*/ */
public static function getInstanceOf($userinfo = []) public static function getInstanceOf(array $userinfo = [])
{ {
if (empty($userinfo)) { if (empty($userinfo)) {
$userinfo = [ $userinfo = [
@@ -153,9 +164,9 @@ class FroxlorLogger
* *
* @param int $action * @param int $action
* @param int $type * @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 // not logging normal stuff if not set to "paranoid" logging
if (!self::$crondebug_flag && Settings::Get('logger.severity') == '1' && $type > LOG_NOTICE) { 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) { switch ($type) {
case LOG_INFO: case LOG_INFO:
@@ -230,7 +245,11 @@ class FroxlorLogger
return $_type; return $_type;
} }
private function getActionTypeDesc($action) /**
* @param $action
* @return string
*/
private function getActionTypeDesc($action): string
{ {
switch ($action) { switch ($action) {
case FroxlorLogger::USR_ACTION: case FroxlorLogger::USR_ACTION:
@@ -262,7 +281,7 @@ class FroxlorLogger
* *
* @return int * @return int
*/ */
public function setCronLog(int $cronlog = 0) public function setCronLog(int $cronlog = 0): int
{ {
if ($cronlog < 0 || $cronlog > 2) { if ($cronlog < 0 || $cronlog > 2) {
$cronlog = 0; $cronlog = 0;

View File

@@ -47,15 +47,17 @@ class Directory
* *
* @param string $dir * @param string $dir
*/ */
public function __construct($dir = null) public function __construct(string $dir = null)
{ {
$this->dir = $dir; $this->dir = $dir;
} }
/** /**
* check whether the directory has options set in panel_htaccess * check whether the directory has options set in panel_htaccess
*
* @return bool
*/ */
public function hasUserOptions() public function hasUserOptions(): bool
{ {
$uo_stmt = Database::prepare(" $uo_stmt = Database::prepare("
SELECT COUNT(`id`) as `usropts` FROM `" . TABLE_PANEL_HTACCESS . "` WHERE `path` = :dir 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, [ $uo_res = Database::pexecute_first($uo_stmt, [
'dir' => FileDir::makeCorrectDir($this->dir) '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 $uo_res['usropts'] > 0;
} }
return false; return false;
@@ -71,8 +73,10 @@ class Directory
/** /**
* check whether the directory is protected using panel_htpasswd * check whether the directory is protected using panel_htpasswd
*
* @return bool
*/ */
public function isUserProtected() public function isUserProtected(): bool
{ {
$up_stmt = Database::prepare(" $up_stmt = Database::prepare("
SELECT COUNT(`id`) as `usrprot` FROM `" . TABLE_PANEL_HTPASSWDS . "` WHERE `path` = :dir 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, [ $up_res = Database::pexecute_first($up_stmt, [
'dir' => FileDir::makeCorrectDir($this->dir) '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 $up_res['usrprot'] > 0;
} }
return false; return false;
@@ -90,12 +94,11 @@ class Directory
* Checks if a given directory is valid for multiple configurations * Checks if a given directory is valid for multiple configurations
* or should rather be used as a single file * or should rather be used as a single file
* *
* @param bool $ifexists * @param bool $ifexists also check whether file/dir exists
* also check whether file/dir exists
* *
* @return bool true if usable as dir, false otherwise * @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)) { if (is_null($this->dir)) {
trigger_error(__CLASS__ . '::' . __FUNCTION__ . ' has been called with a null value', E_USER_WARNING); 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 * Executes simple GET request
* *
* @param string $url
* @param bool $follow_location
* @param int $timeout
*
* @return bool|string * @return bool|string
* @throws Exception * @throws Exception
*/ */
@@ -59,6 +63,10 @@ class HttpClient
/** /**
* Downloads and stores a file from an url * Downloads and stores a file from an url
* *
* @param string $url
* @param string $target
*
* @return bool|string
* @throws Exception * @throws Exception
*/ */
public static function fileGet(string $url, string $target) public static function fileGet(string $url, string $target)

View File

@@ -37,7 +37,7 @@ class PhpConfig
* *
* @return array * @return array
*/ */
public static function getPhpConfigs() public static function getPhpConfigs(): array
{ {
$configs_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. * Create or modify the AWStats configuration file for the given domain.
* Modified by Berend Dekens to allow custom configurations. * Modified by Berend Dekens to allow custom configurations.
* *
* @param * @param string $logFile
* logFile * @param string $siteDomain
* @param * @param string $hostAliases
* siteDomain * @param string $customerDocroot
* @param * @param array $domain_data
* hostAliases *
* @return null * @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 // Generation header
$header = "## GENERATED BY FROXLOR\n"; $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"; $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)); FileDir::safe_exec('mkdir -p ' . escapeshellarg($awstats_dir));
} }
// chown created folder, #258 // chown created folder, #258
self::makeChownWithNewStats($awstats_params); self::makeChownWithNewStats($domain_data);
// weird but could happen... // weird but could happen...
if (!is_dir(Settings::Get('system.awstats_conf'))) { if (!is_dir(Settings::Get('system.awstats_conf'))) {
@@ -127,16 +133,15 @@ class Statistics
} }
/** /**
* chowns either awstats or webalizer folder, * chowns stats-tools folder, either with webserver-user or
* either with webserver-user or - if fcgid * if fcgid/php-fpm is used, the customers name, #258
* is used - the customers name, #258
* *
* @param array $row * @param array $row array of panel_customers
* array if panel_customers
* *
* @return void * @return void
* @throws \Exception
*/ */
public static function makeChownWithNewStats($row) public static function makeChownWithNewStats(array $row)
{ {
// get correct user // get correct user
if ((Settings::Get('system.mod_fcgid') == '1' || Settings::Get('phpfpm.enabled') == '1') && isset($row['deactivated']) && $row['deactivated'] == '0') { 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 * @param string $to_encode May be either a single domain name, e single email address or a list of one
* string May be either a single domain name, e single email address or a list of one
* separated either by ',', ';' or ' '. * separated either by ',', ';' or ' '.
* *
* @return string Returns either a single domain name, a single email address or a list of one of * @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. * 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); $to_encode = $this->isUtf8($to_encode) ? $to_encode : utf8_encode($to_encode);
try { try {
@@ -83,7 +82,7 @@ class IdnaWrapper
* *
* @return boolean * @return boolean
*/ */
private function isUtf8($string = null) private function isUtf8(string $string)
{ {
if (function_exists("mb_detect_encoding")) { if (function_exists("mb_detect_encoding")) {
if (mb_detect_encoding($string, 'UTF-8, ISO-8859-1') === 'UTF-8') { 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 * @param string $to_decode May be either a single domain name, e single email address or a list of one
* string May be either a single domain name, e single email address or a list of one
* separated either by ',', ';' or ' '. * separated either by ',', ';' or ' '.
* *
* @return string Returns either a single domain name, a single email address or a list of one of * @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. * 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); return $this->idna_converter->decode($to_decode);
} }

View File

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

View File

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

View File

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

View File

@@ -68,10 +68,10 @@ class MailLogParser
// Parse MDA traffic // Parse MDA traffic
if (Settings::Get("system.mdaserver") == "dovecot") { if (Settings::Get("system.mdaserver") == "dovecot") {
$this->parseDovecotLog(Settings::Get("system.mdalog")); $this->parseDovecotLog(Settings::Get("system.mdalog"));
$this->parsePostfixLog(Settings::Get("system.mdalog") . ".1"); $this->parseDovecotLog(Settings::Get("system.mdalog") . ".1");
} elseif (Settings::Get("system.mdaserver") == "courier") { } elseif (Settings::Get("system.mdaserver") == "courier") {
$this->parseCourierLog(Settings::Get("system.mdalog")); $this->parseCourierLog(Settings::Get("system.mdalog"));
$this->parsePostfixLog(Settings::Get("system.mdalog") . ".1"); $this->parseCourierLog(Settings::Get("system.mdalog") . ".1");
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -36,6 +36,7 @@ use Froxlor\PhpHelper;
use Froxlor\Settings; use Froxlor\Settings;
use Froxlor\System\Cronjob; use Froxlor\System\Cronjob;
use Froxlor\System\IPTools; use Froxlor\System\IPTools;
use Froxlor\Validate\Validate;
use PDO; use PDO;
class Store class Store
@@ -45,10 +46,21 @@ class Store
{ {
$returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue); $returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue);
if ($returnvalue !== false && is_array($fielddata) && isset($fielddata['settinggroup']) && $fielddata['settinggroup'] == 'system' && isset($fielddata['varname']) && $fielddata['varname'] == 'le_froxlor_enabled' && $newfieldvalue == '0') { if ($returnvalue !== false
Database::query(" && is_array($fielddata)
DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE `domainid` = '0' && isset($fielddata['settinggroup'])
"); && $fielddata['settinggroup'] == 'system'
&& isset($fielddata['varname'])
) {
if ($fielddata['varname'] == 'le_froxlor_enabled' && $newfieldvalue == '0') {
Database::query("
DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE `domainid` = '0'
");
} elseif ($fielddata['varname'] == 'froxloraliases' && $newfieldvalue != $fielddata['value']) {
Database::query("
UPDATE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` SET `validtodate`= NULL WHERE `domainid` = '0'
");
}
} }
return $returnvalue; return $returnvalue;
@@ -415,40 +427,30 @@ class Store
} }
// Make sure mime-type matches an image // Make sure mime-type matches an image
if (function_exists('finfo_open')) { $image_content = file_get_contents($_FILES[$fieldname]['tmp_name']);
$finfo = finfo_open(FILEINFO_MIME_TYPE); $value = base64_encode($image_content);
$mimetype = finfo_file($finfo, $_FILES[$fieldname]['tmp_name']); if (Validate::validateBase64Image($value)) {
finfo_close($finfo); $img_filename = $_FILES[$fieldname]['name'];
} else {
$mimetype = mime_content_type($_FILES[$fieldname]['tmp_name']);
}
if (empty($mimetype)) {
$mimetype = 'application/octet-stream';
}
if (!in_array($mimetype, ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'])) {
throw new \Exception("Uploaded file is not a valid image");
}
// Determine file extension $spl = explode('.', $img_filename);
$spl = explode('.', $_FILES[$fieldname]['name']); $file_extension = strtolower(array_pop($spl));
$file_extension = strtolower(array_pop($spl)); unset($spl);
unset($spl);
if (!in_array($file_extension, [ if (!in_array($file_extension, [
'jpeg', 'jpeg',
'jpg', 'jpg',
'png', 'png',
'gif' 'gif'
])) { ])) {
throw new Exception("Invalid file-extension, use one of: jpeg, jpg, png, gif"); throw new Exception("Invalid file-extension, use one of: jpeg, jpg, png, gif");
}
$filename = bin2hex(random_bytes(16)) . '.' . $file_extension;
// Move file
if (!move_uploaded_file($_FILES[$fieldname]['tmp_name'], $path . $filename)) {
throw new Exception("Unable to save image to img folder");
}
$save_to = 'img/' . $filename . '?v=' . time();
} }
// Move file
if (!move_uploaded_file($_FILES[$fieldname]['tmp_name'], $path . $fielddata['image_name'] . '.' . $file_extension)) {
throw new Exception("Unable to save image to img folder");
}
$save_to = 'img/' . $fielddata['image_name'] . '.' . $file_extension . '?v=' . time();
} }
// Delete file? // Delete file?

View File

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

View File

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

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