Compare commits

...

453 Commits

Author SHA1 Message Date
Michael Kaufmann
1ba5f1bf5c Merge remote-tracking branch 'origin/main' into v2.2 2025-07-08 09:03:16 +02:00
Michael Kaufmann
97360e450d set version to 2.2.8 for maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-07-08 09:02:19 +02:00
Michael Kaufmann
85299085f0 Merge remote-tracking branch 'origin/main' into v2.2 2025-07-08 09:00:20 +02:00
Michael Kaufmann
6d10a9a096 updated dependencies; fixed typo; improved fpm-pool config section splitting
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-07-08 08:59:51 +02:00
Dominik Tugend
4ea5773abf Relax dkim_entry visibilty for admins in domain editor like it is for customers (#1336) 2025-06-24 16:55:50 +02:00
Michael Kaufmann
a8395598c3 do not add ssl_stapling in nginx vhost automatically for let's encrypt certificates as they have removed support for it
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-06-24 16:53:16 +02:00
Michael Kaufmann
5beeae8fd1 validate username for webserver/fcgid/php-fpm in global settings to ensure it exists and is not a froxlor-managed user, fixes #1332
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-06-05 11:28:27 +02:00
Michael Kaufmann
8f3228716a fix parameter of lng() in UpdateCommand
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-05-26 10:18:42 +02:00
kissgyula
f8c8f1c333 Update hu.lng.php (#1330) 2025-05-20 17:33:30 +02:00
Michael Kaufmann
bda644530b fix typo, thx to TechPanda
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-05-10 23:03:53 +02:00
dependabot[bot]
69ed733e87 Bump league/commonmark from 2.6.2 to 2.7.0 (#1329)
Bumps [league/commonmark](https://github.com/thephpleague/commonmark) from 2.6.2 to 2.7.0.
- [Release notes](https://github.com/thephpleague/commonmark/releases)
- [Changelog](https://github.com/thephpleague/commonmark/blob/2.7/CHANGELOG.md)
- [Commits](https://github.com/thephpleague/commonmark/compare/2.6.2...2.7.0)

---
updated-dependencies:
- dependency-name: league/commonmark
  dependency-version: 2.7.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-06 08:28:03 +02:00
Michael Kaufmann
c9c803900a set correct field name for 'rewrite subject' in edit-email form, fixes #1328
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-05-05 09:43:27 +02:00
Michael Kaufmann
9b5c752380 run database-updates in a new process when using CLI updater to use latest extracted files
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-05-05 09:42:46 +02:00
Michael Kaufmann
b6fae81f1b Merge remote-tracking branch 'origin/main' into v2.2 2025-05-02 09:28:33 +02:00
Michael Kaufmann
bab6c3da5b set version to 2.2.7 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-05-02 09:24:50 +02:00
Michael Kaufmann
c494838069 Merge remote-tracking branch 'origin/main' into v2.2 2025-05-01 10:30:31 +02:00
dependabot[bot]
d191693f74 Bump vite from 6.2.6 to 6.3.4 (#1327)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.6 to 6.3.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.3.4/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.3.4
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-01 10:29:52 +02:00
Michael Kaufmann
aaac84245e fix domain.update() documentroot check
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-04-29 20:12:38 +02:00
Michael Kaufmann
46a46816b0 more rework on path/url validation for domains/subdomains documentroot, fixes #1325
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-04-29 19:57:07 +02:00
Michael Kaufmann
ce4f64e73a just dont test for http-response code, not worth the hustle
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-04-28 11:10:53 +02:00
Michael Kaufmann
b7439d0f3b put http-header related checks in output buffering
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-04-28 11:07:06 +02:00
Michael Kaufmann
a9da57f6fb please php8.3 and phpunit with no http-header output
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-04-28 10:28:02 +02:00
Michael Kaufmann
8ab2e43426 switch testing from php 7.4 and 8.2 to 7.4 and 8.3
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-04-28 10:11:55 +02:00
Michael Kaufmann
8932174df8 ext-gnupg is optional/suggested
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-04-28 10:11:12 +02:00
Michael Kaufmann
eb6ea8195d Merge remote-tracking branch 'origin/main' into v2.2 2025-04-27 16:23:40 +02:00
Michael Kaufmann
a9feb97c27 update dependencies
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-04-27 16:23:07 +02:00
Michael Kaufmann
0a7ca058aa Merge remote-tracking branch 'origin/main' into v2.2 2025-04-24 10:02:37 +02:00
Michael Kaufmann
d9032f3790 rework validateUrl(), refs #1325
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-04-24 09:56:03 +02:00
Michael Kaufmann
76793c8992 adjust sql-queries for db-mgmt to be more compatible with mysql-dbms, fixes #1316, #1324, #1326 2025-04-17 14:56:55 +02:00
dependabot[bot]
6068daece2 Bump vite from 6.2.5 to 6.2.6 (#1323)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.5 to 6.2.6.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.6/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.2.6
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-14 08:48:22 +02:00
dependabot[bot]
0624292b49 Bump vite from 6.2.4 to 6.2.5 (#1322)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.4 to 6.2.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.5/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.5/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.2.5
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-05 17:12:20 +02:00
dependabot[bot]
afc3b68abf Bump axios from 1.8.1 to 1.8.2 (#1321)
Bumps [axios](https://github.com/axios/axios) from 1.8.1 to 1.8.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.8.1...v1.8.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.8.2
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 11:05:45 +02:00
dependabot[bot]
830f43a9db Bump vite from 6.2.0 to 6.2.4 (#1320)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.0 to 6.2.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.4/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.4/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 11:00:55 +02:00
Michael Kaufmann
9253a94aad fix wrong database-usage in DbManagerMySQL::grantCreateToCustomerDbs() when using more than 1 mysql-server; refs #1312
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-03-10 17:46:50 +01:00
Daniel
e9d3de0c25 wip (#1313) 2025-03-10 09:44:42 +01:00
Michael Kaufmann
bbda491e82 Merge remote-tracking branch 'origin/main' into v2.2 2025-03-08 11:44:18 +01:00
Michael Kaufmann
a0f179a7e7 fix typo in domain.js
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-03-08 11:43:08 +01:00
Michael Kaufmann
5afc5272d1 add missing js changes when editing domains; set version to 2.2.6 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-03-08 11:37:50 +01:00
Michael Kaufmann
8f5bd789a4 do not check for possible existing certificate in case of issue for froxlor-vhost
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-03-07 20:40:05 +01:00
Michael Kaufmann
ff64740880 Merge remote-tracking branch 'origin/main' into v2.2 2025-03-07 09:21:41 +01:00
Michael Kaufmann
0a221d0479 only show 'move to another admin' if current admin can see other admin-resources; append domainid to pagination of email-domain adresses list; check for invalid lockfile in cron management
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-03-06 09:08:51 +01:00
Michael Kaufmann
097cde13ad Merge remote-tracking branch 'origin/main' into v2.2 2025-03-05 17:12:05 +01:00
kissgyula
f90dc5854d Hungarian translation (#1310) 2025-03-03 18:29:15 +01:00
Maurice Preuß (envoyr)
8eb38a8a28 correction of autocomplete when entering passwords or access data; prevents saving of auto fill data for smtp credentials in system settings
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2025-02-27 02:01:51 +01:00
Maurice Preuß (envoyr)
e17135f0c3 fix text formatting; add composer dev command for local development
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2025-02-27 01:42:06 +01:00
Maurice Preuß (envoyr)
c5017786e0 audit of the npm packages and upgrade of the versions
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2025-02-27 01:11:33 +01:00
Maurice Preuß (envoyr)
2e18d7c581 do not renew domains when ssl_redirect is 2 because we have already added a task to do it later when redirect is temporarily disabled
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2025-02-27 00:30:04 +01:00
Michael Kaufmann
ceb7f5b23d ignore diff as well as patch files
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-02-08 18:42:14 +01:00
Michael Kaufmann
ebed800dec allow admins without change-serversettings to adjust dkim flag of domains, hide webserver-ssl-options for new domains if no default ssl-ip-addresses are selected in the settings; adjust visibility of possibly required dns entries for admins (domain edit)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-02-08 18:38:59 +01:00
Michael Kaufmann
32344e39cf fix updating of std.subdomains when changing default ip-addresses (empty value!)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-02-08 17:46:22 +01:00
Michael Kaufmann
e35092c31f add more detailed info in case of only suggestions on installation; correct display of required dns entries for admins
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-02-08 16:22:13 +01:00
Michael Kaufmann
606377f1d9 fix 'show necessary dns entries for mail/antispan also in admin-view of domain' if bind is enabled but domain is not using nameserver
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-02-05 13:57:00 +01:00
Michael Kaufmann
b9baeb76d5 actually insert task to reconfigure let's encrypt enabled services when triggered
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-02-05 13:33:33 +01:00
Michael Kaufmann
2f2d72851b fix plaintext-mail content, thx to AlexL
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-01-31 00:03:54 +01:00
Michael Kaufmann
a2925af73a fix deletion of webserver-logfiles when customer gehts deleted, thx to irisdina
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-01-30 13:16:06 +01:00
Michael Kaufmann
1008c015a5 Merge remote-tracking branch 'origin/main' into v2.2 2025-01-22 09:17:31 +01:00
Michael Kaufmann
2eda4ae972 adjust unit-test to new unique-admin-mail-rule
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-01-22 09:14:23 +01:00
Michael Kaufmann
38b2dbd81b Merge remote-tracking branch 'origin/main' into v2.2 2025-01-22 09:10:31 +01:00
Michael Kaufmann
aab98e4dae fix 1046 No database selected issue when adding customer
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-01-22 09:09:14 +01:00
dependabot[bot]
4b930375b7 Bump vite from 4.5.5 to 4.5.9 (#1306)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.5 to 4.5.9.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.9/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.9/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-22 08:53:37 +01:00
Michael Kaufmann
c7245d0b9b Merge remote-tracking branch 'origin/main' into v2.2 2025-01-17 08:48:56 +01:00
Michael Kaufmann
ec42003367 add safety when unsetting isemaildomain flag in domain, fixes #1305
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-01-12 16:53:05 +01:00
Michael Kaufmann
fde43f8060 do not output potentially unsafe content, fixes GHSA-26xq-m8xw-6373
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-01-12 10:27:44 +01:00
Michael Kaufmann
a43d53d540 force admin email addresses to be unique and not be used for customers, fixes GHSA-7j6w-p859-464f
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2025-01-12 10:27:02 +01:00
Michael Kaufmann
3638dc08ea add new task to (re)configure mail/ftp services with let's encrypt; refs #1297
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-12-24 09:34:59 +01:00
Michael Kaufmann
c2d166c866 corrected regex for dns CAA entries; fixes #1300
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-12-19 08:59:00 +01:00
Michael Kaufmann
0fb9357e87 set cookie SameSite option to 'Lax' for loginlinks to work as intended; fixes #1299
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-12-10 20:15:41 +01:00
Michael Kaufmann
26c3c87d28 Merge branch 'main' of github.com:froxlor/Froxlor 2024-12-10 08:21:14 +01:00
dependabot[bot]
0aa3e2f7b1 Bump league/commonmark from 2.5.3 to 2.6.0 (#1298)
Bumps [league/commonmark](https://github.com/thephpleague/commonmark) from 2.5.3 to 2.6.0.
- [Release notes](https://github.com/thephpleague/commonmark/releases)
- [Changelog](https://github.com/thephpleague/commonmark/blob/2.6/CHANGELOG.md)
- [Commits](https://github.com/thephpleague/commonmark/compare/2.5.3...2.6.0)

---
updated-dependencies:
- dependency-name: league/commonmark
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-10 08:20:53 +01:00
Michael Kaufmann
9dec83fff2 can-edit-domain is not required to create subdomains of that domain if subdomains are allowed
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-12-09 08:57:18 +01:00
Michael Kaufmann
a839d76d1f adjust permissions for customer global mysql user to access existing databases
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-12-03 21:20:26 +01:00
Michael Kaufmann
079047b9fe fix permissions of global mysql-user for customers; fixes #1286
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-12-03 17:01:28 +01:00
Michael Kaufmann
2bb863baac fix regex for spf entry; refs #1295
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-12-03 16:15:32 +01:00
Michael Kaufmann
8be7372d73 Merge branch 'main' of github.com:froxlor/Froxlor 2024-12-03 15:00:27 +01:00
Michael Kaufmann
dcaff3f7de set sender-address of emails which were sent using an admin/a reseller to the global settings email so sending it using provided smtp settings will not fail antispam checks; fixes #1289
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-12-03 15:00:11 +01:00
dependabot[bot]
b6dadc0d8f Bump vite from 4.5.3 to 4.5.5 (#1296)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.3 to 4.5.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.5/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.5/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 14:55:58 +01:00
Michael Kaufmann
665b879ac5 correctly create ssl-redirect if let's encrypt is already activated; fixes #1294
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-12-03 14:45:39 +01:00
Michael Kaufmann
60f51fd746 allow cidr (forward slash) in spf settings-regex; fixes #1295
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-12-03 14:35:45 +01:00
Michael Kaufmann
5bb450bccc fix empty firstname/name but set company when editing a customer via API
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-12-02 22:04:14 +01:00
Michael Kaufmann
604078ddc6 show necessary dns entries for mail/antispan also in admin-view of domain
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-11-30 11:19:25 +01:00
dependabot[bot]
b018319b8a Bump twig/twig from 3.10.3 to 3.11.2 (#1292)
Bumps [twig/twig](https://github.com/twigphp/Twig) from 3.10.3 to 3.11.2.
- [Changelog](https://github.com/twigphp/Twig/blob/v3.11.2/CHANGELOG)
- [Commits](https://github.com/twigphp/Twig/compare/v3.10.3...v3.11.2)

---
updated-dependencies:
- dependency-name: twig/twig
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-21 08:39:40 +01:00
Michael Kaufmann
13aa07ed1a add new settings to set default values for customer antispam options for new email addresses (settings advanced-mode)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-11-20 16:53:28 +01:00
Michael Kaufmann
4db5b09111 Merge remote-tracking branch 'origin/main' into v2.2 2024-11-02 08:53:33 +01:00
Michael Kaufmann
4f114738e7 set version to 2.2.5 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-11-02 08:53:09 +01:00
Michael Kaufmann
2c9f9ebfe2 Merge remote-tracking branch 'origin/main' into v2.2 2024-10-29 13:40:19 +01:00
Michael Kaufmann
ee986e519e corrected wrong settings-index-name, fixes #1290
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-10-29 13:37:05 +01:00
Michael Kaufmann
103d321003 do not use /var/run/nginx as directory for php-fpm sockets by default as it usually does not exist
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-10-29 13:36:31 +01:00
Michael Kaufmann
99b3deda91 fix editing email-address catchall-flag, fixes #1288 2024-10-24 12:11:39 +02:00
Michael Kaufmann
a47f8ed7ee Merge remote-tracking branch 'origin/main' into v2.2 2024-10-09 16:38:42 +02:00
Michael Kaufmann
ce841e8aa4 set version to 2.2.4 for upcoming bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-10-09 16:38:21 +02:00
Michael Kaufmann
86130616dd Merge remote-tracking branch 'origin/main' into v2.2 2024-10-09 16:36:25 +02:00
Michael Kaufmann
5622ce5011 add 'rewrite_subject' field to select query for rspamd config
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-10-09 16:21:59 +02:00
Michael Kaufmann
00bdadb6e5 Merge remote-tracking branch 'origin/main' into v2.2 2024-10-09 15:43:05 +02:00
Michael Kaufmann
05223369c5 forgot to adjust another d.domain field in email-overview
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-10-09 15:42:34 +02:00
Michael Kaufmann
b45ac3de3c Merge remote-tracking branch 'origin/main' into v2.2 2024-10-09 15:33:24 +02:00
Michael Kaufmann
11a5c38476 use correct field-name in email-domain-overview; set version to 2.2.3 for upcoming bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-10-09 15:32:57 +02:00
Michael Kaufmann
8c48c5a840 Merge remote-tracking branch 'origin/main' into v2.2 2024-10-09 10:57:40 +02:00
Michael Kaufmann
4f4abada6f set version to 2.2.2 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-10-09 10:55:31 +02:00
Michael Kaufmann
289d59f531 Merge remote-tracking branch 'origin/main' into v2.2 2024-10-07 14:40:26 +02:00
Lukas Bableck
f652017c1a fix APCu memory usage (#1284) 2024-10-07 14:39:56 +02:00
Michael Kaufmann
2a50eb43b3 Merge remote-tracking branch 'origin/main' into v2.2 2024-10-02 15:40:42 +02:00
Michael Kaufmann
f31c032508 add possibility to ask for potential update question in CLI updater and also pass them as options to override them; check whether mysql-user exists prior to DROP USER for mysql < 5.7 (as it is missing IF EXISTS options)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-10-02 15:27:42 +02:00
Lukas Bableck
228eb244fa Fix incorrect width of APCu Hit/Miss bar (#1283) 2024-10-01 15:57:00 +02:00
Michael Kaufmann
143d8d42b3 Merge remote-tracking branch 'origin/main' into v2.2 2024-09-28 14:51:55 +02:00
Michael Kaufmann
4ce739667d add rewrite-subject flag to email-edit form; hide spam-related settings if 'bypass_spam' is activated; add possibility to disable rejection of spam-mails, refs #1282
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-09-28 14:49:32 +02:00
Michael Kaufmann
eb3568fca2 Merge remote-tracking branch 'origin/main' into v2.2 2024-09-27 09:10:18 +02:00
dependabot[bot]
dda4c7a846 Bump rollup from 3.29.4 to 3.29.5 (#1280)
Bumps [rollup](https://github.com/rollup/rollup) from 3.29.4 to 3.29.5.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v3.29.4...v3.29.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-27 09:05:37 +02:00
Michael Kaufmann
53b7c501bc Merge remote-tracking branch 'origin/main' into v2.2 2024-09-27 09:04:29 +02:00
Michael Kaufmann
c9e15bf897 do not issue let's encrypt for email_only domains (in case they were web-enabled prior, we do not unset former settings to ease reverting back when disabling email_only)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-09-27 09:04:01 +02:00
Michael Kaufmann
140c6c9549 store IDN email-usernames in ACE, as dovecot/postfix need them this way
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-09-26 09:22:11 +02:00
Michael Kaufmann
ddc439d32f Merge remote-tracking branch 'origin/main' into v2.2 2024-09-18 09:24:47 +02:00
Michael Kaufmann
40aa48a6d4 exchange toggler-links with checkboxes in email edit form to be able to adjust all parameters at once, fixes #1277
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-09-18 09:10:29 +02:00
Michael Kaufmann
c69b38bc42 [config-services] add validation for empty or non-existing configuration template xml files; [php-fpm] remove 'date.timezone' from php_admin_values (superfluous as it is in php_values); [antispam] set rewrite_subject to a slighty higher score then used for add_header, fixes #1275
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-09-10 14:11:26 +02:00
Michael Kaufmann
1fd8b88ed8 fix language replacement and fix 'sending messages' after successfully sending prior, thx to Davidd
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-09-01 13:49:27 +02:00
Michael Kaufmann
0318223fec Merge remote-tracking branch 'origin/main' into v2.2 2024-08-31 16:47:55 +02:00
Michael Kaufmann
53c414be6d fix timestamp matching regex, add lmtp to receving service regex and skip lines not including the main target service name in maillog parser
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-31 08:42:09 +02:00
Michael Kaufmann
2f7a2a32ba do not overwrite needed userinfo to avoid successful login when using email 2fa
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-31 08:41:59 +02:00
Michael Kaufmann
16d77a03cb fix timestamp matching regex, add lmtp to receving service regex and skip lines not including the main target service name in maillog parser
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-31 08:41:09 +02:00
Michael Kaufmann
05ca08c5c3 do not overwrite needed userinfo to avoid successful login when using email 2fa
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-30 16:26:55 +02:00
Michael Kaufmann
d3ec02f258 set version to 2.2.1 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-30 10:10:11 +02:00
Michael Kaufmann
4ea7e10304 set version to 2.2.1 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-30 10:06:26 +02:00
Michael Kaufmann
f31ee2e360 add condition to the remember-me checkbox for updaters when the token-table does not exist yet
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-29 08:21:25 +02:00
Michael Kaufmann
57206b2f72 dont generate dhparam file as fallback but use defined FFDHE4096 group; fixes #1270
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-29 08:21:16 +02:00
Florian Moker
73906f252b Fix missing proftpd-mod-wrap installation (#1272)
Missing Package Installation on Ubuntu Noble 24.04 - proftpd-mod-wrap, fixes #1271
2024-08-29 08:21:10 +02:00
Michael Kaufmann
3367f6dbd8 add condition to the remember-me checkbox for updaters when the token-table does not exist yet
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-29 08:19:45 +02:00
Michael Kaufmann
197eb7954a dont generate dhparam file as fallback but use defined FFDHE4096 group; fixes #1270
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-29 08:17:54 +02:00
Florian Moker
a1b6125c14 Fix missing proftpd-mod-wrap installation (#1272)
Missing Package Installation on Ubuntu Noble 24.04 - proftpd-mod-wrap, fixes #1271
2024-08-29 08:10:59 +02:00
Michael Kaufmann
7206f5fee2 show antispam options for email-editing only if enabled
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-24 20:18:38 +02:00
Michael Kaufmann
d18a9c9d87 show antispam options for email-editing only if enabled
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-24 19:08:24 +02:00
Michael Kaufmann
94046ae6c7 fix storing multiple-choice-select values, thx to 21MILEX on Discord, fixes #1269
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-24 13:03:47 +02:00
Michael Kaufmann
55212607e0 fix storing multiple-choice-select values, thx to 21MILEX on Discord, fixes #1269
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-24 10:23:54 +02:00
Lukas Bableck
bacc6fe073 Add |raw to h5 in formfields template (#1268) 2024-08-23 11:04:30 +02:00
Lukas Bableck
e6bfe205c5 Add |raw to h5 in formfields template (#1268) 2024-08-23 11:03:33 +02:00
Michael Kaufmann
596075d141 set version to 2.2.0 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-23 09:22:35 +02:00
Michael Kaufmann
15d3dd4234 set version to 2.2.0 for upcoming release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-23 09:21:54 +02:00
Michael Kaufmann
a58a5fd972 correctly get target filename for jqSpeciallogfileNote action call via ajax, fixes #1267
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-16 09:22:46 +02:00
Michael Kaufmann
54cda098c1 correctly get target filename for jqSpeciallogfileNote action call via ajax, fixes #1267
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-15 20:11:28 +02:00
Michael Kaufmann
bcbfcb34e8 fix typo in varchar length of selector field of new panel_2fa_tokens table, thx to Davidd
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-14 20:26:18 +02:00
Michael Kaufmann
19995f4345 fix typo in varchar length of selector field of new panel_2fa_tokens table, thx to Davidd
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-14 20:24:24 +02:00
Michael Kaufmann
56d8a565b4 Merge remote-tracking branch 'origin/main' into v2.2 2024-08-14 12:39:03 +02:00
dependabot[bot]
a60c21218c Bump axios from 1.6.0 to 1.7.4 (#1266)
Bumps [axios](https://github.com/axios/axios) from 1.6.0 to 1.7.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.6.0...v1.7.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-14 12:38:33 +02:00
Michael Kaufmann
cd2a08e731 Merge remote-tracking branch 'origin/main' into v2.2 2024-08-14 12:29:27 +02:00
Michael Kaufmann
5d2ce4ecfb allow 60sec discrepancy for email based 2fa; fix dbms version compare issue when removing user; adjust pure-ftpd mysql.conf file permissions
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-12 18:37:38 +02:00
rex2630
869b01204a Add new missing strings + fix typo (#1264) 2024-08-11 21:45:11 +02:00
Michael Kaufmann
d357bded60 Merge remote-tracking branch 'origin/main' into v2.2 2024-08-08 09:28:05 +02:00
Michael Kaufmann
292741516a set version to 2.2.0-rc3 for upcoming release-candidate
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-08 09:25:02 +02:00
Michael Kaufmann
27db472a0c Merge remote-tracking branch 'origin/main' into v2.2 2024-08-08 09:22:02 +02:00
Michael Kaufmann
fc4041e88c fixing reports being sent daily under specific conditions; update dependencies
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-08-08 09:18:49 +02:00
Michael Kaufmann
75bc0142a0 add missing use-statement for opcacheinfo page; ease ClientConnectRate ban-filter for proftpd; allow null-mx entry in dns-editor, fixes #1263
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-07-27 19:51:55 +02:00
Michael Kaufmann
b888e920f4 merge current enhancements from main branch to v2.2 (#1261) 2024-07-21 10:41:24 +02:00
Michael Kaufmann
585b16d199 set version to 2.2.0-rc2 for upcoming release-candidate
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-07-21 10:15:53 +02:00
Michael Kaufmann
4d3cf5da9a add column '2fa status' for customers and admins
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-07-20 23:26:53 +02:00
Michael Kaufmann
2dae780e0b implement 2fa remember browser, fixes #1259
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-07-20 10:16:48 +02:00
Michael Kaufmann
bda24d7d63 show email-only domains in customers list for potential dns entries information (if necessary)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-07-19 22:16:41 +02:00
Michael Kaufmann
9d47d670a1 fix correctly handling catchall-flag when updating email-address, fixes #1260
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-07-17 21:21:47 +02:00
Michael Kaufmann
b3dc7f9187 set version to 2.2.0-rc1 for upcoming release-candidate
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-07-05 11:43:39 +02:00
Michael Kaufmann
1d246fee02 check custom database-name against supported maximum length of username/databasename of used dbms, fixes #1258
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-06-25 10:37:49 +02:00
Michael Kaufmann
10e87a909a update dependencies
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-06-22 12:45:30 +02:00
Michael Kaufmann
0a3caa9f9b show required dns entries to admin and customer for a domain if nameserver-feature is not used
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-06-22 12:43:09 +02:00
dependabot[bot]
820326a7e0 Bump braces from 3.0.2 to 3.0.3 (#1257)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-21 17:10:48 +02:00
Michael Kaufmann
3a2e70f79f more info on preconfig regarding antispam feature and what will happen if not enabled; add comma to allowed characters in log-messages; make admin-username case-insensitive in the installation; show php-settings when adding/editing a domain as customer only if php is enbaled for the customer
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-06-19 10:39:15 +02:00
Starcalc
f8032b1952 Update README.md (#1255)
Corrected commands as provided in https://docs.froxlor.org/latest/general/installation/apt-package.html - the commands starting with "echo" do NOT work.
2024-06-13 10:46:34 +02:00
rex2630
a0794cbbf1 Finished Czech translation of the froxlor panel (#1254)
* Fully translated froxlor panel to Czech
2024-06-08 08:00:39 +02:00
Michael Kaufmann
a3139da388 add/correct missing language-strings
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-06-01 10:55:26 +02:00
Michael Kaufmann
3dd6a7d2ac add missing tasks-description for rebuilding antispam configuration
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-31 16:05:22 +02:00
Michael Kaufmann
062e610ae7 trigger antispam config-rebuild if dkim-flag changed for domain or a new domain with dkim=1 has been created
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-31 15:50:29 +02:00
Michael Kaufmann
5dc9aa34ba fix superfluous 'mkdir' when creating '/var/lib/rspamd/dkim/'
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-31 15:25:05 +02:00
Michael Kaufmann
d4a6c2cacc fix issues in login when 'login with domain' is activated; improved php8.3 compatibity; updated ubuntu noble config-template for dovecot and proftpd
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-31 08:41:18 +02:00
Michael Kaufmann
1f5982e8a0 update dependencies
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-16 08:32:55 +02:00
Michael Kaufmann
c89d320957 use Request-wrapper-class for every access to superglobal
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-16 08:30:35 +02:00
Michael Kaufmann
a602865fee fix force version re-check button
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-12 14:56:47 +02:00
Michael Kaufmann
597f338353 add force-updatecheck renew icon for update-check popover
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-12 14:15:05 +02:00
Michael Kaufmann
cda0b3116f make docs url dynamic based on (night/testing) version
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-12 13:51:03 +02:00
Michael Kaufmann
73182a6909 check for already existing symlink to bin/froxlor-cli; remove some very old dkim related settings
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-12 11:09:48 +02:00
envoyr
0d86340a4c fix session_sessiontimeout request and add missing language string
Signed-off-by: envoyr <hello@envoyr.com>
2024-05-11 18:38:56 +02:00
Michael Kaufmann
1a5680d2a8 never allow {{ }} in user-input 2024-05-10 17:23:25 +02:00
Michael Kaufmann
c07ff16274 Merge branch 'main' of github.com:froxlor/Froxlor 2024-05-10 17:09:59 +02:00
rex2630
cf18140499 Automatic assigment of "worker_processes" in nginx (#1252)
* Upgrade of nginx config by default reference
2024-05-10 17:09:25 +02:00
Michael Kaufmann
9f44b21a04 check for global customer mysql user existence when updating password
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-10 12:37:38 +02:00
Michael Kaufmann
7934684982 use Request-wrapper-class for every access to $_GET superglobal
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-09 16:03:46 +02:00
Michael Kaufmann
fce310049a use Request-wrapper-class for every access to $_POST superglobal
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-09 15:48:23 +02:00
Michael Kaufmann
914204b483 Merge branch 'main' of github.com:froxlor/Froxlor 2024-05-07 19:45:13 +02:00
rex2630
fc3f0d8ebf Add config for Ubuntu 24.04 - Noble Numbat (#1251)
* Add config for Ubuntu 24.04 - Noble Numbat

* Use php 8.3 by default
2024-05-07 19:45:00 +02:00
Michael Kaufmann
27753962cf use default caching_sha2_password auth plugin for mysql8
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-03 09:02:22 +02:00
Michael Kaufmann
63b21f385d mysql8 does not automatically load mysql_native_password-plugin anymore (should not be necessary anyway)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-03 08:36:52 +02:00
Michael Kaufmann
1b44ee2e06 Merge pull request from GHSA-x525-54hf-xr53
* do not log unvalidated user-input to mysql-log (if enabled)

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>

* clean log-text to only allow a subset of special characters

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>

* clean log-text when selecting from database to avoid possible previously added malicious entries

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>

---------

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-03 07:54:13 +02:00
Michael Kaufmann
7f8b36e0bd select homedir/maildir from emails if called interally as it is also called by customers via EmailAccounts.delete()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-04-28 14:03:38 +02:00
Michael Kaufmann
71746f8dac select homedir/maildir from emails if called by admin
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-04-28 13:58:27 +02:00
Michael Kaufmann
d6b8eb08c0 add delete-userfiles flag for Domain.delete() to remove email-account data on the filesystem (if any); fixes #1239
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-04-28 13:49:07 +02:00
Michael Kaufmann
7d99244b9d higher delay and dont reset input to wrong value to avoid not being able to enter a date manually without datetime-picker; fixes #1243
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-04-28 12:11:42 +02:00
Michael Kaufmann
0109c2d26f do not hide nameserver settings via js if email-only is selected for the domain; fixes #1248
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-04-28 12:00:49 +02:00
dependabot[bot]
c1bc422677 Bump vite from 4.5.2 to 4.5.3 (#1247)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.2 to 4.5.3.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.3/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.3/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-27 10:23:48 +02:00
Michael Kaufmann
5625503e2d add compatibility for mariadb-dump executable instead of mysqldump
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-04-27 10:22:42 +02:00
Michael Kaufmann
61ae182ba7 update updater to latest stable release; refactored modal-action-button for UI fixed
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-29 11:40:08 +01:00
Michael Kaufmann
b49f20af95 fix copy-to-clipboard button
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-27 12:59:48 +01:00
Michael Kaufmann
1f4f1d8203 fix domains speciallogfile ajax-check/note; improve ajax ip check in admin_ipsandports
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-27 11:07:55 +01:00
Michael Kaufmann
ff4c54a9d5 also add logfiles to virtual-host if it's a redirect
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-27 10:17:02 +01:00
Michael Kaufmann
bb83e78c64 fix missing csrf tokens for some ajax requests
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-27 10:08:13 +01:00
Wiebe Cazemier
7c3e89ccc0 Fix "expires" option cannot have a year greater than 9999 (#1246)
This fixes the exception: '"expires" option cannot have a year greater
than 9999', which happens on upgrade from Debian 11 to 12. The session
timeout in the DB is 9999999999999, so we constrain the value.
2024-03-23 15:14:11 +01:00
Michael Kaufmann
76c23cf9b1 wrap SetHandler to php-fpm in file-exists check, as we do for customer-domains already
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-17 08:23:57 +01:00
Michael Kaufmann
ed6154fa4b Merge branch 'main' of github.com:Froxlor/Froxlor 2024-03-17 08:10:24 +01:00
dependabot[bot]
f22c1db8cb Bump follow-redirects from 1.15.4 to 1.15.6 (#1244)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-17 08:08:20 +01:00
Michael Kaufmann
ee7b47c3c0 correctly save pass_authorizationheader flag for php-configs if FCGID is used; correctly add 'FcgidPassHeader' for froxlor-vhost itself if set
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-11 08:00:26 +01:00
Michael Kaufmann
537b274b4c correctly validate if a symlink is within the customers home-directory if it's not an absolute path; fixes #1242
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-08 09:23:21 +01:00
Michael Kaufmann
d8b86fc3c5 correctly disabled ssl-related settings when domain update sets ssl-enbled flag to false; fixes #1241
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-04 16:48:35 +01:00
Michael Kaufmann
b675c84ae4 correctly add user-wide mysql-user when creating user with mysql-resources (accesst to all databases starting with the loginname)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-04 10:21:46 +01:00
Michael Kaufmann
c0fdc62032 correctly convert allowed_mysqlserver json-string to array
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-03 11:44:29 +01:00
Michael Kaufmann
b14eaf454c reset Database::needRoot flag after root-user session
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-03 11:34:57 +01:00
Michael Kaufmann
3503d605cc update workflow actions
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-03 10:47:18 +01:00
Michael Kaufmann
2fc319b991 fix typo
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-03 10:39:22 +01:00
Michael Kaufmann
d86da23187 remove unused hidden-settings; correct setting-language-strings-layout; add blacklist for usernames when creating a Customer which may lead to internal issues
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-03 10:35:58 +01:00
Michael Kaufmann
70b3e61f4c re-trigger vhost regeneration on tmp. ssl-redirect
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-03 10:32:32 +01:00
Michael Kaufmann
fb5958f5d4 fix current stable version in updater for nightly users (switching from stable/testing)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-02-11 20:06:38 +01:00
Michael Kaufmann
8132976559 implement 'master database user for customers'; fixes #1227
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-02-11 10:27:18 +01:00
sro0
686ca84a30 Ensure that DMARC entries are generated as subdomain, Allow overwriting of DMARC and SPF subdomain records (#1237)
* Ensure that DMARC entries are generated as subdomain
- see https://datatracker.ietf.org/doc/html/rfc7489#section-6.1

* Add tests for DNS DMARC

* Allow custom SPF and DMARC subdomain records to replace default records

* Improve tests for DMARC, add DMARC tests for subdomain
2024-02-09 08:11:41 +01:00
sro0
953baec023 Allow service ftpserver to be specified via configuration-template-xml default (#1234) 2024-02-09 08:06:57 +01:00
Michael Kaufmann
396274d954 fix adding/editing domains as customer when php is not enabled for the domain; don't add custom-vhost-content to deactivated domain-vhosts
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-02-05 22:09:49 +01:00
Michael Kaufmann
4e23b9652c fix regression bug in 'incorrect top-5 customers' sorting in traffic-overview which leads to incorrect customer-links due to wrong indexing in the array; fixes #1236
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-02-04 19:54:19 +01:00
Michael Kaufmann
594e61408d also fix unittests accordingly
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-02-03 13:53:08 +01:00
Michael Kaufmann
ece4b34f25 fix password crypt hash being always evaluated to argon2i as the case always returns true if PASSWORD_ARGON2I is defined but the froxlor setting might be set to another hash leading to a useless password
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-02-03 10:12:36 +01:00
Michael Kaufmann
9c70976018 fix check for allowed_phpconfigs if using mod_php when adding/editing a customer
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-30 19:31:21 +01:00
Michael Kaufmann
594d7d84bb Merge branch 'main' of github.com:Froxlor/Froxlor 2024-01-29 20:27:12 +01:00
sro0
9d4bc94aef Rename dovecot config file generated be renew hook to ensure it gets included (#1233)
after default froxlor config file
2024-01-29 20:26:38 +01:00
Michael Kaufmann
f03b49d0db api documentation additions for Customers.add()/update() and Admins.add()/update()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-28 10:03:56 +01:00
Michael Kaufmann
bcf0818faf set correct channel for update-check if switching from apt-installed stable/testing to nightly
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-26 13:56:01 +01:00
Michael Kaufmann
dd765089c9 fix wrong setting-name for dkim-keylength when generating dkim-keys
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-26 13:55:14 +01:00
Michael Kaufmann
a7ee5e0ae3 create empty dns-server config if no (dns-enabled) domain is determined; fixes #1230
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-24 08:38:32 +01:00
Michael Kaufmann
2629718b22 add new 'http2 on' directive for nginx >=1.25.1
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-23 00:01:12 +01:00
dependabot[bot]
c4cf8ededc Bump vite from 4.4.12 to 4.5.2 (#1229)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.4.12 to 4.5.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 08:40:05 +01:00
Michael Kaufmann
9b20f4ac39 fix wrong order of ecc/non-ecc in proftpd config adjustment for let's encrypt renew-hook
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-19 16:09:24 +01:00
Michael Kaufmann
616dcb1fda use correct syntax for postconf in Let's Encrypt renew-hook service-configuration replacement; add missing language strings
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-19 16:01:31 +01:00
Michael Kaufmann
bc1892d4ec fix incorrect top-5 customers in traffic overview for admins; show manual update command if webupdate is disabled
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-19 09:21:38 +01:00
sro0
83047019b0 Check for argon2 support before using constant PASSWORD_ARGON2X (#1228) 2024-01-16 21:34:17 +01:00
dependabot[bot]
8fa286a71d Bump follow-redirects from 1.15.3 to 1.15.4 (#1222)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-14 09:41:46 +01:00
Michael Kaufmann
f420551888 added configuration adjustment for prodtpd if renew-hook for lets encrypt is used; updater-compatibility if gui_access field is not present yet (froxlor <2.2); removed depercated gentoo config templates
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-14 09:40:33 +01:00
sro0
854c930696 Ensure XPath for ConfigDaemon matches exactly one element (#1224) 2024-01-13 12:31:15 +01:00
Michael Kaufmann
8740947323 initial integration of let's encrypt renew-hook for froxlor-vhost; refs #1186
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-13 11:12:48 +01:00
Michael Kaufmann
e684de687f implement dmarc to dns-zones; fixes #662
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-07 11:00:07 +01:00
Michael Kaufmann
284def5832 add gui_access flag to admins and customers to allow/disallow login to the webui; fixes #1219
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-07 10:23:02 +01:00
Michael Kaufmann
9c23013777 disable pam auth in dovecot for debian bookworm (like the other distros do it)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-07 09:05:38 +01:00
Michael Kaufmann
75af5c6a1a build nightly only from main branch #2
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-06 15:01:28 +01:00
Michael Kaufmann
2a348cf34e build nightly only from main branch
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-06 14:53:45 +01:00
Michael Kaufmann
089bec7255 convert preexisting dkim public keys to new format in updater if antispam is enabled, else remove all old/invalid values from domains
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-06 14:48:13 +01:00
Michael Kaufmann
c393317adb add v2.1 branch to security md as currently supported as well as update main-branch version; add field.disabled attribute to formfield-input-template
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-06 14:46:54 +01:00
Michael Kaufmann
734d6888c8 backup rspamd configs in config-templates; add 'antispam' to valid_keys for config-json file; test existence of file in config-backup-function
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-05 16:52:40 +01:00
Michael Kaufmann
ba11b0ab7d version-check remote-result-testing not yet possible with new workflow of development
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-05 16:12:44 +01:00
Michael Kaufmann
1054095b3b merge gone wrong, corrected sql
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-05 15:39:59 +01:00
Michael Kaufmann
b15f99b1e1 implementation start of rspam/antispam feature
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-05 15:37:04 +01:00
Michael Kaufmann
63bbcd4e00 add missing language string
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-05 14:48:28 +01:00
Michael Kaufmann
49d67d7c27 set version to 2.1.4 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-05 14:33:45 +01:00
Michael Kaufmann
7cc4c9fedb possibility to specify sender address for froxlor as the admin-email address, custom or empty for system-default; fixes #1217
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-04 08:29:19 +01:00
Michael Kaufmann
afd110a6ed use correct regex for dnscheck-resolver; fixes #1220
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-03 07:35:28 +01:00
Michael Kaufmann
7cdf6c8d64 don't output ipv6 in brackets for system.ipaddress setting as the brackets will be added to the value resulting in an invalid mysql-access-host; fixes #1215
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-01 17:04:02 +01:00
Michael Kaufmann
60621da243 dont use deprecated 'mysql_native_password' for mysql8; fixes #1214
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-01-01 12:24:45 +01:00
Michael Kaufmann
96ccdda304 use different language string for password-placeholder when adding a new customer; fixes #1216
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-31 18:01:39 +01:00
Michael Kaufmann
4073984fd7 traffic-cron: check for standardsubdomain to be in the domainlist array to avoid undefined index if e.g. an alias was set to the standardsubdomain
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-31 16:16:01 +01:00
Michael Kaufmann
ea31c8a64d fix font-color in apcu info; clear updatecheck-cache for nightly users
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-30 23:30:52 +01:00
Michael Kaufmann
832ee07e0e Don't show stats-icon for domains with redirect; hide goaccess output in traffic cron and keepalive database connection for long-running log-analysis; use same certificate-file if child-domain inherits the parentdomains certificate data (avoid possible http 421 Misdirected Request)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-29 10:15:45 +01:00
Michael Kaufmann
b542b140c6 set version to 2.1.3 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-22 14:33:11 +01:00
Michael Kaufmann
ac89fc7120 adjust order of css files
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-22 14:30:23 +01:00
Michael Kaufmann
150858485d include custom.css from config.json if preset correctly
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-21 10:59:15 +01:00
Michael Kaufmann
e7810e2066 correctly merge fielddetails with prefetched-formfielddata in form-processing
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-20 09:39:01 +01:00
Michael Kaufmann
4879446567 domains in php-configs are not sortable
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-19 17:17:51 +01:00
Michael Kaufmann
43eff78088 use panel.password_min_length setting for Froxlor.generatePassword() default length parameter; allow '::1' as valid mysql localhost value; wrapper to clean output for cli installation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-16 20:20:58 +01:00
Gamerboy59
55a2ae3801 Add manual_config install var to cli (#1208)
Make the manual_config var, which is available to the web installer, usuable for the cli installer too. If manual_config is set to true skip else (not set or false) proceed with auto config.
2023-12-16 20:13:58 +01:00
Michael Kaufmann
a3b0332d13 set version to 2.1.2 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-15 09:41:16 +01:00
Michael Kaufmann
4b1846883d Merge pull request from GHSA-625g-fm5w-w7w4
* fix possibility to have empty name/surname and empty company

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>

* let js validation for customer add/edit form also trim() entered data to avoid empty values pass the client-side validation

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>

---------

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-15 09:36:06 +01:00
Michael Kaufmann
778fd3ba65 fix wrong size-unit for mailquota-dashboard-info; fixes #1207
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-15 09:32:02 +01:00
Michael Kaufmann
00456a35e5 fix 2fa login when using email validation, thx to wysiwtf; adjusting row-format of larger tables
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-13 16:20:28 +01:00
Michael Kaufmann
5958f0516b do not css-check/clean passwords of the installation process; fixes #1203
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-12 09:33:28 +01:00
Michael Kaufmann
166ffedf04 correctly merge themeoptions array to use correct image on login when using darkmode
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-12 08:59:56 +01:00
Michael Kaufmann
36dfee1263 fix non-empty value for file-input fields when using uploaded logos
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-12 08:58:33 +01:00
Michael Kaufmann
ec0026ecfd fix wrong type when dns zone for system-hostname is active
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-11 14:20:08 +01:00
Michael Kaufmann
a721bb3f21 remove old 0.10.x and 2.0.x distribution-config-xml's for updaters
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-10 20:47:31 +01:00
Michael Kaufmann
83de3dd719 handle unknown distribution if there's a now unsupported distribution selected for the config-templates
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-10 19:23:58 +01:00
Michael Kaufmann
5615decd96 set version to 2.1.1 for bugfix release (dns and install)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-10 08:20:12 +01:00
Michael Kaufmann
0348b1ec7e fix wrong result in Domain::getMainSubdomainIds(); fixes #1202
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-09 14:25:58 +01:00
Michael Kaufmann
1467dab58f set version to 2.1.0 for upcoming stable release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-08 11:48:32 +01:00
Michael Kaufmann
3a8f48de35 check subclass for cli commands to be \Symfony\Component\Console\Command\Command as the installcommand does not use \Froxlor\Cli\CliCommand
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-07 11:16:53 +01:00
Michael Kaufmann
46391c06ec Merge branch 'main' of github.com:Froxlor/Froxlor 2023-12-06 08:11:17 +01:00
dependabot[bot]
7103f7dd51 Bump vite from 4.4.11 to 4.4.12 (#1201)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.4.11 to 4.4.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.4.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.4.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-06 08:11:01 +01:00
Michael Kaufmann
9fc1dfee41 better check for invalid cli classes
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-05 12:50:57 +01:00
Michael Kaufmann
82dc76fdc6 fix wrong escaping of backslash in class-names when updating cronjobs_run table; add missing validateFormField-method for type 'image' (needs to be present but image-validation is handled elsewhere
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-05 11:16:41 +01:00
Michael Kaufmann
02ae52e3df remove old files in updater; avoid including old cli files in froxlor-cli; fix css for card list-groups
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-05 10:22:12 +01:00
Michael Kaufmann
5c06683e27 set version to 2.1.0-rc3
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-01 21:41:21 +01:00
Michael Kaufmann
2684372156 little work on installation; replace hardcoded strings with variables/constants; update dependencies
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-30 11:41:20 +01:00
Michael Kaufmann
d80c6d5714 dynamically read in CLI commands for froxlor-bin
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-28 09:07:30 +01:00
Michael Kaufmann
1ae5311b81 disable default php-fpm config for apache as for some users, it is enabled and used prior to froxlor generated virtual-host configs resulting in no php-rendering
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-27 16:43:13 +01:00
Michael Kaufmann
e1e7555cce minor textual adjustments; add non-session-based csrf-token for js/axios as it is configured to append it to the http-request
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-27 16:42:15 +01:00
Michael Kaufmann
4f79d7cf4b check php-extension requirements not only on installation (e.g. when php version was changed)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-26 22:22:39 +01:00
Michael Kaufmann
b13b1e8ac7 correctly handle empty logger.logfile setting if 'file' is in the activated log-types and no file name was given, thx to Oops
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-26 17:56:30 +01:00
Michael Kaufmann
6a1e7cc539 actually create notice file for 'unconfigured/unmanaged domain' and redirect it for potential dynamic contents (e.g. file extension php) to work properly
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-26 15:19:49 +01:00
Michael Kaufmann
2e87633ef7 table-adjustments for panel_templates #2
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-26 10:58:19 +01:00
Michael Kaufmann
8a23d0b72c table-adjustments for panel_templates
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-26 10:55:24 +01:00
Michael Kaufmann
735ef85088 make unconfigured/unknown domain page a file-template
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-26 10:53:41 +01:00
Michael Kaufmann
75cf44a6d2 respect custom-theme variants in UI::getTheme(); add margin to customer-services dashboard-badges
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-26 09:24:44 +01:00
Michael Kaufmann
7e0073f4a3 on building nightly, of course also install composer dependencies
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-15 22:53:23 +01:00
Michael Kaufmann
c9291df345 rename validateFormFieldHiddenString to validateFormFieldPassword
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-15 22:37:25 +01:00
Michael Kaufmann
fd5e97d48c introduce nightly builds and nightly-update-channel
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-15 22:16:29 +01:00
Michael Kaufmann
64a9fb163a remove duplicated code-line
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-15 10:34:31 +01:00
Michael Kaufmann
b0256ffb7d add REBUILD_VHOST task if only openbasedir-path value changes; fixes #1200
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-15 08:08:48 +01:00
Michael Kaufmann
e606bdc97f Merge branch 'main' of github.com:Froxlor/Froxlor 2023-11-12 13:09:07 +01:00
Michael Kaufmann
b53b3a924a fix wrong database-update procedure in update-command, fix distribution guessing on installation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-12 13:08:40 +01:00
Michael Kaufmann
539ea7c8fc corrected passing of ref-variable to workflow 2023-11-11 22:02:11 +01:00
Michael Kaufmann
5e8763e160 Update build-docs.yml 2023-11-11 21:55:22 +01:00
Michael Kaufmann
d52f33a50c adjust spf-entry-regex; check for valid spf-entry in updater; set version to 2.1.0-rc2
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-11 21:38:24 +01:00
Maurice Preuß (envoyr)
287ad84b18 various html and js fixes
Signed-off-by: Maurice Preuß (envoyr) <envoyr@froxlor.org>
2023-11-11 21:29:02 +01:00
Maurice Preuß
3f1b792f60 Merge pull request #1199 from Froxlor/dependabot/npm_and_yarn/axios-1.6.0
Bump axios from 1.5.1 to 1.6.0
2023-11-11 18:35:02 +01:00
dependabot[bot]
d94317421d Bump axios from 1.5.1 to 1.6.0
Bumps [axios](https://github.com/axios/axios) from 1.5.1 to 1.6.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.5.1...v1.6.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-11 16:17:05 +00:00
Michael Kaufmann
7717a82d5c adjust searchbar-size for better ux, fixes #1197
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-11 14:53:18 +01:00
Michael Kaufmann
ace1651ceb add extra validation for new domains
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-11 14:31:45 +01:00
Michael Kaufmann
1f74bf059c adjust security.md
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-10 22:12:15 +01:00
Michael Kaufmann
c98e912fc5 add description for 'disable_otp_security_check' flag in config.example.inc.php
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-10 14:35:44 +01:00
Michael Kaufmann
d04a8e7bbf create rebuild-vhost task when only changing ssl-enabled-flag when editing domain
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-08 21:22:39 +01:00
Michael Kaufmann
d4a940b723 fix 2fa code verification if method==email altogether
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-08 11:50:33 +01:00
Michael Kaufmann
0dd20bc29a fix 2fa code verification if method==email for changing system-critical settings
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-08 10:47:12 +01:00
Michael Kaufmann
f71ee9f1f2 Merge branch 'main' of github.com:Froxlor/Froxlor 2023-11-08 10:27:13 +01:00
Fabian Welzer
dd61302445 replace deprecated function utf8_encode (#1198)
utf8_encode is deprecated since PHP 8.2.0
2023-11-08 10:27:04 +01:00
Michael Kaufmann
0bee1f03de add missing language string
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-07 16:37:45 +01:00
Ruben Barkow-Kuder
a59aaa3dc9 add minimum node version to packages.json (#1196) 2023-11-06 11:32:29 +01:00
Michael Kaufmann
1debe9d939 set version to 2.1.0-rc1
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-03 13:44:49 +01:00
Michael Kaufmann
3d2e81b457 mark lighttpd as deprecated
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-03 13:39:15 +01:00
Michael Kaufmann
ac759cd9a4 make ssl-cert and ssl-key optional only if a system fallback is specified, else they are required in IpsAndPorts.add() and IpsAndPorts.update()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-03 10:07:53 +01:00
Michael Kaufmann
05c77929e4 add unconfigured domain template; enhance contrast of tables in light-theme
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-03 09:53:18 +01:00
Michael Kaufmann
cefd9226bd fix possible missing _ecc suffix of let's encrypt folder when cleaning up after deleting a domain
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-11-02 11:33:08 +01:00
Michael Kaufmann
762f295d3d Show nice note if requested domain is 'unknown' to froxlor and thus is being lead to its vhost
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-31 16:43:21 +01:00
Michael Kaufmann
d3e6063027 more password-suggestion fields modernized as the others; little beautifications here and there
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-31 14:55:02 +01:00
Michael Kaufmann
f18c14e119 update readme (cosmetics)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-30 09:27:08 +01:00
Michael Kaufmann
77bcd10729 removed deprecated/old x-xss-protection http-header
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-25 15:03:57 +02:00
Michael Kaufmann
6ee990af0a switch from huntr.dev to github security advisories as huntr drops support for non-AI/ML projects
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-24 23:08:48 +02:00
Michael Kaufmann
a3fe37b69b use absolute path in settings-export to avoid errors when invoking the cli scripts from out of froxlor's homedir
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-24 19:00:09 +02:00
Michael Kaufmann
56388ede54 fix unescaped quotes for input-fields in settings
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-18 21:04:16 +02:00
Michael Kaufmann
b98035bf3a fix froxlor:update cli command; fix html-syntax issue in updater-result-template which leads to a white page after update
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-18 17:08:30 +02:00
Michael Kaufmann
95abe465ef set version to 2.1.0-beta2
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-18 16:00:06 +02:00
Michael Kaufmann
780f607332 remove unnecessary vite-required; fix fonts-path on subdirectory-installation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-18 15:01:49 +02:00
Michael Kaufmann
a11d26522a fix js integrations
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-18 14:25:02 +02:00
Michael Kaufmann
462a798cb6 more beautification b/c of bootstrap 5.3 #2
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-17 15:00:52 +02:00
Michael Kaufmann
7556685881 more beautification b/c of bootstrap 5.3
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-17 14:25:02 +02:00
Michael Kaufmann
965e2dfd95 darkmode optimizations
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-17 10:17:20 +02:00
Michael Kaufmann
1f2cce6195 more work on bootstrap darkmode implementation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-16 18:19:36 +02:00
envoyr
f4f84aa397 update npm packages
Signed-off-by: envoyr <hello@envoyr.com>
2023-10-16 12:50:29 +02:00
envoyr
0f37dfb1eb remove mix; add vite
Signed-off-by: envoyr <hello@envoyr.com>
2023-10-16 12:48:35 +02:00
Michael Kaufmann
7438786a24 adjustments to support bootstrap 5.3 color-scheme; set gentoo config-templates to deprecated as there is no active maintainer for it; remove debian 10 and ubuntu 18.04 as they were deprecated in 2.0.x
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-15 15:49:12 +02:00
Michael Kaufmann
041c2d176c more bootstrap-5.3 adjustments in css-classes etc.
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-15 11:26:08 +02:00
Michael Kaufmann
597e765677 replace deprecated text-muted css class with bootstrap-5.3's text-body-secondary
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-15 10:29:43 +02:00
Michael Kaufmann
f757233d61 dont check for standardsubdomain in SubDomains.listingCount() as it was also removed from SubDomains.listing()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-13 16:29:53 +02:00
Michael Kaufmann
cfae3540fc set version to 2.1.0-beta1
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-13 10:25:23 +02:00
Michael Kaufmann
9e8f32f1e8 check for symlinks when required to be within customer-homedir
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-13 10:18:53 +02:00
dependabot[bot]
a7b66227e6 Bump postcss from 8.4.23 to 8.4.31 (#1192)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.23 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.23...8.4.31)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-10 08:39:16 +02:00
Michael Kaufmann
532982784f updated dependencies
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-10 08:33:30 +02:00
Michael Kaufmann
0754be3028 Merge remote-tracking branch 'origin/2.1.x' 2023-10-06 12:04:52 +02:00
Michael Kaufmann
166ec0575b set version to 2.0.24 for upcoming maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-06 11:18:18 +02:00
Michael Kaufmann
e8ed43056c enable markdown syntax in custom_notes field
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-02 15:40:50 +02:00
Michael Kaufmann
a808a3f782 fix ssl-enabled flag when using Domains.duplicate() and disable ssl-enabled if remove-ssl-ipandport parameter is set
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-02 14:12:55 +02:00
Michael Kaufmann
686065c294 some cleanup; hide ssl-related settings when ssl-usage is off when creating/updating domains; add database-update option to update-cli if files are already up-to-date
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-02 13:49:00 +02:00
Michael Kaufmann
41ac713325 make overview of customers faster by reducing mysql and php load when calculating traffic details; fixes #1161
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-24 17:54:05 +02:00
Michael Kaufmann
d1cb32b47f add formfield for domain-duplication; fix missing check for changed field in Domains.update() to force temporary disabling of ssl-vhost
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-24 17:03:15 +02:00
Michael Kaufmann
13b6ab0b07 add documentation links to customer-ui for certain entities; add setting to allow menu to be expanded
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-24 16:04:20 +02:00
Andreu Trepat Rubirola
215e749ba8 added ca language (#1184) 2023-09-24 15:22:33 +02:00
Michael Kaufmann
0b7d2358ed remove courier mda from gentoo configfiles
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-18 09:42:23 +02:00
Michael Kaufmann
f3c965fe53 more cleaning of planned backup-feature (postponed, see backup-feature branch)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-18 09:29:11 +02:00
Michael Kaufmann
5b58ab4371 fix unit-test as we have one less cronjob now
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-17 13:41:55 +02:00
Michael Kaufmann
3ad203535a adjust github actions buildscript
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-17 13:39:30 +02:00
Michael Kaufmann
6edc6553bd remove wip backup-feature for later releases, see branch backup-feature
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-17 13:19:00 +02:00
Michael Kaufmann
3fc18f9903 fix language-strings; disallow direct removing of certificates if issuer=lets encrypt; fix sql query in updater; porting nginx regex for vhost-merging
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-12 15:25:47 +02:00
Michael Kaufmann
506cccd7c8 fix vhost-cleaning regex for nginx-location directives; fixes #1185
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-12 15:20:56 +02:00
Michael Kaufmann
6ad1ca2ba9 fix API permission error in navigation when customer-hide-options include 'domains'; fixes #1183
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-07 15:35:53 +02:00
Michael Kaufmann
6d9014c29b fix API permission error in navigation when customer-hide-options include 'domains'; fixes #1183
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-07 15:34:06 +02:00
Ruben Barkow-Kuder
7e168f5a0e Add tabindex to search (#1182) 2023-09-06 10:47:48 +02:00
Michael Kaufmann
4fcf0606c7 and again more work on backup-storages
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-05 11:03:39 +02:00
Michael Kaufmann
9d2077ddee more work on backup-storages; add backup cli-command
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-04 10:54:59 +02:00
Michael Kaufmann
338b855947 check for existing userinfo if settings are being imported via cli
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-02 17:36:37 +02:00
Michael Kaufmann
5d04b8c829 only check non-admin resources if user is not an admin in navigation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-02 17:36:31 +02:00
Daniel
a0406932c3 Fix"Add" shortcut link in email address navigation (#1169)
Seems to have changed when adding the domain-filter overview for email addresses, but not updated in the navigation.
2023-08-13 08:22:49 +02: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
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
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
Michael Kaufmann
512a544dd1 Merge remote-tracking branch 'origin/main' into 2.1.x 2023-05-29 20:53:17 +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
e90dae186b Merge remote-tracking branch 'origin/main' into 2.1.x 2023-05-28 10:48:30 +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
d51556f918 Merge remote-tracking branch 'origin/main' into 2.1.x 2023-05-25 08:27: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
341 changed files with 19131 additions and 25052 deletions

View File

@@ -15,7 +15,8 @@ assignees: ''
A clear and concise description of what the bug is.
**System information**
* Froxlor version: $version/$gitSHA1
* Froxlor version: \$version/\$gitSHA1
* PHP sapi & version: php-fpm 8.3 / fcgid 8.0 / etc.
* Web server: apache2/nginx/lighttpd
* DNS server: Bind/PowerDNS (standalone)/PowerDNS (Bind-backend)
* POP/IMAP server: Courier/Dovecot

View File

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

View File

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

View File

@@ -8,11 +8,11 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ['7.4', '8.1']
php-versions: ['7.4', '8.3']
mysql-version: [8.0, 5.7]
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
@@ -39,16 +39,7 @@ jobs:
- name: Wait for database
run: sleep 15
- name: Setup database (8.0)
if: matrix.mysql-version == '8.0'
run: |
mysql -h 127.0.0.1 --protocol=TCP -u root -pfr0xl0r.TravisCI -e "CREATE USER 'froxlor010'@'%' IDENTIFIED WITH mysql_native_password BY 'fr0xl0r.TravisCI';"
mysql -h 127.0.0.1 --protocol=TCP -u root -pfr0xl0r.TravisCI -e "GRANT ALL ON froxlor010.* TO 'froxlor010'@'%';"
php -r "echo include('install/froxlor.sql.php');" > /tmp/froxlor.sql
mysql -h 127.0.0.1 --protocol=TCP -u root -pfr0xl0r.TravisCI froxlor010 < /tmp/froxlor.sql
- name: Setup database (5.7)
if: matrix.mysql-version == '5.7'
- name: Setup database
run: |
mysql -h 127.0.0.1 --protocol=TCP -u root -pfr0xl0r.TravisCI -e "CREATE USER 'froxlor010'@'%' IDENTIFIED BY 'fr0xl0r.TravisCI';"
mysql -h 127.0.0.1 --protocol=TCP -u root -pfr0xl0r.TravisCI -e "GRANT ALL ON froxlor010.* TO 'froxlor010'@'%';"

8
.gitignore vendored
View File

@@ -10,9 +10,11 @@ logs/*
.settings/
.test/
*.diff
*.patch
*~
.well-known
.idea
.DS_Store
*.iml
img/
vendor/
@@ -21,8 +23,6 @@ fonts/
templates/*
!templates/index.html
!templates/Froxlor/
templates/Froxlor/assets/mix-manifest.json
templates/Froxlor/assets/css/
templates/Froxlor/assets/js/
templates/Froxlor/assets/webfonts/
templates/Froxlor/build/
templates/Froxlor/hot
!templates/misc/

View File

@@ -33,6 +33,7 @@ use Froxlor\FroxlorLogger;
use Froxlor\FroxlorTwoFactorAuth;
use Froxlor\Settings;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\PhpHelper;
use Froxlor\User;
@@ -63,7 +64,7 @@ if ($action == 'delete') {
]);
Response::standardSuccess('2fa.2fa_removed');
} elseif ($action == 'preadd') {
$type = isset($_POST['type_2fa']) ? $_POST['type_2fa'] : '0';
$type = Request::post('type_2fa', '0');
$data = "";
if ($type > 0) {
@@ -107,9 +108,9 @@ if ($action == 'delete') {
Response::dynamicError('Select one of the possible values for 2FA');
}
} elseif ($action == 'add') {
$type = isset($_POST['type_2fa']) ? $_POST['type_2fa'] : '0';
$data = isset($_POST['data_2fa']) ? $_POST['data_2fa'] : '';
$code = isset($_POST['codevalidation']) ? $_POST['codevalidation'] : '';
$type = Request::post('type_2fa', '0');
$data = Request::post('data_2fa', '');
$code = Request::post('codevalidation', '');
// validate
$result = $tfa->verifyCode($data, $code, 3);

View File

@@ -10,6 +10,7 @@ Developed by experienced server administrators, this panel simplifies the effort
## Installation
### Fast install
1. Ensure that your webserver serves /var/www/html
2. Extract froxlor into /var/www/html
3. Point your browser to http://[ip-of-webserver]/froxlor
@@ -24,6 +25,7 @@ If you have chosen to do the configuration by hand during the installation, you
3. Follow the steps for your services
### Detailed installation
https://docs.froxlor.org/latest/general/installation/
## Help
@@ -34,19 +36,13 @@ You may find help in the following places:
The froxlor community discord server can be found here: https://discord.froxlor.org
### IRC
froxlor may be found on libera.chat, channel #froxlor:
irc://irc.libera.chat/froxlor
### Forum
The community is located on https://forum.froxlor.org/
### Wiki
### Documentation
More documentation may be found in the froxlor - documentation:
https://docs.froxlor.org/
The documentation may be found at https://docs.froxlor.org/
## License
@@ -55,6 +51,7 @@ May be found in [COPYING](COPYING)
## Downloads
### 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 / Ubuntu repository
@@ -64,17 +61,17 @@ https://files.froxlor.org/releases/froxlor-latest.tar.gz [MD5](https://files.fro
#### Debian
```
apt-get -y install apt-transport-https lsb-release ca-certificates curl
apt -y install apt-transport-https lsb-release ca-certificates curl gnupg
curl -sSLo /usr/share/keyrings/deb.froxlor.org-froxlor.gpg https://deb.froxlor.org/froxlor.gpg
echo sh -c '"deb [signed-by=/usr/share/keyrings/deb.froxlor.org-froxlor.gpg] https://deb.froxlor.org/debian $(lsb_release -sc) main" > /etc/apt/sources.list.d/froxlor.list'
sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.froxlor.org-froxlor.gpg] https://deb.froxlor.org/debian $(lsb_release -sc) main" > /etc/apt/sources.list.d/froxlor.list'
```
#### Ubuntu
```
apt-get -y install apt-transport-https lsb-release ca-certificates curl
apt -y install apt-transport-https lsb-release ca-certificates curl gnupg
curl -sSLo /usr/share/keyrings/deb.froxlor.org-froxlor.gpg https://deb.froxlor.org/froxlor.gpg
echo sh -c '"deb [signed-by=/usr/share/keyrings/deb.froxlor.org-froxlor.gpg] https://deb.froxlor.org/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/froxlor.list'
sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.froxlor.org-froxlor.gpg] https://deb.froxlor.org/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/froxlor.list'
```
## Contributing

View File

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

View File

@@ -265,7 +265,7 @@ return [
'extras.directoryprotection' => lng('menue.extras.extras') . " / " . lng('menue.extras.directoryprotection'),
'extras.pathoptions' => lng('menue.extras.extras') . " / " . lng('menue.extras.pathoptions'),
'extras.logger' => lng('menue.extras.extras') . " / " . lng('menue.logger.logger'),
'extras.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",
@@ -337,7 +337,15 @@ return [
'image_name' => 'logo_login',
'default' => '',
'save_method' => 'storeSettingImage'
]
],
'panel_menu_collapsed' => [
'label' => lng('serversettings.panel_menu_collapsed'),
'settinggroup' => 'panel',
'varname' => 'menu_collapsed',
'type' => 'checkbox',
'default' => true,
'save_method' => 'storeSettingField',
],
]
]
]

View File

@@ -35,6 +35,7 @@ return [
'varname' => 'sessiontimeout',
'type' => 'number',
'min' => 60,
'max' => 31536000,
'default' => 600,
'save_method' => 'storeSettingField'
],
@@ -230,13 +231,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,7 +107,8 @@ return [
'varname' => 'enabled',
'type' => 'checkbox',
'default' => false,
'save_method' => 'storeSettingField'
'save_method' => 'storeSettingField',
'required_otp' => true
],
'api_customer_default' => [
'label' => lng('serversettings.api_customer_default'),
@@ -129,7 +130,8 @@ return [
'default' => 'stable',
'select_var' => [
'stable' => lng('serversettings.uc_stable'),
'testing' => lng('serversettings.uc_testing')
'testing' => lng('serversettings.uc_testing'),
'nightly' => lng('serversettings.uc_nightly')
],
'save_method' => 'storeSettingField',
'advanced_mode' => true
@@ -170,16 +172,6 @@ return [
'default' => false,
'save_method' => 'storeSettingField'
],
'system_index_file_extension' => [
'label' => lng('serversettings.index_file_extension'),
'settinggroup' => 'system',
'varname' => 'index_file_extension',
'type' => 'text',
'string_regexp' => '/^[a-zA-Z0-9]{1,6}$/',
'default' => 'html',
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_store_index_file_subs' => [
'label' => lng('serversettings.system_store_index_file_subs'),
'settinggroup' => 'system',
@@ -188,18 +180,6 @@ return [
'default' => true,
'save_method' => 'storeSettingField'
],
'system_httpuser' => [
'settinggroup' => 'system',
'varname' => 'httpuser',
'type' => 'hidden',
'default' => 'www-data'
],
'system_httpgroup' => [
'settinggroup' => 'system',
'varname' => 'httpgroup',
'type' => 'hidden',
'default' => 'www-data'
],
'system_report_enable' => [
'label' => lng('serversettings.report.report'),
'settinggroup' => 'system',
@@ -277,7 +257,8 @@ return [
'varname' => 'mail_smtp_user',
'type' => 'text',
'default' => '',
'save_method' => 'storeSettingField'
'save_method' => 'storeSettingField',
'autocomplete' => 'off'
],
'system_mail_smtp_passwd' => [
'label' => lng('serversettings.mail_smtp_passwd'),
@@ -285,7 +266,8 @@ return [
'varname' => 'mail_smtp_passwd',
'type' => 'password',
'default' => '',
'save_method' => 'storeSettingField'
'save_method' => 'storeSettingField',
'autocomplete' => 'new-password'
],
'system_apply_specialsettings_default' => [
'label' => lng('serversettings.apply_specialsettings_default'),

View File

@@ -176,6 +176,11 @@ return [
'varname' => 'mod_fcgid_httpuser',
'type' => 'text',
'default' => 'froxlorlocal',
'string_emptyallowed' => false,
'plausibility_check_method' => [
'\\Froxlor\\Validate\\Check',
'checkSystemUsername'
],
'save_method' => 'storeSettingWebserverFcgidFpmUser',
'websrv_avail' => [
'apache2'
@@ -193,6 +198,7 @@ return [
'type' => 'text',
'default' => 'froxlorlocal',
'save_method' => 'storeSettingField',
'string_emptyallowed' => false,
'websrv_avail' => [
'apache2'
],
@@ -243,6 +249,11 @@ return [
'varname' => 'vhost_httpuser',
'type' => 'text',
'default' => 'froxlorlocal',
'string_emptyallowed' => false,
'plausibility_check_method' => [
'\\Froxlor\\Validate\\Check',
'checkSystemUsername'
],
'save_method' => 'storeSettingWebserverFcgidFpmUser',
'visible' => Settings::Get('phpfpm.enabled') && call_user_func([
'\Froxlor\Settings\FroxlorVhostSettings',
@@ -256,6 +267,7 @@ return [
'varname' => 'vhost_httpgroup',
'type' => 'text',
'default' => 'froxlorlocal',
'string_emptyallowed' => false,
'save_method' => 'storeSettingField',
'visible' => Settings::Get('phpfpm.enabled') && call_user_func([
'\Froxlor\Settings\FroxlorVhostSettings',

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

@@ -49,7 +49,7 @@ return [
],
'requires_reconf' => ['http']
],
'system_apache_24' => [
'system_apache24' => [
'label' => lng('serversettings.apache_24'),
'settinggroup' => 'system',
'varname' => 'apache24',
@@ -104,6 +104,10 @@ return [
'varname' => 'httpuser',
'type' => 'text',
'default' => 'www-data',
'plausibility_check_method' => [
'\\Froxlor\\Validate\\Check',
'checkSystemUsername'
],
'save_method' => 'storeSettingWebserverFcgidFpmUser'
],
'system_httpgroup' => [
@@ -181,7 +185,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',
@@ -307,7 +312,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 +325,8 @@ return [
'save_method' => 'storeSettingField',
'websrv_avail' => [
'nginx'
]
],
'required_otp' => true
],
'system_nginx_php_backend' => [
'label' => lng('serversettings.nginx_php_backend'),

View File

@@ -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'),
@@ -247,11 +248,40 @@ return [
'settinggroup' => 'system',
'varname' => 'le_domain_dnscheck_resolver',
'type' => 'text',
'string_regexp' => '/^(([0-9]+ [a-z0-9\-\._]+, ?)*[0-9]+ [a-z0-9\-\._]+)?$/i',
'string_type' => 'validate_ip',
'string_emptyallowed' => true,
'default' => '',
'save_method' => 'storeSettingField'
]
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'system_le_renew_services' => [
'label' => lng('serversettings.le_renew_services'),
'settinggroup' => 'system',
'varname' => 'le_renew_services',
'type' => 'select',
'default' => '',
'select_mode' => 'multiple',
'option_emptyallowed' => true,
'select_var' => [
'' => lng('panel.none_value'),
'postfix' => 'postfix (smtp)',
'dovecot' => 'dovecot (imap/pop3)',
'proftpd' => 'proftpd (ftp)',
],
'save_method' => 'storeSettingFieldInsertUpdateServicesTask',
'advanced_mode' => true
],
'system_le_renew_hook' => [
'label' => lng('serversettings.le_renew_hook'),
'settinggroup' => 'system',
'varname' => 'le_renew_hook',
'type' => 'text',
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
'default' => 'systemctl restart postfix dovecot proftpd',
'save_method' => 'storeSettingFieldInsertUpdateServicesTask',
'advanced_mode' => true,
'required_otp' => true
],
]
]
]

View File

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

View File

@@ -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

@@ -0,0 +1,156 @@
<?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' => [
'antispam' => [
'title' => lng('admin.antispam_settings'),
'icon' => 'fa-solid fa-clipboard-check',
'fields' => [
'antispam_activated' => [
'label' => lng('antispam.activated'),
'settinggroup' => 'antispam',
'varname' => 'activated',
'type' => 'checkbox',
'default' => true,
'overview_option' => true,
'save_method' => 'storeSettingFieldInsertAntispamTask',
],
'antispam_config_file' => [
'label' => lng('antispam.config_file'),
'settinggroup' => 'antispam',
'varname' => 'config_file',
'type' => 'text',
'string_type' => 'file',
'default' => '/etc/rspamd/local.d/froxlor_settings.conf',
'save_method' => 'storeSettingFieldInsertAntispamTask',
'requires_reconf' => ['antispam']
],
'antispam_reload_command' => [
'label' => lng('antispam.reload_command'),
'settinggroup' => 'antispam',
'varname' => 'reload_command',
'type' => 'text',
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
'default' => 'service rspamd restart',
'save_method' => 'storeSettingField',
'required_otp' => true
],
'antispam_default_bypass_spam' => [
'label' => lng('antispam.default_bypass_spam'),
'settinggroup' => 'antispam',
'varname' => 'default_bypass_spam',
'type' => 'select',
'default' => 2,
'select_var' => [
1 => lng('antispam.default_select.on_changeable'),
2 => lng('antispam.default_select.off_changeable'),
3 => lng('antispam.default_select.on_unchangeable'),
4 => lng('antispam.default_select.off_unchangeable'),
],
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'antispam_default_spam_rewrite_subject' => [
'label' => lng('antispam.default_spam_rewrite_subject'),
'settinggroup' => 'antispam',
'varname' => 'default_spam_rewrite_subject',
'type' => 'select',
'default' => 1,
'select_var' => [
1 => lng('antispam.default_select.on_changeable'),
2 => lng('antispam.default_select.off_changeable'),
3 => lng('antispam.default_select.on_unchangeable'),
4 => lng('antispam.default_select.off_unchangeable'),
],
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'antispam_default_policy_greylist' => [
'label' => lng('antispam.default_policy_greylist'),
'settinggroup' => 'antispam',
'varname' => 'default_policy_greylist',
'type' => 'select',
'default' => 1,
'select_var' => [
1 => lng('antispam.default_select.on_changeable'),
2 => lng('antispam.default_select.off_changeable'),
3 => lng('antispam.default_select.on_unchangeable'),
4 => lng('antispam.default_select.off_unchangeable'),
],
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'antispam_dkim_keylength' => [
'label' => lng('antispam.dkim_keylength'),
'settinggroup' => 'antispam',
'varname' => 'dkim_keylength',
'type' => 'select',
'default' => '1024',
'select_var' => [
'1024' => '1024 Bit',
'2048' => '2048 Bit'
],
'save_method' => 'storeSettingFieldInsertBindTask',
'advanced_mode' => true,
],
'spf_use_spf' => [
'label' => lng('spf.use_spf'),
'settinggroup' => 'spf',
'varname' => 'use_spf',
'type' => 'checkbox',
'default' => false,
'save_method' => 'storeSettingField',
],
'spf_spf_entry' => [
'label' => lng('spf.spf_entry'),
'settinggroup' => 'spf',
'varname' => 'spf_entry',
'type' => 'text',
'string_regexp' => '/^v=spf[a-z0-9:~?\s\.\-\/]+$/i',
'default' => 'v=spf1 a mx -all',
'save_method' => 'storeSettingField'
],
'dmarc_use_dmarc' => [
'label' => lng('dmarc.use_dmarc'),
'settinggroup' => 'dmarc',
'varname' => 'use_dmarc',
'type' => 'checkbox',
'default' => false,
'save_method' => 'storeSettingField',
],
'dmarc_dmarc_entry' => [
'label' => lng('dmarc.dmarc_entry'),
'settinggroup' => 'dmarc',
'varname' => 'dmarc_entry',
'type' => 'text',
'string_regexp' => '/^v=dmarc1(.+)$/i',
'default' => 'v=DMARC1; p=none;',
'save_method' => 'storeSettingField'
]
]
]
]
];

View File

@@ -1,145 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
use Froxlor\Settings;
return [
'groups' => [
'dkim' => [
'title' => lng('admin.dkimsettings'),
'icon' => 'fa-solid fa-fingerprint',
'fields' => [
'dkim_use_dkim' => [
'label' => lng('dkim.use_dkim'),
'settinggroup' => 'dkim',
'varname' => 'use_dkim',
'type' => 'checkbox',
'default' => false,
'save_method' => 'storeSettingFieldInsertBindTask',
'overview_option' => true
],
'dkim_dkim_prefix' => [
'label' => lng('dkim.dkim_prefix'),
'settinggroup' => 'dkim',
'varname' => 'dkim_prefix',
'type' => 'text',
'string_type' => 'dir',
'default' => '/etc/postfix/dkim/',
'save_method' => 'storeSettingField'
],
'dkim_privkeysuffix' => [
'label' => lng('dkim.privkeysuffix'),
'settinggroup' => 'dkim',
'varname' => 'privkeysuffix',
'type' => 'text',
'string_regexp' => '/^[a-z0-9\._]+$/i',
'default' => '.priv',
'save_method' => 'storeSettingField',
'advanced_mode' => true
],
'dkim_dkim_domains' => [
'label' => lng('dkim.dkim_domains'),
'settinggroup' => 'dkim',
'varname' => 'dkim_domains',
'type' => 'text',
'string_regexp' => '/^[a-z0-9\._]+$/i',
'default' => 'domains',
'save_method' => 'storeSettingField'
],
'dkim_dkim_dkimkeys' => [
'label' => lng('dkim.dkim_dkimkeys'),
'settinggroup' => 'dkim',
'varname' => 'dkim_dkimkeys',
'type' => 'text',
'string_regexp' => '/^[a-z0-9\._]+$/i',
'default' => 'dkim-keys.conf',
'save_method' => 'storeSettingField'
],
'dkim_dkim_algorithm' => [
'label' => lng('dkim.dkim_algorithm'),
'settinggroup' => 'dkim',
'varname' => 'dkim_algorithm',
'type' => 'select',
'default' => 'all',
'select_mode' => 'multiple',
'select_var' => [
'all' => 'All',
'sha1' => 'SHA1',
'sha256' => 'SHA256'
],
'save_method' => 'storeSettingFieldInsertBindTask',
'advanced_mode' => true
],
'dkim_dkim_servicetype' => [
'label' => lng('dkim.dkim_servicetype'),
'settinggroup' => 'dkim',
'varname' => 'dkim_servicetype',
'type' => 'select',
'default' => '0',
'select_var' => [
'0' => 'All',
'1' => 'E-Mail'
],
'save_method' => 'storeSettingFieldInsertBindTask',
'advanced_mode' => true
],
'dkim_dkim_keylength' => [
'label' => [
'title' => lng('dkim.dkim_keylength.title'),
'description' => lng('dkim.dkim_keylength.description', [Settings::Get('dkim.dkim_prefix')])
],
'settinggroup' => 'dkim',
'varname' => 'dkim_keylength',
'type' => 'select',
'default' => '1024',
'select_var' => [
'1024' => '1024 Bit',
'2048' => '2048 Bit'
],
'save_method' => 'storeSettingFieldInsertBindTask'
],
'dkim_dkim_notes' => [
'label' => lng('dkim.dkim_notes'),
'settinggroup' => 'dkim',
'varname' => 'dkim_notes',
'type' => 'text',
'string_regexp' => '/^[a-z0-9\._]+$/i',
'default' => '',
'save_method' => 'storeSettingFieldInsertBindTask',
'advanced_mode' => true
],
'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'
]
]
]
]
];

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

@@ -44,24 +44,30 @@ return [
'settinggroup' => 'system',
'varname' => 'diskquota_repquota_path',
'type' => 'text',
'string_type' => 'file',
'default' => '/usr/sbin/repquota',
'save_method' => 'storeSettingField'
'save_method' => 'storeSettingField',
'required_otp' => true
],
'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
],
'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

@@ -106,7 +106,7 @@ if (($page == 'admins' || $page == 'overview') && $userinfo['change_serversettin
Response::standardError('youcantdeleteyourself');
}
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
Admins::getLocal($userinfo, [
'id' => $id
])->delete();
@@ -122,9 +122,9 @@ if (($page == 'admins' || $page == 'overview') && $userinfo['change_serversettin
}
}
} elseif ($action == 'add') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
Admins::getLocal($userinfo, $_POST)->add();
Admins::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -159,9 +159,9 @@ if (($page == 'admins' || $page == 'overview') && $userinfo['change_serversettin
$result = json_decode($json_result, true)['data'];
if ($result['loginname'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
Admins::getLocal($userinfo, $_POST)->update();
Admins::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}

View File

@@ -33,6 +33,7 @@
use Froxlor\FroxlorLogger;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\UI\HTML;
@@ -42,7 +43,7 @@ require __DIR__ . '/lib/init.php';
$horizontal_bar_size = 950; // 1280px window width
if ($action == 'delete' && function_exists('apcu_clear_cache') && $userinfo['change_serversettings'] == '1') {
if ($_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
apcu_clear_cache();
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "cleared APCu cache");
header('Location: ' . $linker->getLink([
@@ -62,7 +63,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') {
@@ -117,7 +118,7 @@ if ($page == 'showinfo' && $userinfo['change_serversettings'] == '1') {
'uptime' => duration($cache['start_time'])
];
$overview['mem_used_percentage'] = number_format(($overview['mem_used'] / $overview['mem_avail']) * 100, 1);
$overview['mem_used_percentage'] = number_format(($overview['mem_used'] / $overview['mem_size']) * 100, 1);
$overview['num_hits_percentage'] = number_format(($overview['num_hits'] / $overview['num_hits_and_misses']) * 100,
1);
$overview['num_misses_percentage'] = number_format(($overview['num_misses'] / $overview['num_hits_and_misses']) * 100,

View File

@@ -32,6 +32,7 @@ use Froxlor\FileDir;
use Froxlor\Install\AutoUpdate;
use Froxlor\Settings;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
if ($page != 'error') {
@@ -110,7 +111,7 @@ if ($page == 'overview') {
} // download the new archive
elseif ($page == 'getdownload') {
// retrieve the new version from the form
$newversion = isset($_POST['newversion']) ? $_POST['newversion'] : null;
$newversion = Request::post('newversion');
$result = 6;
// valid?
@@ -130,8 +131,8 @@ elseif ($page == 'getdownload') {
]);
} // extract and install new version
elseif ($page == 'extract') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
$toExtract = isset($_POST['archive']) ? $_POST['archive'] : null;
if (Request::post('send') == 'send') {
$toExtract = Request::post('archive');
$localArchive = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/updates/' . $toExtract);
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "Extracting " . $localArchive . " to " . Froxlor::getInstallDir());
$result = AutoUpdate::extractZip($localArchive);
@@ -145,7 +146,7 @@ elseif ($page == 'extract') {
// redirect to update-page
Response::redirectTo('admin_updates.php');
} else {
$toExtract = isset($_GET['archive']) ? $_GET['archive'] : null;
$toExtract = Request::get('archive');
$localArchive = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/updates/' . $toExtract);
}
@@ -192,7 +193,7 @@ elseif ($page == 'extract') {
} // display error
elseif ($page == 'error') {
// retrieve error-number via url-parameter
$errno = isset($_GET['errno']) ? (int)$_GET['errno'] : 0;
$errno = Request::get('errno', 0);
// 2 = no Zlib
// 3 = custom version detected

View File

@@ -60,7 +60,9 @@ if ($userinfo['change_serversettings'] == '1') {
if (!empty($distribution)) {
if (!file_exists($config_dir . '/' . $distribution . ".xml")) {
Response::dynamicError("Unknown distribution");
// unknown distribution -> redirect to select a valid distribution for config-templates
Settings::Set('system.distribution', '');
Response::redirectTo('admin_configfiles.php', ['reselect' => 1]);
}
// update setting if different
@@ -91,14 +93,14 @@ if ($userinfo['change_serversettings'] == '1') {
asort($distributions_select);
}
if ($distribution != "" && isset($_POST['finish'])) {
$valid_keys = ['http', 'dns', 'smtp', 'mail', 'ftp', 'system', 'distro'];
if ($distribution != "" && !empty(Request::post('finish'))) {
$valid_keys = ['http', 'dns', 'smtp', 'mail', 'antispam', 'ftp', 'system', 'distro'];
unset($_POST['finish']);
unset($_POST['csrf_token']);
$params = $_POST;
$params = Request::postAll();
$params['distro'] = $distribution;
$params['system'] = [];
foreach ($_POST['system'] as $sysdaemon) {
foreach (Request::post('system', []) as $sysdaemon) {
$params['system'][] = $sysdaemon;
}
// validate params

View File

@@ -68,9 +68,9 @@ if (($page == 'cronjobs' || $page == 'overview') && $userinfo['change_serversett
}
$result = json_decode($json_result, true)['data'];
if ($result['cronfile'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
Cronjobs::getLocal($userinfo, $_POST)->update();
Cronjobs::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}

View File

@@ -98,7 +98,7 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "switched user and is now '" . $destination_user . "'");
$target = (isset($_GET['target']) ? $_GET['target'] : 'index');
$target = Request::get('target', 'index');
$redirect = "customer_" . $target . ".php";
if (!file_exists(Froxlor::getInstallDir() . "/" . $redirect)) {
$redirect = "customer_index.php";
@@ -119,7 +119,7 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
}
$result = json_decode($json_result, true)['data'];
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
$json_result = Customers::getLocal($userinfo, [
'id' => $id
@@ -147,11 +147,11 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
}
$result = json_decode($json_result, true)['data'];
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
$json_result = Customers::getLocal($userinfo, [
'id' => $id,
'delete_userfiles' => (isset($_POST['delete_userfiles']) ? (int)$_POST['delete_userfiles'] : 0)
'delete_userfiles' => Request::post('delete_userfiles', 0)
])->delete();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
@@ -167,9 +167,9 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
], $result['loginname']);
}
} elseif ($action == 'add') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
Customers::getLocal($userinfo, $_POST)->add();
Customers::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -243,9 +243,9 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
$result = json_decode($json_result, true)['data'];
if ($result['loginname'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
Customers::getLocal($userinfo, $_POST)->update();
Customers::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -307,17 +307,20 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
$hosting_plans[$row['id']] = $row['name'];
}
$available_admins_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_ADMINS . "`
WHERE (`customers` = '-1' OR `customers` > `customers_used`)
AND adminid <> :currentadmin
");
Database::pexecute($available_admins_stmt, ['currentadmin' => $result['adminid']]);
$admin_select = [
0 => "---"
];
while ($available_admin = $available_admins_stmt->fetch()) {
$admin_select[$available_admin['adminid']] = $available_admin['name'] . " (" . $available_admin['loginname'] . ")";
$admin_select = [];
if ($userinfo['customers_see_all'] == '1') {
$available_admins_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_ADMINS . "`
WHERE (`customers` = '-1' OR `customers` > `customers_used`)
AND adminid <> :currentadmin
");
Database::pexecute($available_admins_stmt, ['currentadmin' => $result['adminid']]);
$admin_select = [
0 => "---"
];
while ($available_admin = $available_admins_stmt->fetch()) {
$admin_select[$available_admin['adminid']] = $available_admin['name'] . " (" . $available_admin['loginname'] . ")";
}
}
$customer_edit_data = include_once dirname(__FILE__) . '/lib/formfields/admin/customer/formfield.customer_edit.php';

View File

@@ -30,9 +30,9 @@ use Froxlor\Api\Commands\Customers as Customers;
use Froxlor\Api\Commands\Domains as Domains;
use Froxlor\Bulk\DomainBulkAction;
use Froxlor\Cron\TaskId;
use Froxlor\CurrentUser;
use Froxlor\Customer\Customer;
use Froxlor\Database\Database;
use Froxlor\Domain\Domain;
use Froxlor\FileDir;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
@@ -45,7 +45,6 @@ use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\User;
use Froxlor\Validate\Validate;
use Froxlor\CurrentUser;
$id = (int)Request::any('id');
@@ -101,9 +100,9 @@ if ($page == 'domains' || $page == 'overview') {
]);
if ($result['domain'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send' && $alias_check['count'] == 0) {
if (Request::post('send') == 'send' && $alias_check['count'] == 0) {
try {
Domains::getLocal($userinfo, $_POST)->delete();
Domains::getLocal($userinfo, Request::postAll())->delete();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -114,21 +113,17 @@ 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::askYesNoWithCheckbox('admin_domain_reallydelete', 'admin_customer_alsoremovemail', $filename, [
'id' => $id,
'page' => $page,
'action' => $action
], $idna_convert->decode($result['domain']), $showcheck);
], $idna_convert->decode($result['domain']));
}
}
} elseif ($action == 'add') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
Domains::getLocal($userinfo, $_POST)->add();
Domains::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -252,21 +247,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
@@ -287,7 +267,7 @@ if ($page == 'domains' || $page == 'overview') {
1 => lng('domain.homedir'),
2 => lng('domain.docparent')
];
// create serveralias options
$serveraliasoptions = [
0 => lng('domains.serveraliasoption_wildcard'),
@@ -339,7 +319,7 @@ if ($page == 'domains' || $page == 'overview') {
$alias_check = $alias_check['count'];
$domain_emails_result_stmt = Database::prepare("
SELECT `email`, `email_full`, `destination`, `popaccountid` AS `number_email_forwarders`
SELECT `email`, `email_full`, `destination`, `popaccountid`
FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `customerid` = :customerid AND `domainid` = :id
");
Database::pexecute($domain_emails_result_stmt, [
@@ -375,13 +355,13 @@ if ($page == 'domains' || $page == 'overview') {
$usedips[] = $ipsresultrow['id_ipandports'];
}
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
// remove ssl ip/ports if set is empty
if (!isset($_POST['ssl_ipandport']) || empty($_POST['ssl_ipandport'])) {
if (empty(Request::post('ssl_ipandport'))) {
$_POST['remove_ssl_ipandport'] = true;
}
Domains::getLocal($userinfo, $_POST)->update();
Domains::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -469,27 +449,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
@@ -556,7 +515,7 @@ if ($page == 'domains' || $page == 'overview') {
1 => lng('domain.homedir'),
2 => lng('domain.docparent')
];
$serveraliasoptions = [
0 => lng('domains.serveraliasoption_wildcard'),
1 => lng('domains.serveraliasoption_www'),
@@ -613,13 +572,13 @@ if ($page == 'domains' || $page == 'overview') {
}
}
} elseif ($action == 'jqGetCustomerPHPConfigs') {
$customerid = intval($_POST['customerid']);
$customerid = intval(Request::post('customerid'));
$allowed_phpconfigs = Customer::getCustomerDetail($customerid, 'allowed_phpconfigs');
echo !empty($allowed_phpconfigs) ? $allowed_phpconfigs : json_encode([]);
exit();
} elseif ($action == 'jqSpeciallogfileNote') {
$domainid = intval($_POST['id']);
$newval = intval($_POST['newval']);
$domainid = intval(Request::post('id'));
$newval = intval(Request::post('newval'));
try {
$json_result = Domains::getLocal($userinfo, [
'id' => $domainid
@@ -634,10 +593,27 @@ if ($page == 'domains' || $page == 'overview') {
}
echo 0;
exit();
} elseif ($action == 'jqEmaildomainNote') {
$domainid = intval(Request::post('id'));
$newval = intval(Request::post('newval'));
try {
$json_result = Domains::getLocal($userinfo, [
'id' => $domainid
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
if ((int)$newval == 0 && $newval != $result['isemaildomain']) {
echo json_encode(['changed' => true, 'info' => lng('admin.emaildomainwarning')]);
exit();
}
echo 0;
exit();
} elseif ($action == 'import') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
$separator = Validate::validate($_POST['separator'], 'separator');
$offset = (int)Validate::validate($_POST['offset'], 'offset', "/[0-9]/i");
if (Request::post('send') == 'send') {
$separator = Validate::validate(Request::post('separator'), 'separator');
$offset = (int)Validate::validate(Request::post('offset'), 'offset', "/[0-9]/i");
$file_name = $_FILES['file']['tmp_name'];
@@ -676,6 +652,23 @@ if ($page == 'domains' || $page == 'overview') {
'alert_msg' => lng('domains.import_description')
]);
}
} elseif ($action == 'duplicate') {
if (Request::post('send') == 'send') {
try {
Domains::getLocal($userinfo, Request::postAll())->duplicate();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page,
'searchfield' => 'd.domain_ace',
'searchtext' => Request::post('domain', "")
]);
} else {
Response::redirectTo($filename, [
'page' => 'overview'
]);
}
}
} elseif ($page == 'domainssleditor') {
require_once __DIR__ . '/ssl_editor.php';

View File

@@ -31,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');
@@ -55,7 +55,7 @@ if ($action == 'logout') {
$result = $result['switched_user'];
session_regenerate_id(true);
CurrentUser::setData($result);
$target = (isset($_GET['target']) ? $_GET['target'] : 'index');
$target = Request::get('target', 'index');
$redirect = "admin_" . $target . ".php";
if (!file_exists(\Froxlor\Froxlor::getInstallDir() . "/" . $redirect)) {
$redirect = "admin_index.php";
@@ -111,7 +111,7 @@ if ($page == 'overview') {
$overview['number_domains'] = $number_domains['number_domains'];
if ((isset($_GET['lookfornewversion']) && $_GET['lookfornewversion'] == 'yes') || (isset($lookfornewversion) && $lookfornewversion == 'yes')) {
if (Request::get('lookfornewversion') == 'yes' || (isset($lookfornewversion) && $lookfornewversion == 'yes')) {
try {
$json_result = Froxlor::getLocal($userinfo)->checkUpdate();
} catch (Exception $e) {
@@ -197,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 (Request::post('send') == 'changepassword') {
$old_password = Validate::validate(Request::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(Request::post('new_password'), 'new password');
$new_password_confirm = Crypt::validatePassword(Request::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 (Request::post('send') == 'changetheme') {
if (Settings::Get('panel.allow_theme_change_admin') == 1) {
$theme = Validate::validate(Request::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 (Request::post('send') == 'changelanguage') {
$def_language = Validate::validate(Request::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

@@ -70,7 +70,7 @@ if (($page == 'ipsandports' || $page == 'overview') && $userinfo['change_servers
$result = json_decode($json_result, true)['data'];
if (isset($result['id']) && $result['id'] == $id) {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
IpsAndPorts::getLocal($userinfo, [
'id' => $id
@@ -91,9 +91,9 @@ if (($page == 'ipsandports' || $page == 'overview') && $userinfo['change_servers
}
}
} elseif ($action == 'add') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
IpsAndPorts::getLocal($userinfo, $_POST)->add();
IpsAndPorts::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -119,9 +119,9 @@ if (($page == 'ipsandports' || $page == 'overview') && $userinfo['change_servers
$result = json_decode($json_result, true)['data'];
if ($result['ip'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
IpsAndPorts::getLocal($userinfo, $_POST)->update();
IpsAndPorts::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -141,9 +141,11 @@ if (($page == 'ipsandports' || $page == 'overview') && $userinfo['change_servers
}
}
} elseif ($action == 'jqCheckIP') {
$ip = $_POST['ip'] ?? "";
if ((filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) || filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE | FILTER_FLAG_NO_PRIV_RANGE) == false) {
// returns notice if private network detected so we can display it
$ip = Request::post('ip', '');
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6)) {
echo json_encode('<div id="ipnote" class="invalid-feedback">'.lng('error.invalidip', [$ip]).'</div>');
} elseif (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE | FILTER_FLAG_NO_PRIV_RANGE)) {
// returns notice if private network detected, so we can display it
echo json_encode(lng('admin.ipsandports.ipnote'));
} else {
echo 0;

View File

@@ -31,6 +31,7 @@ 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;
if ($page == 'log' && $userinfo['change_serversettings'] == '1') {
@@ -55,7 +56,7 @@ if ($page == 'log' && $userinfo['change_serversettings'] == '1') {
]
]);
} elseif ($action == 'truncate') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
SysLog::getLocal($userinfo, [
'min_to_keep' => 10

View File

@@ -42,11 +42,11 @@ if ($page == 'message') {
if ($action == '') {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, 'viewed panel_message');
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if ($_POST['recipient'] == 0 && $userinfo['customers_see_all'] == '1') {
if (Request::post('send') == 'send') {
if (Request::post('recipient', -1) == 0 && $userinfo['customers_see_all'] == '1') {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, 'sending messages to admins');
$result = Database::query('SELECT `name`, `email` FROM `' . TABLE_PANEL_ADMINS . "`");
} elseif ($_POST['recipient'] == 1) {
} elseif (Request::post('recipient', -1) == 1) {
if ($userinfo['customers_see_all'] == '1') {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, 'sending messages to ALL customers');
$result = Database::query('SELECT `firstname`, `name`, `company`, `email` FROM `' . TABLE_PANEL_CUSTOMERS . "`");
@@ -63,8 +63,8 @@ if ($page == 'message') {
Response::standardError('norecipientsgiven');
}
$subject = $_POST['subject'];
$message = wordwrap($_POST['message'], 70);
$subject = Request::post('subject');
$message = wordwrap(Request::post('message'), 70);
if (!empty($message)) {
$mailcounter = 0;
@@ -107,14 +107,14 @@ if ($page == 'message') {
}
}
} elseif ($action == 'showsuccess') {
$sentitems = isset($_GET['sentitems']) ? (int)$_GET['sentitems'] : 0;
$sentitems = Request::get('sentitems', 0);
if ($sentitems == 0) {
$note_type = 'info';
$note_msg = lng('message.norecipients');
} else {
$note_type = 'success';
$note_msg = str_replace('%s', $sentitems, lng('message.success'));
$note_msg = lng('message.success', [$sentitems]);
}
}
@@ -128,7 +128,7 @@ if ($page == 'message') {
$messages_add_data = include_once dirname(__FILE__) . '/lib/formfields/admin/messages/formfield.messages_add.php';
UI::view('user/form-note.html.twig', [
'formaction' => $linker->getLink(['section' => 'message']),
'formaction' => $linker->getLink(['section' => 'message', 'action' => '']),
'formdata' => $messages_add_data['messages_add'],
'actions_links' => [
[

View File

@@ -70,7 +70,7 @@ if (($page == 'mysqlserver' || $page == 'overview') && $userinfo['change_servers
$result = json_decode($json_result, true)['data'];
if (isset($result['id']) && $result['id'] == $id) {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
MysqlServer::getLocal($userinfo, [
'id' => $id
@@ -91,9 +91,9 @@ if (($page == 'mysqlserver' || $page == 'overview') && $userinfo['change_servers
}
}
} elseif ($action == 'add') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
MysqlServer::getLocal($userinfo, $_POST)->add();
MysqlServer::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -119,9 +119,9 @@ if (($page == 'mysqlserver' || $page == 'overview') && $userinfo['change_servers
$result = json_decode($json_result, true)['data'];
if (isset($result['id']) && $result['id'] == $id) {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
MysqlServer::getLocal($userinfo, $_POST)->update();
MysqlServer::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}

View File

@@ -33,12 +33,13 @@ const AREA = 'admin';
require __DIR__ . '/lib/init.php';
use Froxlor\FroxlorLogger;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Response;
use Froxlor\UI\HTML;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
if ($action == 'reset' && function_exists('opcache_reset') && $userinfo['change_serversettings'] == '1') {
if ($_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
opcache_reset();
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "reset OPcache");
header('Location: ' . $linker->getLink([
@@ -57,252 +58,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

@@ -62,9 +62,9 @@ if ($page == 'overview') {
if ($action == 'add') {
if ((int)$userinfo['change_serversettings'] == 1) {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
PhpSettings::getLocal($userinfo, $_POST)->add();
PhpSettings::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -114,7 +114,7 @@ if ($page == 'overview') {
if ($result['id'] != 0 && $result['id'] == $id && (int)$userinfo['change_serversettings'] == 1 && $id != 1) // cannot delete the default php.config
{
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
PhpSettings::getLocal($userinfo, [
'id' => $id
@@ -148,9 +148,9 @@ if ($page == 'overview') {
$result = json_decode($json_result, true)['data'];
if ($result['id'] != 0 && $result['id'] == $id && (int)$userinfo['change_serversettings'] == 1) {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
PhpSettings::getLocal($userinfo, $_POST)->update();
PhpSettings::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -200,9 +200,9 @@ if ($page == 'overview') {
if ($action == 'add') {
if ((int)$userinfo['change_serversettings'] == 1) {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
FpmDaemons::getLocal($userinfo, $_POST)->add();
FpmDaemons::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -239,9 +239,9 @@ if ($page == 'overview') {
if ($result['id'] != 0 && $result['id'] == $id && (int)$userinfo['change_serversettings'] == 1 && $id != 1) // cannot delete the default php.config
{
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
FpmDaemons::getLocal($userinfo, $_POST)->delete();
FpmDaemons::getLocal($userinfo, Request::postAll())->delete();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -271,9 +271,9 @@ if ($page == 'overview') {
$result = json_decode($json_result, true)['data'];
if ($result['id'] != 0 && $result['id'] == $id && (int)$userinfo['change_serversettings'] == 1) {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
FpmDaemons::getLocal($userinfo, $_POST)->update();
FpmDaemons::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}

View File

@@ -73,7 +73,7 @@ if ($page == '' || $page == 'overview') {
$result = json_decode($json_result, true)['data'];
if ($result['id'] != 0 && $result['id'] == $id && (int)$userinfo['adminid'] == $result['adminid']) {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
HostingPlans::getLocal($userinfo, [
'id' => $id
@@ -96,9 +96,9 @@ if ($page == '' || $page == 'overview') {
Response::standardError('nopermissionsorinvalidid');
}
} elseif ($action == 'add') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
HostingPlans::getLocal($userinfo, $_POST)->add();
HostingPlans::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -176,9 +176,9 @@ if ($page == '' || $page == 'overview') {
}
$result['allowed_phpconfigs'] = json_encode($result['allowed_phpconfigs']);
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
HostingPlans::getLocal($userinfo, $_POST)->update();
HostingPlans::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}

View File

@@ -47,10 +47,10 @@ if ($page == 'overview' && $userinfo['change_serversettings'] == '1') {
$settings_data = PhpHelper::loadConfigArrayDir('./actions/admin/settings/');
Settings::loadSettingsInto($settings_data);
if (isset($_POST['send']) && $_POST['send'] == 'send') {
$_part = isset($_GET['part']) ? $_GET['part'] : '';
if (Request::post('send') == 'send') {
$_part = Request::get('part', '');
if ($_part == '') {
$_part = isset($_POST['part']) ? $_POST['part'] : '';
$_part = Request::post('part', '');
}
if ($_part != '') {
@@ -69,15 +69,16 @@ 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'));
if (!empty(Request::post('session_sessiontimeout')) && intval(Request::post('session_sessiontimeout', 0)) < 60) {
Response::standardError(['session_timeout', 'session_timeout_desc']);
}
try {
if (Form::processForm($settings_data, $_POST, [
if (Form::processForm($settings_data, Request::postAll(), [
'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);
@@ -96,9 +97,9 @@ if ($page == 'overview' && $userinfo['change_serversettings'] == '1') {
Response::dynamicError($e->getMessage(), $e->getCode());
}
} else {
$_part = isset($_GET['part']) ? $_GET['part'] : '';
$_part = Request::get('part', '');
if ($_part == '') {
$_part = isset($_POST['part']) ? $_POST['part'] : '';
$_part = Request::post('part', '');
}
$fields = Form::buildForm($settings_data, $_part);
@@ -132,14 +133,14 @@ 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,
'phpinfo' => $phpinfo
]);
} elseif ($page == 'rebuildconfigs' && $userinfo['change_serversettings'] == '1') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "rebuild configfiles");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
Cronjob::inserttask(TaskId::CREATE_QUOTA);
@@ -157,7 +158,7 @@ if ($page == 'overview' && $userinfo['change_serversettings'] == '1') {
]);
}
} elseif ($page == 'updatecounters' && $userinfo['change_serversettings'] == '1') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "updated resource-counters");
$updatecounters = User::updateCounters(true);
UI::view('user/resource-counter.html.twig', [
@@ -169,7 +170,7 @@ if ($page == 'overview' && $userinfo['change_serversettings'] == '1') {
]);
}
} elseif ($page == 'wipecleartextmailpws' && $userinfo['change_serversettings'] == '1') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "wiped all cleartext mail passwords");
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password` = '';");
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `value` = '0' WHERE `settinggroup` = 'system' AND `varname` = 'mailpwcleartext'");
@@ -180,7 +181,7 @@ if ($page == 'overview' && $userinfo['change_serversettings'] == '1') {
]);
}
} elseif ($page == 'wipequotas' && $userinfo['change_serversettings'] == '1') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "wiped all mailquotas");
// Set the quota to 0 which means unlimited
@@ -193,7 +194,7 @@ if ($page == 'overview' && $userinfo['change_serversettings'] == '1') {
]);
}
} elseif ($page == 'enforcequotas' && $userinfo['change_serversettings'] == '1') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
// Fetch all accounts
$result_stmt = Database::query("SELECT `quota`, `customerid` FROM `" . TABLE_MAIL_USERS . "`");
@@ -232,9 +233,9 @@ if ($page == 'overview' && $userinfo['change_serversettings'] == '1') {
}
} elseif ($page == 'integritycheck' && $userinfo['change_serversettings'] == '1') {
$integrity = new IntegrityCheck();
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
$integrity->fixAll();
} elseif (isset($_GET['action']) && $_GET['action'] == "fix") {
} elseif (Request::get('action') == "fix") {
HTML::askYesNo('admin_integritycheck_reallyfix', $filename, [
'page' => $page
]);
@@ -272,7 +273,7 @@ if ($page == 'overview' && $userinfo['change_serversettings'] == '1') {
Response::standardError('jsonextensionnotfound');
}
if (isset($_GET['action']) && $_GET['action'] == "export") {
if (Request::get('action') == "export") {
// export
try {
$json_result = Froxlor::getLocal($userinfo)->exportSettings();
@@ -284,9 +285,9 @@ if ($page == 'overview' && $userinfo['change_serversettings'] == '1') {
header('Content-type: application/json');
echo $json_export;
exit();
} elseif (isset($_GET['action']) && $_GET['action'] == "import") {
} elseif (Request::get('action') == "import") {
// import
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
// get uploaded file
if (isset($_FILES["import_file"]["tmp_name"])) {
$imp_content = file_get_contents($_FILES["import_file"]["tmp_name"]);
@@ -329,8 +330,8 @@ if ($page == 'overview' && $userinfo['change_serversettings'] == '1') {
$note_type = 'info';
$note_msg = lng('admin.smtptestnote');
if (isset($_POST['send']) && $_POST['send'] == 'send') {
$test_addr = isset($_POST['test_addr']) ? $_POST['test_addr'] : null;
if (Request::post('send') == 'send') {
$test_addr = Request::post('test_addr');
// Initialize the mailingsystem
$testmail = new PHPMailer(true);

View File

@@ -60,7 +60,8 @@ if (Settings::Get('panel.sendalternativemail') == 1) {
}
$file_templates = [
'index_html'
'index_html',
'unconfigured_html'
];
$languages = Language::getLanguages();
@@ -191,7 +192,7 @@ if ($action == '') {
$result = $result_stmt->fetch(PDO::FETCH_ASSOC);
if ($result['varname'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
$del_stmt = Database::prepare("
DELETE FROM `" . TABLE_PANEL_TEMPLATES . "`
WHERE `adminid` = :adminid
@@ -227,7 +228,7 @@ if ($action == '') {
if (Database::num_rows() > 0) {
$row = $result_stmt->fetch(PDO::FETCH_ASSOC);
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
$del_stmt = Database::prepare("
DELETE FROM `" . TABLE_PANEL_TEMPLATES . "`
WHERE `adminid` = :adminid AND `id` = :id");
@@ -250,13 +251,13 @@ if ($action == '') {
Response::standardError('templatenotfound');
}
} elseif ($action == 'add') {
if (isset($_POST['prepare']) && $_POST['prepare'] == 'prepare') {
if (Request::post('prepare') == 'prepare') {
// email templates
$language = htmlentities(Validate::validate($_POST['language'], 'language', '/^[^\r\n\0"\']+$/', 'nolanguageselect'));
$language = htmlentities(Validate::validate(Request::post('language'), 'language', '/^[^\r\n\0"\']+$/', 'nolanguageselect'));
if (!array_key_exists($language, $languages)) {
Response::standardError('templatelanguageinvalid');
}
$template = Validate::validate($_POST['template'], 'template');
$template = Validate::validate(Request::post('template'), 'template');
$result_stmt = Database::prepare("
SELECT COUNT(*) as def FROM `" . TABLE_PANEL_TEMPLATES . "`
@@ -288,15 +289,15 @@ if ($action == '') {
'formdata' => $template_add_data['template_add'],
'replacers' => $template_add_data['template_replacers']
]);
} elseif (isset($_POST['send']) && $_POST['send'] == 'send' && !isset($_POST['filesend'])) {
} elseif (Request::post('send') == 'send' && empty(Request::post('filesend'))) {
// email templates
$language = htmlentities(Validate::validate($_POST['language'], 'language', '/^[^\r\n\0"\']+$/', 'nolanguageselect'));
$language = htmlentities(Validate::validate(Request::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');
$template = Validate::validate(Request::post('template'), 'template');
$subject = Validate::validate(Request::post('subject'), 'subject', '/^[^\r\n\0]+$/', 'nosubjectcreate');
$mailbody = Validate::validate(Request::post('mailbody'), 'mailbody', '/^[^\0]+$/', 'nomailbodycreate');
$templates = [];
$result_stmt = Database::prepare("
SELECT `varname` FROM `" . TABLE_PANEL_TEMPLATES . "`
@@ -346,10 +347,10 @@ if ($action == '') {
'page' => $page
]);
}
} elseif (isset($_POST['filesend']) && $_POST['filesend'] == 'filesend') {
} elseif (Request::post('filesend') == 'filesend') {
// file templates
$template = Validate::validate($_POST['template'], 'template');
$filecontent = Validate::validate($_POST['filecontent'], 'filecontent', '/^[^\0]+$/', 'filecontentnotset');
$template = Validate::validate(Request::post('template'), 'template');
$filecontent = Validate::validate(Request::post('filecontent'), 'filecontent', '/^[^\0]+$/', 'filecontentnotset');
$ins_stmt = Database::prepare("
INSERT INTO `" . TABLE_PANEL_TEMPLATES . "` SET
@@ -370,7 +371,7 @@ if ($action == '') {
Response::redirectTo($filename, [
'page' => $page
]);
} elseif (!isset($_GET['files'])) {
} elseif (empty(Request::get('files'))) {
// email templates
$add = false;
$language_options = [];
@@ -482,9 +483,9 @@ if ($action == '') {
$result = $result_stmt->fetch(PDO::FETCH_ASSOC);
if ($result['varname'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
$subject = Validate::validate($_POST['subject'], 'subject', '/^[^\r\n\0]+$/', 'nosubjectcreate');
$mailbody = Validate::validate($_POST['mailbody'], 'mailbody', '/^[^\0]+$/', 'nomailbodycreate');
if (Request::post('send') == 'send') {
$subject = Validate::validate(Request::post('subject'), 'subject', '/^[^\r\n\0]+$/', 'nosubjectcreate');
$mailbody = Validate::validate(Request::post('mailbody'), 'mailbody', '/^[^\0]+$/', 'nomailbodycreate');
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_TEMPLATES . "` SET
@@ -550,8 +551,8 @@ if ($action == '') {
$row = $result_stmt->fetch(PDO::FETCH_ASSOC);
// filetemplates
if (isset($_POST['filesend']) && $_POST['filesend'] == 'filesend') {
$filecontent = Validate::validate($_POST['filecontent'], 'filecontent', '/^[^\0]+$/', 'filecontentnotset');
if (Request::post('filesend') == 'filesend') {
$filecontent = Validate::validate(Request::post('filecontent'), 'filecontent', '/^[^\0]+$/', 'filecontentnotset');
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_TEMPLATES . "` SET
`value` = :value

View File

@@ -34,6 +34,7 @@ use Froxlor\Install\Update;
use Froxlor\Settings;
use Froxlor\System\Cronjob;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\User;
@@ -48,8 +49,8 @@ if ($page == 'overview') {
$successful_update = false;
$message = '';
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if ((isset($_POST['update_preconfig']) && isset($_POST['update_changesagreed']) && intval($_POST['update_changesagreed']) != 0) || !isset($_POST['update_preconfig'])) {
if (Request::post('send') == 'send') {
if ((!empty(Request::post('update_preconfig')) && intval(Request::post('update_changesagreed', 0)) != 0) || empty(Request::post('update_preconfig'))) {
include_once Froxlor::getInstallDir() . 'install/updatesql.php';
User::updateCounters();

View File

@@ -61,7 +61,7 @@ if ($action == 'delete' && $id > 0) {
'section' => 'index',
'page' => $page
]);
} elseif (isset($_POST['send']) && $_POST['send'] == 'send' && $action == 'deletesure' && $id > 0) {
} elseif (Request::post('send') == 'send' && $action == 'deletesure' && $id > 0) {
$chk = (AREA == 'admin' && $userinfo['customers_see_all'] == '1') ? true : false;
if (AREA == 'customer') {
$chk_stmt = Database::prepare("
@@ -94,7 +94,7 @@ if ($action == 'delete' && $id > 0) {
]);
}
} elseif ($action == 'add') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
$ins_stmt = Database::prepare("
INSERT INTO `" . TABLE_API_KEYS . "` SET
`apikey` = :key, `secret` = :secret, `adminid` = :aid, `customerid` = :cid, `valid_until` = '-1', `allowed_from` = ''

View File

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

View File

@@ -52,10 +52,11 @@
"froxlor/idna-convert-legacy": "^2.1",
"voku/anti-xss": "^4.1",
"twig/twig": "^3.3",
"erusev/parsedown": "^1.7",
"symfony/console": "^5.4",
"pear/net_dns2": "^1.5"
},
"pear/net_dns2": "^1.5",
"amnuts/opcache-gui": "^3.4",
"league/commonmark": "^2.4"
},
"require-dev": {
"phpunit/phpunit": "^9",
"ext-pcntl": "*",
@@ -71,9 +72,15 @@
"suggest": {
"ext-bcmath": "*",
"ext-zip": "*",
"ext-gnupg": "*",
"ext-apcu": "*",
"ext-readline": "*"
},
"config": {
"platform": {
"php": "7.4"
}
},
"autoload": {
"psr-4": {
"Froxlor\\": [
@@ -82,6 +89,10 @@
}
},
"scripts": {
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#fdba74\" \"php -S 127.0.0.1:8000\" \"npm run dev\" --names=server,vite"
],
"post-install-cmd": "if [ -f ./vendor/bin/phpcs ]; then \"vendor/bin/phpcs\" --config-set installed_paths vendor/phpcompatibility/php-compatibility ; fi",
"post-update-cmd" : "if [ -f ./vendor/bin/phpcs ]; then \"vendor/bin/phpcs\" --config-set installed_paths vendor/phpcompatibility/php-compatibility ; fi"
}

1464
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,8 @@
const AREA = 'customer';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\SubDomains as SubDomains;
use Froxlor\Api\Commands\SubDomains;
use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\Domain\Domain;
use Froxlor\FileDir;
@@ -40,7 +41,6 @@ use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\Validate\Validate;
use Froxlor\CurrentUser;
// redirect if this customer page is hidden via settings
if (Settings::IsInList('panel.customer_hide_options', 'domains')) {
@@ -51,7 +51,7 @@ $id = (int)Request::any('id');
if ($page == 'overview' || $page == 'domains') {
if ($action == '') {
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_domains::domains");
$log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "viewed customer_domains::domains");
$parentdomain_id = (int)Request::any('pid', '0');
@@ -63,20 +63,32 @@ if ($page == 'overview' || $page == 'domains') {
Response::dynamicError($e->getMessage());
}
$actions_links = false;
$actions_links = [];
if (CurrentUser::canAddResource('subdomains')) {
$actions_links = [
[
'href' => $linker->getLink(['section' => 'domains', 'page' => 'domains', 'action' => 'add']),
'label' => lng('domains.subdomain_add')
]
$actions_links[] = [
'href' => $linker->getLink(['section' => 'domains', 'page' => 'domains', 'action' => 'add']),
'label' => lng('domains.subdomain_add')
];
}
UI::view('user/table.html.twig', [
$actions_links[] = [
'href' => \Froxlor\Froxlor::getDocsUrl() . 'user-guide/domains/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
$table_tpl = 'table.html.twig';
if ($collection->count() == 0) {
$table_tpl = 'table-note.html.twig';
}
UI::view('user/' . $table_tpl, [
'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 {
@@ -94,9 +106,9 @@ if ($page == 'overview' || $page == 'domains') {
]);
if (isset($result['parentdomainid']) && $result['parentdomainid'] != '0' && $alias_check['count'] == 0) {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
SubDomains::getLocal($userinfo, $_POST)->delete();
SubDomains::getLocal($userinfo, Request::postAll())->delete();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -115,9 +127,9 @@ if ($page == 'overview' || $page == 'domains') {
}
} elseif ($action == 'add') {
if ($userinfo['subdomains_used'] < $userinfo['subdomains'] || $userinfo['subdomains'] == '-1') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
SubDomains::getLocal($userinfo, $_POST)->add();
SubDomains::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -129,7 +141,7 @@ if ($page == 'overview' || $page == 'domains') {
WHERE `customerid` = :customerid
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 +151,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
@@ -223,13 +243,13 @@ if ($page == 'overview' || $page == 'domains') {
if (isset($result['customerid']) && $result['customerid'] == $userinfo['customerid']) {
if ((int) $result['caneditdomain'] == 0) {
if ((int)$result['caneditdomain'] == 0) {
Response::standardError('domaincannotbeedited', $result['domain']);
}
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
SubDomains::getLocal($userinfo, $_POST)->update();
SubDomains::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -373,6 +393,23 @@ if ($page == 'overview' || $page == 'domains') {
} else {
Response::standardError('domains_canteditdomain');
}
} elseif ($action == 'jqSpeciallogfileNote') {
$domainid = intval(Request::post('id'));
$newval = intval(Request::post('newval'));
try {
$json_result = SubDomains::getLocal($userinfo, [
'id' => $domainid
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
if ($newval != $result['speciallogfile']) {
echo json_encode(['changed' => true, 'info' => lng('admin.speciallogwarning')]);
exit();
}
echo 0;
exit();
}
} elseif ($page == 'domainssleditor') {
require_once __DIR__ . '/ssl_editor.php';

View File

@@ -27,9 +27,10 @@ const AREA = 'customer';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\EmailAccounts;
use Froxlor\Api\Commands\EmailDomains;
use Froxlor\Api\Commands\EmailForwarders;
use Froxlor\Api\Commands\Emails;
use Froxlor\Api\Commands\EmailDomains;
use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
use Froxlor\PhpHelper;
@@ -41,7 +42,6 @@ use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\Validate\Check;
use Froxlor\CurrentUser;
// redirect if this customer page is hidden via settings
if (Settings::IsInList('panel.customer_hide_options', 'email') || $userinfo['emails'] == 0) {
@@ -67,14 +67,24 @@ if ($page == 'overview' || $page == 'emails') {
Response::dynamicError($e->getMessage());
}
$actions_links = [];
if (CurrentUser::canAddResource('emails')) {
$actions_links[] = [
'href' => $linker->getLink(['section' => 'email', 'page' => 'email_domain', 'action' => 'add']),
'label' => lng('emails.emails_add')
];
}
$actions_links[] = [
'href' => \Froxlor\Froxlor::getDocsUrl() . 'user-guide/emails/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $emaildomain_list_data, 'emaildomain_list'),
'actions_links' => CurrentUser::canAddResource('emails') ? [
[
'href' => $linker->getLink(['section' => 'email', 'page' => 'email_domain', 'action' => 'add']),
'label' => lng('emails.emails_add')
]
] : null,
'actions_links' => $actions_links,
]);
} else {
// only emails for one domain -> show email address listing directly
@@ -84,7 +94,7 @@ if ($page == 'overview' || $page == 'emails') {
if ($page == 'email_domain') {
$email_domainid = Request::any('domainid', 0);
if ($action == '') {
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_email::emails");
$log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "viewed customer_email::emails");
$sql_search = [];
if ($email_domainid > 0) {
@@ -94,7 +104,7 @@ if ($page == 'email_domain') {
$email_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.emails.php';
$collection = (new Collection(Emails::class, $userinfo, $sql_search))
->withPagination($email_list_data['email_list']['columns'],
$email_list_data['email_list']['default_sorting']);
$email_list_data['email_list']['default_sorting'], ['domainid=' . $email_domainid]);
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -127,6 +137,12 @@ if ($page == 'email_domain') {
'label' => lng('emails.emails_add')
];
}
$actions_links[] = [
'href' => \Froxlor\Froxlor::getDocsUrl() . 'user-guide/emails/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $email_list_data, 'email_list'),
@@ -144,11 +160,11 @@ if ($page == 'email_domain') {
$result = json_decode($json_result, true)['data'];
if (isset($result['email']) && $result['email'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
Emails::getLocal($userinfo, [
'id' => $id,
'delete_userfiles' => ($_POST['delete_userfiles'] ?? 0)
'delete_userfiles' => Request::post('delete_userfiles', 0)
])->delete();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
@@ -171,9 +187,9 @@ if ($page == 'email_domain') {
}
} elseif ($action == 'add') {
if ($userinfo['emails_used'] < $userinfo['emails'] || $userinfo['emails'] == '-1') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
$json_result = Emails::getLocal($userinfo, $_POST)->add();
$json_result = Emails::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -228,7 +244,12 @@ if ($page == 'email_domain') {
$result = json_decode($json_result, true)['data'];
if (isset($result['email']) && $result['email'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
Emails::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page
]);
@@ -265,40 +286,12 @@ if ($page == 'email_domain') {
$email_edit_data = include_once dirname(__FILE__) . '/lib/formfields/customer/email/formfield.emails_edit.php';
if (Settings::Get('catchall.catchall_enabled') != '1') {
unset($email_edit_data['emails_edit']['sections']['section_a']['fields']['mail_catchall']);
}
UI::view('user/form.html.twig', [
'formaction' => $linker->getLink(['section' => 'email']),
'formdata' => $email_edit_data['emails_edit'],
'editid' => $id
]);
}
} elseif ($action == 'togglecatchall' && $id != 0) {
try {
$json_result = Emails::getLocal($userinfo, [
'id' => $id
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
try {
Emails::getLocal($userinfo, [
'id' => $id,
'iscatchall' => ($result['iscatchall'] == '1' ? 0 : 1)
])->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page,
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id,
]);
}
} elseif ($page == 'accounts') {
$email_domainid = Request::any('domainid', 0);
@@ -313,9 +306,9 @@ if ($page == 'email_domain') {
}
$result = json_decode($json_result, true)['data'];
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
EmailAccounts::getLocal($userinfo, $_POST)->add();
EmailAccounts::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -384,9 +377,9 @@ if ($page == 'email_domain') {
$result = json_decode($json_result, true)['data'];
if (isset($result['popaccountid']) && $result['popaccountid'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
EmailAccounts::getLocal($userinfo, $_POST)->update();
EmailAccounts::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -443,9 +436,9 @@ if ($page == 'email_domain') {
$result = json_decode($json_result, true)['data'];
if (isset($result['popaccountid']) && $result['popaccountid'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
EmailAccounts::getLocal($userinfo, $_POST)->update();
EmailAccounts::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -502,9 +495,9 @@ if ($page == 'email_domain') {
$result = json_decode($json_result, true)['data'];
if (isset($result['popaccountid']) && $result['popaccountid'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
EmailAccounts::getLocal($userinfo, $_POST)->delete();
EmailAccounts::getLocal($userinfo, Request::postAll())->delete();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -538,9 +531,9 @@ if ($page == 'email_domain') {
$result = json_decode($json_result, true)['data'];
if (isset($result['email']) && $result['email'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
EmailForwarders::getLocal($userinfo, $_POST)->add();
EmailForwarders::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -600,22 +593,15 @@ if ($page == 'email_domain') {
$result = json_decode($json_result, true)['data'];
if (isset($result['destination']) && $result['destination'] != '') {
if (isset($_POST['forwarderid'])) {
$forwarderid = intval($_POST['forwarderid']);
} elseif (isset($_GET['forwarderid'])) {
$forwarderid = intval($_GET['forwarderid']);
} else {
$forwarderid = 0;
}
$forwarderid = Request::any('forwarderid', 0);
$result['destination'] = explode(' ', $result['destination']);
if (isset($result['destination'][$forwarderid]) && $result['email'] != $result['destination'][$forwarderid]) {
$forwarder = $result['destination'][$forwarderid];
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
EmailForwarders::getLocal($userinfo, $_POST)->delete();
EmailForwarders::getLocal($userinfo, Request::postAll())->delete();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}

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;
@@ -68,14 +68,22 @@ if ($page == 'overview' || $page == 'htpasswds') {
Response::dynamicError($e->getMessage());
}
$actions_links = [];
$actions_links[] = [
'href' => $linker->getLink(['section' => 'extras', 'page' => 'htpasswds', 'action' => 'add']),
'label' => lng('extras.directoryprotection_add')
];
$actions_links[] = [
'href' => \Froxlor\Froxlor::getDocsUrl() . 'user-guide/extras/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $htpasswd_list_data, 'htpasswd_list'),
'actions_links' => [
[
'href' => $linker->getLink(['section' => 'extras', 'page' => 'htpasswds', 'action' => 'add']),
'label' => lng('extras.directoryprotection_add')
]
],
'actions_links' => $actions_links,
'entity_info' => lng('extras.description')
]);
} elseif ($action == 'delete' && $id != 0) {
@@ -89,9 +97,9 @@ if ($page == 'overview' || $page == 'htpasswds') {
$result = json_decode($json_result, true)['data'];
if (isset($result['username']) && $result['username'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
DirProtections::getLocal($userinfo, $_POST)->delete();
DirProtections::getLocal($userinfo, Request::postAll())->delete();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -111,9 +119,9 @@ if ($page == 'overview' || $page == 'htpasswds') {
}
}
} elseif ($action == 'add') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
DirProtections::getLocal($userinfo, $_POST)->add();
DirProtections::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -141,9 +149,9 @@ if ($page == 'overview' || $page == 'htpasswds') {
$result = json_decode($json_result, true)['data'];
if (isset($result['username']) && $result['username'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
DirProtections::getLocal($userinfo, $_POST)->update();
DirProtections::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -185,14 +193,22 @@ if ($page == 'overview' || $page == 'htpasswds') {
Response::dynamicError($e->getMessage());
}
$actions_links = [];
$actions_links[] = [
'href' => $linker->getLink(['section' => 'extras', 'page' => 'htaccess', 'action' => 'add']),
'label' => lng('extras.pathoptions_add')
];
$actions_links[] = [
'href' => \Froxlor\Froxlor::getDocsUrl() . 'user-guide/extras/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $htaccess_list_data, 'htaccess_list'),
'actions_links' => [
[
'href' => $linker->getLink(['section' => 'extras', 'page' => 'htaccess', 'action' => 'add']),
'label' => lng('extras.pathoptions_add')
]
],
'actions_links' => $actions_links,
'entity_info' => lng('extras.description')
]);
} elseif ($action == 'delete' && $id != 0) {
@@ -206,9 +222,9 @@ if ($page == 'overview' || $page == 'htpasswds') {
$result = json_decode($json_result, true)['data'];
if (isset($result['customerid']) && $result['customerid'] != '' && $result['customerid'] == $userinfo['customerid']) {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
DirOptions::getLocal($userinfo, $_POST)->delete();
DirOptions::getLocal($userinfo, Request::postAll())->delete();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -224,9 +240,9 @@ if ($page == 'overview' || $page == 'htpasswds') {
}
}
} elseif ($action == 'add') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
DirOptions::getLocal($userinfo, $_POST)->add();
DirOptions::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -255,9 +271,9 @@ if ($page == 'overview' || $page == 'htpasswds') {
$result = json_decode($json_result, true)['data'];
if ((isset($result['customerid'])) && ($result['customerid'] != '') && ($result['customerid'] == $userinfo['customerid'])) {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
DirOptions::getLocal($userinfo, $_POST)->update();
DirOptions::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -282,18 +298,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");
if (Request::post('send') == 'send') {
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "customer_extras::export - aborted scheduled data export job");
try {
CustomerBackups::getLocal($userinfo, $_POST)->delete();
DataDump::getLocal($userinfo, Request::postAll())->delete();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -302,43 +318,53 @@ 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') {
if (Request::post('send') == 'send') {
try {
CustomerBackups::getLocal($userinfo, $_POST)->add();
DataDump::getLocal($userinfo, Request::postAll())->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';
$actions_links = [
[
'href' => \Froxlor\Froxlor::getDocsUrl() . 'user-guide/extras/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
]
];
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'],
'actions_links' => $actions_links,
'tabledata' => Listing::format($collection, $export_list_data, 'export_list'),
]);
}
}
} else {
Response::standardError('backupfunctionnotenabled');
Response::standardError('exportfunctionnotenabled');
}
}

View File

@@ -27,6 +27,7 @@ const AREA = 'customer';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\Ftps as Ftps;
use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\FroxlorLogger;
@@ -37,7 +38,6 @@ use Froxlor\UI\Listing;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\CurrentUser;
// redirect if this customer page is hidden via settings
if (Settings::IsInList('panel.customer_hide_options', 'ftp')) {
@@ -57,15 +57,19 @@ if ($page == 'overview' || $page == 'accounts') {
Response::dynamicError($e->getMessage());
}
$actions_links = false;
$actions_links = [];
if (CurrentUser::canAddResource('ftps')) {
$actions_links = [
[
'href' => $linker->getLink(['section' => 'ftp', 'page' => 'accounts', 'action' => 'add']),
'label' => lng('ftp.account_add')
]
$actions_links[] = [
'href' => $linker->getLink(['section' => 'ftp', 'page' => 'accounts', 'action' => 'add']),
'label' => lng('ftp.account_add')
];
}
$actions_links[] = [
'href' => \Froxlor\Froxlor::getDocsUrl() . 'user-guide/ftp-accounts/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $ftp_list_data, 'ftp_list'),
@@ -83,9 +87,9 @@ if ($page == 'overview' || $page == 'accounts') {
$result = json_decode($json_result, true)['data'];
if (isset($result['username']) && $result['username'] != $userinfo['loginname']) {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
Ftps::getLocal($userinfo, $_POST)->delete();
Ftps::getLocal($userinfo, Request::postAll())->delete();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -104,9 +108,9 @@ if ($page == 'overview' || $page == 'accounts') {
}
} elseif ($action == 'add') {
if ($userinfo['ftps_used'] < $userinfo['ftps'] || $userinfo['ftps'] == '-1') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
Ftps::getLocal($userinfo, $_POST)->add();
Ftps::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -160,9 +164,9 @@ if ($page == 'overview' || $page == 'accounts') {
$result = json_decode($json_result, true)['data'];
if (isset($result['username']) && $result['username'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
Ftps::getLocal($userinfo, $_POST)->update();
Ftps::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}

View File

@@ -27,21 +27,23 @@ 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\Database\DbManager;
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\Request;
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();
@@ -54,7 +56,7 @@ if ($action == 'logout') {
$result = $result['switched_user'];
session_regenerate_id(true);
CurrentUser::setData($result);
$target = (isset($_GET['target']) ? $_GET['target'] : 'index');
$target = Request::get('target', 'index');
$redirect = "admin_" . $target . ".php";
if (!file_exists(Froxlor::getInstallDir() . "/" . $redirect)) {
$redirect = "admin_index.php";
@@ -66,7 +68,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
@@ -114,15 +116,20 @@ if ($page == 'overview') {
$userinfo['traffic_bytes'] = ($userinfo['traffic'] > -1) ? $userinfo['traffic'] * 1024 : -1;
$userinfo['traffic_bytes_used'] = $userinfo['traffic_used'] * 1024;
if (Settings::Get('system.mail_quota_enabled')) {
$userinfo['email_quota_bytes'] = ($userinfo['email_quota'] > -1) ? $userinfo['email_quota'] * 1024 * 1024 : -1;
$userinfo['email_quota_bytes_used'] = $userinfo['email_quota_used'] * 1024 * 1024;
}
if ($usages) {
$userinfo['diskspace_bytes_used'] = $usages['webspace'] * 1024;
$userinfo['mailspace_used'] = $usages['mail'] * 1024;
$userinfo['mailspace_used'] = $usages['mail'] * 1024;
$userinfo['dbspace_used'] = $usages['mysql'] * 1024;
$userinfo['total_bytes_used'] = ($usages['webspace'] + $usages['mail'] + $usages['mysql']) * 1024;
} else {
$userinfo['diskspace_bytes_used'] = 0;
$userinfo['total_bytes_used'] = 0;
$userinfo['mailspace_used'] = 0;
$userinfo['mailspace_used'] = 0;
$userinfo['dbspace_used'] = 0;
}
@@ -131,141 +138,159 @@ 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 (Request::post('send') == 'changepassword') {
$old_password = Validate::validate(Request::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(Request::post('new_password'), 'new password');
$new_password_confirm = Crypt::validatePassword(Request::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 (Request::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 (Request::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);
}
// Update global myqsl user password
if ($userinfo['mysqls'] != 0 && Request::post('change_global_mysql') == 'true') {
$allowed_mysqlservers = json_decode($userinfo['allowed_mysqlserver'] ?? '[]', true);
foreach ($allowed_mysqlservers as $dbserver) {
// require privileged access for target db-server
Database::needRoot(true, $dbserver, false);
// get DbManager
$dbm = new DbManager($log);
// give permission to the user on every access-host we have
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
if ($dbm->getManager()->userExistsOnHost($userinfo['loginname'], $mysql_access_host)) {
$dbm->getManager()->grantPrivilegesTo($userinfo['loginname'], $new_password, $mysql_access_host, false, true);
} else {
// create global mysql user if not exists
$dbm->getManager()->grantPrivilegesTo($userinfo['loginname'], $new_password, $mysql_access_host, false, false, true);
}
}
$dbm->getManager()->flushPrivileges();
}
}
Response::redirectTo($filename);
}
} elseif (Request::post('send') == 'changetheme') {
if (Settings::Get('panel.allow_theme_change_customer') == 1) {
$theme = Validate::validate(Request::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 (Request::post('send') == 'changelanguage') {
$def_language = Validate::validate(Request::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

@@ -28,16 +28,18 @@ require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\Mysqls;
use Froxlor\Api\Commands\MysqlServer;
use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\Database\DbManager;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
use Froxlor\System\Crypt;
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;
use Froxlor\CurrentUser;
// redirect if this customer page is hidden via settings or no resources given
if (Settings::IsInList('panel.customer_hide_options', 'mysql') || $userinfo['mysqls'] == 0) {
@@ -66,20 +68,40 @@ if ($page == 'overview' || $page == 'mysqls') {
Response::dynamicError($e->getMessage());
}
$actions_links = false;
$actions_links = [];
if (CurrentUser::canAddResource('mysqls')) {
$actions_links = [
[
'href' => $linker->getLink(['section' => 'mysql', 'page' => 'mysqls', 'action' => 'add']),
'label' => lng('mysql.database_create')
]
$actions_links[] = [
'href' => $linker->getLink(['section' => 'mysql', 'page' => 'mysqls', 'action' => 'add']),
'label' => lng('mysql.database_create')
];
}
UI::view('user/table.html.twig', [
$view = 'user/table.html.twig';
if ($collection->count() > 0) {
$view = 'user/table-note.html.twig';
$actions_links[] = [
'href' => $linker->getLink(['section' => 'mysql', 'page' => 'mysqls', 'action' => 'global_user']),
'label' => lng('mysql.edit_global_user'),
'icon' => 'fa-solid fa-user-tie',
'class' => 'btn-outline-secondary'
];
}
$actions_links[] = [
'href' => \Froxlor\Froxlor::getDocsUrl() . 'user-guide/databases/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view($view, [
'listing' => Listing::format($collection, $mysql_list_data, 'mysql_list'),
'actions_links' => $actions_links,
'entity_info' => lng('mysql.description')
'entity_info' => lng('mysql.description'),
// alert-box
'type' => 'info',
'alert_msg' => lng('mysql.globaluserinfo', [$userinfo['loginname']]),
]);
} elseif ($action == 'delete' && $id != 0) {
try {
@@ -101,9 +123,9 @@ if ($page == 'overview' || $page == 'mysqls') {
$result['dbserver'] = 0;
}
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
Mysqls::getLocal($userinfo, $_POST)->delete();
Mysqls::getLocal($userinfo, Request::postAll())->delete();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -124,9 +146,9 @@ if ($page == 'overview' || $page == 'mysqls') {
}
} elseif ($action == 'add') {
if ($userinfo['mysqls_used'] < $userinfo['mysqls'] || $userinfo['mysqls'] == '-1') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
Mysqls::getLocal($userinfo, $_POST)->add();
Mysqls::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -164,9 +186,9 @@ if ($page == 'overview' || $page == 'mysqls') {
$result = json_decode($json_result, true)['data'];
if (isset($result['databasename']) && $result['databasename'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
$json_result = Mysqls::getLocal($userinfo, $_POST)->update();
$json_result = Mysqls::getLocal($userinfo, Request::postAll())->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -179,7 +201,7 @@ if ($page == 'overview' || $page == 'mysqls') {
$result_json = MysqlServer::getLocal($userinfo)->listing();
$result_decoded = json_decode($result_json, true)['data']['list'];
foreach ($result_decoded as $dbserver => $dbdata) {
$mysql_servers[$dbserver] = $dbdata['caption'] . ' (' . $dbdata['host'] . (isset($dbdata['port']) && !empty($dbdata['port']) ? ':' . $dbdata['port'] : '').')';
$mysql_servers[$dbserver] = $dbdata['caption'] . ' (' . $dbdata['host'] . (isset($dbdata['port']) && !empty($dbdata['port']) ? ':' . $dbdata['port'] : '') . ')';
}
} catch (Exception $e) {
/* just none */
@@ -194,5 +216,45 @@ if ($page == 'overview' || $page == 'mysqls') {
]);
}
}
} elseif ($action == 'global_user') {
$allowed_mysqlservers = json_decode($userinfo['allowed_mysqlserver'] ?? '[]', true);
if ($userinfo['mysqls'] == 0 || empty($allowed_mysqlservers)) {
Response::dynamicError('No permission');
}
if (Request::post('send') == 'send') {
$new_password = Crypt::validatePassword(Request::post('mysql_password'));
foreach ($allowed_mysqlservers as $dbserver) {
// require privileged access for target db-server
Database::needRoot(true, $dbserver, true);
// get DbManager
$dbm = new DbManager($log);
// give permission to the user on every access-host we have
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
if ($dbm->getManager()->userExistsOnHost($userinfo['loginname'], $mysql_access_host)) {
// update password
$dbm->getManager()->grantPrivilegesTo($userinfo['loginname'], $new_password, $mysql_access_host, false, true, true);
} else {
// create missing user
$dbm->getManager()->grantPrivilegesTo($userinfo['loginname'], $new_password, $mysql_access_host, false, false, true);
}
}
$dbm->getManager()->flushPrivileges();
}
Response::redirectTo($filename, [
'page' => 'overview'
]);
} else {
$mysql_global_user_data = include_once dirname(__FILE__) . '/lib/formfields/customer/mysql/formfield.mysql_global_user.php';
UI::view('user/form.html.twig', [
'formaction' => $linker->getLink(['section' => 'mysql', 'page' => 'mysqls', 'action' => 'global_user']),
'formdata' => $mysql_global_user_data['mysql_global_user'],
'editid' => $id
]);
}
}
}

View File

@@ -30,6 +30,7 @@ if (!defined('AREA')) {
use Froxlor\Api\Commands\DomainZones;
use Froxlor\Dns\Dns;
use Froxlor\Settings;
use Froxlor\UI\Collection;
use Froxlor\UI\HTML;
use Froxlor\UI\Listing;
@@ -42,11 +43,11 @@ use Froxlor\UI\Response;
$domain_id = (int)Request::any('domain_id');
$record = isset($_POST['dns_record']) ? trim($_POST['dns_record']) : null;
$type = isset($_POST['dns_type']) ? $_POST['dns_type'] : 'A';
$prio = isset($_POST['dns_mxp']) ? (int)$_POST['dns_mxp'] : null;
$content = isset($_POST['dns_content']) ? trim($_POST['dns_content']) : null;
$ttl = isset($_POST['dns_ttl']) ? (int)$_POST['dns_ttl'] : 18000;
$record = Request::post('dns_record');
$type = Request::post('dns_type', 'A');
$prio = Request::post('dns_mxp');
$content = Request::post('dns_content');
$ttl = (int)Request::post('dns_ttl', Settings::get('system.defaultttl'));
// get domain-name
$domain = Dns::getAllowedDomainEntry($domain_id, AREA, $userinfo);
@@ -71,7 +72,7 @@ if ($action == 'add_record' && !empty($_POST)) {
$errors = str_replace("\n", "<br>", $e->getMessage());
}
} elseif ($action == 'delete') {
$entry_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$entry_id = (int)Request::get('id', 0);
HTML::askYesNo('dnsentry_reallydelete', $filename, [
'id' => $entry_id,
'domain_id' => $domain_id,
@@ -82,9 +83,9 @@ if ($action == 'add_record' && !empty($_POST)) {
'page' => $page,
'domain_id' => $domain_id
]);
} elseif (isset($_POST['send']) && $_POST['send'] == 'send' && $action == 'deletesure' && !empty($_POST)) {
$entry_id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
$domain_id = isset($_POST['domain_id']) ? (int)$_POST['domain_id'] : 0;
} elseif (Request::post('send') == 'send' && $action == 'deletesure' && !empty($_POST)) {
$entry_id = (int)Request::post('id', 0);
$domain_id = (int)Request::post('domain_id', 0);
// remove entry
if ($entry_id > 0 && $domain_id > 0) {
try {

View File

@@ -77,7 +77,7 @@ if (!empty($errid)) {
$mail_html = nl2br($mail_body);
// send actual report to dev-team
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
// send mail and say thanks
$_mailerror = false;
try {

267
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,6 +38,7 @@ 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;
@@ -52,7 +54,7 @@ if ($action == '2fa_entercode') {
Response::redirectTo('index.php');
exit();
}
$smessage = isset($_GET['showmessage']) ? (int)$_GET['showmessage'] : 0;
$smessage = (int)Request::get('showmessage', 0);
$message = "";
if ($smessage > 0) {
$message = lng('error.2fa_wrongcode');
@@ -60,6 +62,7 @@ if ($action == '2fa_entercode') {
// show template to enter code
UI::view('login/enter2fa.html.twig', [
'pagetitle' => lng('login.2fa'),
'remember_me' => (Settings::Get('panel.db_version') >= 202407200) ? true : false,
'message' => $message
]);
} elseif ($action == '2fa_verify') {
@@ -69,30 +72,31 @@ if ($action == '2fa_entercode') {
Response::redirectTo('index.php');
exit();
}
$code = isset($_POST['2fa_code']) ? $_POST['2fa_code'] : null;
$code = Request::post('2fa_code');
$remember = Request::post('2fa_remember');
// verify entered code
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
$result = ($_SESSION['secret_2fa'] == 'email' ? true : $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3));
// get user-data
$table = $_SESSION['uidtable_2fa'];
$field = $_SESSION['uidfield_2fa'];
$uid = $_SESSION['uid_2fa'];
$isadmin = $_SESSION['unfo_2fa'];
if ($_SESSION['secret_2fa'] == 'email') {
// verify code set to user's data_2fa field
$sel_stmt = Database::prepare("SELECT `data_2fa` FROM " . $table . " WHERE `" . $field . "` = :uid");
$userinfo_code = Database::pexecute_first($sel_stmt, ['uid' => $uid]);
// 60sec discrepancy (possible slow email delivery)
$result = $tfa->verifyCode($userinfo_code['data_2fa'], $code, 60);
} else {
$result = $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3);
}
// either the code is valid when using authenticator-app, or we will select userdata by id and entered code
// which is temporarily stored for the customer when using email-2fa
if ($result) {
$sel_param = [
'uid' => $uid
];
if ($_SESSION['secret_2fa'] == 'email') {
// verify code by selecting user by id and the temp. stored code,
// so only if it's the correct code, we get the user-data
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid AND `data_2fa` = :code");
$sel_param['code'] = $code;
} else {
// Authenticator-verification has already happened at this point, so just get the user-data
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid");
}
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid");
$userinfo = Database::pexecute_first($sel_stmt, $sel_param);
// whoops, no (valid) user? Start again
if (empty($userinfo)) {
@@ -104,20 +108,49 @@ if ($action == '2fa_entercode') {
$userinfo['adminsession'] = $isadmin;
$userinfo['userid'] = $uid;
// 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");
Database::pexecute_first($del_stmt, [
'uid' => $uid
]);
}
// when remember is activated, set the cookie
if ($remember) {
$selector = base64_encode(Froxlor::genSessionId(9));
$authenticator = Froxlor::genSessionId(33);
$valid_until = time()+60*60*24*30;
$ins_stmt = Database::prepare("
INSERT INTO `".TABLE_PANEL_2FA_TOKENS."` SET
`selector` = :selector,
`token` = :authenticator,
`userid` = :userid,
`valid_until` = :valid_until
");
Database::pexecute($ins_stmt, [
'selector' => $selector,
'authenticator' => hash('sha256', $authenticator),
'userid' => $uid,
'valid_until' => $valid_until
]);
$cookie_params = [
'expires' => $valid_until, // 30 days
'path' => '/',
'domain' => UI::getCookieHost(),
'secure' => UI::requestIsHttps(),
'httponly' => true,
'samesite' => 'Strict'
];
setcookie('frx_2fa_remember', $selector.':'.base64_encode($authenticator), $cookie_params);
}
// if not successful somehow - start again
if (!finishLogin($userinfo)) {
Response::redirectTo('index.php', [
'showmessage' => '2'
]);
}
// 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");
$userinfo = Database::pexecute_first($del_stmt, [
'uid' => $uid
]);
}
exit();
}
// wrong 2fa code - treat like "wrong password"
@@ -161,30 +194,41 @@ if ($action == '2fa_entercode') {
exit();
} elseif ($action == 'login') {
if (!empty($_POST)) {
$loginname = Validate::validate($_POST['loginname'], 'loginname');
$password = Validate::validate($_POST['password'], 'password');
$loginname = Validate::validate(Request::post('loginname'), 'loginname');
$password = Validate::validate(Request::post('password'), 'password');
$stmt = Database::prepare("SELECT `loginname` AS `customer` FROM `" . TABLE_PANEL_CUSTOMERS . "`
WHERE `loginname`= :loginname");
$select_additional = '';
if (Settings::Get('panel.db_version') >= 202312230) {
$select_additional = ' AND `gui_access` = 1';
}
$stmt = Database::prepare("
SELECT `loginname` AS `customer`
FROM `" . TABLE_PANEL_CUSTOMERS . "`
WHERE `loginname`= :loginname" .
$select_additional
);
Database::pexecute($stmt, [
"loginname" => $loginname
]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$is_admin = false;
$table = "";
if ($row && $row['customer'] == $loginname) {
$table = "`" . TABLE_PANEL_CUSTOMERS . "`";
$uid = 'customerid';
$adminsession = '0';
$is_admin = false;
} else {
$is_admin = true;
if ((int)Settings::Get('login.domain_login') == 1) {
$domainname = $idna_convert->encode(preg_replace([
'/\:(\d)+$/',
'/^https?\:\/\//'
], '', $loginname));
$stmt = Database::prepare("SELECT `customerid` FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE `domain` = :domain");
$stmt = Database::prepare("
SELECT `customerid`
FROM `" . TABLE_PANEL_DOMAINS . "`
WHERE `domain` = :domain
");
Database::pexecute($stmt, [
"domain" => $domainname
]);
@@ -193,8 +237,11 @@ if ($action == '2fa_entercode') {
if (isset($row2['customerid']) && $row2['customerid'] > 0) {
$loginname = Customer::getCustomerDetail($row2['customerid'], 'loginname');
if ($loginname !== false) {
$stmt = Database::prepare("SELECT `loginname` AS `customer` FROM `" . TABLE_PANEL_CUSTOMERS . "`
WHERE `loginname`= :loginname");
$stmt = Database::prepare("
SELECT `loginname` AS `customer`
FROM `" . TABLE_PANEL_CUSTOMERS . "`
WHERE `loginname`= :loginname
");
Database::pexecute($stmt, [
"loginname" => $loginname
]);
@@ -203,13 +250,17 @@ if ($action == '2fa_entercode') {
$table = "`" . TABLE_PANEL_CUSTOMERS . "`";
$uid = 'customerid';
$adminsession = '0';
$is_admin = false;
}
}
}
}
}
if (empty($table)) {
// try login as admin of no customer-login method worked
$is_admin = true;
}
if ((Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) && $is_admin == false) {
Response::redirectTo('index.php');
exit();
@@ -217,9 +268,11 @@ if ($action == '2fa_entercode') {
if ($is_admin) {
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
$stmt = Database::prepare("SELECT `loginname` AS `admin` FROM `" . TABLE_PANEL_ADMINS . "`
$stmt = Database::prepare("
SELECT `loginname` AS `admin` FROM `" . TABLE_PANEL_ADMINS . "`
WHERE `loginname`= :loginname
AND `change_serversettings` = '1'");
AND `change_serversettings` = '1'
");
Database::pexecute($stmt, [
"loginname" => $loginname
]);
@@ -230,8 +283,16 @@ if ($action == '2fa_entercode') {
exit();
}
} else {
$stmt = Database::prepare("SELECT `loginname` AS `admin` FROM `" . TABLE_PANEL_ADMINS . "`
WHERE `loginname`= :loginname");
$select_additional = '';
if (Settings::Get('panel.db_version') >= 202312230) {
$select_additional = ' AND `gui_access` = 1';
}
$stmt = Database::prepare("
SELECT `loginname` AS `admin`
FROM `" . TABLE_PANEL_ADMINS . "`
WHERE `loginname`= :loginname" .
$select_additional
);
Database::pexecute($stmt, [
"loginname" => $loginname
]);
@@ -247,7 +308,7 @@ if ($action == '2fa_entercode') {
$rstlog = FroxlorLogger::getInstanceOf([
'loginname' => $_SERVER['REMOTE_ADDR']
]);
$rstlog->logAction(FroxlorLogger::LOGIN_ACTION, LOG_WARNING, "Unknown user '" . $loginname . "' tried to login.");
$rstlog->logAction(FroxlorLogger::LOGIN_ACTION, LOG_WARNING, "Unknown user tried to login.");
Response::redirectTo('index.php', [
'showmessage' => '2'
@@ -256,8 +317,9 @@ if ($action == '2fa_entercode') {
}
}
$userinfo_stmt = Database::prepare("SELECT * FROM $table
WHERE `loginname`= :loginname");
$userinfo_stmt = Database::prepare("
SELECT * FROM $table WHERE `loginname`= :loginname
");
Database::pexecute($userinfo_stmt, [
"loginname" => $loginname
]);
@@ -280,9 +342,11 @@ if ($action == '2fa_entercode') {
} else {
// login correct
// reset loginfail_counter, set lastlogin_succ
$stmt = Database::prepare("UPDATE $table
SET `lastlogin_succ`= :lastlogin_succ, `loginfail_count`='0'
WHERE `$uid`= :uid");
$stmt = Database::prepare("
UPDATE $table
SET `lastlogin_succ`= :lastlogin_succ, `loginfail_count`='0'
WHERE `$uid`= :uid
");
Database::pexecute($stmt, [
"lastlogin_succ" => time(),
"uid" => $userinfo[$uid]
@@ -292,9 +356,11 @@ if ($action == '2fa_entercode') {
}
} else {
// login incorrect
$stmt = Database::prepare("UPDATE $table
$stmt = Database::prepare("
UPDATE $table
SET `lastlogin_fail`= :lastlogin_fail, `loginfail_count`=`loginfail_count`+1
WHERE `$uid`= :uid");
WHERE `$uid`= :uid
");
Database::pexecute($stmt, [
"lastlogin_fail" => time(),
"uid" => $userinfo[$uid]
@@ -304,7 +370,7 @@ if ($action == '2fa_entercode') {
$rstlog = FroxlorLogger::getInstanceOf([
'loginname' => $_SERVER['REMOTE_ADDR']
]);
$rstlog->logAction(FroxlorLogger::LOGIN_ACTION, LOG_WARNING, "User '" . $loginname . "' tried to login with wrong password.");
$rstlog->logAction(FroxlorLogger::LOGIN_ACTION, LOG_WARNING, "User tried to login with wrong password.");
unset($userinfo);
Response::redirectTo('index.php', [
@@ -315,6 +381,25 @@ if ($action == '2fa_entercode') {
// 2FA activated
if (Settings::Get('2fa.enabled') == '1' && $userinfo['type_2fa'] > 0) {
// check for remember cookie
if (!empty($_COOKIE['frx_2fa_remember'])) {
list($selector, $authenticator) = explode(':', $_COOKIE['frx_2fa_remember']);
$sel_stmt = Database::prepare("SELECT `token` FROM `".TABLE_PANEL_2FA_TOKENS."` WHERE `selector` = :selector AND `userid` = :uid AND `valid_until` >= UNIX_TIMESTAMP()");
$token_check = Database::pexecute_first($sel_stmt, ['selector' => $selector, 'uid' => $userinfo[$uid]]);
if ($token_check && hash_equals($token_check['token'], hash('sha256', base64_decode($authenticator)))) {
if (!finishLogin($userinfo)) {
Response::redirectTo('index.php', [
'showmessage' => '2'
]);
}
exit();
}
// not found or invalid, this cookie is useless, get rid of it
unset($_COOKIE['frx_2fa_remember']);
setcookie('frx_2fa_remember', "", time()-3600);
}
// redirect to code-enter-page
$_SESSION['secret_2fa'] = ($userinfo['type_2fa'] == 2 ? $userinfo['data_2fa'] : 'email');
$_SESSION['uid_2fa'] = $userinfo[$uid];
@@ -325,11 +410,12 @@ if ($action == '2fa_entercode') {
if ($userinfo['type_2fa'] == 1) {
// generate code
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
$code = $tfa->getCode($tfa->createSecret());
$secret = $tfa->createSecret();
$code = $tfa->getCode($secret);
// set code for user
$stmt = Database::prepare("UPDATE $table SET `data_2fa` = :d2fa WHERE `$uid` = :uid");
Database::pexecute($stmt, [
"d2fa" => $code,
"d2fa" => $secret,
"uid" => $userinfo[$uid]
]);
// build up & send email
@@ -381,7 +467,7 @@ if ($action == '2fa_entercode') {
}
exit();
} else {
$smessage = isset($_GET['showmessage']) ? (int)$_GET['showmessage'] : 0;
$smessage = (int)Request::get('showmessage', 0);
$message = '';
$successmessage = '';
@@ -418,25 +504,20 @@ if ($action == '2fa_entercode') {
}
// Pass the last used page if needed
$lastscript = "";
if (isset($_REQUEST['script']) && $_REQUEST['script'] != "") {
$lastscript = $_REQUEST['script'];
$lastscript = Request::any('script', '');
if (!empty($lastscript)) {
$lastscript = str_replace("..", "", $lastscript);
$lastscript = htmlspecialchars($lastscript, ENT_QUOTES);
if (!file_exists(__DIR__ . "/" . $lastscript)) {
if (file_exists(__DIR__ . "/" . $lastscript)) {
$_SESSION['lastscript'] = $lastscript;
} else {
$lastscript = "";
}
}
$lastqrystr = "";
if (isset($_REQUEST['qrystr']) && $_REQUEST['qrystr'] != "") {
$lastqrystr = urlencode($_REQUEST['qrystr']);
}
if (!empty($lastscript)) {
$_SESSION['lastscript'] = $lastscript;
}
$lastqrystr = Request::any('qrystr', '');
if (!empty($lastqrystr)) {
$lastqrystr = urlencode($lastqrystr);
$_SESSION['lastqrystr'] = $lastqrystr;
}
@@ -454,8 +535,8 @@ if ($action == 'forgotpwd') {
$message = '';
if (!empty($_POST)) {
$loginname = Validate::validate($_POST['loginname'], 'loginname');
$email = Validate::validateEmail($_POST['loginemail']);
$loginname = Validate::validate(Request::post('loginname'), 'loginname');
$email = Validate::validateEmail(Request::post('loginemail'));
$result_stmt = Database::prepare("SELECT `adminid`, `customerid`, `customernumber`, `firstname`, `name`, `company`, `email`, `loginname`, `def_language`, `deactivated` FROM `" . TABLE_PANEL_CUSTOMERS . "`
WHERE `loginname`= :loginname
AND `email`= :email");
@@ -622,7 +703,7 @@ if ($action == 'forgotpwd') {
$rstlog = FroxlorLogger::getInstanceOf([
'loginname' => 'password_reset'
]);
$rstlog->logAction(FroxlorLogger::USR_ACTION, LOG_WARNING, "User '" . $loginname . "' requested to set a new password, but was not found in database!");
$rstlog->logAction(FroxlorLogger::USR_ACTION, LOG_WARNING, "Unknown user requested to set a new password, but was not found in database!");
$message = lng('login.usernotfound');
}
@@ -652,9 +733,9 @@ if ($action == 'resetpwd') {
"oldest" => time() - 86400
]);
if (isset($_GET['resetcode']) && strlen($_GET['resetcode']) == 50) {
$activationcode = Request::get('resetcode');
if (!empty($activationcode) && strlen($activationcode) == 50) {
// Check if activation code is valid
$activationcode = $_GET['resetcode'];
$timestamp = substr($activationcode, 15, 10);
$third = substr($activationcode, 25, 15);
$check = substr($activationcode, 40, 10);
@@ -669,8 +750,8 @@ if ($action == 'resetpwd') {
if ($result !== false) {
try {
$new_password = Crypt::validatePassword($_POST['new_password'], true);
$new_password_confirm = Crypt::validatePassword($_POST['new_password_confirm'], true);
$new_password = Crypt::validatePassword(Request::post('new_password'), true);
$new_password_confirm = Crypt::validatePassword(Request::post('new_password_confirm'), true);
} catch (Exception $e) {
$message = $e->getMessage();
}
@@ -734,6 +815,58 @@ 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'] != '') {
@@ -747,8 +880,8 @@ function finishLogin($userinfo)
$theme = $userinfo['theme'];
} else {
$theme = Settings::Get('panel.default_theme');
CurrentUser::setField('theme', $theme);
}
CurrentUser::setField('theme', $theme);
$qryparams = [];
if (!empty($_SESSION['lastqrystr'])) {

View File

@@ -94,6 +94,11 @@ CREATE TABLE `mail_virtual` (
`popaccountid` int(11) NOT NULL default '0',
`iscatchall` tinyint(1) unsigned NOT NULL default '0',
`description` varchar(255) NOT NULL DEFAULT '',
`spam_tag_level` float(4,1) NOT NULL DEFAULT 7.0,
`rewrite_subject` tinyint(1) NOT NULL default '1',
`spam_kill_level` float(4,1) NOT NULL DEFAULT 14.0,
`bypass_spam` tinyint(1) NOT NULL default '0',
`policy_greylist` tinyint(1) NOT NULL default '1',
PRIMARY KEY (`id`),
KEY `email` (`email`)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
@@ -155,9 +160,10 @@ CREATE TABLE `panel_admins` (
`type_2fa` tinyint(1) NOT NULL default '0',
`data_2fa` varchar(25) NOT NULL default '',
`api_allowed` tinyint(1) NOT NULL default '1',
`gui_access` tinyint(1) NOT NULL default '1',
PRIMARY KEY (`adminid`),
UNIQUE KEY `loginname` (`loginname`)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC;
DROP TABLE IF EXISTS `panel_customers`;
@@ -223,6 +229,7 @@ CREATE TABLE `panel_customers` (
`api_allowed` tinyint(1) NOT NULL default '1',
`logviewenabled` tinyint(1) NOT NULL default '0',
`allowed_mysqlserver` text NOT NULL,
`gui_access` 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 +285,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',
@@ -300,7 +306,7 @@ CREATE TABLE `panel_domains` (
KEY `customerid` (`customerid`),
KEY `parentdomain` (`parentdomainid`),
KEY `domain` (`domain`)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC;
DROP TABLE IF EXISTS `panel_ipsandports`;
@@ -357,23 +363,6 @@ CREATE TABLE `panel_htpasswds` (
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
DROP TABLE IF EXISTS `panel_sessions`;
CREATE TABLE `panel_sessions` (
`hash` varchar(32) NOT NULL default '',
`userid` int(11) unsigned NOT NULL default '0',
`ipaddress` varchar(255) NOT NULL default '',
`useragent` varchar(255) NOT NULL default '',
`lastactivity` int(11) unsigned NOT NULL default '0',
`lastpaging` varchar(255) NOT NULL default '',
`formtoken` char(32) NOT NULL default '',
`language` varchar(64) NOT NULL default '',
`adminsession` tinyint(1) unsigned NOT NULL default '0',
`theme` varchar(255) NOT NULL default '',
PRIMARY KEY (`hash`),
KEY `userid` (`userid`)
) ENGINE=HEAP;
DROP TABLE IF EXISTS `panel_settings`;
CREATE TABLE `panel_settings` (
`settingid` int(11) unsigned NOT NULL auto_increment,
@@ -398,22 +387,21 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES
('logger', 'logfile', ''),
('logger', 'logtypes', 'syslog,mysql'),
('logger', 'severity', '1'),
('dkim', 'use_dkim', '0'),
('dkim', 'dkim_prefix', '/etc/postfix/dkim/'),
('dkim', 'dkim_domains', 'domains'),
('dkim', 'dkim_dkimkeys', 'dkim-keys.conf'),
('dkim', 'dkimrestart_command', 'service dkim-filter restart'),
('dkim', 'privkeysuffix', '.priv'),
('antispam', 'activated', '0'),
('antispam', 'config_file', '/etc/rspamd/local.d/froxlor_settings.conf'),
('antispam', 'reload_command', 'service rspamd restart'),
('antispam', 'dkim_keylength', '1024'),
('antispam', 'default_bypass_spam', '2'),
('antispam', 'default_spam_rewrite_subject', '1'),
('antispam', 'default_policy_greylist', '1'),
('admin', 'show_news_feed', '0'),
('admin', 'show_version_login', '0'),
('admin', 'show_version_footer', '0'),
('caa', 'caa_entry', ''),
('spf', 'use_spf', '0'),
('spf', 'spf_entry', '"v=spf1 a mx -all"'),
('dkim', 'dkim_algorithm', 'all'),
('dkim', 'dkim_keylength', '1024'),
('dkim', 'dkim_servicetype', '0'),
('dkim', 'dkim_notes', ''),
('spf', 'spf_entry', 'v=spf1 a mx -all'),
('dmarc', 'use_dmarc', '0'),
('dmarc', 'dmarc_entry', 'v=DMARC1; p=none;'),
('defaultwebsrverrhandler', 'enabled', '0'),
('defaultwebsrverrhandler', 'err401', ''),
('defaultwebsrverrhandler', 'err403', ''),
@@ -511,7 +499,6 @@ opcache.save_comments
opcache.use_cwd
opcache.fast_shutdown'),
('phpfpm', 'ini_admin_values', 'cgi.redirect_status_env
date.timezone
disable_classes
disable_functions
error_log
@@ -555,7 +542,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', ''),
@@ -563,7 +550,7 @@ opcache.validate_timestamps'),
('system', 'mod_fcgid', '0'),
('system', 'apacheconf_vhost', '/etc/apache2/sites-enabled/'),
('system', 'apacheconf_diroptions', '/etc/apache2/sites-enabled/'),
('system', 'apacheconf_htpasswddir', '/etc/apache2/htpasswd/'),
('system', 'apacheconf_htpasswddir', '/etc/apache2/froxlor-htpasswd/'),
('system', 'webalizer_quiet', '2'),
('system', 'last_archive_run', '000000'),
('system', 'mod_fcgid_configdir', '/var/www/php-fcgi-scripts'),
@@ -580,7 +567,6 @@ opcache.validate_timestamps'),
('system', 'mod_fcgid_wrapper', '1'),
('system', 'mod_fcgid_starter', '0'),
('system', 'mod_fcgid_peardir', '/usr/share/php/:/usr/share/php5/'),
('system', 'index_file_extension', 'html'),
('system', 'mod_fcgid_maxrequests', '250'),
('system', 'ssl_key_file','/etc/ssl/froxlor_selfsigned.key'),
('system', 'ssl_ca_file', ''),
@@ -647,7 +633,7 @@ opcache.validate_timestamps'),
('system', 'letsencryptreuseold', 0),
('system', 'leenabled', '0'),
('system', 'leapiversion', '2'),
('system', 'backupenabled', '0'),
('system', 'exportenabled', '0'),
('system', 'dnsenabled', '0'),
('system', 'dns_server', 'Bind'),
('system', 'apacheglobaldiropt', ''),
@@ -655,6 +641,8 @@ opcache.validate_timestamps'),
('system', 'available_shells', ''),
('system', 'le_froxlor_enabled', '0'),
('system', 'le_froxlor_redirect', '0'),
('system', 'le_renew_hook', 'systemctl restart postfix dovecot proftpd'),
('system', 'le_renew_services', ''),
('system', 'letsencryptacmeconf', '/etc/apache2/conf-enabled/acme.conf'),
('system', 'mail_use_smtp', '0'),
('system', 'mail_smtp_host', 'localhost'),
@@ -697,7 +685,7 @@ opcache.validate_timestamps'),
('system', 'distribution', ''),
('system', 'update_channel', 'stable'),
('system', 'updatecheck_data', ''),
('system', 'update_notify_last', '2.0.23'),
('system', 'update_notify_last', ''),
('system', 'traffictool', 'goaccess'),
('system', 'req_limit_per_interval', 60),
('system', 'req_limit_interval', 60),
@@ -705,7 +693,7 @@ opcache.validate_timestamps'),
('api', 'customer_default', '1'),
('2fa', 'enabled', '1'),
('panel', 'decimal_places', '4'),
('panel', 'adminmail', 'admin@SERVERNAME'),
('panel', 'adminmail', 'ADMIN_MAIL'),
('panel', 'phpmyadmin_url', ''),
('panel', 'webmail_url', ''),
('panel', 'webftp_url', ''),
@@ -744,8 +732,9 @@ opcache.validate_timestamps'),
('panel', 'logo_overridetheme', '0'),
('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'),
('panel', 'version', '2.0.23'),
('panel', 'db_version', '202304260');
('panel', 'menu_collapsed', '1'),
('panel', 'version', '2.2.8'),
('panel', 'db_version', '202412030');
DROP TABLE IF EXISTS `panel_tasks`;
@@ -768,6 +757,7 @@ CREATE TABLE `panel_templates` (
`templategroup` varchar(255) NOT NULL default '',
`varname` varchar(255) NOT NULL default '',
`value` longtext NOT NULL,
`file_extension` varchar(50) NOT NULL default 'html',
PRIMARY KEY (id),
KEY adminid (adminid)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
@@ -914,7 +904,7 @@ INSERT INTO `cronjobs_run` (`id`, `module`, `cronfile`, `cronclass`, `interval`,
(3, 'froxlor/reports', 'usage_report', '\\Froxlor\\Cron\\Traffic\\ReportsCron', '1 DAY', '1', 'cron_usage_report'),
(4, 'froxlor/core', 'mailboxsize', '\\Froxlor\\Cron\\System\\MailboxsizeCron', '6 HOUR', '1', 'cron_mailboxsize'),
(5, 'froxlor/letsencrypt', 'letsencrypt', '\\Froxlor\\Cron\\Http\\LetsEncrypt\\AcmeSh', '5 MINUTE', '0', 'cron_letsencrypt'),
(6, 'froxlor/backup', 'backup', '\\Froxlor\\Cron\\System\\BackupCron', '1 DAY', '0', 'cron_backup');
(6, 'froxlor/export', 'export', '\\Froxlor\\Cron\\System\\ExportCron', '1 HOUR', '0', 'cron_export');
DROP TABLE IF EXISTS `ftp_quotalimits`;
@@ -1052,4 +1042,25 @@ 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_2fa_tokens`;
CREATE TABLE `panel_2fa_tokens` (
`id` int(11) NOT NULL auto_increment,
`selector` varchar(200) NOT NULL,
`token` varchar(200) NOT NULL,
`userid` int(11) NOT NULL default '0',
`valid_until` int(15) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
FROXLORSQL;

View File

@@ -23,11 +23,11 @@
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
use Froxlor\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

@@ -99,7 +99,6 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
}
Update::lastStepStatus(0);
Update::showUpdateStep("Cleaning up old files");
$to_clean = array(
"install/lib",
"install/lng",
@@ -121,30 +120,7 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
"lng/swedish.lng.php",
"scripts",
);
$disabled = explode(',', ini_get('disable_functions'));
$exec_allowed = !in_array('exec', $disabled);
$del_list = "";
foreach ($to_clean as $filedir) {
$complete_filedir = Froxlor::getInstallDir() . $filedir;
if (file_exists($complete_filedir)) {
if ($exec_allowed) {
FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir));
} else {
$del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL;
}
}
}
if ($exec_allowed) {
Update::lastStepStatus(0);
} else {
if (empty($del_list)) {
// none of the files existed
Update::lastStepStatus(0);
} else {
Update::lastStepStatus(1, 'manual commands needed',
'Please run the following commands manually:<br><pre>' . $del_list . '</pre>');
}
}
Update::cleanOldFiles($to_clean);
Update::showUpdateStep("Adding new settings");
$panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int)$_POST['panel_settings_mode'] : 0;
@@ -512,3 +488,8 @@ if (Froxlor::isFroxlorVersion('2.0.22')) {
Update::showUpdateStep("Updating from 2.0.22 to 2.0.23", false);
Froxlor::updateToVersion('2.0.23');
}
if (Froxlor::isFroxlorVersion('2.0.23')) {
Update::showUpdateStep("Updating from 2.0.23 to 2.0.24", false);
Froxlor::updateToVersion('2.0.24');
}

View File

@@ -0,0 +1,254 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\Install\Update;
use Froxlor\Settings;
if (!defined('_CRON_UPDATE')) {
if (!defined('AREA') || (defined('AREA') && AREA != 'admin') || !isset($userinfo['loginname']) || (isset($userinfo['loginname']) && $userinfo['loginname'] == '')) {
header('Location: ../../../../index.php');
exit();
}
}
if (Froxlor::isFroxlorVersion('2.0.24')) {
Update::showUpdateStep("Cleaning domains table");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` ROW_FORMAT=DYNAMIC;");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` DROP COLUMN `ismainbutsubto`;");
Update::lastStepStatus(0);
Update::showUpdateStep("Creating new tables and fields");
Database::query("DROP TABLE IF EXISTS `panel_loginlinks`;");
$sql = "CREATE TABLE `panel_loginlinks` (
`hash` varchar(500) NOT NULL,
`loginname` varchar(50) NOT NULL,
`valid_until` int(15) NOT NULL,
`allowed_from` text NOT NULL,
UNIQUE KEY `loginname` (`loginname`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
Database::query($sql);
Update::lastStepStatus(0);
Update::showUpdateStep("Adding new settings");
Settings::AddNew('panel.menu_collapsed', 1);
Update::lastStepStatus(0);
Update::showUpdateStep("Adjusting setting for deactivated webroot");
$current_deactivated_webroot = Settings::Get('system.deactivateddocroot');
if (empty($current_deactivated_webroot)) {
Settings::Set('system.deactivateddocroot', FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/templates/misc/deactivated/'));
Update::lastStepStatus(0);
} else {
Update::lastStepStatus(1, 'Customized setting, not changing');
}
Update::showUpdateStep("Adjusting cronjobs");
$cfupd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET
`module`= 'froxlor/export',
`cronfile` = 'export',
`cronclass` = :cc,
`interval` = '1 HOUR',
`desc_lng_key` = 'cron_export'
WHERE `module` = 'froxlor/backup'
");
Database::pexecute($cfupd_stmt, [
'cc' => '\\Froxlor\\Cron\\System\\ExportCron'
]);
Update::lastStepStatus(0);
Update::showUpdateStep("Adjusting system for data-export function");
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "`SET `varname` = 'exportenabled' WHERE `settinggroup`= 'system' AND `varname`= 'backupenabled'");
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "`SET `value` = REPLACE(`value`, 'extras.backup', 'extras.export') WHERE `settinggroup` = 'panel' AND `varname` = 'customer_hide_options'");
Database::query("DELETE FROM `" . TABLE_PANEL_USERCOLUMNS . "` WHERE `section` = 'backup_list'");
Database::query("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202305240');
Froxlor::updateToVersion('2.1.0-dev1');
}
if (Froxlor::isFroxlorVersion('2.1.0-dev1')) {
Update::showUpdateStep("Updating from 2.1.0-dev1 to 2.1.0-beta1", false);
Froxlor::updateToVersion('2.1.0-beta1');
}
if (Froxlor::isFroxlorVersion('2.1.0-beta1')) {
Update::showUpdateStep("Updating from 2.1.0-beta1 to 2.1.0-beta2", false);
Update::showUpdateStep("Removing unused table");
Database::query("DROP TABLE IF EXISTS `panel_sessions`;");
Update::lastStepStatus(0);
Froxlor::updateToVersion('2.1.0-beta2');
}
if (Froxlor::isFroxlorVersion('2.1.0-beta2')) {
Update::showUpdateStep("Updating from 2.1.0-beta2 to 2.1.0-rc1", false);
Froxlor::updateToVersion('2.1.0-rc1');
}
if (Froxlor::isFroxlorVersion('2.1.0-rc1')) {
Update::showUpdateStep("Updating from 2.1.0-rc1 to 2.1.0-rc2", false);
Update::showUpdateStep("Adjusting setting spf_entry");
$spf_entry = Settings::Get('spf.spf_entry');
if (!preg_match('/^v=spf[a-z0-9:~?\s.-]+$/i', $spf_entry)) {
Settings::Set('spf.spf_entry', 'v=spf1 a mx -all');
Update::lastStepStatus(1, 'corrected');
} else {
Update::lastStepStatus(0);
}
Froxlor::updateToVersion('2.1.0-rc2');
}
if (Froxlor::isDatabaseVersion('202305240')) {
Update::showUpdateStep("Adjusting file-template file extension setttings");
$current_fileextension = Settings::Get('system.index_file_extension');
Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup`= 'system' AND `varname`= 'index_file_extension'");
Database::query("ALTER TABLE `" . TABLE_PANEL_TEMPLATES . "` ADD `file_extension` varchar(50) NOT NULL default 'html';");
if (!empty(trim($current_fileextension)) && strtolower(trim($current_fileextension)) != 'html') {
$stmt = Database::prepare("UPDATE `" . TABLE_PANEL_TEMPLATES . "` SET `file_extension` = :ext WHERE `templategroup` = 'files'");
Database::pexecute($stmt, ['ext' => strtolower(trim($current_fileextension))]);
}
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202311260');
}
if (Froxlor::isFroxlorVersion('2.1.0-rc2')) {
Update::showUpdateStep("Updating from 2.1.0-rc2 to 2.1.0-rc3", false);
Froxlor::updateToVersion('2.1.0-rc3');
}
if (Froxlor::isDatabaseVersion('202311260')) {
$to_clean = array(
"install/updates/froxlor/update_2.x.inc.php",
"install/updates/preconfig/preconfig_2.x.inc.php",
"lib/Froxlor/Api/Commands/CustomerBackups.php",
"lib/Froxlor/Cli/Action",
"lib/Froxlor/Cli/Action.php",
"lib/Froxlor/Cli/CmdLineHandler.php",
"lib/Froxlor/Cli/ConfigServicesCmd.php",
"lib/Froxlor/Cli/PhpSessioncleanCmd.php",
"lib/Froxlor/Cli/SwitchServerIpCmd.php",
"lib/Froxlor/Cli/UpdateCliCmd.php",
"lib/Froxlor/Cron/System/BackupCron.php",
"lib/formfields/customer/extras/formfield.backup.php",
"lib/tablelisting/customer/tablelisting.backups.php",
"templates/Froxlor/assets/mix-manifest.json",
"templates/Froxlor/assets/css",
"templates/Froxlor/assets/webfonts",
"templates/Froxlor/assets/js/main.js",
"templates/Froxlor/assets/js/main.js.LICENSE.txt",
"templates/Froxlor/src",
"templates/Froxlor/user/change_language.html.twig",
"templates/Froxlor/user/change_password.html.twig",
"templates/Froxlor/user/change_theme.html.twig",
"tests/Backup/CustomerBackupsTest.php"
);
Update::cleanOldFiles($to_clean);
Froxlor::updateToDbVersion('202312050');
}
if (Froxlor::isFroxlorVersion('2.1.0-rc3')) {
Update::showUpdateStep("Updating from 2.1.0-rc3 to 2.1.0 stable", false);
Froxlor::updateToVersion('2.1.0');
}
if (Froxlor::isFroxlorVersion('2.1.0')) {
Update::showUpdateStep("Updating from 2.1.0 to 2.1.1", false);
Froxlor::updateToVersion('2.1.1');
}
if (Froxlor::isDatabaseVersion('202312050')) {
$to_clean = array(
"lib/configfiles/centos7.xml",
"lib/configfiles/centos8.xml",
"lib/configfiles/stretch.xml",
"lib/configfiles/xenial.xml",
"lib/configfiles/buster.xml",
"lib/configfiles/bionic.xml",
);
Update::cleanOldFiles($to_clean);
Froxlor::updateToDbVersion('202312100');
}
if (Froxlor::isDatabaseVersion('202312100')) {
Update::showUpdateStep("Adjusting table row format of larger tables");
Database::query("ALTER TABLE `" . TABLE_PANEL_ADMINS . "` ROW_FORMAT=DYNAMIC;");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` ROW_FORMAT=DYNAMIC;");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202312120');
}
if (Froxlor::isFroxlorVersion('2.1.1')) {
Update::showUpdateStep("Updating from 2.1.1 to 2.1.2", false);
Froxlor::updateToVersion('2.1.2');
}
if (Froxlor::isFroxlorVersion('2.1.2')) {
Update::showUpdateStep("Updating from 2.1.2 to 2.1.3", false);
Froxlor::updateToVersion('2.1.3');
}
if (Froxlor::isFroxlorVersion('2.1.3')) {
Update::showUpdateStep("Updating from 2.1.3 to 2.1.4", false);
Froxlor::updateToVersion('2.1.4');
}
if (Froxlor::isFroxlorVersion('2.1.4')) {
Update::showUpdateStep("Updating from 2.1.4 to 2.1.5", false);
Froxlor::updateToVersion('2.1.5');
}
if (Froxlor::isFroxlorVersion('2.1.5')) {
Update::showUpdateStep("Updating from 2.1.5 to 2.1.6", false);
Froxlor::updateToVersion('2.1.6');
}
if (Froxlor::isFroxlorVersion('2.1.6')) {
Update::showUpdateStep("Updating from 2.1.6 to 2.1.7", false);
Froxlor::updateToVersion('2.1.7');
}
if (Froxlor::isFroxlorVersion('2.1.7')) {
Update::showUpdateStep("Updating from 2.1.7 to 2.1.8", false);
Froxlor::updateToVersion('2.1.8');
}
if (Froxlor::isFroxlorVersion('2.1.8')) {
Update::showUpdateStep("Updating from 2.1.8 to 2.1.9", false);
Froxlor::updateToVersion('2.1.9');
}

View File

@@ -0,0 +1,262 @@
<?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\Database\DbManager;
use Froxlor\Froxlor;
use Froxlor\FroxlorLogger;
use Froxlor\Install\Update;
use Froxlor\Settings;
if (!defined('_CRON_UPDATE')) {
if (!defined('AREA') || (defined('AREA') && AREA != 'admin') || !isset($userinfo['loginname']) || (isset($userinfo['loginname']) && $userinfo['loginname'] == '')) {
header('Location: ../../../../index.php');
exit();
}
}
if (Froxlor::isFroxlorVersion('2.1.9')) {
Update::showUpdateStep("Enhancing virtual email table");
Database::query("ALTER TABLE `" . TABLE_MAIL_VIRTUAL . "` ADD `spam_tag_level` float(4,1) NOT NULL DEFAULT 7.0;");
Database::query("ALTER TABLE `" . TABLE_MAIL_VIRTUAL . "` ADD `spam_kill_level` float(4,1) NOT NULL DEFAULT 14.0;");
Database::query("ALTER TABLE `" . TABLE_MAIL_VIRTUAL . "` ADD `bypass_spam` tinyint(1) NOT NULL default '0';");
Database::query("ALTER TABLE `" . TABLE_MAIL_VIRTUAL . "` ADD `policy_greylist` tinyint(1) NOT NULL default '1';");
Update::lastStepStatus(0);
Update::showUpdateStep("Adjusting settings");
$antispam_activated = $_POST['antispam_activated'] ?? 0;
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `settinggroup` = 'antispam', `varname` = 'activated', `value` = '" . (int)$antispam_activated . "' WHERE `settinggroup` = 'dkim' AND `varname` = 'use_dkim';");
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `settinggroup` = 'antispam', `varname` = 'reload_command', `value` = 'service rspamd restart' WHERE `settinggroup` = 'dkim' AND `varname` = 'dkimrestart_command';");
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `settinggroup` = 'antispam', `varname` = 'config_file', `value` = '/etc/rspamd/local.d/froxlor_settings.conf' WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_prefix';");
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `settinggroup` = 'antispam' WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_keylength';");
Settings::AddNew("dmarc.use_dmarc", "0");
Settings::AddNew("dmarc.dmarc_entry", "v=DMARC1; p=none;");
Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'dkim' AND `varname` = 'privkeysuffix';");
Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_domains';");
Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_algorithm';");
Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_notes';");
Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_add_adsp';");
Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_dkimkeys';");
Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_servicetype';");
Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'dkim' AND `varname` = 'dkim_add_adsppolicy';");
Update::lastStepStatus(0);
if ($antispam_activated) {
Update::showUpdateStep("Converting existing domainkeys");
$sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `dkim` = '1' AND `dkim_pubkey` <> ''");
Database::pexecute($sel_stmt);
$upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `dkim_pubkey` = :pkey WHERE `id` = :did");
while ($domain = $sel_stmt->fetch(\PDO::FETCH_ASSOC)) {
$pubkey = trim(preg_replace(
'/-----BEGIN PUBLIC KEY-----(.+)-----END PUBLIC KEY-----/s',
'$1',
str_replace("\n", '', $domain['dkim_pubkey'])
));
Database::pexecute($upd_stmt, ['pkey' => $pubkey, 'did' => $domain['id']]);
}
Update::lastStepStatus(0);
Update::showUpdateStep("Configure antispam services");
$froxlorCliBin = Froxlor::getInstallDir() . '/bin/froxlor-cli';
$currentDistro = Settings::Get('system.distribution');
$manual_command = <<<EOC
{$froxlorCliBin} froxlor:config-services -a '{"http":"x","dns":"x","smtp":"x","mail":"x","antispam":"rspamd","ftp":"x","distro":"{$currentDistro}","system":[]}'
EOC;
Update::lastStepStatus(
1,
'manual action needed',
"Please run the following command manually as root:<br><pre>" . $manual_command . "</pre>"
);
} else {
Update::showUpdateStep("Removing existing domainkeys because antispam is disabled");
Database::query("UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `dkim` = '0', `dkim_id` = '0', `dkim_privkey` = '', `dkim_pubkey` = '' WHERE `dkim` = '1';");
Update::lastStepStatus(1, '!!!');
}
Update::showUpdateStep("Enhancing admin and user table");
Database::query("ALTER TABLE `" . TABLE_PANEL_ADMINS . "` ADD `gui_access` tinyint(1) NOT NULL default '1';");
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` ADD `gui_access` tinyint(1) NOT NULL default '1';");
Update::lastStepStatus(0);
$to_clean = [
'actions/admin/settings/180.dkim.php',
'actions/admin/settings/185.spf.php',
];
Update::cleanOldFiles($to_clean);
Froxlor::updateToDbVersion('202312230');
Froxlor::updateToVersion('2.2.0-dev1');
}
if (Froxlor::isDatabaseVersion('202312230')) {
Update::showUpdateStep("Adding new settings");
Settings::AddNew("system.le_renew_services", "");
Settings::AddNew("system.le_renew_hook", "systemctl restart postfix dovecot proftpd");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202401090');
}
if (Froxlor::isFroxlorVersion('2.2.0-dev1')) {
Update::showUpdateStep("Updating from 2.2.0-dev1 to 2.2.0-rc1", false);
Froxlor::updateToVersion('2.2.0-rc1');
}
if (Froxlor::isDatabaseVersion('202401090')) {
Update::showUpdateStep("Adding new table for 2fa tokens");
Database::query("DROP TABLE IF EXISTS `panel_2fa_tokens`;");
$sql = "CREATE TABLE `panel_2fa_tokens` (
`id` int(11) NOT NULL auto_increment,
`selector` varchar(20) NOT NULL,
`token` varchar(200) NOT NULL,
`userid` int(11) NOT NULL default '0',
`valid_until` int(15) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
Database::query($sql);
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202407200');
}
if (Froxlor::isFroxlorVersion('2.2.0-rc1')) {
Update::showUpdateStep("Updating from 2.2.0-rc1 to 2.2.0-rc2", false);
Froxlor::updateToVersion('2.2.0-rc2');
}
if (Froxlor::isFroxlorVersion('2.2.0-rc2')) {
Update::showUpdateStep("Updating from 2.2.0-rc2 to 2.2.0-rc3", false);
Froxlor::updateToVersion('2.2.0-rc3');
}
if (Froxlor::isDatabaseVersion('202407200')) {
Update::showUpdateStep("Adjusting field in 2fa-token table");
Database::query("ALTER TABLE `panel_2fa_tokens` CHANGE COLUMN `selector` `selector` varchar(200) NOT NULL;");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202408140');
}
if (Froxlor::isFroxlorVersion('2.2.0-rc3')) {
Update::showUpdateStep("Updating from 2.2.0-rc3 to 2.2.0 stable", false);
Froxlor::updateToVersion('2.2.0');
}
if (Froxlor::isFroxlorVersion('2.2.0')) {
Update::showUpdateStep("Updating from 2.2.0 to 2.2.1", false);
Froxlor::updateToVersion('2.2.1');
}
if (Froxlor::isDatabaseVersion('202408140')) {
Update::showUpdateStep("Adding new rewrite-subject field to email table");
Database::query("ALTER TABLE `" . TABLE_MAIL_VIRTUAL . "` ADD `rewrite_subject` tinyint(1) NOT NULL default '1' AFTER `spam_tag_level`;");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202409280');
}
if (Froxlor::isFroxlorVersion('2.2.1')) {
Update::showUpdateStep("Updating from 2.2.1 to 2.2.2", false);
Froxlor::updateToVersion('2.2.2');
}
if (Froxlor::isFroxlorVersion('2.2.2')) {
Update::showUpdateStep("Updating from 2.2.2 to 2.2.3", false);
Froxlor::updateToVersion('2.2.3');
}
if (Froxlor::isFroxlorVersion('2.2.3')) {
Update::showUpdateStep("Updating from 2.2.3 to 2.2.4", false);
Froxlor::updateToVersion('2.2.4');
}
if (Froxlor::isFroxlorVersion('2.2.4')) {
Update::showUpdateStep("Updating from 2.2.4 to 2.2.5", false);
Froxlor::updateToVersion('2.2.5');
}
if (Froxlor::isDatabaseVersion('202409280')) {
Update::showUpdateStep("Adding new antispam settings");
Settings::AddNew("antispam.default_bypass_spam", "2");
Settings::AddNew("antispam.default_spam_rewrite_subject", "1");
Settings::AddNew("antispam.default_policy_greylist", "1");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202411200');
}
if (Froxlor::isDatabaseVersion('202411200')) {
Update::showUpdateStep("Adjusting customer mysql global user");
// get all customers that are not deactivated and that have at least one database (hence a global database-user)
$customers = Database::query("
SELECT DISTINCT c.loginname, c.allowed_mysqlserver
FROM `" . TABLE_PANEL_CUSTOMERS . "` c
LEFT JOIN `" . TABLE_PANEL_DATABASES . "` d ON c.customerid = d.customerid
WHERE c.deactivated = '0' AND d.id IS NOT NULL
");
while ($customer = $customers->fetch(\PDO::FETCH_ASSOC)) {
$current_allowed_mysqlserver = !empty($customer['allowed_mysqlserver']) ? json_decode($customer['allowed_mysqlserver'], true) : [];
foreach ($current_allowed_mysqlserver as $dbserver) {
// require privileged access for target db-server
Database::needRoot(true, $dbserver, false);
// get DbManager
$dbm = new DbManager(FroxlorLogger::getInstanceOf());
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
if ($dbm->getManager()->userExistsOnHost($customer['loginname'], $mysql_access_host)) {
// deactivate temporarily
$dbm->getManager()->disableUser($customer['loginname'], $mysql_access_host);
// re-enable
$dbm->getManager()->enableUser($customer['loginname'], $mysql_access_host, true);
}
}
$dbm->getManager()->flushPrivileges();
Database::needRoot();
}
}
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202412030');
}
if (Froxlor::isFroxlorVersion('2.2.5')) {
Update::showUpdateStep("Updating from 2.2.5 to 2.2.6", false);
Froxlor::updateToVersion('2.2.6');
}
if (Froxlor::isFroxlorVersion('2.2.6')) {
Update::showUpdateStep("Updating from 2.2.6 to 2.2.7", false);
Froxlor::updateToVersion('2.2.7');
}
if (Froxlor::isFroxlorVersion('2.2.7')) {
Update::showUpdateStep("Updating from 2.2.7 to 2.2.8", false);
Froxlor::updateToVersion('2.2.8');
}

View File

@@ -34,7 +34,7 @@ $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).';
$question = '<strong>Validate DNS of domains when using Lets Encrypt&nbsp;';
$question = '<strong>Validate DNS of domains when using Lets Encrypt</strong>';
$return['system_le_domain_dnscheck'] = [
'type' => 'checkbox',
'value' => 1,

View File

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

View File

@@ -23,30 +23,21 @@
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
return [
'groups' => [
'spf' => [
'title' => lng('admin.spfsettings'),
'icon' => 'fa-solid fa-clipboard-check',
'fields' => [
'spf_use_spf' => [
'label' => lng('spf.use_spf'),
'settinggroup' => 'spf',
'varname' => 'use_spf',
'type' => 'checkbox',
'default' => false,
'save_method' => 'storeSettingField',
'overview_option' => true
],
'spf_spf_entry' => [
'label' => lng('spf.spf_entry'),
'settinggroup' => 'spf',
'varname' => 'spf_entry',
'type' => 'text',
'default' => '"v=spf1 a mx -all"',
'save_method' => 'storeSettingField'
]
]
]
]
use Froxlor\Froxlor;
use Froxlor\FileDir;
use Froxlor\Config\ConfigParser;
use Froxlor\Install\Update;
use Froxlor\Settings;
$preconfig = [
'title' => '2.1.x updates',
'fields' => []
];
$return = [];
if (Update::versionInUpdate($current_version, '2.1.0-dev1')) {
}
$preconfig['fields'] = $return;
return $preconfig;

View File

@@ -0,0 +1,48 @@
<?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\Install\Update;
$preconfig = [
'title' => '2.2.x updates',
'fields' => []
];
$return = [];
if (Update::versionInUpdate($current_version, '2.2.0-dev1')) {
$has_preconfig = true;
$description = 'Froxlor now features antispam configurations using rspamd. Would you like to enable the antispam feature (required re-configuration of services)?<br><strong>ATTENTION:</strong> When not enabled and the former DomainKey feature was used, keep in mind that all existing domainkeys for all domain are being removed and the dkim-flag disabled for the domains.';
$question = '<strong>Enable antispam (recommended)</strong>&nbsp;';
$return['antispam_activated'] = [
'type' => 'checkbox',
'value' => 1,
'checked' => 0,
'label' => $question,
'prior_infotext' => $description
];
}
$preconfig['fields'] = $return;
return $preconfig;

View File

@@ -53,7 +53,9 @@ 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'));
include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_2.2.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

@@ -193,7 +193,8 @@ class Ajax
UI::initTwig();
try {
$json_result = \Froxlor\Api\Commands\Froxlor::getLocal($this->userinfo)->checkUpdate();
$force = Request::get('force', 0);
$json_result = \Froxlor\Api\Commands\Froxlor::getLocal($this->userinfo, ['force' => $force])->checkUpdate();
$result = json_decode($json_result, true)['data'];
$result['full_version'] = Froxlor::getFullVersion();
$result['dbversion'] = Froxlor::DBVERSION;

View File

@@ -156,7 +156,7 @@ class GlobalSearch
],
'result_key' => 'domain_ace',
'result_format' => [
'title' => ['self', 'getFieldFromResult'],
'title' => ['\\Froxlor\\Ajax\\GlobalSearch', 'getFieldFromResult'],
'title_args' => 'domain_ace',
'href' => 'admin_domains.php?page=domains&searchfield=d.domain_ace&searchtext='
]
@@ -172,7 +172,7 @@ class GlobalSearch
'result_key' => 'ip',
'result_groupkey' => 'ip',
'result_format' => [
'title' => ['self', 'getFieldFromResult'],
'title' => ['\\Froxlor\\Ajax\\GlobalSearch', 'getFieldFromResult'],
'title_args' => 'ip',
'href' => 'admin_ipsandports.php?page=ipsandports&searchfield=ip&searchtext='
]
@@ -186,7 +186,7 @@ class GlobalSearch
],
'result_key' => 'id',
'result_format' => [
'title' => ['self', 'getFieldFromResult'],
'title' => ['\\Froxlor\\Ajax\\GlobalSearch', 'getFieldFromResult'],
'title_args' => 'name',
'href' => 'admin_plans.php?page=overview&searchfield=id&searchtext='
]
@@ -201,7 +201,7 @@ class GlobalSearch
],
'result_key' => 'id',
'result_format' => [
'title' => ['self', 'getFieldFromResult'],
'title' => ['\\Froxlor\\Ajax\\GlobalSearch', 'getFieldFromResult'],
'title_args' => 'description',
'href' => 'admin_phpsettings.php?page=overview&searchfield=id&searchtext='
]
@@ -215,7 +215,7 @@ class GlobalSearch
],
'result_key' => 'id',
'result_format' => [
'title' => ['self', 'getFieldFromResult'],
'title' => ['\\Froxlor\\Ajax\\GlobalSearch', 'getFieldFromResult'],
'title_args' => 'description',
'href' => 'admin_phpsettings.php?page=fpmdaemons&searchfield=id&searchtext='
]
@@ -234,7 +234,7 @@ class GlobalSearch
],
'result_key' => 'loginname',
'result_format' => [
'title' => ['self', 'getFieldFromResult'],
'title' => ['\\Froxlor\\Ajax\\GlobalSearch', 'getFieldFromResult'],
'title_args' => 'name',
'href' => 'admin_admins.php?page=admins&searchfield=loginname&searchtext='
]
@@ -252,7 +252,7 @@ class GlobalSearch
],
'result_key' => 'domain_ace',
'result_format' => [
'title' => ['self', 'getFieldFromResult'],
'title' => ['\\Froxlor\\Ajax\\GlobalSearch', 'getFieldFromResult'],
'title_args' => 'domain_ace',
'href' => 'customer_domains.php?page=domains&searchfield=d.domain_ace&searchtext='
]
@@ -266,7 +266,7 @@ class GlobalSearch
],
'result_key' => 'email',
'result_format' => [
'title' => ['self', 'getFieldFromResult'],
'title' => ['\\Froxlor\\Ajax\\GlobalSearch', 'getFieldFromResult'],
'title_args' => 'email',
'href' => 'customer_email.php?page=email_domain&domainid={domainid}&searchfield=m.email&searchtext='
]
@@ -279,7 +279,7 @@ class GlobalSearch
],
'result_key' => 'domain',
'result_format' => [
'title' => ['self', 'getFieldFromResult'],
'title' => ['\\Froxlor\\Ajax\\GlobalSearch', 'getFieldFromResult'],
'title_args' => 'domain',
'href' => 'customer_email.php?page=emails&searchfield=d.domain&searchtext='
]
@@ -293,7 +293,7 @@ class GlobalSearch
],
'result_key' => 'databasename',
'result_format' => [
'title' => ['self', 'getFieldFromResult'],
'title' => ['\\Froxlor\\Ajax\\GlobalSearch', 'getFieldFromResult'],
'title_args' => 'databasename',
'href' => 'customer_mysql.php?page=mysqls&searchfield=databasename&searchtext='
]
@@ -307,7 +307,7 @@ class GlobalSearch
],
'result_key' => 'username',
'result_format' => [
'title' => ['self', 'getFieldFromResult'],
'title' => ['\\Froxlor\\Ajax\\GlobalSearch', 'getFieldFromResult'],
'title_args' => 'username',
'href' => 'customer_ftp.php?page=accounts&searchfield=username&searchtext='
]

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

@@ -44,7 +44,7 @@ abstract class ApiParameter
*
* @throws Exception
*/
public function __construct(array $params = null)
public function __construct(?array $params = null)
{
if (!is_null($params)) {
$params = $this->trimArray($params);
@@ -91,7 +91,7 @@ abstract class ApiParameter
* @return mixed
* @throws Exception
*/
protected function getUlParam(string $param = null, string $ul_field = null, bool $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);
@@ -116,7 +116,7 @@ abstract class ApiParameter
* @return mixed
* @throws Exception
*/
protected function getParam(string $param = null, bool $optional = false, $default = '')
protected function getParam(?string $param = null, bool $optional = false, $default = '')
{
// does it exist?
if (!isset($this->cmd_params[$param])) {
@@ -183,7 +183,7 @@ abstract class ApiParameter
*
* @return string
*/
protected function getBoolParam(string $param = null, bool $optional = false, $default = false)
protected function getBoolParam(?string $param = null, bool $optional = false, $default = false)
{
$_default = '0';
if ($default) {

View File

@@ -140,12 +140,18 @@ class Admins extends ApiCommand implements ResourceEntity
* create a new admin user
*
* @param string $name
* required, name of the adminstrator
* @param string $email
* required, email address of the administrator
* @param string $new_loginname
* required, loginname/username of the administrator
* @param string $admin_password
* optional, default auto-generated
* @param string $def_language
* optional, default is system-default language
* * optional, ISO 639-1 language code (e.g. 'en', 'de', see lng-folder for supported languages),
* * default is system-default language
* @param bool $gui_access
* optional, allow login via webui, if false ONLY the login via webui is disallowed; default true
* @param bool $api_allowed
* optional, default is true if system setting api.enabled is true, else false
* @param string $custom_notes
@@ -219,6 +225,7 @@ class Admins extends ApiCommand implements ResourceEntity
// parameters
$def_language = $this->getParam('def_language', true, Settings::Get('panel.standardlanguage'));
$gui_access = $this->getBoolParam('gui_access', true, true);
$api_allowed = $this->getBoolParam('api_allowed', true, Settings::Get('api.enabled'));
$custom_notes = $this->getParam('custom_notes', true, '');
$custom_notes_show = $this->getBoolParam('custom_notes_show', true, 0);
@@ -280,6 +287,15 @@ class Admins extends ApiCommand implements ResourceEntity
'login' => $loginname
], true, true);
// Check for existing email address
// do not check via api as we skip any permission checks for this task
$email_check_admin_stmt = Database::prepare("
SELECT `email` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `email` = :email
");
$email_check_admin = Database::pexecute_first($email_check_admin_stmt, [
'email' => $email
], true, true);
if (($loginname_check && strtolower($loginname_check['loginname']) == strtolower($loginname)) || ($loginname_check_admin && strtolower($loginname_check_admin['loginname']) == strtolower($loginname))) {
Response::standardError('loginnameexists', $loginname, true);
} elseif (preg_match('/^' . preg_quote(Settings::Get('customer.accountprefix'), '/') . '([0-9]+)/', $loginname)) {
@@ -291,6 +307,8 @@ class Admins extends ApiCommand implements ResourceEntity
Response::standardError('loginnameiswrong', $loginname, true);
} elseif (!Validate::validateEmail($email)) {
Response::standardError('emailiswrong', $email, true);
} elseif ($email_check_admin && strtolower($email_check_admin['email']) == strtolower($email)) {
Response::standardError('emailexists', $email, true);
} else {
if ($customers_see_all != '1') {
$customers_see_all = '0';
@@ -316,6 +334,7 @@ class Admins extends ApiCommand implements ResourceEntity
'name' => $name,
'email' => $email,
'lang' => $def_language,
'gui_access' => $gui_access,
'api_allowed' => $api_allowed,
'change_serversettings' => $change_serversettings,
'customers' => $customers,
@@ -344,6 +363,7 @@ class Admins extends ApiCommand implements ResourceEntity
`name` = :name,
`email` = :email,
`def_language` = :lang,
`gui_access` = :gui_access,
`api_allowed` = :api_allowed,
`change_serversettings` = :change_serversettings,
`customers` = :customers,
@@ -430,7 +450,10 @@ class Admins extends ApiCommand implements ResourceEntity
* @param string $admin_password
* optional, default auto-generated
* @param string $def_language
* optional, default is system-default language
* * optional, ISO 639-1 language code (e.g. 'en', 'de', see lng-folder for supported languages),
* * default is system-default language
* @param bool $gui_access
* * optional, allow login via webui, if false ONLY the login via webui is disallowed; default true
* @param bool $api_allowed
* optional, default is true if system setting api.enabled is true, else false
* @param string $custom_notes
@@ -524,6 +547,7 @@ class Admins extends ApiCommand implements ResourceEntity
// you cannot edit some of the details of yourself
if ($result['adminid'] == $this->getUserDetail('adminid')) {
$gui_access = $result['gui_access'];
$api_allowed = $result['api_allowed'];
$deactivated = $result['deactivated'];
$customers = $result['customers'];
@@ -542,6 +566,7 @@ class Admins extends ApiCommand implements ResourceEntity
$traffic = $result['traffic'];
$ipaddress = ($result['ip'] != -1 ? json_decode($result['ip'], true) : -1);
} else {
$gui_access = $this->getBoolParam('gui_access', true, $result['gui_access']);
$api_allowed = $this->getBoolParam('api_allowed', true, $result['api_allowed']);
$deactivated = $this->getBoolParam('deactivated', true, $result['deactivated']);
@@ -596,8 +621,20 @@ class Admins extends ApiCommand implements ResourceEntity
'admin.email'
], '', true);
}
// Check for existing email address
// do not check via api as we skip any permission checks for this task
$email_check_admin_stmt = Database::prepare("
SELECT `email` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `email` = :email and `adminid` <> :adminid
");
$email_check_admin = Database::pexecute_first($email_check_admin_stmt, [
'email' => $email,
'adminid' => $id,
], true, true);
if (!Validate::validateEmail($email)) {
Response::standardError('emailiswrong', $email, true);
} elseif ($email_check_admin && strtolower($email_check_admin['email']) == strtolower($email)) {
Response::standardError('emailexists', $email, true);
} else {
if ($deactivated != '1') {
$deactivated = '0';
@@ -665,6 +702,7 @@ class Admins extends ApiCommand implements ResourceEntity
'name' => $name,
'email' => $email,
'lang' => $def_language,
'gui_access' => $gui_access,
'api_allowed' => $api_allowed,
'change_serversettings' => $change_serversettings,
'customers' => $customers,
@@ -694,6 +732,7 @@ class Admins extends ApiCommand implements ResourceEntity
`name` = :name,
`email` = :email,
`def_language` = :lang,
`gui_access` = :gui_access,
`api_allowed` = :api_allowed,
`change_serversettings` = :change_serversettings,
`customers` = :customers,

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, [
@@ -172,6 +171,7 @@ class Customers extends ApiCommand implements ResourceEntity
* create a new customer with default ftp-user and standard-subdomain (if wanted)
*
* @param string $email
* required, email address of new customer
* @param string $name
* optional if company is set, else required
* @param string $firstname
@@ -190,8 +190,11 @@ class Customers extends ApiCommand implements ResourceEntity
* optional
* @param int $customernumber
* optional
* @param string $def_language ,
* optional, default is system-default language
* @param string $def_language
* optional, ISO 639-1 language code (e.g. 'en', 'de', see lng-folder for supported languages),
* default is system-default language
* @param bool $gui_access
* optional, allow login via webui, if false ONLY the login via webui is disallowed; default true
* @param bool $api_allowed
* optional, default is true if system setting api.enabled is true, else false
* @param int $gender
@@ -272,7 +275,7 @@ class Customers extends ApiCommand implements ResourceEntity
* optional, specify a hosting-plan to set certain resource-values from the plan
* instead of specifying them
* @param array $allowed_mysqlserver
* optional, array of IDs of defined mysql-servers the customer is allowed to use,
* optional, array of IDs of defined mysql-servers the customer is allowed to use,
* default is to allow the default dbserver (id=0)
*
* @access admin
@@ -298,6 +301,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'));
$gui_access = $this->getBoolParam('gui_access', true, 1);
$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, '');
@@ -400,8 +404,15 @@ class Customers extends ApiCommand implements ResourceEntity
}
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
if (empty($allowed_phpconfigs) && $phpenabled == 1) {
// only required if not using mod_php
if ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) {
Response::standardError('customerphpenabledbutnoconfig', '', true);
}
}
$allowed_mysqlserver = array();
if (! empty($p_allowed_mysqlserver) && is_array($p_allowed_mysqlserver)) {
if (!empty($p_allowed_mysqlserver) && is_array($p_allowed_mysqlserver)) {
foreach ($p_allowed_mysqlserver as $allowed_ms) {
$allowed_ms = intval($allowed_ms);
$allowed_mysqlserver[] = $allowed_ms;
@@ -449,6 +460,28 @@ class Customers extends ApiCommand implements ResourceEntity
if (function_exists('posix_getpwnam') && !in_array("posix_getpwnam", explode(",", ini_get('disable_functions'))) && posix_getpwnam($loginname)) {
Response::standardError('loginnameissystemaccount', $loginname, true);
}
// blacklist some system-internal names that might lead to issues
Database::needSqlData();
$sqldata = Database::getSqlData();
Database::needRoot(true);
Database::needSqlData();
$sqlrdata = Database::getSqlData();
$login_blacklist = [
'root',
'admin',
'froxroot',
'froxlor',
$sqldata['user'],
$sqldata['db'],
$sqlrdata['user'],
];
unset($sqldata);
unset($sqlrdata);
$login_blacklist = array_unique($login_blacklist);
if (in_array($loginname, $login_blacklist)) {
Response::standardError('loginnameisreservedname', $loginname, true);
}
} else {
$accountnumber = intval(Settings::Get('system.lastaccountnumber')) + 1;
$loginname = Settings::Get('customer.accountprefix') . $accountnumber;
@@ -472,6 +505,15 @@ class Customers extends ApiCommand implements ResourceEntity
'login' => $loginname
], true, true);
// Check for existing email address
// do not check via api as we skip any permission checks for this task
$email_check_admin_stmt = Database::prepare("
SELECT `email` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `email` = :email
");
$email_check_admin = Database::pexecute_first($email_check_admin_stmt, [
'email' => $email
], true, true);
$mysql_maxlen = Database::getSqlUsernameLength() - strlen(Settings::Get('customer.mysqlprefix'));
if (($loginname_check && strtolower($loginname_check['loginname']) == strtolower($loginname)) || ($loginname_check_admin && strtolower($loginname_check_admin['loginname']) == strtolower($loginname))) {
Response::standardError('loginnameexists', $loginname, true);
@@ -481,6 +523,8 @@ class Customers extends ApiCommand implements ResourceEntity
} else {
Response::standardError('loginnameiswrong', $loginname, true);
}
} elseif ($email_check_admin && strtolower($email_check_admin['email']) == strtolower($email)) {
Response::standardError('emailexistsanon', $email, true);
}
$guid = intval(Settings::Get('system.lastguid')) + 1;
@@ -512,6 +556,7 @@ class Customers extends ApiCommand implements ResourceEntity
'email' => $email,
'customerno' => $customernumber,
'lang' => $def_language,
'gui_access' => $gui_access,
'api_allowed' => $api_allowed,
'docroot' => $documentroot,
'guid' => $guid,
@@ -554,6 +599,7 @@ class Customers extends ApiCommand implements ResourceEntity
`email` = :email,
`customernumber` = :customerno,
`def_language` = :lang,
`gui_access` = :gui_access,
`api_allowed` = :api_allowed,
`documentroot` = :docroot,
`guid` = :guid,
@@ -703,11 +749,12 @@ class Customers extends ApiCommand implements ResourceEntity
'adminid' => $this->getUserDetail('adminid'),
'docroot' => $documentroot,
'phpenabled' => $phpenabled,
'openbasedir' => '1'
'openbasedir' => '1',
'is_stdsubdomain' => 1
];
$domainid = -1;
try {
$std_domain = $this->apiCall('Domains.add', $ins_data);
$std_domain = $this->apiCall('Domains.add', $ins_data, true);
$domainid = $std_domain['id'];
} catch (Exception $e) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_ERR, "[API] Unable to add standard-subdomain: " . $e->getMessage());
@@ -726,6 +773,22 @@ class Customers extends ApiCommand implements ResourceEntity
}
}
// Create default mysql-user if enabled
if ($mysqls != 0) {
foreach ($allowed_mysqlserver as $dbserver) {
// require privileged access for target db-server
Database::needRoot(true, $dbserver, false);
// get DbManager
$dbm = new DbManager($this->logger());
// give permission to the user on every access-host we have
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
$dbm->getManager()->grantPrivilegesTo($loginname, $password, $mysql_access_host, false, false, true);
}
$dbm->getManager()->flushPrivileges();
Database::needRoot(false);
}
}
if ($sendpassword == '1') {
$srv_hostname = Settings::Get('system.hostname');
if (Settings::Get('system.froxlordirectlyviahostname') == '0') {
@@ -776,7 +839,7 @@ class Customers extends ApiCommand implements ResourceEntity
try {
$this->mailer()->Subject = $mail_subject;
$this->mailer()->AltBody = $mail_body;
$this->mailer()->msgHTML(str_replace("\n", "<br />", $mail_body));
$this->mailer()->Body = str_replace("\n", "<br />", $mail_body);
$this->mailer()->addAddress($email, User::getCorrectUserSalutation([
'firstname' => $firstname,
'name' => $name,
@@ -925,6 +988,7 @@ class Customers extends ApiCommand implements ResourceEntity
* @param string $loginname
* optional, the loginname
* @param string $email
* optional
* @param string $name
* optional if company is set, else required
* @param string $firstname
@@ -943,8 +1007,11 @@ class Customers extends ApiCommand implements ResourceEntity
* optional
* @param int $customernumber
* optional
* @param string $def_language ,
* optional, default is system-default language
* @param string $def_language
* * optional, ISO 639-1 language code (e.g. 'en', 'de', see lng-folder for supported languages),
* * default is system-default language
* @param bool $gui_access
* optional, allow login via webui, if false ONLY the login via webui is disallowed; default true
* @param bool $api_allowed
* optional, default is true if system setting api.enabled is true, else false
* @param int $gender
@@ -955,7 +1022,7 @@ class Customers extends ApiCommand implements ResourceEntity
* optional, whether to show the content of custom_notes to the customer, default 0
* (false)
* @param string $new_customer_password
* optional, iset new password
* optional, set new password
* @param bool $sendpassword
* optional, whether to send the password to the customer after creation, default 0
* (false)
@@ -1023,7 +1090,7 @@ class Customers extends ApiCommand implements ResourceEntity
* @param string $theme
* optional, change theme
* @param array $allowed_mysqlserver
* optional, array of IDs of defined mysql-servers the customer is allowed to use,
* optional, array of IDs of defined mysql-servers the customer is allowed to use,
* default is to allow the default dbserver (id=0)
*
* @access admin, customer
@@ -1050,7 +1117,7 @@ class Customers extends ApiCommand implements ResourceEntity
$email = $this->getParam('email', true, $idna_convert->decode($result['email']));
$name = $this->getParam('name', true, $result['name']);
$firstname = $this->getParam('firstname', true, $result['firstname']);
$company_required = empty($result['company']) && ((!empty($name) && empty($firstname)) || (empty($name) && !empty($firstname)) || (empty($name) && empty($firstname)));
$company_required = ((!empty($name) && empty($firstname)) || (empty($name) && !empty($firstname)) || (empty($name) && empty($firstname))) && empty($result['company']);
$company = $this->getParam('company', !$company_required, $result['company']);
$street = $this->getParam('street', true, $result['street']);
$zipcode = $this->getParam('zipcode', true, $result['zipcode']);
@@ -1059,6 +1126,7 @@ class Customers extends ApiCommand implements ResourceEntity
$fax = $this->getParam('fax', true, $result['fax']);
$customernumber = $this->getParam('customernumber', true, $result['customernumber']);
$def_language = $this->getParam('def_language', true, $result['def_language']);
$gui_access = $this->getBoolParam('gui_access', true, $result['gui_access']);
$api_allowed = $this->getBoolParam('api_allowed', true, $result['api_allowed']);
$gender = (int)$this->getParam('gender', true, $result['gender']);
$custom_notes = $this->getParam('custom_notes', true, $result['custom_notes']);
@@ -1110,14 +1178,21 @@ class Customers extends ApiCommand implements ResourceEntity
if (!empty($allowed_phpconfigs)) {
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
}
if (empty($allowed_phpconfigs) && $phpenabled == 1) {
// only required if not using mod_php
if ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 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)) {
$allowed_mysqlserver = $this->getParam('allowed_mysqlserver', true, [0]);
}
if (! empty($allowed_mysqlserver)) {
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);
@@ -1179,6 +1254,18 @@ class Customers extends ApiCommand implements ResourceEntity
], '', true);
} elseif (!Validate::validateEmail($email)) {
Response::standardError('emailiswrong', $email, true);
} else {
// Check for existing email address
// do not check via api as we skip any permission checks for this task
$email_check_admin_stmt = Database::prepare("
SELECT `email` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `email` = :email
");
$email_check_admin = Database::pexecute_first($email_check_admin_stmt, [
'email' => $email
], true, true);
if ($email_check_admin && strtolower($email_check_admin['email']) == strtolower($email)) {
Response::standardError('emailexistsanon', $email, true);
}
}
}
@@ -1272,12 +1359,34 @@ class Customers extends ApiCommand implements ResourceEntity
]);
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `deactivated`= :deactivated WHERE `customerid` = :customerid");
UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `deactivated`= :deactivated WHERE `customerid` = :customerid
");
Database::pexecute($upd_stmt, [
'deactivated' => $deactivated,
'customerid' => $id
]);
// enable/disable global mysql-user (loginname)
$current_allowed_mysqlserver = isset($result['allowed_mysqlserver']) && !empty($result['allowed_mysqlserver']) ? json_decode($result['allowed_mysqlserver'], true) : [];
foreach ($current_allowed_mysqlserver as $dbserver) {
// require privileged access for target db-server
Database::needRoot(true, $dbserver, true);
// get DbManager
$dbm = new DbManager($this->logger());
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
// Prevent access, if deactivated
if ($deactivated) {
// failsafe if user has been deleted manually (requires MySQL 4.1.2+)
$dbm->getManager()->disableUser($result['loginname'], $mysql_access_host);
} else {
// Otherwise grant access
$dbm->getManager()->enableUser($result['loginname'], $mysql_access_host, true);
}
}
$dbm->getManager()->flushPrivileges();
Database::needRoot(false);
}
// Retrieve customer's databases
$databases_stmt = Database::prepare("SELECT * FROM " . TABLE_PANEL_DATABASES . " WHERE customerid = :customerid ORDER BY `dbserver`");
Database::pexecute($databases_stmt, [
@@ -1298,9 +1407,7 @@ class Customers extends ApiCommand implements ResourceEntity
$last_dbserver = $row_database['dbserver'];
}
foreach (array_unique(explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
$mysql_access_host = trim($mysql_access_host);
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
// Prevent access, if deactivated
if ($deactivated) {
// failsafe if user has been deleted manually (requires MySQL 4.1.2+)
@@ -1389,6 +1496,7 @@ class Customers extends ApiCommand implements ResourceEntity
'logviewenabled' => $logviewenabled,
'custom_notes' => $custom_notes,
'custom_notes_show' => $custom_notes_show,
'gui_access' => $gui_access,
'api_allowed' => $api_allowed,
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver)
];
@@ -1432,6 +1540,7 @@ class Customers extends ApiCommand implements ResourceEntity
`logviewenabled` = :logviewenabled,
`custom_notes` = :custom_notes,
`custom_notes_show` = :custom_notes_show,
`gui_access` = :gui_access,
`api_allowed` = :api_allowed,
`allowed_mysqlserver` = :allowed_mysqlserver";
$upd_query .= $admin_upd_query;
@@ -1589,6 +1698,21 @@ class Customers extends ApiCommand implements ResourceEntity
]);
$id = $result['customerid'];
// remove global mysql-user (loginname)
$current_allowed_mysqlserver = isset($result['allowed_mysqlserver']) && !empty($result['allowed_mysqlserver']) ? json_decode($result['allowed_mysqlserver'], true) : [];
foreach ($current_allowed_mysqlserver as $dbserver) {
// require privileged access for target db-server
Database::needRoot(true, $dbserver, false);
// get DbManager
$dbm = new DbManager($this->logger());
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
$dbm->getManager()->deleteUser($result['loginname'], $mysql_access_host);
}
$dbm->getManager()->flushPrivileges();
Database::needRoot(false);
}
// remove all databases
$databases_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_DATABASES . "`
WHERE `customerid` = :id ORDER BY `dbserver`
@@ -1604,8 +1728,8 @@ class Customers extends ApiCommand implements ResourceEntity
$priv_changed = false;
while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) {
if ($last_dbserver != $row_database['dbserver']) {
Database::needRoot(true, $row_database['dbserver']);
$dbm->getManager()->flushPrivileges();
Database::needRoot(true, $row_database['dbserver']);
$last_dbserver = $row_database['dbserver'];
}
$dbm->getManager()->deleteDatabase($row_database['databasename']);

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

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

View File

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

View File

@@ -115,7 +115,7 @@ class DomainZones extends ApiCommand implements ResourceEntity
// validation
$errors = [];
if (empty($record)) {
if (empty(trim($record))) {
$record = "@";
}
@@ -178,7 +178,7 @@ class DomainZones extends ApiCommand implements ResourceEntity
}
}
} elseif ($type == 'CAA' && !empty($content)) {
$re = '/(?\'critical\'\d)\h*(?\'type\'iodef|issue|issuewild)\h*(?\'value\'(?\'issuevalue\'"(?\'domain\'(?=.{3,128}$)(?>(?>[a-zA-Z0-9]+[a-zA-Z0-9-]*[a-zA-Z0-9]+|[a-zA-Z0-9]+)\.)*(?>[a-zA-Z]{2,}|[a-zA-Z0-9]{2,}\.[a-zA-Z]{2,}))[;\h]*(?\'parameters\'(?>[a-zA-Z0-9]{1,60}=[a-zA-Z0-9]{1,60}\h*)+)?")|(?\'iodefvalue\'"(?\'url\'(mailto:.*|http:\/\/.*|https:\/\/.*))"))/';
$re = '/(?\'critical\'\d+)\h*(?\'type\'iodef|issue|issuewild)\h*(?\'value\'(?\'issuevalue\'"(?\'domain\'(?=.{3,128}$)(?>(?>[a-zA-Z0-9]+[a-zA-Z0-9-]*[a-zA-Z0-9]+|[a-zA-Z0-9]+)\.)*(?>[a-zA-Z]{2,}|[a-zA-Z0-9]{2,}\.[a-zA-Z]{2,}))[;\h]*(?\'parameters\'(?>[a-zA-Z0-9]{1,60}=[a-zA-Z0-9:\.\/\-]{1,60}\h*)+)?")|(?\'iodefvalue\'"(?\'url\'(mailto:.*|http:\/\/.*|https:\/\/.*))"))/';
preg_match($re, $content, $matches);
if (empty($matches)) {
@@ -227,7 +227,7 @@ class DomainZones extends ApiCommand implements ResourceEntity
// remove it for checks
$content = substr($content, 0, -1);
}
if (!Validate::validateDomain($content)) {
if (!empty($content) && !Validate::validateDomain($content)) {
$errors[] = lng('error.dns_mx_needdom');
} else {
// check whether there is a CNAME-record for the same resource
@@ -244,6 +244,10 @@ class DomainZones extends ApiCommand implements ResourceEntity
}
// append trailing dot (again)
$content .= '.';
// if content is only ".", the prio needs to be 0 which results in a "null mx" entry
if ($content == '.' && $prio != 0) {
$prio = 0;
}
} elseif ($type == 'NS') {
// check for trailing dot
if (substr($content, -1) == '.') {
@@ -302,6 +306,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);

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
@@ -204,13 +201,13 @@ class Domains extends ApiCommand implements ResourceEntity
* @param string $zonefile
* optional, custom dns zone filename (only of nameserver is activated), default empty (auto-generated)
* @param bool $dkim
* optional, currently not in use, default 0 (false)
* optional, whether this domain should use dkim if antispam is activated, default 0 (false)
* @param string $specialsettings
* optional, custom webserver vhost-content which is added to the generated vhost, default empty
* @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,7 +216,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)
@@ -244,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
@@ -252,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
@@ -263,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
@@ -277,7 +274,8 @@ class Domains extends ApiCommand implements ResourceEntity
* $override_tls is true
* @param string $description
* optional custom description (currently not used/shown in the frontend), default empty
*
* @param bool $is_stdsubdomain (internally)
* optional whether this is a standard subdomain for a customer which is being added so no usage is decreased
* @access admin
* @return string json-encoded array
* @throws Exception
@@ -285,7 +283,8 @@ class Domains extends ApiCommand implements ResourceEntity
public function add()
{
if ($this->isAdmin()) {
if ($this->getUserDetail('domains_used') < $this->getUserDetail('domains') || $this->getUserDetail('domains') == '-1') {
$is_stdsubdomain = $this->isInternal() ? $this->getBoolParam('is_stdsubdomain', true, 0) : false;
if ($is_stdsubdomain || $this->getUserDetail('domains_used') < $this->getUserDetail('domains') || $this->getUserDetail('domains') == '-1') {
// parameters
$p_domain = $this->getParam('domain');
@@ -298,7 +297,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);
@@ -320,9 +318,9 @@ class Domains extends ApiCommand implements ResourceEntity
$mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, -1);
$ssl_redirect = $this->getBoolParam('ssl_redirect', true, 0);
$letsencrypt = $this->getBoolParam('letsencrypt', true, 0);
$sslenabled = $this->getBoolParam('sslenabled', true, 1);
$dont_use_default_ssl_ipandport_if_empty = $this->getBoolParam('dont_use_default_ssl_ipandport_if_empty', true, 0);
$p_ssl_ipandports = $this->getParam('ssl_ipandport', true, $dont_use_default_ssl_ipandport_if_empty ? [] : explode(',', Settings::Get('system.defaultsslip')));
$sslenabled = $this->getBoolParam('sslenabled', true, 1);
$http2 = $this->getBoolParam('http2', true, 0);
$hsts_maxage = $this->getParam('hsts_maxage', true, 0);
$hsts_sub = $this->getBoolParam('hsts_sub', true, 0);
@@ -353,6 +351,8 @@ class Domains extends ApiCommand implements ResourceEntity
if (substr($p_domain, 0, 4) == 'xn--') {
Response::standardError('domain_nopunycode', '', true);
} elseif (Validate::validate_ip2($p_domain, true, '', true, true)) {
Response::standardError('domain_noipaddress', '', true);
}
$idna_convert = new IdnaWrapper();
@@ -474,7 +474,6 @@ class Domains extends ApiCommand implements ResourceEntity
}
$caneditdomain = '1';
$zonefile = '';
$dkim = '0';
$specialsettings = '';
$ssl_specialsettings = '';
$include_specialsettings = 0;
@@ -521,7 +520,8 @@ class Domains extends ApiCommand implements ResourceEntity
$mod_fcgid_maxrequests = '-1';
}
} else {
$phpenabled = '1';
// set default to whether the customer has php enabled or not
$phpenabled = $customer['phpenabled'];
$openbasedir = '1';
if ((int)Settings::Get('phpfpm.enabled') == 1) {
@@ -548,6 +548,13 @@ class Domains extends ApiCommand implements ResourceEntity
$ssl_specialsettings = Validate::validate(str_replace("\r\n", "\n", $ssl_specialsettings), 'ssl_specialsettings', '/^[^\0]*$/', '', [], true);
}
}
if (Settings::Get('system.use_ssl') == "1" && $sslenabled == 1 && empty($ssl_ipandports)) {
// if this is a customer standard-subdomain, we simply ignore this and disable ssl-related settings (see if-statement below)
if (!$is_stdsubdomain) {
// enabled ssl for the domain but no ssl ip/port is selected
Response::standardError('nosslippportgiven', '', true);
}
}
if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) {
$ssl_redirect = 0;
$letsencrypt = 0;
@@ -588,12 +595,18 @@ class Domains extends ApiCommand implements ResourceEntity
$ssl_redirect = 2;
}
if (!preg_match('/^https?\:\/\//', $documentroot)) {
if (strstr($documentroot, ":") !== false) {
Response::standardError('pathmaynotcontaincolon', '', true);
} else {
$documentroot = FileDir::makeCorrectDir($documentroot);
// Check if given documentroot is either a valid URL or a valid path
if (preg_match('/^https?\:\/\//', $documentroot)) {
$encoded = $idna_convert->encode($documentroot);
if (!Validate::validateUrl($encoded, true)) {
Response::standardError('invaliddocumentrooturl', '', true);
}
$documentroot = $encoded;
} else {
if (strpos($documentroot, ':') !== false) {
Response::standardError('pathmaynotcontaincolon', '', true);
}
$documentroot = FileDir::makeCorrectDir($documentroot);
}
$domain_check_stmt = Database::prepare("
@@ -665,10 +678,6 @@ class Domains extends ApiCommand implements ResourceEntity
$serveraliasoption = '0';
}
if ($issubof <= 0) {
$issubof = '0';
}
$idna_convert = new IdnaWrapper();
if ($domain == '') {
Response::standardError([
@@ -723,7 +732,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,
@@ -777,7 +785,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,
@@ -798,12 +805,15 @@ class Domains extends ApiCommand implements ResourceEntity
$ins_data['id'] = $domainid;
unset($ins_data);
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` + 1
WHERE `adminid` = :adminid");
Database::pexecute($upd_stmt, [
'adminid' => $adminid
], true, true);
if (!$is_stdsubdomain) {
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` + 1
WHERE `adminid` = :adminid
");
Database::pexecute($upd_stmt, [
'adminid' => $adminid
], true, true);
}
$ins_stmt = Database::prepare("
INSERT INTO `" . TABLE_DOMAINTOIP . "` SET
@@ -834,6 +844,9 @@ class Domains extends ApiCommand implements ResourceEntity
Cronjob::inserttask(TaskId::REBUILD_VHOST);
// Using nameserver, insert a task which rebuilds the server config
Cronjob::inserttask(TaskId::REBUILD_DNS);
if ($dkim == '1') {
Cronjob::inserttask(TaskId::REBUILD_RSPAMD);
}
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] added domain '" . $domain . "'");
@@ -1058,6 +1071,9 @@ class Domains extends ApiCommand implements ResourceEntity
* (default yes), 3 = always, default 0 (never)
* @param bool $isemaildomain
* optional, allow email usage with this domain, default 0 (false)
* @param bool $emaildomainverified
* optional, when setting $isemaildomain to false, this needs to be set to true to confirm the action in case email addresses exist for this domain,
* default 0 (false)
* @param bool $email_only
* optional, restrict domain to email usage, default 0 (false)
* @param int $selectserveralias
@@ -1069,9 +1085,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
@@ -1083,13 +1096,13 @@ class Domains extends ApiCommand implements ResourceEntity
* @param string $zonefile
* optional, custom dns zone filename (only of nameserver is activated), default empty (auto-generated)
* @param bool $dkim
* optional, currently not in use, default 0 (false)
* optional, whether this domain should use dkim if antispam is activated, default 0 (false)
* @param string $specialsettings
* optional, custom webserver vhost-content which is added to the generated vhost, default empty
* @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
@@ -1101,7 +1114,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)
@@ -1130,7 +1143,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
@@ -1138,9 +1151,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
@@ -1150,6 +1163,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
@@ -1186,12 +1201,12 @@ class Domains extends ApiCommand implements ResourceEntity
$subcanemaildomain = $this->getParam('subcanemaildomain', true, $result['subcanemaildomain']);
$isemaildomain = $this->getBoolParam('isemaildomain', true, $result['isemaildomain']);
$emaildomainverified = $this->getBoolParam('emaildomainverified', true, 0);
$email_only = $this->getBoolParam('email_only', true, $result['email_only']);
$p_serveraliasoption = $this->getParam('selectserveralias', true, -1);
$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']);
@@ -1219,7 +1234,7 @@ class Domains extends ApiCommand implements ResourceEntity
$p_ssl_ipandports = $this->getParam('ssl_ipandport', true, $remove_ssl_ipandport ? [
-1
] : null);
$sslenabled = $this->getBoolParam('sslenabled', true, $result['ssl_enabled']);
$sslenabled = $remove_ssl_ipandport ? false : $this->getBoolParam('sslenabled', true, $result['ssl_enabled']);
$http2 = $this->getBoolParam('http2', true, $result['http2']);
$hsts_maxage = $this->getParam('hsts_maxage', true, $result['hsts']);
$hsts_sub = $this->getBoolParam('hsts_sub', true, $result['hsts_sub']);
@@ -1246,6 +1261,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("
@@ -1269,7 +1285,7 @@ class Domains extends ApiCommand implements ResourceEntity
// count where we are used in email-accounts
$domain_emails_result_stmt = Database::prepare("
SELECT `email`, `email_full`, `destination`, `popaccountid` AS `number_email_forwarders`
SELECT `email`, `email_full`, `destination`, `popaccountid`
FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `customerid` = :customerid AND `domainid` = :id
");
Database::pexecute($domain_emails_result_stmt, [
@@ -1292,6 +1308,10 @@ class Domains extends ApiCommand implements ResourceEntity
}
}
if ($emails > 0 && (int)$isemaildomain == 0 && (int)$result['isemaildomain'] == 1 && (int)$emaildomainverified == 0) {
Response::standardError('emaildomainstillhasaddresses', '', true);
}
// handle change of customer (move domain from customer to customer)
if ($customerid > 0 && $customerid != $result['customerid'] && Settings::Get('panel.allow_domain_change_customer') == '1') {
// check whether target customer has enough resources
@@ -1400,10 +1420,6 @@ class Domains extends ApiCommand implements ResourceEntity
}
}
if (!preg_match('/^https?\:\/\//', $documentroot) && strstr($documentroot, ":") !== false) {
Response::standardError('pathmaynotcontaincolon', '', true);
}
if ($this->getUserDetail('change_serversettings') == '1') {
if (Settings::Get('system.bind_enable') == '1') {
$zonefile = Validate::validate($zonefile, 'zonefile', '', '', [], true);
@@ -1412,7 +1428,7 @@ class Domains extends ApiCommand implements ResourceEntity
$zonefile = $result['zonefile'];
}
if (Settings::Get('dkim.use_dkim') != '1') {
if (Settings::Get('antispam.activated') != '1') {
$dkim = $result['dkim'];
}
@@ -1448,7 +1464,6 @@ class Domains extends ApiCommand implements ResourceEntity
} else {
$isbinddomain = $result['isbinddomain'];
$zonefile = $result['zonefile'];
$dkim = $result['dkim'];
$specialsettings = $result['specialsettings'];
$ssl_specialsettings = $result['ssl_specialsettings'];
$include_specialsettings = $result['include_specialsettings'];
@@ -1528,13 +1543,16 @@ class Domains extends ApiCommand implements ResourceEntity
if ($remove_ssl_ipandport || (!empty($p_ssl_ipandports) && $p_ssl_ipandports[0] == -1)) {
$ssl_ipandports = [];
}
if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) {
if (Settings::Get('system.use_ssl') == "1" && $sslenabled && empty($ssl_ipandports)) {
// enabled ssl for the domain but no ssl ip/port is selected
Response::standardError('nosslippportgiven', '', true);
}
if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports) || !$sslenabled) {
$ssl_redirect = 0;
$letsencrypt = 0;
$http2 = 0;
// we need this for the json_encode
// if ssl is disabled or no ssl-ip/port exists
$ssl_ipandports[] = -1;
// act like $remove_ssl_ipandport
$ssl_ipandports = [];
// HSTS
$hsts_maxage = 0;
@@ -1564,15 +1582,25 @@ class Domains extends ApiCommand implements ResourceEntity
}
// Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated
if ($ssl_redirect > 0 && $letsencrypt == 1 && $result['letsencrypt'] != $letsencrypt) {
if ($result['letsencrypt'] != $letsencrypt && $ssl_redirect > 0 && $letsencrypt == 1) {
$ssl_redirect = 2;
}
if (!preg_match('/^https?\:\/\//', $documentroot)) {
if ($documentroot != $result['documentroot']) {
$idna_convert = new IdnaWrapper();
if ($documentroot != $result['documentroot']) {
if (preg_match('/^https?\:\/\//', $documentroot)) {
$encoded = $idna_convert->encode($documentroot);
if (!Validate::validateUrl($encoded, true)) {
Response::standardError('invaliddocumentrooturl', '', true);
}
$documentroot = $encoded;
} else {
if (substr($documentroot, 0, 1) != "/") {
$documentroot = $customer['documentroot'] . '/' . $documentroot;
}
if (strpos($documentroot, ':') !== false) {
Response::standardError('pathmaynotcontaincolon', '', true);
}
$documentroot = FileDir::makeCorrectDir($documentroot);
}
}
@@ -1640,10 +1668,6 @@ class Domains extends ApiCommand implements ResourceEntity
Response::standardError('domainisaliasorothercustomer', '', true);
}
if ($issubof <= 0) {
$issubof = '0';
}
if ($serveraliasoption != '1' && $serveraliasoption != '2') {
$serveraliasoption = '0';
}
@@ -1657,6 +1681,7 @@ class Domains extends ApiCommand implements ResourceEntity
|| $iswildcarddomain != $result['iswildcarddomain']
|| $phpenabled != $result['phpenabled']
|| $openbasedir != $result['openbasedir']
|| $openbasedir_path != $result['openbasedir_path']
|| $phpsettingid != $result['phpsettingid']
|| $mod_fcgid_starter != $result['mod_fcgid_starter']
|| $mod_fcgid_maxrequests != $result['mod_fcgid_maxrequests']
@@ -1666,7 +1691,6 @@ class Domains extends ApiCommand implements ResourceEntity
|| $writeaccesslog != $result['writeaccesslog']
|| $writeerrorlog != $result['writeerrorlog']
|| $aliasdomain != $result['aliasdomain']
|| $issubof != $result['ismainbutsubto']
|| $email_only != $result['email_only']
|| ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1')
|| $letsencrypt != $result['letsencrypt']
@@ -1675,10 +1699,15 @@ class Domains extends ApiCommand implements ResourceEntity
|| $hsts_sub != $result['hsts_sub']
|| $hsts_preload != $result['hsts_preload']
|| $ocsp_stapling != $result['ocsp_stapling']
|| $sslenabled != $result['ssl_enabled']
) {
Cronjob::inserttask(TaskId::REBUILD_VHOST);
}
if ($dkim != $result['dkim']) {
Cronjob::inserttask(TaskId::REBUILD_RSPAMD);
}
if ($speciallogfile != $result['speciallogfile'] && $speciallogverified != '1') {
$speciallogfile = $result['speciallogfile'];
}
@@ -1823,7 +1852,7 @@ class Domains extends ApiCommand implements ResourceEntity
$update_data['wwwserveralias'] = $wwwserveralias;
$update_data['iswildcarddomain'] = $iswildcarddomain;
$update_data['phpenabled'] = $phpenabled;
$update_data['openbasedir'] = $openbasedir;;
$update_data['openbasedir'] = $openbasedir;
$update_data['openbasedir_path'] = $openbasedir_path;
$update_data['speciallogfile'] = $speciallogfile;
$update_data['phpsettingid'] = $phpsettingid;
@@ -1837,7 +1866,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;
@@ -1852,6 +1880,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("
@@ -1885,7 +1914,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,
@@ -1899,11 +1927,36 @@ 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;
@@ -1921,6 +1974,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 = '';
@@ -1953,7 +2007,8 @@ 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
");
@@ -2056,7 +2111,6 @@ class Domains extends ApiCommand implements ResourceEntity
}
}
$idna_convert = new IdnaWrapper();
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] updated domain '" . $idna_convert->decode($result['domain']) . "'");
$result = $this->apiCall('Domains.get', [
'domainname' => $result['domain']
@@ -2073,12 +2127,11 @@ 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
* @param bool $delete_userfiles
* optional, delete email account files on filesystem (if any), default false
*
* @access admin
* @return string json-encoded array
@@ -2090,8 +2143,8 @@ class Domains extends ApiCommand implements ResourceEntity
$id = $this->getParam('id', true, 0);
$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);
$is_stdsubdomain = $this->getBoolParam('is_stdsubdomain', true, 0);
$delete_user_emailfiles = $this->getBoolParam('delete_userfiles', true, 0);
$result = $this->apiCall('Domains.get', [
'id' => $id,
@@ -2099,15 +2152,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);
@@ -2120,6 +2168,14 @@ class Domains extends ApiCommand implements ResourceEntity
$idString = implode(' OR ', $idString);
if ($idString != '') {
if ($delete_user_emailfiles) {
// determine all connected email-accounts
$emailaccount_sel = Database::prepare("SELECT `email`, `homedir`, `maildir` FROM `" . TABLE_MAIL_USERS . "` WHERE " . $idString);
Database::pexecute($emailaccount_sel, $paramString, true, true);
while ($emailacc_row = $emailaccount_sel->fetch(PDO::FETCH_ASSOC)) {
Cronjob::inserttask(TaskId::DELETE_EMAIL_DATA, $emailacc_row['email'], FileDir::makeCorrectDir($emailacc_row['homedir'] . '/' . $emailacc_row['maildir']));
}
}
$del_stmt = Database::prepare("
DELETE FROM `" . TABLE_MAIL_USERS . "` WHERE " . $idString);
Database::pexecute($del_stmt, $paramString, true, true);
@@ -2129,23 +2185,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);
@@ -2230,4 +2273,118 @@ class Domains extends ApiCommand implements ResourceEntity
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* duplicate domain entry by either id or domainname. All parameters from Domains.add() can be used
* to overwrite source entity values if necessary.
*
* @param int $id
* optional, the domain-id
* @param string $domainname
* optional, the domainname
* @param string $domain
* required, name of the new domain to be added
*
* @access admin
* @return string json-encoded array
* @throws Exception
*/
public function duplicate()
{
if ($this->isAdmin()) {
// parameters
$id = $this->getParam('id', true, 0);
$dn_optional = $id > 0;
$domainname = $this->getParam('domainname', $dn_optional, '');
$p_domain = $this->getParam('domain');
// get requested domain
$result = $this->apiCall('Domains.get', [
'id' => $id,
'domainname' => $domainname,
]);
// clear some defaults
unset($result['domain_ace']);
unset($result['adminid']);
unset($result['documentroot']);
unset($result['registration_date']);
unset($result['termination_date']);
unset($result['zonefile']);
// clear auto-generated values
unset($result['bindserial']);
unset($result['dkim_privkey']);
unset($result['dkim_pubkey']);
// clear api-call generated fields
unset($result['domain_hascert']);
// set correct ip/port information
$domain_ips = $result['ipsandports'];
unset($result['ipsandports']);
$result['ipandport'] = [];
$result['ssl_ipandport'] = [];
foreach ($domain_ips as $dip) {
if ($dip['ssl'] == 1) {
$result['ssl_ipandport'][] = $dip['id'];
} else {
$result['ipandport'][] = $dip['id'];
}
}
// check whether we are changing the customer/owner
if ($this->getParam('customerid', true, 0) == 0 && $this->getParam('loginname', true, '') == '') {
$customerid = $result['customerid'];
} else {
$customer = $this->getCustomerData();
$customerid = $customer['customerid'];
}
// check for alias-domain and whether it belongs to the target user
if (!empty($result['aliasdomain']) && $customerid == $result['customerid']) {
// duplicate alias entry
$result['alias'] = $result['aliasdomain'];
}
unset($result['aliasdomain']);
// validate possible fpm configs and whether the customer is allowed to use them
if ($customerid != $result['customerid']) {
$allowed_phpconfigs = json_decode($customer['allowed_phpconfigs'] ?? '[]', true);
if (empty($allowed_phpconfigs)) {
// system defaults
unset($result['phpsettingid']);
} elseif (!in_array($result['phpsettingid'], $allowed_phpconfigs)) {
// use the first customer allowed config
$result['phpsettingid'] = array_shift($allowed_phpconfigs);
}
}
// translate serveralias values
$result['selectserveralias'] = 2;
if ((int)$result['wwwserveralias'] == 1) {
$result['selectserveralias'] = 1;
} elseif ((int)$result['iswildcarddomain'] == 1) {
$result['selectserveralias'] = 0;
}
unset($result['wwwserveralias']);
unset($result['iswildcarddomain']);
// translate sslenabled flag
$result['sslenabled'] = $result['ssl_enabled'];
unset($result['ssl_enabled']);
$additional_params = $this->getParamList();
// unset unneeded params from this call
unset($additional_params['id']);
unset($additional_params['domainname']);
unset($additional_params['domain']);
// set new values and merge with optional add() parameters
$new_domain = array_merge($result, $additional_params);
$new_domain['domain'] = $p_domain;
$result_new = $this->apiCall('Domains.add', $new_domain);
return $this->response($result_new);
}
throw new Exception("Not allowed to execute given command.", 403);
}
}

View File

@@ -95,9 +95,13 @@ 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();
@@ -153,10 +157,10 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
// prefix hash-algo
switch (Settings::Get('system.passwordcryptfunc')) {
case PASSWORD_ARGON2I:
case 'argon2i':
$cpPrefix = '{ARGON2I}';
break;
case PASSWORD_ARGON2ID:
case 'argon2id':
$cpPrefix = '{ARGON2ID}';
break;
default:
@@ -256,10 +260,12 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$_mailerror = false;
$mailerr_msg = "";
try {
$this->mailer()->setFrom($admin['email'], User::getCorrectUserSalutation($admin));
$this->mailer()->setFrom(Settings::Get('panel.adminmail'), User::getCorrectUserSalutation($admin));
$this->mailer()->clearReplyTos();
$this->mailer()->addReplyTo($admin['email'], User::getCorrectUserSalutation($admin));
$this->mailer()->Subject = $mail_subject;
$this->mailer()->AltBody = $mail_body;
$this->mailer()->msgHTML(str_replace("\n", "<br />", $mail_body));
$this->mailer()->Body = str_replace("\n", "<br />", $mail_body);
$this->mailer()->addAddress($email_full);
$this->mailer()->send();
} catch (\PHPMailer\PHPMailer\Exception $e) {
@@ -286,7 +292,9 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$_mailerror = false;
try {
$this->mailer()->setFrom($admin['email'], User::getCorrectUserSalutation($admin));
$this->mailer()->setFrom(Settings::Get('panel.adminmail'), User::getCorrectUserSalutation($admin));
$this->mailer()->clearReplyTos();
$this->mailer()->addReplyTo($admin['email'], User::getCorrectUserSalutation($admin));
$this->mailer()->Subject = $mail_subject;
$this->mailer()->AltBody = $mail_body;
$this->mailer()->msgHTML(str_replace("\n", "<br />", $mail_body));
@@ -400,10 +408,10 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$password = Crypt::validatePassword($password, true);
// prefix hash-algo
switch (Settings::Get('system.passwordcryptfunc')) {
case PASSWORD_ARGON2I:
case 'argon2i':
$cpPrefix = '{ARGON2I}';
break;
case PASSWORD_ARGON2ID:
case 'argon2id':
$cpPrefix = '{ARGON2ID}';
break;
default:
@@ -519,7 +527,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
$result = $this->apiCall('Emails.get', [
'id' => $id,
'emailaddr' => $emailaddr
]);
], true);
$id = $result['id'];
if (empty($result['popaccountid']) || $result['popaccountid'] == 0) {
@@ -559,7 +567,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
}
if ($delete_userfiles) {
Cronjob::inserttask(TaskId::DELETE_EMAIL_DATA, $customer['loginname'], $result['email_full']);
Cronjob::inserttask(TaskId::DELETE_EMAIL_DATA, $customer['loginname'], FileDir::makeCorrectDir($result['homedir'] . '/' . $result['maildir']));
}
// decrease usage for customer

View File

@@ -69,7 +69,7 @@ class EmailDomains extends ApiCommand implements ResourceEntity
$result = [];
$query_fields = [];
$result_stmt = Database::prepare("
SELECT DISTINCT d.domain, e.domainid,
SELECT DISTINCT d.domain, d.domain_ace, e.domainid,
COUNT(e.email) as addresses,
IFNULL(SUM(CASE WHEN e.popaccountid > 0 THEN 1 ELSE 0 END), 0) as accounts,
IFNULL(SUM(

View File

@@ -28,10 +28,12 @@ namespace Froxlor\Api\Commands;
use Exception;
use Froxlor\Api\ApiCommand;
use Froxlor\Api\ResourceEntity;
use Froxlor\Cron\TaskId;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
use Froxlor\Idna\IdnaWrapper;
use Froxlor\Settings;
use Froxlor\System\Cronjob;
use Froxlor\UI\Response;
use Froxlor\Validate\Validate;
use PDO;
@@ -49,6 +51,16 @@ class Emails extends ApiCommand implements ResourceEntity
* name of the address before @
* @param string $domain
* domain-name for the email-address
* @param float $spam_tag_level
* optional, score which is required to tag emails as spam, default: 7.0
* @param bool $rewrite_subject
* optional, whether to add ***SPAM*** to the email's subject if applicable, default: [antispam.default_spam_rewrite_subject]
* @param float $spam_kill_level
* optional, score which is required to discard emails, default: 14.0
* @param boolean $bypass_spam
* optional, disable spam-filter entirely, default: [antispam.default_bypass_spam]
* @param boolean $policy_greylist
* optional, enable grey-listing, default: [antispam.default_policy_greylist]
* @param boolean $iscatchall
* optional, make this address a catchall address, default: no
* @param int $customerid
@@ -74,22 +86,44 @@ class Emails extends ApiCommand implements ResourceEntity
$domain = $this->getParam('domain');
// parameters
$spam_tag_level = $this->getParam('spam_tag_level', true, '7.0');
$spam_kill_level = $this->getUlParam('spam_kill_level', 'spam_kill_level_ul', true, '14.0');
$iscatchall = $this->getBoolParam('iscatchall', true, 0);
$description = $this->getParam('description', true, '');
if ((int)Settings::Get('antispam.default_spam_rewrite_subject') <= 2) {
$rewrite_subject = $this->getBoolParam('rewrite_subject', true, (int)Settings::Get('antispam.default_spam_rewrite_subject') == 1 ? 1 : 0);
} else {
$rewrite_subject = (int)Settings::Get('antispam.default_spam_rewrite_subject') == 3 ? 1 : 0;
}
if ((int)Settings::Get('antispam.default_bypass_spam') <= 2) {
$bypass_spam = $this->getBoolParam('bypass_spam', true, (int)Settings::Get('antispam.default_bypass_spam') == 1 ? 1 : 0);
} else {
$bypass_spam = (int)Settings::Get('antispam.default_bypass_spam') == 3 ? 1 : 0;
}
if ((int)Settings::Get('antispam.default_policy_greylist') <= 2) {
$policy_greylist = $this->getBoolParam('policy_greylist', true, (int)Settings::Get('antispam.default_policy_greylist') == 1 ? 1 : 0);
} else {
$policy_greylist = (int)Settings::Get('antispam.default_policy_greylist') == 3 ? 1 : 0;
}
// validation
$idna_convert = new IdnaWrapper();
if (substr($domain, 0, 4) != 'xn--') {
$idna_convert = new IdnaWrapper();
$domain = $idna_convert->encode(Validate::validate($domain, 'domain', '', '', [], true));
}
$email_part = $idna_convert->encode($email_part);
// check domain and whether it's an email-enabled domain
// use internal call because the customer might have 'domains' in customer_hide_options
$domain_check = $this->apiCall('SubDomains.get', [
'domainname' => $domain
], true);
if ($domain_check['isemaildomain'] == 0) {
Response::standardError('maindomainnonexist', $domain, true);
if ((int)$domain_check['isemaildomain'] == 0) {
Response::standardError('maindomainnonexist', $idna_convert->decode($domain), true);
}
if ((int)$domain_check['deactivated'] == 1) {
Response::standardError('maindomaindeactivated', $idna_convert->decode($domain), true);
}
if (Settings::Get('catchall.catchall_enabled') != '1') {
@@ -110,7 +144,7 @@ class Emails extends ApiCommand implements ResourceEntity
// validate it
if (!Validate::validateEmail($email_full)) {
Response::standardError('emailiswrong', $email_full, true);
Response::standardError('emailiswrong', $idna_convert->decode($email_full), true);
}
// get needed customer info to reduce the email-address-counter by one
@@ -131,17 +165,28 @@ class Emails extends ApiCommand implements ResourceEntity
if ($email_check) {
if (strtolower($email_check['email_full']) == strtolower($email_full)) {
Response::standardError('emailexistalready', $email_full, true);
Response::standardError('emailexistalready', $idna_convert->decode($email_full), true);
} elseif ($email_check['email'] == $email) {
Response::standardError('youhavealreadyacatchallforthisdomain', '', true);
}
}
$spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1})?$/', '', [7.0], true);
if ($spam_kill_level > -1) {
$spam_kill_level = Validate::validate($spam_kill_level, 'spam_kill_level', '/^\d{1,}(\.\d{1})?$/', '', [14.0], true);
}
$description = Validate::validate(trim($description), 'description', Validate::REGEX_DESC_TEXT, '', [], true);
$stmt = Database::prepare("
INSERT INTO `" . TABLE_MAIL_VIRTUAL . "` SET
`customerid` = :cid,
`email` = :email,
`email_full` = :email_full,
`spam_tag_level` = :spam_tag_level,
`rewrite_subject` = :rewrite_subject,
`spam_kill_level` = :spam_kill_level,
`bypass_spam` = :bypass_spam,
`policy_greylist` = :policy_greylist,
`iscatchall` = :iscatchall,
`domainid` = :domainid,
`description` = :description
@@ -150,6 +195,11 @@ class Emails extends ApiCommand implements ResourceEntity
"cid" => $customer['customerid'],
"email" => $email,
"email_full" => $email_full,
"spam_tag_level" => $spam_tag_level,
"rewrite_subject" => $rewrite_subject,
"spam_kill_level" => $spam_kill_level,
"bypass_spam" => $bypass_spam,
"policy_greylist" => $policy_greylist,
"iscatchall" => $iscatchall,
"domainid" => $domain_check['id'],
"description" => $description
@@ -159,6 +209,7 @@ class Emails extends ApiCommand implements ResourceEntity
// update customer usage
Customers::increaseUsage($customer['customerid'], 'emails_used');
Cronjob::inserttask(TaskId::REBUILD_RSPAMD);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added email address '" . $email_full . "'");
$result = $this->apiCall('Emails.get', [
@@ -191,12 +242,12 @@ class Emails extends ApiCommand implements ResourceEntity
$customer_ids = $this->getAllowedCustomerIds('email');
$params['idea'] = ($id <= 0 ? $emailaddr : $id);
$result_stmt = Database::prepare("SELECT v.`id`, v.`email`, v.`email_full`, v.`iscatchall`, v.`destination`, v.`customerid`, v.`popaccountid`, v.`domainid`, v.`description`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
$result_stmt = Database::prepare("SELECT v.*, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize` " . ($this->isInternal() ? ", `u`.`homedir`, `u`.`maildir`" : "") . "
FROM `" . TABLE_MAIL_VIRTUAL . "` v
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON v.`popaccountid` = u.`id`
WHERE v.`customerid` IN (" . implode(", ", $customer_ids) . ")
AND " . (is_numeric($params['idea']) ? "v.`id`= :idea" : "(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_INFO, "[API] get email address '" . $result['email_full'] . "'");
@@ -217,6 +268,16 @@ class Emails extends ApiCommand implements ResourceEntity
* optional, required when called as admin (if $loginname is not specified)
* @param string $loginname
* optional, required when called as admin (if $customerid is not specified)
* @param float $spam_tag_level
* optional, score which is required to tag emails as spam, default: 7.0
* @param bool $rewrite_subject
* optional, whether to add ***SPAM*** to the email's subject if applicable, default: [antispam.default_spam_rewrite_subject]
* @param float $spam_kill_level
* optional, score which is required to discard emails, default: 14.0
* @param boolean $bypass_spam
* optional, disable spam-filter entirely, default: [antispam.default_bypass_spam]
* @param boolean $policy_greylist
* optional, enable grey-listing, default: [antispam.default_policy_greylist]
* @param boolean $iscatchall
* optional
* @param string $description
@@ -232,15 +293,6 @@ class Emails extends ApiCommand implements ResourceEntity
throw new Exception("You cannot access this resource", 405);
}
// if enabling catchall is not allowed by settings, we do not need
// to run update()
if (Settings::Get('catchall.catchall_enabled') != '1') {
Response::standardError([
'operationnotpermitted',
'featureisdisabled'
], 'catchall', true);
}
$id = $this->getParam('id', true, 0);
$ea_optional = $id > 0;
$emailaddr = $this->getParam('emailaddr', $ea_optional, '');
@@ -252,48 +304,96 @@ class Emails extends ApiCommand implements ResourceEntity
$id = $result['id'];
// parameters
$spam_tag_level = $this->getParam('spam_tag_level', true, $result['spam_tag_level']);
$spam_kill_level = $this->getUlParam('spam_kill_level', 'spam_kill_level_ul', true, $result['spam_kill_level']);
$iscatchall = $this->getBoolParam('iscatchall', true, $result['iscatchall']);
$description = $this->getParam('description', true, $result['description']);
if ((int)Settings::Get('antispam.default_spam_rewrite_subject') <= 2) {
$rewrite_subject = $this->getBoolParam('rewrite_subject', true, $result['rewrite_subject']);
} else {
$rewrite_subject = (int)Settings::Get('antispam.default_spam_rewrite_subject') == 3 ? 1 : 0;
}
if ((int)Settings::Get('antispam.default_bypass_spam') <= 2) {
$bypass_spam = $this->getBoolParam('bypass_spam', true, $result['bypass_spam']);
} else {
$bypass_spam = (int)Settings::Get('antispam.default_bypass_spam') == 3 ? 1 : 0;
}
if ((int)Settings::Get('antispam.default_policy_greylist') <= 2) {
$policy_greylist = $this->getBoolParam('policy_greylist', true, $result['policy_greylist']);
} else {
$policy_greylist = (int)Settings::Get('antispam.default_policy_greylist') == 3 ? 1 : 0;
}
// if enabling catchall is not allowed by settings, we do not need
// to run update()
if ($iscatchall && $result['iscatchall'] == 0 && Settings::Get('catchall.catchall_enabled') != '1') {
Response::standardError([
'operationnotpermitted',
'featureisdisabled'
], 'catchall', true);
}
// get needed customer info to reduce the email-address-counter by one
$customer = $this->getCustomerData();
// check for catchall-flag
$email = $result['email_full'];
if ($iscatchall) {
$iscatchall = '1';
$email_parts = explode('@', $result['email_full']);
$email = '@' . $email_parts[1];
// catchall check
$stmt = Database::prepare("
SELECT `email_full` FROM `" . TABLE_MAIL_VIRTUAL . "`
WHERE `email` = :email AND `customerid` = :cid AND `iscatchall` = '1'
");
$params = [
"email" => $email,
"cid" => $customer['customerid']
];
$email_check = Database::pexecute_first($stmt, $params, true, true);
if ($email_check) {
Response::standardError('youhavealreadyacatchallforthisdomain', '', true);
$email = $result['email'];
// update only required if it was not a catchall before
if ($result['iscatchall'] == 0) {
$email_parts = explode('@', $result['email_full']);
$email = '@' . $email_parts[1];
// catchall check
$stmt = Database::prepare("
SELECT `email_full` FROM `" . TABLE_MAIL_VIRTUAL . "`
WHERE `email` = :email AND `customerid` = :cid AND `iscatchall` = '1'
");
$params = [
"email" => $email,
"cid" => $customer['customerid']
];
$email_check = Database::pexecute_first($stmt, $params, true, true);
if ($email_check) {
Response::standardError('youhavealreadyacatchallforthisdomain', '', true);
}
}
} else {
$iscatchall = '0';
$email = $result['email_full'];
}
$spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1,2})?$/', '', [7.0], true);
if ($spam_kill_level > -1) {
$spam_kill_level = Validate::validate($spam_kill_level, 'spam_kill_level', '/^\d{1,}(\.\d{1,2})?$/', '', [14.0], true);
}
$description = Validate::validate(trim($description), 'description', Validate::REGEX_DESC_TEXT, '', [], true);
$stmt = Database::prepare("
UPDATE `" . TABLE_MAIL_VIRTUAL . "`
SET `email` = :email , `iscatchall` = :caflag, `description` = :description
UPDATE `" . TABLE_MAIL_VIRTUAL . "` SET
`email` = :email ,
`spam_tag_level` = :spam_tag_level,
`rewrite_subject` = :rewrite_subject,
`spam_kill_level` = :spam_kill_level,
`bypass_spam` = :bypass_spam,
`policy_greylist` = :policy_greylist,
`iscatchall` = :caflag,
`description` = :description
WHERE `customerid`= :cid AND `id`= :id
");
$params = [
"email" => $email,
"spam_tag_level" => $spam_tag_level,
"rewrite_subject" => $rewrite_subject,
"spam_kill_level" => $spam_kill_level,
"bypass_spam" => $bypass_spam,
"policy_greylist" => $policy_greylist,
"caflag" => $iscatchall,
"description" => $description,
"cid" => $customer['customerid'],
"id" => $id
];
Database::pexecute($stmt, $params, true, true);
Cronjob::inserttask(TaskId::REBUILD_RSPAMD);
$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', [
@@ -331,13 +431,16 @@ class Emails extends ApiCommand implements ResourceEntity
$result = [];
$query_fields = [];
$result_stmt = Database::prepare("
SELECT m.`id`, m.`domainid`, m.`email`, m.`email_full`, m.`iscatchall`, m.`destination`, m.`popaccountid`, d.`domain`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
SELECT m.*, d.`domain`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
FROM `" . TABLE_MAIL_VIRTUAL . "` m
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` d ON (m.`domainid` = d.`id`)
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON (m.`popaccountid` = u.`id`)
WHERE m.`customerid` IN (" . implode(", ", $customer_ids) . ")" . $this->getSearchWhere($query_fields, true) . $this->getOrderBy() . $this->getLimit());
Database::pexecute($result_stmt, $query_fields, true, true);
$idna_convert = new IdnaWrapper();
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$row['email'] = $idna_convert->decode($row['email']);
$row['email_full'] = $idna_convert->decode($row['email_full']);
$result[] = $row;
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list email-addresses");

View File

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

View File

@@ -37,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;
@@ -81,7 +82,7 @@ class Froxlor extends ApiCommand
if ($aucheck == 1) {
// anzeige über version-status mit ggfls. formular
// zum update schritt #1 -> download
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') == 'testing' ? 'testing ' : ''), AutoUpdate::getFromResult('version'), $this->version]);
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel').' ' : ''), AutoUpdate::getFromResult('version'), $this->version]);
$response = [
'isnewerversion' => (int) !AutoUpdate::getFromResult('has_latest'),
'version' => $this->version,
@@ -90,7 +91,7 @@ class Froxlor extends ApiCommand
'additional_info' => AutoUpdate::getFromResult('info'),
'aucheck' => $aucheck
];
} else if ($aucheck < 0 || $aucheck > 1) {
} elseif ($aucheck < 0 || $aucheck > 1) {
// errors
if ($aucheck < 0) {
$errmsg = AutoUpdate::getLastError();
@@ -258,17 +259,91 @@ class Froxlor extends ApiCommand
* returns a random password based on froxlor settings for min-length, included characters, etc.
*
* @param int $length
* optional length of password, defaults to 10
* optional length of password, defaults to 0 (panel.password_min_length)
*
* @access admin, customer
* @return string
* @throws Exception
*/
public function generatePassword()
public function generatePassword(): string
{
$length = $this->getParam('length', true, 10);
$length = $this->getParam('length', true, 0);
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
@@ -171,18 +174,19 @@ class Ftps extends ApiCommand implements ResourceEntity
} elseif ($username == $password) {
Response::standardError('passwordshouldnotbeusername', '', true);
} else {
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
$cryptPassword = Crypt::makeCryptPassword($password, false, true);
$stmt = Database::prepare("INSERT INTO `" . TABLE_FTP_USERS . "`
(`customerid`, `username`, `description`, `password`, `homedir`, `login_enabled`, `uid`, `gid`, `shell`)
VALUES (:customerid, :username, :description, :password, :homedir, '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
];
@@ -284,7 +288,7 @@ class Ftps extends ApiCommand implements ResourceEntity
try {
$this->mailer()->Subject = $mail_subject;
$this->mailer()->AltBody = $mail_body;
$this->mailer()->msgHTML(str_replace("\n", "<br />", $mail_body));
$this->mailer()->Body = str_replace("\n", "<br />", $mail_body);
$this->mailer()->addAddress($customer['email'], User::getCorrectUserSalutation($customer));
$this->mailer()->send();
} catch (\PHPMailer\PHPMailer\Exception $e) {
@@ -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();
@@ -458,7 +469,7 @@ class Ftps extends ApiCommand implements ResourceEntity
// path update?
if ($path != '') {
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
if ($path != $result['homedir']) {
$stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "`
@@ -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);

View File

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

View File

@@ -176,8 +176,9 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
if ((int)Settings::Get('system.use_ssl') == 1) {
$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);
$cert_optional = !($ssl && empty(Settings::Get('system.ssl_cert_file')));
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', $cert_optional, ''), 'ssl_cert_file', '', '', [], true);
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', $cert_optional, ''), 'ssl_key_file', '', '', [], true);
$ssl_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, '');
@@ -415,8 +416,9 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
if ((int)Settings::Get('system.use_ssl') == 1) {
$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);
$cert_optional = !($ssl && empty(Settings::Get('system.ssl_cert_file')));
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', $cert_optional, $result['ssl_cert_file']), 'ssl_cert_file', '', '', [], true);
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', $cert_optional, $result['ssl_key_file']), 'ssl_key_file', '', '', [], true);
$ssl_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

@@ -54,7 +54,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
* @param string $description
* optional, description for database
* @param string $custom_suffix
* optional, name for database
* optional, name for database if customer.mysqlprefix setting is set to "DBNAME"
* @param bool $sendinfomail
* optional, send created resource-information to customer, default: false
* @param int $customerid
@@ -110,9 +110,12 @@ class Mysqls extends ApiCommand implements ResourceEntity
$dbm = new DbManager($this->logger());
if (strtoupper(Settings::Get('customer.mysqlprefix')) == 'DBNAME' && !empty($databasename)) {
$username = $dbm->createDatabase($newdb_params['loginname'] . '_' . $databasename, $password, $dbserver);
if (strlen($newdb_params['loginname'] . '_' . $databasename) > Database::getSqlUsernameLength()) {
throw new Exception("Database name cannot be longer than " . (Database::getSqlUsernameLength() - strlen($newdb_params['loginname'] . '_')) . " characters.", 406);
}
$username = $dbm->createDatabase($newdb_params['loginname'] . '_' . $databasename, $password, $dbserver, 0, $newdb_params['loginname']);
} else {
$username = $dbm->createDatabase($newdb_params['loginname'], $password, $dbserver, $newdb_params['mysql_lastaccountnumber']);
$username = $dbm->createDatabase($newdb_params['loginname'], $password, $dbserver, $newdb_params['mysql_lastaccountnumber'], $newdb_params['loginname']);
}
// we've checked against the password in dbm->createDatabase
@@ -181,7 +184,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
try {
$this->mailer()->Subject = $mail_subject;
$this->mailer()->AltBody = $mail_body;
$this->mailer()->msgHTML(str_replace("\n", "<br />", $mail_body));
$this->mailer()->Body = str_replace("\n", "<br />", $mail_body);
$this->mailer()->addAddress($userinfo['email'], User::getCorrectUserSalutation($userinfo));
$this->mailer()->send();
} catch (\PHPMailer\PHPMailer\Exception $e) {
@@ -538,7 +541,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
// Begin root-session
Database::needRoot(true, $result['dbserver'], false);
$dbm = new DbManager($this->logger());
$dbm->getManager()->deleteDatabase($result['databasename']);
$dbm->getManager()->deleteDatabase($result['databasename'], $customer['loginname']);
Database::needRoot(false);
// End root-session

View File

@@ -222,8 +222,8 @@ class PhpSettings extends ApiCommand implements ResourceEntity
* optional request terminate timeout if FPM is used, default is '60s'
* @param string $phpfpm_reqslowtimeout
* optional request slowlog timeout if FPM is used, default is '5s'
* @param bool $phpfpm_pass_authorizationheader
* optional whether to pass authorization header to webserver if FPM is used, default is 0 (false)
* @param bool $pass_authorizationheader
* optional whether to pass authorization header to webserver if FPM/FCGID is used, default is 0 (false)
* @param bool $override_fpmconfig
* optional whether to override fpm-daemon-config value for the following settings if FPM is used,
* default is 0 (false)
@@ -276,7 +276,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
$fpm_enableslowlog = $this->getBoolParam('phpfpm_enable_slowlog', true, 0);
$fpm_reqtermtimeout = $this->getParam('phpfpm_reqtermtimeout', true, "60s");
$fpm_reqslowtimeout = $this->getParam('phpfpm_reqslowtimeout', true, "5s");
$fpm_pass_authorizationheader = $this->getBoolParam('phpfpm_pass_authorizationheader', true, 0);
$pass_authorizationheader = $this->getBoolParam('pass_authorizationheader', true, 0);
$override_fpmconfig = $this->getBoolParam('override_fpmconfig', true, 0);
$def_fpmconfig = $this->apiCall('FpmDaemons.get', [
@@ -312,7 +312,6 @@ class PhpSettings extends ApiCommand implements ResourceEntity
$fpm_enableslowlog = 0;
$fpm_reqtermtimeout = 0;
$fpm_reqslowtimeout = 0;
$fpm_pass_authorizationheader = 0;
$override_fpmconfig = 0;
} elseif (Settings::Get('phpfpm.enabled') == 1) {
$fpm_reqtermtimeout = Validate::validate($fpm_reqtermtimeout, 'phpfpm_reqtermtimeout', '/^([0-9]+)(|s|m|h|d)$/', '', [], true);
@@ -377,7 +376,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
'fpmreqslow' => $fpm_reqslowtimeout,
'phpsettings' => $phpsettings,
'fpmsettingid' => $fpm_config_id,
'fpmpassauth' => $fpm_pass_authorizationheader,
'fpmpassauth' => $pass_authorizationheader,
'ofc' => $override_fpmconfig,
'pm' => $pmanager,
'max_children' => $max_children,
@@ -464,7 +463,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
* optional request terminate timeout if FPM is used, default is '60s'
* @param string $phpfpm_reqslowtimeout
* optional request slowlog timeout if FPM is used, default is '5s'
* @param bool $phpfpm_pass_authorizationheader
* @param bool $pass_authorizationheader
* optional whether to pass authorization header to webserver if FPM is used, default is 0 (false)
* @param bool $override_fpmconfig
* optional whether to override fpm-daemon-config value for the following settings if FPM is used,
@@ -516,7 +515,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
$fpm_enableslowlog = $this->getBoolParam('phpfpm_enable_slowlog', true, $result['fpm_slowlog']);
$fpm_reqtermtimeout = $this->getParam('phpfpm_reqtermtimeout', true, $result['fpm_reqterm']);
$fpm_reqslowtimeout = $this->getParam('phpfpm_reqslowtimeout', true, $result['fpm_reqslow']);
$fpm_pass_authorizationheader = $this->getBoolParam('phpfpm_pass_authorizationheader', true, $result['pass_authorizationheader']);
$pass_authorizationheader = $this->getBoolParam('pass_authorizationheader', true, $result['pass_authorizationheader']);
$override_fpmconfig = $this->getBoolParam('override_fpmconfig', true, $result['override_fpmconfig']);
$pmanager = $this->getParam('pm', true, $result['pm']);
$max_children = $this->getParam('max_children', true, $result['max_children']);
@@ -548,7 +547,6 @@ class PhpSettings extends ApiCommand implements ResourceEntity
$fpm_enableslowlog = 0;
$fpm_reqtermtimeout = 0;
$fpm_reqslowtimeout = 0;
$fpm_pass_authorizationheader = 0;
$override_fpmconfig = 0;
} elseif (Settings::Get('phpfpm.enabled') == 1) {
$fpm_reqtermtimeout = Validate::validate($fpm_reqtermtimeout, 'phpfpm_reqtermtimeout', '/^([0-9]+)(|s|m|h|d)$/', '', [], true);
@@ -614,7 +612,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity
'fpmreqslow' => $fpm_reqslowtimeout,
'phpsettings' => $phpsettings,
'fpmsettingid' => $fpm_config_id,
'fpmpassauth' => $fpm_pass_authorizationheader,
'fpmpassauth' => $pass_authorizationheader,
'ofc' => $override_fpmconfig,
'pm' => $pmanager,
'max_children' => $max_children,

View File

@@ -67,6 +67,8 @@ class SubDomains extends ApiCommand implements ResourceEntity
* optional, php-settings-id, if empty the $domain value is used
* @param int $redirectcode
* optional, redirect-code-id from TABLE_PANEL_REDIRECTCODES
* @param int $speciallogfile
* optional, whether to create an exclusive web-logfile for this domain (1) or not (0) or inherit value from parentdomain (2, default)
* @param bool $sslenabled
* optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default
* 1 (true)
@@ -107,6 +109,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
$openbasedir_path = $this->getParam('openbasedir_path', true, 0);
$phpsettingid = $this->getParam('phpsettingid', true, 0);
$redirectcode = $this->getParam('redirectcode', true, Settings::Get('customredirect.default'));
$speciallogfile = intval($this->getParam('speciallogfile', true, 2));
$isemaildomain = $this->getParam('isemaildomain', true, 0);
if (Settings::Get('system.use_ssl')) {
$sslenabled = $this->getBoolParam('sslenabled', true, 1);
@@ -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
@@ -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
@@ -285,21 +296,24 @@ class SubDomains extends ApiCommand implements ResourceEntity
// assign default config
$phpsid_result['phpsettingid'] = 1;
}
// check whether the customer has chosen its own php-config
if ($phpsettingid > 0 && $phpsettingid != $phpsid_result['phpsettingid']) {
$phpsid_result['phpsettingid'] = intval($phpsettingid);
}
$allowed_phpconfigs = $customer['allowed_phpconfigs'];
if (!empty($allowed_phpconfigs)) {
$allowed_phpconfigs = json_decode($allowed_phpconfigs, true);
} else {
$allowed_phpconfigs = [];
}
// only with fcgid/fpm enabled will it be possible to select a php-setting
if ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) {
if (!in_array($phpsid_result['phpsettingid'], $allowed_phpconfigs)) {
Response::standardError('notallowedphpconfigused', '', true);
if ($domain_check['phpenabled'] == 1) {
// check whether the customer has chosen its own php-config
if ($phpsettingid > 0 && $phpsettingid != $phpsid_result['phpsettingid']) {
$phpsid_result['phpsettingid'] = intval($phpsettingid);
}
$allowed_phpconfigs = $customer['allowed_phpconfigs'];
if (!empty($allowed_phpconfigs)) {
$allowed_phpconfigs = json_decode($allowed_phpconfigs, true);
} else {
$allowed_phpconfigs = [];
}
// only with fcgid/fpm enabled will it be possible to select a php-setting
if ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) {
if (!in_array($phpsid_result['phpsettingid'], $allowed_phpconfigs)) {
Response::standardError('notallowedphpconfigused', '', true);
}
}
}
@@ -351,7 +365,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'],
@@ -489,8 +503,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
$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 . "'");
throw new Exception("Subdomain with " . $key . " could not be found", 404);
throw new Exception("Requested subdomain could not be found", 404);
}
private function getHasCertValueForDomain(int $domainid, int $parentdomainid): int
@@ -536,32 +549,33 @@ class SubDomains extends ApiCommand implements ResourceEntity
*/
private function validateDomainDocumentRoot($path = null, $url = null, $customer = null, $completedomain = null, &$_doredirect = false)
{
// check whether an URL was specified
$_doredirect = false;
if (!empty($url) && Validate::validateUrl($url, true)) {
$path = $url;
$idna = new IdnaWrapper();
// url mode: either $url or $path begins with http:// or https://
$maybeUrl = !empty($url) ? $url : (preg_match('/^https?\:\/\//', $path) ? $path : '');
if ($maybeUrl !== '') {
$encoded = $idna->encode($maybeUrl);
if (!Validate::validateUrl($encoded, true)) {
Response::standardError('invaliddocumentrooturl', '', true);
}
$_doredirect = true;
} else {
$path = Validate::validate($path, 'path', '', '', [], true);
return $encoded;
}
// check whether path is a real path
if (!preg_match('/^https?\:\/\//', $path) || !Validate::validateUrl($path, true)) {
if (strstr($path, ":") !== false) {
Response::standardError('pathmaynotcontaincolon', '', true);
}
// If path is empty or '/' and 'Use domain name as default value for DocumentRoot path' is enabled in settings,
// set default path to subdomain or domain name
if ((($path == '') || ($path == '/')) && Settings::Get('system.documentroot_use_default_value') == 1) {
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $completedomain);
} else {
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
}
} else {
// no it's not, create a redirect
$_doredirect = true;
// path mode: regular directory path
$path = Validate::validate($path, 'path', Validate::REGEX_DIR, '', [], true);
// default path if empty and setting active
if (($path === '' || $path === '/') && Settings::Get('system.documentroot_use_default_value') == 1) {
return FileDir::makeCorrectDir($customer['documentroot'] . '/' . $completedomain, $customer['documentroot']);
}
return $path;
// check if path does not contain a colon
if (strpos($path, ':') !== false) {
Response::standardError('pathmaynotcontaincolon', '', true);
}
return FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
}
/**
@@ -588,6 +602,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 +664,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']);
@@ -754,6 +775,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 = [
@@ -775,7 +800,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
$allowed_phpconfigs = [];
}
// only with fcgid/fpm enabled will it be possible to select a php-setting
if ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) {
if ((int)$result['phpenabled'] == 1 && ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1)) {
if (!in_array($phpsettingid, $allowed_phpconfigs)) {
Response::standardError('notallowedphpconfigused', '', true);
}
@@ -786,7 +811,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 +841,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 +860,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
"hsts_sub" => $hsts_sub,
"hsts_preload" => $hsts_preload,
"phpsettingid" => $phpsettingid,
"speciallogfile" => $speciallogfile,
"customerid" => $customer['customerid'],
"id" => $id
];
@@ -865,7 +906,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 +951,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 +968,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,9 +982,12 @@ class SubDomains extends ApiCommand implements ResourceEntity
'`d`.`parentdomainid`',
'`d`.`letsencrypt`',
'`d`.`registration_date`',
'`d`.`termination_date`'
'`d`.`termination_date`',
'`d`.`deactivated`',
'`d`.`email_only`',
];
}
$query_fields = [];
// prepare select statement
@@ -962,8 +998,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `da` ON `da`.`aliasdomain`=`d`.`id`
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);
@@ -1047,10 +1082,8 @@ class SubDomains extends ApiCommand implements ResourceEntity
$custom_list_result = $_custom_list_result['list'];
}
$customer_ids = [];
$customer_stdsubs = [];
foreach ($custom_list_result as $customer) {
$customer_ids[] = $customer['customerid'];
$customer_stdsubs[$customer['customerid']] = $customer['standardsubdomain'];
}
} else {
if (Settings::IsInList('panel.customer_hide_options', 'domains')) {
@@ -1059,21 +1092,19 @@ class SubDomains extends ApiCommand implements ResourceEntity
$customer_ids = [
$this->getUserDetail('customerid')
];
$customer_stdsubs = [
$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) . ")
");
$result = Database::pexecute_first($domains_stmt, null, true, true);
if ($result) {
return $this->response($result['num_subdom']);
}
}
return $this->response(0);
}

View File

@@ -90,6 +90,8 @@ class SysLog extends ApiCommand implements ResourceEntity
}
Database::pexecute($result_stmt, $query_fields, true, true);
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
// clean log-text
$row['text'] = preg_replace("/[^\w @#\"':.,()\[\]+\-_\/\\\!]/i", "_", $row['text']);
$result[] = $row;
}
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list log-entries");
@@ -223,7 +225,7 @@ class SysLog extends ApiCommand implements ResourceEntity
}
$params['trunc'] = $truncatedate;
Database::pexecute($result_stmt, $params, true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] truncated the froxlor syslog");
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] truncated the froxlor syslog");
return $this->response(true);
}
throw new Exception("Not allowed to execute given command.", 403);

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

@@ -34,7 +34,9 @@ class Response
public static function jsonResponse($data = null, int $response_code = 200)
{
http_response_code($response_code);
if (!defined('TRAVIS_CI') || TRAVIS_CI == 0) {
http_response_code($response_code);
}
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
}

View File

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

View File

@@ -45,6 +45,9 @@ final class ConfigDiff extends CliCommand
->addOption('diff-params', '', InputOption::VALUE_REQUIRED, 'Additional parameters for `diff`, e.g. --diff-params="--color=always"');
}
/**
* @throws \Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
require Froxlor::getInstallDir() . '/lib/functions.php';

View File

@@ -25,6 +25,7 @@
namespace Froxlor\Cli;
use Exception;
use Froxlor\Config\ConfigParser;
use Froxlor\Database\Database;
use Froxlor\FileDir;
@@ -40,14 +41,12 @@ use Symfony\Component\Console\Style\SymfonyStyle;
final class ConfigServices extends CliCommand
{
private $yes_to_all_supported = [
/* 'bookworm', */
'bionic',
'bookworm',
'bullseye',
'buster',
'focal',
'jammy',
'noble',
];
protected function configure()
@@ -62,11 +61,9 @@ final class ConfigServices extends CliCommand
->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Install packages without asking questions (Debian/Ubuntu only currently)');
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
$result = $this->validateRequirements($output);
require Froxlor::getInstallDir() . '/lib/functions.php';
@@ -93,7 +90,7 @@ final class ConfigServices extends CliCommand
if ($result == self::SUCCESS) {
$io = new SymfonyStyle($input, $output);
if ($input->getOption('create')) {
$result = $this->createConfig($input, $output, $io);
$result = $this->createConfig($output, $io);
} elseif ($input->getOption('apply')) {
$result = $this->applyConfig($input, $output, $io);
} elseif ($input->getOption('list') || $input->getOption('daemon')) {
@@ -158,7 +155,10 @@ final class ConfigServices extends CliCommand
fclose($fp);
}
private function createConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
/**
* @throws Exception
*/
private function createConfig(OutputInterface $output, SymfonyStyle $io): int
{
$_daemons_config = [
'distro' => ""
@@ -171,8 +171,8 @@ final class ConfigServices extends CliCommand
$distributions_select_data = [];
//set default os.
$os_dist = ['ID' => 'bullseye'];
$os_version = ['0' => '11'];
$os_dist = ['ID' => 'bookworm'];
$os_version = ['0' => '12'];
$os_default = $os_dist['ID'];
//read os-release
@@ -217,6 +217,10 @@ final class ConfigServices extends CliCommand
$_daemons_config['distro'] = $io->choice('Choose distribution', $valid_dists, $os_default);
// go through all services and let user check whether to include it or not
if (empty($_daemons_config['distro']) || !file_exists($config_dir . '/' . $_daemons_config['distro']. ".xml")) {
$output->writeln('<error>Empty or non-existing distribution given.</>');
return self::INVALID;
}
$configfiles = new ConfigParser($config_dir . '/' . $_daemons_config['distro'] . ".xml");
$services = $configfiles->getServices();
@@ -285,7 +289,10 @@ final class ConfigServices extends CliCommand
return self::SUCCESS;
}
private function applyConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
/**
* @throws Exception
*/
private function applyConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io): int
{
$applyFile = $input->getOption('apply');
@@ -349,8 +356,13 @@ final class ConfigServices extends CliCommand
}
if (!empty($decoded_config)) {
$config_dir = Froxlor::getInstallDir() . 'lib/configfiles/';
$configfiles = new ConfigParser($config_dir . '/' . $decoded_config['distro'] . ".xml");
if (empty($decoded_config['distro']) || !file_exists($config_dir . '/' . $decoded_config['distro']. ".xml")) {
$output->writeln('<error>Empty or non-existing distribution given. Please login with an admin, go to "System -> Configuration" and select your correct distribution in the top-right corner or specify valid distribution name for "distro" parameter.</>');
return self::INVALID;
}
$configfiles = new ConfigParser($config_dir . '/' . $decoded_config['distro']. ".xml");
$services = $configfiles->getServices();
$replace_arr = $this->getReplacerArray();
@@ -398,7 +410,7 @@ final class ConfigServices extends CliCommand
case "file":
if (array_key_exists('content', $action)) {
$output->writeln('<comment>Creating file "' . $action['name'] . '"</>');
file_put_contents($action['name'], trim(strtr($action['content'], $replace_arr)));
file_put_contents($action['name'], trim(strtr($action['content'], $replace_arr)) . PHP_EOL);
} elseif (array_key_exists('subcommands', $action)) {
foreach ($action['subcommands'] as $fileaction) {
if (array_key_exists('execute', $fileaction) && $fileaction['execute'] == "pre") {
@@ -407,7 +419,7 @@ final class ConfigServices extends CliCommand
exec(strtr($fileaction['content'], $replace_arr));
} elseif ($fileaction['type'] == 'file') {
$output->writeln('<comment>Creating file "' . $fileaction['name'] . '"</>');
file_put_contents($fileaction['name'], trim(strtr($fileaction['content'], $replace_arr)));
file_put_contents($fileaction['name'], trim(strtr($fileaction['content'], $replace_arr)) . PHP_EOL);
}
}
}
@@ -429,7 +441,10 @@ final class ConfigServices extends CliCommand
}
}
private function getReplacerArray()
/**
* @throws Exception
*/
private function getReplacerArray(): array
{
$customer_tmpdir = '/tmp/';
if (Settings::Get('system.mod_fcgid') == '1' && Settings::Get('system.mod_fcgid_tmpdir') != '') {
@@ -438,7 +453,7 @@ final class ConfigServices extends CliCommand
$customer_tmpdir = Settings::Get('phpfpm.tmpdir');
}
// try to convert namserver hosts to ip's
// try to convert nameserver hosts to ip's
$ns_ips = "";
$known_ns_ips = [];
if (Settings::Get('system.nameservers') != '') {
@@ -484,12 +499,12 @@ final class ConfigServices extends CliCommand
Database::needSqlData();
$sql = Database::getSqlData();
$replace_arr = [
return [
'<SQL_UNPRIVILEGED_USER>' => $sql['user'],
'<SQL_UNPRIVILEGED_PASSWORD>' => $sql['passwd'],
'<SQL_DB>' => $sql['db'],
'<SQL_HOST>' => $sql['host'],
'<SQL_SOCKET>' => isset($sql['socket']) ? $sql['socket'] : null,
'<SQL_SOCKET>' => $sql['socket'] ?? null,
'<SERVERNAME>' => Settings::Get('system.hostname'),
'<SERVERIP>' => Settings::Get('system.ipaddress'),
'<NAMESERVERS>' => Settings::Get('system.nameservers'),
@@ -507,7 +522,7 @@ final class ConfigServices extends CliCommand
'<WEBSERVER_GROUP>' => Settings::Get('system.httpgroup'),
'<SSL_CERT_FILE>' => Settings::Get('system.ssl_cert_file'),
'<SSL_KEY_FILE>' => Settings::Get('system.ssl_key_file'),
'<ADMIN_MAIL>' => Settings::Get('panel.adminmail'),
];
return $replace_arr;
}
}

View File

@@ -26,13 +26,16 @@
namespace Froxlor\Cli;
use Exception;
use Froxlor\Froxlor;
use Froxlor\Config\ConfigParser;
use Froxlor\Database\Database;
use Froxlor\Froxlor;
use Froxlor\Install\Install;
use Froxlor\Install\Install\Core;
use Froxlor\Settings;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -50,10 +53,14 @@ final class InstallCommand extends Command
$this->setDescription('Installation process to use instead of web-ui');
$this->addArgument('input-file', InputArgument::OPTIONAL, 'Optional JSON array file to use for unattended installations');
$this->addOption('print-example-file', 'p', InputOption::VALUE_NONE, 'Outputs an example JSON content to be used with the input file parameter')
->addOption('create-userdata-from-str', 'c', InputOption::VALUE_REQUIRED, 'Creates lib/userdata.inc.php file from string created by web-install process');
->addOption('create-userdata-from-str', 'c', InputOption::VALUE_REQUIRED, 'Creates lib/userdata.inc.php file from string created by web-install process')
->addOption('show-sysinfo', 's', InputOption::VALUE_NONE, 'Outputs system information about your froxlor installation');
}
protected function execute(InputInterface $input, OutputInterface $output)
/**
* @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$result = self::SUCCESS;
@@ -69,6 +76,15 @@ final class InstallCommand extends Command
return self::INVALID;
}
if ($input->getOption('show-sysinfo') !== false) {
if (!file_exists(Froxlor::getInstallDir() . '/lib/userdata.inc.php')) {
$output->writeln("<error>Could not find froxlor's userdata.inc.php file. You can use this parameter only with an installed froxlor system.</>");
return self::INVALID;
}
$this->printSysInfo($output);
return self::SUCCESS;
}
session_start();
require __DIR__ . '/install.functions.php';
@@ -137,10 +153,12 @@ final class InstallCommand extends Command
$decoded_input = [];
}
$result = $this->showStep(0, $extended, $decoded_input);
return $result;
return $this->showStep(0, $extended, $decoded_input);
}
/**
* @throws Exception
*/
private function showStep(int $step = 0, bool $extended = false, array $decoded_input = []): int
{
$result = self::SUCCESS;
@@ -206,7 +224,7 @@ final class InstallCommand extends Command
$ask_field = false;
}
$fielddata['value'] = $this->formfielddata[$fieldname] ?? ($fielddata['value'] ?? null);
$fielddata['label'] = strip_tags(str_replace("<br>", " ", $fielddata['label']));
$fielddata['label'] = $this->cliTextFormat($fielddata['label'], " ");
if ($ask_field) {
if ($fielddata['type'] == 'password') {
$this->formfielddata[$fieldname] = $this->io->askHidden($fielddata['label'], function ($value) use ($fielddata) {
@@ -262,14 +280,16 @@ final class InstallCommand extends Command
case 4:
$section = $inst->formfield['install']['sections']['step' . $step] ?? [];
$this->io->section($section['title']);
$this->io->note($section['description']);
$this->io->note($this->cliTextFormat($section['description']));
$cmdfield = $section['fields']['system'];
$this->io->success([
$cmdfield['label'],
$cmdfield['value']
]);
if (!empty($decoded_input) || $this->io->confirm('Execute command now?', false)) {
passthru($cmdfield['value']);
if (!isset($decoded_input['manual_config']) || (bool)$decoded_input['manual_config'] === false) {
if (!empty($decoded_input) || $this->io->confirm('Execute command now?', false)) {
passthru($cmdfield['value']);
}
}
break;
}
@@ -300,7 +320,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' || $name == 'target_servername') {
if ($name == 'system' || $name == 'target_servername') {
continue;
}
if ($field['type'] == 'text' || $field['type'] == 'email') {
@@ -313,7 +333,7 @@ final class InstallCommand extends Command
$fieldval = '******';
} elseif ($field['type'] == 'select') {
$fieldval = implode("|", array_keys($field['select_var']));
} else if ($field['type'] == 'checkbox') {
} elseif ($field['type'] == 'checkbox') {
$fieldval = "1|0";
} else {
$fieldval = "?";
@@ -341,4 +361,61 @@ final class InstallCommand extends Command
curl_close($ch);
fclose($fp);
}
private function printSysInfo(OutputInterface $output)
{
$php_sapi = 'mod_php';
$php_version = phpversion();
if (Settings::Get('system.mod_fcgid') == '1') {
$php_sapi = 'FCGID';
if (Settings::Get('system.mod_fcgid_ownvhost') == '1') {
$php_sapi .= ' (+ froxlor)';
}
} elseif (Settings::Get('phpfpm.enabled') == '1') {
$php_sapi = 'PHP-FPM';
if (Settings::Get('phpfpm.enabled_ownvhost') == '1') {
$php_sapi .= ' (+ froxlor)';
}
}
$kernel = 'unknown';
if (function_exists('posix_uname')) {
$kernel_nfo = posix_uname();
$kernel = $kernel_nfo['release'] . ' (' . $kernel_nfo['machine'] . ')';
}
$ips = [];
$ips_stmt = Database::query("SELECT CONCAT(`ip`, ' (', `port`, ')') as ipaddr FROM `" . TABLE_PANEL_IPSANDPORTS . "` ORDER BY `id`");
while ($ip = $ips_stmt->fetch(\PDO::FETCH_ASSOC)) {
$ips[] = $ip['ipaddr'];
}
$table = new Table($output);
$table
->setHeaders([
'Key', 'Value'
])
->setRows([
['Froxlor', Froxlor::getVersionString()],
['Update-channel', Settings::Get('system.update_channel')],
['Hostname', Settings::Get('system.hostname')],
['Install-dir', Froxlor::getInstallDir()],
['PHP CLI', $php_version],
['PHP SAPI', $php_sapi],
['Webserver', Settings::Get('system.webserver')],
['Kernel', $kernel],
['Database', Database::getAttribute(\PDO::ATTR_SERVER_VERSION)],
['Distro config', Settings::Get('system.distribution')],
['IP addresses', implode("\n", $ips)],
]);
$table->setStyle('box');
$table->render();
}
private function cliTextFormat(string $text, string $nl_char = "\n"): string
{
$text = str_replace(['<br>', '<br/>', '<br />'], [$nl_char, $nl_char, $nl_char], $text);
return strip_tags($text);
}
}

View File

@@ -25,19 +25,20 @@
namespace Froxlor\Cli;
use PDO;
use Froxlor\Froxlor;
use Froxlor\FileDir;
use Froxlor\Settings;
use Froxlor\FroxlorLogger;
use Froxlor\Database\Database;
use Froxlor\System\Cronjob;
use Froxlor\Cron\TaskId;
use Exception;
use Froxlor\Cron\CronConfig;
use Froxlor\Cron\System\Extrausers;
use Froxlor\Cron\TaskId;
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
use Froxlor\System\Cronjob;
use PDO;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
final class MasterCron extends CliCommand
@@ -51,16 +52,18 @@ final class MasterCron extends CliCommand
$this->setName('froxlor:cron');
$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.)')
$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, 9 = re-generate rspamd configs, 10 = re-set quotas, 99 = re-create cron.d-file]')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces given job or, if none given, forces re-generating of config-files (webserver, nameserver, etc.)')
->addOption('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).');
}
protected function execute(InputInterface $input, OutputInterface $output)
/**
* @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
$result = $this->validateRequirements($output);
if ($result != self::SUCCESS) {
// requirements failed, exit
@@ -71,12 +74,15 @@ final class MasterCron extends CliCommand
// 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::REBUILD_RSPAMD);
Cronjob::inserttask(TaskId::CREATE_QUOTA);
Cronjob::inserttask(TaskId::REBUILD_CRON);
Cronjob::inserttask(TaskId::UPDATE_LE_SERVICES);
$jobs[] = 'tasks';
}
define('CRON_IS_FORCED', 1);
}
// handle debug option
@@ -91,9 +97,9 @@ final class MasterCron extends CliCommand
if ($input->getOption('run-task')) {
$tasks_to_run = $input->getOption('run-task');
foreach ($tasks_to_run as $ttr) {
if (in_array($ttr, [1, 4, 10, 99])) {
if (in_array($ttr, [TaskId::REBUILD_VHOST, TaskId::REBUILD_DNS, TaskId::REBUILD_RSPAMD, TaskId::CREATE_QUOTA, TaskId::REBUILD_CRON])) {
Cronjob::inserttask($ttr);
array_push($jobs, 'tasks');
$jobs[] = 'tasks';
} else {
$output->writeln('<comment>Unknown task number "' . $ttr . '"</>');
}
@@ -139,12 +145,12 @@ final class MasterCron extends CliCommand
$cronfile::run();
}
// free the lockfile
$this->unlockJob($job);
$this->unlockJob();
}
}
// regenerate nss-extrausers files / invalidate nscd cache (if used)
$this->refreshUsers((int) $tasks_cnt['jobcnt']);
$this->refreshUsers((int)$tasks_cnt['jobcnt']);
// we have to check the system's last guid with every cron run
// in case the admin installed new software which added a new user
@@ -156,40 +162,26 @@ final class MasterCron extends CliCommand
CronConfig::checkCrondConfigurationFile();
// check for old/compatibility cronjob file
if (file_exists(Froxlor::getInstallDir().'/scripts/froxlor_master_cronjob.php')) {
@unlink(Froxlor::getInstallDir().'/scripts/froxlor_master_cronjob.php');
@rmdir(Froxlor::getInstallDir().'/scripts');
if (file_exists(Froxlor::getInstallDir() . '/scripts/froxlor_master_cronjob.php')) {
@unlink(Froxlor::getInstallDir() . '/scripts/froxlor_master_cronjob.php');
@rmdir(Froxlor::getInstallDir() . '/scripts');
}
// reset cronlog-flag if set to "once"
if ((int) Settings::Get('logger.log_cron') == 1) {
if ((int)Settings::Get('logger.log_cron') == 1) {
FroxlorLogger::getInstanceOf()->setCronLog(0);
}
// clean up possible old login-links and 2fa tokens
Database::query("DELETE FROM `" . TABLE_PANEL_LOGINLINKS . "` WHERE `valid_until` < UNIX_TIMESTAMP()");
Database::query("DELETE FROM `" . TABLE_PANEL_2FA_TOKENS . "` WHERE `valid_until` < UNIX_TIMESTAMP()");
return $result;
}
private function refreshUsers(int $jobcount = 0)
{
if ($jobcount > 0) {
if (Settings::Get('system.nssextrausers') == 1) {
Extrausers::generateFiles($this->cronLog);
return;
}
// clear NSCD cache if using fcgid or fpm, #1570 - not needed for nss-extrausers
if ((Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && Settings::Get('system.nssextrausers') == 0) {
$false_val = false;
FileDir::safe_exec('nscd -i passwd 1> /dev/null', $false_val, [
'>'
]);
FileDir::safe_exec('nscd -i group 1> /dev/null', $false_val, [
'>'
]);
}
}
}
/**
* @throws Exception
*/
private function validateOwnership(OutputInterface $output)
{
// when using fcgid or fpm for froxlor-vhost itself, we have to check
@@ -216,21 +208,6 @@ final class MasterCron extends CliCommand
$output->writeln('OK');
}
private function getCronModule(string $cronname, OutputInterface $output)
{
$upd_stmt = Database::prepare("
SELECT `cronclass` FROM `" . TABLE_PANEL_CRONRUNS . "` WHERE `cronfile` = :cron;
");
$cron = Database::pexecute_first($upd_stmt, [
'cron' => $cronname
]);
if ($cron) {
return $cron['cronclass'];
}
$output->writeln("<error>Requested cronjob '" . $cronname . "' could not be found.</>");
return false;
}
private function lockJob(string $job, OutputInterface $output): bool
{
@@ -238,17 +215,22 @@ final class MasterCron extends CliCommand
if (file_exists($this->lockFile)) {
$jobinfo = json_decode(file_get_contents($this->lockFile), true);
$check_pid_return = null;
// get status of process
system("kill -CHLD " . (int)$jobinfo['pid'] . " 1> /dev/null 2> /dev/null", $check_pid_return);
if ($jobinfo === false || !is_array($jobinfo)) {
// looks like an invalid lockfile
$check_pid_return = 1;
} else {
$check_pid_return = null;
// get status of process
system("kill -CHLD " . (int)$jobinfo['pid'] . " 1> /dev/null 2> /dev/null", $check_pid_return);
}
if ($check_pid_return == 1) {
// Process does not seem to run, most likely it has died
$this->unlockJob($job);
$this->unlockJob();
} else {
// cronjob still running, output info and stop
$output->writeln([
'<comment>Job "' . $jobinfo['job'] . '" is currently running.',
'Started: ' . date('d.m.Y H:i', (int) $jobinfo['startts']),
'Started: ' . date('d.m.Y H:i', (int)$jobinfo['startts']),
'PID: ' . $jobinfo['pid'] . '</>'
]);
return false;
@@ -264,8 +246,48 @@ final class MasterCron extends CliCommand
return true;
}
private function unlockJob(string $job): bool
private function unlockJob(): bool
{
return @unlink($this->lockFile);
}
private function getCronModule(string $cronname, OutputInterface $output)
{
$upd_stmt = Database::prepare("
SELECT `cronclass` FROM `" . TABLE_PANEL_CRONRUNS . "` WHERE `cronfile` = :cron;
");
$cron = Database::pexecute_first($upd_stmt, [
'cron' => $cronname
]);
if ($cron) {
return $cron['cronclass'];
}
$output->writeln("<error>Requested cronjob '" . $cronname . "' could not be found.</>");
return false;
}
private function refreshUsers(int $jobcount = 0)
{
if ($jobcount > 0) {
if (Settings::Get('system.nssextrausers') == 1) {
Extrausers::generateFiles($this->cronLog);
// reload crond as shell users might use crontab and the user is only known to crond if reloaded
FileDir::safe_exec(escapeshellcmd(Settings::Get('system.crondreload')));
return;
}
// clear NSCD cache if using fcgid or fpm, #1570 - not needed for nss-extrausers
if ((Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && Settings::Get('system.nssextrausers') == 0) {
$false_val = false;
FileDir::safe_exec('nscd -i passwd 1> /dev/null', $false_val, [
'>'
]);
FileDir::safe_exec('nscd -i group 1> /dev/null', $false_val, [
'>'
]);
// reload crond as shell users might use crontab and the user is only known to crond if reloaded
FileDir::safe_exec(escapeshellcmd(Settings::Get('system.crondreload')));
}
}
}
}

View File

@@ -43,9 +43,9 @@ final class PhpSessionclean extends CliCommand
$this->addArgument('max-lifetime', InputArgument::OPTIONAL, 'The number of seconds after which data will be seen as "garbage" and potentially cleaned up. Defaults to "1440"');
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$result = $this->validateRequirements($input, $output);
$result = $this->validateRequirements($output);
if ($result == self::SUCCESS) {
if ((int)Settings::Get('phpfpm.enabled') == 1) {
@@ -89,7 +89,7 @@ final class PhpSessionclean extends CliCommand
if (count($paths_to_clean) > 0) {
foreach ($paths_to_clean as $ptc) {
// find all files older then maxlifetime and delete them
// find all files older than maxlifetime and delete them
FileDir::safe_exec("find -O3 \"" . $ptc . "\" -ignore_readdir_race -depth -mindepth 1 -name 'sess_*' -type f -cmin \"+" . $maxlifetime . "\" -delete");
}
}

View File

@@ -26,14 +26,12 @@
namespace Froxlor\Cli;
use Exception;
use PDO;
use Symfony\Component\Console\Input\InputInterface;
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;
use Symfony\Component\Console\Style\SymfonyStyle;
use Froxlor\Database\Database;
use Froxlor\Froxlor;
final class RunApiCommand extends CliCommand
{
@@ -44,15 +42,13 @@ final class RunApiCommand extends CliCommand
$this->setDescription('Run an API command as given user');
$this->addArgument('user', InputArgument::REQUIRED, 'Loginname of the user you want to run the command as')
->addArgument('api-command', InputArgument::REQUIRED, 'The command to execute in the form "Module.function"')
->addArgument('parameters', InputArgument::OPTIONAL, 'Paramaters to pass to the command as JSON array');
->addArgument('parameters', InputArgument::OPTIONAL, 'Parameters to pass to the command as JSON array');
$this->addOption('show-params', 's', InputOption::VALUE_NONE, 'Show possible parameters for given api-command (given command will *not* be called)');
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
$result = $this->validateRequirements($output);
require Froxlor::getInstallDir() . '/lib/functions.php';
@@ -110,6 +106,9 @@ final class RunApiCommand extends CliCommand
return self::SUCCESS;
}
/**
* @throws Exception
*/
private function validateCommand(string $command): array
{
$command = explode(".", $command);

View File

@@ -43,11 +43,9 @@ final class SwitchServerIp extends CliCommand
->addOption('list', 'l', InputOption::VALUE_NONE, 'List all IP addresses currently added for this server in froxlor');
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
$result = $this->validateRequirements($output);
if ($result == self::SUCCESS && $input->getOption('list') == false && $input->getOption('switch') == false) {
$output->writeln('<error>Either --list or --switch option must be provided. Nothing to do, exiting.</>');
@@ -83,6 +81,7 @@ final class SwitchServerIp extends CliCommand
$ip_list = $input->getOption('switch');
$has_error = false;
$ips_to_switch = [];
foreach ($ip_list as $ips_combo) {
$ip_pair = explode(",", $ips_combo);
if (count($ip_pair) != 2) {

View File

@@ -27,14 +27,18 @@ namespace Froxlor\Cli;
use Exception;
use Froxlor\Froxlor;
use Froxlor\Settings;
use Froxlor\Install\Update;
use Froxlor\Install\AutoUpdate;
use Froxlor\Install\Preconfig;
use Froxlor\Install\Update;
use Froxlor\Settings;
use Froxlor\System\Mailer;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
final class UpdateCommand extends CliCommand
{
@@ -44,6 +48,9 @@ final class UpdateCommand extends CliCommand
$this->setName('froxlor:update');
$this->setDescription('Check for newer version and update froxlor');
$this->addOption('check-only', 'c', InputOption::VALUE_NONE, 'Only check for newer version and exit')
->addOption('show-update-options', 'o', InputOption::VALUE_NONE, 'Show possible update option parameter for the update if any. Only usable in combination with "check-only".')
->addOption('update-options', 'O', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Parameter list of update options.')
->addOption('database', 'd', InputOption::VALUE_NONE, 'Only run database updates in case updates are done via apt or manually.')
->addOption('mail-notify', 'm', InputOption::VALUE_NONE, 'Additionally inform administrator via email if a newer version was found')
->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for download, extract and database-update, just do it (if not --check-only is set)')
->addOption('integer-return', 'i', InputOption::VALUE_NONE, 'Return integer whether a new version is available or not (implies --check-only). Useful for programmatic use.');
@@ -53,7 +60,40 @@ final class UpdateCommand extends CliCommand
{
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
// database update only
if ($input->getOption('database')) {
$result = $this->validateRequirements($output, true);
if ($result == self::SUCCESS) {
require Froxlor::getInstallDir() . '/lib/functions.php';
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
$output->writeln('<info>' . lng('update.dbupdate_required') . '</>');
if ($input->getOption('check-only')) {
$output->writeln('<comment>Doing nothing because of "check-only" flag.</>');
$this->askUpdateOptions($input, $output, null, false);
} else {
$yestoall = $input->getOption('yes-to-all') !== false;
$helper = $this->getHelper('question');
$this->askUpdateOptions($input, $output, $helper, $yestoall);
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
if ($yestoall || $helper->ask($input, $output, $question)) {
$result = $this->runUpdate($output, true);
}
}
return $result;
}
$output->writeln('<info>' . lng('update.noupdatesavail', [(Settings::Get('system.update_channel') == 'testing' ? lng('serversettings.uc_testing') . ' ' : '')]) . '</>');
}
return $result;
}
$result = $this->validateRequirements($output);
if ($result != self::SUCCESS) {
// requirements failed, exit
return $result;
}
require Froxlor::getInstallDir() . '/lib/functions.php';
@@ -71,7 +111,7 @@ final class UpdateCommand extends CliCommand
}
// there is a new version
if ($input->getOption('check-only')) {
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') == 'testing' ? 'testing ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel') . ' ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
} else {
$text = lng('admin.newerversionavailable') . ' ' . lng('admin.newerversiondetails', [AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
}
@@ -81,7 +121,7 @@ final class UpdateCommand extends CliCommand
$newversionavail = true;
$output->writeln('<comment>' . $text . '</>');
$result = self::SUCCESS;
} else if ($aucheck < 0 || $aucheck > 1) {
} elseif ($aucheck < 0 || $aucheck > 1) {
if ($input->getOption('integer-return')) {
$output->write(-1);
return self::INVALID;
@@ -122,6 +162,7 @@ final class UpdateCommand extends CliCommand
// check whether we only wanted to check
if ($input->getOption('check-only')) {
//$output->writeln('<comment>Not proceeding as "check-only" is specified</>');
$this->askUpdateOptions($input, $output, null, false);
return $result;
} else {
$yestoall = $input->getOption('yes-to-all') !== false;
@@ -144,9 +185,13 @@ final class UpdateCommand extends CliCommand
if ($auex == 0) {
$output->writeln("<info>Froxlor files updated successfully.</>");
$result = self::SUCCESS;
$this->askUpdateOptions($input, $output, $helper, $yestoall);
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
if ($yestoall || $helper->ask($input, $output, $question)) {
$result = $this->updateDatabase();
// run in separate process to ensure the use of newly unpacked files
passthru(Froxlor::getInstallDir() . '/bin/froxlor-cli froxlor:update -dA', $result);
}
} else {
$errmsg = 'error.autoupdate_' . $auex;
@@ -165,12 +210,141 @@ final class UpdateCommand extends CliCommand
return $result;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param $helper
* @param bool $yestoall
* @return void
*/
private function askUpdateOptions(InputInterface $input, OutputInterface $output, $helper, bool $yestoall = false)
{
// check for preconfigs
$preconfig = Preconfig::getPreConfig(true);
$show_options_only = $input->getOption('show-update-options') !== false;
if (!is_null($helper) && $show_options_only) {
$output->writeln('<comment>Unsetting "show-update-options" due to not being called with "check-only".</>');
$show_options_only = false;
}
$update_options = [];
// set parameters
$uOptions = $input->getOption('update-options');
if (!empty($uOptions)) {
$options_value = [];
foreach ($uOptions as $givenOption) {
$optVal = explode("=", $givenOption);
if (count($optVal) == 2) {
$options_value[$optVal[0]] = $optVal[1];
}
}
}
if (!empty($preconfig)) {
krsort($preconfig);
foreach ($preconfig as $section) {
if (!$show_options_only) {
$output->writeln("<info>Updater questions for " . $section['title'] . "</>");
}
foreach ($section['fields'] as $update_field => $metainfo) {
if (isset($options_value[$update_field])) {
$output->writeln('Setting given parameter "' . $update_field . '" to "' . $options_value[$update_field] . '"');
$_POST[$update_field] = $options_value[$update_field];
continue;
}
$default = null;
$question_text = html_entity_decode(strip_tags($metainfo['label']), ENT_QUOTES | ENT_IGNORE, "UTF-8");
if ($metainfo['type'] == 'checkbox') {
$default = (int)$metainfo['checked'];
if ($show_options_only) {
$update_options[] = [
'name' => $update_field,
'question' => $question_text,
'default' => $default,
'choices' => '0: No' . PHP_EOL . '1: Yes' . PHP_EOL
];
} else {
$question = new ConfirmationQuestion($question_text . ' [' . ($metainfo['checked'] ? 'yes' : 'no') . '] ', (bool)$metainfo['checked'], '/^(y|j)/i');
}
} elseif ($metainfo['type'] == 'select') {
$default = $metainfo['selected'];
$choices = "";
foreach (array_values($metainfo['select_var'] ?? []) as $index => $choice) {
$choices .= $index . ': ' . $choice . PHP_EOL;
}
if ($show_options_only) {
$update_options[] = [
'name' => $update_field,
'question' => $question_text,
'default' => !empty($default) ? $default : '-',
'choices' => $choices
];
} else {
$question = new ChoiceQuestion(
$question_text,
array_values($metainfo['select_var'] ?? []),
$metainfo['selected']
);
$question->setValidator(function ($answer) use ($metainfo): string {
$key = array_keys($metainfo['select_var'])[(int)$answer] ?? false; // Find the key based on the selected value
if ($key === false) {
throw new \RuntimeException('Invalid selection.');
}
return $key;
});
}
} elseif ($metainfo['type'] == 'text') {
$default = $metainfo['value'] ?? '';
if ($show_options_only) {
$update_options[] = [
'name' => $update_field,
'question' => $question_text,
'default' => $default,
'choices' => PHP_EOL
];
} else {
$question = new Question($question_text . (!empty($metainfo['value']) ? ' [' . $metainfo['value'] . ']' : ''), $default);
$question->setValidator(function (string $answer) use ($metainfo): string {
if (($metainfo['mandatory'] ?? false) && empty($answer)) {
throw new \RuntimeException(
'Answer cannot be empty'
);
}
if (!empty($metainfo['pattern'] ?? "") && !preg_match("/" . $metainfo['pattern'] . "/", $answer)) {
throw new \RuntimeException('Answer does not seem to be in valid format');
}
return $answer;
});
}
} else {
$output->writeln("<error>Unknown type " . $metainfo['type'] . "</error>");
continue;
}
if (!$show_options_only) {
if ($yestoall) {
$_POST[$update_field] = $default;
} else {
$_POST[$update_field] = $helper->ask($input, $output, $question);
}
}
}
}
if ($show_options_only) {
$io = new SymfonyStyle($input, $output);
$io->table(
['Parameter', 'Description', 'Default', 'Choices'],
$update_options
);
}
}
}
private function mailNotify(InputInterface $input, OutputInterface $output)
{
if ($input->getOption('mail-notify')) {
$last_check_version = Settings::Get('system.update_notify_last');
if (Update::versionInUpdate($last_check_version, AutoUpdate::getFromResult('version'))) {
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') == 'testing' ? 'testing ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel') . ' ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
$mail = new Mailer(true);
$mail->Body = $text;
$mail->Subject = "[froxlor] " . lng('update.notify_subject');
@@ -182,22 +356,4 @@ final class UpdateCommand extends CliCommand
}
}
}
private function updateDatabase()
{
include_once Froxlor::getInstallDir() . '/lib/tables.inc.php';
define('_CRON_UPDATE', 1);
ob_start([
$this,
'cleanUpdateOutput'
]);
include_once Froxlor::getInstallDir() . '/install/updatesql.php';
ob_end_flush();
return self::SUCCESS;
}
private function cleanUpdateOutput($buffer)
{
return strip_tags(preg_replace("/<br\W*?\/>/", "\n", $buffer));
}
}

View File

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

View File

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

View File

@@ -91,6 +91,9 @@ class ConfigDaemon
$this->fullxml = $xml;
$this->xpath = $xpath;
$this->daemon = $this->fullxml->xpath($this->xpath);
if (count($this->daemon) !== 1) {
throw new Exception('XPath "' . $this->xpath . '" didn\'t return exactly one element');
}
$attributes = $this->daemon[0]->attributes();
if ($attributes['title'] != '') {
$this->title = $this->parseContent((string)$attributes['title']);
@@ -409,7 +412,7 @@ class ConfigDaemon
}
$return[] = [
'type' => 'command',
'content' => $cmd . ' "' . $this->parseContent($attributes['name']) . '" "' . $this->parseContent($attributes['name']) . '.frx.bak"',
'content' => '[ -f ' . $this->parseContent($attributes['name']) . ' ] && ' . $cmd . ' "' . $this->parseContent($attributes['name']) . '" "' . $this->parseContent($attributes['name']) . '.frx.bak"',
'execute' => "pre"
];
}

View File

@@ -117,7 +117,7 @@ class ConfigDisplay
'<SQL_UNPRIVILEGED_PASSWORD>' => 'FROXLOR_MYSQL_PASSWORD',
'<SQL_DB>' => $sql['db'],
'<SQL_HOST>' => $sql['host'],
'<SQL_SOCKET>' => isset($sql['socket']) ? $sql['socket'] : null,
'<SQL_SOCKET>' => $sql['socket'] ?? null,
'<SERVERNAME>' => Settings::Get('system.hostname'),
'<SERVERIP>' => Settings::Get('system.ipaddress'),
'<NAMESERVERS>' => Settings::Get('system.nameservers'),
@@ -127,12 +127,15 @@ class ConfigDisplay
'<VIRTUAL_GID_MAPS>' => Settings::Get('system.vmail_gid'),
'<SSLPROTOCOLS>' => (Settings::Get('system.use_ssl') == '1') ? 'imaps pop3s' : '',
'<CUSTOMER_TMP>' => FileDir::makeCorrectDir($customer_tmpdir),
'<BASE_PATH>' => FileDir::makeCorrectDir(Froxlor::getInstallDir()),
'<BASE_PATH>' => Froxlor::getInstallDir(),
'<BIND_CONFIG_PATH>' => FileDir::makeCorrectDir(Settings::Get('system.bindconf_directory')),
'<WEBSERVER_RELOAD_CMD>' => Settings::Get('system.apachereload_command'),
'<CUSTOMER_LOGS>' => FileDir::makeCorrectDir(Settings::Get('system.logfiles_directory')),
'<FPM_IPCDIR>' => FileDir::makeCorrectDir(Settings::Get('phpfpm.fastcgi_ipcdir')),
'<WEBSERVER_GROUP>' => Settings::Get('system.httpgroup')
'<WEBSERVER_GROUP>' => Settings::Get('system.httpgroup'),
'<SSL_CERT_FILE>' => Settings::Get('system.ssl_cert_file'),
'<SSL_KEY_FILE>' => Settings::Get('system.ssl_key_file'),
'<ADMIN_MAIL>' => Settings::Get('panel.adminmail'),
];
$commands_pre = "";

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