Compare commits

..

380 Commits

Author SHA1 Message Date
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
10555bff76 set version to 2.0.23 for upcoming bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-03 20:16:18 +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
Michael Kaufmann
37aa7af4da check for existing userinfo if settings are being imported via cli
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-02 17:11:06 +02:00
Michael Kaufmann
4b75369597 only check non-admin resources if user is not an admin in navigation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-02 15:53:15 +02:00
Michael Kaufmann
9d0e463906 set version to 2.0.22 for upcoming maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-01 09:58:33 +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
Daniel
a7198f58ce 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:19:32 +02:00
Michael Kaufmann
47be4b2847 remove shortcode for --diff-params in configdiff command
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-12 09:04:58 +02:00
Daniel
b0fae4bd14 Add config-diff CLI Command (#1168)
---------

Co-authored-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-12 09:03:16 +02:00
Michael Kaufmann
4711a41436 correct validation of hostingplan name and description
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-11 13:57:21 +02:00
Michael Kaufmann
faa71ceaef forgot to save one file for the last commit
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-11 12:13:33 +02:00
Michael Kaufmann
2d30394150 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:09:23 +02:00
Michael Kaufmann
99c1182af8 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:03 +02:00
Michael Kaufmann
d9abe58dd2 adjust proftpd config for debian 12 bookworm
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-22 13:00:11 +02:00
Michael Kaufmann
23034b8ad2 rework path to certificates non-ecc/ecc, regardless of current setting
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-21 08:54:29 +02:00
Michael Kaufmann
1cae5638d3 fix optional-flag for IpsAndPorts.add() and IpsAndPorts.update()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-16 17:09:45 +02:00
Michael Kaufmann
ce9a5f97a3 validate non-empy admin-name in Admins.update()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-16 16:44:46 +02:00
Michael Kaufmann
c38b90deef Merge branch 'main' of github.com:Froxlor/Froxlor 2023-07-07 09:52:37 +02:00
Michael Kaufmann
13daa7d6fa set version to 2.0.21 for upcoming maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-07 09:50:50 +02:00
Michael Kaufmann
b0e43d332d validate generated config-json parameter string
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-07 09:47:17 +02:00
jabertwo
75c8754fb4 Fix typo in pathDescriptionSubdomain (#1156) 2023-06-26 11:03:48 +02:00
333 changed files with 16715 additions and 26293 deletions

View File

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

View File

@@ -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'@'%';"

7
.gitignore vendored
View File

@@ -10,6 +10,7 @@ logs/*
.settings/
.test/
*.diff
*.patch
*~
.well-known
.idea
@@ -22,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

@@ -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'
],

View File

@@ -130,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
@@ -171,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',
@@ -189,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',
@@ -278,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'),
@@ -286,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,7 @@ return [
'varname' => 'mod_fcgid_httpuser',
'type' => 'text',
'default' => 'froxlorlocal',
'string_emptyallowed' => false,
'save_method' => 'storeSettingWebserverFcgidFpmUser',
'websrv_avail' => [
'apache2'
@@ -193,6 +194,7 @@ return [
'type' => 'text',
'default' => 'froxlorlocal',
'save_method' => 'storeSettingField',
'string_emptyallowed' => false,
'websrv_avail' => [
'apache2'
],
@@ -243,6 +245,7 @@ return [
'varname' => 'vhost_httpuser',
'type' => 'text',
'default' => 'froxlorlocal',
'string_emptyallowed' => false,
'save_method' => 'storeSettingWebserverFcgidFpmUser',
'visible' => Settings::Get('phpfpm.enabled') && call_user_func([
'\Froxlor\Settings\FroxlorVhostSettings',
@@ -256,6 +259,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

@@ -49,7 +49,7 @@ return [
],
'requires_reconf' => ['http']
],
'system_apache_24' => [
'system_apache24' => [
'label' => lng('serversettings.apache_24'),
'settinggroup' => 'system',
'varname' => 'apache24',

View File

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

@@ -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,146 +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',
'required_otp' => true
]
]
]
]
];

View File

@@ -1,87 +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
*/
return [
'groups' => [
'backup' => [
'title' => lng('backup'),
'icon' => 'fa-solid fa-sliders',
'advanced_mode' => true,
'fields' => [
'backup_enabled' => [
'label' => lng('serversettings.backup_enabled'),
'settinggroup' => 'backup',
'varname' => 'enabled',
'type' => 'checkbox',
'default' => false,
'save_method' => 'storeSettingField',
'overview_option' => true,
'cronmodule' => 'froxlor/backup'
],
'backup_default_storage' => [
'label' => lng('serversettings.backup_default_storage'),
'settinggroup' => 'backup',
'varname' => 'default_storage',
'type' => 'select',
'default' => '1',
'option_options_method' => [
'\\Froxlor\\Backup\\Backup',
'getBackupStorages'
],
'save_method' => 'storeSettingField'
],
'backup_default_retention' => [
'label' => lng('serversettings.backup_default_retention'),
'settinggroup' => 'backup',
'varname' => 'default_retention',
'type' => 'number',
'default' => 3,
'min' => 0,
'save_method' => 'storeSettingField',
],
'backup_default_customer_access' => [
'label' => lng('serversettings.backup_default_customer_access'),
'settinggroup' => 'backup',
'varname' => 'default_customer_access',
'type' => 'checkbox',
'default' => true,
'save_method' => 'storeSettingField',
],
'backup_default_pgp_public_key' => [
'label' => lng('serversettings.backup_default_pgp_public_key'),
'settinggroup' => 'backup',
'varname' => 'default_pgp_public_key',
'type' => 'textarea',
'default' => '',
'save_method' => 'storeSettingField',
'plausibility_check_method' => [
'\\Froxlor\\Validate\\Check',
'checkPgpPublicKeySetting'
],
],
]
]
]
];

View File

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

@@ -1,183 +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
*/
const AREA = 'admin';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\Backups;
use Froxlor\Api\Commands\BackupStorages;
use Froxlor\FroxlorLogger;
use Froxlor\UI\Collection;
use Froxlor\UI\HTML;
use Froxlor\UI\Listing;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
$id = (int)Request::any('id');
if (($page == 'backups' || $page == 'overview')) {
if ($action == '') {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "viewed admin_backups");
try {
$admin_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/admin/tablelisting.backups.php';
$collection = (new Collection(Backups::class, $userinfo))
->withPagination($admin_list_data['backups_list']['columns'], $admin_list_data['backups_list']['default_sorting']);
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $admin_list_data, 'backups_list'),
'actions_links' => [
[
'href' => $linker->getLink(['section' => 'backups', 'page' => $page, 'action' => 'restore']),
'label' => lng('admin.backups_restore'),
'icon' => 'fa-solid fa-file-import',
'class' => 'btn-outline-secondary'
],
[
'href' => $linker->getLink(['section' => 'backups', 'page' => 'storages']),
'label' => lng('admin.backup_storages'),
'icon' => 'fa-solid fa-hard-drive',
'class' => 'btn-outline-secondary',
'visible' => $userinfo['change_serversettings'] == '1'
]
]
]);
} elseif ($action == 'delete' && $id != 0) {
} elseif ($action == 'add') {
} elseif ($action == 'edit' && $id != 0) {
} elseif ($action == 'restore') {
}
} else if ($page == 'storages' && $userinfo['change_serversettings'] == '1') {
if ($action == '') {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "list backup storages");
try {
$backup_storage_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/admin/tablelisting.backup_storages.php';
$collection = (new Collection(BackupStorages::class, $userinfo))
->withPagination($backup_storage_list_data['backup_storages_list']['columns'], $backup_storage_list_data['backup_storages_list']['default_sorting']);
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $backup_storage_list_data, 'backup_storages_list'),
'actions_links' => [
[
'href' => $linker->getLink(['section' => 'backups', 'page' => 'backups']),
'label' => lng('admin.backups'),
'icon' => 'fa-solid fa-reply'
],
[
'href' => $linker->getLink(['section' => 'backups', 'page' => $page, 'action' => 'add']),
'label' => lng('admin.backup_storage_add')
]
]
]);
} elseif ($action == 'delete' && $id != 0) {
try {
$json_result = BackupStorages::getLocal($userinfo, [
'id' => $id
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
if ($result['id'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
BackupStorages::getLocal($userinfo, [
'id' => $id
])->delete();
Response::redirectTo($filename, [
'page' => $page
]);
} else {
HTML::askYesNo('backup_backup_server_reallydelete', $filename, [
'id' => $id,
'page' => $page,
'action' => $action
], $result['id']);
}
}
} elseif ($action == 'add') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
try {
BackupStorages::getLocal($userinfo, $_POST)->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page
]);
} else {
$admin_add_data = include_once dirname(__FILE__) . '/lib/formfields/admin/backup_storages/formfield.backup_storage_add.php';
UI::view('user/form.html.twig', [
'formaction' => $linker->getLink(['section' => 'backups']),
'formdata' => $admin_add_data['backup_storage_add']
]);
}
} elseif ($action == 'edit' && $id != 0) {
try {
$json_result = BackupStorages::getLocal($userinfo, [
'id' => $id
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
if ($result['id'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
try {
BackupStorages::getLocal($userinfo, $_POST)->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page
]);
} else {
$backup_storage_edit_data = include_once dirname(__FILE__) . '/lib/formfields/admin/backup_storages/formfield.backup_storage_edit.php';
UI::view('user/form.html.twig', [
'formaction' => $linker->getLink(['section' => 'backups', 'id' => $id]),
'formdata' => $backup_storage_edit_data['backup_storage_edit'],
'editid' => $id
]);
}
}
}
} else {
Response::dynamicError('403');
}

View File

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

@@ -27,7 +27,6 @@ const AREA = 'admin';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\Admins;
use Froxlor\Api\Commands\BackupStorages;
use Froxlor\Api\Commands\Customers;
use Froxlor\Api\Commands\MysqlServer;
use Froxlor\CurrentUser;
@@ -99,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";
@@ -120,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
@@ -148,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());
@@ -168,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());
}
@@ -226,23 +225,6 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
$hosting_plans[$row['id']] = $row['name'];
}
// backup storages
$backup_storages = [];
if (Settings::Get('backup.enabled') == '1' && $userinfo['change_serversettings'] == '1') {
$backup_storages = [
0 => lng('backup.storage_none')
];
try {
$result_json = BackupStorages::getLocal($userinfo)->listing();
$result_decoded = json_decode($result_json, true)['data']['list'];
foreach ($result_decoded as $storagedata) {
$backup_storages[$storagedata['id']] = "[" . $storagedata['type'] . "] " . html_entity_decode($storagedata['description']);
}
} catch (Exception $e) {
/* just none */
}
}
$customer_add_data = include_once dirname(__FILE__) . '/lib/formfields/admin/customer/formfield.customer_add.php';
UI::view('user/form.html.twig', [
@@ -261,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());
}
@@ -325,36 +307,22 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
$hosting_plans[$row['id']] = $row['name'];
}
// backup storages
$backup_storages = [];
if (Settings::Get('backup.enabled') == '1' && $userinfo['change_serversettings'] == '1') {
$backup_storages = [
0 => lng('backup.storage_none')
$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 => "---"
];
try {
$result_json = BackupStorages::getLocal($userinfo)->listing();
$result_decoded = json_decode($result_json, true)['data']['list'];
foreach ($result_decoded as $storagedata) {
$backup_storages[$storagedata['id']] = "[" . $storagedata['type'] . "] " . html_entity_decode($storagedata['description']);
}
} catch (Exception $e) {
/* just none */
while ($available_admin = $available_admins_stmt->fetch()) {
$admin_select[$available_admin['adminid']] = $available_admin['name'] . " (" . $available_admin['loginname'] . ")";
}
}
$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';
UI::view('user/form.html.twig', [

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,7 +113,7 @@ if ($page == 'domains' || $page == 'overview') {
} elseif ($alias_check['count'] > 0) {
Response::standardError('domains_cantdeletedomainwithaliases');
} else {
HTML::askYesNo('admin_domain_reallydelete', $filename, [
HTML::askYesNoWithCheckbox('admin_domain_reallydelete', 'admin_customer_alsoremovemail', $filename, [
'id' => $id,
'page' => $page,
'action' => $action
@@ -122,9 +121,9 @@ if ($page == 'domains' || $page == 'overview') {
}
}
} 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());
}
@@ -320,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, [
@@ -356,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());
}
@@ -573,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
@@ -594,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'];
@@ -637,16 +653,16 @@ if ($page == 'domains' || $page == 'overview') {
]);
}
} elseif ($action == 'duplicate') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
Domains::getLocal($userinfo, $_POST)->duplicate();
Domains::getLocal($userinfo, Request::postAll())->duplicate();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page,
'searchfield' => 'd.domain_ace',
'searchtext' => $_POST['domain'] ?? ""
'searchtext' => Request::post('domain', "")
]);
} else {
Response::redirectTo($filename, [

View File

@@ -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) {
@@ -201,16 +201,16 @@ if ($page == 'overview') {
$languages = Language::getLanguages();
if (!empty($_POST)) {
if ($_POST['send'] == 'changepassword') {
$old_password = Validate::validate($_POST['old_password'], 'old password');
if (Request::post('send') == 'changepassword') {
$old_password = Validate::validate(Request::post('old_password'), 'old password');
if (!Crypt::validatePasswordLogin($userinfo, $old_password, TABLE_PANEL_ADMINS, 'adminid')) {
Response::standardError('oldpasswordnotcorrect');
}
try {
$new_password = Crypt::validatePassword($_POST['new_password'], 'new password');
$new_password_confirm = Crypt::validatePassword($_POST['new_password_confirm'], 'new password confirm');
$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());
}
@@ -244,9 +244,9 @@ if ($page == 'overview') {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, 'changed password');
Response::redirectTo($filename);
}
} elseif ($_POST['send'] == 'changetheme') {
} elseif (Request::post('send') == 'changetheme') {
if (Settings::Get('panel.allow_theme_change_admin') == 1) {
$theme = Validate::validate($_POST['theme'], 'theme');
$theme = Validate::validate(Request::post('theme'), 'theme');
try {
Admins::getLocal($userinfo, [
'id' => $userinfo['adminid'],
@@ -259,8 +259,8 @@ if ($page == 'overview') {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "changed his/her theme to '" . $theme . "'");
}
Response::redirectTo($filename);
} elseif ($_POST['send'] == 'changelanguage') {
$def_language = Validate::validate($_POST['def_language'], 'default language');
} elseif (Request::post('send') == 'changelanguage') {
$def_language = Validate::validate(Request::post('def_language'), 'default language');
if (isset($languages[$def_language])) {
try {

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

@@ -35,10 +35,11 @@ require __DIR__ . '/lib/init.php';
use Froxlor\FroxlorLogger;
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([

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,12 +69,12 @@ 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) {
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,
@@ -97,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);
@@ -140,7 +140,7 @@ if ($page == 'overview' && $userinfo['change_serversettings'] == '1') {
'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);
@@ -158,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', [
@@ -170,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'");
@@ -181,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
@@ -194,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 . "`");
@@ -233,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
]);
@@ -273,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();
@@ -285,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"]);
@@ -330,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

@@ -46,18 +46,17 @@
"ext-fileinfo": "*",
"ext-gmp": "*",
"ext-gd": "*",
"ext-ftp": "*",
"phpmailer/phpmailer": "~6.0",
"monolog/monolog": "^1.24",
"robthree/twofactorauth": "^1.6",
"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",
"amnuts/opcache-gui": "^3.4"
},
"amnuts/opcache-gui": "^3.4",
"league/commonmark": "^2.4"
},
"require-dev": {
"phpunit/phpunit": "^9",
"ext-pcntl": "*",
@@ -73,9 +72,15 @@
"suggest": {
"ext-bcmath": "*",
"ext-zip": "*",
"ext-gnupg": "*",
"ext-apcu": "*",
"ext-readline": "*"
},
"config": {
"platform": {
"php": "7.4"
}
},
"autoload": {
"psr-4": {
"Froxlor\\": [
@@ -84,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"
}

1375
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')) {
@@ -63,16 +63,21 @@ 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')
];
}
$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';
@@ -101,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());
}
@@ -122,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());
}
@@ -136,7 +141,6 @@ 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, [
@@ -239,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());
}
@@ -390,8 +394,8 @@ if ($page == 'overview' || $page == 'domains') {
Response::standardError('domains_canteditdomain');
}
} elseif ($action == 'jqSpeciallogfileNote') {
$domainid = intval($_POST['id']);
$newval = intval($_POST['newval']);
$domainid = intval(Request::post('id'));
$newval = intval(Request::post('newval'));
try {
$json_result = SubDomains::getLocal($userinfo, [
'id' => $domainid

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

@@ -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());
}
@@ -290,10 +306,10 @@ if ($page == 'overview' || $page == 'htpasswds') {
if (Settings::Get('system.exportenabled') == 1) {
if ($action == 'abort') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "customer_extras::export - aborted scheduled data export job");
try {
DataDump::getLocal($userinfo, $_POST)->delete();
DataDump::getLocal($userinfo, Request::postAll())->delete();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -320,9 +336,9 @@ if ($page == 'overview' || $page == 'htpasswds') {
Response::dynamicError($e->getMessage());
}
if (isset($_POST['send']) && $_POST['send'] == 'send') {
if (Request::post('send') == 'send') {
try {
DataDump::getLocal($userinfo, $_POST)->add();
DataDump::getLocal($userinfo, Request::postAll())->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
@@ -331,9 +347,19 @@ if ($page == 'overview' || $page == 'htpasswds') {
$pathSelect = FileDir::makePathfield($userinfo['documentroot'], $userinfo['guid'], $userinfo['guid']);
$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' => $export_data['export'],
'actions_links' => $actions_links,
'tabledata' => Listing::format($collection, $export_list_data, 'export_list'),
]);
}

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

@@ -30,6 +30,7 @@ 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;
@@ -37,6 +38,7 @@ 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;
@@ -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";
@@ -115,8 +117,8 @@ if ($page == 'overview') {
$userinfo['traffic_bytes_used'] = $userinfo['traffic_used'] * 1024;
if (Settings::Get('system.mail_quota_enabled')) {
$userinfo['email_quota_bytes'] = ($userinfo['email_quota'] > -1) ? $userinfo['email_quota'] * 1024 : -1;
$userinfo['email_quota_bytes_used'] = $userinfo['email_quota_used'] * 1024;
$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) {
@@ -140,16 +142,16 @@ if ($page == 'overview') {
$languages = Language::getLanguages();
if (!empty($_POST)) {
if ($_POST['send'] == 'changepassword') {
$old_password = Validate::validate($_POST['old_password'], 'old password');
if (Request::post('send') == 'changepassword') {
$old_password = Validate::validate(Request::post('old_password'), 'old password');
if (!Crypt::validatePasswordLogin($userinfo, $old_password, TABLE_PANEL_CUSTOMERS, 'customerid')) {
Response::standardError('oldpasswordnotcorrect');
}
try {
$new_password = Crypt::validatePassword($_POST['new_password'], 'new password');
$new_password_confirm = Crypt::validatePassword($_POST['new_password_confirm'], 'new password confirm');
$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());
}
@@ -184,7 +186,7 @@ if ($page == 'overview') {
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, 'changed password');
// Update ftp password
if (isset($_POST['change_main_ftp']) && $_POST['change_main_ftp'] == 'true') {
if (Request::post('change_main_ftp') == 'true') {
$cryptPassword = Crypt::makeCryptPassword($new_password);
$stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "`
SET `password` = :password
@@ -200,7 +202,7 @@ if ($page == 'overview') {
}
// Update statistics password
if (isset($_POST['change_stats']) && $_POST['change_stats'] == 'true') {
if (Request::post('change_stats') == 'true') {
$new_stats_password = Crypt::makeCryptPassword($new_password, true);
$stmt = Database::prepare("UPDATE `" . TABLE_PANEL_HTPASSWDS . "`
@@ -216,11 +218,32 @@ if ($page == 'overview') {
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 ($_POST['send'] == 'changetheme') {
} elseif (Request::post('send') == 'changetheme') {
if (Settings::Get('panel.allow_theme_change_customer') == 1) {
$theme = Validate::validate($_POST['theme'], 'theme');
$theme = Validate::validate(Request::post('theme'), 'theme');
try {
Customers::getLocal($userinfo, [
'id' => $userinfo['customerid'],
@@ -233,8 +256,8 @@ if ($page == 'overview') {
$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "changed default theme to '" . $theme . "'");
}
Response::redirectTo($filename);
} elseif ($_POST['send'] == 'changelanguage') {
$def_language = Validate::validate($_POST['def_language'], 'default language');
} elseif (Request::post('send') == 'changelanguage') {
$def_language = Validate::validate(Request::post('def_language'), 'default language');
if (isset($languages[$def_language])) {
try {
Customers::getLocal($userinfo, [

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 {

213
index.php
View File

@@ -54,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');
@@ -62,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') {
@@ -71,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)) {
@@ -106,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"
@@ -163,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
]);
@@ -195,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
]);
@@ -205,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();
@@ -219,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
]);
@@ -232,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
]);
@@ -249,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'
@@ -258,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
]);
@@ -282,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]
@@ -294,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]
@@ -306,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', [
@@ -317,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];
@@ -327,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
@@ -383,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 = '';
@@ -420,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;
}
@@ -456,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");
@@ -624,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');
}
@@ -654,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);
@@ -671,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();
}
@@ -801,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,8 +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,
`backup` int(11) NOT NULL default '1',
`access_backups` tinyint(1) NOT NULL default '1',
`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;
@@ -301,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`;
@@ -358,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,
@@ -399,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', ''),
@@ -512,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
@@ -564,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'),
@@ -581,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', ''),
@@ -656,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'),
@@ -698,20 +685,15 @@ opcache.validate_timestamps'),
('system', 'distribution', ''),
('system', 'update_channel', 'stable'),
('system', 'updatecheck_data', ''),
('system', 'update_notify_last', '2.0.20'),
('system', 'update_notify_last', ''),
('system', 'traffictool', 'goaccess'),
('system', 'req_limit_per_interval', 60),
('system', 'req_limit_interval', 60),
('backup', 'enabled', 0),
('backup', 'default_storage', '1'),
('backup', 'default_customer_access', '1'),
('backup', 'default_pgp_public_key', ''),
('backup', 'default_retention', '3'),
('api', 'enabled', '0'),
('api', 'customer_default', '1'),
('2fa', 'enabled', '1'),
('panel', 'decimal_places', '4'),
('panel', 'adminmail', 'admin@SERVERNAME'),
('panel', 'adminmail', 'ADMIN_MAIL'),
('panel', 'phpmyadmin_url', ''),
('panel', 'webmail_url', ''),
('panel', 'webftp_url', ''),
@@ -750,8 +732,9 @@ opcache.validate_timestamps'),
('panel', 'logo_overridetheme', '0'),
('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'),
('panel', 'version', '2.0.20'),
('panel', 'db_version', '202305240');
('panel', 'menu_collapsed', '1'),
('panel', 'version', '2.2.7'),
('panel', 'db_version', '202412030');
DROP TABLE IF EXISTS `panel_tasks`;
@@ -774,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;
@@ -920,8 +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/export', 'export', '\\Froxlor\\Cron\\System\\ExportCron', '1 HOUR', '0', 'cron_export'),
(7, 'froxlor/backup', 'backup', '\\Froxlor\\Cron\\Backup\\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`;
@@ -1071,36 +1054,13 @@ CREATE TABLE `panel_loginlinks` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
DROP TABLE IF EXISTS `panel_backup_storages`;
CREATE TABLE `panel_backup_storages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(255) NOT NULL,
`type` varchar(255) NOT NULL DEFAULT 'local',
`region` varchar(255) NULL,
`bucket` varchar(255) NULL,
`destination_path` varchar(255) NOT NULL,
`hostname` varchar(255) NULL,
`username` varchar(255) NULL,
`password` text,
`pgp_public_key` text,
`retention` int(3) NOT NULL DEFAULT 3,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
INSERT INTO `panel_backup_storages` (`id`, `description`, `destination_path`) VALUES
(1, 'Local backup storage', '/var/customers/backups');
DROP TABLE IF EXISTS `panel_backups`;
CREATE TABLE `panel_backups` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`adminid` int(11) NOT NULL,
`customerid` int(11) NOT NULL,
`loginname` varchar(255) NOT NULL,
`size` bigint(20) NOT NULL,
`storage_id` int(11) NOT NULL,
`filename` varchar(255) NOT NULL,
`created_at` int(15) NOT NULL,
PRIMARY KEY (`id`)
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

@@ -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,35 +120,12 @@ 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;
Settings::AddNew("panel.settings_mode", $panel_settings_mode);
$system_distribution = isset($_POST['system_distribution']) ? $_POST['system_distribution'] : '';
$system_distribution = isset($_POST['system_distribution']) ? $_POST['system_distribution'] : 'bullseye';
Settings::AddNew("system.distribution", $system_distribution);
Settings::AddNew("system.update_channel", 'stable');
Settings::AddNew("system.updatecheck_data", '');
@@ -497,3 +473,23 @@ if (Froxlor::isFroxlorVersion('2.0.19')) {
Update::showUpdateStep("Updating from 2.0.19 to 2.0.20", false);
Froxlor::updateToVersion('2.0.20');
}
if (Froxlor::isFroxlorVersion('2.0.20')) {
Update::showUpdateStep("Updating from 2.0.20 to 2.0.21", false);
Froxlor::updateToVersion('2.0.21');
}
if (Froxlor::isFroxlorVersion('2.0.21')) {
Update::showUpdateStep("Updating from 2.0.21 to 2.0.22", false);
Froxlor::updateToVersion('2.0.22');
}
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

@@ -36,9 +36,10 @@ if (!defined('_CRON_UPDATE')) {
}
}
if (Froxlor::isDatabaseVersion('202304260')) {
//Update::showUpdateStep("Cleaning domains table");
//Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` DROP COLUMN `ismainbutsubto`;");
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");
@@ -53,6 +54,10 @@ if (Froxlor::isDatabaseVersion('202304260')) {
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)) {
@@ -62,80 +67,188 @@ if (Froxlor::isDatabaseVersion('202304260')) {
Update::lastStepStatus(1, 'Customized setting, not changing');
}
Update::showUpdateStep("Creating new tables and fields for backups");
Database::query("DROP TABLE IF EXISTS `". TABLE_PANEL_BACKUP_STORAGES ."`;");
$sql = "CREATE TABLE `". TABLE_PANEL_BACKUP_STORAGES ."` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(255) NOT NULL,
`type` varchar(255) NOT NULL DEFAULT 'local',
`region` varchar(255) NULL,
`bucket` varchar(255) NULL,
`destination_path` varchar(255) NOT NULL,
`hostname` varchar(255) NULL,
`username` varchar(255) NULL,
`password` text,
`pgp_public_key` text,
`retention` int(3) NOT NULL DEFAULT 3,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
Database::query($sql);
Database::query("
INSERT INTO `panel_backup_storages` (`id`, `description`, `destination_path`) VALUES
(1, 'Local backup storage', '/var/customers/backups');
");
Database::query("DROP TABLE IF EXISTS `". TABLE_PANEL_BACKUPS ."`;");
$sql = "CREATE TABLE `". TABLE_PANEL_BACKUPS ."` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`adminid` int(11) NOT NULL,
`customerid` int(11) NOT NULL,
`loginname` varchar(255) NOT NULL,
`size` bigint(20) NOT NULL,
`storage_id` int(11) NOT NULL,
`filename` varchar(255) NOT NULL,
`created_at` int(15) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
Database::query($sql);
// add customer backup-target-storage
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` ADD `backup` int(11) NOT NULL default '1' AFTER `allowed_mysqlserver`;");
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` ADD `access_backups` tinyint(1) NOT NULL default '1' AFTER `backup`;");
Update::lastStepStatus(0);
Update::showUpdateStep("Adding new backup settings");
Settings::AddNew('backup.enabled', 0);
Settings::AddNew('backup.default_storage', 1);
Settings::AddNew('backup.default_customer_access', 1);
Settings::AddNew('backup.default_pgp_public_key', '');
Settings::AddNew('backup.default_retention', 3);
Update::lastStepStatus(0);
Update::showUpdateStep("Adjusting cronjobs");
Database::query("
$cfupd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET
`module`= 'froxlor/export',
`cronfile` = 'export',
`cronclass` = '\\Froxlor\\Cron\\System\\ExportCron',
`cronclass` = :cc,
`interval` = '1 HOUR',
`desc_lng_key` = 'cron_export'
WHERE `module` = 'froxlor/backup'
");
Database::query("
INSERT INTO `" . TABLE_PANEL_CRONRUNS . "` SET
`module`= 'froxlor/backup',
`cronfile` = 'backup',
`cronclass` = '\\Froxlor\\Cron\\Backup\\BackupCron',
`interval` = '1 DAY',
`isactive` = '0',
`desc_lng_key` = 'cron_backup'
");
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 `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,257 @@
<?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');
}

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

@@ -54,7 +54,7 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
$config_dir = FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/lib/configfiles/');
// show list of available distro's
$distros = glob($config_dir . '*.xml');
$distributions_select[''] = '-';
// selection is required $distributions_select[''] = '-';
// read in all the distros
foreach ($distros as $_distribution) {
// get configparser object

View File

@@ -36,19 +36,7 @@ $preconfig = [
$return = [];
if (Update::versionInUpdate($current_version, '2.1.0-dev1')) {
// Backup
$description = 'Froxlor now comes with a backup capability (More info see [DOCS LINK].';
$question = '<strong>Would you like to enable the backup-feature (default: yes)</strong>';
$return['panel_settings_mode'] = [
'type' => 'select',
'select_var' => [
0 => 'No',
1 => 'Yes'
],
'selected' => 1,
'label' => $question,
'prior_infotext' => $description
];
}
$preconfig['fields'] = $return;

View File

@@ -23,28 +23,26 @@
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor\UI\Callbacks;
use Froxlor\Install\Update;
use Froxlor\Database\Database;
use Froxlor\UI\Panel\UI;
$preconfig = [
'title' => '2.2.x updates',
'fields' => []
];
$return = [];
class Backup
{
public static function backupStorageLink(array $attributes)
{
$sel_stmt = Database::prepare("SELECT `description` FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` WHERE `id` = :id");
$backupstorage = Database::pexecute_first($sel_stmt, ['id' => $attributes['data']]);
if ((int)UI::getCurrentUser()['adminsession'] == 1 && UI::getCurrentUser()['change_serversettings']) {
$linker = UI::getLinker();
$result = '<a href="' . $linker->getLink([
'section' => 'backups',
'page' => 'storages',
'searchfield' => 'id',
'searchtext' => $attributes['data'],
]) . '">' . $backupstorage['description'] . '</a>';
} else {
$result = $backupstorage['description'];
}
return $result;
}
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

@@ -55,6 +55,7 @@ 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.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

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

@@ -1,487 +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
*/
namespace Froxlor\Api\Commands;
use Exception;
use Froxlor\Api\ApiCommand;
use Froxlor\Api\ResourceEntity;
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
use Froxlor\UI\Response;
use Froxlor\Validate\Validate;
use PDO;
/**
* @since 2.1.0
*/
class BackupStorages extends ApiCommand implements ResourceEntity
{
const SUPPORTED_TYPES = [
'local',
'ftp',
'sftp',
'rsync',
's3',
];
/**
* lists all backup storages entries
*
* @param array $sql_search
* optional array with index = fieldname, and value = array with 'op' => operator (one of <, > or =),
* LIKE is used if left empty and 'value' => searchvalue
* @param int $sql_limit
* optional specify number of results to be returned
* @param int $sql_offset
* optional specify offset for resultset
* @param array $sql_orderby
* optional array with index = fieldname and value = ASC|DESC to order the resultset by one or more
* fields
*
* @access admin
* @return string json-encoded array count|list
* @throws Exception
*/
public function listing()
{
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list backup storages");
$query_fields = [];
$result_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` ". $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit()
);
Database::pexecute($result_stmt, $query_fields, true, true);
$result = [];
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $row;
}
return $this->response([
'count' => count($result),
'list' => $result
]);
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* returns the total number of backup storages
*
* @access admin
* @return string json-encoded response message
* @throws Exception
*/
public function listingCount()
{
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
$result_stmt = Database::prepare("
SELECT COUNT(*) as num_backup_storagess
FROM `" . TABLE_PANEL_BACKUP_STORAGES . "`
");
$result = Database::pexecute_first($result_stmt, null, true, true);
if ($result) {
return $this->response($result['num_backup_storagess']);
}
$this->response(0);
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* create a backup storage
*
* @param string $type
* required, backup storage type
* @param string $destination_path
* required, destination path for backup storage
* @param string $description
* required, description for backup storage
* @param string $region
* optional, required if type=s3. Region for backup storage (used for S3)
* @param string $bucket
* optional, required if type=s3. Bucket for backup storage (used for S3)
* @param string $hostname
* optional, required if type != local. Hostname for backup storage
* @param string $username
* optional, required if type != local. Username for backup storage (also used as access key for S3)
* @param string $password
* optional, required if type != local. Password for backup storage (also used as secret key for S3)
* @param string $pgp_public_key
* optional, pgp public key for backup storage
* @param string $retention
* optional, retention for backup storage (default 3)
*
* @access admin
* @return string json-encoded array
* @throws Exception
*/
public function add()
{
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
// required parameters
$type = $this->getParam('type');
$destination_path = $this->getParam('destination_path');
$description = $this->getParam('description');
// type related requirements
$optional_flags = [
'region' => true,
'bucket' => true,
'hostname' => true,
'username' => true,
'password' => true,
];
if (!in_array($type, self::SUPPORTED_TYPES)) {
throw new Exception("Unsupported storage type: '" . $type . "'", 406);
}
if ($type != 'local') {
$optional_flags['hostname'] = false;
$optional_flags['username'] = false;
$optional_flags['password'] = false;
}
if ($type == 's3') {
$optional_flags['region'] = false;
$optional_flags['bucket'] = false;
}
// parameters
$region = $this->getParam('region', $optional_flags['region']);
$bucket = $this->getParam('bucket', $optional_flags['bucket']);
$hostname = $this->getParam('hostname', $optional_flags['hostname']);
$username = $this->getParam('username', $optional_flags['username']);
$password = $this->getParam('password', $optional_flags['password']);
$pgp_public_key = $this->getParam('pgp_public_key', true, null);
$retention = $this->getParam('retention', true, 3);
// validation
$destination_path = FileDir::makeCorrectDir(Validate::validate($destination_path, 'destination_path', Validate::REGEX_DIR, '', [], true));
// TODO: add more validation
// pgp public key validation
if (!empty($pgp_public_key)) {
// check if gnupg extension is loaded
if (!extension_loaded('gnupg')) {
Response::standardError('gnupgextensionnotavailable', '', true);
}
// check if the pgp public key is a valid key
putenv('GNUPGHOME=' . sys_get_temp_dir());
if (gnupg_import(gnupg_init(), $pgp_public_key) === false) {
Response::standardError('invalidpgppublickey', '', true);
}
}
// store
$stmt = Database::prepare("
INSERT INTO `" . TABLE_PANEL_BACKUP_STORAGES . "` (
`description`,
`type`,
`region`,
`bucket`,
`destination_path`,
`hostname`,
`username`,
`password`,
`pgp_public_key`,
`retention`
) VALUES (
:description,
:type,
:region,
:bucket,
:destination_path,
:hostname,
:username,
:password,
:pgp_public_key,
:retention
)
");
$params = [
"description" => $description,
"type" => $type,
"region" => $region,
"bucket" => $bucket,
"destination_path" => $destination_path,
"hostname" => $hostname,
"username" => $username,
"password" => $password,
"pgp_public_key" => $pgp_public_key,
"retention" => $retention,
];
Database::pexecute($stmt, $params, true, true);
$id = Database::lastInsertId();
// return
$result = $this->apiCall('BackupStorages.get', [
'id' => $id
]);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] added backup storage '" . $result['description'] . "' (" . $result['type'] . ")");
return $this->response($result);
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* return a backup storage entry by id
*
* @param int $id
* the backup-storage-id
*
* @access admin
* @return string json-encoded array
* @throws Exception
*/
public function get()
{
$id = $this->getParam('id');
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
$result_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "`
WHERE `id` = :id"
);
$params = [
'id' => $id
];
$result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get backup storage '" . $result['description'] . "'");
return $this->response($result);
}
throw new Exception("Backup storage with " . $id . " could not be found", 404);
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* update a backup storage by given id
*
* @param int $id
* required, the backup-storage-id
* @param string $type
* optional, backup storage type
* @param string $destination_path
* optional, destination path for backup storage
* @param string $description
* required, description for backup storage
* @param string $region
* optional, region for backup storage (used for S3)
* @param string $bucket
* optional, bucket for backup storage (used for S3)
* @param string $hostname
* optional, hostname for backup storage
* @param string $username
* optional, username for backup storage (also used as access key for S3)
* @param string $password
* optional, password for backup storage (also used as secret key for S3)
* @param string $pgp_public_key
* optional, pgp public key for backup storage
* @param string $retention
* optional, retention for backup storage (default 3)
*
* @access admin
* @return string json-encoded array
* @throws Exception
*/
public function update()
{
$id = $this->getParam('id');
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
// validation
$result = $this->apiCall('BackupStorages.get', [
'id' => $id
]);
// parameters
$description = $this->getParam('description', true, $result['description']);
$type = $this->getParam('type', true, $result['type']);
$region = $this->getParam('region', true, $result['region']);
$bucket = $this->getParam('bucket', true, $result['bucket']);
$destination_path = $this->getParam('destination_path', true, $result['destination_path']);
$hostname = $this->getParam('hostname', true, $result['hostname']);
$username = $this->getParam('username', true, $result['username']);
$password = $this->getParam('password', true, '');
$pgp_public_key = $this->getParam('pgp_public_key', true, $result['pgp_public_key']);
$retention = $this->getParam('retention', true, $result['retention']);
if (!in_array($type, self::SUPPORTED_TYPES)) {
throw new Exception("Unsupported storage type: '" . $type . "'", 406);
}
if ($type != 'local') {
if (empty($hostname)) {
throw new Exception("Field 'hostname' cannot be empty", 406);
}
if (empty($username)) {
throw new Exception("Field 'username' cannot be empty", 406);
}
$password = Validate::validate($password, 'password', '', '', [], true);
}
if ($type == 's3') {
if (empty($region)) {
throw new Exception("Field 'region' cannot be empty", 406);
}
if (empty($bucket)) {
throw new Exception("Field 'bucket' cannot be empty", 406);
}
}
// validation
$destination_path = FileDir::makeCorrectDir(Validate::validate($destination_path, 'destination_path', Validate::REGEX_DIR, '', [], true));
// TODO: add more validation
// pgp public key validation
if (!empty($pgp_public_key) && $pgp_public_key != $result['pgp_public_key']) {
// check if gnupg extension is loaded
if (!extension_loaded('gnupg')) {
Response::standardError('gnupgextensionnotavailable', '', true);
}
// check if the pgp public key is a valid key
putenv('GNUPGHOME=' . sys_get_temp_dir());
if (gnupg_import(gnupg_init(), $pgp_public_key) === false) {
Response::standardError('invalidpgppublickey', '', true);
}
}
if (!empty($password)) {
$stmt = Database::prepare("UPDATE `" . TABLE_PANEL_BACKUP_STORAGES . "`
SET `password` = :password
WHERE `id` = :id
");
Database::pexecute($stmt, [
"id" => $id,
"password" => $password
], true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] updated password for backup-storage '" . $result['description'] . "'");
}
// update
$stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_BACKUP_STORAGES . "`
SET `description` = :description,
`type` = :type,
`region` = :region,
`bucket` = :bucket,
`destination_path` = :destination_path,
`hostname` = :hostname,
`username` = :username,
`pgp_public_key` = :pgp_public_key,
`retention` = :retention
WHERE `id` = :id
");
$params = [
"id" => $id,
"description" => $description,
"type" => $type,
"region" => $region,
"bucket" => $bucket,
"destination_path" => $destination_path,
"hostname" => $hostname,
"username" => $username,
"pgp_public_key" => $pgp_public_key,
"retention" => $retention,
];
Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] edited backup storage '" . $result['description'] . "'");
// return
$result = $this->apiCall('BackupStorages.get', [
'id' => $id
]);
return $this->response($result);
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* delete a backup-storage entry by id
*
* @param int $id
* required, the backup-storage-id
*
* @access admin
* @return string json-encoded array
* @throws Exception
*/
public function delete()
{
$id = $this->getParam('id');
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
// validation
$result = $this->apiCall('BackupStorages.get', [
'id' => $id
]);
// validate no-one's using it
// settings
if ($id == Settings::Get('backup.default_storage')) {
throw new Exception("Given backup storage is currently set as default storage and cannot be deleted.", 406);
}
// customers
$sel_stmt = Database::prepare("
SELECT COUNT(*) as num_storage_users
FROM `" . TABLE_PANEL_CUSTOMERS . "`
WHERE `backup` = :id
");
$storage_users_result = Database::pexecute_first($sel_stmt, ['id' => $id]);
if ($storage_users_result && $storage_users_result['num_storage_users'] > 0) {
throw new Exception("Given backup storage is currently assigned to " . $storage_users_result['num_storage_users'] . " customers and cannot be deleted.", 406);
}
// existing backups
$sel_stmt = Database::prepare("
SELECT COUNT(*) as num_storage_backups
FROM `" . TABLE_PANEL_BACKUPS . "`
WHERE `storage_id` = :id
");
$storage_backups_result = Database::pexecute_first($sel_stmt, ['id' => $id]);
if ($storage_backups_result && $storage_backups_result['num_storage_backups'] > 0) {
throw new Exception("Given backup storage has still " . $storage_backups_result['num_storage_backups'] . " backups on it and cannot be deleted.", 406);
}
// delete
$stmt = Database::prepare("
DELETE FROM `" . TABLE_PANEL_BACKUP_STORAGES . "`
WHERE `id` = :id
");
$params = [
"id" => $id
];
Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] deleted backup storage '" . $result['description'] . "'");
// return
return $this->response(true);
}
throw new Exception("Not allowed to execute given command.", 403);
}
}

View File

@@ -1,211 +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
*/
namespace Froxlor\Api\Commands;
use Exception;
use Froxlor\Api\ApiCommand;
use Froxlor\Api\ResourceEntity;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
use PDO;
/**
* @since 2.1.0
*/
class Backups extends ApiCommand implements ResourceEntity
{
/**
* lists all admin entries
*
* @param array $sql_search
* optional array with index = fieldname, and value = array with 'op' => operator (one of <, > or =),
* LIKE is used if left empty and 'value' => searchvalue
* @param int $sql_limit
* optional specify number of results to be returned
* @param int $sql_offset
* optional specify offset for resultset
* @param array $sql_orderby
* optional array with index = fieldname and value = ASC|DESC to order the resultset by one or more
* fields
*
* @access admin
* @return string json-encoded array count|list
* @throws Exception
*/
public function listing()
{
if ($this->isAdmin()) {
// if we're an admin, list all backups of all the admins customers
// or optionally for one specific customer identified by id or loginname
$customerid = $this->getParam('customerid', true, 0);
$loginname = $this->getParam('loginname', true, '');
if (!empty($customerid) || !empty($loginname)) {
$result = $this->apiCall('Customers.get', [
'id' => $customerid,
'loginname' => $loginname
]);
$custom_list_result = [
$result
];
} else {
$_custom_list_result = $this->apiCall('Customers.listing');
$custom_list_result = $_custom_list_result['list'];
}
$customer_ids = [];
foreach ($custom_list_result as $customer) {
$customer_ids[] = $customer['customerid'];
}
if (empty($customer_ids)) {
throw new Exception("Required resource unsatisfied.", 405);
}
} else {
$customer_ids = [
$this->getUserDetail('customerid')
];
}
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list backups");
$query_fields = [];
$result_stmt = Database::prepare("
SELECT `b`.*, `a`.`loginname` as `adminname`
FROM `" . TABLE_PANEL_BACKUPS . "` `b`
LEFT JOIN `" . TABLE_PANEL_ADMINS . "` `a` USING(`adminid`)
WHERE `b`.`customerid` IN (" . implode(', ', $customer_ids) . ")
" . $this->getSearchWhere($query_fields, true) . $this->getOrderBy() . $this->getLimit()
);
Database::pexecute($result_stmt, $query_fields, true, true);
$result = [];
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result[] = $row;
}
return $this->response([
'count' => count($result),
'list' => $result
]);
}
/**
* returns the total number of backups for the given admin
*
* @access admin
* @return string json-encoded response message
* @throws Exception
*/
public function listingCount()
{
if ($this->isAdmin()) {
// if we're an admin, list all backups of all the admins customers
// or optionally for one specific customer identified by id or loginname
$customerid = $this->getParam('customerid', true, 0);
$loginname = $this->getParam('loginname', true, '');
if (!empty($customerid) || !empty($loginname)) {
$result = $this->apiCall('Customers.get', [
'id' => $customerid,
'loginname' => $loginname
]);
$custom_list_result = [
$result
];
} else {
$_custom_list_result = $this->apiCall('Customers.listing');
$custom_list_result = $_custom_list_result['list'];
}
$customer_ids = [];
foreach ($custom_list_result as $customer) {
$customer_ids[] = $customer['customerid'];
}
if (empty($customer_ids)) {
throw new Exception("Required resource unsatisfied.", 405);
}
} else {
$customer_ids = [
$this->getUserDetail('customerid')
];
}
$result_stmt = Database::prepare("
SELECT COUNT(*) as num_backups
FROM `" . TABLE_PANEL_BACKUPS . "` `b`
WHERE `b`.`customerid` IN (" . implode(', ', $customer_ids) . ")
");
$result = Database::pexecute_first($result_stmt, null, true, true);
if ($result) {
return $this->response($result['num_backups']);
}
$this->response(0);
}
/**
* You cannot add a backup entry
*
* @throws Exception
*/
public function add()
{
throw new Exception('You cannot add a backup entry', 303);
}
/**
* return a backup entry by id
*
* @param int $id
* optional, the backup-entry-id
*
* @access admin, customers
* @return string json-encoded array
* @throws Exception
*/
public function get()
{
throw new Exception("@TODO", 303);
}
/**
* You cannot update a backup entry
*
* @throws Exception
*/
public function update()
{
throw new Exception('You cannot update a backup entry', 303);
}
/**
* delete a backup entry by id
*
* @param int $id
* required, the backup-entry-id
*
* @access admin, customer
* @return string json-encoded array
* @throws Exception
*/
public function delete()
{
throw new Exception("@TODO", 303);
}
}

View File

@@ -171,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
@@ -189,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
@@ -271,15 +275,8 @@ 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)
* @param int $backup
* optional, either 0 to disable backup for this customer or a backup-storage-id
* where backups are to be stored, requires change_serversettings permissions,
* default is system-setting backup.default_storage
* @param bool $access_backups
* optional, where the customer is allowed to view backups, default is system-setting
* default_customer_access
*
* @access admin
* @return string json-encoded array
@@ -304,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, '');
@@ -366,24 +364,6 @@ class Customers extends ApiCommand implements ResourceEntity
$p_allowed_mysqlserver = [];
}
if ($this->getUserDetail('change_serversettings')) {
$backup = $this->getParam('backup', true, Settings::Get('backup.default_storage'));
if ($backup > 0) {
try {
$this->apiCall('BackupStorages.get', [
'id' => $backup
]);
} catch (Exception $e) {
// not found or other issue, set default
$backup = Settings::Get('backup.default_storage');
}
}
$access_backups = $this->getBoolParam('access_backups', true, Settings::Get('backup.default_customer_access'));
} else {
$backup = Settings::Get('backup.default_storage');
$access_backups = Settings::Get('backup.default_customer_access');
}
// validation
$name = Validate::validate($name, 'name', Validate::REGEX_DESC_TEXT, '', [], true);
$firstname = Validate::validate($firstname, 'first name', Validate::REGEX_DESC_TEXT, '', [], true);
@@ -425,11 +405,14 @@ class Customers extends ApiCommand implements ResourceEntity
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
if (empty($allowed_phpconfigs) && $phpenabled == 1) {
Response::standardError('customerphpenabledbutnoconfig', '', true);
// 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;
@@ -477,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;
@@ -500,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);
@@ -509,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;
@@ -540,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,
@@ -562,9 +579,7 @@ class Customers extends ApiCommand implements ResourceEntity
'theme' => $_theme,
'custom_notes' => $custom_notes,
'custom_notes_show' => $custom_notes_show,
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver),
'backup' => $backup,
'access_backups' => $access_backups
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver)
];
$ins_stmt = Database::prepare("
@@ -584,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,
@@ -607,9 +623,7 @@ class Customers extends ApiCommand implements ResourceEntity
`theme` = :theme,
`custom_notes` = :custom_notes,
`custom_notes_show` = :custom_notes_show,
`allowed_mysqlserver`= :allowed_mysqlserver,
`backup` = :backup,
`access_backups` = :access_backups
`allowed_mysqlserver`= :allowed_mysqlserver
");
Database::pexecute($ins_stmt, $ins_data, true, true);
@@ -735,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());
@@ -758,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') {
@@ -808,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,
@@ -957,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
@@ -975,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
@@ -987,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)
@@ -1055,15 +1090,8 @@ 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)
* @param int $backup
* optional, either 0 to disable backup for this customer or a backup-storage-id
* where backups are to be stored, requires change_serversettings permissions,
* default is system-setting backup.default_storage
* @param bool $access_backups
* optional, where the customer is allowed to view backups, default is system-setting
* default_customer_access
*
* @access admin, customer
* @return string json-encoded array
@@ -1089,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']);
@@ -1098,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']);
@@ -1125,24 +1154,6 @@ class Customers extends ApiCommand implements ResourceEntity
$deactivated = $this->getBoolParam('deactivated', true, $result['deactivated']);
$theme = $this->getParam('theme', true, $result['theme']);
$allowed_mysqlserver = $this->getParam('allowed_mysqlserver', true, json_decode($result['allowed_mysqlserver'], true));
if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) {
$backup = $this->getParam('backup', true, $result['backup']);
if ($backup > 0) {
try {
$this->apiCall('BackupStorages.get', [
'id' => $backup
]);
} catch (Exception $e) {
// not found or other issue, dont update
$backup = $result['backup'];
}
}
$access_backups = $this->getBoolParam('access_backups', true, Settings::Get('backup.default_customer_access'));
} else {
$backup = $result['backup'];
$access_backups = $result['access_backups'];
}
} else {
// allowed parameters
$def_language = $this->getParam('def_language', true, $result['def_language']);
@@ -1168,14 +1179,17 @@ class Customers extends ApiCommand implements ResourceEntity
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
}
if (empty($allowed_phpconfigs) && $phpenabled == 1) {
Response::standardError('customerphpenabledbutnoconfig', '', true);
// 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);
}
@@ -1240,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);
}
}
}
@@ -1333,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, [
@@ -1359,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+)
@@ -1450,10 +1496,9 @@ 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),
'backup' => $backup,
'access_backups' => $access_backups
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver)
];
$upd_data += $admin_upd_data;
}
@@ -1495,10 +1540,9 @@ 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,
`backup`= :backup,
`access_backups` = :access_backups";
`allowed_mysqlserver` = :allowed_mysqlserver";
$upd_query .= $admin_upd_query;
}
$upd_query .= " WHERE `customerid` = :customerid";
@@ -1654,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`
@@ -1669,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

@@ -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) == '.') {

View File

@@ -201,7 +201,7 @@ 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
@@ -274,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
@@ -282,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');
@@ -316,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);
@@ -349,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();
@@ -470,7 +474,6 @@ class Domains extends ApiCommand implements ResourceEntity
}
$caneditdomain = '1';
$zonefile = '';
$dkim = '0';
$specialsettings = '';
$ssl_specialsettings = '';
$include_specialsettings = 0;
@@ -517,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) {
@@ -544,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;
@@ -584,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("
@@ -788,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
@@ -824,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 . "'");
@@ -1048,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
@@ -1070,7 +1096,7 @@ 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
@@ -1175,6 +1201,7 @@ 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']);
@@ -1207,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']);
@@ -1258,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, [
@@ -1281,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
@@ -1389,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);
@@ -1401,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'];
}
@@ -1437,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'];
@@ -1517,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;
@@ -1553,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);
}
}
@@ -1642,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']
@@ -1659,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'];
}
@@ -1807,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;
@@ -2066,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']
@@ -2086,6 +2130,8 @@ class Domains extends ApiCommand implements ResourceEntity
* @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
@@ -2097,7 +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);
$is_stdsubdomain = $this->getBoolParam('is_stdsubdomain', true, 0);
$delete_user_emailfiles = $this->getBoolParam('delete_userfiles', true, 0);
$result = $this->apiCall('Domains.get', [
'id' => $id,
@@ -2121,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);
@@ -2313,6 +2368,10 @@ class Domains extends ApiCommand implements ResourceEntity
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']);

View File

@@ -157,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:
@@ -260,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) {
@@ -290,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));
@@ -404,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:
@@ -523,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) {
@@ -563,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,14 +86,33 @@ 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
@@ -89,10 +120,10 @@ class Emails extends ApiCommand implements ResourceEntity
'domainname' => $domain
], true);
if ((int)$domain_check['isemaildomain'] == 0) {
Response::standardError('maindomainnonexist', $domain, true);
Response::standardError('maindomainnonexist', $idna_convert->decode($domain), true);
}
if ((int)$domain_check['deactivated'] == 1) {
Response::standardError('maindomaindeactivated', $domain, true);
Response::standardError('maindomaindeactivated', $idna_convert->decode($domain), true);
}
if (Settings::Get('catchall.catchall_enabled') != '1') {
@@ -113,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
@@ -134,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
@@ -153,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
@@ -162,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', [
@@ -194,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'] . "'");
@@ -220,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
@@ -235,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, '');
@@ -255,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', [
@@ -334,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

@@ -82,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,
@@ -91,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();
@@ -259,14 +259,15 @@ 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));
}

View File

@@ -174,7 +174,7 @@ 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 . "`
@@ -288,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) {
@@ -469,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 . "`

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

@@ -296,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);
}
}
}
@@ -500,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
@@ -547,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']);
}
/**
@@ -797,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);
}
@@ -980,9 +983,11 @@ class SubDomains extends ApiCommand implements ResourceEntity
'`d`.`letsencrypt`',
'`d`.`registration_date`',
'`d`.`termination_date`',
'`d`.`deactivated`'
'`d`.`deactivated`',
'`d`.`email_only`',
];
}
$query_fields = [];
// prepare select statement
@@ -993,7 +998,6 @@ 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'
" . $this->getSearchWhere($query_fields, true) . " GROUP BY `d`.`id` ORDER BY `parentdomainname` ASC, `d`.`parentdomainid` ASC " . $this->getOrderBy(true) . $this->getLimit());
$result = [];
@@ -1078,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')) {
@@ -1090,18 +1092,14 @@ class SubDomains extends ApiCommand implements ResourceEntity
$customer_ids = [
$this->getUserDetail('customerid')
];
$customer_stdsubs = [
$this->getUserDetail('customerid') => $this->getUserDetail('standardsubdomain')
];
}
if (!empty($customer_ids)) {
// prepare select statement
$domains_stmt = Database::prepare("
SELECT COUNT(*) as num_subdom
FROM `" . TABLE_PANEL_DOMAINS . "` `d`
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
AND `d`.`email_only` = '0'
AND `d`.`id` NOT IN (" . implode(', ', $customer_stdsubs) . ")
");
$result = Database::pexecute_first($domains_stmt, null, true, true);
if ($result) {

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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',
'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]')
$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
@@ -74,9 +77,11 @@ final class MasterCron extends CliCommand
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);
array_push($jobs, 'tasks');
Cronjob::inserttask(TaskId::UPDATE_LE_SERVICES);
$jobs[] = 'tasks';
}
define('CRON_IS_FORCED', 1);
}
@@ -92,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, [TaskId::REBUILD_VHOST, TaskId::REBUILD_DNS, TaskId::CREATE_QUOTA, TaskId::REBUILD_CRON])) {
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 . '"</>');
}
@@ -140,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
@@ -157,43 +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
// 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
@@ -220,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
{
@@ -242,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;
@@ -268,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
{
@@ -48,11 +46,9 @@ final class RunApiCommand extends CliCommand
$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,12 @@ 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();
$result = $this->runUpdate($output, true);
}
} else {
$errmsg = 'error.autoupdate_' . $auex;
@@ -165,12 +209,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 +355,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';

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