Compare commits

...

238 Commits

Author SHA1 Message Date
f68c4bc34b fixes #1
Some checks reported errors
continuous-integration/drone/push Build was killed
Merge branch 'upgrade-2' of ssh://gitea.service.nr5:2222/maketank/Froxlor into upgrade-2
2023-12-06 19:56:20 +01:00
b9afa347d7 fixes #1: we don't have that column 2023-12-06 19:54:06 +01:00
97cf30ed7a we don't have that column
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-12-06 19:52:30 +01:00
305f3f6f86 drone deployment - test version
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-12-06 19:34:51 +01:00
Michael Kaufmann
22aa197864 remove shortcode for --diff-params in configdiff command
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-12 09:07:37 +02:00
Daniel
d53f9b8e58 Add config-diff CLI Command (#1168)
---------

Co-authored-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-12 09:06:53 +02:00
Michael Kaufmann
9d4205acf6 correct validation of hostingplan name and description
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-11 13:57:58 +02:00
Michael Kaufmann
cb8b969ddd forgot to save one file for the last commit
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-11 12:14:47 +02:00
Michael Kaufmann
fcfd44f726 correctly redirect to last-page if session is timed out and remove passing script/qrystr url parameters
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-11 12:12:31 +02:00
Michael Kaufmann
52a06bf806 validate allowed php configurations to be none-empty if php is enabled for the customer
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-28 14:51:35 +02:00
overgrow
20aa162fcc Added support DNS TLSA record (#1165)
Co-authored-by: netcarlos <carlos@allhighseeds.com>
2023-07-28 14:18:53 +02:00
Michael Kaufmann
bb60df0709 more work on backup feature
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-28 12:20:06 +02:00
Michael Kaufmann
a86c8535e0 fix tablelisting of backup-storages
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-27 16:58:22 +02:00
Michael Kaufmann
ab82695806 adjustments in installation for debian 12 and fcgid / disabling mod_php; thx to Konstantin
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-27 11:25:43 +02:00
Michael Kaufmann
c52d9bbd03 work on backup storages
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-27 11:08:27 +02:00
Michael Kaufmann
d1043b4645 Allow customers to enable separate log-file for (sub)domains; fixes #1117
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-26 11:18:14 +02:00
Michael Kaufmann
9d113afc83 send email notification to admin for non-successful let's encrypt results; fixes #1162
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-26 10:13:50 +02:00
Michael Kaufmann
bbd1dca30e make password-suggestion as next-to like we always do
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-24 20:43:06 +02:00
Michael Kaufmann
6d42968d1a added abbility to enable/disable login for ftp users; fixes #1146
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-24 15:23:15 +02:00
Michael Kaufmann
8d66a4aec4 readd missing email quota information on customers dashboard, fixes #1159
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-24 14:59:18 +02:00
Michael Kaufmann
e071365cd6 improve description of resources assignment in admins and customers
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-24 14:28:08 +02:00
Michael Kaufmann
5f05478c76 improve/update proftpd configuration template; fixes #1148
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-24 10:38:44 +02:00
Michael Kaufmann
6616bd9a38 adjust proftpd config for debian 12 bookworm
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-22 16:26:36 +02:00
Michael Kaufmann
947df2079f rework path to certificates non-ecc/ecc, regardless of current setting
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-21 08:58:51 +02:00
Michael Kaufmann
b791cd5c3e fix optional-flag for IpsAndPorts.add() and IpsAndPorts.update()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-16 17:10:32 +02:00
Michael Kaufmann
1662745991 validate non-empy admin-name in Admins.update()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-16 17:10:23 +02:00
Michael Kaufmann
067c9f8c76 add language strings for otp-validation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-10 13:52:49 +02:00
Michael Kaufmann
f396bd5184 add otp security check to critical settings
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-10 13:40:48 +02:00
Michael Kaufmann
03b5a921ff validate generated config-json parameter string
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-07 09:46:31 +02:00
Michael Kaufmann
e357f7e9c3 beautification
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-12 13:06:18 +02:00
Michael Kaufmann
da7309c41e integrate opcache-gui data from amnuts/opcache-gui as intended; refs #1151
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-12 12:15:52 +02:00
Michael Kaufmann
257ef4c059 allow more complex access-log formats to be displayed correctly on the UI, fixes #1139
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-12 10:39:04 +02:00
Michael Kaufmann
eda8700217 add last successful login field to column-select for admins/admins overview
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-11 12:06:06 +02:00
Michael Kaufmann
94d9c3eedf regenerate session-id after login / su-action
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-11 12:05:33 +02:00
Michael Kaufmann
f9f1048477 corrected Backups.listingCount() for admins/reseller/customers accordingly to listing(); minor fixes
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-08 17:30:12 +02:00
Michael Kaufmann
a2fca3fe69 Merge branch '2.1.x' of github.com:Froxlor/Froxlor into 2.1.x 2023-06-08 17:18:24 +02:00
Maurice Preuß (envoyr)
105213fd3f update backup cron
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-06-08 17:17:25 +02:00
Michael Kaufmann
07f4491db6 Merge remote-tracking branch 'origin/main' into 2.1.x 2023-06-08 12:34:29 +02:00
Michael Kaufmann
e0fa64f897 fix update-check unit-tests now that the current testing version is at 2.1.0-dev1
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-08 12:33:48 +02:00
Michael Kaufmann
ed72fd1766 exclude password fields from being filtered/escaped by AntiXSS, fixes #1150
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-08 12:14:13 +02:00
Michael Kaufmann
8a30bc07f5 exclude password fields from being filtered/escaped by AntiXSS, fixes #1150
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-08 12:13:24 +02:00
Michael Kaufmann
ddcd55a83d more work on backup-storages
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-08 11:56:58 +02:00
Maurice Preuß (envoyr)
c3205d3672 update backup storage api
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-06-07 21:22:04 +02:00
Michael Kaufmann
c668c180d5 fix access_backup*s* paramter name
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-07 21:14:01 +02:00
Michael Kaufmann
1cf5865bba minor fixes in customer-backup-flags integration
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-07 21:08:04 +02:00
Michael Kaufmann
6f5e49de79 integrate backup/access_backup fields into Customers
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-07 20:57:58 +02:00
Maurice Preuß (envoyr)
a9c613e71c update backups
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-06-07 20:51:53 +02:00
Michael Kaufmann
981d7f3d9a dblayout enhancements for backup-feature; added preconfig and updates for 2.1.x
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-07 17:58:29 +02:00
Michael Kaufmann
3445472049 combine change-password, change-theme and change-language into 'my profile'
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-07 16:18:05 +02:00
Michael Kaufmann
bfc816a51e add php-configuration to domain-listing, fixes #1141; move backup-menu to 'System' instead of 'Resources'
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-07 15:08:36 +02:00
Michael Kaufmann
5afe5a8c46 minor bugfixes for data export / UI
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-07 11:49:57 +02:00
Michael Kaufmann
eb9dded947 forgot to save one parameter change in the unit-test
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-07 11:40:42 +02:00
Michael Kaufmann
e958cfed84 big renaming of CustomerBackup to DataDump / export for the real backup-feature to shine :P
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-07 11:37:00 +02:00
Michael Kaufmann
5181602921 fix cronjobs test
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-06 17:51:07 +02:00
Michael Kaufmann
4bfed71ac9 fix install sql
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-06 17:47:59 +02:00
Michael Kaufmann
69c58d21be correctly fork export cron action with new Forkable-trait
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-06 17:45:14 +02:00
Maurice Preuß (envoyr)
94051dc9eb add backup settings and update cron fork
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-06-06 17:30:12 +02:00
Maurice Preuß (envoyr)
2ec039372d Merge remote-tracking branch 'origin/2.1.x' into feature-backup 2023-06-06 14:41:41 +02:00
Maurice Preuß (envoyr)
0c3ac31231 initial backup feature
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-06-06 14:38:08 +02:00
Michael Kaufmann
03257f04cb more integration of domain deactivated flag
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-06 14:10:07 +02:00
Michael Kaufmann
6e37b55ac6 more integration of domain deactivated flag
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-06 10:23:47 +02:00
Michael Kaufmann
196ef9378a deactivate/reactivate email-accounts for deactivated/reactivated domain; set deactivated-docroot by default to not have deactivated domains point to froxlor login but rather show a message
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-06 10:10:32 +02:00
Michael Kaufmann
9ed45ea7f8 make alpha to dev, version check does not know about 'alpha'
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-06 09:46:31 +02:00
Michael Kaufmann
20755bcead set version in 2.1.x branch to 2.1.0-alpha1
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-06 09:35:12 +02:00
Michael Kaufmann
bde19997ba Merge remote-tracking branch 'origin/main' into 2.1.x 2023-06-06 09:05:59 +02:00
Michael Kaufmann
981d819fd7 display notice if customer has no domains assigned yet to add subdomains; corrected subdomains-usage-check to exclude std-subdomain
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-06 09:05:49 +02:00
Michael Kaufmann
826ae36647 adjust log-levels in API methods
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-05 12:13:38 +02:00
Michael Kaufmann
9ddf24539e remove hidden fields from login/passwd-reset; refs #1102
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-05 12:10:39 +02:00
Michael Kaufmann
c079047180 Merge remote-tracking branch 'origin/main' into 2.1.x 2023-06-05 09:05:13 +02:00
Michael Kaufmann
e1e7bc7b42 set fastcgi_ipcdir according to webserver after installation (regardless of using phpfpm)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-05 09:01:41 +02:00
Grigory Morozov
3940c1429d Correcting Nginx location match, fixes #1153 2023-06-05 08:06:44 +02:00
Michael Kaufmann
c236d9eaab set version to 2.0.20 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-06-02 20:13:36 +02:00
Michael Kaufmann
512a544dd1 Merge remote-tracking branch 'origin/main' into 2.1.x 2023-05-29 20:53:17 +02:00
Michael Kaufmann
688994e40c idna encode umlaut-emailaddresses when adding email-forwarder
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-29 20:52:57 +02:00
Michael Kaufmann
0fb338b32d Merge remote-tracking branch 'origin/main' into 2.1.x 2023-05-28 15:49:58 +02:00
Michael Kaufmann
9facaee809 re-enable fcgid/php-fpm activation-validate-check
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-28 15:49:06 +02:00
Michael Kaufmann
e90dae186b Merge remote-tracking branch 'origin/main' into 2.1.x 2023-05-28 10:48:30 +02:00
Michael Kaufmann
a7dd5f4685 show 0 value of resource-fields if value is empty, fixes #1149
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-28 10:46:28 +02:00
Michael Kaufmann
ca5f36d912 corrected language index in system-settings, fixes #1145
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-26 21:24:08 +02:00
Michael Kaufmann
63d81201de fix typo
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-26 14:02:09 +02:00
Michael Kaufmann
0b685d569f start to integrate domain-deactivated flag in UI
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-26 13:58:41 +02:00
Michael Kaufmann
426f204473 specify default search-field for on-page listing-search/filter
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-26 13:15:01 +02:00
Michael Kaufmann
84599011cf Allow editing/viewing of standard subdomain for customer, fixes #1121
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-26 12:53:27 +02:00
Michael Kaufmann
6202e24b77 Merge remote-tracking branch 'origin/domain-duplicate' into 2.1.x 2023-05-25 12:35:10 +02:00
Michael Kaufmann
8deaf6a013 frontend implementation of Domains.duplicate()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-25 12:33:43 +02:00
Michael Kaufmann
a491667ce5 Merge remote-tracking branch 'origin/main' into 2.1.x 2023-05-25 09:54:34 +02:00
Michael Kaufmann
da810ea953 secure filename of local-archive in webupdate
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-25 09:51:55 +02:00
Michael Kaufmann
d51556f918 Merge remote-tracking branch 'origin/main' into 2.1.x 2023-05-25 08:27:39 +02:00
Michael Kaufmann
51b6e067e8 idna encode umlaut-emailaddresses when adding/editing email-account; use correct password-suggestion-layout in change-email-account formfield
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-25 08:26:39 +02:00
Michael Kaufmann
233bf27afe add Froxlor.generateLoginLink() API call to allow generation of one-time-login links for customers, thx to INWX for supporting and sponsoring this feature
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-24 16:02:07 +02:00
Michael Kaufmann
09b3c1c45a implement Domains.duplicate() API call, refs #807
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-24 09:05:50 +02:00
Michael Kaufmann
2e6b939ec6 set dbversion
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-23 15:21:25 +02:00
Michael Kaufmann
c1f03c1683 remove 'main but subdomain' as we now automatically create the correct order of vhost configs and dns zones
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-23 14:36:45 +02:00
Maurice Preuß (envoyr)
5731f5ffff add pgp public key encryption for backup
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-05-21 20:51:18 +02:00
Michael Kaufmann
34cf6698bc remove superfluous try_files in nginx config if php-backend (non-fastcgi) is used
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-15 20:14:26 +02:00
Michael Kaufmann
4642160724 add same loginfail restrictions for entering 2fa code as for user/pwd login
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-12 10:36:27 +02:00
Nicolas Thumann
78a259ef3b Fix IPv6 address in cookie domain (#1137)
* Implement getCookieHost to extract cookie host from HTTP_HOST
2023-05-10 08:26:08 +02:00
Nicolas Thumann
68cf4ab69a Fix typo in English privileged_passwd (#1136) 2023-05-09 18:52:43 +02:00
Michael Kaufmann
d5661d492d set version to 2.0.19 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-07 11:07:31 +02:00
Michael Kaufmann
6900898ae1 typo in updater
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-07 11:03:21 +02:00
Michael Kaufmann
d90fb7fa68 fix mysql-pdo check on installation, set version to 2.0.18 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-07 10:54:47 +02:00
Michael Kaufmann
4ea8629fcc set version to 2.0.17 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-06 22:08:43 +02:00
Michael Kaufmann
9d4ff8698d fix ratelimiting when settings do not exist (yet)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-06 22:00:19 +02:00
Michael Kaufmann
b164038846 set version to 2.0.16 for upcoming maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-05 21:10:01 +02:00
Michael Kaufmann
5c46960734 fix language mixup for rate-limit-interval setting
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-05 13:21:12 +02:00
Michael Kaufmann
a7f4f0c737 output nicer message when hitting rate limit
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-04 10:55:34 +02:00
Michael Kaufmann
b64dd501dd fix missing use-statement
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-02 10:27:28 +02:00
Michael Kaufmann
1679675aa1 introduce http-request rate-limit; smaller fixes
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-05-02 10:19:53 +02:00
sro0
640466f301 Disable autocomplete on 2FA input element (#1133)
2FA codes change every login. So there is no need to save entered values in browser and suggest them again during future logins.

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-15 13:21:01 +01:00
Michael Kaufmann
64d8bf4fba avoid socket length limitations leading to cut-off/invalid filename for very long domain and/or loginnames, fixes #1108
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-15 13:18:43 +01:00
Michael Kaufmann
ae6ee95973 avoid using posix-extension function before requirement-check can test for it and inform user
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-15 08:54:50 +01:00
Michael Kaufmann
e9051dc30a add spanish language translation reference to german language file
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-08 09:48:13 +01:00
scramatte
b6c7c53c3a Add Spanish language (#1105)
* Add Spanish localization
* add spanish to languages list
2023-03-08 09:43:35 +01:00
Michael Kaufmann
f36bc61fc7 better validation for uploaded/imported image files
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-08 09:33:30 +01:00
Michael Kaufmann
c56e0b9dac add 'Passing HTTP AUTH BASIC' header option when using FCGID; fix typeerror in parameter for Froxlor\Dns\Dns; require php-gd extension for validating uploaded images
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-08 09:33:02 +01:00
Michael Kaufmann
1deb08bf75 use correct parameter in PowerDNS::cleanDomainZone(), fixes #1104
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-04 14:42:31 +01:00
Michael Kaufmann
b30d7a8252 set version to 2.0.13 for maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-03 13:05:09 +01:00
Michael Kaufmann
b03e11c18d fix email-domain navigation and descriptions
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-03-03 13:03:12 +01:00
Michael Kaufmann
bf7d22a794 typecast parameter values for sizeReadable(), fixes #1103
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-28 08:15:31 +01:00
Michael Kaufmann
fb57a8a3b5 update dependencies
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-26 10:24:44 +01:00
Mickey
0d625797b0 Add command to remove debians prerotate script (#1101)
Co-authored-by: Mickey Knox <mickey@netfreaks.org>
2023-02-22 10:01:25 +01:00
Michael Kaufmann
6777fbf229 type-safe comparsion of md5-compatibility hash-validation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-22 09:35:27 +01:00
Michael Kaufmann
23f1f79eff specify clearly which tls settings are being overwritten/ignored depending on the 'Override system TLS settings' flag when adding/updating Domains
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-19 12:27:56 +01:00
Michael Kaufmann
a5af104d53 keep search-fields/text in pagination links of displaying a search-result
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-19 12:06:47 +01:00
Michael Kaufmann
38d94698ce set version to 2.0.12 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-17 20:47:17 +01:00
Michael Kaufmann
5ba28ef599 fix wrong request-parameter reading for table-column mangement
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-17 19:32:27 +01:00
Michael Kaufmann
a3486cc5b3 updated workflow for building/deploying documentation; added missing api-method-description for EmailDomains-API
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-17 17:53:32 +01:00
Michael Kaufmann
5ab322ab1d remove unused required function parameter in nginx cron; set default value for function parameter in lighttpd cron
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-17 13:19:48 +01:00
Michael Kaufmann
4f26bdd535 set version to 2.0.11 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-17 10:26:32 +01:00
Michael Kaufmann
88f76e4355 use bcrypt hash algorithms for htpasswd password hashing instead of the old SHA1
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-17 10:00:08 +01:00
Oliver Rahner
a464d8cb19 fixed duplicated column heading (#1100) 2023-02-15 20:44:57 +01:00
Michael Kaufmann
0f596dce8b fix api parameter issue when empty values are passed
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-15 08:33:49 +01:00
Michael Kaufmann
60270b20b3 backup possible remote-db-server databases in backup-cron
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-14 11:36:39 +01:00
Michael Kaufmann
4003a8d2b6 check for existing fields when setting/updating tablelisting-columns
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-14 11:36:11 +01:00
Michael Kaufmann
89843d6f37 fix referenced quota field for searching/sorting, fixes #1099
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-12 10:08:41 +01:00
Michael Kaufmann
256a52a5da fix setting incorrect acme-challenge path on installation; fixes #1097
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-11 08:54:09 +01:00
Michael Kaufmann
c9b2bfe53c fix pagination for entity-listings with extra parameters
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-11 08:52:43 +01:00
Michael Kaufmann
98cb36327e add SPF/DKIM to Subdomain DNS Zone separately if isemaildomain = 1
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-08 12:37:20 +01:00
Michael Kaufmann
7d23e4882d fix '0 illegal offset type' when changing mysql-access-host setting
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-08 12:36:38 +01:00
Michael Kaufmann
1cc3a1d066 re-add special image_data import for exported custom-logos
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 17:21:39 +01:00
Michael Kaufmann
de0f7d2f01 generalize array-index name of settings to be settingsgroup_varname
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 13:39:09 +01:00
Michael Kaufmann
aa48ffca2b run Form::processForm() when importing settings so the same validations apply if the import file has malicious content
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 13:02:11 +01:00
Michael Kaufmann
802168cb5b forgot to add Validate/Validate to the last commit
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 11:45:57 +01:00
Michael Kaufmann
6ace2e9f3d corrected call to Domain::triggerLetsEncryptCSRForAliasDestinationDomain only if aliasdomain is a valid id; validate registration-date and termination-date only if given
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 11:44:07 +01:00
Michael Kaufmann
0bff360d22 another type fix
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 11:26:13 +01:00
Michael Kaufmann
e300acf109 corrected return type of pexecute_first
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 11:23:21 +01:00
Michael Kaufmann
14d8e12cdc honor deactivated flag for redirects and prepare to use domain.deactivated for domain-specific deactivation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 11:19:31 +01:00
Michael Kaufmann
d29411dba6 backup nginx.conf when configuring service
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 11:15:56 +01:00
Michael Kaufmann
464663877c cleanup function/parameters and add type declarations where possible
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-07 11:15:19 +01:00
Michael Kaufmann
c3f769d48b remove robots.txt to actually make meta-tag robots work; fixes #1096
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-06 14:27:23 +01:00
Michael Kaufmann
f97536ed02 minor adjustments in customer-email-domain-overview
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-05 18:30:29 +01:00
Michael Kaufmann
7686effc8c new setting to select default value of 'allow api access' for new customers; fixes #1087
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-03 14:16:37 +01:00
Michael Kaufmann
ee8385467b add fallback to system-hostname for faulty http-clients not setting 'Host' in the request
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-03 13:59:12 +01:00
Michael Kaufmann
0a51d97684 add translation for new email domain overview
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-02-03 13:57:28 +01:00
Michael Kaufmann
67fc762eef fix let's encrypt dns validation check caused by issue in PhpHelper::gethostbynamel6()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-31 17:32:56 +01:00
Michael Kaufmann
8378795f5d Merge branch 'main' of github.com:Froxlor/Froxlor 2023-01-31 09:33:02 +01:00
Michael Kaufmann
98e6f1df4a Merge branch 'main' of github.com:Froxlor/Froxlor 2023-01-31 09:32:21 +01:00
Michael Kaufmann
674e35e5c5 add new EmailDomains API Commands for listing domain/email-usage information; show email-domain overview when customer has >1 domains with email addresses; add EmailDomains to GlobalSearch
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-31 09:31:58 +01:00
Michael Kaufmann
b24ca44e6f fix typos
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-30 08:47:11 +01:00
Michael Kaufmann
e0f7fcd2ef fix awstats path in generated vhost config if speciallogfile=0
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-29 15:58:49 +01:00
Michael Kaufmann
c5bece64ce set version to 2.0.10 for security release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-28 20:07:15 +01:00
Michael Kaufmann
0034681412 fix possible privilege escalation from customer to root when specifying custom error documents in directory-options
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-28 20:00:24 +01:00
Michael Kaufmann
bd5b99dc1c verify cronjob interval is one of the fixed available values
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-28 13:06:44 +01:00
Michael Kaufmann
2feb802094 validate existence of language in admin-templates
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-28 12:16:40 +01:00
Michael Kaufmann
7b08a71c59 add missing use statement for error-reporting to include the dbms version
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-28 11:57:43 +01:00
Michael Kaufmann
2a84e9c120 enforce password requirements set in settings for directory-protection
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-28 11:40:07 +01:00
Michael Kaufmann
d854e8e991 Merge branch 'main' of github.com:Froxlor/Froxlor 2023-01-26 15:23:03 +01:00
Michael Kaufmann
0a363910d6 fix potential infinite loop on errors in cli-installation; fixes #1092
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-26 15:22:39 +01:00
Maurice Preuß (envoyr)
b23d5cd909 merge branch 'main' of github.com:Froxlor/Froxlor 2023-01-25 18:51:03 +01:00
Maurice Preuß (envoyr)
3b753aa69d change session/cookie domain value, this prevents using the _ server_name when using nginx
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-01-25 18:50:49 +01:00
Michael Kaufmann
492cd288bc enhanced themefile validation for non-default themes
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-25 13:09:06 +01:00
Marvin Stark
47938c5082 Update README.md (#1090)
Fixed typo.
2023-01-24 18:56:29 +01:00
Michael Kaufmann
97c4c9a366 Merge branch 'main' of github.com:Froxlor/Froxlor 2023-01-23 09:00:21 +01:00
Michael Kaufmann
d090e48544 validate result of Net_DNS2_Resolver::query (CNAME's are being resolved to their corresponding target A/AAAA addresses); fixes #1089
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-23 08:58:42 +01:00
Michael Kaufmann
314e4407a0 add lasst successful login to table-columns for customer overview
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-22 15:04:19 +01:00
Michael Kaufmann
ed50e03957 Merge remote-tracking branch 'origin/main' into customeremail-overview 2023-01-22 14:03:07 +01:00
Michael Kaufmann
dff7530cc5 include froxlor-vhost in validate-acme-webroot command; fixes #1088
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-22 13:01:20 +01:00
Maurice Preuß (envoyr)
19423c9644 normalize (compress) ip addresses
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-01-20 21:26:24 +01:00
Michael Kaufmann
42b3f1e59d set version to 2.0.9
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-20 18:01:28 +01:00
Michael Kaufmann
1b77632fa8 correctly display config-services command in updater if manual commands are needed
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-19 20:19:43 +01:00
Maurice Preuß (envoyr)
867b7b1390 fix domain variable for gethostbynamel6 function
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-01-18 14:47:25 +01:00
Maurice Preuß (envoyr)
4c6ebde58c adding new dns resolver setting for let's encrypt
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
Co-authored-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-18 13:57:47 +01:00
Michael Kaufmann
1e013d9e9a enhance information on updater regarding acme-challenge (if lets encrypt is enabled and applicable)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-18 11:47:55 +01:00
Michael Kaufmann
c56bc651b9 allow hiding documentation menu for customers via customers-hide-option; use --staging for acme.sh for every test-CA
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-18 08:59:59 +01:00
aPollO2k
6cbdf45a7c Typo fixed in update_2.x.inc.php (#1082)
PHO_EOL => PHP_EOL
2023-01-16 21:32:56 +01:00
Michael Kaufmann
715667e227 Merge branch 'main' of github.com:Froxlor/Froxlor 2023-01-15 23:49:09 +01:00
Michael Kaufmann
41de161555 show exact froxlor:config-services parameter for updater; better checks for changed acme-challenge paths; fix typo in PHP_EOL statement; remove crsf token from config-apply-parameter generation from within the ui
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-15 23:48:37 +01:00
Maurice Preuß (envoyr)
1f1ea370c0 add version to mix-manifest.json and add mix function
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-01-14 21:14:55 +01:00
Michael Kaufmann
090cfc26f2 set file-log (if enabled) to be in froxlor/logs/ folder; fix ssl param directive for dovecot in Ubuntu Bionic; set version to 2.0.8
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-14 13:09:42 +01:00
Michael Kaufmann
529890b5d2 fix typo in langauge-definition
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 22:27:45 +01:00
Michael Kaufmann
d4a6ab146d re-add total-disspace dashboard-display on customer dashboard
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 16:52:14 +01:00
Michael Kaufmann
e3f02879cf restore mandatory field on domain-formfields; add translated require message and correct error-placement of the message
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 15:16:42 +01:00
Michael Kaufmann
b52d6df777 [UI] change require of ipandport field in domains.add and domains.delete to one-of instead of all; fixes #1078
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 14:53:05 +01:00
Michael Kaufmann
9e671100ae acme-challenge path adjustments if docroot changed after update from 0.10.x (via apt)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 14:21:14 +01:00
Michael Kaufmann
7e801ea502 Merge branch 'main' of github.com:Froxlor/Froxlor 2023-01-12 12:20:23 +01:00
Daniel
b68522f7d5 Fix formfield image preview path (#1077) 2023-01-12 12:19:31 +01:00
Michael Kaufmann
86852942e0 add missing language-strings for traffic page
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 11:30:52 +01:00
Michael Kaufmann
ea88d53e39 Merge remote-tracking branch 'origin/main' into customeremail-overview 2023-01-12 09:59:22 +01:00
Michael Kaufmann
61f6a474e4 add emails-overview tablelisting
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 09:59:19 +01:00
Michael Kaufmann
ec05c84f4d check whether let's encrypt is enabled at all and correct acme-alias configuration file if necessary/selected
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 09:40:35 +01:00
Michael Kaufmann
9e13c077e9 show command to regenerate cron.d-file if previous deletion of old files could not be done automatically, fixes #1076
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-12 08:33:19 +01:00
Maurice Preuß (envoyr)
da8d315e77 remove hardcoded logo height
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-01-11 22:43:00 +01:00
Michael Kaufmann
82af9af1e1 group email-domains in overview if there are email addresses for multiple domains
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-11 22:22:39 +01:00
Michael Kaufmann
cb67e3ae63 continue checking domains even if no config was found, thx knox
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-11 21:07:00 +01:00
Michael Kaufmann
82d15c4dc2 fixes for ValidateAcmeWebroot command
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-11 20:47:07 +01:00
Michael Kaufmann
6d048e2cee fix default mysql-dbserver for customers if not allowed to use the default (id=0) one; fixes #1075
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-11 19:41:24 +01:00
Michael Kaufmann
87bd80eea1 reenable access to ftp view for customers with ftps=0 because the main account is always being created
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-01-11 14:58:18 +01:00
276 changed files with 12148 additions and 10933 deletions

40
.drone.yml Normal file
View File

@@ -0,0 +1,40 @@
kind: pipeline
name: deploy-froxlor
type: docker
platform:
os: linux
arch: arm64
trigger:
branch:
- upgrade-2
event:
include:
- push
steps:
- name: deploy
image: cr.wks/drone/drone-rsync:latest
settings:
hosts: ["rechner02.maketank.net"]
source: ./
target: ~/froxlor-test
user: www-data
exclude: ['vendor', '.git*', '*drone.yml', '.settings', '.buildpath', '.editorconfig', '.project', '.travis.yml']
args: '-v --delete'
log_level: quiet
key:
from_secret: ssh-www-data-maketank-rsa
command_timeout: 10m
- name: compose-install
image: appleboy/drone-ssh
settings:
host:
- rechner02.maketank.net
username: www-data
key:
from_secret: ssh-www-data-maketank-rsa
script:
- cd ~/froxlor-test && composer install --no-dev

View File

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

1
.gitignore vendored
View File

@@ -13,6 +13,7 @@ logs/*
*~
.well-known
.idea
.DS_Store
*.iml
img/
vendor/

View File

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

View File

@@ -265,11 +265,12 @@ return [
'extras.directoryprotection' => lng('menue.extras.extras') . " / " . lng('menue.extras.directoryprotection'),
'extras.pathoptions' => lng('menue.extras.extras') . " / " . lng('menue.extras.pathoptions'),
'extras.logger' => lng('menue.extras.extras') . " / " . lng('menue.logger.logger'),
'extras.backup' => lng('menue.extras.extras') . " / " . lng('menue.extras.backup'),
'extras.export' => lng('menue.extras.extras') . " / " . lng('menue.extras.export'),
'traffic' => lng('menue.traffic.traffic'),
'traffic.http' => lng('menue.traffic.traffic') . " / HTTP",
'traffic.ftp' => lng('menue.traffic.traffic') . " / FTP",
'traffic.mail' => lng('menue.traffic.traffic') . " / Mail"
'traffic.mail' => lng('menue.traffic.traffic') . " / Mail",
'misc.documentation' => lng('admin.documentation'),
],
'save_method' => 'storeSettingField',
'advanced_mode' => true

View File

@@ -138,6 +138,26 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_req_limit_per_interval' => [
'label' => lng('serversettings.req_limit_per_interval'),
'settinggroup' => 'system',
'varname' => 'req_limit_per_interval',
'type' => 'number',
'min' => 30,
'default' => 60,
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_req_limit_interval' => [
'label' => lng('serversettings.req_limit_interval'),
'settinggroup' => 'system',
'varname' => 'req_limit_interval',
'type' => 'number',
'min' => 5,
'default' => 60,
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'customer_accountprefix' => [
'label' => lng('serversettings.accountprefix'),
'settinggroup' => 'customer',
@@ -210,13 +230,13 @@ return [
'onlyif' => 1
]
],
'system_backupenabled' => [
'label' => lng('serversettings.backupenabled'),
'system_exportenabled' => [
'label' => lng('serversettings.exportenabled'),
'settinggroup' => 'system',
'varname' => 'backupenabled',
'varname' => 'exportenabled',
'type' => 'checkbox',
'default' => false,
'cronmodule' => 'froxlor/backup',
'cronmodule' => 'froxlor/export',
'save_method' => 'storeSettingField'
],
'system_createstdsubdom_default' => [

View File

@@ -107,9 +107,22 @@ return [
'varname' => 'enabled',
'type' => 'checkbox',
'default' => false,
'save_method' => 'storeSettingField',
'required_otp' => true
],
'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'
],
'update_channel' => [
'system_update_channel' => [
'label' => lng('serversettings.update_channel'),
'settinggroup' => 'system',
'varname' => 'update_channel',
@@ -122,7 +135,7 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_validatedomain' => [
'system_validate_domain' => [
'label' => lng('serversettings.validate_domain'),
'settinggroup' => 'system',
'varname' => 'validate_domain',
@@ -307,7 +320,7 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'hide_incompatible_settings' => [
'system_hide_incompatible_settings' => [
'label' => lng('serversettings.hide_incompatible_settings'),
'settinggroup' => 'system',
'varname' => 'hide_incompatible_settings',

View File

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

View File

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

View File

@@ -60,7 +60,7 @@ return [
'apache2'
]
],
'system_apache_itksupport' => [
'system_apacheitksupport' => [
'label' => lng('serversettings.apache_itksupport'),
'settinggroup' => 'system',
'varname' => 'apacheitksupport',
@@ -181,7 +181,8 @@ return [
'label' => lng('serversettings.logfiles_format'),
'settinggroup' => 'system',
'varname' => 'logfiles_format',
'type' => 'text',
'type' => (strpos(Settings::Get('system.logfiles_format'), '"') !== false ? 'textarea' : 'text'),
'string_regexp' => '/^[^\0\r\n<>]*$/i',
'default' => '',
'string_emptyallowed' => true,
'save_method' => 'storeSettingField',
@@ -229,7 +230,7 @@ return [
'nginx'
]
],
'system_customersslpath' => [
'system_customer_ssl_path' => [
'label' => lng('serversettings.customerssl_directory'),
'settinggroup' => 'system',
'varname' => 'customer_ssl_path',
@@ -287,7 +288,7 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_apache_globaldiropt' => [
'system_apacheglobaldiropt' => [
'label' => lng('serversettings.apache_globaldiropt'),
'settinggroup' => 'system',
'varname' => 'apacheglobaldiropt',
@@ -307,7 +308,8 @@ return [
'type' => 'text',
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
'default' => '/etc/init.d/apache2 reload',
'save_method' => 'storeSettingField'
'save_method' => 'storeSettingField',
'required_otp' => true
],
'system_phpreload_command' => [
'label' => lng('serversettings.phpreload_command'),
@@ -319,7 +321,8 @@ return [
'save_method' => 'storeSettingField',
'websrv_avail' => [
'nginx'
]
],
'required_otp' => true
],
'system_nginx_php_backend' => [
'label' => lng('serversettings.nginx_php_backend'),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,7 +31,7 @@ return [
'title' => lng('admin.nameserversettings'),
'icon' => 'fa-solid fa-globe',
'fields' => [
'nameserver_enable' => [
'system_bind_enable' => [
'label' => lng('serversettings.bindenable'),
'settinggroup' => 'system',
'varname' => 'bind_enable',
@@ -80,7 +80,8 @@ return [
'type' => 'text',
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
'default' => '/etc/init.d/bind9 reload',
'save_method' => 'storeSettingField'
'save_method' => 'storeSettingField',
'required_otp' => true
],
'system_nameservers' => [
'label' => lng('serversettings.nameservers'),
@@ -111,7 +112,8 @@ return [
'string_delimiter' => ',',
'string_emptyallowed' => true,
'default' => '',
'save_method' => 'storeSettingField'
'save_method' => 'storeSettingField',
'required_otp' => true
],
'system_powerdns_mode' => [
'label' => lng('serversettings.powerdns_mode'),

View File

@@ -31,7 +31,7 @@ return [
'title' => lng('admin.dkimsettings'),
'icon' => 'fa-solid fa-fingerprint',
'fields' => [
'dkim_enabled' => [
'dkim_use_dkim' => [
'label' => lng('dkim.use_dkim'),
'settinggroup' => 'dkim',
'varname' => 'use_dkim',
@@ -40,7 +40,7 @@ return [
'save_method' => 'storeSettingFieldInsertBindTask',
'overview_option' => true
],
'dkim_prefix' => [
'dkim_dkim_prefix' => [
'label' => lng('dkim.dkim_prefix'),
'settinggroup' => 'dkim',
'varname' => 'dkim_prefix',
@@ -59,7 +59,7 @@ return [
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'dkim_domains' => [
'dkim_dkim_domains' => [
'label' => lng('dkim.dkim_domains'),
'settinggroup' => 'dkim',
'varname' => 'dkim_domains',
@@ -68,7 +68,7 @@ return [
'default' => 'domains',
'save_method' => 'storeSettingField'
],
'dkim_dkimkeys' => [
'dkim_dkim_dkimkeys' => [
'label' => lng('dkim.dkim_dkimkeys'),
'settinggroup' => 'dkim',
'varname' => 'dkim_dkimkeys',
@@ -77,7 +77,7 @@ return [
'default' => 'dkim-keys.conf',
'save_method' => 'storeSettingField'
],
'dkim_algorithm' => [
'dkim_dkim_algorithm' => [
'label' => lng('dkim.dkim_algorithm'),
'settinggroup' => 'dkim',
'varname' => 'dkim_algorithm',
@@ -92,7 +92,7 @@ return [
'save_method' => 'storeSettingFieldInsertBindTask',
'advanced_mode' => true
],
'dkim_servicetype' => [
'dkim_dkim_servicetype' => [
'label' => lng('dkim.dkim_servicetype'),
'settinggroup' => 'dkim',
'varname' => 'dkim_servicetype',
@@ -105,7 +105,7 @@ return [
'save_method' => 'storeSettingFieldInsertBindTask',
'advanced_mode' => true
],
'dkim_keylength' => [
'dkim_dkim_keylength' => [
'label' => [
'title' => lng('dkim.dkim_keylength.title'),
'description' => lng('dkim.dkim_keylength.description', [Settings::Get('dkim.dkim_prefix')])
@@ -120,7 +120,7 @@ return [
],
'save_method' => 'storeSettingFieldInsertBindTask'
],
'dkim_notes' => [
'dkim_dkim_notes' => [
'label' => lng('dkim.dkim_notes'),
'settinggroup' => 'dkim',
'varname' => 'dkim_notes',
@@ -130,14 +130,15 @@ return [
'save_method' => 'storeSettingFieldInsertBindTask',
'advanced_mode' => true
],
'dkimrestart_command' => [
'dkim_dkimrestart_command' => [
'label' => lng('dkim.dkimrestart_command'),
'settinggroup' => 'dkim',
'varname' => 'dkimrestart_command',
'type' => 'text',
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
'default' => '/etc/init.d/dkim-filter restart',
'save_method' => 'storeSettingField'
'save_method' => 'storeSettingField',
'required_otp' => true
]
]
]

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,87 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
return [
'groups' => [
'backup' => [
'title' => lng('backup'),
'icon' => 'fa-solid fa-sliders',
'advanced_mode' => true,
'fields' => [
'backup_enabled' => [
'label' => lng('serversettings.backup_enabled'),
'settinggroup' => 'backup',
'varname' => 'enabled',
'type' => 'checkbox',
'default' => false,
'save_method' => 'storeSettingField',
'overview_option' => true,
'cronmodule' => 'froxlor/backup'
],
'backup_default_storage' => [
'label' => lng('serversettings.backup_default_storage'),
'settinggroup' => 'backup',
'varname' => 'default_storage',
'type' => 'select',
'default' => '1',
'option_options_method' => [
'\\Froxlor\\Backup\\Backup',
'getBackupStorages'
],
'save_method' => 'storeSettingField'
],
'backup_default_retention' => [
'label' => lng('serversettings.backup_default_retention'),
'settinggroup' => 'backup',
'varname' => 'default_retention',
'type' => 'number',
'default' => 3,
'min' => 0,
'save_method' => 'storeSettingField',
],
'backup_default_customer_access' => [
'label' => lng('serversettings.backup_default_customer_access'),
'settinggroup' => 'backup',
'varname' => 'default_customer_access',
'type' => 'checkbox',
'default' => true,
'save_method' => 'storeSettingField',
],
'backup_default_pgp_public_key' => [
'label' => lng('serversettings.backup_default_pgp_public_key'),
'settinggroup' => 'backup',
'varname' => 'default_pgp_public_key',
'type' => 'textarea',
'default' => '',
'save_method' => 'storeSettingField',
'plausibility_check_method' => [
'\\Froxlor\\Validate\\Check',
'checkPgpPublicKeySetting'
],
],
]
]
]
];

View File

@@ -77,6 +77,7 @@ if (($page == 'admins' || $page == 'overview') && $userinfo['change_serversettin
$result['switched_user'] = CurrentUser::getData();
$result['adminsession'] = 1;
$result['userid'] = $result['adminid'];
session_regenerate_id(true);
CurrentUser::setData($result);
$log->logAction(

View File

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

View File

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

183
admin_backups.php Normal file
View File

@@ -0,0 +1,183 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
const AREA = 'admin';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\Backups;
use Froxlor\Api\Commands\BackupStorages;
use Froxlor\FroxlorLogger;
use Froxlor\UI\Collection;
use Froxlor\UI\HTML;
use Froxlor\UI\Listing;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
$id = (int)Request::any('id');
if (($page == 'backups' || $page == 'overview')) {
if ($action == '') {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "viewed admin_backups");
try {
$admin_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/admin/tablelisting.backups.php';
$collection = (new Collection(Backups::class, $userinfo))
->withPagination($admin_list_data['backups_list']['columns'], $admin_list_data['backups_list']['default_sorting']);
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $admin_list_data, 'backups_list'),
'actions_links' => [
[
'href' => $linker->getLink(['section' => 'backups', 'page' => $page, 'action' => 'restore']),
'label' => lng('admin.backups_restore'),
'icon' => 'fa-solid fa-file-import',
'class' => 'btn-outline-secondary'
],
[
'href' => $linker->getLink(['section' => 'backups', 'page' => 'storages']),
'label' => lng('admin.backup_storages'),
'icon' => 'fa-solid fa-hard-drive',
'class' => 'btn-outline-secondary',
'visible' => $userinfo['change_serversettings'] == '1'
]
]
]);
} elseif ($action == 'delete' && $id != 0) {
} elseif ($action == 'add') {
} elseif ($action == 'edit' && $id != 0) {
} elseif ($action == 'restore') {
}
} else if ($page == 'storages' && $userinfo['change_serversettings'] == '1') {
if ($action == '') {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "list backup storages");
try {
$backup_storage_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/admin/tablelisting.backup_storages.php';
$collection = (new Collection(BackupStorages::class, $userinfo))
->withPagination($backup_storage_list_data['backup_storages_list']['columns'], $backup_storage_list_data['backup_storages_list']['default_sorting']);
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $backup_storage_list_data, 'backup_storages_list'),
'actions_links' => [
[
'href' => $linker->getLink(['section' => 'backups', 'page' => 'backups']),
'label' => lng('admin.backups'),
'icon' => 'fa-solid fa-reply'
],
[
'href' => $linker->getLink(['section' => 'backups', 'page' => $page, 'action' => 'add']),
'label' => lng('admin.backup_storage_add')
]
]
]);
} elseif ($action == 'delete' && $id != 0) {
try {
$json_result = BackupStorages::getLocal($userinfo, [
'id' => $id
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
if ($result['id'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
BackupStorages::getLocal($userinfo, [
'id' => $id
])->delete();
Response::redirectTo($filename, [
'page' => $page
]);
} else {
HTML::askYesNo('backup_backup_server_reallydelete', $filename, [
'id' => $id,
'page' => $page,
'action' => $action
], $result['id']);
}
}
} elseif ($action == 'add') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
try {
BackupStorages::getLocal($userinfo, $_POST)->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page
]);
} else {
$admin_add_data = include_once dirname(__FILE__) . '/lib/formfields/admin/backup_storages/formfield.backup_storage_add.php';
UI::view('user/form.html.twig', [
'formaction' => $linker->getLink(['section' => 'backups']),
'formdata' => $admin_add_data['backup_storage_add']
]);
}
} elseif ($action == 'edit' && $id != 0) {
try {
$json_result = BackupStorages::getLocal($userinfo, [
'id' => $id
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
if ($result['id'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
try {
BackupStorages::getLocal($userinfo, $_POST)->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page
]);
} else {
$backup_storage_edit_data = include_once dirname(__FILE__) . '/lib/formfields/admin/backup_storages/formfield.backup_storage_edit.php';
UI::view('user/form.html.twig', [
'formaction' => $linker->getLink(['section' => 'backups', 'id' => $id]),
'formdata' => $backup_storage_edit_data['backup_storage_edit'],
'editid' => $id
]);
}
}
}
} else {
Response::dynamicError('403');
}

View File

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

View File

@@ -27,6 +27,7 @@ const AREA = 'admin';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\Admins;
use Froxlor\Api\Commands\BackupStorages;
use Froxlor\Api\Commands\Customers;
use Froxlor\Api\Commands\MysqlServer;
use Froxlor\CurrentUser;
@@ -93,6 +94,7 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
$result['switched_user'] = CurrentUser::getData();
$result['adminsession'] = 0;
$result['userid'] = $result['customerid'];
session_regenerate_id(true);
CurrentUser::setData($result);
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "switched user and is now '" . $destination_user . "'");
@@ -224,6 +226,23 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
$hosting_plans[$row['id']] = $row['name'];
}
// backup storages
$backup_storages = [];
if (Settings::Get('backup.enabled') == '1' && $userinfo['change_serversettings'] == '1') {
$backup_storages = [
0 => lng('backup.storage_none')
];
try {
$result_json = BackupStorages::getLocal($userinfo)->listing();
$result_decoded = json_decode($result_json, true)['data']['list'];
foreach ($result_decoded as $storagedata) {
$backup_storages[$storagedata['id']] = "[" . $storagedata['type'] . "] " . html_entity_decode($storagedata['description']);
}
} catch (Exception $e) {
/* just none */
}
}
$customer_add_data = include_once dirname(__FILE__) . '/lib/formfields/admin/customer/formfield.customer_add.php';
UI::view('user/form.html.twig', [
@@ -306,6 +325,23 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
$hosting_plans[$row['id']] = $row['name'];
}
// backup storages
$backup_storages = [];
if (Settings::Get('backup.enabled') == '1' && $userinfo['change_serversettings'] == '1') {
$backup_storages = [
0 => lng('backup.storage_none')
];
try {
$result_json = BackupStorages::getLocal($userinfo)->listing();
$result_decoded = json_decode($result_json, true)['data']['list'];
foreach ($result_decoded as $storagedata) {
$backup_storages[$storagedata['id']] = "[" . $storagedata['type'] . "] " . html_entity_decode($storagedata['description']);
}
} catch (Exception $e) {
/* just none */
}
}
$available_admins_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_ADMINS . "`
WHERE (`customers` = '-1' OR `customers` > `customers_used`)

View File

@@ -114,15 +114,11 @@ if ($page == 'domains' || $page == 'overview') {
} elseif ($alias_check['count'] > 0) {
Response::standardError('domains_cantdeletedomainwithaliases');
} else {
$showcheck = false;
if (Domain::domainHasMainSubDomains($id)) {
$showcheck = true;
}
HTML::askYesNoWithCheckbox('admin_domain_reallydelete', 'remove_subbutmain_domains', $filename, [
HTML::askYesNo('admin_domain_reallydelete', $filename, [
'id' => $id,
'page' => $page,
'action' => $action
], $idna_convert->decode($result['domain']), $showcheck);
], $idna_convert->decode($result['domain']));
}
}
} elseif ($action == 'add') {
@@ -252,21 +248,6 @@ if ($page == 'domains' || $page == 'overview') {
$domains[$row_domain['id']] = $idna_convert->decode($row_domain['domain']) . ' (' . $row_domain['loginname'] . ')';
}
$subtodomains = [
0 => lng('domains.nosubtomaindomain')
];
$result_domains_stmt = Database::prepare("
SELECT `d`.`id`, `d`.`domain`, `c`.`loginname` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c`
WHERE `d`.`aliasdomain` IS NULL AND `d`.`parentdomainid` = 0 AND `d`.`ismainbutsubto` = 0 " . $standardsubdomains . ($userinfo['customers_see_all'] ? '' : " AND `d`.`adminid` = :adminid") . "
AND `d`.`customerid`=`c`.`customerid` ORDER BY `loginname`, `domain` ASC
");
// params from above still valid
Database::pexecute($result_domains_stmt, $params);
while ($row_domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) {
$subtodomains[$row_domain['id']] = $idna_convert->decode($row_domain['domain']) . ' (' . $row_domain['loginname'] . ')';
}
$phpconfigs = [];
$configs = Database::query("
SELECT c.*, fc.description as interpreter
@@ -282,6 +263,12 @@ if ($page == 'domains' || $page == 'overview') {
}
}
$openbasedir = [
0 => lng('domain.docroot'),
1 => lng('domain.homedir'),
2 => lng('domain.docparent')
];
// create serveralias options
$serveraliasoptions = [
0 => lng('domains.serveraliasoption_wildcard'),
@@ -463,27 +450,6 @@ if ($page == 'domains' || $page == 'overview') {
$domains[$row_domain['id']] = $idna_convert->decode($row_domain['domain']);
}
$subtodomains = [
0 => lng('domains.nosubtomaindomain')
];
$result_domains_stmt = Database::prepare("
SELECT `d`.`id`, `d`.`domain` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c`
WHERE `d`.`aliasdomain` IS NULL AND `d`.`parentdomainid` = '0' AND `d`.`id` <> :id
AND `c`.`standardsubdomain`<>`d`.`id` AND `c`.`customerid`=`d`.`customerid`" . ($userinfo['customers_see_all'] ? '' : " AND `d`.`adminid` = :adminid") . "
ORDER BY `d`.`domain` ASC
");
$params = [
'id' => $result['id']
];
if ($userinfo['customers_see_all'] == '0') {
$params['adminid'] = $userinfo['adminid'];
}
Database::pexecute($result_domains_stmt, $params);
while ($row_domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) {
$subtodomains[$row_domain['id']] = $idna_convert->decode($row_domain['domain']);
}
if ($userinfo['ip'] == "-1") {
$result_ipsandports_stmt = Database::query("
SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `ssl`='0' ORDER BY `ip`, `port` ASC
@@ -545,6 +511,12 @@ if ($page == 'domains' || $page == 'overview') {
$result['temporary_ssl_redirect'] = $result['ssl_redirect'];
$result['ssl_redirect'] = ($result['ssl_redirect'] == 0 ? 0 : 1);
$openbasedir = [
0 => lng('domain.docroot'),
1 => lng('domain.homedir'),
2 => lng('domain.docparent')
];
$serveraliasoptions = [
0 => lng('domains.serveraliasoption_wildcard'),
1 => lng('domains.serveraliasoption_www'),
@@ -664,6 +636,23 @@ if ($page == 'domains' || $page == 'overview') {
'alert_msg' => lng('domains.import_description')
]);
}
} elseif ($action == 'duplicate') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
try {
Domains::getLocal($userinfo, $_POST)->duplicate();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page,
'searchfield' => 'd.domain_ace',
'searchtext' => $_POST['domain'] ?? ""
]);
} else {
Response::redirectTo($filename, [
'page' => 'overview'
]);
}
}
} elseif ($page == 'domainssleditor') {
require_once __DIR__ . '/ssl_editor.php';

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,6 +26,7 @@
declare(strict_types=1);
use Froxlor\Cli\ConfigDiff;
use Symfony\Component\Console\Application;
use Froxlor\Cli\RunApiCommand;
use Froxlor\Cli\ConfigServices;
@@ -35,6 +36,7 @@ use Froxlor\Cli\UpdateCommand;
use Froxlor\Cli\InstallCommand;
use Froxlor\Cli\MasterCron;
use Froxlor\Cli\UserCommand;
use Froxlor\Cli\ValidateAcmeWebroot;
use Froxlor\Froxlor;
// validate correct php version
@@ -59,4 +61,6 @@ $application->add(new UpdateCommand());
$application->add(new InstallCommand());
$application->add(new MasterCron());
$application->add(new UserCommand());
$application->add(new ValidateAcmeWebroot());
$application->add(new ConfigDiff());
$application->run();

View File

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

921
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -51,7 +51,7 @@ $id = (int)Request::any('id');
if ($page == 'overview' || $page == 'domains') {
if ($action == '') {
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_domains::domains");
$log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "viewed customer_domains::domains");
$parentdomain_id = (int)Request::any('pid', '0');
@@ -73,10 +73,17 @@ if ($page == 'overview' || $page == 'domains') {
];
}
UI::view('user/table.html.twig', [
$table_tpl = 'table.html.twig';
if ($collection->count() == 0) {
$table_tpl = 'table-note.html.twig';
}
UI::view('user/' . $table_tpl, [
'listing' => Listing::format($collection, $domain_list_data, 'domain_list'),
'actions_links' => $actions_links,
'entity_info' => lng('domains.description')
'entity_info' => lng('domains.description'),
// alert-box
'type' => 'warning',
'alert_msg' => lng('domains.nodomainsassignedbyadmin')
]);
} elseif ($action == 'delete' && $id != 0) {
try {
@@ -130,6 +137,7 @@ if ($page == 'overview' || $page == 'domains') {
AND `parentdomainid` = '0'
AND `email_only` = '0'
AND `caneditdomain` = '1'
AND `deactivated` = '0'
ORDER BY `domain` ASC");
Database::pexecute($stmt, [
"customerid" => $userinfo['customerid']
@@ -139,6 +147,14 @@ if ($page == 'overview' || $page == 'domains') {
$domains[$row['domain']] = $idna_convert->decode($row['domain']);
}
// check of there are any domains to be used
if (count($domains) <= 0) {
// no, possible direct URL access, redirect to overview
Response::redirectTo($filename, [
'page' => $page
]);
}
$aliasdomains[0] = lng('domains.noaliasdomain');
$domains_stmt = Database::prepare("SELECT `d`.`id`, `d`.`domain` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c`
WHERE `d`.`aliasdomain` IS NULL
@@ -373,6 +389,23 @@ if ($page == 'overview' || $page == 'domains') {
} else {
Response::standardError('domains_canteditdomain');
}
} elseif ($action == 'jqSpeciallogfileNote') {
$domainid = intval($_POST['id']);
$newval = intval($_POST['newval']);
try {
$json_result = SubDomains::getLocal($userinfo, [
'id' => $domainid
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
if ($newval != $result['speciallogfile']) {
echo json_encode(['changed' => true, 'info' => lng('admin.speciallogwarning')]);
exit();
}
echo 0;
exit();
}
} elseif ($page == 'domainssleditor') {
require_once __DIR__ . '/ssl_editor.php';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

155
index.php
View File

@@ -26,6 +26,7 @@
const AREA = 'login';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\FroxlorRPC;
use Froxlor\CurrentUser;
use Froxlor\Customer\Customer;
use Froxlor\Database\Database;
@@ -37,10 +38,10 @@ use Froxlor\PhpHelper;
use Froxlor\Settings;
use Froxlor\System\Crypt;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\User;
use Froxlor\Validate\Validate;
use Froxlor\Language;
if ($action == '') {
$action = 'login';
@@ -53,9 +54,15 @@ if ($action == '2fa_entercode') {
Response::redirectTo('index.php');
exit();
}
$smessage = isset($_GET['showmessage']) ? (int)$_GET['showmessage'] : 0;
$message = "";
if ($smessage > 0) {
$message = lng('error.2fa_wrongcode');
}
// show template to enter code
UI::view('login/enter2fa.html.twig', [
'pagetitle' => lng('login.2fa')
'pagetitle' => lng('login.2fa'),
'message' => $message
]);
} elseif ($action == '2fa_verify') {
// verify code from 2fa code-enter form
@@ -68,25 +75,25 @@ if ($action == '2fa_entercode') {
// verify entered code
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
$result = ($_SESSION['secret_2fa'] == 'email' ? true : $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3));
// get user-data
$table = $_SESSION['uidtable_2fa'];
$field = $_SESSION['uidfield_2fa'];
$uid = $_SESSION['uid_2fa'];
$isadmin = $_SESSION['unfo_2fa'];
// either the code is valid when using authenticator-app, or we will select userdata by id and entered code
// which is temporarily stored for the customer when using email-2fa
if ($result) {
// get user-data
$table = $_SESSION['uidtable_2fa'];
$field = $_SESSION['uidfield_2fa'];
$uid = $_SESSION['uid_2fa'];
$isadmin = $_SESSION['unfo_2fa'];
$sel_param = [
'uid' => $uid
];
if ($_SESSION['secret_2fa'] == 'email') {
// verify code by selecting user by id and the temp. stored code,
// so only if it's the correct code, we get the user-data
$sel_stmt = Database::prepare("SELECT * FROM $table WHERE `" . $field . "` = :uid AND `data_2fa` = :code");
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid AND `data_2fa` = :code");
$sel_param['code'] = $code;
} else {
// Authenticator-verification has already happened at this point, so just get the user-data
$sel_stmt = Database::prepare("SELECT * FROM $table WHERE `" . $field . "` = :uid");
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid");
}
$userinfo = Database::pexecute_first($sel_stmt, $sel_param);
// whoops, no (valid) user? Start again
@@ -108,19 +115,54 @@ if ($action == '2fa_entercode') {
// when using email-2fa, remove the one-time-code
if ($userinfo['type_2fa'] == '1') {
$del_stmt = Database::prepare("UPDATE $table SET `data_2fa` = '' WHERE `" . $field . "` = :uid");
$del_stmt = Database::prepare("UPDATE " . $table . " SET `data_2fa` = '' WHERE `" . $field . "` = :uid");
$userinfo = Database::pexecute_first($del_stmt, [
'uid' => $uid
]);
}
exit();
}
// wrong 2fa code - treat like "wrong password"
$stmt = Database::prepare("
UPDATE " . $table . "
SET `lastlogin_fail`= :lastlogin_fail, `loginfail_count`=`loginfail_count`+1
WHERE `" . $field . "`= :uid
");
Database::pexecute($stmt, [
"lastlogin_fail" => time(),
"uid" => $uid
]);
// get data for processing further
$stmt = Database::prepare("
SELECT `loginname`, `loginfail_count`, `lastlogin_fail` FROM " . $table . "
WHERE `" . $field . "`= :uid
");
$fail_user = Database::pexecute_first($stmt, [
"uid" => $uid
]);
if ($fail_user['loginfail_count'] >= Settings::Get('login.maxloginattempts') && $fail_user['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime'))) {
// Log failed login
$rstlog = FroxlorLogger::getInstanceOf([
'loginname' => $_SERVER['REMOTE_ADDR']
]);
$rstlog->logAction(FroxlorLogger::LOGIN_ACTION, LOG_WARNING, "User '" . $fail_user['loginname'] . "' entered wrong 2fa code too often.");
unset($fail_user);
Response::redirectTo('index.php', [
'showmessage' => '3'
]);
exit();
}
unset($fail_user);
// back to form
Response::redirectTo('index.php', [
'showmessage' => '2'
'action' => '2fa_entercode',
'showmessage' => '1'
]);
exit();
} elseif ($action == 'login') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (!empty($_POST)) {
$loginname = Validate::validate($_POST['loginname'], 'loginname');
$password = Validate::validate($_POST['password'], 'password');
@@ -390,13 +432,18 @@ if ($action == '2fa_entercode') {
}
$lastqrystr = "";
if (isset($_REQUEST['qrystr']) && $_REQUEST['qrystr'] != "") {
$lastqrystr = htmlspecialchars($_REQUEST['qrystr'], ENT_QUOTES);
$lastqrystr = urlencode($_REQUEST['qrystr']);
}
if (!empty($lastscript)) {
$_SESSION['lastscript'] = $lastscript;
}
if (!empty($lastqrystr)) {
$_SESSION['lastqrystr'] = $lastqrystr;
}
UI::view('login/login.html.twig', [
'pagetitle' => 'Login',
'lastscript' => $lastscript,
'lastqrystr' => $lastqrystr,
'upd_in_progress' => $update_in_progress,
'message' => $message,
'successmsg' => $successmessage
@@ -408,7 +455,7 @@ if ($action == 'forgotpwd') {
$adminchecked = false;
$message = '';
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (!empty($_POST)) {
$loginname = Validate::validate($_POST['loginname'], 'loginname');
$email = Validate::validateEmail($_POST['loginemail']);
$result_stmt = Database::prepare("SELECT `adminid`, `customerid`, `customernumber`, `firstname`, `name`, `company`, `email`, `loginname`, `def_language`, `deactivated` FROM `" . TABLE_PANEL_CUSTOMERS . "`
@@ -592,7 +639,7 @@ if ($action == 'forgotpwd') {
UI::view('login/fpwd.html.twig', [
'pagetitle' => lng('login.presend'),
'action' => $action,
'formaction' => 'index.php?action=' . $action,
'message' => $message,
]);
}
@@ -615,7 +662,7 @@ if ($action == 'resetpwd') {
$check = substr($activationcode, 40, 10);
if (substr(md5($third . $timestamp), 0, 10) == $check && $timestamp >= time() - 86400) {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (!empty($_POST)) {
$stmt = Database::prepare("SELECT `userid`, `admin` FROM `" . TABLE_PANEL_ACTIVATION . "`
WHERE `activationcode` = :activationcode");
$result = Database::pexecute_first($stmt, [
@@ -689,9 +736,62 @@ if ($action == 'resetpwd') {
}
}
// one-time link login
if ($action == 'll') {
if (!Froxlor::hasUpdates() && !Froxlor::hasDbUpdates()) {
$loginname = Request::get('ln');
$hash = Request::get('h');
if ($loginname && $hash) {
$sel_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_LOGINLINKS . "`
WHERE `loginname` = :loginname AND `hash` = :hash
");
try {
$entry = Database::pexecute_first($sel_stmt, ['loginname' => $loginname, 'hash' => $hash]);
} catch (Exception $e) {
$entry = false;
}
if ($entry) {
// delete entry
$del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_LOGINLINKS . "` WHERE `loginname` = :loginname AND `hash` = :hash");
Database::pexecute($del_stmt, ['loginname' => $loginname, 'hash' => $hash]);
if (time() <= $entry['valid_until']) {
$valid = true;
// validate source ip if specified
if (!empty($entry['allowed_from'])) {
$valid = false;
$ip_list = explode(",", $entry['allowed_from']);
if (FroxlorRPC::validateAllowedFrom($ip_list, $_SERVER['REMOTE_ADDR'])) {
$valid = true;
}
}
if ($valid) {
// login user / select only non-deactivated (in case the user got deactivated after generating the link)
$userinfo_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `loginname`= :loginname AND `deactivated` = 0");
try {
$userinfo = Database::pexecute_first($userinfo_stmt, [
"loginname" => $loginname
]);
} catch (Exception $e) {
$userinfo = false;
}
if ($userinfo) {
$userinfo['userid'] = $userinfo['customerid'];
$userinfo['adminsession'] = 0;
finishLogin($userinfo);
}
}
}
}
}
}
Response::redirectTo('index.php');
}
function finishLogin($userinfo)
{
if (isset($userinfo['userid']) && $userinfo['userid'] != '') {
session_regenerate_id(true);
CurrentUser::setData($userinfo);
$language = $userinfo['def_language'] ?? Settings::Get('panel.standardlanguage');
@@ -705,29 +805,34 @@ function finishLogin($userinfo)
}
$qryparams = [];
if (isset($_POST['qrystr']) && $_POST['qrystr'] != "") {
parse_str(urldecode($_POST['qrystr']), $qryparams);
if (!empty($_SESSION['lastqrystr'])) {
parse_str(urldecode($_SESSION['lastqrystr']), $qryparams);
unset($_SESSION['lastqrystr']);
}
if ($userinfo['adminsession'] == '1') {
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
Response::redirectTo('admin_updates.php?page=overview');
} else {
if (isset($_POST['script']) && $_POST['script'] != "") {
if (preg_match("/customer\_/", $_POST['script']) === 1) {
if (!empty($_SESSION['lastscript'])) {
$lastscript = $_SESSION['lastscript'];
unset($_SESSION['lastscript']);
if (preg_match("/customer\_/", $lastscript) === 1) {
Response::redirectTo('admin_customers.php', [
"page" => "customers"
]);
} else {
Response::redirectTo($_POST['script'], $qryparams);
Response::redirectTo($lastscript, $qryparams);
}
} else {
Response::redirectTo('admin_index.php', $qryparams);
}
}
} else {
if (isset($_POST['script']) && $_POST['script'] != "") {
Response::redirectTo($_POST['script'], $qryparams);
if (!empty($_SESSION['lastscript'])) {
$lastscript = $_SESSION['lastscript'];
unset($_SESSION['lastscript']);
Response::redirectTo($lastscript, $qryparams);
} else {
Response::redirectTo('customer_index.php', $qryparams);
}

View File

@@ -223,6 +223,8 @@ CREATE TABLE `panel_customers` (
`api_allowed` tinyint(1) NOT NULL default '1',
`logviewenabled` tinyint(1) NOT NULL default '0',
`allowed_mysqlserver` text NOT NULL,
`backup` int(11) NOT NULL default '1',
`access_backups` tinyint(1) NOT NULL default '1',
PRIMARY KEY (`customerid`),
UNIQUE KEY `loginname` (`loginname`)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC;
@@ -278,7 +280,6 @@ CREATE TABLE `panel_domains` (
`phpsettingid` INT( 11 ) UNSIGNED NOT NULL DEFAULT '1',
`mod_fcgid_starter` int(4) default '-1',
`mod_fcgid_maxrequests` int(4) default '-1',
`ismainbutsubto` int(11) unsigned NOT NULL default '0',
`letsencrypt` tinyint(1) NOT NULL default '0',
`hsts` varchar(10) NOT NULL default '0',
`hsts_sub` tinyint(1) NOT NULL default '0',
@@ -555,7 +556,7 @@ opcache.validate_timestamps'),
('system', 'defaultip', '1'),
('system', 'defaultsslip', ''),
('system', 'phpappendopenbasedir', '/tmp/'),
('system', 'deactivateddocroot', ''),
('system', 'deactivateddocroot', '/var/www/html/froxlor/templates/misc/deactivated/'),
('system', 'mailpwcleartext', '0'),
('system', 'last_tasks_run', '000000'),
('system', 'nameservers', ''),
@@ -642,12 +643,12 @@ opcache.validate_timestamps'),
('system', 'leprivatekey', 'unset'),
('system', 'lepublickey', 'unset'),
('system', 'letsencryptca', 'letsencrypt'),
('system', 'letsencryptchallengepath', '/var/www/froxlor'),
('system', 'letsencryptchallengepath', '/var/www/html/froxlor'),
('system', 'letsencryptkeysize', '4096'),
('system', 'letsencryptreuseold', 0),
('system', 'leenabled', '0'),
('system', 'leapiversion', '2'),
('system', 'backupenabled', '0'),
('system', 'exportenabled', '0'),
('system', 'dnsenabled', '0'),
('system', 'dns_server', 'Bind'),
('system', 'apacheglobaldiropt', ''),
@@ -670,6 +671,7 @@ opcache.validate_timestamps'),
('system', 'leaccount', ''),
('system', 'nssextrausers', '1'),
('system', 'le_domain_dnscheck', '1'),
('system', 'le_domain_dnscheck_resolver', '1.1.1.1'),
('system', 'ssl_protocols', 'TLSv1.2'),
('system', 'tlsv13_cipher_list', ''),
('system', 'honorcipherorder', '0'),
@@ -696,9 +698,17 @@ opcache.validate_timestamps'),
('system', 'distribution', ''),
('system', 'update_channel', 'stable'),
('system', 'updatecheck_data', ''),
('system', 'update_notify_last', '2.0.7'),
('system', 'update_notify_last', '2.0.20'),
('system', 'traffictool', 'goaccess'),
('system', 'req_limit_per_interval', 60),
('system', 'req_limit_interval', 60),
('backup', 'enabled', 0),
('backup', 'default_storage', '1'),
('backup', 'default_customer_access', '1'),
('backup', 'default_pgp_public_key', ''),
('backup', 'default_retention', '3'),
('api', 'enabled', '0'),
('api', 'customer_default', '1'),
('2fa', 'enabled', '1'),
('panel', 'decimal_places', '4'),
('panel', 'adminmail', 'admin@SERVERNAME'),
@@ -740,8 +750,8 @@ opcache.validate_timestamps'),
('panel', 'logo_overridetheme', '0'),
('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'),
('panel', 'version', '2.0.7'),
('panel', 'db_version', '202212060');
('panel', 'version', '2.0.20'),
('panel', 'db_version', '202305240');
DROP TABLE IF EXISTS `panel_tasks`;
@@ -910,7 +920,8 @@ INSERT INTO `cronjobs_run` (`id`, `module`, `cronfile`, `cronclass`, `interval`,
(3, 'froxlor/reports', 'usage_report', '\\Froxlor\\Cron\\Traffic\\ReportsCron', '1 DAY', '1', 'cron_usage_report'),
(4, 'froxlor/core', 'mailboxsize', '\\Froxlor\\Cron\\System\\MailboxsizeCron', '6 HOUR', '1', 'cron_mailboxsize'),
(5, 'froxlor/letsencrypt', 'letsencrypt', '\\Froxlor\\Cron\\Http\\LetsEncrypt\\AcmeSh', '5 MINUTE', '0', 'cron_letsencrypt'),
(6, 'froxlor/backup', 'backup', '\\Froxlor\\Cron\\System\\BackupCron', '1 DAY', '0', 'cron_backup');
(6, 'froxlor/export', 'export', '\\Froxlor\\Cron\\System\\ExportCron', '1 HOUR', '0', 'cron_export'),
(7, 'froxlor/backup', 'backup', '\\Froxlor\\Cron\\Backup\\BackupCron', '1 DAY', '0', 'cron_backup');
DROP TABLE IF EXISTS `ftp_quotalimits`;
@@ -981,7 +992,9 @@ CREATE TABLE IF NOT EXISTS `domain_ssl_settings` (
`ssl_cert_chainfile` mediumtext,
`ssl_csr_file` mediumtext,
`ssl_fullchain_file` mediumtext,
`expirationdate` datetime DEFAULT NULL,
`validfromdate` datetime DEFAULT NULL,
`validtodate` datetime DEFAULT NULL,
`issuer` varchar(255) NOT NULL default '',
PRIMARY KEY (`id`),
UNIQUE KEY (`domainid`)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
@@ -1046,4 +1059,48 @@ CREATE TABLE `panel_usercolumns` (
KEY adminid (adminid),
KEY customerid (customerid)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
DROP TABLE IF EXISTS `panel_loginlinks`;
CREATE TABLE `panel_loginlinks` (
`hash` varchar(500) NOT NULL,
`loginname` varchar(50) NOT NULL,
`valid_until` int(15) NOT NULL,
`allowed_from` text NOT NULL,
UNIQUE KEY `loginname` (`loginname`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
DROP TABLE IF EXISTS `panel_backup_storages`;
CREATE TABLE `panel_backup_storages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(255) NOT NULL,
`type` varchar(255) NOT NULL DEFAULT 'local',
`region` varchar(255) NULL,
`bucket` varchar(255) NULL,
`destination_path` varchar(255) NOT NULL,
`hostname` varchar(255) NULL,
`username` varchar(255) NULL,
`password` text,
`pgp_public_key` text,
`retention` int(3) NOT NULL DEFAULT 3,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
INSERT INTO `panel_backup_storages` (`id`, `description`, `destination_path`) VALUES
(1, 'Local backup storage', '/var/customers/backups');
DROP TABLE IF EXISTS `panel_backups`;
CREATE TABLE `panel_backups` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`adminid` int(11) NOT NULL,
`customerid` int(11) NOT NULL,
`loginname` varchar(255) NOT NULL,
`size` bigint(20) NOT NULL,
`storage_id` int(11) NOT NULL,
`filename` varchar(255) NOT NULL,
`created_at` int(15) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
FROXLORSQL;

View File

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

View File

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

View File

@@ -23,11 +23,11 @@
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
use Froxlor\Froxlor;
use Froxlor\FileDir;
use Froxlor\Database\Database;
use Froxlor\Settings;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\Install\Update;
use Froxlor\Settings;
if (!defined('_CRON_UPDATE')) {
if (!defined('AREA') || (defined('AREA') && AREA != 'admin') || !isset($userinfo['loginname']) || (isset($userinfo['loginname']) && $userinfo['loginname'] == '')) {
@@ -38,10 +38,9 @@ if (!defined('_CRON_UPDATE')) {
// last 0.10.x release
if (Froxlor::isFroxlorVersion('0.10.38.3')) {
$update_to = '2.0.0-beta1';
Update::showUpdateStep("Updating from 0.10.38.3 to ".$update_to, false);
Update::showUpdateStep("Updating from 0.10.38.3 to " . $update_to, false);
Update::showUpdateStep("Removing unused table");
Database::query("DROP TABLE IF EXISTS `panel_sessions`;");
@@ -83,7 +82,7 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
Database::query("ALTER TABLE `" . TABLE_PANEL_ADMINS . "` DROP COLUMN `domains_see_all`;");
Update::lastStepStatus(0);
Update::showUpdateStep("Checking for multiple mysql-servers to allow acccess to customers for existing databases");
Update::showUpdateStep("Checking for multiple mysql-servers to allow access to customers for existing databases");
$dbservers_stmt = Database::query("
SELECT `customerid`,
GROUP_CONCAT(DISTINCT `dbserver` SEPARATOR ',') as allowed_mysqlserver
@@ -94,7 +93,8 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
while ($dbserver = $dbservers_stmt->fetch(PDO::FETCH_ASSOC)) {
if (isset($dbserver['allowed_mysqlserver']) && !empty($dbserver['allowed_mysqlserver'])) {
$allowed_mysqlserver = json_encode(explode(",", $dbserver['allowed_mysqlserver']));
Database::pexecute($upd_stmt, ['allowed_mysql_server' => $allowed_mysqlserver, 'customerid' => $dbserver['customerid']]);
Database::pexecute($upd_stmt,
['allowed_mysql_server' => $allowed_mysqlserver, 'customerid' => $dbserver['customerid']]);
}
}
Update::lastStepStatus(0);
@@ -141,12 +141,13 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
// none of the files existed
Update::lastStepStatus(0);
} else {
Update::lastStepStatus(1, 'manual commands needed', 'Please run the following commands manually:<br><pre>' . $del_list . '</pre>');
Update::lastStepStatus(1, 'manual commands needed',
'Please run the following commands manually:<br><pre>' . $del_list . '</pre>');
}
}
Update::showUpdateStep("Adding new settings");
$panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int) $_POST['panel_settings_mode'] : 0;
$panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int)$_POST['panel_settings_mode'] : 0;
Settings::AddNew("panel.settings_mode", $panel_settings_mode);
$system_distribution = isset($_POST['system_distribution']) ? $_POST['system_distribution'] : '';
Settings::AddNew("system.distribution", $system_distribution);
@@ -193,7 +194,6 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
}
if (Froxlor::isDatabaseVersion('202112310')) {
Update::showUpdateStep("Adjusting traffic tool settings");
$traffic_tool = Settings::Get('system.awstats_enabled') == 1 ? 'awstats' : 'webalizer';
Settings::AddNew("system.traffictool", $traffic_tool);
@@ -204,20 +204,31 @@ if (Froxlor::isDatabaseVersion('202112310')) {
}
if (Froxlor::isDatabaseVersion('202211030')) {
Update::showUpdateStep("Creating backward compatibility for cronjob");
$complete_filedir = Froxlor::getInstallDir() . '/scripts';
mkdir($complete_filedir, 0750, true);
$newCronBin = Froxlor::getInstallDir().'/bin/froxlor-cli';
$compCron = <<<EOF
$disabled = explode(',', ini_get('disable_functions'));
$exec_allowed = !in_array('exec', $disabled);
// check whether old files could be deleted in previous updates and if not,
// user should run cron to regenerate cron.d-file manually as he will run
// the other commands manually only after the update so this file would be deleted too
if ($exec_allowed) {
$complete_filedir = Froxlor::getInstallDir() . '/scripts';
mkdir($complete_filedir, 0750, true);
$newCronBin = Froxlor::getInstallDir() . '/bin/froxlor-cli';
$compCron = <<<EOF
<?php
chmod('$newCronBin', 0755);
// re-create cron.d configuration file
exec('$newCronBin froxlor:cron -r 99');
exit;
EOF;
file_put_contents($complete_filedir.'/froxlor_master_cronjob.php', $compCron);
Update::lastStepStatus(0);
file_put_contents($complete_filedir . '/froxlor_master_cronjob.php', $compCron);
Update::lastStepStatus(0);
} else {
$cron_run_cmd = 'chmod +x ' . FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . PHP_EOL;
$cron_run_cmd .= FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . ' froxlor:cron -r 99';
Update::lastStepStatus(1, 'manual commands needed',
'Please run the following commands manually:<br><pre>' . $cron_run_cmd . '</pre>');
}
Froxlor::updateToDbVersion('202212060');
}
@@ -257,20 +268,28 @@ if (Froxlor::isFroxlorVersion('2.0.3')) {
$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)
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");
$newCronBin = Froxlor::getInstallDir() . '/bin/froxlor-cli';
$compCron = <<<EOF
$disabled = explode(',', ini_get('disable_functions'));
$exec_allowed = !in_array('exec', $disabled);
if ($exec_allowed) {
$newCronBin = Froxlor::getInstallDir() . '/bin/froxlor-cli';
$compCron = <<<EOF
<?php
chmod('$newCronBin', 0755);
// re-create cron.d configuration file
exec('$newCronBin froxlor:cron -r 99');
exit;
EOF;
file_put_contents($complete_filedir . '/froxlor_master_cronjob.php', $compCron);
Update::lastStepStatus(0);
file_put_contents($complete_filedir . '/froxlor_master_cronjob.php', $compCron);
Update::lastStepStatus(0);
} else {
$cron_run_cmd = 'chmod +x ' . FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . PHP_EOL;
$cron_run_cmd .= FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . ' froxlor:cron -r 99';
Update::lastStepStatus(1, 'manual commands needed',
'Please run the following commands manually:<br><pre>' . $cron_run_cmd . '</pre>');
}
}
Froxlor::updateToVersion('2.0.4');
}
@@ -301,3 +320,180 @@ if (Froxlor::isFroxlorVersion('2.0.6')) {
Froxlor::updateToVersion('2.0.7');
}
if (Froxlor::isDatabaseVersion('202212060')) {
Update::showUpdateStep("Validating acme.sh challenge path");
$acmesh_challenge_dir = Settings::Get('system.letsencryptchallengepath');
$system_letsencryptchallengepath_upd = isset($_POST['system_letsencryptchallengepath_upd']) ? $_POST['system_letsencryptchallengepath_upd'] : $acmesh_challenge_dir;
if ($acmesh_challenge_dir != $system_letsencryptchallengepath_upd) {
Settings::Set('system.letsencryptchallengepath', $system_letsencryptchallengepath_upd);
if ((int)Settings::Get('system.leenabled') == 1) {
// create JSON string for --apply
$dist = Settings::Get('system.distribution');
$webserver = Settings::Get('system.webserver');
if ($webserver == 'apache2') {
$webserver = 'apache22';
if (Settings::Get('system.apache24')) {
$webserver = 'apache24';
}
}
$apply_json = '{"http":"' . $webserver . '","dns":"x","smtp":"x","mail":"x","ftp":"x","distro":"' . $dist . '","system":[]}';
Update::lastStepStatus(1, 'manual commands needed',
"Please reconfigure webserver service using <pre>bin/froxlor-cli froxlor:config-services --apply='" . $apply_json . "'</pre>" .
'<br>or adjust the path manually in <pre>' . Settings::Get('system.letsencryptacmeconf') . '</pre>' .
'<br><br>In case you already have certificates issued, run the following command to validate and correct the webroot used for renewal:<br>' .
'<pre>bin/froxlor-cli froxlor:validate-acme-webroot</pre><br>'
);
} else {
Update::lastStepStatus(0);
}
} else {
Update::lastStepStatus(0);
}
Froxlor::updateToDbVersion('202301120');
}
if (Froxlor::isFroxlorVersion('2.0.7')) {
Update::showUpdateStep("Updating from 2.0.7 to 2.0.8", false);
// adjust file-logging to be set to froxlor/logs/
$logtypes = explode(',', Settings::Get('logger.logtypes'));
if (in_array('file', $logtypes)) {
Update::showUpdateStep("Adjusting froxlor logfile for system-logging to be stored in logs/froxlor.log");
Settings::Set('logger.logfile', 'froxlor.log');
Update::lastStepStatus(0);
}
Froxlor::updateToVersion('2.0.8');
}
if (Froxlor::isDatabaseVersion('202301120')) {
Update::showUpdateStep("Adding new setting for DNS resolver when using Let's Encrypt");
$system_le_domain_dnscheck_resolver = isset($_POST['system_le_domain_dnscheck_resolver']) ? $_POST['system_le_domain_dnscheck_resolver'] : '1.1.1.1';
Settings::AddNew("system.le_domain_dnscheck_resolver", $system_le_domain_dnscheck_resolver);
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202301180');
}
if (Froxlor::isFroxlorVersion('2.0.8')) {
Update::showUpdateStep("Updating from 2.0.8 to 2.0.9", false);
Froxlor::updateToVersion('2.0.9');
}
if (Froxlor::isFroxlorVersion('2.0.9')) {
Update::showUpdateStep("Updating from 2.0.9 to 2.0.10", false);
Froxlor::updateToVersion('2.0.10');
}
if (Froxlor::isDatabaseVersion('202301180')) {
Update::showUpdateStep("Adding new setting for 'Allow API access' default value for new customers");
Settings::AddNew("api.customer_default", "1");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202302030');
}
if (Froxlor::isFroxlorVersion('2.0.10')) {
Update::showUpdateStep("Updating from 2.0.10 to 2.0.11", false);
Froxlor::updateToVersion('2.0.11');
}
if (Froxlor::isFroxlorVersion('2.0.11')) {
Update::showUpdateStep("Updating from 2.0.11 to 2.0.12", false);
Froxlor::updateToVersion('2.0.12');
}
if (Froxlor::isFroxlorVersion('2.0.12')) {
Update::showUpdateStep("Updating from 2.0.12 to 2.0.13", false);
Froxlor::updateToVersion('2.0.13');
}
if (Froxlor::isDatabaseVersion('202302030')) {
Update::showUpdateStep("Correcting language mapping of templates created pre 2.0.x");
// languages from 0.10.x
$language_mapping_comp = [
'de' => 'Deutsch',
'en' => 'English',
'fr' => 'Fran&ccedil;ais',
'pt' => 'Portugu&ecirc;s',
'it' => 'Italiano',
'nl' => 'Nederlands',
'se' => 'Svenska',
'cz' => '&#268;esk&aacute; republika'
];
$upd_tpl_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_TEMPLATES . "` SET `language` = :iso WHERE `language` = :lng");
foreach ($language_mapping_comp as $iso => $lang) {
Database::pexecute($upd_tpl_stmt, ['iso' => $iso, 'lng' => $lang]);
}
Update::lastStepStatus(0);
Update::showUpdateStep("Enhancing ssl data table");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` CHANGE `expirationdate` `validtodate` datetime DEFAULT NULL;");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` ADD `validfromdate` datetime DEFAULT NULL AFTER `ssl_fullchain_file`;");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` ADD `issuer` varchar(255) NOT NULL default '' AFTER `validtodate`;");
Update::lastStepStatus(0);
Update::showUpdateStep("Filling new ssl data fields with existing certificate data");
$crt_upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` SET `validfromdate` = :validfromdate, `issuer` = :issuer WHERE `id` = :id");
$crt_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`");
Database::pexecute($crt_stmt);
while ($cert = $crt_stmt->fetch(\PDO::FETCH_ASSOC)) {
$cert_content = openssl_x509_parse($cert['ssl_cert_file']);
if (is_array($cert_content)) {
$validfromdate = empty($cert_content['validFrom_time_t']) ? null : date("Y-m-d H:i:s", $cert_content['validFrom_time_t']);
$issuer = $cert_content['issuer']['O'] ?? "";
Database::pexecute($crt_upd_stmt, ['validfromdate' => $validfromdate, 'issuer' => $issuer, 'id' => $cert['id']]);
}
}
// clear possible user customized columns
Database::query("DELETE FROM `" . TABLE_PANEL_USERCOLUMNS . "` WHERE `section` = 'sslcertificates_list'");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202303150');
}
if (Froxlor::isFroxlorVersion('2.0.13')) {
Update::showUpdateStep("Updating from 2.0.13 to 2.0.14", false);
Froxlor::updateToVersion('2.0.14');
}
if (Froxlor::isFroxlorVersion('2.0.14')) {
Update::showUpdateStep("Updating from 2.0.14 to 2.0.15", false);
Froxlor::updateToVersion('2.0.15');
}
if (Froxlor::isDatabaseVersion('202303150')) {
Update::showUpdateStep("Adding new request rate limit settings");
Settings::AddNew("system.req_limit_per_interval", "60");
Settings::AddNew("system.req_limit_interval", "60");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202304260');
}
if (Froxlor::isFroxlorVersion('2.0.15')) {
Update::showUpdateStep("Updating from 2.0.15 to 2.0.16", false);
Froxlor::updateToVersion('2.0.16');
}
if (Froxlor::isFroxlorVersion('2.0.16')) {
Update::showUpdateStep("Updating from 2.0.16 to 2.0.17", false);
Froxlor::updateToVersion('2.0.17');
}
if (Froxlor::isFroxlorVersion('2.0.17')) {
Update::showUpdateStep("Updating from 2.0.17 to 2.0.18", false);
Froxlor::updateToVersion('2.0.18');
}
if (Froxlor::isFroxlorVersion('2.0.18')) {
Update::showUpdateStep("Updating from 2.0.18 to 2.0.19", false);
Froxlor::updateToVersion('2.0.19');
}
if (Froxlor::isFroxlorVersion('2.0.19')) {
Update::showUpdateStep("Updating from 2.0.19 to 2.0.20", false);
Froxlor::updateToVersion('2.0.20');
}

View File

@@ -0,0 +1,141 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\Install\Update;
use Froxlor\Settings;
if (!defined('_CRON_UPDATE')) {
if (!defined('AREA') || (defined('AREA') && AREA != 'admin') || !isset($userinfo['loginname']) || (isset($userinfo['loginname']) && $userinfo['loginname'] == '')) {
header('Location: ../../../../index.php');
exit();
}
}
if (Froxlor::isDatabaseVersion('202304260')) {
//Update::showUpdateStep("Cleaning domains table");
//Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` DROP COLUMN `ismainbutsubto`;");
Update::lastStepStatus(0);
Update::showUpdateStep("Creating new tables and fields");
Database::query("DROP TABLE IF EXISTS `panel_loginlinks`;");
$sql = "CREATE TABLE `panel_loginlinks` (
`hash` varchar(500) NOT NULL,
`loginname` varchar(50) NOT NULL,
`valid_until` int(15) NOT NULL,
`allowed_from` text NOT NULL,
UNIQUE KEY `loginname` (`loginname`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
Database::query($sql);
Update::lastStepStatus(0);
Update::showUpdateStep("Adjusting setting for deactivated webroot");
$current_deactivated_webroot = Settings::Get('system.deactivateddocroot');
if (empty($current_deactivated_webroot)) {
Settings::Set('system.deactivateddocroot', FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/templates/misc/deactivated/'));
Update::lastStepStatus(0);
} else {
Update::lastStepStatus(1, 'Customized setting, not changing');
}
Update::showUpdateStep("Creating new tables and fields for backups");
Database::query("DROP TABLE IF EXISTS `". TABLE_PANEL_BACKUP_STORAGES ."`;");
$sql = "CREATE TABLE `". TABLE_PANEL_BACKUP_STORAGES ."` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(255) NOT NULL,
`type` varchar(255) NOT NULL DEFAULT 'local',
`region` varchar(255) NULL,
`bucket` varchar(255) NULL,
`destination_path` varchar(255) NOT NULL,
`hostname` varchar(255) NULL,
`username` varchar(255) NULL,
`password` text,
`pgp_public_key` text,
`retention` int(3) NOT NULL DEFAULT 3,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
Database::query($sql);
Database::query("
INSERT INTO `panel_backup_storages` (`id`, `description`, `destination_path`) VALUES
(1, 'Local backup storage', '/var/customers/backups');
");
Database::query("DROP TABLE IF EXISTS `". TABLE_PANEL_BACKUPS ."`;");
$sql = "CREATE TABLE `". TABLE_PANEL_BACKUPS ."` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`adminid` int(11) NOT NULL,
`customerid` int(11) NOT NULL,
`loginname` varchar(255) NOT NULL,
`size` bigint(20) NOT NULL,
`storage_id` int(11) NOT NULL,
`filename` varchar(255) NOT NULL,
`created_at` int(15) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
Database::query($sql);
// add customer backup-target-storage
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` ADD `backup` int(11) NOT NULL default '1' AFTER `allowed_mysqlserver`;");
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` ADD `access_backups` tinyint(1) NOT NULL default '1' AFTER `backup`;");
Update::lastStepStatus(0);
Update::showUpdateStep("Adding new backup settings");
Settings::AddNew('backup.enabled', 0);
Settings::AddNew('backup.default_storage', 1);
Settings::AddNew('backup.default_customer_access', 1);
Settings::AddNew('backup.default_pgp_public_key', '');
Settings::AddNew('backup.default_retention', 3);
Update::lastStepStatus(0);
Update::showUpdateStep("Adjusting cronjobs");
Database::query("
UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET
`module`= 'froxlor/export',
`cronfile` = 'export',
`cronclass` = '\\Froxlor\\Cron\\System\\ExportCron',
`interval` = '1 HOUR',
`desc_lng_key` = 'cron_export'
WHERE `module` = 'froxlor/backup'
");
Database::query("
INSERT INTO `" . TABLE_PANEL_CRONRUNS . "` SET
`module`= 'froxlor/backup',
`cronfile` = 'backup',
`cronclass` = '\\Froxlor\\Cron\\Backup\\BackupCron',
`interval` = '1 DAY',
`isactive` = '0',
`desc_lng_key` = 'cron_backup'
");
Update::lastStepStatus(0);
Update::showUpdateStep("Adjusting system for data-export function");
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "`SET `varname` = 'exportenabled' WHERE `settinggroup`= 'system' AND `varname`= 'backupenabled");
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "`SET `value` = REPLACE(`value`, 'extras.backup', 'extras.export') WHERE `settinggroup` = 'panel' AND `varname` = 'customer_hide_options'");
Database::query("DELETE FROM `" . TABLE_PANEL_USERCOLUMNS . "` WHERE `section` = 'backup_list'");
Database::query("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202305240');
}

View File

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

View File

@@ -27,16 +27,16 @@ use Froxlor\Froxlor;
use Froxlor\FileDir;
use Froxlor\Config\ConfigParser;
use Froxlor\Install\Update;
use Froxlor\Settings;
$preconfig = [
'title' => '2.x updates',
'title' => '2.0.x updates',
'fields' => []
];
$return = [];
if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
$description = 'We have rearranged the settings and split them into basic and advanced categories. This makes it easier for users who do not need all the detailed or very specific settings and options and gives a better overview of the basic/mostly used settings.';
$return['panel_settings_mode_note'] = ['type' => 'infotext', 'value' => $description];
$question = '<strong>Chose settings mode (you can change that at any time)</strong>';
$return['panel_settings_mode'] = [
'type' => 'select',
@@ -45,11 +45,11 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
1 => 'Advanced'
],
'selected' => 1,
'label' => $question
'label' => $question,
'prior_infotext' => $description
];
$description = 'The configuration page now can preselect a distribution, please select your current distribution';
$return['system_distribution_note'] = ['type' => 'infotext', 'value' => $description];
$question = '<strong>Select distribution</strong>';
$config_dir = FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/lib/configfiles/');
// show list of available distro's
@@ -68,9 +68,44 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
'type' => 'select',
'select_var' => $distributions_select,
'selected' => '',
'label' => $question
'label' => $question,
'prior_infotext' => $description
];
}
if (Update::versionInUpdate($current_db_version, '202301120')) {
$acmesh_challenge_dir = rtrim(FileDir::makeCorrectDir(Settings::Get('system.letsencryptchallengepath')), "/");
$recommended = rtrim(FileDir::makeCorrectDir(Froxlor::getInstallDir()), "/");
if ((int) Settings::Get('system.leenabled') == 1 && $acmesh_challenge_dir != $recommended) {
$has_preconfig = true;
$description = 'ACME challenge docroot from settings differs from the current installation directory.';
$question = '<strong>Validate Let\'s Encrypt challenge path (recommended value: ' . $recommended . ')</strong>';
$return['system_letsencryptchallengepath_upd'] = [
'type' => 'text',
'value' => $recommended,
'placeholder' => $acmesh_challenge_dir,
'label' => $question,
'prior_infotext' => $description,
'mandatory' => true,
];
}
}
if (Update::versionInUpdate($current_db_version, '202301180')) {
if ((int) Settings::Get('system.leenabled') == 1) {
$has_preconfig = true;
$description = 'Froxlor now supports to set an external DNS resolver for the Let\'s Encrypt pre-check.';
$question = '<strong>Specify a DNS resolver IP (recommended value: 1.1.1.1 or similar)</strong>';
$return['system_le_domain_dnscheck_resolver'] = [
'type' => 'text',
'pattern' => '^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$|^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$|^\s*$',
'value' => '1.1.1.1',
'placeholder' => '1.1.1.1',
'label' => $question,
'prior_infotext' => $description,
];
}
}
$preconfig['fields'] = $return;
return $preconfig;

View File

@@ -0,0 +1,55 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
use Froxlor\Froxlor;
use Froxlor\FileDir;
use Froxlor\Config\ConfigParser;
use Froxlor\Install\Update;
use Froxlor\Settings;
$preconfig = [
'title' => '2.1.x updates',
'fields' => []
];
$return = [];
if (Update::versionInUpdate($current_version, '2.1.0-dev1')) {
// Backup
$description = 'Froxlor now comes with a backup capability (More info see [DOCS LINK].';
$question = '<strong>Would you like to enable the backup-feature (default: yes)</strong>';
$return['panel_settings_mode'] = [
'type' => 'select',
'select_var' => [
0 => 'No',
1 => 'Yes'
],
'selected' => 1,
'label' => $question,
'prior_infotext' => $description
];
}
$preconfig['fields'] = $return;
return $preconfig;

View File

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

View File

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

View File

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

View File

@@ -26,6 +26,7 @@
namespace Froxlor\Api;
use Exception;
use Froxlor\Http\RateLimiter;
use Froxlor\Settings;
use voku\helper\AntiXSS;
@@ -52,6 +53,8 @@ class Api
if (Settings::Get('api.enabled') != 1) {
throw new Exception('API is not enabled. Please contact the administrator if you think this is wrong.', 400);
}
RateLimiter::run();
}
/**
@@ -117,6 +120,6 @@ class Api
private function stripcslashesDeep($value)
{
return is_array($value) ? array_map([$this, 'stripcslashesDeep'], $value) : stripcslashes($value);
return is_array($value) ? array_map([$this, 'stripcslashesDeep'], $value) : (!empty($value) ? stripcslashes($value) : $value);
}
}

View File

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

View File

@@ -39,12 +39,12 @@ abstract class ApiParameter
/**
*
* @param array $params
* @param array|null $params
* optional, array of parameters (var=>value) for the command
*
* @throws Exception
*/
public function __construct($params = null)
public function __construct(array $params = null)
{
if (!is_null($params)) {
$params = $this->trimArray($params);
@@ -57,7 +57,7 @@ abstract class ApiParameter
*
* @param array $input
*
* @return array
* @return string|array
*/
private function trimArray($input)
{
@@ -79,9 +79,9 @@ abstract class ApiParameter
/**
* get specific parameter which also has and unlimited-field
*
* @param string $param
* @param string|null $param
* parameter to get out of the request-parameter list
* @param string $ul_field
* @param string|null $ul_field
* parameter to get out of the request-parameter list
* @param bool $optional
* default: false
@@ -91,7 +91,7 @@ abstract class ApiParameter
* @return mixed
* @throws Exception
*/
protected function getUlParam($param = null, $ul_field = null, $optional = false, $default = 0)
protected function getUlParam(string $param = null, string $ul_field = null, bool $optional = false, $default = 0)
{
$param_value = (int)$this->getParam($param, $optional, $default);
$ul_field_value = $this->getBoolParam($ul_field, true, 0);
@@ -102,11 +102,11 @@ abstract class ApiParameter
}
/**
* get specific parameter from the parameterlist;
* get specific parameter from the parameter list;
* check for existence and != empty if needed.
* Maybe more in the future
*
* @param string $param
* @param string|null $param
* parameter to get out of the request-parameter list
* @param bool $optional
* default: false
@@ -116,7 +116,7 @@ abstract class ApiParameter
* @return mixed
* @throws Exception
*/
protected function getParam($param = null, $optional = false, $default = '')
protected function getParam(string $param = null, bool $optional = false, $default = '')
{
// does it exist?
if (!isset($this->cmd_params[$param])) {
@@ -128,7 +128,7 @@ abstract class ApiParameter
return $default;
}
// is it empty? - test really on string, as value 0 is being seen as empty by php
if ($this->cmd_params[$param] === "") {
if (!is_array($this->cmd_params[$param]) && trim($this->cmd_params[$param]) === "") {
if ($optional === false) {
// get module + function for better error-messages
$inmod = $this->getModFunctionString();
@@ -142,7 +142,7 @@ abstract class ApiParameter
/**
* returns "module::function()" for better error-messages (missing parameter etc.)
* makes debugging a whole lot more comfortable
* makes debugging a lot more comfortable
*
* @param int $level
* depth of backtrace, default 2
@@ -152,7 +152,7 @@ abstract class ApiParameter
*
* @return string
*/
private function getModFunctionString($level = 1, $max_level = 5, $trace = null)
private function getModFunctionString(int $level = 1, int $max_level = 5, $trace = null)
{
// which class called us
$_class = get_called_class();
@@ -174,7 +174,7 @@ abstract class ApiParameter
/**
* getParam wrapper for boolean parameter
*
* @param string $param
* @param string|null $param
* parameter to get out of the request-parameter list
* @param bool $optional
* default: false
@@ -183,7 +183,7 @@ abstract class ApiParameter
*
* @return string
*/
protected function getBoolParam($param = null, $optional = false, $default = false)
protected function getBoolParam(string $param = null, bool $optional = false, $default = false)
{
$_default = '0';
if ($default) {

View File

@@ -95,7 +95,7 @@ class Admins extends ApiCommand implements ResourceEntity
public function listing()
{
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list admins");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list admins");
$query_fields = [];
$result_stmt = Database::prepare("
SELECT *
@@ -407,7 +407,7 @@ class Admins extends ApiCommand implements ResourceEntity
];
$result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] get admin '" . $result['loginname'] . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get admin '" . $result['loginname'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "loginname '" . $loginname . "'");
@@ -584,6 +584,18 @@ class Admins extends ApiCommand implements ResourceEntity
$theme = Settings::Get('panel.default_theme');
}
if (empty(trim($name))) {
Response::standardError([
'stringisempty',
'admin.name'
], '', true);
}
if (empty(trim($email))) {
Response::standardError([
'stringisempty',
'admin.email'
], '', true);
}
if (!Validate::validateEmail($email)) {
Response::standardError('emailiswrong', $email, true);
} else {
@@ -705,7 +717,7 @@ class Admins extends ApiCommand implements ResourceEntity
WHERE `adminid` = :adminid
");
Database::pexecute($upd_stmt, $upd_data, true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] edited admin '" . $result['loginname'] . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] edited admin '" . $result['loginname'] . "'");
// get all admin-data for return-array
$result = $this->apiCall('Admins.get', [

View File

@@ -0,0 +1,487 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor\Api\Commands;
use Exception;
use Froxlor\Api\ApiCommand;
use Froxlor\Api\ResourceEntity;
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
use Froxlor\UI\Response;
use Froxlor\Validate\Validate;
use PDO;
/**
* @since 2.1.0
*/
class BackupStorages extends ApiCommand implements ResourceEntity
{
const SUPPORTED_TYPES = [
'local',
'ftp',
'sftp',
'rsync',
's3',
];
/**
* lists all backup storages entries
*
* @param array $sql_search
* optional array with index = fieldname, and value = array with 'op' => operator (one of <, > or =),
* LIKE is used if left empty and 'value' => searchvalue
* @param int $sql_limit
* optional specify number of results to be returned
* @param int $sql_offset
* optional specify offset for resultset
* @param array $sql_orderby
* optional array with index = fieldname and value = ASC|DESC to order the resultset by one or more
* fields
*
* @access admin
* @return string json-encoded array count|list
* @throws Exception
*/
public function listing()
{
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list backup storages");
$query_fields = [];
$result_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` ". $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit()
);
Database::pexecute($result_stmt, $query_fields, true, true);
$result = [];
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $row;
}
return $this->response([
'count' => count($result),
'list' => $result
]);
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* returns the total number of backup storages
*
* @access admin
* @return string json-encoded response message
* @throws Exception
*/
public function listingCount()
{
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
$result_stmt = Database::prepare("
SELECT COUNT(*) as num_backup_storagess
FROM `" . TABLE_PANEL_BACKUP_STORAGES . "`
");
$result = Database::pexecute_first($result_stmt, null, true, true);
if ($result) {
return $this->response($result['num_backup_storagess']);
}
$this->response(0);
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* create a backup storage
*
* @param string $type
* required, backup storage type
* @param string $destination_path
* required, destination path for backup storage
* @param string $description
* required, description for backup storage
* @param string $region
* optional, required if type=s3. Region for backup storage (used for S3)
* @param string $bucket
* optional, required if type=s3. Bucket for backup storage (used for S3)
* @param string $hostname
* optional, required if type != local. Hostname for backup storage
* @param string $username
* optional, required if type != local. Username for backup storage (also used as access key for S3)
* @param string $password
* optional, required if type != local. Password for backup storage (also used as secret key for S3)
* @param string $pgp_public_key
* optional, pgp public key for backup storage
* @param string $retention
* optional, retention for backup storage (default 3)
*
* @access admin
* @return string json-encoded array
* @throws Exception
*/
public function add()
{
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
// required parameters
$type = $this->getParam('type');
$destination_path = $this->getParam('destination_path');
$description = $this->getParam('description');
// type related requirements
$optional_flags = [
'region' => true,
'bucket' => true,
'hostname' => true,
'username' => true,
'password' => true,
];
if (!in_array($type, self::SUPPORTED_TYPES)) {
throw new Exception("Unsupported storage type: '" . $type . "'", 406);
}
if ($type != 'local') {
$optional_flags['hostname'] = false;
$optional_flags['username'] = false;
$optional_flags['password'] = false;
}
if ($type == 's3') {
$optional_flags['region'] = false;
$optional_flags['bucket'] = false;
}
// parameters
$region = $this->getParam('region', $optional_flags['region']);
$bucket = $this->getParam('bucket', $optional_flags['bucket']);
$hostname = $this->getParam('hostname', $optional_flags['hostname']);
$username = $this->getParam('username', $optional_flags['username']);
$password = $this->getParam('password', $optional_flags['password']);
$pgp_public_key = $this->getParam('pgp_public_key', true, null);
$retention = $this->getParam('retention', true, 3);
// validation
$destination_path = FileDir::makeCorrectDir(Validate::validate($destination_path, 'destination_path', Validate::REGEX_DIR, '', [], true));
// TODO: add more validation
// pgp public key validation
if (!empty($pgp_public_key)) {
// check if gnupg extension is loaded
if (!extension_loaded('gnupg')) {
Response::standardError('gnupgextensionnotavailable', '', true);
}
// check if the pgp public key is a valid key
putenv('GNUPGHOME=' . sys_get_temp_dir());
if (gnupg_import(gnupg_init(), $pgp_public_key) === false) {
Response::standardError('invalidpgppublickey', '', true);
}
}
// store
$stmt = Database::prepare("
INSERT INTO `" . TABLE_PANEL_BACKUP_STORAGES . "` (
`description`,
`type`,
`region`,
`bucket`,
`destination_path`,
`hostname`,
`username`,
`password`,
`pgp_public_key`,
`retention`
) VALUES (
:description,
:type,
:region,
:bucket,
:destination_path,
:hostname,
:username,
:password,
:pgp_public_key,
:retention
)
");
$params = [
"description" => $description,
"type" => $type,
"region" => $region,
"bucket" => $bucket,
"destination_path" => $destination_path,
"hostname" => $hostname,
"username" => $username,
"password" => $password,
"pgp_public_key" => $pgp_public_key,
"retention" => $retention,
];
Database::pexecute($stmt, $params, true, true);
$id = Database::lastInsertId();
// return
$result = $this->apiCall('BackupStorages.get', [
'id' => $id
]);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] added backup storage '" . $result['description'] . "' (" . $result['type'] . ")");
return $this->response($result);
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* return a backup storage entry by id
*
* @param int $id
* the backup-storage-id
*
* @access admin
* @return string json-encoded array
* @throws Exception
*/
public function get()
{
$id = $this->getParam('id');
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
$result_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "`
WHERE `id` = :id"
);
$params = [
'id' => $id
];
$result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get backup storage '" . $result['description'] . "'");
return $this->response($result);
}
throw new Exception("Backup storage with " . $id . " could not be found", 404);
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* update a backup storage by given id
*
* @param int $id
* required, the backup-storage-id
* @param string $type
* optional, backup storage type
* @param string $destination_path
* optional, destination path for backup storage
* @param string $description
* required, description for backup storage
* @param string $region
* optional, region for backup storage (used for S3)
* @param string $bucket
* optional, bucket for backup storage (used for S3)
* @param string $hostname
* optional, hostname for backup storage
* @param string $username
* optional, username for backup storage (also used as access key for S3)
* @param string $password
* optional, password for backup storage (also used as secret key for S3)
* @param string $pgp_public_key
* optional, pgp public key for backup storage
* @param string $retention
* optional, retention for backup storage (default 3)
*
* @access admin
* @return string json-encoded array
* @throws Exception
*/
public function update()
{
$id = $this->getParam('id');
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
// validation
$result = $this->apiCall('BackupStorages.get', [
'id' => $id
]);
// parameters
$description = $this->getParam('description', true, $result['description']);
$type = $this->getParam('type', true, $result['type']);
$region = $this->getParam('region', true, $result['region']);
$bucket = $this->getParam('bucket', true, $result['bucket']);
$destination_path = $this->getParam('destination_path', true, $result['destination_path']);
$hostname = $this->getParam('hostname', true, $result['hostname']);
$username = $this->getParam('username', true, $result['username']);
$password = $this->getParam('password', true, '');
$pgp_public_key = $this->getParam('pgp_public_key', true, $result['pgp_public_key']);
$retention = $this->getParam('retention', true, $result['retention']);
if (!in_array($type, self::SUPPORTED_TYPES)) {
throw new Exception("Unsupported storage type: '" . $type . "'", 406);
}
if ($type != 'local') {
if (empty($hostname)) {
throw new Exception("Field 'hostname' cannot be empty", 406);
}
if (empty($username)) {
throw new Exception("Field 'username' cannot be empty", 406);
}
$password = Validate::validate($password, 'password', '', '', [], true);
}
if ($type == 's3') {
if (empty($region)) {
throw new Exception("Field 'region' cannot be empty", 406);
}
if (empty($bucket)) {
throw new Exception("Field 'bucket' cannot be empty", 406);
}
}
// validation
$destination_path = FileDir::makeCorrectDir(Validate::validate($destination_path, 'destination_path', Validate::REGEX_DIR, '', [], true));
// TODO: add more validation
// pgp public key validation
if (!empty($pgp_public_key) && $pgp_public_key != $result['pgp_public_key']) {
// check if gnupg extension is loaded
if (!extension_loaded('gnupg')) {
Response::standardError('gnupgextensionnotavailable', '', true);
}
// check if the pgp public key is a valid key
putenv('GNUPGHOME=' . sys_get_temp_dir());
if (gnupg_import(gnupg_init(), $pgp_public_key) === false) {
Response::standardError('invalidpgppublickey', '', true);
}
}
if (!empty($password)) {
$stmt = Database::prepare("UPDATE `" . TABLE_PANEL_BACKUP_STORAGES . "`
SET `password` = :password
WHERE `id` = :id
");
Database::pexecute($stmt, [
"id" => $id,
"password" => $password
], true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] updated password for backup-storage '" . $result['description'] . "'");
}
// update
$stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_BACKUP_STORAGES . "`
SET `description` = :description,
`type` = :type,
`region` = :region,
`bucket` = :bucket,
`destination_path` = :destination_path,
`hostname` = :hostname,
`username` = :username,
`pgp_public_key` = :pgp_public_key,
`retention` = :retention
WHERE `id` = :id
");
$params = [
"id" => $id,
"description" => $description,
"type" => $type,
"region" => $region,
"bucket" => $bucket,
"destination_path" => $destination_path,
"hostname" => $hostname,
"username" => $username,
"pgp_public_key" => $pgp_public_key,
"retention" => $retention,
];
Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] edited backup storage '" . $result['description'] . "'");
// return
$result = $this->apiCall('BackupStorages.get', [
'id' => $id
]);
return $this->response($result);
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* delete a backup-storage entry by id
*
* @param int $id
* required, the backup-storage-id
*
* @access admin
* @return string json-encoded array
* @throws Exception
*/
public function delete()
{
$id = $this->getParam('id');
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
// validation
$result = $this->apiCall('BackupStorages.get', [
'id' => $id
]);
// validate no-one's using it
// settings
if ($id == Settings::Get('backup.default_storage')) {
throw new Exception("Given backup storage is currently set as default storage and cannot be deleted.", 406);
}
// customers
$sel_stmt = Database::prepare("
SELECT COUNT(*) as num_storage_users
FROM `" . TABLE_PANEL_CUSTOMERS . "`
WHERE `backup` = :id
");
$storage_users_result = Database::pexecute_first($sel_stmt, ['id' => $id]);
if ($storage_users_result && $storage_users_result['num_storage_users'] > 0) {
throw new Exception("Given backup storage is currently assigned to " . $storage_users_result['num_storage_users'] . " customers and cannot be deleted.", 406);
}
// existing backups
$sel_stmt = Database::prepare("
SELECT COUNT(*) as num_storage_backups
FROM `" . TABLE_PANEL_BACKUPS . "`
WHERE `storage_id` = :id
");
$storage_backups_result = Database::pexecute_first($sel_stmt, ['id' => $id]);
if ($storage_backups_result && $storage_backups_result['num_storage_backups'] > 0) {
throw new Exception("Given backup storage has still " . $storage_backups_result['num_storage_backups'] . " backups on it and cannot be deleted.", 406);
}
// delete
$stmt = Database::prepare("
DELETE FROM `" . TABLE_PANEL_BACKUP_STORAGES . "`
WHERE `id` = :id
");
$params = [
"id" => $id
];
Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] deleted backup storage '" . $result['description'] . "'");
// return
return $this->response(true);
}
throw new Exception("Not allowed to execute given command.", 403);
}
}

View File

@@ -0,0 +1,211 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor\Api\Commands;
use Exception;
use Froxlor\Api\ApiCommand;
use Froxlor\Api\ResourceEntity;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
use PDO;
/**
* @since 2.1.0
*/
class Backups extends ApiCommand implements ResourceEntity
{
/**
* lists all admin entries
*
* @param array $sql_search
* optional array with index = fieldname, and value = array with 'op' => operator (one of <, > or =),
* LIKE is used if left empty and 'value' => searchvalue
* @param int $sql_limit
* optional specify number of results to be returned
* @param int $sql_offset
* optional specify offset for resultset
* @param array $sql_orderby
* optional array with index = fieldname and value = ASC|DESC to order the resultset by one or more
* fields
*
* @access admin
* @return string json-encoded array count|list
* @throws Exception
*/
public function listing()
{
if ($this->isAdmin()) {
// if we're an admin, list all backups of all the admins customers
// or optionally for one specific customer identified by id or loginname
$customerid = $this->getParam('customerid', true, 0);
$loginname = $this->getParam('loginname', true, '');
if (!empty($customerid) || !empty($loginname)) {
$result = $this->apiCall('Customers.get', [
'id' => $customerid,
'loginname' => $loginname
]);
$custom_list_result = [
$result
];
} else {
$_custom_list_result = $this->apiCall('Customers.listing');
$custom_list_result = $_custom_list_result['list'];
}
$customer_ids = [];
foreach ($custom_list_result as $customer) {
$customer_ids[] = $customer['customerid'];
}
if (empty($customer_ids)) {
throw new Exception("Required resource unsatisfied.", 405);
}
} else {
$customer_ids = [
$this->getUserDetail('customerid')
];
}
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list backups");
$query_fields = [];
$result_stmt = Database::prepare("
SELECT `b`.*, `a`.`loginname` as `adminname`
FROM `" . TABLE_PANEL_BACKUPS . "` `b`
LEFT JOIN `" . TABLE_PANEL_ADMINS . "` `a` USING(`adminid`)
WHERE `b`.`customerid` IN (" . implode(', ', $customer_ids) . ")
" . $this->getSearchWhere($query_fields, true) . $this->getOrderBy() . $this->getLimit()
);
Database::pexecute($result_stmt, $query_fields, true, true);
$result = [];
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $row;
}
return $this->response([
'count' => count($result),
'list' => $result
]);
}
/**
* returns the total number of backups for the given admin
*
* @access admin
* @return string json-encoded response message
* @throws Exception
*/
public function listingCount()
{
if ($this->isAdmin()) {
// if we're an admin, list all backups of all the admins customers
// or optionally for one specific customer identified by id or loginname
$customerid = $this->getParam('customerid', true, 0);
$loginname = $this->getParam('loginname', true, '');
if (!empty($customerid) || !empty($loginname)) {
$result = $this->apiCall('Customers.get', [
'id' => $customerid,
'loginname' => $loginname
]);
$custom_list_result = [
$result
];
} else {
$_custom_list_result = $this->apiCall('Customers.listing');
$custom_list_result = $_custom_list_result['list'];
}
$customer_ids = [];
foreach ($custom_list_result as $customer) {
$customer_ids[] = $customer['customerid'];
}
if (empty($customer_ids)) {
throw new Exception("Required resource unsatisfied.", 405);
}
} else {
$customer_ids = [
$this->getUserDetail('customerid')
];
}
$result_stmt = Database::prepare("
SELECT COUNT(*) as num_backups
FROM `" . TABLE_PANEL_BACKUPS . "` `b`
WHERE `b`.`customerid` IN (" . implode(', ', $customer_ids) . ")
");
$result = Database::pexecute_first($result_stmt, null, true, true);
if ($result) {
return $this->response($result['num_backups']);
}
$this->response(0);
}
/**
* You cannot add a backup entry
*
* @throws Exception
*/
public function add()
{
throw new Exception('You cannot add a backup entry', 303);
}
/**
* return a backup entry by id
*
* @param int $id
* optional, the backup-entry-id
*
* @access admin, customers
* @return string json-encoded array
* @throws Exception
*/
public function get()
{
throw new Exception("@TODO", 303);
}
/**
* You cannot update a backup entry
*
* @throws Exception
*/
public function update()
{
throw new Exception('You cannot update a backup entry', 303);
}
/**
* delete a backup entry by id
*
* @param int $id
* required, the backup-entry-id
*
* @access admin, customer
* @return string json-encoded array
* @throws Exception
*/
public function delete()
{
throw new Exception("@TODO", 303);
}
}

View File

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

View File

@@ -32,6 +32,7 @@ use Froxlor\Cron\TaskId;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
use Froxlor\System\Cronjob;
use Froxlor\UI\Response;
use Froxlor\Validate\Validate;
use PDO;
@@ -41,6 +42,14 @@ use PDO;
class Cronjobs extends ApiCommand implements ResourceEntity
{
private array $allowed_intervals = [
'MINUTE',
'HOUR',
'DAY',
'WEEK',
'MONTH'
];
/**
* You cannot add new cronjobs yet.
*/
@@ -118,6 +127,10 @@ class Cronjobs extends ApiCommand implements ResourceEntity
$interval_value = Validate::validate($interval_value, 'interval_value', '/^([0-9]+)$/Di', 'stringisempty', [], true);
$interval_interval = Validate::validate($interval_interval, 'interval_interval', '', '', [], true);
if (!in_array(strtoupper($interval_interval), $this->allowed_intervals)) {
Response::standardError('invalidcronjobintervalvalue', implode(", ", $this->allowed_intervals), true);
}
// put together interval value
$interval = $interval_value . ' ' . strtoupper($interval_interval);
@@ -134,7 +147,7 @@ class Cronjobs extends ApiCommand implements ResourceEntity
// insert task to re-generate the cron.d-file
Cronjob::inserttask(TaskId::REBUILD_CRON);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] cronjob with description '" . $result['module'] . '/' . $result['cronfile'] . "' has been updated by '" . $this->getUserDetail('loginname') . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] cronjob with description '" . $result['module'] . '/' . $result['cronfile'] . "' has been updated by '" . $this->getUserDetail('loginname') . "'");
$result = $this->apiCall('Cronjobs.get', [
'id' => $id
]);
@@ -164,7 +177,7 @@ class Cronjobs extends ApiCommand implements ResourceEntity
public function listing()
{
if ($this->isAdmin()) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list cronjobs");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list cronjobs");
$query_fields = [];
$result_stmt = Database::prepare("
SELECT `c`.* FROM `" . TABLE_PANEL_CRONRUNS . "` `c` " . $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit());

View File

@@ -100,7 +100,7 @@ class Customers extends ApiCommand implements ResourceEntity
AND `id`<> :stdd
");
$usages_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_DISKSPACE . "`
SELECT webspace, mail, mysql FROM `" . TABLE_PANEL_DISKSPACE . "`
WHERE `customerid` = :cid
ORDER BY `stamp` DESC LIMIT 1
");
@@ -109,11 +109,10 @@ class Customers extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
if ($show_usages) {
// get number of domains
Database::pexecute($domains_stmt, [
$domains = Database::pexecute_first($domains_stmt, [
'cid' => $row['customerid'],
'stdd' => $row['standardsubdomain']
]);
$domains = $domains_stmt->fetch(PDO::FETCH_ASSOC);
$row['domains'] = intval($domains['domains']);
// get disk-space usages for web, mysql and mail
$usages = Database::pexecute_first($usages_stmt, [
@@ -274,6 +273,13 @@ class Customers extends ApiCommand implements ResourceEntity
* @param array $allowed_mysqlserver
* optional, array of IDs of defined mysql-servers the customer is allowed to use,
* default is to allow the default dbserver (id=0)
* @param int $backup
* optional, either 0 to disable backup for this customer or a backup-storage-id
* where backups are to be stored, requires change_serversettings permissions,
* default is system-setting backup.default_storage
* @param bool $access_backups
* optional, where the customer is allowed to view backups, default is system-setting
* default_customer_access
*
* @access admin
* @return string json-encoded array
@@ -298,7 +304,7 @@ class Customers extends ApiCommand implements ResourceEntity
$fax = $this->getParam('fax', true, '');
$customernumber = $this->getParam('customernumber', true, '');
$def_language = $this->getParam('def_language', true, Settings::Get('panel.standardlanguage'));
$api_allowed = $this->getBoolParam('api_allowed', true, Settings::Get('api.enabled'));
$api_allowed = $this->getBoolParam('api_allowed', true, (Settings::Get('api.enabled') && Settings::Get('api.customer_default')));
$gender = (int)$this->getParam('gender', true, 0);
$custom_notes = $this->getParam('custom_notes', true, '');
$custom_notes_show = $this->getBoolParam('custom_notes_show', true, 0);
@@ -360,6 +366,24 @@ class Customers extends ApiCommand implements ResourceEntity
$p_allowed_mysqlserver = [];
}
if ($this->getUserDetail('change_serversettings')) {
$backup = $this->getParam('backup', true, Settings::Get('backup.default_storage'));
if ($backup > 0) {
try {
$this->apiCall('BackupStorages.get', [
'id' => $backup
]);
} catch (Exception $e) {
// not found or other issue, set default
$backup = Settings::Get('backup.default_storage');
}
}
$access_backups = $this->getBoolParam('access_backups', true, Settings::Get('backup.default_customer_access'));
} else {
$backup = Settings::Get('backup.default_storage');
$access_backups = Settings::Get('backup.default_customer_access');
}
// validation
$name = Validate::validate($name, 'name', Validate::REGEX_DESC_TEXT, '', [], true);
$firstname = Validate::validate($firstname, 'first name', Validate::REGEX_DESC_TEXT, '', [], true);
@@ -400,6 +424,10 @@ class Customers extends ApiCommand implements ResourceEntity
}
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
if (empty($allowed_phpconfigs) && $phpenabled == 1) {
Response::standardError('customerphpenabledbutnoconfig', '', true);
}
$allowed_mysqlserver = array();
if (! empty($p_allowed_mysqlserver) && is_array($p_allowed_mysqlserver)) {
foreach ($p_allowed_mysqlserver as $allowed_ms) {
@@ -534,7 +562,9 @@ class Customers extends ApiCommand implements ResourceEntity
'theme' => $_theme,
'custom_notes' => $custom_notes,
'custom_notes_show' => $custom_notes_show,
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver)
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver),
'backup' => $backup,
'access_backups' => $access_backups
];
$ins_stmt = Database::prepare("
@@ -577,7 +607,9 @@ class Customers extends ApiCommand implements ResourceEntity
`theme` = :theme,
`custom_notes` = :custom_notes,
`custom_notes_show` = :custom_notes_show,
`allowed_mysqlserver`= :allowed_mysqlserver
`allowed_mysqlserver`= :allowed_mysqlserver,
`backup` = :backup,
`access_backups` = :access_backups
");
Database::pexecute($ins_stmt, $ins_data, true, true);
@@ -895,7 +927,7 @@ class Customers extends ApiCommand implements ResourceEntity
$result['dbspace_used'] = 0;
}
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get customer '" . $result['loginname'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get customer '" . $result['loginname'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "loginname '" . $loginname . "'");
@@ -1025,6 +1057,13 @@ class Customers extends ApiCommand implements ResourceEntity
* @param array $allowed_mysqlserver
* optional, array of IDs of defined mysql-servers the customer is allowed to use,
* default is to allow the default dbserver (id=0)
* @param int $backup
* optional, either 0 to disable backup for this customer or a backup-storage-id
* where backups are to be stored, requires change_serversettings permissions,
* default is system-setting backup.default_storage
* @param bool $access_backups
* optional, where the customer is allowed to view backups, default is system-setting
* default_customer_access
*
* @access admin, customer
* @return string json-encoded array
@@ -1086,6 +1125,24 @@ class Customers extends ApiCommand implements ResourceEntity
$deactivated = $this->getBoolParam('deactivated', true, $result['deactivated']);
$theme = $this->getParam('theme', true, $result['theme']);
$allowed_mysqlserver = $this->getParam('allowed_mysqlserver', true, json_decode($result['allowed_mysqlserver'], true));
if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) {
$backup = $this->getParam('backup', true, $result['backup']);
if ($backup > 0) {
try {
$this->apiCall('BackupStorages.get', [
'id' => $backup
]);
} catch (Exception $e) {
// not found or other issue, dont update
$backup = $result['backup'];
}
}
$access_backups = $this->getBoolParam('access_backups', true, Settings::Get('backup.default_customer_access'));
} else {
$backup = $result['backup'];
$access_backups = $result['access_backups'];
}
} else {
// allowed parameters
$def_language = $this->getParam('def_language', true, $result['def_language']);
@@ -1110,6 +1167,9 @@ class Customers extends ApiCommand implements ResourceEntity
if (!empty($allowed_phpconfigs)) {
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
}
if (empty($allowed_phpconfigs) && $phpenabled == 1) {
Response::standardError('customerphpenabledbutnoconfig', '', true);
}
// add permission for allowed mysql usage if customer was not allowed to use mysql prior
if ($result['mysqls'] == 0 && ($mysqls == -1 || $mysqls > 0)) {
@@ -1118,6 +1178,7 @@ class Customers extends ApiCommand implements ResourceEntity
if (! empty($allowed_mysqlserver)) {
$allowed_mysqlserver = array_map('intval', $allowed_mysqlserver);
}
}
$def_language = Validate::validate($def_language, 'default language', '', '', [], true);
$theme = Validate::validate($theme, 'theme', '', '', [], true);
@@ -1327,7 +1388,7 @@ class Customers extends ApiCommand implements ResourceEntity
'vu' => $valid_until
], true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] " . ($deactivated ? 'deactivated' : 'reactivated') . " user '" . $result['loginname'] . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] " . ($deactivated ? 'deactivated' : 'reactivated') . " user '" . $result['loginname'] . "'");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
}
@@ -1390,7 +1451,9 @@ class Customers extends ApiCommand implements ResourceEntity
'custom_notes' => $custom_notes,
'custom_notes_show' => $custom_notes_show,
'api_allowed' => $api_allowed,
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver)
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver),
'backup' => $backup,
'access_backups' => $access_backups
];
$upd_data += $admin_upd_data;
}
@@ -1433,7 +1496,9 @@ class Customers extends ApiCommand implements ResourceEntity
`custom_notes` = :custom_notes,
`custom_notes_show` = :custom_notes_show,
`api_allowed` = :api_allowed,
`allowed_mysqlserver` = :allowed_mysqlserver";
`allowed_mysqlserver` = :allowed_mysqlserver,
`backup`= :backup,
`access_backups` = :access_backups";
$upd_query .= $admin_upd_query;
}
$upd_query .= " WHERE `customerid` = :customerid";
@@ -1538,7 +1603,7 @@ class Customers extends ApiCommand implements ResourceEntity
Database::query($admin_update_query);
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] edited user '" . $result['loginname'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] edited user '" . $result['loginname'] . "'");
/*
* move customer to another admin/reseller; #1166
@@ -1911,7 +1976,7 @@ class Customers extends ApiCommand implements ResourceEntity
// now, recalculate the resource-usage for the old and the new admin
User::updateCounters(false);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] moved user '" . $c_result['loginname'] . "' from admin/reseller '" . $c_result['adminname'] . " to admin/reseller '" . $a_result['loginname'] . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] moved user '" . $c_result['loginname'] . "' from admin/reseller '" . $c_result['adminname'] . " to admin/reseller '" . $a_result['loginname'] . "'");
$result = $this->apiCall('Customers.get', [
'id' => $c_result['customerid']

View File

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

View File

@@ -144,7 +144,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
];
Database::pexecute($stmt, $params, true, true);
$id = Database::lastInsertId();
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added directory-option for '" . $userpath . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added directory-option for '" . $userpath . "'");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
$result = $this->apiCall('DirOptions.get', [
@@ -157,16 +157,15 @@ class DirOptions extends ApiCommand implements ResourceEntity
* this functions validates a given value as ErrorDocument
* refs #267
*
* @param
* string error-document-string
* @param string $errdoc
* @param bool $throw_exception
*
* @return string error-document-string
*
*/
private function correctErrorDocument($errdoc = null, $throw_exception = false)
private function correctErrorDocument(string $errdoc, $throw_exception = false)
{
if ($errdoc !== null && $errdoc != '') {
if (trim($errdoc) != '') {
// not a URL
if ((strtoupper(substr($errdoc, 0, 5)) != 'HTTP:' && strtoupper(substr($errdoc, 0, 6)) != 'HTTPS:') || !Validate::validateUrl($errdoc)) {
// a file
@@ -176,14 +175,14 @@ class DirOptions extends ApiCommand implements ResourceEntity
if (!substr($errdoc, 0, 1) == '/') {
$errdoc = '/' . $errdoc;
}
} else {
} elseif (preg_match('/^"([^\r\n\t\f\0"]+)"$/', $errdoc)) {
// a string (check for ending ")
// string won't work for lighty
if (Settings::Get('system.webserver') == 'lighttpd') {
Response::standardError('stringerrordocumentnotvalidforlighty', '', $throw_exception);
} elseif (substr($errdoc, -1) != '"') {
$errdoc .= '"';
}
} else {
Response::standardError('invaliderrordocumentvalue', '', $throw_exception);
}
} else {
if (Settings::Get('system.webserver') == 'lighttpd') {
@@ -191,7 +190,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
}
}
}
return $errdoc;
return trim($errdoc);
}
/**
@@ -248,7 +247,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
$params['id'] = $id;
$result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) {
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get directory options for '" . $result['path'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get directory options for '" . $result['path'] . "'");
return $this->response($result);
}
$key = "id #" . $id;
@@ -332,7 +331,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
"id" => $id
];
Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] edited directory options for '" . str_replace($customer['documentroot'], '/', $result['path']) . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] edited directory options for '" . str_replace($customer['documentroot'], '/', $result['path']) . "'");
}
$result = $this->apiCall('DirOptions.get', [
@@ -380,7 +379,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $row;
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list directory-options");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list directory-options");
return $this->response([
'count' => count($result),
'list' => $result
@@ -479,7 +478,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
"customerid" => $customer_data['customerid'],
"id" => $id
], true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted directory-option for '" . str_replace($customer_data['documentroot'], '/', $result['path']) . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] deleted directory-option for '" . str_replace($customer_data['documentroot'], '/', $result['path']) . "'");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
return $this->response($result);
}

View File

@@ -87,7 +87,8 @@ class DirProtections extends ApiCommand implements ResourceEntity
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
$username = Validate::validate($username, 'username', '/^[a-zA-Z0-9][a-zA-Z0-9\-_]+\$?$/', '', [], true);
$authname = Validate::validate($authname, 'directory_authname', '/^[a-zA-Z0-9][a-zA-Z0-9\-_ ]+\$?$/', '', [], true);
Validate::validate($password, 'password', '', '', [], true);
$password = Validate::validate($password, 'password', '', '', [], true);
$password = Crypt::validatePassword($password, true);
// check for duplicate usernames for the path
$username_path_check_stmt = Database::prepare("
@@ -128,7 +129,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
];
Database::pexecute($stmt, $params, true, true);
$id = Database::lastInsertId();
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added directory-protection for '" . $username . " (" . $path . ")'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added directory-protection for '" . $username . " (" . $path . ")'");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
$result = $this->apiCall('DirProtections.get', [
@@ -195,7 +196,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
$params['idun'] = ($id <= 0 ? $username : $id);
$result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) {
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get directory protection for '" . $result['path'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get directory protection for '" . $result['path'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "username '" . $username . "'");
@@ -244,7 +245,8 @@ class DirProtections extends ApiCommand implements ResourceEntity
// validation
$authname = Validate::validate($authname, 'directory_authname', '/^[a-zA-Z0-9][a-zA-Z0-9\-_ ]+\$?$/', '', [], true);
Validate::validate($password, 'password', '', '', [], true);
$password = Validate::validate($password, 'password', '', '', [], true);
$password = Crypt::validatePassword($password, true);
$upd_query = "";
$upd_params = [
@@ -277,7 +279,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
Cronjob::inserttask(TaskId::REBUILD_VHOST);
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated directory-protection '" . $result['username'] . " (" . $result['path'] . ")'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated directory-protection '" . $result['username'] . " (" . $result['path'] . ")'");
$result = $this->apiCall('DirProtections.get', [
'id' => $result['id']
]);
@@ -323,7 +325,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $row;
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list directory-protections");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list directory-protections");
return $this->response([
'count' => count($result),
'list' => $result
@@ -411,7 +413,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
"id" => $id
]);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted htpasswd for '" . $result['username'] . " (" . $result['path'] . ")'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] deleted htpasswd for '" . $result['username'] . " (" . $result['path'] . ")'");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
return $this->response($result);
}

View File

@@ -302,6 +302,8 @@ class DomainZones extends ApiCommand implements ResourceEntity
}
} elseif ($type == 'SSHFP' && !empty($content)) {
$content = $content;
} elseif ($type == 'TLSA' && !empty($content)) {
$content = $content;
} elseif ($type == 'TXT' && !empty($content)) {
// check that TXT content is enclosed in " "
$content = Dns::encloseTXTContent($content);
@@ -413,7 +415,7 @@ class DomainZones extends ApiCommand implements ResourceEntity
$zone = Dns::createDomainZone($id);
$zonefile = (string)$zone;
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get dns-zone for '" . $result['domain'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get dns-zone for '" . $result['domain'] . "'");
return $this->response(explode("\n", $zonefile));
}

View File

@@ -76,7 +76,7 @@ class Domains extends ApiCommand implements ResourceEntity
$query_fields = [];
$result_stmt = Database::prepare("
SELECT
`d`.*, `c`.`loginname`, `c`.`deactivated`, `c`.`name`, `c`.`firstname`, `c`.`company`, `c`.`standardsubdomain`, `c`.`adminid` as customeradmin,
`d`.*, `c`.`loginname`, `c`.`deactivated` as `customer_deactivated`, `c`.`name`, `c`.`firstname`, `c`.`company`, `c`.`standardsubdomain`, `c`.`adminid` as customeradmin,
`ad`.`id` AS `aliasdomainid`, `ad`.`domain` AS `aliasdomain`
FROM `" . TABLE_PANEL_DOMAINS . "` `d`
LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`)
@@ -110,7 +110,7 @@ class Domains extends ApiCommand implements ResourceEntity
*
* @param number $domain_id
* @param bool $ssl_only
* optional, return only ssl enabled ip's, default false
* optional, return only ssl enabled ips, default false
* @return array
*/
private function getIpsForDomain($domain_id = 0, $ssl_only = false)
@@ -190,9 +190,6 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, whether to create an exclusive web-logfile for this domain, default 0 (false)
* @param int $alias
* optional, domain-id of a domain that the new domain should be an alias of, default 0 (none)
* @param int $issubof
* optional, domain-id of a domain this domain is a subdomain of (required for webserver-cronjob to
* generate the correct order), default 0 (none)
* @param string $registration_date
* optional, date of domain registration in form of YYYY-MM-DD, default empty (none)
* @param string $termination_date
@@ -210,7 +207,7 @@ class Domains extends ApiCommand implements ResourceEntity
* @param string $ssl_specialsettings
* optional, custom webserver vhost-content which is added to the generated ssl-vhost, default empty
* @param bool $include_specialsettings
* optional, whether or not to include non-ssl specialsettings in the generated ssl-vhost, default false
* optional, whether to include non-ssl specialsettings in the generated ssl-vhost, default false
* @param bool $notryfiles
* optional, [nginx only] do not generate the default try-files directive, default 0 (false)
* @param bool $writeaccesslog
@@ -219,12 +216,14 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, Enable writing an error-log file for this domain, default 1 (true)
* @param string $documentroot
* optional, specify homedir of domain by specifying a directory (relative to customer-docroot), be
* aware, if path starts with / it it considered a full path, not relative to customer-docroot. Also
* aware, if path starts with / it is considered a full path, not relative to customer-docroot. Also
* specifying a URL is possible here (redirect), default empty (autogenerated)
* @param bool $phpenabled
* optional, whether php is enabled for this domain, default 0 (false)
* @param bool $openbasedir
* optional, whether to activate openbasedir restriction for this domain, default 0 (false)
* @param int $openbasedir_path
* optional, either 0 for domains-docroot, 1 for customers-homedir or 2 for parent-directory of domains-docroot
* @param int $phpsettingid
* optional, specify php-configuration that is being used by id, default 1 (system-default)
* @param int $mod_fcgid_starter
@@ -242,7 +241,7 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, do NOT set the systems default ssl ip addresses if none are given via $ssl_ipandport
* parameter
* @param bool $sslenabled
* optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default
* optional, whether SSL is enabled for this domain, regardless of the assigned ssl-ips, default
* 1 (true)
* @param bool $http2
* optional, whether to enable http/2 for this domain (requires to be enabled in the settings), default
@@ -250,9 +249,9 @@ class Domains extends ApiCommand implements ResourceEntity
* @param int $hsts_maxage
* optional max-age value for HSTS header
* @param bool $hsts_sub
* optional whether or not to add subdomains to the HSTS header
* optional whether to add subdomains to the HSTS header
* @param bool $hsts_preload
* optional whether or not to preload HSTS header value
* optional whether to preload HSTS header value
* @param bool $ocsp_stapling
* optional whether to enable ocsp-stapling for this domain. default 0 (false), requires SSL
* @param bool $honorcipherorder
@@ -261,7 +260,7 @@ class Domains extends ApiCommand implements ResourceEntity
* optional whether to enable or disable TLS sessiontickets (RFC 5077) for this domain. default 1
* (true), requires SSL
* @param bool $override_tls
* optional whether or not to override system-tls settings like protocol, ssl-ciphers and if applicable
* optional whether to override system-tls settings like protocol, ssl-ciphers and if applicable
* tls-1.3 ciphers, requires change_serversettings flag for the admin, default false
* @param array $ssl_protocols
* optional list of allowed/used ssl/tls protocols, see system.ssl_protocols setting, only used/required
@@ -296,7 +295,6 @@ class Domains extends ApiCommand implements ResourceEntity
$serveraliasoption = $this->getParam('selectserveralias', true, Settings::Get('system.domaindefaultalias'));
$speciallogfile = $this->getBoolParam('speciallogfile', true, 0);
$aliasdomain = intval($this->getParam('alias', true, 0));
$issubof = $this->getParam('issubof', true, 0);
$registration_date = $this->getParam('registration_date', true, '');
$termination_date = $this->getParam('termination_date', true, '');
$caneditdomain = $this->getBoolParam('caneditdomain', true, 0);
@@ -312,6 +310,7 @@ class Domains extends ApiCommand implements ResourceEntity
$documentroot = $this->getParam('documentroot', true, '');
$phpenabled = $this->getBoolParam('phpenabled', true, 0);
$openbasedir = $this->getBoolParam('openbasedir', true, 0);
$openbasedir_path = $this->getParam('openbasedir_path', true, 0);
$phpsettingid = $this->getParam('phpsettingid', true, 1);
$mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, -1);
$mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, -1);
@@ -404,20 +403,25 @@ class Domains extends ApiCommand implements ResourceEntity
$documentroot = $_documentroot;
}
$registration_date = Validate::validate($registration_date, 'registration_date', Validate::REGEX_YYYY_MM_DD, '', [
'0000-00-00',
'0',
''
], true);
if (!is_null($registration_date)) {
$registration_date = Validate::validate($registration_date, 'registration_date',
Validate::REGEX_YYYY_MM_DD, '', [
'0000-00-00',
'0',
''
], true);
}
if ($registration_date == '0000-00-00' || empty($registration_date)) {
$registration_date = null;
}
$termination_date = Validate::validate($termination_date, 'termination_date', Validate::REGEX_YYYY_MM_DD, '', [
'0000-00-00',
'0',
''
], true);
if (!is_null($termination_date)) {
$termination_date = Validate::validate($termination_date, 'termination_date',
Validate::REGEX_YYYY_MM_DD, '', [
'0000-00-00',
'0',
''
], true);
}
if ($termination_date == '0000-00-00' || empty($termination_date)) {
$termination_date = null;
}
@@ -525,6 +529,10 @@ class Domains extends ApiCommand implements ResourceEntity
$mod_fcgid_maxrequests = '-1';
}
if ($openbasedir_path > 2 && $openbasedir_path < 0) {
$openbasedir_path = 0;
}
// check non-ssl IP
$ipandports = $this->validateIpAddresses($p_ipandports);
// check ssl IP
@@ -559,7 +567,7 @@ class Domains extends ApiCommand implements ResourceEntity
// validate dns if lets encrypt is enabled to check whether we can use it at all
if ($letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
$domain_ips = PhpHelper::gethostbynamel6($domain);
$domain_ips = PhpHelper::gethostbynamel6($domain, true, Settings::Get('system.le_domain_dnscheck_resolver'));
$selected_ips = $this->getIpsFromIdArray($ssl_ipandports);
if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true);
@@ -653,10 +661,6 @@ class Domains extends ApiCommand implements ResourceEntity
$serveraliasoption = '0';
}
if ($issubof <= 0) {
$issubof = '0';
}
$idna_convert = new IdnaWrapper();
if ($domain == '') {
Response::standardError([
@@ -696,6 +700,7 @@ class Domains extends ApiCommand implements ResourceEntity
'caneditdomain' => $caneditdomain,
'phpenabled' => $phpenabled,
'openbasedir' => $openbasedir,
'openbasedir_path' => $openbasedir_path,
'speciallogfile' => $speciallogfile,
'specialsettings' => $specialsettings,
'ssl_specialsettings' => $ssl_specialsettings,
@@ -710,7 +715,6 @@ class Domains extends ApiCommand implements ResourceEntity
'phpsettingid' => $phpsettingid,
'mod_fcgid_starter' => $mod_fcgid_starter,
'mod_fcgid_maxrequests' => $mod_fcgid_maxrequests,
'ismainbutsubto' => $issubof,
'letsencrypt' => $letsencrypt,
'http2' => $http2,
'hsts' => $hsts_maxage,
@@ -749,6 +753,7 @@ class Domains extends ApiCommand implements ResourceEntity
`caneditdomain` = :caneditdomain,
`phpenabled` = :phpenabled,
`openbasedir` = :openbasedir,
`openbasedir_path` = :openbasedir_path,
`speciallogfile` = :speciallogfile,
`specialsettings` = :specialsettings,
`ssl_specialsettings` = :ssl_specialsettings,
@@ -763,7 +768,6 @@ class Domains extends ApiCommand implements ResourceEntity
`phpsettingid` = :phpsettingid,
`mod_fcgid_starter` = :mod_fcgid_starter,
`mod_fcgid_maxrequests` = :mod_fcgid_maxrequests,
`ismainbutsubto` = :ismainbutsubto,
`letsencrypt` = :letsencrypt,
`http2` = :http2,
`hsts` = :hsts,
@@ -884,7 +888,7 @@ class Domains extends ApiCommand implements ResourceEntity
$result['ipsandports'] = $this->getIpsForDomain($result['id']);
}
$result['domain_hascert'] = $this->getHasCertValueForDomain((int)$result['id'], (int)$result['parentdomainid']);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] get domain '" . $result['domain'] . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get domain '" . $result['domain'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "domainname '" . $domainname . "'");
@@ -1055,9 +1059,6 @@ class Domains extends ApiCommand implements ResourceEntity
* default 0 (false)
* @param int $alias
* optional, domain-id of a domain that the new domain should be an alias of, default 0 (none)
* @param int $issubof
* optional, domain-id of a domain this domain is a subdomain of (required for webserver-cronjob to
* generate the correct order), default 0 (none)
* @param string $registration_date
* optional, date of domain registration in form of YYYY-MM-DD, default empty (none)
* @param string $termination_date
@@ -1075,7 +1076,7 @@ class Domains extends ApiCommand implements ResourceEntity
* @param string $ssl_specialsettings
* optional, custom webserver vhost-content which is added to the generated ssl-vhost, default empty
* @param bool $include_specialsettings
* optional, whether or not to include non-ssl specialsettings in the generated ssl-vhost, default false
* optional, whether to include non-ssl specialsettings in the generated ssl-vhost, default false
* @param bool $specialsettingsforsubdomains
* optional, whether to apply specialsettings to all subdomains of this domain, default is read from
* setting system.apply_specialsettings_default
@@ -1087,7 +1088,7 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, Enable writing an error-log file for this domain, default 1 (true)
* @param string $documentroot
* optional, specify homedir of domain by specifying a directory (relative to customer-docroot), be
* aware, if path starts with / it it considered a full path, not relative to customer-docroot. Also
* aware, if path starts with / it is considered a full path, not relative to customer-docroot. Also
* specifying a URL is possible here (redirect), default empty (autogenerated)
* @param bool $phpenabled
* optional, whether php is enabled for this domain, default 0 (false)
@@ -1096,6 +1097,8 @@ class Domains extends ApiCommand implements ResourceEntity
* from setting system.apply_phpconfigs_default
* @param bool $openbasedir
* optional, whether to activate openbasedir restriction for this domain, default 0 (false)
* @param int $openbasedir_path
* optional, either 0 for domains-docroot, 1 for customers-homedir or 2 for parent-directory of domains-docroot
* @param int $phpsettingid
* optional, specify php-configuration that is being used by id, default 1 (system-default)
* @param int $mod_fcgid_starter
@@ -1114,7 +1117,7 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, if set to true and no $ssl_ipandport value is given, the ip's get removed, otherwise, the
* currently set value is used, default false
* @param bool $sslenabled
* optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default
* optional, whether SSL is enabled for this domain, regardless of the assigned ssl-ips, default
* 1 (true)
* @param bool $http2
* optional, whether to enable http/2 for this domain (requires to be enabled in the settings), default
@@ -1122,9 +1125,9 @@ class Domains extends ApiCommand implements ResourceEntity
* @param int $hsts_maxage
* optional max-age value for HSTS header
* @param bool $hsts_sub
* optional whether or not to add subdomains to the HSTS header
* optional whether to add subdomains to the HSTS header
* @param bool $hsts_preload
* optional whether or not to preload HSTS header value
* optional whether to preload HSTS header value
* @param bool $ocsp_stapling
* optional whether to enable ocsp-stapling for this domain. default 0 (false), requires SSL
* @param bool $honorcipherorder
@@ -1134,6 +1137,8 @@ class Domains extends ApiCommand implements ResourceEntity
* (true), requires SSL
* @param string $description
* optional custom description (currently not used/shown in the frontend), default empty
* @param bool $deactivated
* optional, if 1 (true) the domain can be deactivated/suspended
*
* @access admin
* @return string json-encoded array
@@ -1175,7 +1180,6 @@ class Domains extends ApiCommand implements ResourceEntity
$speciallogfile = $this->getBoolParam('speciallogfile', true, $result['speciallogfile']);
$speciallogverified = $this->getBoolParam('speciallogverified', true, 0);
$aliasdomain = intval($this->getParam('alias', true, $result['aliasdomain']));
$issubof = $this->getParam('issubof', true, $result['ismainbutsubto']);
$registration_date = $this->getParam('registration_date', true, $result['registration_date']);
$termination_date = $this->getParam('termination_date', true, $result['termination_date']);
$caneditdomain = $this->getBoolParam('caneditdomain', true, $result['caneditdomain']);
@@ -1193,6 +1197,7 @@ class Domains extends ApiCommand implements ResourceEntity
$phpenabled = $this->getBoolParam('phpenabled', true, $result['phpenabled']);
$phpfs = $this->getBoolParam('phpsettingsforsubdomains', true, Settings::Get('system.apply_phpconfigs_default'));
$openbasedir = $this->getBoolParam('openbasedir', true, $result['openbasedir']);
$openbasedir_path = $this->getParam('openbasedir_path', true, $result['openbasedir_path']);
$phpsettingid = $this->getParam('phpsettingid', true, $result['phpsettingid']);
$mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, $result['mod_fcgid_starter']);
$mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, $result['mod_fcgid_maxrequests']);
@@ -1229,6 +1234,7 @@ class Domains extends ApiCommand implements ResourceEntity
$tlsv13_cipher_list = $result['tlsv13_cipher_list'];
}
$description = $this->getParam('description', true, $result['description']);
$deactivated = $this->getBoolParam('deactivated', true, $result['deactivated']);
// count subdomain usage of source-domain
$subdomains_stmt = Database::prepare("
@@ -1322,19 +1328,25 @@ class Domains extends ApiCommand implements ResourceEntity
$adminid = $result['adminid'];
}
$registration_date = Validate::validate($registration_date, 'registration_date', Validate::REGEX_YYYY_MM_DD, '', [
'0000-00-00',
'0',
''
], true);
if (!is_null($registration_date)) {
$registration_date = Validate::validate($registration_date, 'registration_date',
Validate::REGEX_YYYY_MM_DD, '', [
'0000-00-00',
'0',
''
], true);
}
if ($registration_date == '0000-00-00' || empty($registration_date)) {
$registration_date = null;
}
$termination_date = Validate::validate($termination_date, 'termination_date', Validate::REGEX_YYYY_MM_DD, '', [
'0000-00-00',
'0',
''
], true);
if (!is_null($termination_date)) {
$termination_date = Validate::validate($termination_date, 'termination_date',
Validate::REGEX_YYYY_MM_DD, '', [
'0000-00-00',
'0',
''
], true);
}
if ($termination_date == '0000-00-00' || empty($termination_date)) {
$termination_date = null;
}
@@ -1478,6 +1490,11 @@ class Domains extends ApiCommand implements ResourceEntity
$mod_fcgid_maxrequests = $result['mod_fcgid_maxrequests'];
}
// check changes of openbasedir-path variable
if ($openbasedir_path > 2 && $openbasedir_path < 0) {
$openbasedir_path = 0;
}
// check non-ssl IP
$ipandports = $this->validateIpAddresses($p_ipandports, false, $result['id']);
// check ssl IP
@@ -1523,7 +1540,7 @@ class Domains extends ApiCommand implements ResourceEntity
// validate dns if lets encrypt is enabled to check whether we can use it at all
if ($letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
$domain_ips = PhpHelper::gethostbynamel6($result['domain']);
$domain_ips = PhpHelper::gethostbynamel6($result['domain'], true, Settings::Get('system.le_domain_dnscheck_resolver'));
$selected_ips = $this->getIpsFromIdArray($ssl_ipandports);
if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true);
@@ -1612,10 +1629,6 @@ class Domains extends ApiCommand implements ResourceEntity
Response::standardError('domainisaliasorothercustomer', '', true);
}
if ($issubof <= 0) {
$issubof = '0';
}
if ($serveraliasoption != '1' && $serveraliasoption != '2') {
$serveraliasoption = '0';
}
@@ -1623,7 +1636,30 @@ class Domains extends ApiCommand implements ResourceEntity
$wwwserveralias = ($serveraliasoption == '1') ? '1' : '0';
$iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0';
if ($documentroot != $result['documentroot'] || $ssl_redirect != $result['ssl_redirect'] || $wwwserveralias != $result['wwwserveralias'] || $iswildcarddomain != $result['iswildcarddomain'] || $phpenabled != $result['phpenabled'] || $openbasedir != $result['openbasedir'] || $phpsettingid != $result['phpsettingid'] || $mod_fcgid_starter != $result['mod_fcgid_starter'] || $mod_fcgid_maxrequests != $result['mod_fcgid_maxrequests'] || $specialsettings != $result['specialsettings'] || $ssl_specialsettings != $result['ssl_specialsettings'] || $notryfiles != $result['notryfiles'] || $writeaccesslog != $result['writeaccesslog'] || $writeerrorlog != $result['writeerrorlog'] || $aliasdomain != $result['aliasdomain'] || $issubof != $result['ismainbutsubto'] || $email_only != $result['email_only'] || ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1') || $letsencrypt != $result['letsencrypt'] || $http2 != $result['http2'] || $hsts_maxage != $result['hsts'] || $hsts_sub != $result['hsts_sub'] || $hsts_preload != $result['hsts_preload'] || $ocsp_stapling != $result['ocsp_stapling']) {
if ($documentroot != $result['documentroot']
|| $ssl_redirect != $result['ssl_redirect']
|| $wwwserveralias != $result['wwwserveralias']
|| $iswildcarddomain != $result['iswildcarddomain']
|| $phpenabled != $result['phpenabled']
|| $openbasedir != $result['openbasedir']
|| $phpsettingid != $result['phpsettingid']
|| $mod_fcgid_starter != $result['mod_fcgid_starter']
|| $mod_fcgid_maxrequests != $result['mod_fcgid_maxrequests']
|| $specialsettings != $result['specialsettings']
|| $ssl_specialsettings != $result['ssl_specialsettings']
|| $notryfiles != $result['notryfiles']
|| $writeaccesslog != $result['writeaccesslog']
|| $writeerrorlog != $result['writeerrorlog']
|| $aliasdomain != $result['aliasdomain']
|| $email_only != $result['email_only']
|| ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1')
|| $letsencrypt != $result['letsencrypt']
|| $http2 != $result['http2']
|| $hsts_maxage != $result['hsts']
|| $hsts_sub != $result['hsts_sub']
|| $hsts_preload != $result['hsts_preload']
|| $ocsp_stapling != $result['ocsp_stapling']
) {
Cronjob::inserttask(TaskId::REBUILD_VHOST);
}
@@ -1749,7 +1785,7 @@ class Domains extends ApiCommand implements ResourceEntity
Database::pexecute($upd_stmt, [
'id' => $id
], true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] removed specialsettings on all subdomains of domain #" . $id);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] removed specialsettings on all subdomains of domain #" . $id);
}
$wwwserveralias = ($serveraliasoption == '1') ? '1' : '0';
@@ -1771,7 +1807,8 @@ class Domains extends ApiCommand implements ResourceEntity
$update_data['wwwserveralias'] = $wwwserveralias;
$update_data['iswildcarddomain'] = $iswildcarddomain;
$update_data['phpenabled'] = $phpenabled;
$update_data['openbasedir'] = $openbasedir;
$update_data['openbasedir'] = $openbasedir;;
$update_data['openbasedir_path'] = $openbasedir_path;
$update_data['speciallogfile'] = $speciallogfile;
$update_data['phpsettingid'] = $phpsettingid;
$update_data['mod_fcgid_starter'] = $mod_fcgid_starter;
@@ -1784,7 +1821,6 @@ class Domains extends ApiCommand implements ResourceEntity
$update_data['writeerrorlog'] = $writeerrorlog;
$update_data['registration_date'] = $registration_date;
$update_data['termination_date'] = $termination_date;
$update_data['ismainbutsubto'] = $issubof;
$update_data['letsencrypt'] = $letsencrypt;
$update_data['http2'] = $http2;
$update_data['hsts'] = $hsts_maxage;
@@ -1799,6 +1835,7 @@ class Domains extends ApiCommand implements ResourceEntity
$update_data['honorcipherorder'] = $honorcipherorder;
$update_data['sessiontickets'] = $sessiontickets;
$update_data['description'] = $description;
$update_data['deactivated'] = $deactivated;
$update_data['id'] = $id;
$update_stmt = Database::prepare("
@@ -1819,6 +1856,7 @@ class Domains extends ApiCommand implements ResourceEntity
`iswildcarddomain` = :iswildcarddomain,
`phpenabled` = :phpenabled,
`openbasedir` = :openbasedir,
`openbasedir_path` = :openbasedir_path,
`speciallogfile` = :speciallogfile,
`phpsettingid` = :phpsettingid,
`mod_fcgid_starter` = :mod_fcgid_starter,
@@ -1831,7 +1869,6 @@ class Domains extends ApiCommand implements ResourceEntity
`writeerrorlog` = :writeerrorlog,
`registration_date` = :registration_date,
`termination_date` = :termination_date,
`ismainbutsubto` = :ismainbutsubto,
`letsencrypt` = :letsencrypt,
`http2` = :http2,
`hsts` = :hsts,
@@ -1845,15 +1882,41 @@ class Domains extends ApiCommand implements ResourceEntity
`ssl_enabled` = :sslenabled,
`ssl_honorcipherorder` = :honorcipherorder,
`ssl_sessiontickets` = :sessiontickets,
`description` = :description
`description` = :description,
`deactivated` = :deactivated
WHERE `id` = :id
");
Database::pexecute($update_stmt, $update_data, true, true);
// activate/deactivate domain-based services
if ($deactivated != $result['deactivated']) {
// deactivate email accounts
$yesno = ($deactivated ? 'N' : 'Y');
$pop3 = ($deactivated ? '0' : (int)$customer['pop3']);
$imap = ($deactivated ? '0' : (int)$customer['imap']);
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_MAIL_USERS . "`
SET `postfix`= :yesno, `pop3` = :pop3, `imap` = :imap
WHERE `customerid` = :customerid AND `domainid` = :domainid
");
Database::pexecute($upd_stmt, [
'yesno' => $yesno,
'pop3' => $pop3,
'imap' => $imap,
'customerid' => $customerid,
'domainid' => $id
]);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] " . ($deactivated ? 'deactivated' : 'reactivated') . " domain '" . $result['domain'] . "'");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
}
$_update_data['customerid'] = $customerid;
$_update_data['adminid'] = $adminid;
$_update_data['phpenabled'] = $phpenabled;
$_update_data['openbasedir'] = $openbasedir;
$_update_data['openbasedir_path'] = $openbasedir_path;
$_update_data['mod_fcgid_starter'] = $mod_fcgid_starter;
$_update_data['mod_fcgid_maxrequests'] = $mod_fcgid_maxrequests;
$_update_data['notryfiles'] = $notryfiles;
@@ -1866,6 +1929,7 @@ class Domains extends ApiCommand implements ResourceEntity
$_update_data['honorcipherorder'] = $honorcipherorder;
$_update_data['sessiontickets'] = $sessiontickets;
$_update_data['parentdomainid'] = $id;
$_update_data['deactivated'] = $deactivated;
// if php config is to be set for all subdomains, check here
$update_phpconfig = '';
@@ -1887,6 +1951,7 @@ class Domains extends ApiCommand implements ResourceEntity
`adminid` = :adminid,
`phpenabled` = :phpenabled,
`openbasedir` = :openbasedir,
`openbasedir_path` = :openbasedir_path,
`mod_fcgid_starter` = :mod_fcgid_starter,
`mod_fcgid_maxrequests` = :mod_fcgid_maxrequests,
`notryfiles` = :notryfiles,
@@ -1897,12 +1962,25 @@ class Domains extends ApiCommand implements ResourceEntity
`ssl_cipher_list` = :ssl_cipher_list,
`tlsv13_cipher_list` = :tlsv13_cipher_list,
`ssl_honorcipherorder` = :honorcipherorder,
`ssl_sessiontickets` = :sessiontickets
`ssl_sessiontickets` = :sessiontickets,
`deactivated` = :deactivated
" . $update_phpconfig . $upd_specialsettings . $updatechildren . $update_sslredirect . "
WHERE `parentdomainid` = :parentdomainid
");
Database::pexecute($_update_stmt, $_update_data, true, true);
// get current ip<>domain entries
$ip_sel_stmt = Database::prepare("
SELECT id_ipandports FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id
");
Database::pexecute($ip_sel_stmt, [
'id' => $id
], true, true);
$current_ips = [];
while ($cIP = $ip_sel_stmt->fetch(\PDO::FETCH_ASSOC)) {
$current_ips[] = $cIP['id_ipandports'];
}
// Cleanup domain <-> ip mapping
$del_stmt = Database::prepare("
DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id
@@ -1930,6 +2008,12 @@ class Domains extends ApiCommand implements ResourceEntity
}
}
// check ip changes
$all_new_ips = array_merge($ipandports, $ssl_ipandports);
if (count(array_diff($current_ips, $all_new_ips)) != 0 || count(array_diff($all_new_ips, $current_ips)) != 0) {
Cronjob::inserttask(TaskId::REBUILD_VHOST);
}
// Cleanup domain <-> ip mapping for subdomains
$domainidsresult_stmt = Database::prepare("
SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `parentdomainid` = :id
@@ -1974,12 +2058,11 @@ class Domains extends ApiCommand implements ResourceEntity
}
if ($result['wwwserveralias'] != $wwwserveralias || $result['letsencrypt'] != $letsencrypt) {
// or when wwwserveralias or letsencrypt was changed
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger());
if ((int)$aliasdomain === 0) {
// in case the wwwserveralias is set on a main domain, $aliasdomain is 0
// --> the call just above to triggerLetsEncryptCSRForAliasDestinationDomain
// is a noop...let's repeat it with the domain id of the main domain
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($id, $this->logger());
} else {
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger());
}
}
@@ -2000,9 +2083,6 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, the domain-id
* @param string $domainname
* optional, the domainname
* @param bool $delete_mainsubdomains
* optional, remove also domains that are subdomains of this domain but added as main domains; default
* false
* @param bool $is_stdsubdomain
* optional, default false, specify whether it's a std-subdomain you are deleting as it does not count
* as subdomain-resource
@@ -2018,7 +2098,6 @@ class Domains extends ApiCommand implements ResourceEntity
$dn_optional = $id > 0;
$domainname = $this->getParam('domainname', $dn_optional, '');
$is_stdsubdomain = $this->getParam('is_stdsubdomain', true, 0);
$remove_subbutmain_domains = $this->getParam('delete_mainsubdomains', true, 0);
$result = $this->apiCall('Domains.get', [
'id' => $id,
@@ -2026,15 +2105,10 @@ class Domains extends ApiCommand implements ResourceEntity
]);
$id = $result['id'];
// check for deletion of main-domains which are logically subdomains, #329
$rsd_sql = '';
if ($remove_subbutmain_domains) {
$rsd_sql .= " OR `ismainbutsubto` = :id";
}
$subresult_stmt = Database::prepare("
SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE (`id` = :id OR `parentdomainid` = :id " . $rsd_sql . ")");
SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE (`id` = :id OR `parentdomainid` = :id)
");
Database::pexecute($subresult_stmt, [
'id' => $id
], true, true);
@@ -2056,23 +2130,10 @@ class Domains extends ApiCommand implements ResourceEntity
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] deleted domain/s from mail-tables");
}
// if mainbutsubto-domains are not to be deleted, re-assign the (ismainbutsubto value of the main
// domain which is being deleted) as their new ismainbutsubto value
if ($remove_subbutmain_domains !== 1) {
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_DOMAINS . "` SET
`ismainbutsubto` = :newIsMainButSubtoValue
WHERE `ismainbutsubto` = :deletedMainDomainId
");
Database::pexecute($upd_stmt, [
'newIsMainButSubtoValue' => $result['ismainbutsubto'],
'deletedMainDomainId' => $id
], true, true);
}
$del_stmt = Database::prepare("
DELETE FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE `id` = :id OR `parentdomainid` = :id " . $rsd_sql);
DELETE FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE `id` = :id OR `parentdomainid` = :id
");
Database::pexecute($del_stmt, [
'id' => $id
], true, true);
@@ -2138,7 +2199,9 @@ class Domains extends ApiCommand implements ResourceEntity
'domainid' => $id
], true, true);
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger());
if ((int)$result['aliasdomain'] !== 0) {
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger());
}
// remove domains DNS from powerDNS if used, #581
Cronjob::inserttask(TaskId::DELETE_DOMAIN_PDNS, $result['domain']);
@@ -2146,7 +2209,7 @@ class Domains extends ApiCommand implements ResourceEntity
// remove domain from acme.sh / lets encrypt if used
Cronjob::inserttask(TaskId::DELETE_DOMAIN_SSL, $result['domain']);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] deleted domain/subdomains (#" . $result['id'] . ")");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] deleted domain/subdomains (#" . $result['id'] . ")");
User::updateCounters();
Cronjob::inserttask(TaskId::REBUILD_VHOST);
// Using nameserver, insert a task which rebuilds the server config
@@ -2155,4 +2218,114 @@ class Domains extends ApiCommand implements ResourceEntity
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* duplicate domain entry by either id or domainname. All parameters from Domains.add() can be used
* to overwrite source entity values if necessary.
*
* @param int $id
* optional, the domain-id
* @param string $domainname
* optional, the domainname
* @param string $domain
* required, name of the new domain to be added
*
* @access admin
* @return string json-encoded array
* @throws Exception
*/
public function duplicate()
{
if ($this->isAdmin()) {
// parameters
$id = $this->getParam('id', true, 0);
$dn_optional = $id > 0;
$domainname = $this->getParam('domainname', $dn_optional, '');
$p_domain = $this->getParam('domain');
// get requested domain
$result = $this->apiCall('Domains.get', [
'id' => $id,
'domainname' => $domainname,
]);
// clear some defaults
unset($result['domain_ace']);
unset($result['adminid']);
unset($result['documentroot']);
unset($result['registration_date']);
unset($result['termination_date']);
unset($result['zonefile']);
// clear auto-generated values
unset($result['bindserial']);
unset($result['dkim_privkey']);
unset($result['dkim_pubkey']);
// clear api-call generated fields
unset($result['domain_hascert']);
// set correct ip/port information
$domain_ips = $result['ipsandports'];
unset($result['ipsandports']);
$result['ipandport'] = [];
$result['ssl_ipandport'] = [];
foreach ($domain_ips as $dip) {
if ($dip['ssl'] == 1) {
$result['ssl_ipandport'][] = $dip['id'];
} else {
$result['ipandport'][] = $dip['id'];
}
}
// check whether we are changing the customer/owner
if ($this->getParam('customerid', true, 0) == 0 && $this->getParam('loginname', true, '') == '') {
$customerid = $result['customerid'];
} else {
$customer = $this->getCustomerData();
$customerid = $customer['customerid'];
}
// check for alias-domain and whether it belongs to the target user
if (!empty($result['aliasdomain']) && $customerid == $result['customerid']) {
// duplicate alias entry
$result['alias'] = $result['aliasdomain'];
}
unset($result['aliasdomain']);
// validate possible fpm configs and whether the customer is allowed to use them
if ($customerid != $result['customerid']) {
$allowed_phpconfigs = json_decode($customer['allowed_phpconfigs'] ?? '[]', true);
if (empty($allowed_phpconfigs)) {
// system defaults
unset($result['phpsettingid']);
} elseif (!in_array($result['phpsettingid'], $allowed_phpconfigs)) {
// use the first customer allowed config
$result['phpsettingid'] = array_shift($allowed_phpconfigs);
}
}
// translate serveralias values
$result['selectserveralias'] = 2;
if ((int)$result['wwwserveralias'] == 1) {
$result['selectserveralias'] = 1;
} elseif ((int)$result['iswildcarddomain'] == 1) {
$result['selectserveralias'] = 0;
}
unset($result['wwwserveralias']);
unset($result['iswildcarddomain']);
$additional_params = $this->getParamList();
// unset unneeded params from this call
unset($additional_params['id']);
unset($additional_params['domainname']);
unset($additional_params['domain']);
// set new values and merge with optional add() parameters
$new_domain = array_merge($result, $additional_params);
$new_domain['domain'] = $p_domain;
$result_new = $this->apiCall('Domains.add', $new_domain);
return $this->response($result_new);
}
throw new Exception("Not allowed to execute given command.", 403);
}
}

View File

@@ -63,7 +63,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
* @param string $alternative_email
* optional email address to send account information to, default is the account that is being created
* @param int $email_quota
* optional quota if enabled in MB, default 0
* optional quota if enabled in MB, default setting: system.mail_quota
* @param bool $sendinfomail
* optional, sends the welcome message to the new account (needed for creation, without the user won't
* be able to login before any mail is received), default 1 (true)
@@ -85,7 +85,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$emailaddr = $this->getParam('emailaddr', $ea_optional, '');
$email_password = $this->getParam('email_password');
$alternative_email = $this->getParam('alternative_email', true, '');
$quota = $this->getParam('email_quota', true, 0);
$quota = $this->getParam('email_quota', true, Settings::Get('system.mail_quota') ?? 0);
$sendinfomail = $this->getBoolParam('sendinfomail', true, 1);
// validation
@@ -95,9 +95,18 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$customer = $this->getCustomerData('email_accounts');
// check for imap||pop3 == 1, see #1298
// d00p, 6.5.2023 @revert this - if a customer has resources which allow email accounts
// it implicitly allowed SMTP, e.g. sending of emails which also requires an account to exist
/*
if ($customer['imap'] != '1' && $customer['pop3'] != '1') {
Response::standardError('notallowedtouseaccounts', '', true);
}
*/
if (!empty($emailaddr)) {
$idna_convert = new IdnaWrapper();
$emailaddr = $idna_convert->encode($emailaddr);
}
// get email address
$result = $this->apiCall('Emails.get', [
@@ -306,7 +315,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
}
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added email account for '" . $result['email_full'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added email account for '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [
'emailaddr' => $result['email_full']
]);
@@ -357,6 +366,11 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$ea_optional = $id > 0;
$emailaddr = $this->getParam('emailaddr', $ea_optional, '');
if (!empty($emailaddr)) {
$idna_convert = new IdnaWrapper();
$emailaddr = $idna_convert->encode($emailaddr);
}
// validation
$result = $this->apiCall('Emails.get', [
'id' => $id,
@@ -450,7 +464,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
Admins::increaseUsage($customer['adminid'], 'email_quota_used', '', ($quota - $result['quota']));
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated email account '" . $result['email_full'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated email account '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [
'emailaddr' => $result['email_full']
]);
@@ -556,7 +570,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
Customers::decreaseUsage($customer['customerid'], 'email_accounts_used');
Customers::decreaseUsage($customer['customerid'], 'email_quota_used', '', $quota);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted email account for '" . $result['email_full'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] deleted email account for '" . $result['email_full'] . "'");
return $this->response($result);
}
}

View File

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

View File

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

View File

@@ -88,9 +88,12 @@ class Emails extends ApiCommand implements ResourceEntity
$domain_check = $this->apiCall('SubDomains.get', [
'domainname' => $domain
], true);
if ($domain_check['isemaildomain'] == 0) {
if ((int)$domain_check['isemaildomain'] == 0) {
Response::standardError('maindomainnonexist', $domain, true);
}
if ((int)$domain_check['deactivated'] == 1) {
Response::standardError('maindomaindeactivated', $domain, true);
}
if (Settings::Get('catchall.catchall_enabled') != '1') {
$iscatchall = 0;
@@ -159,7 +162,7 @@ class Emails extends ApiCommand implements ResourceEntity
// update customer usage
Customers::increaseUsage($customer['customerid'], 'emails_used');
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added email address '" . $email_full . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added email address '" . $email_full . "'");
$result = $this->apiCall('Emails.get', [
'emailaddr' => $email_full
@@ -195,11 +198,11 @@ class Emails extends ApiCommand implements ResourceEntity
FROM `" . TABLE_MAIL_VIRTUAL . "` v
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON v.`popaccountid` = u.`id`
WHERE v.`customerid` IN (" . implode(", ", $customer_ids) . ")
AND (v.`id`= :idea OR (v.`email` = :idea OR v.`email_full` = :idea))
");
AND " . (is_numeric($params['idea']) ? "v.`id`= :idea" : "(v.`email` = :idea OR v.`email_full` = :idea)")
);
$result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) {
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get email address '" . $result['email_full'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get email address '" . $result['email_full'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "emailaddr '" . $emailaddr . "'");
@@ -294,7 +297,7 @@ class Emails extends ApiCommand implements ResourceEntity
"id" => $id
];
Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] toggled catchall-flag for email address '" . $result['email_full'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] toggled catchall-flag for email address '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [
'emailaddr' => $result['email_full']
@@ -340,7 +343,7 @@ class Emails extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $row;
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list email-addresses");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list email-addresses");
return $this->response([
'count' => count($result),
'list' => $result
@@ -445,7 +448,7 @@ class Emails extends ApiCommand implements ResourceEntity
], true, true);
Customers::decreaseUsage($customer['customerid'], 'emails_used');
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted email address '" . $result['email_full'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] deleted email address '" . $result['email_full'] . "'");
return $this->response($result);
}
}

View File

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

View File

@@ -37,6 +37,7 @@ use Froxlor\Settings;
use Froxlor\SImExporter;
use Froxlor\System\Cronjob;
use Froxlor\System\Crypt;
use Froxlor\Validate\Validate;
use PDO;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
@@ -72,7 +73,7 @@ class Froxlor extends ApiCommand
if (empty($uc_data) || empty($response) || $uc_data['ts'] + self::UPDATE_CHECK_INTERVAL < time() || $uc_data['channel'] != Settings::Get('system.update_channel') || $force_ucheck) {
// log our actions
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] checking for updates");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] checking for updates");
// check for new version
$aucheck = AutoUpdate::checkVersion();
@@ -142,7 +143,7 @@ class Froxlor extends ApiCommand
{
if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) {
$json_str = $this->getParam('json_str');
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "User " . $this->getUserDetail('loginname') . " imported settings");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "User " . $this->getUserDetail('loginname') . " imported settings");
try {
SImExporter::import($json_str);
Cronjob::inserttask(TaskId::REBUILD_VHOST);
@@ -269,6 +270,79 @@ class Froxlor extends ApiCommand
return $this->response(Crypt::generatePassword($length));
}
/**
* return a one-time login link URL for a given user
*
* @param int $customerid optional, required if $loginname is not specified, user to create link for
* @param string $loginname optional, required if $customerid is not specified, user to create link for
* @param int $valid_time optional, value in seconds how long the link will be valid, default is 10 seconds, valid values are numbers from 10 to 120
* @param string $allowed_from optional, comma separated list of ip addresses or networks to allow login from via this link
*
* @access admin
* @return string json-encoded array [base => domain, uri => relative link]
* @throws Exception
*/
public function generateLoginLink()
{
if ($this->isAdmin()) {
$customer = $this->getCustomerData();
// cannot create link for deactivated users
if ((int)$customer['deactivated'] == 1) {
throw new Exception("Cannot generate link for deactivated user", 406);
}
$valid_time = (int)$this->getParam('valid_time', true, 10);
$allowed_from = $this->getParam('allowed_from', true, '');
$valid_time = Validate::validate($valid_time, 'valid time', '/^(1[0-1][0-9]|120|[1-9][0-9])$/', 'invalid_validtime', [10], true);
// validate allowed_from
if (!empty($allowed_from)) {
$ip_list = array_map('trim', explode(",", $allowed_from));
$_check_list = $ip_list;
foreach ($_check_list as $idx => $ip) {
if (Validate::validate_ip2($ip, true, 'invalidip', true, true, true) == false) {
throw new Exception('Invalid ip address', 406);
}
// check for cidr
if (strpos($ip, '/') !== false) {
$ipparts = explode("/", $ip);
// shorten IP
$ip = inet_ntop(inet_pton($ipparts[0]));
// re-add cidr
$ip .= '/' . $ipparts[1];
} else {
// shorten IP
$ip = inet_ntop(inet_pton($ip));
}
$ip_list[$idx] = $ip;
}
$allowed_from = implode(",", array_unique($ip_list));
}
$hash = hash('sha256', openssl_random_pseudo_bytes(64 * 64));
$ins_stmt = Database::prepare("
INSERT INTO `" . TABLE_PANEL_LOGINLINKS . "`
SET `hash` = :hash, `loginname` = :loginname, `valid_until` = :validuntil, `allowed_from` = :allowedfrom
ON DUPLICATE KEY UPDATE `hash` = :hash, `valid_until` = :validuntil, `allowed_from` = :allowedfrom
");
Database::pexecute($ins_stmt, [
'hash' => $hash,
'loginname' => $customer['loginname'],
'validuntil' => time() + $valid_time,
'allowedfrom' => $allowed_from
]);
return $this->response([
'base' => 'https://' . Settings::Get('system.hostname') . '/' . (Settings::Get('system.froxlordirectlyviahostname') != 1 ? basename(\Froxlor\Froxlor::getInstallDir()) . '/' : ''),
'uri' => 'index.php?action=ll&ln=' . $customer['loginname'] . '&h=' . $hash
]);
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* can be used to remotely run the integritiy checks froxlor implements
*

View File

@@ -72,6 +72,8 @@ class Ftps extends ApiCommand implements ResourceEntity
* optional whether to add additional usernames to the group
* @param bool $is_defaultuser
* optional whether this is the standard default ftp user which is being added so no usage is decreased
* @param bool $login_enabled
* optional whether to allow login (default) or not
*
* @access admin, customer
* @return string json-encoded array
@@ -84,6 +86,7 @@ class Ftps extends ApiCommand implements ResourceEntity
}
$is_defaultuser = $this->getBoolParam('is_defaultuser', true, 0);
$login_enabled = $this->getBoolParam('login_enabled', true, 1);
if (($this->getUserDetail('ftps_used') < $this->getUserDetail('ftps') || $this->getUserDetail('ftps') == '-1') || $this->isAdmin() && $is_defaultuser == 1) {
// required parameters
@@ -176,13 +179,14 @@ class Ftps extends ApiCommand implements ResourceEntity
$stmt = Database::prepare("INSERT INTO `" . TABLE_FTP_USERS . "`
(`customerid`, `username`, `description`, `password`, `homedir`, `login_enabled`, `uid`, `gid`, `shell`)
VALUES (:customerid, :username, :description, :password, :homedir, 'y', :guid, :guid, :shell)");
VALUES (:customerid, :username, :description, :password, :homedir, :loginenabled, :guid, :guid, :shell)");
$params = [
"customerid" => $customer['customerid'],
"username" => $username,
"description" => $description,
"password" => $cryptPassword,
"homedir" => $path,
"loginenabled" => $login_enabled ? 'Y' : 'N',
"guid" => $customer['guid'],
"shell" => $shell
];
@@ -257,7 +261,7 @@ class Ftps extends ApiCommand implements ResourceEntity
Customers::increaseUsage($customer['customerid'], 'ftp_lastaccountnumber');
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added ftp-account '" . $username . " (" . $path . ")'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added ftp-account '" . $username . " (" . $path . ")'");
Cronjob::inserttask(TaskId::CREATE_FTP);
if ($sendinfomail == 1) {
@@ -302,7 +306,7 @@ class Ftps extends ApiCommand implements ResourceEntity
$this->mailer()->clearAddresses();
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] added ftp-user '" . $username . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added ftp-user '" . $username . "'");
$result = $this->apiCall('Ftps.get', [
'username' => $username
@@ -367,7 +371,7 @@ class Ftps extends ApiCommand implements ResourceEntity
$params['idun'] = ($id <= 0 ? $username : $id);
$result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) {
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get ftp-user '" . $result['username'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get ftp-user '" . $result['username'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "username '" . $username . "'");
@@ -389,6 +393,8 @@ class Ftps extends ApiCommand implements ResourceEntity
* optional, description for ftp-user
* @param string $shell
* optional, default /bin/false (not changeable when deactivated)
* @param bool $login_enabled
* optional whether to allow login (default) or not
* @param int $customerid
* optional, required when called as admin (if $loginname is not specified)
* @param string $loginname
@@ -419,6 +425,7 @@ class Ftps extends ApiCommand implements ResourceEntity
$password = $this->getParam('ftp_password', true, '');
$description = $this->getParam('ftp_description', true, $result['description']);
$shell = $this->getParam('shell', true, $result['shell']);
$login_enabled = $this->getBoolParam('login_enabled', true, ($result['login_enabled'] == 'Y' ? 1 : 0));
// validation
$password = Validate::validate($password, 'password', '', '', [], true);
@@ -430,6 +437,10 @@ class Ftps extends ApiCommand implements ResourceEntity
$shell = "/bin/false";
}
if ($login_enabled != 1) {
$login_enabled = 0;
}
// get needed customer info to reduce the ftp-user-counter by one
$customer = $this->getCustomerData();
@@ -453,7 +464,7 @@ class Ftps extends ApiCommand implements ResourceEntity
"id" => $id,
"password" => $cryptPassword
], true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated ftp-account password for '" . $result['username'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated ftp-account password for '" . $result['username'] . "'");
}
// path update?
@@ -471,7 +482,7 @@ class Ftps extends ApiCommand implements ResourceEntity
"customerid" => $customer['customerid'],
"id" => $id
], true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated ftp-account homdir for '" . $result['username'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated ftp-account homdir for '" . $result['username'] . "'");
}
}
// it's the task for "new ftp" but that will
@@ -480,13 +491,14 @@ class Ftps extends ApiCommand implements ResourceEntity
$stmt = Database::prepare("
UPDATE `" . TABLE_FTP_USERS . "`
SET `description` = :desc, `shell` = :shell
SET `description` = :desc, `shell` = :shell, `login_enabled` = :loginenabled
WHERE `customerid` = :customerid
AND `id` = :id
");
Database::pexecute($stmt, [
"desc" => $description,
"shell" => $shell,
"loginenabled" => $login_enabled ? 'Y' : 'N',
"customerid" => $customer['customerid'],
"id" => $id
], true, true);
@@ -533,7 +545,7 @@ class Ftps extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $row;
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list ftp-users");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list ftp-users");
return $this->response([
'count' => count($result),
'list' => $result

View File

@@ -61,7 +61,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
public function listing()
{
if ($this->isAdmin()) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list hosting-plans");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list hosting-plans");
$query_fields = [];
$result_stmt = Database::prepare("
SELECT p.*, a.loginname as adminname
@@ -200,8 +200,8 @@ class HostingPlans extends ApiCommand implements ResourceEntity
$value_arr['logviewenabled'] = $this->getBoolParam('logviewenabled', true, 0);
// validation
$name = Validate::validate(trim($name), 'name', '', '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT);
$name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_CONF_TEXT);
if (Settings::Get('system.mail_quota_enabled') != '1') {
$value_arr['email_quota'] = -1;
@@ -227,7 +227,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
'valuearr' => json_encode($value_arr)
];
Database::pexecute($ins_stmt, $ins_data, true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] added hosting-plan '" . $name . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] added hosting-plan '" . $name . "'");
$result = $this->apiCall('HostingPlans.get', [
'planname' => $name
]);
@@ -264,7 +264,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
}
$result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] get hosting-plan '" . $result['name'] . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get hosting-plan '" . $result['name'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "planname '" . $planname . "'");
@@ -382,8 +382,8 @@ class HostingPlans extends ApiCommand implements ResourceEntity
$value_arr['logviewenabled'] = $this->getBoolParam('logviewenabled', true, $result['logviewenabled']);
// validation
$name = Validate::validate(trim($name), 'name', '', '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT);
$name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_CONF_TEXT);
if (Settings::Get('system.mail_quota_enabled') != '1') {
$value_arr['email_quota'] = -1;
@@ -414,7 +414,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
'id' => $id
];
Database::pexecute($upd_stmt, $update_data, true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] updated hosting-plan '" . $result['name'] . "'");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] updated hosting-plan '" . $result['name'] . "'");
return $this->response($update_data);
}
throw new Exception("Not allowed to execute given command.", 403);

View File

@@ -65,7 +65,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
public function listing()
{
if ($this->isAdmin() && ($this->getUserDetail('change_serversettings') || !empty($this->getUserDetail('ip')))) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list ips and ports");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list ips and ports");
$ip_where = "";
$append_where = false;
if (!empty($this->getUserDetail('ip')) && $this->getUserDetail('ip') != -1) {
@@ -175,9 +175,9 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
$docroot = Validate::validate($this->getParam('docroot', true, ''), 'docroot', Validate::REGEX_DIR, '', [], true);
if ((int)Settings::Get('system.use_ssl') == 1) {
$ssl = !empty($this->getBoolParam('ssl', true, 0)) ? intval($this->getBoolParam('ssl', true, 0)) : 0;
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', $ssl, ''), 'ssl_cert_file', '', '', [], true);
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', $ssl, ''), 'ssl_key_file', '', '', [], true);
$ssl = (bool)$this->getBoolParam('ssl', true, 0);
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', !$ssl, ''), 'ssl_cert_file', '', '', [], true);
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', !$ssl, ''), 'ssl_key_file', '', '', [], true);
$ssl_ca_file = Validate::validate($this->getParam('ssl_ca_file', true, ''), 'ssl_ca_file', '', '', [], true);
$ssl_cert_chainfile = Validate::validate($this->getParam('ssl_cert_chainfile', true, ''), 'ssl_cert_chainfile', '', '', [], true);
$sslss = $this->getParam('ssl_specialsettings', true, '');
@@ -335,7 +335,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
'id' => $id
], true, true);
if ($result) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] get ip " . $result['ip'] . " " . $result['port']);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get ip " . $result['ip'] . " " . $result['port']);
return $this->response($result);
}
throw new Exception("IP/port with id #" . $id . " could not be found", 404);
@@ -414,9 +414,9 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
$docroot = Validate::validate($this->getParam('docroot', true, $result['docroot']), 'docroot', Validate::REGEX_DIR, '', [], true);
if ((int)Settings::Get('system.use_ssl') == 1) {
$ssl = $this->getBoolParam('ssl', true, $result['ssl']);
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', $ssl, $result['ssl_cert_file']), 'ssl_cert_file', '', '', [], true);
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', $ssl, $result['ssl_key_file']), 'ssl_key_file', '', '', [], true);
$ssl = (bool)$this->getBoolParam('ssl', true, $result['ssl']);
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', !$ssl, $result['ssl_cert_file']), 'ssl_cert_file', '', '', [], true);
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', !$ssl, $result['ssl_key_file']), 'ssl_key_file', '', '', [], true);
$ssl_ca_file = Validate::validate($this->getParam('ssl_ca_file', true, $result['ssl_ca_file']), 'ssl_ca_file', '', '', [], true);
$ssl_cert_chainfile = Validate::validate($this->getParam('ssl_cert_chainfile', true, $result['ssl_cert_chainfile']), 'ssl_cert_chainfile', '', '', [], true);
$sslss = $this->getParam('ssl_specialsettings', true, $result['ssl_specialsettings']);

View File

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

View File

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

View File

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

View File

@@ -62,11 +62,13 @@ class SubDomains extends ApiCommand implements ResourceEntity
* optional, overwrites path value with an URL to generate a redirect, alternatively use the path
* parameter also for URLs
* @param int $openbasedir_path
* optional, either 0 for domains-docroot, 1 for customers-homedir or 2 for parent-directory of domains-docroot
* optional, either 0 for domains-docroot [default], 1 for customers-homedir or 2 for parent-directory of domains-docroot
* @param int $phpsettingid
* optional, php-settings-id, if empty the $domain value is used
* @param int $redirectcode
* optional, redirect-code-id from TABLE_PANEL_REDIRECTCODES
* @param int $speciallogfile
* optional, whether to create an exclusive web-logfile for this domain (1) or not (0) or inherit value from parentdomain (2, default)
* @param bool $sslenabled
* optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default
* 1 (true)
@@ -104,9 +106,10 @@ class SubDomains extends ApiCommand implements ResourceEntity
$aliasdomain = $this->getParam('alias', true, 0);
$path = $this->getParam('path', true, '');
$url = $this->getParam('url', true, '');
$openbasedir_path = $this->getParam('openbasedir_path', true, 1);
$openbasedir_path = $this->getParam('openbasedir_path', true, 0);
$phpsettingid = $this->getParam('phpsettingid', true, 0);
$redirectcode = $this->getParam('redirectcode', true, Settings::Get('customredirect.default'));
$speciallogfile = intval($this->getParam('speciallogfile', true, 2));
$isemaildomain = $this->getParam('isemaildomain', true, 0);
if (Settings::Get('system.use_ssl')) {
$sslenabled = $this->getBoolParam('sslenabled', true, 1);
@@ -229,6 +232,9 @@ class SubDomains extends ApiCommand implements ResourceEntity
} elseif ($completedomain_check && strtolower($completedomain_check['domain']) == strtolower($completedomain)) {
// the domain does already exist as main-domain
Response::standardError('domainexistalready', $completedomain, true);
} elseif ((int)$domain_check['deactivated'] == 1) {
// main domain is deactivated
Response::standardError('maindomaindeactivated', $domain, true);
}
// if allowed, check for 'is email domain'-flag
@@ -262,7 +268,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
// validate dns if lets encrypt is enabled to check whether we can use it at all
if ($letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
$our_ips = Domain::getIpsOfDomain($domain_check['id']);
$domain_ips = PhpHelper::gethostbynamel6($completedomain);
$domain_ips = PhpHelper::gethostbynamel6($completedomain, true, Settings::Get('system.le_domain_dnscheck_resolver'));
if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true);
}
@@ -273,6 +279,11 @@ class SubDomains extends ApiCommand implements ResourceEntity
$ssl_redirect = 2;
}
// validate speciallogfile value
if ($speciallogfile < 0 || $speciallogfile > 2) {
$speciallogfile = 2; // inherit from parent-domain
}
// get the phpsettingid from parentdomain, #107
$phpsid_stmt = Database::prepare("
SELECT `phpsettingid` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `id` = :id
@@ -351,7 +362,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
"openbasedir" => $domain_check['openbasedir'],
"openbasedir_path" => $openbasedir_path,
"phpenabled" => $domain_check['phpenabled'],
"speciallogfile" => $domain_check['speciallogfile'],
"speciallogfile" => $speciallogfile == 2 ? $domain_check['speciallogfile'] : $speciallogfile,
"specialsettings" => $domain_check['specialsettings'],
"ssl_specialsettings" => $domain_check['ssl_specialsettings'],
"include_specialsettings" => $domain_check['include_specialsettings'],
@@ -486,7 +497,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
$result['ipsandports'] = $this->getIpsForDomain($result['id']);
}
$result['domain_hascert'] = $this->getHasCertValueForDomain((int)$result['id'], (int)$result['parentdomainid']);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get subdomain '" . $result['domain'] . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get subdomain '" . $result['domain'] . "'");
return $this->response($result);
}
$key = ($id > 0 ? "id #" . $id : "domainname '" . $domainname . "'");
@@ -588,6 +599,11 @@ class SubDomains extends ApiCommand implements ResourceEntity
* optional, php-settings-id, if empty the $domain value is used
* @param int $redirectcode
* optional, redirect-code-id from TABLE_PANEL_REDIRECTCODES
* @param bool $speciallogfile
* optional, whether to create an exclusive web-logfile for this domain
* @param bool $speciallogverified
* optional, when setting $speciallogfile to false, this needs to be set to true to confirm the action,
* default 0 (false)
* @param bool $sslenabled
* optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default
* 1 (true)
@@ -645,6 +661,8 @@ class SubDomains extends ApiCommand implements ResourceEntity
$openbasedir_path = $this->getParam('openbasedir_path', true, $result['openbasedir_path']);
$phpsettingid = $this->getParam('phpsettingid', true, $result['phpsettingid']);
$redirectcode = $this->getParam('redirectcode', true, Domain::getDomainRedirectId($id));
$speciallogfile = $this->getBoolParam('speciallogfile', true, $result['speciallogfile']);
$speciallogverified = $this->getBoolParam('speciallogverified', true, 0);
if (Settings::Get('system.use_ssl')) {
$sslenabled = $this->getBoolParam('sslenabled', true, $result['ssl_enabled']);
$ssl_redirect = $this->getBoolParam('ssl_redirect', true, $result['ssl_redirect']);
@@ -738,7 +756,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
// validate dns if lets encrypt is enabled to check whether we can use it at all
if ($result['letsencrypt'] != $letsencrypt && $letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
$our_ips = Domain::getIpsOfDomain($result['parentdomainid']);
$domain_ips = PhpHelper::gethostbynamel6($result['domain']);
$domain_ips = PhpHelper::gethostbynamel6($result['domain'], true, Settings::Get('system.le_domain_dnscheck_resolver'));
if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true);
}
@@ -754,6 +772,10 @@ class SubDomains extends ApiCommand implements ResourceEntity
$ssl_redirect = 2;
}
if ($speciallogfile != $result['speciallogfile'] && $speciallogverified != '1') {
$speciallogfile = $result['speciallogfile'];
}
// is-email-domain flag changed - remove mail accounts and mail-addresses
if (($result['isemaildomain'] == '1') && $isemaildomain == '0') {
$params = [
@@ -786,7 +808,21 @@ class SubDomains extends ApiCommand implements ResourceEntity
Domain::updateRedirectOfDomain($id, $redirectcode);
}
if ($path != $result['documentroot'] || $isemaildomain != $result['isemaildomain'] || $wwwserveralias != $result['wwwserveralias'] || $iswildcarddomain != $result['iswildcarddomain'] || $aliasdomain != (int)$result['aliasdomain'] || $openbasedir_path != $result['openbasedir_path'] || $ssl_redirect != $result['ssl_redirect'] || $letsencrypt != $result['letsencrypt'] || $hsts_maxage != $result['hsts'] || $hsts_sub != $result['hsts_sub'] || $hsts_preload != $result['hsts_preload'] || $phpsettingid != $result['phpsettingid'] || $http2 != $result['http2']) {
if ($path != $result['documentroot']
|| $isemaildomain != $result['isemaildomain']
|| $wwwserveralias != $result['wwwserveralias']
|| $iswildcarddomain != $result['iswildcarddomain']
|| $aliasdomain != (int)$result['aliasdomain']
|| $openbasedir_path != $result['openbasedir_path']
|| $ssl_redirect != $result['ssl_redirect']
|| $letsencrypt != $result['letsencrypt']
|| $hsts_maxage != $result['hsts']
|| $hsts_sub != $result['hsts_sub']
|| $hsts_preload != $result['hsts_preload']
|| $phpsettingid != $result['phpsettingid']
|| $http2 != $result['http2']
|| ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1')
) {
$stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_DOMAINS . "` SET
`documentroot` = :documentroot,
@@ -802,7 +838,8 @@ class SubDomains extends ApiCommand implements ResourceEntity
`hsts` = :hsts,
`hsts_sub` = :hsts_sub,
`hsts_preload` = :hsts_preload,
`phpsettingid` = :phpsettingid
`phpsettingid` = :phpsettingid,
`speciallogfile` = :speciallogfile
WHERE `customerid`= :customerid AND `id`= :id
");
$params = [
@@ -820,6 +857,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
"hsts_sub" => $hsts_sub,
"hsts_preload" => $hsts_preload,
"phpsettingid" => $phpsettingid,
"speciallogfile" => $speciallogfile,
"customerid" => $customer['customerid'],
"id" => $id
];
@@ -856,7 +894,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
Cronjob::inserttask(TaskId::REBUILD_VHOST);
Cronjob::inserttask(TaskId::REBUILD_DNS);
$idna_convert = new IdnaWrapper();
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] edited domain '" . $idna_convert->decode($result['domain']) . "'");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] edited domain '" . $idna_convert->decode($result['domain']) . "'");
}
$result = $this->apiCall('SubDomains.get', [
'id' => $id
@@ -865,7 +903,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
}
/**
* lists all subdomain entries
* lists all customer domain/subdomain entries
*
* @param bool $with_ips
* optional, default true
@@ -910,17 +948,12 @@ class SubDomains extends ApiCommand implements ResourceEntity
$custom_list_result = $_custom_list_result['list'];
}
$customer_ids = [];
$customer_stdsubs = [];
foreach ($custom_list_result as $customer) {
$customer_ids[] = $customer['customerid'];
$customer_stdsubs[$customer['customerid']] = $customer['standardsubdomain'];
}
if (empty($customer_ids)) {
throw new Exception("Required resource unsatisfied.", 405);
}
if (empty($customer_stdsubs)) {
throw new Exception("Required resource unsatisfied.", 405);
}
$select_fields = [
'`d`.*'
@@ -932,9 +965,6 @@ class SubDomains extends ApiCommand implements ResourceEntity
$customer_ids = [
$this->getUserDetail('customerid')
];
$customer_stdsubs = [
$this->getUserDetail('customerid') => $this->getUserDetail('standardsubdomain')
];
$select_fields = [
'`d`.`id`',
@@ -949,7 +979,8 @@ class SubDomains extends ApiCommand implements ResourceEntity
'`d`.`parentdomainid`',
'`d`.`letsencrypt`',
'`d`.`registration_date`',
'`d`.`termination_date`'
'`d`.`termination_date`',
'`d`.`deactivated`'
];
}
$query_fields = [];
@@ -963,7 +994,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `pd` ON `pd`.`id`=`d`.`parentdomainid`
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
AND `d`.`email_only` = '0'
AND `d`.`id` NOT IN (" . implode(', ', $customer_stdsubs) . ")" . $this->getSearchWhere($query_fields, true) . " GROUP BY `d`.`id` ORDER BY `parentdomainname` ASC, `d`.`parentdomainid` ASC " . $this->getOrderBy(true) . $this->getLimit());
" . $this->getSearchWhere($query_fields, true) . " GROUP BY `d`.`id` ORDER BY `parentdomainname` ASC, `d`.`parentdomainid` ASC " . $this->getOrderBy(true) . $this->getLimit());
$result = [];
Database::pexecute($domains_stmt, $query_fields, true, true);
@@ -1063,17 +1094,19 @@ class SubDomains extends ApiCommand implements ResourceEntity
$this->getUserDetail('customerid') => $this->getUserDetail('standardsubdomain')
];
}
// prepare select statement
$domains_stmt = Database::prepare("
SELECT COUNT(*) as num_subdom
FROM `" . TABLE_PANEL_DOMAINS . "` `d`
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
AND `d`.`email_only` = '0'
AND `d`.`id` NOT IN (" . implode(', ', $customer_stdsubs) . ")
");
$result = Database::pexecute_first($domains_stmt, null, true, true);
if ($result) {
return $this->response($result['num_subdom']);
if (!empty($customer_ids)) {
// prepare select statement
$domains_stmt = Database::prepare("
SELECT COUNT(*) as num_subdom
FROM `" . TABLE_PANEL_DOMAINS . "` `d`
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
AND `d`.`email_only` = '0'
AND `d`.`id` NOT IN (" . implode(', ', $customer_stdsubs) . ")
");
$result = Database::pexecute_first($domains_stmt, null, true, true);
if ($result) {
return $this->response($result['num_subdom']);
}
}
return $this->response(0);
}
@@ -1133,7 +1166,9 @@ class SubDomains extends ApiCommand implements ResourceEntity
}
}
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger());
if ((int)$result['aliasdomain'] !== 0) {
Domain::triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger());
}
// delete domain from table
$stmt = Database::prepare("

View File

@@ -92,7 +92,7 @@ class SysLog extends ApiCommand implements ResourceEntity
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $row;
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list log-entries");
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list log-entries");
return $this->response([
'count' => count($result),
'list' => $result

View File

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

View File

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

View File

@@ -0,0 +1,53 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor\Backup;
use Froxlor\Database\Database;
use PDO;
class Backup
{
/**
* returns an array of existing backup-storages
* in our database for the settings-array
*
* @return array
*/
public static function getBackupStorages(): array
{
$storages_array = [
0 => lng('backup.storage_none')
];
// get all storages
$result_stmt = Database::query("SELECT id, type, description FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` ORDER BY type, description");
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
if (!isset($storages_array[$row['id']]) && !in_array($row['id'], $storages_array)) {
$storages_array[$row['id']] = "[" . $row['type'] . "] " . html_entity_decode($row['description']);
}
}
return $storages_array;
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace Froxlor\Backup\Storages;
use Exception;
use Froxlor\FileDir;
class Ftp extends Storage
{
private $ftp_conn = null;
/**
* @return bool
* @throws Exception
*/
public function init(): bool
{
$hostname = $this->sData['storage']['hostname'] ?? '';
$username = $this->sData['storage']['username'] ?? '';
$password = $this->sData['storage']['password'] ?? '';
if (!empty($hostname) && !empty($username) && !empty($password)) {
$tmp = explode(":", $hostname);
$hostname = $tmp[0];
$port = $tmp[1] ?? 21;
$this->ftp_conn = ftp_connect($hostname, $port);
if ($this->ftp_conn === false) {
throw new Exception('Unable to connect to ftp-server "' . $hostname . ':' . $port . '"');
}
if (!ftp_login($this->ftp_conn, $username, $password)) {
throw new Exception('Unable to login to ftp-server "' . $hostname . ':' . $port . '"');
}
return $this->changeToCorrectDirectory();
}
throw new Exception('Empty hostname for FTP backup storage');
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return string
* @throws Exception
*/
protected function putFile(string $filename, string $tmp_source_directory): string
{
$source = FileDir::makeCorrectFile($tmp_source_directory . "/" . $filename);
if (file_exists($source) && ftp_size($this->ftp_conn, $filename) == -1) {
if (ftp_put($this->ftp_conn, $filename, $source, FTP_BINARY)) {
return FileDir::makeCorrectFile($this->getDestinationDirectory() . '/' . $filename);
}
}
return "";
}
/**
* @param string $filename
* @return bool
* @throws Exception
*/
protected function rmFile(string $filename): bool
{
$target = basename($filename);
if (ftp_size($this->ftp_conn, $target) >= 0) {
return ftp_delete($this->ftp_conn, $target);
}
return true;
}
/**
* @return bool
*/
public function shutdown(): bool
{
return ftp_close($this->ftp_conn);
}
/**
* @return bool
* @throws Exception
*/
private function changeToCorrectDirectory(): bool
{
$dirs = explode("/", $this->getDestinationDirectory());
array_shift($dirs);
if (count($dirs) > 0 && !empty($dirs[0])) {
foreach ($dirs as $dir) {
if (empty($dir)) {
continue;
}
if (!@ftp_chdir($this->ftp_conn, $dir)) {
ftp_mkdir($this->ftp_conn, $dir);
ftp_chmod($this->ftp_conn, 0700, $dir);
ftp_chdir($this->ftp_conn, $dir);
}
}
return true;
}
return ftp_chdir($this->ftp_conn, "/");
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Froxlor\Backup\Storages;
use Exception;
use Froxlor\FileDir;
class Local extends Storage
{
/**
* @throws Exception
*/
public function init(): bool
{
// create destination_path
if (!file_exists($this->getDestinationDirectory())) {
return mkdir($this->getDestinationDirectory(), 0700, true);
}
return true;
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return string
* @throws Exception
*/
protected function putFile(string $filename, string $tmp_source_directory): string
{
$source = FileDir::makeCorrectFile($tmp_source_directory . "/" . $filename);
$target = FileDir::makeCorrectFile($this->getDestinationDirectory() . "/" . $filename);
if (file_exists($source) && !file_exists($target)) {
rename($source, $target);
return $target;
}
return "";
}
/**
* @param string $filename
* @return bool
* @throws Exception
*/
protected function rmFile(string $filename): bool
{
$target = FileDir::makeCorrectFile($this->getDestinationDirectory() . "/" . $filename);
if (file_exists($target)) {
return @unlink($target);
}
return true;
}
/**
* @return bool
*/
public function shutdown(): bool
{
return true;
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Froxlor\Backup\Storages;
class Rsync extends Storage
{
/**
* @return bool
*/
public function init(): bool
{
// TODO: Implement init() method.
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return string
*/
protected function putFile(string $filename, string $tmp_source_directory): string
{
return "";
}
/**
* @param string $filename
* @return bool
*/
protected function rmFile(string $filename): bool
{
// TODO: Implement removeOld() method.
}
/**
* @return bool
*/
public function shutdown(): bool
{
return true;
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Froxlor\Backup\Storages;
class S3 extends Storage
{
/**
* @return bool
*/
public function init(): bool
{
// TODO: Implement init() method.
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return string
*/
protected function putFile(string $filename, string $tmp_source_directory): string
{
return "";
}
/**
* @param string $filename
* @return bool
*/
protected function rmFile(string $filename): bool
{
// TODO: Implement removeOld() method.
}
/**
* @return bool
*/
public function shutdown(): bool
{
return true;
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Froxlor\Backup\Storages;
class Sftp extends Storage
{
/**
* @return bool
*/
public function init(): bool
{
// TODO: Implement init() method.
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return string
*/
protected function putFile(string $filename, string $tmp_source_directory): string
{
return "";
}
/**
* @param string $filename
* @return bool
*/
protected function rmFile(string $filename): bool
{
// TODO: Implement removeOld() method.
}
/**
* @return bool
*/
public function shutdown(): bool
{
return true;
}
}

View File

@@ -0,0 +1,281 @@
<?php
namespace Froxlor\Backup\Storages;
use Exception;
use Froxlor\Database\Database;
use Froxlor\FileDir;
abstract class Storage
{
private string $tmpDirectory;
protected array $sData;
protected array $filesToStore;
/**
* @throws Exception
*/
public function __construct(array $storage_data)
{
$this->sData = $storage_data;
$this->tmpDirectory = FileDir::makeCorrectDir(sys_get_temp_dir() . '/backup-' . $this->sData['loginname']);
}
/**
* Validate sData, open connection to target storage, etc.
*
* @return bool
*/
abstract public function init(): bool;
/**
* Disconnect / clean up connection if needed
*
* @return bool
*/
abstract public function shutdown(): bool;
/**
* prepare files to back up (e.g. create archive or similar) and fill $filesToStore
*
* @return void
* @throws Exception
*/
public function prepareFiles(): void
{
$this->filesToStore = [];
$tmpdir = FileDir::makeCorrectDir($this->tmpDirectory . '/.tmp/');
FileDir::safe_exec('mkdir -p ' . escapeshellarg($tmpdir));
// create archive of web, mail and database data
$this->prepareWebData();
$this->prepareDatabaseData();
$this->prepareMailData();
// create json-info-file
}
/**
* @throws Exception
*/
private function prepareWebData(): void
{
$tmpdir = FileDir::makeCorrectDir($this->tmpDirectory . '/.tmp/web');
FileDir::safe_exec('mkdir -p ' . escapeshellarg($tmpdir));
FileDir::safe_exec('tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/' . $this->sData['loginname'] . '-web.tar.gz')) . ' -C ' . escapeshellarg($this->sData['documentroot']) . ' .');
$this->filesToStore[] = FileDir::makeCorrectFile($tmpdir . '/' . $this->sData['loginname'] . '-web.tar.gz');
}
/**
* @throws Exception
*/
private function prepareDatabaseData(): void
{
$tmpdir = FileDir::makeCorrectDir($this->tmpDirectory . '/.tmp/mysql');
FileDir::safe_exec('mkdir -p ' . escapeshellarg($tmpdir));
// get all customer database-names
$sel_stmt = Database::prepare("
SELECT `databasename`, `dbserver` FROM `" . TABLE_PANEL_DATABASES . "`
WHERE `customerid` = :cid ORDER BY `dbserver`
");
Database::pexecute($sel_stmt, [
'cid' => $this->sData['customerid']
]);
$has_dbs = false;
$current_dbserver = -1;
while ($row = $sel_stmt->fetch()) {
// Get sql_root data for the specific database-server the database resides on
if ($current_dbserver != $row['dbserver']) {
Database::needRoot(true, $row['dbserver']);
Database::needSqlData();
$sql_root = Database::getSqlData();
Database::needRoot(false);
// create temporary mysql-defaults file for the connection-credentials/details
$mysqlcnf_file = tempnam("/tmp", "frx");
$mysqlcnf = "[mysqldump]\npassword=" . $sql_root['passwd'] . "\nhost=" . $sql_root['host'] . "\n";
if (!empty($sql_root['port'])) {
$mysqlcnf .= "port=" . $sql_root['port'] . "\n";
} elseif (!empty($sql_root['socket'])) {
$mysqlcnf .= "socket=" . $sql_root['socket'] . "\n";
}
file_put_contents($mysqlcnf_file, $mysqlcnf);
}
$bool_false = false;
FileDir::safe_exec('mysqldump --defaults-file=' . escapeshellarg($mysqlcnf_file) . ' -u ' . escapeshellarg($sql_root['user']) . ' ' . $row['databasename'] . ' > ' . FileDir::makeCorrectFile($tmpdir . '/' . $row['databasename'] . '_' . date('YmdHi', time()) . '.sql'), $bool_false, [
'>'
]);
$has_dbs = true;
$current_dbserver = $row['dbserver'];
}
if ($has_dbs) {
$this->filesToStore[] = $tmpdir;
}
if (@file_exists($mysqlcnf_file)) {
@unlink($mysqlcnf_file);
}
}
private function prepareMailData(): void
{
$tmpdir = FileDir::makeCorrectDir($this->tmpDirectory . '/.tmp/mail');
FileDir::safe_exec('mkdir -p ' . escapeshellarg($tmpdir));
// get all customer mail-accounts
$sel_stmt = Database::prepare("
SELECT `homedir`, `maildir` FROM `" . TABLE_MAIL_USERS . "`
WHERE `customerid` = :cid
");
Database::pexecute($sel_stmt, [
'cid' => $this->sData['customerid']
]);
$tar_file_list = "";
$mail_homedir = "";
while ($row = $sel_stmt->fetch()) {
$tar_file_list .= escapeshellarg("./" . $row['maildir']) . " ";
if (empty($mail_homedir)) {
// this should be equal for all entries
$mail_homedir = $row['homedir'];
}
}
if (!empty($tar_file_list)) {
FileDir::safe_exec('tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/' . $this->sData['loginname'] . '-mail.tar.gz')) . ' -C ' . escapeshellarg($mail_homedir) . ' ' . trim($tar_file_list));
$this->filesToStore[] = FileDir::makeCorrectFile($tmpdir . '/' . $this->sData['loginname'] . '-mail.tar.gz');
}
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return string
*/
abstract protected function putFile(string $filename, string $tmp_source_directory): string;
/**
* @param string $filename
* @return bool
*/
abstract protected function rmFile(string $filename): bool;
/**
* @return bool
* @throws Exception
*/
public function removeOld(): bool
{
// retention in days
$retention = $this->sData['storage']['retention'] ?? 3;
// keep date
$keepDate = new \DateTime();
$keepDate->setTime(0, 0, 0, 1);
// subtract retention days
$keepDate->sub(new \DateInterval('P' . $retention . 'D'));
// select target backups to remove for this storage-id and customer
$sel_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_BACKUPS . "`
WHERE `created_at` < :keepdate
AND `storage_id` = :sid
AND `customerid` = :cid
");
Database::pexecute($sel_stmt, [
'keepdate' => $keepDate->format('U'),
'sid' => $this->sData['backup'],
'cid' => $this->sData['customerid']
]);
while ($oldBackup = $sel_stmt->fetch(\PDO::FETCH_ASSOC)) {
$this->rmFile($oldBackup['filename']);
}
}
/**
* Returns the storage configured destination path for all backups
*
* @return string
* @throws Exception
*/
public function getDestinationDirectory(): string
{
return FileDir::makeCorrectDir($this->sData['storage']['destination_path'] ?? "/");
}
/**
* Create backup-archive/file from $filesToStore and call putFile()
*
* @return bool
* @throws Exception
*/
public function createFromFiles(): bool
{
if (empty($this->filesToStore)) {
return false;
}
$filename = FileDir::makeCorrectFile($this->tmpDirectory . "/backup-" . $this->sData['loginname'] . "-" . date('c') . ".tar.gz");
$tmpdir = FileDir::makeCorrectDir($this->tmpDirectory . '/.tmp/');
$create_export_tar_data = implode(" ", $this->filesToStore);
FileDir::safe_exec('chown -R ' . (int)$this->sData['guid'] . ':' . (int)$this->sData['guid'] . ' ' . escapeshellarg($tmpdir));
if (!empty($data['pgp_public_key'])) {
// pack all archives in tmp-dir to one archive and encrypt it with gpg
$recipient_file = FileDir::makeCorrectFile($this->tmpDirectory . '/' . $this->sData['loginname'] . '-recipients.gpg');
file_put_contents($recipient_file, $data['pgp_public_key']);
$return_value = [];
FileDir::safe_exec('tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data) . ' | gpg --encrypt --recipient-file ' . escapeshellarg($recipient_file) . ' --output ' . escapeshellarg($filename) . ' --trust-model always --batch --yes', $return_value, ['|']);
} else {
// pack all archives in tmp-dir to one archive
FileDir::safe_exec('tar cfz ' . escapeshellarg($filename) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data));
}
// determine filesize (use stat locally here b/c files are possibly large and php's filesize() can't handle them)
$fileSizeOutput = FileDir::safe_exec('/usr/bin/stat -c "%s" ' . escapeshellarg($filename));
$fileSize = (int)array_shift($fileSizeOutput);
// add entry to database and upload/store file
FileDir::safe_exec('rm -rf ' . escapeshellarg($tmpdir));
$fileDest = $this->putFile(basename($filename), $this->tmpDirectory);
if (!empty($fileDest)) {
$this->addEntry($fileDest, $fileSize);
return true;
}
return false;
}
/**
* @param string $filename
* @param int $fileSize
* @return void
* @throws Exception
*/
private function addEntry(string $filename, int $fileSize): void
{
$ins_stmt = Database::prepare("
INSERT INTO `" . TABLE_PANEL_BACKUPS . "` SET
`adminid` = :adminid,
`customerid` = :customerid,
`loginname` = :loginname,
`size` = :size,
`storage_id` = :sid,
`filename` = :filename,
`created_at` = UNIX_TIMESTAMP()
");
Database::pexecute($ins_stmt, [
'adminid' => $this->sData['adminid'],
'customerid' => $this->sData['customerid'],
'loginname' => $this->sData['loginname'],
'size' => $fileSize,
'sid' => $this->sData['backup'],
'filename' => $filename
]);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Froxlor\Backup\Storages;
use Exception;
use Froxlor\Database\Database;
class StorageFactory
{
public static function fromType(string $type, array $storage_data): Storage
{
$type = "\\Froxlor\\Backup\\Storages\\" . ucfirst($type);
return new $type($storage_data);
}
/**
* @throws Exception
*/
public static function fromStorageId(int $storage_id, array $user_data): Storage
{
$storage = self::readStorageData($storage_id);
$storage_data = $user_data;
$storage_data['storage'] = $storage;
return self::fromType($storage['type'], $storage_data);
}
/**
* @throws Exception
*/
private static function readStorageData(int $storage_id): array
{
$stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` WHERE `id` = :bid");
$storage = Database::pexecute_first($stmt, ['bid' => $storage_id]);
if (empty($storage)) {
throw new Exception("Invalid/empty backup-storage. Unable to continue");
}
return $storage;
}
}

View File

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

View File

@@ -0,0 +1,178 @@
<?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\Config\ConfigParser;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
final class ConfigDiff extends CliCommand
{
protected function configure(): void
{
$this->setName('froxlor:config-diff')
->setDescription('Shows differences in config templates between OS versions')
->addArgument('from', InputArgument::OPTIONAL, 'OS version to compare against')
->addArgument('to', InputArgument::OPTIONAL, 'OS version to compare from')
->addOption('list', 'l', InputOption::VALUE_NONE, 'List all possible OS versions')
->addOption('diff-params', '', InputOption::VALUE_REQUIRED, 'Additional parameters for `diff`, e.g. --diff-params="--color=always"');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
require Froxlor::getInstallDir() . '/lib/functions.php';
$parsers = $versions = [];
foreach (glob(Froxlor::getInstallDir() . '/lib/configfiles/*.xml') as $config) {
$name = str_replace(".xml", "", strtolower(basename($config)));
$parser = new ConfigParser($config);
$versions[$name] = $parser->getCompleteDistroName();
$parsers[$name] = $parser;
}
asort($versions);
if ($input->getOption('list') === true) {
$output->writeln('The following OS version templates are available:');
foreach ($versions as $k => $v) {
$output->writeln(str_pad($k, 20) . $v);
}
return self::SUCCESS;
}
if (!$input->hasArgument('from') || !array_key_exists($input->getArgument('from'), $versions)) {
$output->writeln('<error>Missing or invalid "from" argument.</error>');
$output->writeln('Available versions: ' . implode(', ', array_keys($versions)));
return self::INVALID;
}
if (!$input->hasArgument('to') || !array_key_exists($input->getArgument('to'), $versions)) {
$output->writeln('<error>Missing or invalid "to" argument.</error>');
$output->writeln('Available versions: ' . implode(', ', array_keys($versions)));
return self::INVALID;
}
// Make sure diff is installed
$check_diff_installed = FileDir::safe_exec('which diff');
if (count($check_diff_installed) === 0) {
$output->writeln('<error>Unable to find "diff" installation on your system.</error>');
return self::INVALID;
}
$parser_from = $parsers[$input->getArgument('from')];
$parser_to = $parsers[$input->getArgument('to')];
$tmp_from = tempnam(sys_get_temp_dir(), 'froxlor_config_diff_from');
$tmp_to = tempnam(sys_get_temp_dir(), 'froxlor_config_diff_to');
$files = [];
$titles_by_key = [];
// Aggregate content for each config file
foreach ([[$parser_from, 'from'], [$parser_to, 'to']] as $todo) {
foreach ($todo[0]->getServices() as $service_type => $service) {
foreach ($service->getDaemons() as $daemon_name => $daemon) {
foreach ($daemon->getConfig() as $instruction) {
if ($instruction['type'] !== 'file') {
continue;
}
if (isset($instruction['subcommands'])) {
foreach ($instruction['subcommands'] as $subinstruction) {
if ($subinstruction['type'] !== 'file') {
continue;
}
$content = $subinstruction['content'];
}
} else {
$content = $instruction['content'];
}
if (!isset($content)) {
throw new \Exception("Cannot find content for {$instruction['name']}");
}
$key = "{$service_type}_{$daemon_name}_{$instruction['name']}";
$titles_by_key[$key] = "{$service->title} : {$daemon->title} : {$instruction['name']}";
if (!isset($files[$key])) {
$files[$key] = ['from' => '', 'to' => ''];
}
$files[$key][$todo[1]] = $this->filterContent($content);
}
}
}
}
ksort($files);
$diff_params = '';
if ($input->hasOption('diff-params') && trim($input->getOption('diff-params')) !== '') {
$diff_params = trim($input->getOption('diff-params'));
}
// Run diff on each file and output, if anything changed
foreach ($files as $file_key => $content) {
file_put_contents($tmp_from, $content['from']);
file_put_contents($tmp_to, $content['to']);
$diff_output = FileDir::safe_exec("{$check_diff_installed[0]} {$diff_params} {$tmp_from} {$tmp_to}");
if (count($diff_output) === 0) {
continue;
}
$output->writeln('<info># ' . $titles_by_key[$file_key] . '</info>');
$output->writeln(implode("\n", $diff_output) . "\n");
unset($diff_output);
}
// Remove tmp files again
unlink($tmp_from);
unlink($tmp_to);
return self::SUCCESS;
}
private function filterContent(string $content): string
{
$new_content = '';
foreach (explode("\n", $content) as $n) {
$n = trim($n);
if (!$n) {
continue;
}
if (str_starts_with($n, '#')) {
continue;
}
$new_content .= $n . "\n";
}
return $new_content;
}
}

View File

@@ -42,7 +42,7 @@ final class ConfigServices extends CliCommand
{
private $yes_to_all_supported = [
/* 'bookworm', */
'bookworm',
'bionic',
'bullseye',
'buster',

View File

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

View File

@@ -52,7 +52,7 @@ final class MasterCron extends CliCommand
$this->setDescription('Regulary perform tasks created by froxlor');
$this->addArgument('job', InputArgument::IS_ARRAY, 'Job(s) to run');
$this->addOption('run-task', 'r', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Run a specific task [1 = re-generate configs, 4 = re-generate dns zones, 10 = re-set quotas, 99 = re-create cron.d-file]')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces re-generating of config-files (webserver, nameserver, etc.)')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces given job or, if none given, forces re-generating of config-files (webserver, nameserver, etc.)')
->addOption('debug', 'd', InputOption::VALUE_NONE, 'Output debug information about what is going on to STDOUT.')
->addOption('no-fork', 'N', InputOption::VALUE_NONE, 'Do not fork to background (traffic cron only).');
}
@@ -62,16 +62,22 @@ final class MasterCron extends CliCommand
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
if ($result != self::SUCCESS) {
// requirements failed, exit
return $result;
}
$jobs = $input->getArgument('job');
// handle force option
if ($input->getOption('force')) {
// rebuild all config files
Cronjob::inserttask(TaskId::REBUILD_VHOST);
Cronjob::inserttask(TaskId::REBUILD_DNS);
Cronjob::inserttask(TaskId::CREATE_QUOTA);
Cronjob::inserttask(TaskId::REBUILD_CRON);
array_push($jobs, 'tasks');
if (empty($jobs) || in_array('tasks', $jobs)) {
Cronjob::inserttask(TaskId::REBUILD_VHOST);
Cronjob::inserttask(TaskId::REBUILD_DNS);
Cronjob::inserttask(TaskId::CREATE_QUOTA);
Cronjob::inserttask(TaskId::REBUILD_CRON);
array_push($jobs, 'tasks');
}
define('CRON_IS_FORCED', 1);
}
// handle debug option
@@ -86,7 +92,7 @@ final class MasterCron extends CliCommand
if ($input->getOption('run-task')) {
$tasks_to_run = $input->getOption('run-task');
foreach ($tasks_to_run as $ttr) {
if (in_array($ttr, [1, 4, 10, 99])) {
if (in_array($ttr, [TaskId::REBUILD_VHOST, TaskId::REBUILD_DNS, TaskId::CREATE_QUOTA, TaskId::REBUILD_CRON])) {
Cronjob::inserttask($ttr);
array_push($jobs, 'tasks');
} else {
@@ -111,8 +117,8 @@ final class MasterCron extends CliCommand
]);
$this->cronLog->setCronDebugFlag(defined('CRON_DEBUG_FLAG'));
// check whether there are actual tasks to perform by 'tasks'-cron so
// we dont regenerate files unnecessarily
// check whether there are actual tasks to perform by 'tasks'-cron, so
// we don't regenerate files unnecessarily
$tasks_cnt_stmt = Database::query("SELECT COUNT(*) as jobcnt FROM `panel_tasks`");
$tasks_cnt = $tasks_cnt_stmt->fetch(PDO::FETCH_ASSOC);
@@ -161,6 +167,9 @@ final class MasterCron extends CliCommand
FroxlorLogger::getInstanceOf()->setCronLog(0);
}
// clean up possible old login-links
Database::query("DELETE FROM `" . TABLE_PANEL_LOGINLINKS . "` WHERE `valid_until` < UNIX_TIMESTAMP()");
return $result;
}

View File

@@ -0,0 +1,165 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor\Cli;
use Froxlor\Cron\TaskId;
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\Settings;
use Froxlor\System\Cronjob;
use PDO;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Style\SymfonyStyle;
final class ValidateAcmeWebroot extends CliCommand
{
protected function configure()
{
$this->setName('froxlor:validate-acme-webroot');
$this->setDescription('Validates the Le_Webroot value is correct for froxlor managed domains with Let\'s Encrypt certificate.');
$this->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for confirmation, update files if necessary');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output, true);
$io = new SymfonyStyle($input, $output);
if ((int) Settings::Get('system.leenabled') == 0) {
$io->info("Let's Encrypt not activated in froxlor settings.");
$result = self::INVALID;
}
if ($result == self::SUCCESS) {
$yestoall = $input->getOption('yes-to-all') !== false;
$helper = $this->getHelper('question');
$count_changes = 0;
// get all Let's Encrypt enabled domains
$sel_stmt = Database::prepare("SELECT id, domain FROM panel_domains WHERE `letsencrypt` = '1' AND aliasdomain IS NULL ORDER BY id ASC");
Database::pexecute($sel_stmt);
$domains = $sel_stmt->fetchAll(PDO::FETCH_ASSOC);
// check for froxlor-vhost
if (Settings::Get('system.le_froxlor_enabled') == '1') {
$domains[] = [
'id' => 0,
'domain' => Settings::Get('system.hostname')
];
}
$upd_stmt = Database::prepare("UPDATE domain_ssl_settings SET `validtodate`=NULL WHERE `domainid` = :did");
$acmesh_dir = dirname(Settings::Get('system.acmeshpath'));
$acmesh_challenge_dir = rtrim(FileDir::makeCorrectDir(Settings::Get('system.letsencryptchallengepath')), "/");
$recommended = rtrim(FileDir::makeCorrectDir(Froxlor::getInstallDir()), "/");
if ($acmesh_challenge_dir != $recommended) {
$io->warning([
"ACME challenge docroot from settings differs from the current installation directory.",
"Settings: '" . $acmesh_challenge_dir . "'",
"Default/recommended value: '" . $recommended . "'",
]);
$question = new ConfirmationQuestion('Fix ACME challenge docroot setting? [yes] ', true, '/^(y|j)/i');
if ($yestoall || $helper->ask($input, $output, $question)) {
Settings::Set('system.letsencryptchallengepath', $recommended);
$former_value = $acmesh_challenge_dir;
$acmesh_challenge_dir = $recommended;
// need to update the corresponding acme-alias config-file
$acme_alias_file = Settings::Get('system.letsencryptacmeconf');
$sed_params = "s@".$former_value."@" . $acmesh_challenge_dir . "@";
FileDir::safe_exec('sed -i -e "' . $sed_params . '" ' . escapeshellarg($acme_alias_file));
$count_changes++;
}
}
foreach ($domains as $domain_arr) {
$domain = $domain_arr['domain'];
$acme_domain_conf = FileDir::makeCorrectFile($acmesh_dir . '/' . $domain . '/' . $domain . '.conf');
if (file_exists($acme_domain_conf)) {
$io->text("Getting info from " . $acme_domain_conf);
$conf_content = file_get_contents($acme_domain_conf);
} else {
$acme_domain_conf = FileDir::makeCorrectFile($acmesh_dir . '/' . $domain . '_ecc/' . $domain . '.conf');
if (file_exists($acme_domain_conf)) {
$io->text("Getting info from " . $acme_domain_conf);
$conf_content = file_get_contents($acme_domain_conf);
} else {
$io->info("No domain configuration file found in '" . $acmesh_dir . "'");
continue;
}
}
if (!empty($conf_content)) {
$lines = explode("\n", $conf_content);
foreach ($lines as $line) {
$val_key = explode("=", $line);
if ($val_key[0] == 'Le_Webroot') {
$domain_webroot = trim(trim($val_key[1], "'"), '"');
if ($domain_webroot != $acmesh_challenge_dir) {
$io->warning("Domain '" . $domain . "' has old/wrong Le_Webroot setting: '" . $domain_webroot . ' <> ' . $acmesh_challenge_dir . "'");
$question = new ConfirmationQuestion('Fix Le_Webroot? [yes] ', true, '/^(y|j)/i');
if ($yestoall || $helper->ask($input, $output, $question)) {
$sed_params = "s@Le_Webroot=.*@Le_Webroot='" . $acmesh_challenge_dir . "'@";
FileDir::safe_exec('sed -i -e "' . $sed_params . '" ' . escapeshellarg($acme_domain_conf));
Database::pexecute($upd_stmt, ['did' => $domain_arr['id']]);
$io->success("Correction of Le_Webroot successful");
$count_changes++;
} else {
continue;
}
} else {
$io->info("Domain '" . $domain . "' Le_Webroot value is correct");
}
break;
} else {
continue;
}
}
}
}
if ($count_changes > 0) {
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
$io->info("Changes detected but froxlor has been updated. Inserting task to rebuild vhosts after update.");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
} else {
$question = new ConfirmationQuestion('Changes detected. Force cronjob to refresh certificates? [yes] ', true, '/^(y|j)/i');
if ($yestoall || $helper->ask($input, $output, $question)) {
passthru(FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . ' froxlor:cron -f -d');
}
}
} else {
$io->success("No changes necessary.");
}
}
return $result;
}
}

View File

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

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