Compare commits
155 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ba5f1bf5c | ||
|
|
97360e450d | ||
|
|
85299085f0 | ||
|
|
6d10a9a096 | ||
|
|
4ea5773abf | ||
|
|
a8395598c3 | ||
|
|
5beeae8fd1 | ||
|
|
8f3228716a | ||
|
|
f8c8f1c333 | ||
|
|
bda644530b | ||
|
|
69ed733e87 | ||
|
|
c9c803900a | ||
|
|
9b5c752380 | ||
|
|
b6fae81f1b | ||
|
|
bab6c3da5b | ||
|
|
c494838069 | ||
|
|
d191693f74 | ||
|
|
aaac84245e | ||
|
|
46a46816b0 | ||
|
|
ce4f64e73a | ||
|
|
b7439d0f3b | ||
|
|
a9da57f6fb | ||
|
|
8ab2e43426 | ||
|
|
8932174df8 | ||
|
|
eb6ea8195d | ||
|
|
a9feb97c27 | ||
|
|
0a7ca058aa | ||
|
|
d9032f3790 | ||
|
|
76793c8992 | ||
|
|
6068daece2 | ||
|
|
0624292b49 | ||
|
|
afc3b68abf | ||
|
|
830f43a9db | ||
|
|
9253a94aad | ||
|
|
e9d3de0c25 | ||
|
|
bbda491e82 | ||
|
|
a0f179a7e7 | ||
|
|
5afc5272d1 | ||
|
|
8f5bd789a4 | ||
|
|
ff64740880 | ||
|
|
0a221d0479 | ||
|
|
097cde13ad | ||
|
|
f90dc5854d | ||
|
|
8eb38a8a28 | ||
|
|
e17135f0c3 | ||
|
|
c5017786e0 | ||
|
|
2e18d7c581 | ||
|
|
ceb7f5b23d | ||
|
|
ebed800dec | ||
|
|
32344e39cf | ||
|
|
e35092c31f | ||
|
|
606377f1d9 | ||
|
|
b9baeb76d5 | ||
|
|
2f2d72851b | ||
|
|
a2925af73a | ||
|
|
1008c015a5 | ||
|
|
2eda4ae972 | ||
|
|
38b2dbd81b | ||
|
|
aab98e4dae | ||
|
|
4b930375b7 | ||
|
|
c7245d0b9b | ||
|
|
ec42003367 | ||
|
|
fde43f8060 | ||
|
|
a43d53d540 | ||
|
|
3638dc08ea | ||
|
|
c2d166c866 | ||
|
|
0fb9357e87 | ||
|
|
26c3c87d28 | ||
|
|
0aa3e2f7b1 | ||
|
|
9dec83fff2 | ||
|
|
a839d76d1f | ||
|
|
079047b9fe | ||
|
|
2bb863baac | ||
|
|
8be7372d73 | ||
|
|
dcaff3f7de | ||
|
|
b6dadc0d8f | ||
|
|
665b879ac5 | ||
|
|
60f51fd746 | ||
|
|
5bb450bccc | ||
|
|
604078ddc6 | ||
|
|
b018319b8a | ||
|
|
13aa07ed1a | ||
|
|
4db5b09111 | ||
|
|
4f114738e7 | ||
|
|
2c9f9ebfe2 | ||
|
|
ee986e519e | ||
|
|
103d321003 | ||
|
|
99b3deda91 | ||
|
|
a47f8ed7ee | ||
|
|
ce841e8aa4 | ||
|
|
86130616dd | ||
|
|
5622ce5011 | ||
|
|
00bdadb6e5 | ||
|
|
05223369c5 | ||
|
|
b45ac3de3c | ||
|
|
11a5c38476 | ||
|
|
8c48c5a840 | ||
|
|
4f4abada6f | ||
|
|
289d59f531 | ||
|
|
f652017c1a | ||
|
|
2a50eb43b3 | ||
|
|
f31c032508 | ||
|
|
228eb244fa | ||
|
|
143d8d42b3 | ||
|
|
4ce739667d | ||
|
|
eb3568fca2 | ||
|
|
dda4c7a846 | ||
|
|
53b7c501bc | ||
|
|
c9e15bf897 | ||
|
|
140c6c9549 | ||
|
|
ddc439d32f | ||
|
|
40aa48a6d4 | ||
|
|
c69b38bc42 | ||
|
|
1fd8b88ed8 | ||
|
|
0318223fec | ||
|
|
53c414be6d | ||
|
|
2f7a2a32ba | ||
|
|
16d77a03cb | ||
|
|
05ca08c5c3 | ||
|
|
d3ec02f258 | ||
|
|
4ea7e10304 | ||
|
|
f31ee2e360 | ||
|
|
57206b2f72 | ||
|
|
73906f252b | ||
|
|
3367f6dbd8 | ||
|
|
197eb7954a | ||
|
|
a1b6125c14 | ||
|
|
7206f5fee2 | ||
|
|
d18a9c9d87 | ||
|
|
94046ae6c7 | ||
|
|
55212607e0 | ||
|
|
bacc6fe073 | ||
|
|
e6bfe205c5 | ||
|
|
596075d141 | ||
|
|
15d3dd4234 | ||
|
|
a58a5fd972 | ||
|
|
54cda098c1 | ||
|
|
bcbfcb34e8 | ||
|
|
19995f4345 | ||
|
|
56d8a565b4 | ||
|
|
a60c21218c | ||
|
|
cd2a08e731 | ||
|
|
5d2ce4ecfb | ||
|
|
869b01204a | ||
|
|
d357bded60 | ||
|
|
292741516a | ||
|
|
27db472a0c | ||
|
|
fc4041e88c | ||
|
|
75bc0142a0 | ||
|
|
b888e920f4 | ||
|
|
585b16d199 | ||
|
|
4d3cf5da9a | ||
|
|
2dae780e0b | ||
|
|
bda24d7d63 | ||
|
|
9d47d670a1 |
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -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
|
||||
|
||||
8
.github/workflows/build-mariadb.yml
vendored
8
.github/workflows/build-mariadb.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: [ '7.4', '8.2' ]
|
||||
php-versions: [ '7.4', '8.3' ]
|
||||
mariadb-version: [ 10.11, 10.5 ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: composer:v2
|
||||
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp, gnupg
|
||||
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp
|
||||
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y ant
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
node-version: '22.x'
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: npm install
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
|
||||
- name: Deploy nightly to server
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
with:
|
||||
ARGS: "-rltDzvO --chown=${{ secrets.WEB_USER }}:${{ secrets.WEB_USER }}"
|
||||
SOURCE: "dist/"
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }}
|
||||
|
||||
4
.github/workflows/build-mysql.yml
vendored
4
.github/workflows/build-mysql.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['7.4', '8.2']
|
||||
php-versions: ['7.4', '8.3']
|
||||
mysql-version: [8.0, 5.7]
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: composer:v2
|
||||
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp, gnupg
|
||||
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp
|
||||
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y ant
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,6 +10,7 @@ logs/*
|
||||
.settings/
|
||||
.test/
|
||||
*.diff
|
||||
*.patch
|
||||
*~
|
||||
.well-known
|
||||
.idea
|
||||
@@ -23,4 +24,5 @@ templates/*
|
||||
!templates/index.html
|
||||
!templates/Froxlor/
|
||||
templates/Froxlor/build/
|
||||
templates/Froxlor/hot
|
||||
!templates/misc/
|
||||
|
||||
@@ -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
|
||||
@@ -49,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
|
||||
|
||||
@@ -257,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'),
|
||||
@@ -265,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'),
|
||||
|
||||
@@ -177,6 +177,10 @@ return [
|
||||
'type' => 'text',
|
||||
'default' => 'froxlorlocal',
|
||||
'string_emptyallowed' => false,
|
||||
'plausibility_check_method' => [
|
||||
'\\Froxlor\\Validate\\Check',
|
||||
'checkSystemUsername'
|
||||
],
|
||||
'save_method' => 'storeSettingWebserverFcgidFpmUser',
|
||||
'websrv_avail' => [
|
||||
'apache2'
|
||||
@@ -246,6 +250,10 @@ return [
|
||||
'type' => 'text',
|
||||
'default' => 'froxlorlocal',
|
||||
'string_emptyallowed' => false,
|
||||
'plausibility_check_method' => [
|
||||
'\\Froxlor\\Validate\\Check',
|
||||
'checkSystemUsername'
|
||||
],
|
||||
'save_method' => 'storeSettingWebserverFcgidFpmUser',
|
||||
'visible' => Settings::Get('phpfpm.enabled') && call_user_func([
|
||||
'\Froxlor\Settings\FroxlorVhostSettings',
|
||||
|
||||
@@ -49,7 +49,7 @@ return [
|
||||
],
|
||||
'requires_reconf' => ['http']
|
||||
],
|
||||
'system_apache_24' => [
|
||||
'system_apache24' => [
|
||||
'label' => lng('serversettings.apache_24'),
|
||||
'settinggroup' => 'system',
|
||||
'varname' => 'apache24',
|
||||
@@ -104,6 +104,10 @@ return [
|
||||
'varname' => 'httpuser',
|
||||
'type' => 'text',
|
||||
'default' => 'www-data',
|
||||
'plausibility_check_method' => [
|
||||
'\\Froxlor\\Validate\\Check',
|
||||
'checkSystemUsername'
|
||||
],
|
||||
'save_method' => 'storeSettingWebserverFcgidFpmUser'
|
||||
],
|
||||
'system_httpgroup' => [
|
||||
|
||||
@@ -268,7 +268,7 @@ return [
|
||||
'dovecot' => 'dovecot (imap/pop3)',
|
||||
'proftpd' => 'proftpd (ftp)',
|
||||
],
|
||||
'save_method' => 'storeSettingField',
|
||||
'save_method' => 'storeSettingFieldInsertUpdateServicesTask',
|
||||
'advanced_mode' => true
|
||||
],
|
||||
'system_le_renew_hook' => [
|
||||
@@ -278,7 +278,7 @@ return [
|
||||
'type' => 'text',
|
||||
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
|
||||
'default' => 'systemctl restart postfix dovecot proftpd',
|
||||
'save_method' => 'storeSettingField',
|
||||
'save_method' => 'storeSettingFieldInsertUpdateServicesTask',
|
||||
'advanced_mode' => true,
|
||||
'required_otp' => true
|
||||
],
|
||||
|
||||
@@ -58,6 +58,51 @@ return [
|
||||
'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',
|
||||
@@ -84,7 +129,7 @@ return [
|
||||
'settinggroup' => 'spf',
|
||||
'varname' => 'spf_entry',
|
||||
'type' => 'text',
|
||||
'string_regexp' => '/^v=spf[a-z0-9:~?\s.-]+$/i',
|
||||
'string_regexp' => '/^v=spf[a-z0-9:~?\s\.\-\/]+$/i',
|
||||
'default' => 'v=spf1 a mx -all',
|
||||
'save_method' => 'storeSettingField'
|
||||
],
|
||||
|
||||
@@ -118,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,
|
||||
|
||||
@@ -307,17 +307,20 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
|
||||
$hosting_plans[$row['id']] = $row['name'];
|
||||
}
|
||||
|
||||
$available_admins_stmt = Database::prepare("
|
||||
SELECT * FROM `" . TABLE_PANEL_ADMINS . "`
|
||||
WHERE (`customers` = '-1' OR `customers` > `customers_used`)
|
||||
AND adminid <> :currentadmin
|
||||
");
|
||||
Database::pexecute($available_admins_stmt, ['currentadmin' => $result['adminid']]);
|
||||
$admin_select = [
|
||||
0 => "---"
|
||||
];
|
||||
while ($available_admin = $available_admins_stmt->fetch()) {
|
||||
$admin_select[$available_admin['adminid']] = $available_admin['name'] . " (" . $available_admin['loginname'] . ")";
|
||||
$admin_select = [];
|
||||
if ($userinfo['customers_see_all'] == '1') {
|
||||
$available_admins_stmt = Database::prepare("
|
||||
SELECT * FROM `" . TABLE_PANEL_ADMINS . "`
|
||||
WHERE (`customers` = '-1' OR `customers` > `customers_used`)
|
||||
AND adminid <> :currentadmin
|
||||
");
|
||||
Database::pexecute($available_admins_stmt, ['currentadmin' => $result['adminid']]);
|
||||
$admin_select = [
|
||||
0 => "---"
|
||||
];
|
||||
while ($available_admin = $available_admins_stmt->fetch()) {
|
||||
$admin_select[$available_admin['adminid']] = $available_admin['name'] . " (" . $available_admin['loginname'] . ")";
|
||||
}
|
||||
}
|
||||
|
||||
$customer_edit_data = include_once dirname(__FILE__) . '/lib/formfields/admin/customer/formfield.customer_edit.php';
|
||||
|
||||
@@ -319,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, [
|
||||
@@ -593,6 +593,23 @@ 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 (Request::post('send') == 'send') {
|
||||
$separator = Validate::validate(Request::post('separator'), 'separator');
|
||||
|
||||
@@ -114,7 +114,7 @@ if ($page == 'message') {
|
||||
$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' => [
|
||||
[
|
||||
|
||||
@@ -35,6 +35,7 @@ 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') {
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
"ext-fileinfo": "*",
|
||||
"ext-gmp": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-gnupg": "*",
|
||||
"phpmailer/phpmailer": "~6.0",
|
||||
"monolog/monolog": "^1.24",
|
||||
"robthree/twofactorauth": "^1.6",
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
684
composer.lock
generated
684
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -141,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, [
|
||||
|
||||
@@ -30,7 +30,6 @@ use Froxlor\Api\Commands\EmailAccounts;
|
||||
use Froxlor\Api\Commands\EmailDomains;
|
||||
use Froxlor\Api\Commands\EmailForwarders;
|
||||
use Froxlor\Api\Commands\Emails;
|
||||
use Froxlor\Cron\Mail\Rspamd;
|
||||
use Froxlor\CurrentUser;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FroxlorLogger;
|
||||
@@ -105,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());
|
||||
}
|
||||
@@ -247,11 +246,7 @@ if ($page == 'email_domain') {
|
||||
if (isset($result['email']) && $result['email'] != '') {
|
||||
if (Request::post('send') == 'send') {
|
||||
try {
|
||||
Emails::getLocal($userinfo, [
|
||||
'id' => $id,
|
||||
'spam_tag_level' => Request::post('spam_tag_level', Rspamd::DEFAULT_MARK_LVL),
|
||||
'spam_kill_level' => Request::post('spam_kill_level', Rspamd::DEFAULT_REJECT_LVL)
|
||||
])->update();
|
||||
Emails::getLocal($userinfo, Request::postAll())->update();
|
||||
} catch (Exception $e) {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
@@ -291,88 +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 == 'togglebypass' && $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,
|
||||
'bypass_spam' => ($result['bypass_spam'] == '1' ? 0 : 1)
|
||||
])->update();
|
||||
} catch (Exception $e) {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
Response::redirectTo($filename, [
|
||||
'page' => $page,
|
||||
'domainid' => $email_domainid,
|
||||
'action' => 'edit',
|
||||
'id' => $id,
|
||||
]);
|
||||
} elseif ($action == 'togglegreylist' && $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,
|
||||
'policy_greylist' => ($result['policy_greylist'] == '1' ? 0 : 1)
|
||||
])->update();
|
||||
} catch (Exception $e) {
|
||||
Response::dynamicError($e->getMessage());
|
||||
}
|
||||
Response::redirectTo($filename, [
|
||||
'page' => $page,
|
||||
'domainid' => $email_domainid,
|
||||
'action' => 'edit',
|
||||
'id' => $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);
|
||||
|
||||
@@ -228,7 +228,7 @@ if ($page == 'overview' || $page == 'mysqls') {
|
||||
$new_password = Crypt::validatePassword(Request::post('mysql_password'));
|
||||
foreach ($allowed_mysqlservers as $dbserver) {
|
||||
// require privileged access for target db-server
|
||||
Database::needRoot(true, $dbserver, false);
|
||||
Database::needRoot(true, $dbserver, true);
|
||||
// get DbManager
|
||||
$dbm = new DbManager($log);
|
||||
// give permission to the user on every access-host we have
|
||||
|
||||
71
index.php
71
index.php
@@ -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') {
|
||||
@@ -72,6 +73,7 @@ if ($action == '2fa_entercode') {
|
||||
exit();
|
||||
}
|
||||
$code = Request::post('2fa_code');
|
||||
$remember = Request::post('2fa_remember');
|
||||
// verify entered code
|
||||
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
|
||||
// get user-data
|
||||
@@ -83,7 +85,8 @@ if ($action == '2fa_entercode') {
|
||||
// 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]);
|
||||
$result = $tfa->verifyCode($userinfo_code['data_2fa'], $code);
|
||||
// 60sec discrepancy (possible slow email delivery)
|
||||
$result = $tfa->verifyCode($userinfo_code['data_2fa'], $code, 60);
|
||||
} else {
|
||||
$result = $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3);
|
||||
}
|
||||
@@ -105,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"
|
||||
@@ -349,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];
|
||||
@@ -829,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'])) {
|
||||
|
||||
@@ -95,6 +95,7 @@ CREATE TABLE `mail_virtual` (
|
||||
`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',
|
||||
@@ -390,6 +391,9 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES
|
||||
('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'),
|
||||
@@ -495,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
|
||||
@@ -730,8 +733,8 @@ opcache.validate_timestamps'),
|
||||
('panel', 'logo_overridecustom', '0'),
|
||||
('panel', 'settings_mode', '0'),
|
||||
('panel', 'menu_collapsed', '1'),
|
||||
('panel', 'version', '2.2.0-rc1'),
|
||||
('panel', 'db_version', '202401090');
|
||||
('panel', 'version', '2.2.8'),
|
||||
('panel', 'db_version', '202412030');
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `panel_tasks`;
|
||||
@@ -1049,4 +1052,15 @@ CREATE TABLE `panel_loginlinks` (
|
||||
`allowed_from` text NOT NULL,
|
||||
UNIQUE KEY `loginname` (`loginname`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `panel_2fa_tokens`;
|
||||
CREATE TABLE `panel_2fa_tokens` (
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`selector` varchar(200) NOT NULL,
|
||||
`token` varchar(200) NOT NULL,
|
||||
`userid` int(11) NOT NULL default '0',
|
||||
`valid_until` int(15) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
FROXLORSQL;
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
*/
|
||||
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Database\DbManager;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\FroxlorLogger;
|
||||
use Froxlor\Install\Update;
|
||||
use Froxlor\Settings;
|
||||
|
||||
@@ -122,3 +124,139 @@ if (Froxlor::isFroxlorVersion('2.2.0-dev1')) {
|
||||
Update::showUpdateStep("Updating from 2.2.0-dev1 to 2.2.0-rc1", false);
|
||||
Froxlor::updateToVersion('2.2.0-rc1');
|
||||
}
|
||||
|
||||
if (Froxlor::isDatabaseVersion('202401090')) {
|
||||
|
||||
Update::showUpdateStep("Adding new table for 2fa tokens");
|
||||
Database::query("DROP TABLE IF EXISTS `panel_2fa_tokens`;");
|
||||
$sql = "CREATE TABLE `panel_2fa_tokens` (
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`selector` varchar(20) NOT NULL,
|
||||
`token` varchar(200) NOT NULL,
|
||||
`userid` int(11) NOT NULL default '0',
|
||||
`valid_until` int(15) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
|
||||
Database::query($sql);
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Froxlor::updateToDbVersion('202407200');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.2.0-rc1')) {
|
||||
Update::showUpdateStep("Updating from 2.2.0-rc1 to 2.2.0-rc2", false);
|
||||
Froxlor::updateToVersion('2.2.0-rc2');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.2.0-rc2')) {
|
||||
Update::showUpdateStep("Updating from 2.2.0-rc2 to 2.2.0-rc3", false);
|
||||
Froxlor::updateToVersion('2.2.0-rc3');
|
||||
}
|
||||
|
||||
if (Froxlor::isDatabaseVersion('202407200')) {
|
||||
|
||||
Update::showUpdateStep("Adjusting field in 2fa-token table");
|
||||
Database::query("ALTER TABLE `panel_2fa_tokens` CHANGE COLUMN `selector` `selector` varchar(200) NOT NULL;");
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Froxlor::updateToDbVersion('202408140');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.2.0-rc3')) {
|
||||
Update::showUpdateStep("Updating from 2.2.0-rc3 to 2.2.0 stable", false);
|
||||
Froxlor::updateToVersion('2.2.0');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.2.0')) {
|
||||
Update::showUpdateStep("Updating from 2.2.0 to 2.2.1", false);
|
||||
Froxlor::updateToVersion('2.2.1');
|
||||
}
|
||||
|
||||
if (Froxlor::isDatabaseVersion('202408140')) {
|
||||
|
||||
Update::showUpdateStep("Adding new rewrite-subject field to email table");
|
||||
Database::query("ALTER TABLE `" . TABLE_MAIL_VIRTUAL . "` ADD `rewrite_subject` tinyint(1) NOT NULL default '1' AFTER `spam_tag_level`;");
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Froxlor::updateToDbVersion('202409280');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.2.1')) {
|
||||
Update::showUpdateStep("Updating from 2.2.1 to 2.2.2", false);
|
||||
Froxlor::updateToVersion('2.2.2');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.2.2')) {
|
||||
Update::showUpdateStep("Updating from 2.2.2 to 2.2.3", false);
|
||||
Froxlor::updateToVersion('2.2.3');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.2.3')) {
|
||||
Update::showUpdateStep("Updating from 2.2.3 to 2.2.4", false);
|
||||
Froxlor::updateToVersion('2.2.4');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.2.4')) {
|
||||
Update::showUpdateStep("Updating from 2.2.4 to 2.2.5", false);
|
||||
Froxlor::updateToVersion('2.2.5');
|
||||
}
|
||||
|
||||
if (Froxlor::isDatabaseVersion('202409280')) {
|
||||
|
||||
Update::showUpdateStep("Adding new antispam settings");
|
||||
Settings::AddNew("antispam.default_bypass_spam", "2");
|
||||
Settings::AddNew("antispam.default_spam_rewrite_subject", "1");
|
||||
Settings::AddNew("antispam.default_policy_greylist", "1");
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Froxlor::updateToDbVersion('202411200');
|
||||
}
|
||||
|
||||
if (Froxlor::isDatabaseVersion('202411200')) {
|
||||
|
||||
Update::showUpdateStep("Adjusting customer mysql global user");
|
||||
// get all customers that are not deactivated and that have at least one database (hence a global database-user)
|
||||
$customers = Database::query("
|
||||
SELECT DISTINCT c.loginname, c.allowed_mysqlserver
|
||||
FROM `" . TABLE_PANEL_CUSTOMERS . "` c
|
||||
LEFT JOIN `" . TABLE_PANEL_DATABASES . "` d ON c.customerid = d.customerid
|
||||
WHERE c.deactivated = '0' AND d.id IS NOT NULL
|
||||
");
|
||||
while ($customer = $customers->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$current_allowed_mysqlserver = !empty($customer['allowed_mysqlserver']) ? json_decode($customer['allowed_mysqlserver'], true) : [];
|
||||
foreach ($current_allowed_mysqlserver as $dbserver) {
|
||||
// require privileged access for target db-server
|
||||
Database::needRoot(true, $dbserver, false);
|
||||
// get DbManager
|
||||
$dbm = new DbManager(FroxlorLogger::getInstanceOf());
|
||||
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
|
||||
if ($dbm->getManager()->userExistsOnHost($customer['loginname'], $mysql_access_host)) {
|
||||
// deactivate temporarily
|
||||
$dbm->getManager()->disableUser($customer['loginname'], $mysql_access_host);
|
||||
// re-enable
|
||||
$dbm->getManager()->enableUser($customer['loginname'], $mysql_access_host, true);
|
||||
}
|
||||
}
|
||||
$dbm->getManager()->flushPrivileges();
|
||||
Database::needRoot();
|
||||
}
|
||||
}
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Froxlor::updateToDbVersion('202412030');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.2.5')) {
|
||||
Update::showUpdateStep("Updating from 2.2.5 to 2.2.6", false);
|
||||
Froxlor::updateToVersion('2.2.6');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.2.6')) {
|
||||
Update::showUpdateStep("Updating from 2.2.6 to 2.2.7", false);
|
||||
Froxlor::updateToVersion('2.2.7');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.2.7')) {
|
||||
Update::showUpdateStep("Updating from 2.2.7 to 2.2.8", false);
|
||||
Froxlor::updateToVersion('2.2.8');
|
||||
}
|
||||
|
||||
@@ -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 ';
|
||||
$question = '<strong>Validate DNS of domains when using Lets Encrypt</strong>';
|
||||
$return['system_le_domain_dnscheck'] = [
|
||||
'type' => 'checkbox',
|
||||
'value' => 1,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -287,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)) {
|
||||
@@ -298,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';
|
||||
@@ -610,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';
|
||||
|
||||
@@ -505,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);
|
||||
@@ -514,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;
|
||||
@@ -738,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());
|
||||
@@ -827,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,
|
||||
@@ -1105,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($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']);
|
||||
@@ -1242,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1346,7 +1370,7 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
$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);
|
||||
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) {
|
||||
|
||||
@@ -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) == '.') {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -472,7 +474,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
}
|
||||
$caneditdomain = '1';
|
||||
$zonefile = '';
|
||||
$dkim = '0';
|
||||
$specialsettings = '';
|
||||
$ssl_specialsettings = '';
|
||||
$include_specialsettings = 0;
|
||||
@@ -548,8 +549,11 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
}
|
||||
}
|
||||
if (Settings::Get('system.use_ssl') == "1" && $sslenabled == 1 && empty($ssl_ipandports)) {
|
||||
// enabled ssl for the domain but no ssl ip/port is selected
|
||||
Response::standardError('nosslippportgiven', '', true);
|
||||
// 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;
|
||||
@@ -591,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("
|
||||
@@ -795,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
|
||||
@@ -1058,6 +1071,9 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
* (default yes), 3 = always, default 0 (never)
|
||||
* @param bool $isemaildomain
|
||||
* optional, allow email usage with this domain, default 0 (false)
|
||||
* @param bool $emaildomainverified
|
||||
* optional, when setting $isemaildomain to false, this needs to be set to true to confirm the action in case email addresses exist for this domain,
|
||||
* default 0 (false)
|
||||
* @param bool $email_only
|
||||
* optional, restrict domain to email usage, default 0 (false)
|
||||
* @param int $selectserveralias
|
||||
@@ -1080,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
|
||||
@@ -1185,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']);
|
||||
@@ -1268,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, [
|
||||
@@ -1291,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
|
||||
@@ -1399,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);
|
||||
@@ -1447,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'];
|
||||
@@ -1566,15 +1582,25 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
}
|
||||
|
||||
// Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated
|
||||
if (($result['letsencrypt'] != $letsencrypt || $result['ssl_redirect'] != $ssl_redirect) && $ssl_redirect > 0 && $letsencrypt == 1) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -2085,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']
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -53,12 +53,14 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
* 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: no
|
||||
* optional, disable spam-filter entirely, default: [antispam.default_bypass_spam]
|
||||
* @param boolean $policy_greylist
|
||||
* optional, enable grey-listing, default: yes
|
||||
* optional, enable grey-listing, default: [antispam.default_policy_greylist]
|
||||
* @param boolean $iscatchall
|
||||
* optional, make this address a catchall address, default: no
|
||||
* @param int $customerid
|
||||
@@ -85,17 +87,32 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
|
||||
// parameters
|
||||
$spam_tag_level = $this->getParam('spam_tag_level', true, '7.0');
|
||||
$spam_kill_level = $this->getParam('spam_kill_level', true, '14.0');
|
||||
$bypass_spam = $this->getBoolParam('bypass_spam', true, 0);
|
||||
$policy_greylist = $this->getBoolParam('policy_greylist', true, 1);
|
||||
$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
|
||||
@@ -103,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') {
|
||||
@@ -127,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
|
||||
@@ -148,14 +165,16 @@ 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,2})?$/', '', [7.0], true);
|
||||
$spam_kill_level = Validate::validate($spam_kill_level, 'spam_kill_level', '/^\d{1,}(\.\d{1,2})?$/', '', [14.0], 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("
|
||||
@@ -164,6 +183,7 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
`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,
|
||||
@@ -176,6 +196,7 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
"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,
|
||||
@@ -226,7 +247,7 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
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)"
|
||||
));
|
||||
));
|
||||
$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'] . "'");
|
||||
@@ -249,12 +270,14 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
* 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: no
|
||||
* optional, disable spam-filter entirely, default: [antispam.default_bypass_spam]
|
||||
* @param boolean $policy_greylist
|
||||
* optional, enable grey-listing, default: yes
|
||||
* optional, enable grey-listing, default: [antispam.default_policy_greylist]
|
||||
* @param boolean $iscatchall
|
||||
* optional
|
||||
* @param string $description
|
||||
@@ -270,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, '');
|
||||
@@ -291,46 +305,74 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
|
||||
// parameters
|
||||
$spam_tag_level = $this->getParam('spam_tag_level', true, $result['spam_tag_level']);
|
||||
$spam_kill_level = $this->getParam('spam_kill_level', true, $result['spam_kill_level']);
|
||||
$bypass_spam = $this->getBoolParam('bypass_spam', true, $result['bypass_spam']);
|
||||
$policy_greylist = $this->getBoolParam('policy_greylist', true, $result['policy_greylist']);
|
||||
$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);
|
||||
$spam_kill_level = Validate::validate($spam_kill_level, 'spam_kill_level', '/^\d{1,}(\.\d{1,2})?$/', '', [14.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 ,
|
||||
`spam_tag_level` = :spam_tag_level,
|
||||
`rewrite_subject` = :rewrite_subject,
|
||||
`spam_kill_level` = :spam_kill_level,
|
||||
`bypass_spam` = :bypass_spam,
|
||||
`policy_greylist` = :policy_greylist,
|
||||
@@ -341,6 +383,7 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
$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,
|
||||
@@ -394,7 +437,10 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
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");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -113,9 +113,9 @@ class Mysqls extends ApiCommand implements ResourceEntity
|
||||
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);
|
||||
$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
|
||||
@@ -184,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) {
|
||||
@@ -541,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
|
||||
|
||||
|
||||
@@ -503,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
|
||||
@@ -550,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, $customer['documentroot']);
|
||||
} else {
|
||||
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
|
||||
}
|
||||
} 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']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -983,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
|
||||
@@ -996,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 = [];
|
||||
@@ -1092,13 +1093,13 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
$this->getUserDetail('customerid')
|
||||
];
|
||||
}
|
||||
|
||||
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'
|
||||
");
|
||||
$result = Database::pexecute_first($domains_stmt, null, true, true);
|
||||
if ($result) {
|
||||
|
||||
@@ -225,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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -352,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();
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ final class MasterCron extends CliCommand
|
||||
Cronjob::inserttask(TaskId::REBUILD_RSPAMD);
|
||||
Cronjob::inserttask(TaskId::CREATE_QUOTA);
|
||||
Cronjob::inserttask(TaskId::REBUILD_CRON);
|
||||
Cronjob::inserttask(TaskId::UPDATE_LE_SERVICES);
|
||||
$jobs[] = 'tasks';
|
||||
}
|
||||
define('CRON_IS_FORCED', 1);
|
||||
@@ -171,8 +172,9 @@ final class MasterCron extends CliCommand
|
||||
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;
|
||||
}
|
||||
@@ -213,9 +215,14 @@ 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();
|
||||
|
||||
@@ -42,7 +42,7 @@ final class RunApiCommand extends CliCommand
|
||||
$this->setDescription('Run an API command as given user');
|
||||
$this->addArgument('user', InputArgument::REQUIRED, 'Loginname of the user you want to run the command as')
|
||||
->addArgument('api-command', InputArgument::REQUIRED, 'The command to execute in the form "Module.function"')
|
||||
->addArgument('parameters', InputArgument::OPTIONAL, 'Paramaters to pass to the command as JSON array');
|
||||
->addArgument('parameters', InputArgument::OPTIONAL, 'Parameters to pass to the command as JSON array');
|
||||
$this->addOption('show-params', 's', InputOption::VALUE_NONE, 'Show possible parameters for given api-command (given command will *not* be called)');
|
||||
}
|
||||
|
||||
|
||||
@@ -28,13 +28,17 @@ namespace Froxlor\Cli;
|
||||
use Exception;
|
||||
use Froxlor\Froxlor;
|
||||
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,8 @@ 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)')
|
||||
@@ -63,9 +69,13 @@ final class UpdateCommand extends CliCommand
|
||||
$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);
|
||||
@@ -73,7 +83,7 @@ final class UpdateCommand extends CliCommand
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
$output->writeln('<info>' . lng('update.noupdatesavail', (Settings::Get('system.update_channel') == 'testing' ? lng('serversettings.uc_testing') . ' ' : '')) . '</>');
|
||||
$output->writeln('<info>' . lng('update.noupdatesavail', [(Settings::Get('system.update_channel') == 'testing' ? lng('serversettings.uc_testing') . ' ' : '')]) . '</>');
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
@@ -101,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') != 'stable' ? Settings::Get('system.update_channel').' ' : ''), 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]);
|
||||
}
|
||||
@@ -152,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;
|
||||
@@ -174,9 +185,13 @@ final class UpdateCommand extends CliCommand
|
||||
if ($auex == 0) {
|
||||
$output->writeln("<info>Froxlor files updated successfully.</>");
|
||||
$result = self::SUCCESS;
|
||||
|
||||
$this->askUpdateOptions($input, $output, $helper, $yestoall);
|
||||
|
||||
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
|
||||
if ($yestoall || $helper->ask($input, $output, $question)) {
|
||||
$result = $this->runUpdate($output, true);
|
||||
// run in separate process to ensure the use of newly unpacked files
|
||||
passthru(Froxlor::getInstallDir() . '/bin/froxlor-cli froxlor:update -dA', $result);
|
||||
}
|
||||
} else {
|
||||
$errmsg = 'error.autoupdate_' . $auex;
|
||||
@@ -195,12 +210,141 @@ final class UpdateCommand extends CliCommand
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @param $helper
|
||||
* @param bool $yestoall
|
||||
* @return void
|
||||
*/
|
||||
private function askUpdateOptions(InputInterface $input, OutputInterface $output, $helper, bool $yestoall = false)
|
||||
{
|
||||
// check for preconfigs
|
||||
$preconfig = Preconfig::getPreConfig(true);
|
||||
$show_options_only = $input->getOption('show-update-options') !== false;
|
||||
if (!is_null($helper) && $show_options_only) {
|
||||
$output->writeln('<comment>Unsetting "show-update-options" due to not being called with "check-only".</>');
|
||||
$show_options_only = false;
|
||||
}
|
||||
$update_options = [];
|
||||
// set parameters
|
||||
$uOptions = $input->getOption('update-options');
|
||||
if (!empty($uOptions)) {
|
||||
$options_value = [];
|
||||
foreach ($uOptions as $givenOption) {
|
||||
$optVal = explode("=", $givenOption);
|
||||
if (count($optVal) == 2) {
|
||||
$options_value[$optVal[0]] = $optVal[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($preconfig)) {
|
||||
krsort($preconfig);
|
||||
foreach ($preconfig as $section) {
|
||||
if (!$show_options_only) {
|
||||
$output->writeln("<info>Updater questions for " . $section['title'] . "</>");
|
||||
}
|
||||
foreach ($section['fields'] as $update_field => $metainfo) {
|
||||
if (isset($options_value[$update_field])) {
|
||||
$output->writeln('Setting given parameter "' . $update_field . '" to "' . $options_value[$update_field] . '"');
|
||||
$_POST[$update_field] = $options_value[$update_field];
|
||||
continue;
|
||||
}
|
||||
$default = null;
|
||||
$question_text = html_entity_decode(strip_tags($metainfo['label']), ENT_QUOTES | ENT_IGNORE, "UTF-8");
|
||||
if ($metainfo['type'] == 'checkbox') {
|
||||
$default = (int)$metainfo['checked'];
|
||||
if ($show_options_only) {
|
||||
$update_options[] = [
|
||||
'name' => $update_field,
|
||||
'question' => $question_text,
|
||||
'default' => $default,
|
||||
'choices' => '0: No' . PHP_EOL . '1: Yes' . PHP_EOL
|
||||
];
|
||||
} else {
|
||||
$question = new ConfirmationQuestion($question_text . ' [' . ($metainfo['checked'] ? 'yes' : 'no') . '] ', (bool)$metainfo['checked'], '/^(y|j)/i');
|
||||
}
|
||||
} elseif ($metainfo['type'] == 'select') {
|
||||
$default = $metainfo['selected'];
|
||||
$choices = "";
|
||||
foreach (array_values($metainfo['select_var'] ?? []) as $index => $choice) {
|
||||
$choices .= $index . ': ' . $choice . PHP_EOL;
|
||||
}
|
||||
if ($show_options_only) {
|
||||
$update_options[] = [
|
||||
'name' => $update_field,
|
||||
'question' => $question_text,
|
||||
'default' => !empty($default) ? $default : '-',
|
||||
'choices' => $choices
|
||||
];
|
||||
} else {
|
||||
$question = new ChoiceQuestion(
|
||||
$question_text,
|
||||
array_values($metainfo['select_var'] ?? []),
|
||||
$metainfo['selected']
|
||||
);
|
||||
$question->setValidator(function ($answer) use ($metainfo): string {
|
||||
$key = array_keys($metainfo['select_var'])[(int)$answer] ?? false; // Find the key based on the selected value
|
||||
if ($key === false) {
|
||||
throw new \RuntimeException('Invalid selection.');
|
||||
}
|
||||
return $key;
|
||||
});
|
||||
}
|
||||
} elseif ($metainfo['type'] == 'text') {
|
||||
$default = $metainfo['value'] ?? '';
|
||||
if ($show_options_only) {
|
||||
$update_options[] = [
|
||||
'name' => $update_field,
|
||||
'question' => $question_text,
|
||||
'default' => $default,
|
||||
'choices' => PHP_EOL
|
||||
];
|
||||
} else {
|
||||
$question = new Question($question_text . (!empty($metainfo['value']) ? ' [' . $metainfo['value'] . ']' : ''), $default);
|
||||
$question->setValidator(function (string $answer) use ($metainfo): string {
|
||||
if (($metainfo['mandatory'] ?? false) && empty($answer)) {
|
||||
throw new \RuntimeException(
|
||||
'Answer cannot be empty'
|
||||
);
|
||||
}
|
||||
if (!empty($metainfo['pattern'] ?? "") && !preg_match("/" . $metainfo['pattern'] . "/", $answer)) {
|
||||
throw new \RuntimeException('Answer does not seem to be in valid format');
|
||||
}
|
||||
return $answer;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$output->writeln("<error>Unknown type " . $metainfo['type'] . "</error>");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$show_options_only) {
|
||||
if ($yestoall) {
|
||||
$_POST[$update_field] = $default;
|
||||
} else {
|
||||
$_POST[$update_field] = $helper->ask($input, $output, $question);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($show_options_only) {
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$io->table(
|
||||
['Parameter', 'Description', 'Default', 'Choices'],
|
||||
$update_options
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function mailNotify(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
if ($input->getOption('mail-notify')) {
|
||||
$last_check_version = Settings::Get('system.update_notify_last');
|
||||
if (Update::versionInUpdate($last_check_version, AutoUpdate::getFromResult('version'))) {
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel').' ' : ''), 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');
|
||||
|
||||
@@ -441,7 +441,7 @@ class Apache extends HttpConfigBase
|
||||
if (!empty(Settings::Get('system.dhparams_file'))) {
|
||||
$dhparams = FileDir::makeCorrectFile(Settings::Get('system.dhparams_file'));
|
||||
if (!file_exists($dhparams)) {
|
||||
FileDir::safe_exec('openssl dhparam -out ' . escapeshellarg($dhparams) . ' 4096');
|
||||
file_put_contents($dhparams, self::FFDHE4096);
|
||||
}
|
||||
$this->virtualhosts_data[$vhosts_filename] .= ' SSLOpenSSLConfCmd DHParameters "' . $dhparams . '"' . "\n";
|
||||
}
|
||||
@@ -754,7 +754,7 @@ class Apache extends HttpConfigBase
|
||||
if (!empty(Settings::Get('system.dhparams_file'))) {
|
||||
$dhparams = FileDir::makeCorrectFile(Settings::Get('system.dhparams_file'));
|
||||
if (!file_exists($dhparams)) {
|
||||
FileDir::safe_exec('openssl dhparam -out ' . escapeshellarg($dhparams) . ' 4096');
|
||||
file_put_contents($dhparams, self::FFDHE4096);
|
||||
}
|
||||
$vhost_content .= ' SSLOpenSSLConfCmd DHParameters "' . $dhparams . '"' . "\n";
|
||||
}
|
||||
|
||||
@@ -45,6 +45,26 @@ use PDO;
|
||||
class HttpConfigBase
|
||||
{
|
||||
|
||||
/**
|
||||
* Pre-defined DHE groups to use as fallback if dhparams_file
|
||||
* is given, but non-existent, see also https://github.com/froxlor/Froxlor/issues/1270
|
||||
*/
|
||||
const FFDHE4096 = <<<EOC
|
||||
-----BEGIN DH PARAMETERS-----
|
||||
MIICCAKCAgEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
|
||||
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
|
||||
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
|
||||
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
|
||||
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
|
||||
ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3
|
||||
7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32
|
||||
nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e
|
||||
8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx
|
||||
iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K
|
||||
zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eZV9q//////////8CAQI=
|
||||
-----END DH PARAMETERS-----
|
||||
EOC;
|
||||
|
||||
public function init()
|
||||
{
|
||||
// if Let's Encrypt is activated, run it before regeneration of webserver configfiles
|
||||
|
||||
@@ -69,7 +69,8 @@ class AcmeSh extends FroxlorCron
|
||||
* run the task
|
||||
*
|
||||
* @param bool $internal
|
||||
* @return number
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function run(bool $internal = false)
|
||||
{
|
||||
@@ -85,6 +86,9 @@ class AcmeSh extends FroxlorCron
|
||||
if ($issue_froxlor || !empty($issue_domains) || !empty($renew_froxlor) || $renew_domains) {
|
||||
// insert task to generate certificates and vhost-configs
|
||||
Cronjob::inserttask(TaskId::REBUILD_VHOST);
|
||||
if ($renew_froxlor) {
|
||||
Cronjob::inserttask(TaskId::UPDATE_LE_SERVICES);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -217,6 +221,7 @@ class AcmeSh extends FroxlorCron
|
||||
* check whether we need to issue a new certificate for froxlor itself
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function issueFroxlorVhost()
|
||||
{
|
||||
@@ -228,9 +233,7 @@ class AcmeSh extends FroxlorCron
|
||||
");
|
||||
$froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt);
|
||||
// also check for possible existing certificate
|
||||
if (($froxlor_ssl && empty($froxlor_ssl['validtodate']))
|
||||
|| (!$froxlor_ssl && !self::checkFsFilesAreNewer(Settings::Get('system.hostname'), date('Y-m-d H:i:s')))
|
||||
) {
|
||||
if (!$froxlor_ssl || empty($froxlor_ssl['validtodate'])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -321,10 +324,12 @@ EOC;
|
||||
WHERE
|
||||
dom.`customerid` = cust.`customerid`
|
||||
AND cust.deactivated = 0
|
||||
AND dom.deactivated = 0
|
||||
AND dom.`ssl_enabled` = 1
|
||||
AND dom.`letsencrypt` = 1
|
||||
AND dom.`aliasdomain` IS NULL
|
||||
AND dom.`iswildcarddomain` = 0
|
||||
AND dom.`email_only` = 0
|
||||
AND domssl.`validtodate` IS NULL
|
||||
");
|
||||
$customer_ssl = $certificates_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
@@ -338,6 +343,7 @@ EOC;
|
||||
* check whether we need to renew-check the certificate for froxlor itself
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function renewFroxlorVhost()
|
||||
{
|
||||
@@ -383,10 +389,13 @@ EOC;
|
||||
WHERE
|
||||
dom.`customerid` = cust.`customerid`
|
||||
AND cust.deactivated = 0
|
||||
AND dom.deactivated = 0
|
||||
AND dom.`ssl_enabled` = 1
|
||||
AND dom.`letsencrypt` = 1
|
||||
AND dom.`aliasdomain` IS NULL
|
||||
AND dom.`iswildcarddomain` = 0
|
||||
AND dom.`email_only` = 0
|
||||
AND dom.`ssl_redirect` != 2
|
||||
");
|
||||
$renew_certs = $certificates_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
if ($renew_certs) {
|
||||
@@ -535,6 +544,7 @@ EOC;
|
||||
* @param array $domains
|
||||
* @param int $domain_id
|
||||
* @param FroxlorLogger $cronlog
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function validateDns(array &$domains, $domain_id, &$cronlog)
|
||||
{
|
||||
@@ -615,27 +625,47 @@ EOC;
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Successful exit-code returned - storing certificate");
|
||||
$cert_stored = self::certToDb($certrow, $cronlog, $acme_result);
|
||||
|
||||
if ($cert_stored
|
||||
&& $renew_hook
|
||||
&& !empty(trim(Settings::Get('system.le_renew_services') ?? ""))
|
||||
&& !empty(trim(Settings::Get('system.le_renew_hook') ?? ""))
|
||||
) {
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Renew-hook is enabled - adjusting configurations");
|
||||
if ($cert_stored && $renew_hook) {
|
||||
self::renewHookConfigs($cronlog);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$certificate_folder = self::getCertificateFolder(strtolower(Settings::Get('system.hostname')));
|
||||
$fullchain = FileDir::makeCorrectFile($certificate_folder . '/fullchain.cer');
|
||||
$keyfile = FileDir::makeCorrectFile($certificate_folder . '/' . strtolower(Settings::Get('system.hostname')) . '.key');
|
||||
$ca_file = FileDir::makeCorrectFile($certificate_folder . '/ca.cer');
|
||||
public static function renewHookConfigs($cronlog)
|
||||
{
|
||||
if (!empty(trim(Settings::Get('system.le_renew_services') ?? ""))
|
||||
&& !empty(trim(Settings::Get('system.le_renew_hook') ?? ""))
|
||||
) {
|
||||
|
||||
if (Settings::IsInList('system.le_renew_services', 'postfix')) {
|
||||
// "postconf -e" for postfix
|
||||
FileDir::safe_exec('postconf -e smtpd_tls_cert_file=' . escapeshellarg($fullchain));
|
||||
FileDir::safe_exec('postconf -e smtpd_tls_key_file=' . escapeshellarg($keyfile));
|
||||
}
|
||||
if (Settings::IsInList('system.le_renew_services', 'dovecot')) {
|
||||
// custom config for dovecot
|
||||
$dovecot_conf = '/etc/dovecot/conf.d/99-froxlor.ssl.conf'; // @fixme setting?
|
||||
$ssl_content = <<<EOSSL
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Renew-hook is enabled - adjusting configurations");
|
||||
|
||||
$certificate_folder = self::getCertificateFolder(strtolower(Settings::Get('system.hostname')));
|
||||
|
||||
if (empty($certificate_folder)) {
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "No certificate folder for '" . Settings::Get('system.hostname') . "' found");
|
||||
return;
|
||||
}
|
||||
|
||||
$fullchain = FileDir::makeCorrectFile($certificate_folder . '/fullchain.cer');
|
||||
$keyfile = FileDir::makeCorrectFile($certificate_folder . '/' . strtolower(Settings::Get('system.hostname')) . '.key');
|
||||
$ca_file = FileDir::makeCorrectFile($certificate_folder . '/ca.cer');
|
||||
|
||||
if (!file_exists($fullchain) || !file_exists($keyfile) || !file_exists($ca_file)) {
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "At least one of the required certificate files for '" . Settings::Get('system.hostname') . "' could not be found");
|
||||
return;
|
||||
}
|
||||
|
||||
$dovecot_conf = '/etc/dovecot/conf.d/99-froxlor.ssl.conf'; // @fixme setting?
|
||||
|
||||
if (Settings::IsInList('system.le_renew_services', 'postfix')) {
|
||||
// "postconf -e" for postfix
|
||||
FileDir::safe_exec('postconf -e smtpd_tls_cert_file=' . escapeshellarg($fullchain));
|
||||
FileDir::safe_exec('postconf -e smtpd_tls_key_file=' . escapeshellarg($keyfile));
|
||||
}
|
||||
if (Settings::IsInList('system.le_renew_services', 'dovecot')) {
|
||||
// custom config for dovecot
|
||||
$ssl_content = <<<EOSSL
|
||||
# Autogenerated configuration by froxlor.
|
||||
# Do not manually edit this file as it will be overwritten.
|
||||
|
||||
@@ -643,33 +673,35 @@ ssl = yes
|
||||
ssl_cert = <{$fullchain}
|
||||
ssl_key = <{$keyfile}
|
||||
EOSSL;
|
||||
file_put_contents($dovecot_conf, $ssl_content);
|
||||
}
|
||||
if (Settings::IsInList('system.le_renew_services', 'proftpd')) {
|
||||
$proftpd_conf = '/etc/proftpd/tls.conf'; // @fixme setting?
|
||||
$rval = false;
|
||||
// ECC certificate or not?
|
||||
if (strpos($certificate_folder, '_ecc') === false) {
|
||||
// comment out ECC related settings
|
||||
FileDir::safe_exec("sed -i.bak 's|^TLSECCertificateFile|# TLSECCertificateFile|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
FileDir::safe_exec("sed -i.bak 's|^TLSECCertificateKeyFile|# TLSECCertificateKeyFile|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
// add RSA directives
|
||||
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSRSACertificateFile.*|TLSRSACertificateFile " . $fullchain . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSRSACertificateKeyFile.*|TLSRSACertificateKeyFile " . $keyfile . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
} else {
|
||||
// comment out RSA related settings
|
||||
FileDir::safe_exec("sed -i.bak 's|^TLSRSACertificateFile|# TLSRSACertificateFile|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
FileDir::safe_exec("sed -i.bak 's|^TLSRSACertificateKeyFile|# TLSRSACertificateKeyFile|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
// add ECC directives
|
||||
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSECCertificateFile.*|TLSECCertificateFile " . $fullchain . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSECCertificateKeyFile.*|TLSECCertificateKeyFile " . $keyfile . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
}
|
||||
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSCACertificateFile.*|TLSCACertificateFile " . $ca_file . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
}
|
||||
// reload the services
|
||||
FileDir::safe_exec(Settings::Get('system.le_renew_hook'));
|
||||
}
|
||||
file_put_contents($dovecot_conf, $ssl_content);
|
||||
} elseif (file_exists($dovecot_conf)) {
|
||||
// safely remove the autogenerated config file
|
||||
unlink($dovecot_conf);
|
||||
}
|
||||
if (Settings::IsInList('system.le_renew_services', 'proftpd')) {
|
||||
$proftpd_conf = '/etc/proftpd/tls.conf'; // @fixme setting?
|
||||
$rval = false;
|
||||
// ECC certificate or not?
|
||||
if (strpos($certificate_folder, '_ecc') === false) {
|
||||
// comment out ECC related settings
|
||||
FileDir::safe_exec("sed -i.bak 's|^TLSECCertificateFile|# TLSECCertificateFile|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
FileDir::safe_exec("sed -i.bak 's|^TLSECCertificateKeyFile|# TLSECCertificateKeyFile|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
// add RSA directives
|
||||
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSRSACertificateFile.*|TLSRSACertificateFile " . $fullchain . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSRSACertificateKeyFile.*|TLSRSACertificateKeyFile " . $keyfile . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
} else {
|
||||
// comment out RSA related settings
|
||||
FileDir::safe_exec("sed -i.bak 's|^TLSRSACertificateFile|# TLSRSACertificateFile|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
FileDir::safe_exec("sed -i.bak 's|^TLSRSACertificateKeyFile|# TLSRSACertificateKeyFile|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
// add ECC directives
|
||||
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSECCertificateFile.*|TLSECCertificateFile " . $fullchain . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSECCertificateKeyFile.*|TLSECCertificateKeyFile " . $keyfile . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
}
|
||||
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSCACertificateFile.*|TLSCACertificateFile " . $ca_file . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||
}
|
||||
|
||||
// reload the services
|
||||
FileDir::safe_exec(Settings::Get('system.le_renew_hook'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -273,7 +273,7 @@ class Lighttpd extends HttpConfigBase
|
||||
if (!empty(Settings::Get('system.dhparams_file'))) {
|
||||
$dhparams = FileDir::makeCorrectFile(Settings::Get('system.dhparams_file'));
|
||||
if (!file_exists($dhparams)) {
|
||||
FileDir::safe_exec('openssl dhparam -out ' . escapeshellarg($dhparams) . ' 4096');
|
||||
file_put_contents($dhparams, self::FFDHE4096);
|
||||
}
|
||||
$this->lighttpd_data[$vhost_filename] .= 'ssl.dh-file = "' . $dhparams . '"' . "\n";
|
||||
$this->lighttpd_data[$vhost_filename] .= 'ssl.ec-curve = "secp384r1"' . "\n";
|
||||
@@ -756,7 +756,7 @@ class Lighttpd extends HttpConfigBase
|
||||
if (!empty(Settings::Get('system.dhparams_file'))) {
|
||||
$dhparams = FileDir::makeCorrectFile(Settings::Get('system.dhparams_file'));
|
||||
if (!file_exists($dhparams)) {
|
||||
FileDir::safe_exec('openssl dhparam -out ' . escapeshellarg($dhparams) . ' 4096');
|
||||
file_put_contents($dhparams, self::FFDHE4096);
|
||||
}
|
||||
$ssl_settings .= 'ssl.dh-file = "' . $dhparams . '"' . "\n";
|
||||
$ssl_settings .= 'ssl.ec-curve = "secp384r1"' . "\n";
|
||||
|
||||
@@ -399,7 +399,7 @@ class Nginx extends HttpConfigBase
|
||||
if (!empty(Settings::Get('system.dhparams_file'))) {
|
||||
$dhparams = FileDir::makeCorrectFile(Settings::Get('system.dhparams_file'));
|
||||
if (!file_exists($dhparams)) {
|
||||
FileDir::safe_exec('openssl dhparam -out ' . escapeshellarg($dhparams) . ' 4096');
|
||||
file_put_contents($dhparams, self::FFDHE4096);
|
||||
}
|
||||
$sslsettings .= "\t" . 'ssl_dhparam ' . $dhparams . ';' . "\n";
|
||||
}
|
||||
@@ -435,7 +435,7 @@ class Nginx extends HttpConfigBase
|
||||
$sslsettings .= '";' . "\n";
|
||||
}
|
||||
|
||||
if ((isset($domain_or_ip['ocsp_stapling']) && $domain_or_ip['ocsp_stapling'] == "1") || (isset($domain_or_ip['letsencrypt']) && $domain_or_ip['letsencrypt'] == "1")) {
|
||||
if ((isset($domain_or_ip['ocsp_stapling']) && $domain_or_ip['ocsp_stapling'] == "1")) {
|
||||
$sslsettings .= "\t" . 'ssl_stapling on;' . "\n";
|
||||
$sslsettings .= "\t" . 'ssl_stapling_verify on;' . "\n";
|
||||
$sslsettings .= "\t" . 'ssl_trusted_certificate ' . FileDir::makeCorrectFile($domain_or_ip['ssl_cert_file']) . ';' . "\n";
|
||||
|
||||
@@ -274,8 +274,8 @@ pm.max_children = 1
|
||||
|
||||
$fpm_config .= "\n\n";
|
||||
foreach ($phpini_array as $inisection) {
|
||||
$is = explode("=", trim($inisection));
|
||||
if (count($is) !== 2 || empty($is[0])) {
|
||||
$is = explode("=", trim($inisection), 2);
|
||||
if (empty($is[0])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($this->ini as $sec => $possibles) {
|
||||
|
||||
@@ -55,7 +55,7 @@ class Rspamd
|
||||
|
||||
// get all email addresses
|
||||
$antispam_stmt = Database::prepare("
|
||||
SELECT email, spam_tag_level, spam_kill_level, bypass_spam, policy_greylist, iscatchall
|
||||
SELECT email, spam_tag_level, rewrite_subject, spam_kill_level, bypass_spam, policy_greylist, iscatchall
|
||||
FROM `" . TABLE_MAIL_VIRTUAL . "`
|
||||
ORDER BY email
|
||||
");
|
||||
@@ -165,7 +165,7 @@ class Rspamd
|
||||
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Generating antispam config for ' . $email['email']);
|
||||
|
||||
$email['spam_tag_level'] = floatval($email['spam_tag_level']);
|
||||
$email['spam_kill_level'] = floatval($email['spam_kill_level']);
|
||||
$email['spam_kill_level'] = $email['spam_kill_level'] == -1 ? "null" : floatval($email['spam_kill_level']);
|
||||
$email_id = md5($email['email']);
|
||||
|
||||
$this->frx_settings_file .= '# Email: ' . $email['email'] . "\n";
|
||||
@@ -185,7 +185,9 @@ class Rspamd
|
||||
$this->frx_settings_file .= ' apply {' . "\n";
|
||||
$this->frx_settings_file .= ' actions {' . "\n";
|
||||
$this->frx_settings_file .= ' "add header" = ' . $email['spam_tag_level'] . ';' . "\n";
|
||||
$this->frx_settings_file .= ' rewrite_subject = ' . $email['spam_tag_level'] . ';' . "\n";
|
||||
if ((int)$email['rewrite_subject'] == 1) {
|
||||
$this->frx_settings_file .= ' rewrite_subject = ' . ($email['spam_tag_level'] + 0.01) . ';' . "\n";
|
||||
}
|
||||
$this->frx_settings_file .= ' reject = ' . $email['spam_kill_level'] . ';' . "\n";
|
||||
if ($type == 'rcpt' && (int)$email['policy_greylist'] == 0) {
|
||||
$this->frx_settings_file .= ' greylist = null;' . "\n";
|
||||
|
||||
@@ -29,6 +29,7 @@ use Exception;
|
||||
use Froxlor\Cron\FroxlorCron;
|
||||
use Froxlor\Cron\Http\ConfigIO;
|
||||
use Froxlor\Cron\Http\HttpConfigBase;
|
||||
use Froxlor\Cron\Http\LetsEncrypt\AcmeSh;
|
||||
use Froxlor\Cron\Mail\Rspamd;
|
||||
use Froxlor\Cron\TaskId;
|
||||
use Froxlor\Database\Database;
|
||||
@@ -125,6 +126,12 @@ class TasksCron extends FroxlorCron
|
||||
*/
|
||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, "Removing Let's Encrypt entries for domain " . $row['data']['domain']);
|
||||
Domain::doLetsEncryptCleanUp($row['data']['domain']);
|
||||
} elseif ($row['type'] == TaskId::UPDATE_LE_SERVICES) {
|
||||
/**
|
||||
* TYPE=13 set configuration for selected services regarding the use of Let's Encrypt certificate
|
||||
*/
|
||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, "Updating Let's Encrypt configuration for selected services");
|
||||
AcmeSh::renewHookConfigs(FroxlorLogger::getInstanceOf());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,10 +341,11 @@ class TasksCron extends FroxlorCron
|
||||
// webserver logs
|
||||
$logsdir = FileDir::makeCorrectFile(Settings::Get('system.logfiles_directory') . '/' . $row['data']['loginname']);
|
||||
|
||||
if (file_exists($logsdir) && $logsdir != '/' && $logsdir != FileDir::makeCorrectDir(Settings::Get('system.logfiles_directory')) && substr($logsdir, 0, strlen(Settings::Get('system.logfiles_directory'))) == Settings::Get('system.logfiles_directory')) {
|
||||
if (file_exists(dirname($logsdir)) && $logsdir != '/' && $logsdir != FileDir::makeCorrectDir(Settings::Get('system.logfiles_directory')) && substr($logsdir, 0, strlen(Settings::Get('system.logfiles_directory'))) == Settings::Get('system.logfiles_directory')) {
|
||||
// build up wildcard for webX-{access,error}.log{*}
|
||||
$logsdir .= '-*';
|
||||
FileDir::safe_exec('rm -f ' . escapeshellarg($logsdir));
|
||||
$logsdir .= '-*.log';
|
||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'Running: rm -rf ' .FileDir::makeCorrectFile($logsdir));
|
||||
FileDir::safe_exec('rm -f ' . FileDir::makeCorrectFile($logsdir));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,11 @@ final class TaskId
|
||||
*/
|
||||
const DELETE_DOMAIN_SSL = 12;
|
||||
|
||||
/**
|
||||
* TYPE=13 set configuration for selected services regarding the use of Let's Encrypt certificate
|
||||
*/
|
||||
const UPDATE_LE_SERVICES = 13;
|
||||
|
||||
/**
|
||||
* TYPE=20 CUSTUMER DATA DUMP
|
||||
*/
|
||||
|
||||
@@ -70,7 +70,7 @@ class ReportsCron extends FroxlorCron
|
||||
) as `traffic_used`
|
||||
FROM `" . TABLE_PANEL_CUSTOMERS . "` AS `c`
|
||||
LEFT JOIN `" . TABLE_PANEL_ADMINS . "` AS `a`
|
||||
ON `a`.`adminid` = `c`.`adminid` WHERE `c`.`reportsent` <> '1'
|
||||
ON `a`.`adminid` = `c`.`adminid` WHERE `c`.`reportsent` & 1 = 0
|
||||
");
|
||||
|
||||
$result_data = [
|
||||
@@ -79,6 +79,11 @@ class ReportsCron extends FroxlorCron
|
||||
];
|
||||
Database::pexecute($result_stmt, $result_data);
|
||||
|
||||
$upd_stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `reportsent` = `reportsent` + 1
|
||||
WHERE `customerid` = :customerid
|
||||
");
|
||||
|
||||
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$row['traffic'] *= 1024;
|
||||
$row['traffic_used'] *= 1024;
|
||||
@@ -128,7 +133,9 @@ class ReportsCron extends FroxlorCron
|
||||
$_mailerror = false;
|
||||
$mailerr_msg = "";
|
||||
try {
|
||||
$mail->SetFrom($row['adminmail'], $row['adminname']);
|
||||
$mail->setFrom(Settings::Get('panel.adminmail'), $row['adminname']);
|
||||
$mail->clearReplyTos();
|
||||
$mail->addReplyTo($row['adminmail'], $row['adminname']);
|
||||
$mail->Subject = $mail_subject;
|
||||
$mail->AltBody = $mail_body;
|
||||
$mail->MsgHTML(nl2br($mail_body));
|
||||
@@ -148,10 +155,6 @@ class ReportsCron extends FroxlorCron
|
||||
}
|
||||
|
||||
$mail->ClearAddresses();
|
||||
$upd_stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `reportsent` = '1'
|
||||
WHERE `customerid` = :customerid
|
||||
");
|
||||
Database::pexecute($upd_stmt, [
|
||||
'customerid' => $row['customerid']
|
||||
]);
|
||||
@@ -165,7 +168,7 @@ class ReportsCron extends FroxlorCron
|
||||
FROM `" . TABLE_PANEL_TRAFFIC_ADMINS . "` `t`
|
||||
WHERE `t`.`adminid` = `a`.`adminid` AND `t`.`year` = :year AND `t`.`month` = :month
|
||||
) as `traffic_used_total`
|
||||
FROM `" . TABLE_PANEL_ADMINS . "` `a` WHERE `a`.`reportsent` = '0'
|
||||
FROM `" . TABLE_PANEL_ADMINS . "` `a` WHERE `a`.`reportsent` & 1 = 0
|
||||
");
|
||||
|
||||
$result_data = [
|
||||
@@ -174,6 +177,11 @@ class ReportsCron extends FroxlorCron
|
||||
];
|
||||
Database::pexecute($result_stmt, $result_data);
|
||||
|
||||
$upd_stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_PANEL_ADMINS . "` SET `reportsent` = `reportsent` + 1
|
||||
WHERE `adminid` = :adminid
|
||||
");
|
||||
|
||||
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$row['traffic'] *= 1024;
|
||||
$row['traffic_used_total'] *= 1024;
|
||||
@@ -231,10 +239,6 @@ class ReportsCron extends FroxlorCron
|
||||
}
|
||||
|
||||
$mail->ClearAddresses();
|
||||
$upd_stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_PANEL_ADMINS . "` SET `reportsent` = '1'
|
||||
WHERE `adminid` = :adminid
|
||||
");
|
||||
Database::pexecute($upd_stmt, [
|
||||
'adminid' => $row['adminid']
|
||||
]);
|
||||
@@ -344,11 +348,16 @@ class ReportsCron extends FroxlorCron
|
||||
FROM `" . TABLE_PANEL_CUSTOMERS . "` AS `c`
|
||||
LEFT JOIN `" . TABLE_PANEL_ADMINS . "` AS `a`
|
||||
ON `a`.`adminid` = `c`.`adminid`
|
||||
WHERE `c`.`diskspace` > '0' AND `c`.`reportsent` <> '2'
|
||||
WHERE `c`.`diskspace` > '0' AND `c`.`reportsent` & 2 = 0
|
||||
");
|
||||
|
||||
$mail = new Mailer(true);
|
||||
|
||||
$upd_stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `reportsent` = `reportsent` + 2
|
||||
WHERE `customerid` = :customerid
|
||||
");
|
||||
|
||||
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$row['diskspace'] *= 1024;
|
||||
$row['diskspace_used'] *= 1024;
|
||||
@@ -398,7 +407,9 @@ class ReportsCron extends FroxlorCron
|
||||
$_mailerror = false;
|
||||
$mailerr_msg = "";
|
||||
try {
|
||||
$mail->SetFrom($row['adminmail'], $row['adminname']);
|
||||
$mail->setFrom(Settings::Get('panel.adminmail'), $row['adminname']);
|
||||
$mail->clearReplyTos();
|
||||
$mail->addReplyTo($row['adminmail'], $row['adminname']);
|
||||
$mail->Subject = $mail_subject;
|
||||
$mail->AltBody = $mail_body;
|
||||
$mail->MsgHTML(nl2br($mail_body));
|
||||
@@ -418,10 +429,6 @@ class ReportsCron extends FroxlorCron
|
||||
}
|
||||
|
||||
$mail->ClearAddresses();
|
||||
$upd_stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `reportsent` = '2'
|
||||
WHERE `customerid` = :customerid
|
||||
");
|
||||
Database::pexecute($upd_stmt, [
|
||||
'customerid' => $row['customerid']
|
||||
]);
|
||||
@@ -432,7 +439,12 @@ class ReportsCron extends FroxlorCron
|
||||
* report about diskusage for admins/reseller
|
||||
*/
|
||||
$result_stmt = Database::query("
|
||||
SELECT `a`.* FROM `" . TABLE_PANEL_ADMINS . "` `a` WHERE `a`.`reportsent` <> '2'
|
||||
SELECT `a`.* FROM `" . TABLE_PANEL_ADMINS . "` `a` WHERE `a`.`reportsent` & 2 = 0
|
||||
");
|
||||
|
||||
$upd_stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_PANEL_ADMINS . "` SET `reportsent` = `reportsent` + 2
|
||||
WHERE `adminid` = :adminid
|
||||
");
|
||||
|
||||
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
@@ -492,10 +504,6 @@ class ReportsCron extends FroxlorCron
|
||||
}
|
||||
|
||||
$mail->ClearAddresses();
|
||||
$upd_stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_PANEL_ADMINS . "` SET `reportsent` = '2'
|
||||
WHERE `adminid` = :adminid
|
||||
");
|
||||
Database::pexecute($upd_stmt, [
|
||||
'adminid' => $row['adminid']
|
||||
]);
|
||||
|
||||
@@ -133,7 +133,7 @@ class Database
|
||||
* if set to false, the error will be logged, but we go on
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function showerror(Exception $error, bool $showerror = true, bool $json_response = false, PDOStatement $stmt = null)
|
||||
private static function showerror(Exception $error, bool $showerror = true, bool $json_response = false, ?PDOStatement $stmt = null)
|
||||
{
|
||||
global $userinfo, $theme, $linker;
|
||||
|
||||
@@ -377,6 +377,14 @@ class Database
|
||||
self::$link = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the currently used database-server (relevant for root-connection)
|
||||
*/
|
||||
public static function getServer()
|
||||
{
|
||||
return self::$dbserver;
|
||||
}
|
||||
|
||||
/**
|
||||
* enable the temporary access to sql-access data
|
||||
* note: if you want root-sqldata you need to
|
||||
|
||||
@@ -102,8 +102,26 @@ class DbManager
|
||||
$databases[$databases_row['dbserver']][] = $databases_row['databasename'];
|
||||
}
|
||||
|
||||
$customers_sel = Database::query("
|
||||
SELECT DISTINCT c.loginname
|
||||
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
|
||||
");
|
||||
$customers = [];
|
||||
while ($customer = $customers_sel->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$customers[] = $customer['loginname'];
|
||||
}
|
||||
|
||||
$dbservers_stmt = Database::query("SELECT DISTINCT `dbserver` FROM `" . TABLE_PANEL_DATABASES . "`");
|
||||
while ($dbserver = $dbservers_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
|
||||
// add all customer loginnames to the $databases array for this database-server to correct
|
||||
// a possible existing global mysql-user for that customer
|
||||
foreach ($customers as $customer) {
|
||||
$databases[$dbserver['dbserver']][] = $customer;
|
||||
}
|
||||
|
||||
// require privileged access for target db-server
|
||||
Database::needRoot(true, $dbserver['dbserver'], false);
|
||||
|
||||
@@ -136,6 +154,8 @@ class DbManager
|
||||
|
||||
$dbm->getManager()->flushPrivileges();
|
||||
Database::needRoot(false);
|
||||
|
||||
unset($databases[$dbserver['dbserver']]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,11 +169,12 @@ class DbManager
|
||||
* @param ?string $password
|
||||
* @param int $dbserver
|
||||
* @param int $last_accnumber
|
||||
* @param ?string $global_user
|
||||
*
|
||||
* @return string|bool $username if successful or false of username is equal to the password
|
||||
* @throws Exception
|
||||
*/
|
||||
public function createDatabase(string $loginname = null, string $password = null, int $dbserver = 0, int $last_accnumber = 0)
|
||||
public function createDatabase(string $loginname = null, string $password = null, int $dbserver = 0, int $last_accnumber = 0, string $global_user = "")
|
||||
{
|
||||
Database::needRoot(true, $dbserver, false);
|
||||
|
||||
@@ -184,10 +205,13 @@ class DbManager
|
||||
// and give permission to the user on every access-host we have
|
||||
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
|
||||
$this->getManager()->grantPrivilegesTo($username, $password, $mysql_access_host);
|
||||
if (!empty($global_user)) {
|
||||
$this->getManager()->grantCreateToDb($global_user, $username, $mysql_access_host);
|
||||
}
|
||||
}
|
||||
|
||||
$this->getManager()->flushPrivileges();
|
||||
Database::needRoot(false);
|
||||
Database::needRoot();
|
||||
|
||||
$this->log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "created database '" . $username . "'");
|
||||
|
||||
|
||||
@@ -110,13 +110,18 @@ class DbManagerMySQL
|
||||
"password" => $password
|
||||
]);
|
||||
// grant privileges
|
||||
$grants = "ALL";
|
||||
if ($grant_access_prefix) {
|
||||
$grants = "SELECT, INSERT, UPDATE, DELETE, DROP, INDEX, ALTER";
|
||||
}
|
||||
$stmt = Database::prepare("
|
||||
GRANT ALL ON `" . $username . ($grant_access_prefix ? '%' : '') . "`.* TO :username@:host
|
||||
GRANT " . $grants . " ON `" . $username . ($grant_access_prefix ? '%' : '') . "`.* TO `" . $username . "`@`" . $access_host . "`
|
||||
");
|
||||
Database::pexecute($stmt, [
|
||||
"username" => $username,
|
||||
"host" => $access_host
|
||||
]);
|
||||
Database::pexecute($stmt);
|
||||
|
||||
if ($grant_access_prefix) {
|
||||
$this->grantCreateToCustomerDbs($username, $access_host);
|
||||
}
|
||||
} else {
|
||||
// set password
|
||||
if (version_compare(Database::getAttribute(\PDO::ATTR_SERVER_VERSION), '5.7.6', '<') || version_compare(Database::getAttribute(\PDO::ATTR_SERVER_VERSION), '10.0.0', '>=')) {
|
||||
@@ -145,9 +150,10 @@ class DbManagerMySQL
|
||||
* takes away any privileges from a user to that db
|
||||
*
|
||||
* @param string $dbname
|
||||
* @param ?string $global_user
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function deleteDatabase(string $dbname)
|
||||
public function deleteDatabase(string $dbname, string $global_user = "")
|
||||
{
|
||||
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.0.2', '<')) {
|
||||
// failsafe if user has been deleted manually (requires MySQL 4.1.2+)
|
||||
@@ -167,11 +173,19 @@ class DbManagerMySQL
|
||||
} else {
|
||||
$drop_stmt = Database::prepare("DROP USER IF EXISTS :dbname@:host");
|
||||
}
|
||||
$rev_stmt = Database::prepare("REVOKE ALL PRIVILEGES ON `" . $dbname . "`.* FROM :guser@:host;");
|
||||
while ($host = $host_res_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
Database::pexecute($drop_stmt, [
|
||||
'dbname' => $dbname,
|
||||
'host' => $host['Host']
|
||||
], false);
|
||||
|
||||
if (!empty($global_user)) {
|
||||
Database::pexecute($rev_stmt, [
|
||||
'guser' => $global_user,
|
||||
'host' => $host['Host']
|
||||
], false);
|
||||
}
|
||||
}
|
||||
|
||||
$drop_stmt = Database::prepare("DROP DATABASE IF EXISTS `" . $dbname . "`");
|
||||
@@ -187,21 +201,23 @@ class DbManagerMySQL
|
||||
*/
|
||||
public function deleteUser(string $username, string $host)
|
||||
{
|
||||
if (Database::getAttribute(PDO::ATTR_SERVER_VERSION) < '5.0.2') {
|
||||
// Revoke privileges (only required for MySQL 4.1.2 - 5.0.1)
|
||||
$stmt = Database::prepare("REVOKE ALL PRIVILEGES ON * . * FROM `" . $username . "`@`" . $host . "`");
|
||||
Database::pexecute($stmt);
|
||||
if ($this->userExistsOnHost($username, $host)) {
|
||||
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.0.2', '<')) {
|
||||
// Revoke privileges (only required for MySQL 4.1.2 - 5.0.1)
|
||||
$stmt = Database::prepare("REVOKE ALL PRIVILEGES ON * . * FROM `" . $username . "`@`" . $host . "`");
|
||||
Database::pexecute($stmt);
|
||||
}
|
||||
// as of MySQL 5.0.2 this also revokes privileges. (requires MySQL 4.1.2+)
|
||||
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.7.0', '<')) {
|
||||
$stmt = Database::prepare("DROP USER :username@:host");
|
||||
} else {
|
||||
$stmt = Database::prepare("DROP USER IF EXISTS :username@:host");
|
||||
}
|
||||
Database::pexecute($stmt, [
|
||||
"username" => $username,
|
||||
"host" => $host
|
||||
]);
|
||||
}
|
||||
// as of MySQL 5.0.2 this also revokes privileges. (requires MySQL 4.1.2+)
|
||||
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.7.0', '<')) {
|
||||
$stmt = Database::prepare("DROP USER :username@:host");
|
||||
} else {
|
||||
$stmt = Database::prepare("DROP USER IF EXISTS :username@:host");
|
||||
}
|
||||
Database::pexecute($stmt, [
|
||||
"username" => $username,
|
||||
"host" => $host
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,8 +245,15 @@ class DbManagerMySQL
|
||||
{
|
||||
// check whether user exists to avoid errors
|
||||
if ($this->userExistsOnHost($username, $host)) {
|
||||
Database::query('GRANT ALL PRIVILEGES ON `' . $username . ($grant_access_prefix ? '%' : '') . '`.* TO `' . $username . '`@`' . $host . '`');
|
||||
Database::query('GRANT ALL PRIVILEGES ON `' . str_replace('_', '\_', $username) . ($grant_access_prefix ? '%' : '') . '` . * TO `' . $username . '`@`' . $host . '`');
|
||||
$grants = "ALL PRIVILEGES";
|
||||
if ($grant_access_prefix) {
|
||||
$grants = "SELECT, INSERT, UPDATE, DELETE, DROP, INDEX, ALTER";
|
||||
}
|
||||
Database::query('GRANT ' . $grants . ' ON `' . $username . ($grant_access_prefix ? '%' : '') . '`.* TO `' . $username . '`@`' . $host . '`');
|
||||
Database::query('GRANT ' . $grants . ' ON `' . str_replace('_', '\_', $username) . ($grant_access_prefix ? '%' : '') . '` . * TO `' . $username . '`@`' . $host . '`');
|
||||
if ($grant_access_prefix) {
|
||||
$this->grantCreateToCustomerDbs($username, $host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,4 +313,54 @@ class DbManagerMySQL
|
||||
}
|
||||
return $allsqlusers;
|
||||
}
|
||||
|
||||
/**
|
||||
* grant "CREATE" for prefix user to all existing databases of that customer
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $access_host
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function grantCreateToCustomerDbs(string $username, string $access_host)
|
||||
{
|
||||
// remember what (possible remote) db-server we're on
|
||||
$currentDbServer = Database::getServer();
|
||||
// use "unprivileged" connection
|
||||
Database::needRoot();
|
||||
$cus_stmt = Database::prepare("SELECT customerid FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE loginname = :username");
|
||||
$cust = Database::pexecute_first($cus_stmt, ['username' => $username]);
|
||||
if ($cust) {
|
||||
$sel_stmt = Database::prepare("SELECT databasename FROM `" . TABLE_PANEL_DATABASES . "` WHERE `customerid` = :cid AND `dbserver` = :dbserver");
|
||||
Database::pexecute($sel_stmt, ['cid' => $cust['customerid'], 'dbserver' => $currentDbServer]);
|
||||
// reset to root-connection for used dbserver
|
||||
Database::needRoot(true, $currentDbServer, false);
|
||||
while ($dbdata = $sel_stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$stmt = Database::prepare("
|
||||
GRANT ALL ON `" . $dbdata['databasename'] . "`.* TO `" . $username . "`@`" . $access_host . "`
|
||||
");
|
||||
Database::pexecute($stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* grant "CREATE" for prefix user to all existing databases of that customer
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $database
|
||||
* @param string $access_host
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function grantCreateToDb(string $username, string $database, string $access_host)
|
||||
{
|
||||
// only grant permission if the user exists
|
||||
if ($this->userExistsOnHost($username, $access_host)) {
|
||||
$stmt = Database::prepare("
|
||||
GRANT ALL ON `" . $database . "`.* TO `" . $username . "`@`" . $access_host . "`
|
||||
");
|
||||
Database::pexecute($stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ class Dns
|
||||
$dom_data['uid'] = $userinfo['userid'];
|
||||
}
|
||||
} else {
|
||||
$where_clause = '`customerid` = :uid AND ';
|
||||
$where_clause = '`customerid` = :uid AND `email_only` = "0" AND ';
|
||||
$dom_data['uid'] = $userinfo['userid'];
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,10 @@ final class Froxlor
|
||||
{
|
||||
|
||||
// Main version variable
|
||||
const VERSION = '2.2.0-rc1';
|
||||
const VERSION = '2.2.8';
|
||||
|
||||
// Database version (YYYYMMDDC where C is a daily counter)
|
||||
const DBVERSION = '202401090';
|
||||
const DBVERSION = '202412030';
|
||||
|
||||
// Distribution branding-tag (used for Debian etc.)
|
||||
const BRANDING = '';
|
||||
@@ -316,7 +316,7 @@ final class Froxlor
|
||||
* @param array|null $arr
|
||||
* @return void
|
||||
*/
|
||||
private static function parseVersionArray(array &$arr = null)
|
||||
private static function parseVersionArray(?array &$arr)
|
||||
{
|
||||
// -dev or -beta or -rc ?
|
||||
if (stripos($arr[count($arr) - 1], '-') !== false) {
|
||||
|
||||
@@ -164,7 +164,7 @@ class FroxlorLogger
|
||||
* @param int $type
|
||||
* @param ?string $text
|
||||
*/
|
||||
public function logAction($action = FroxlorLogger::USR_ACTION, int $type = LOG_NOTICE, string $text = null)
|
||||
public function logAction($action = FroxlorLogger::USR_ACTION, int $type = LOG_NOTICE, ?string $text = null)
|
||||
{
|
||||
// not logging normal stuff if not set to "paranoid" logging
|
||||
if (!self::$crondebug_flag && Settings::Get('logger.severity') == '1' && $type > LOG_NOTICE) {
|
||||
|
||||
@@ -85,27 +85,31 @@ class Preconfig
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function getPreConfig
|
||||
*
|
||||
* outputs various form-field-arrays before the update process
|
||||
* can be continued (asks for agreement whatever is being asked)
|
||||
*
|
||||
* @param bool $no_check
|
||||
* @return array
|
||||
*/
|
||||
public static function getPreConfig(): array
|
||||
public static function getPreConfig(bool $no_check = false): array
|
||||
{
|
||||
$preconfig = new self();
|
||||
|
||||
if ($preconfig->hasPreConfig()) {
|
||||
$agree = [
|
||||
'title' => 'Check',
|
||||
'fields' => [
|
||||
'update_changesagreed' => ['mandatory' => true, 'type' => 'checkrequired', 'value' => 1, 'label' => '<strong>I have read the update notifications above and I am aware of the changes made to my system.</strong>'],
|
||||
'update_preconfig' => ['type' => 'hidden', 'value' => 1]
|
||||
]
|
||||
];
|
||||
$preconfig->addToPreConfig($agree);
|
||||
if (!$no_check) {
|
||||
$agree = [
|
||||
'title' => 'Check',
|
||||
'fields' => [
|
||||
'update_changesagreed' => ['mandatory' => true, 'type' => 'checkrequired', 'value' => 1, 'label' => '<strong>I have read the update notifications above and I am aware of the changes made to my system.</strong>'],
|
||||
'update_preconfig' => ['type' => 'hidden', 'value' => 1]
|
||||
]
|
||||
];
|
||||
$preconfig->addToPreConfig($agree);
|
||||
}
|
||||
return $preconfig->getData();
|
||||
}
|
||||
return [];
|
||||
|
||||
@@ -104,6 +104,10 @@ class MailLogParser
|
||||
unset($matches);
|
||||
$line = fgets($file_handle);
|
||||
|
||||
if (strpos($line, 'postfix') === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$timestamp = $this->getLogTimestamp($line);
|
||||
if ($this->startTime < $timestamp) {
|
||||
if (preg_match("/postfix\/qmgr.*(?::|\])\s([A-Z\d]+).*from=<?(?:.*\@([a-zA-Z\d\.\-]+))?>?, size=(\d+),/", $line, $matches)) {
|
||||
@@ -112,7 +116,7 @@ class MailLogParser
|
||||
"domainFrom" => strtolower($matches[2]),
|
||||
"size" => $matches[3]
|
||||
];
|
||||
} elseif (preg_match("/postfix\/(?:pipe|smtp).*(?::|\])\s([A-Z\d]+).*to=<?(?:.*\@([a-zA-Z\d\.\-]+))?>?,/", $line, $matches)) {
|
||||
} elseif (preg_match("/postfix\/(?:pipe|smtp|lmtp).*(?::|\])\s([A-Z\d]+).*to=<?(?:.*\@([a-zA-Z\d\.\-]+))?>?,/", $line, $matches)) {
|
||||
// Postfix to
|
||||
if (array_key_exists($matches[1], $this->mails)) {
|
||||
$this->mails[$matches[1]]["domainTo"] = strtolower($matches[2]);
|
||||
@@ -149,7 +153,7 @@ class MailLogParser
|
||||
private function getLogTimestamp($line)
|
||||
{
|
||||
$matches = null;
|
||||
if (preg_match("/((?:[A-Z]{3}\s{1,2}\d{1,2}|\d{4}-\d{2}-\d{2}) \d{2}:\d{2}:\d{2})/i", $line, $matches)) {
|
||||
if (preg_match("/((?:[A-Z]{3}\s{1,2}\d{1,2}|\d{4}-\d{2}-\d{2}).\d{2}:\d{2}:\d{2})/i", $line, $matches)) {
|
||||
$timestamp = strtotime($matches[1]);
|
||||
if ($timestamp > ($this->startTime + 60 * 60 * 24)) {
|
||||
return strtotime($matches[1] . " -1 year");
|
||||
@@ -258,6 +262,10 @@ class MailLogParser
|
||||
unset($matches);
|
||||
$line = fgets($file_handle);
|
||||
|
||||
if (strpos($line, 'dovecot') === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$timestamp = $this->getLogTimestamp($line);
|
||||
if ($this->startTime < $timestamp) {
|
||||
if (preg_match("/dovecot.*(?::|\]) imap\(.*@([a-z0-9\.\-]+)\)(<\d+><[a-z0-9+\/=]+>)?:.*(?:in=(\d+) out=(\d+)|bytes=(\d+)\/(\d+))/i", $line, $matches)) {
|
||||
|
||||
@@ -199,7 +199,7 @@ class PhpHelper
|
||||
* @param string|null $nameserver set additional resolver nameserver to use (e.g. 1.1.1.1)
|
||||
* @return bool|array
|
||||
*/
|
||||
public static function gethostbynamel6(string $host, bool $try_a = true, string $nameserver = null)
|
||||
public static function gethostbynamel6(string $host, bool $try_a = true, ?string $nameserver = null)
|
||||
{
|
||||
$ips = [];
|
||||
|
||||
@@ -442,7 +442,7 @@ class PhpHelper
|
||||
* @param bool $asReturn
|
||||
* @return string
|
||||
*/
|
||||
public static function parseArrayToPhpFile(array $array, string $comment = null, bool $asReturn = false): string
|
||||
public static function parseArrayToPhpFile(array $array, ?string $comment = null, bool $asReturn = false): string
|
||||
{
|
||||
$str = sprintf("<?php\n// %s\n\n", $comment ?? 'autogenerated froxlor file');
|
||||
|
||||
@@ -464,7 +464,7 @@ class PhpHelper
|
||||
* @param int $depth
|
||||
* @return string
|
||||
*/
|
||||
public static function parseArrayToString(array $array, string $key = null, int $depth = 1): string
|
||||
public static function parseArrayToString(array $array, ?string $key = null, int $depth = 1): string
|
||||
{
|
||||
$str = '';
|
||||
if (!is_null($key)) {
|
||||
|
||||
@@ -125,25 +125,20 @@ class Store
|
||||
}
|
||||
|
||||
if (count($ids) > 0) {
|
||||
$defaultips_new = explode(',', $newfieldvalue);
|
||||
|
||||
if (!empty($defaultips_old) && !empty($newfieldvalue)) {
|
||||
$in_value = $defaultips_old . ", " . $newfieldvalue;
|
||||
} elseif (!empty($defaultips_old) && empty($newfieldvalue)) {
|
||||
$in_value = $defaultips_old;
|
||||
} else {
|
||||
$in_value = $newfieldvalue;
|
||||
if (!empty($defaultips_old)) {
|
||||
// Delete the existing mappings linking to default IPs
|
||||
$del_stmt = Database::prepare("
|
||||
DELETE FROM `" . TABLE_DOMAINTOIP . "`
|
||||
WHERE `id_domain` IN (" . implode(', ', $ids) . ")
|
||||
AND `id_ipandports` IN (" . $defaultips_old . ")
|
||||
");
|
||||
Database::pexecute($del_stmt);
|
||||
}
|
||||
|
||||
// Delete the existing mappings linking to default IPs
|
||||
$del_stmt = Database::prepare("
|
||||
DELETE FROM `" . TABLE_DOMAINTOIP . "`
|
||||
WHERE `id_domain` IN (" . implode(', ', $ids) . ")
|
||||
AND `id_ipandports` IN (" . $in_value . ")
|
||||
");
|
||||
Database::pexecute($del_stmt);
|
||||
|
||||
$defaultips_new = !empty($newfieldvalue) ? explode(",", $newfieldvalue) : [];
|
||||
if (count($defaultips_new) > 0) {
|
||||
|
||||
// Insert the new mappings
|
||||
$ins_stmt = Database::prepare("
|
||||
INSERT INTO `" . TABLE_DOMAINTOIP . "`
|
||||
@@ -166,6 +161,9 @@ class Store
|
||||
{
|
||||
$defaultips_old = Settings::Get('system.defaultsslip');
|
||||
|
||||
self::cleanIpSelection($defaultips_old);
|
||||
self::cleanIpSelection($newfieldvalue);
|
||||
|
||||
$returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue);
|
||||
|
||||
if ($returnvalue !== false && is_array($fielddata) && isset($fielddata['settinggroup']) && $fielddata['settinggroup'] == 'system' && isset($fielddata['varname']) && $fielddata['varname'] == 'defaultsslip') {
|
||||
@@ -175,6 +173,14 @@ class Store
|
||||
return $returnvalue;
|
||||
}
|
||||
|
||||
private static function cleanIpSelection(&$selection)
|
||||
{
|
||||
$selection_arr = array_filter(explode(',', $selection), function ($value) {
|
||||
return !empty($value);
|
||||
});
|
||||
$selection = implode(",", $selection_arr);
|
||||
}
|
||||
|
||||
/**
|
||||
* updates the setting for the default panel-theme
|
||||
* and also the user themes (customers and admins) if
|
||||
@@ -237,6 +243,17 @@ class Store
|
||||
return $returnvalue;
|
||||
}
|
||||
|
||||
public static function storeSettingFieldInsertUpdateServicesTask($fieldname, $fielddata, $newfieldvalue)
|
||||
{
|
||||
// first save the setting itself
|
||||
$returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue);
|
||||
|
||||
if ($returnvalue !== false) {
|
||||
Cronjob::inserttask(TaskId::UPDATE_LE_SERVICES);
|
||||
}
|
||||
return $returnvalue;
|
||||
}
|
||||
|
||||
public static function storeSettingHostname($fieldname, $fielddata, $newfieldvalue)
|
||||
{
|
||||
$returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue);
|
||||
|
||||
@@ -134,7 +134,7 @@ class Cronjob
|
||||
INSERT INTO `" . TABLE_PANEL_TASKS . "` SET `type` = :type, `data` = :data
|
||||
");
|
||||
|
||||
if ($type == TaskId::REBUILD_VHOST || $type == TaskId::REBUILD_DNS || $type == TaskId::CREATE_FTP || $type == TaskId::REBUILD_RSPAMD || $type == TaskId::CREATE_QUOTA || $type == TaskId::REBUILD_CRON) {
|
||||
if ($type == TaskId::REBUILD_VHOST || $type == TaskId::REBUILD_DNS || $type == TaskId::CREATE_FTP || $type == TaskId::REBUILD_RSPAMD || $type == TaskId::CREATE_QUOTA || $type == TaskId::REBUILD_CRON || $type == TaskId::UPDATE_LE_SERVICES) {
|
||||
// 4 = bind -> if bind disabled -> no task
|
||||
if ($type == TaskId::REBUILD_DNS && Settings::Get('system.bind_enable') == '0') {
|
||||
return;
|
||||
@@ -147,6 +147,10 @@ class Cronjob
|
||||
if ($type == TaskId::CREATE_QUOTA && Settings::Get('system.diskquota_enabled') == '0') {
|
||||
return;
|
||||
}
|
||||
// 13 = let's encrypt for services -> if services empty = no task
|
||||
if ($type == TaskId::UPDATE_LE_SERVICES && (Settings::Get('system.le_froxlor_enabled') == '0' || Settings::Get('system.le_renew_services') == '')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// delete previously inserted tasks if they are the same as we only need ONE
|
||||
$del_stmt = Database::prepare("
|
||||
|
||||
@@ -68,9 +68,9 @@ class Mailer extends PHPMailer
|
||||
|
||||
if (self::ValidateAddress(Settings::Get('panel.adminmail')) !== false) {
|
||||
// set return-to address and custom sender-name, see #76
|
||||
$this->SetFrom(Settings::Get('panel.adminmail'), Settings::Get('panel.adminmail_defname'));
|
||||
$this->setFrom(Settings::Get('panel.adminmail'), Settings::Get('panel.adminmail_defname'));
|
||||
if (Settings::Get('panel.adminmail_return') != '') {
|
||||
$this->AddReplyTo(Settings::Get('panel.adminmail_return'), Settings::Get('panel.adminmail_defname'));
|
||||
$this->addReplyTo(Settings::Get('panel.adminmail_return'), Settings::Get('panel.adminmail_defname'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,9 @@ class Domain
|
||||
if ($attributes['fields']['deactivated']) {
|
||||
return lng('admin.deactivated');
|
||||
}
|
||||
if ($attributes['fields']['email_only']) {
|
||||
return lng('domains.email_only');
|
||||
}
|
||||
// path or redirect
|
||||
if (preg_match('/^https?\:\/\//', $attributes['fields']['documentroot'])) {
|
||||
return [
|
||||
@@ -127,7 +130,7 @@ class Domain
|
||||
|
||||
public static function canViewLogs(array $attributes): bool
|
||||
{
|
||||
if ((!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0)) && !$attributes['fields']['deactivated']) {
|
||||
if ((int)$attributes['fields']['email_only'] == 0 && !$attributes['fields']['deactivated']) {
|
||||
if ((int)UI::getCurrentUser()['adminsession'] == 0 && (bool)UI::getCurrentUser()['logviewenabled']) {
|
||||
return true;
|
||||
} elseif ((int)UI::getCurrentUser()['adminsession'] == 1) {
|
||||
@@ -157,6 +160,7 @@ class Domain
|
||||
&& $attributes['fields']['caneditdomain'] == '1'
|
||||
&& Settings::Get('system.bind_enable') == '1'
|
||||
&& Settings::Get('system.dnsenabled') == '1'
|
||||
&& !$attributes['fields']['email_only']
|
||||
&& !$attributes['fields']['deactivated'];
|
||||
}
|
||||
|
||||
@@ -169,7 +173,7 @@ class Domain
|
||||
|
||||
public static function hasLetsEncryptActivated(array $attributes): bool
|
||||
{
|
||||
return ((bool)$attributes['fields']['letsencrypt'] && (!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0)));
|
||||
return ((bool)$attributes['fields']['letsencrypt'] && (int)$attributes['fields']['email_only'] == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,7 +185,7 @@ class Domain
|
||||
&& DDomain::domainHasSslIpPort($attributes['fields']['id'])
|
||||
&& (CurrentUser::isAdmin() || (!CurrentUser::isAdmin() && (int)$attributes['fields']['caneditdomain'] == 1))
|
||||
&& (int)$attributes['fields']['letsencrypt'] == 0
|
||||
&& (!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0))
|
||||
&& !(int)$attributes['fields']['email_only']
|
||||
&& !$attributes['fields']['deactivated']
|
||||
) {
|
||||
return true;
|
||||
|
||||
@@ -52,6 +52,14 @@ class Text
|
||||
];
|
||||
}
|
||||
|
||||
public static function type2fa(array $attributes): array
|
||||
{
|
||||
return [
|
||||
'macro' => 'type2fa',
|
||||
'data' => (int)$attributes['data']
|
||||
];
|
||||
}
|
||||
|
||||
public static function customerfullname(array $attributes): string
|
||||
{
|
||||
return User::getCorrectFullUserDetails($attributes['fields'], true);
|
||||
|
||||
@@ -162,7 +162,7 @@ class Pagination
|
||||
*
|
||||
* @return Pagination
|
||||
*/
|
||||
public function addSearch(string $searchtext = null, string $field = null, string $operator = null): Pagination
|
||||
public function addSearch(?string $searchtext = null, string $field = null, string $operator = null): Pagination
|
||||
{
|
||||
if (!isset($this->data['sql_search'])) {
|
||||
$this->data['sql_search'] = [];
|
||||
|
||||
@@ -121,7 +121,7 @@ class UI
|
||||
'domain' => self::getCookieHost(),
|
||||
'secure' => self::requestIsHttps(),
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict'
|
||||
'samesite' => 'Lax'
|
||||
]);
|
||||
session_start();
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ class User
|
||||
]);
|
||||
$customer['emails_used_new'] = (int)$customer_emails['number_emails'];
|
||||
|
||||
$customer_emails_result_stmt = Database::prepare('SELECT `email`, `email_full`, `destination`, `popaccountid` AS `number_email_forwarders` FROM `' . TABLE_MAIL_VIRTUAL . '`
|
||||
$customer_emails_result_stmt = Database::prepare('SELECT `email`, `email_full`, `destination`, `popaccountid` FROM `' . TABLE_MAIL_VIRTUAL . '`
|
||||
WHERE `customerid` = :cid');
|
||||
Database::pexecute($customer_emails_result_stmt, [
|
||||
"cid" => $customer['customerid']
|
||||
|
||||
@@ -359,4 +359,41 @@ class Check
|
||||
|
||||
return [self::FORMFIELDS_PLAUSIBILITY_CHECK_OK];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $fieldname
|
||||
* @param $fielddata
|
||||
* @param $newfieldvalue
|
||||
* @param $allnewfieldvalues
|
||||
* @return array|int[]
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function checkSystemUsername($fieldname, $fielddata, $newfieldvalue, $allnewfieldvalues)
|
||||
{
|
||||
if (empty($newfieldvalue) || $fielddata['value'] == $newfieldvalue) {
|
||||
$returnvalue = [
|
||||
self::FORMFIELDS_PLAUSIBILITY_CHECK_OK
|
||||
];
|
||||
} elseif (function_exists('posix_getpwnam') && posix_getpwnam($newfieldvalue) == false) {
|
||||
$returnvalue = [
|
||||
self::FORMFIELDS_PLAUSIBILITY_CHECK_ERROR,
|
||||
'local_user_invalid'
|
||||
];
|
||||
} else {
|
||||
// user exists, but cannot be one of the froxlor-customers
|
||||
$sel_stmt = Database::prepare("SELECT COUNT(*) as numUsers FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `loginname` = :username");
|
||||
$result = Database::pexecute_first($sel_stmt, [':username' => $newfieldvalue]);
|
||||
if ($result && $result['numUsers'] > 0) {
|
||||
$returnvalue = [
|
||||
self::FORMFIELDS_PLAUSIBILITY_CHECK_ERROR,
|
||||
'local_user_isfroxloruser'
|
||||
];
|
||||
} else {
|
||||
$returnvalue = [
|
||||
self::FORMFIELDS_PLAUSIBILITY_CHECK_OK
|
||||
];
|
||||
}
|
||||
}
|
||||
return $returnvalue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ class Data
|
||||
{
|
||||
$returnvalue = true;
|
||||
|
||||
if (isset($fielddata['option_mode']) && $fielddata['option_mode'] == 'multiple') {
|
||||
if (isset($fielddata['select_mode']) && $fielddata['select_mode'] == 'multiple') {
|
||||
$options = explode(',', $newfieldvalue);
|
||||
foreach ($options as $option) {
|
||||
$returnvalue = ($returnvalue && isset($fielddata['select_var'][$option]));
|
||||
@@ -247,7 +247,7 @@ class Data
|
||||
if (isset($fielddata['option_emptyallowed']) && $fielddata['option_emptyallowed']) {
|
||||
return true;
|
||||
}
|
||||
return 'not in option';
|
||||
return 'not in option (field: ' . $fieldname . ')';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace Froxlor\Validate;
|
||||
use Exception;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FroxlorLogger;
|
||||
use Froxlor\Idna\IdnaWrapper;
|
||||
use Froxlor\System\IPTools;
|
||||
use Froxlor\UI\Response;
|
||||
|
||||
@@ -63,10 +62,11 @@ class Validate
|
||||
string $str,
|
||||
string $fieldname,
|
||||
string $pattern = '',
|
||||
$lng = '',
|
||||
$emptydefault = [],
|
||||
bool $throw_exception = false
|
||||
) {
|
||||
$lng = '',
|
||||
$emptydefault = [],
|
||||
bool $throw_exception = false
|
||||
)
|
||||
{
|
||||
if (!is_array($emptydefault)) {
|
||||
$emptydefault_array = [
|
||||
$emptydefault
|
||||
@@ -122,14 +122,15 @@ class Validate
|
||||
*/
|
||||
public static function validate_ip2(
|
||||
string $ip,
|
||||
bool $return_bool = false,
|
||||
bool $return_bool = false,
|
||||
string $lng = 'invalidip',
|
||||
bool $allow_localhost = false,
|
||||
bool $allow_priv = false,
|
||||
bool $allow_cidr = false,
|
||||
bool $cidr_as_netmask = false,
|
||||
bool $throw_exception = false
|
||||
) {
|
||||
bool $allow_localhost = false,
|
||||
bool $allow_priv = false,
|
||||
bool $allow_cidr = false,
|
||||
bool $cidr_as_netmask = false,
|
||||
bool $throw_exception = false
|
||||
)
|
||||
{
|
||||
$cidr = "";
|
||||
if ($allow_cidr) {
|
||||
$org_ip = $ip;
|
||||
@@ -200,20 +201,34 @@ class Validate
|
||||
$url = 'http://' . $url;
|
||||
}
|
||||
|
||||
// needs converting
|
||||
try {
|
||||
$idna_convert = new IdnaWrapper();
|
||||
$url = $idna_convert->encode($url);
|
||||
} catch (Exception $e) {
|
||||
// Parse parts
|
||||
$parts = parse_url($url);
|
||||
if ($parts === false || !isset($parts['scheme'], $parts['host'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($allow_private_ip) {
|
||||
$pattern = '%^(?:(?:https?):\/\/)(?:\S+(?::\S*)?@)?(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$%iuS';
|
||||
} else {
|
||||
$pattern = '%^(?:(?:https?):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,})))(?::\d{2,5})?(?:/[^\s]*)?$%iuS';
|
||||
// Check allowed schemes
|
||||
if (!in_array(strtolower($parts['scheme']), ['http', 'https'], true)) {
|
||||
return false;
|
||||
}
|
||||
if (preg_match($pattern, $url)) {
|
||||
|
||||
// Check if host is valid domain or valid IP (v4 or v6)
|
||||
$host = $parts['host'];
|
||||
if (substr($host, 0, 1) == '[' && substr($host, -1) == ']') {
|
||||
$host = substr($host, 1, -1);
|
||||
}
|
||||
|
||||
$opts = FILTER_FLAG_IPV4 | FILTER_FLAG_NO_RES_RANGE | FILTER_FLAG_NO_PRIV_RANGE;
|
||||
$opts6 = FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE | FILTER_FLAG_NO_PRIV_RANGE;
|
||||
if ($allow_private_ip) {
|
||||
$opts = FILTER_FLAG_IPV4 | FILTER_FLAG_NO_RES_RANGE;
|
||||
$opts6 = FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE;
|
||||
}
|
||||
if (filter_var($host, FILTER_VALIDATE_IP, $opts)) {
|
||||
return true;
|
||||
} elseif (substr($parts['host'], 0, 1) == '[' && substr($parts['host'], -1) == ']' && filter_var($host, FILTER_VALIDATE_IP, $opts6)) {
|
||||
return true;
|
||||
} elseif (!preg_match('/^([0-9]{1,3}\.)+[0-9]{1,3}$/', $host) && self::validateDomain($host) !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -342,7 +357,8 @@ class Validate
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function validateBase64Image(string $base64string) {
|
||||
public static function validateBase64Image(string $base64string)
|
||||
{
|
||||
|
||||
if (!extension_loaded('gd')) {
|
||||
Response::standardError('phpgdextensionnotavailable', null, true);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<default for="nginx" settinggroup="system" varname="apacheconf_htpasswddir" value="/etc/nginx/froxlor-htpasswd/"></default>
|
||||
<default for="nginx" settinggroup="system" varname="apachereload_command" value="service nginx reload"></default>
|
||||
<default for="nginx" settinggroup="system" varname="letsencryptacmeconf" value="/etc/nginx/acme.conf"></default>
|
||||
<default for="nginx" settinggroup="phpfpm" varname="fastcgi_ipcdir" value="/var/run/nginx/"></default>
|
||||
<default for="nginx" settinggroup="phpfpm" varname="fastcgi_ipcdir" value="/var/run/php/"></default>
|
||||
</defaults>
|
||||
<services>
|
||||
<!-- HTTP -->
|
||||
@@ -2599,6 +2599,7 @@ try_fallback = true;
|
||||
allow_username_mismatch = true;
|
||||
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
|
||||
selector_map = "/etc/rspamd/dkim_selectors.map";
|
||||
use_esld = false;
|
||||
]]>
|
||||
</content>
|
||||
</file>
|
||||
@@ -3093,7 +3094,7 @@ MaxLoginAttempts 3
|
||||
BanLog /var/log/proftpd/ban.log
|
||||
BanTable /etc/proftpd/ban.tab
|
||||
BanMessage "User %u was banned."
|
||||
BanOnEvent ClientConnectRate 5/00:00:02 12:00:00 "Stop connecting frequently"
|
||||
BanOnEvent ClientConnectRate 10/00:00:02 02:00:00 "Stop connecting frequently"
|
||||
BanOnEvent MaxLoginAttempts 3/00:30:00 12:00:00
|
||||
BanOnEvent AnonRejectPasswords 1/01:00:00 99:99:99
|
||||
BanControlsACLs all allow user root
|
||||
@@ -3174,7 +3175,7 @@ no
|
||||
</content>
|
||||
</file>
|
||||
<file name="/etc/pure-ftpd/db/mysql.conf" chown="root:0"
|
||||
chmod="0644" backup="true">
|
||||
chmod="0640" backup="true">
|
||||
<content><![CDATA[
|
||||
##############################################
|
||||
# #
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<default for="nginx" settinggroup="system" varname="apacheconf_htpasswddir" value="/etc/nginx/froxlor-htpasswd/"></default>
|
||||
<default for="nginx" settinggroup="system" varname="apachereload_command" value="service nginx reload"></default>
|
||||
<default for="nginx" settinggroup="system" varname="letsencryptacmeconf" value="/etc/nginx/acme.conf"></default>
|
||||
<default for="nginx" settinggroup="phpfpm" varname="fastcgi_ipcdir" value="/var/run/nginx/"></default>
|
||||
<default for="nginx" settinggroup="phpfpm" varname="fastcgi_ipcdir" value="/var/run/php/"></default>
|
||||
</defaults>
|
||||
<services>
|
||||
<!-- HTTP -->
|
||||
@@ -4168,6 +4168,7 @@ try_fallback = true;
|
||||
allow_username_mismatch = true;
|
||||
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
|
||||
selector_map = "/etc/rspamd/dkim_selectors.map";
|
||||
use_esld = false;
|
||||
]]>
|
||||
</content>
|
||||
</file>
|
||||
@@ -4661,7 +4662,7 @@ MaxLoginAttempts 3
|
||||
BanLog /var/log/proftpd/ban.log
|
||||
BanTable /etc/proftpd/ban.tab
|
||||
BanMessage "User %u was banned."
|
||||
BanOnEvent ClientConnectRate 5/00:00:02 12:00:00 "Stop connecting frequently"
|
||||
BanOnEvent ClientConnectRate 10/00:00:02 02:00:00 "Stop connecting frequently"
|
||||
BanOnEvent MaxLoginAttempts 3/00:30:00 12:00:00
|
||||
BanOnEvent AnonRejectPasswords 1/01:00:00 99:99:99
|
||||
BanControlsACLs all allow user root
|
||||
@@ -4742,7 +4743,7 @@ no
|
||||
</content>
|
||||
</file>
|
||||
<file name="/etc/pure-ftpd/db/mysql.conf" chown="root:0"
|
||||
chmod="0644" backup="true">
|
||||
chmod="0640" backup="true">
|
||||
<content><![CDATA[
|
||||
##############################################
|
||||
# #
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<default for="nginx" settinggroup="system" varname="apacheconf_htpasswddir" value="/etc/nginx/froxlor-htpasswd/"></default>
|
||||
<default for="nginx" settinggroup="system" varname="apachereload_command" value="service nginx reload"></default>
|
||||
<default for="nginx" settinggroup="system" varname="letsencryptacmeconf" value="/etc/nginx/acme.conf"></default>
|
||||
<default for="nginx" settinggroup="phpfpm" varname="fastcgi_ipcdir" value="/var/run/nginx/"></default>
|
||||
<default for="nginx" settinggroup="phpfpm" varname="fastcgi_ipcdir" value="/var/run/php/"></default>
|
||||
</defaults>
|
||||
<services>
|
||||
<!-- HTTP -->
|
||||
@@ -3391,6 +3391,7 @@ try_fallback = true;
|
||||
allow_username_mismatch = true;
|
||||
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
|
||||
selector_map = "/etc/rspamd/dkim_selectors.map";
|
||||
use_esld = false;
|
||||
]]>
|
||||
</content>
|
||||
</file>
|
||||
@@ -3880,7 +3881,7 @@ MaxLoginAttempts 3
|
||||
BanLog /var/log/proftpd/ban.log
|
||||
BanTable /etc/proftpd/ban.tab
|
||||
BanMessage "User %u was banned."
|
||||
BanOnEvent ClientConnectRate 5/00:00:02 12:00:00 "Stop connecting frequently"
|
||||
BanOnEvent ClientConnectRate 10/00:00:02 02:00:00 "Stop connecting frequently"
|
||||
BanOnEvent MaxLoginAttempts 3/00:30:00 12:00:00
|
||||
BanOnEvent AnonRejectPasswords 1/01:00:00 99:99:99
|
||||
BanControlsACLs all allow user root
|
||||
@@ -3961,7 +3962,7 @@ no
|
||||
</content>
|
||||
</file>
|
||||
<file name="/etc/pure-ftpd/db/mysql.conf" chown="root:0"
|
||||
chmod="0644" backup="true">
|
||||
chmod="0640" backup="true">
|
||||
<content><![CDATA[
|
||||
##############################################
|
||||
# #
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<default for="nginx" settinggroup="system" varname="apacheconf_htpasswddir" value="/etc/nginx/froxlor-htpasswd/"></default>
|
||||
<default for="nginx" settinggroup="system" varname="apachereload_command" value="service nginx reload"></default>
|
||||
<default for="nginx" settinggroup="system" varname="letsencryptacmeconf" value="/etc/nginx/acme.conf"></default>
|
||||
<default for="nginx" settinggroup="phpfpm" varname="fastcgi_ipcdir" value="/var/run/nginx/"></default>
|
||||
<default for="nginx" settinggroup="phpfpm" varname="fastcgi_ipcdir" value="/var/run/php/"></default>
|
||||
</defaults>
|
||||
<services>
|
||||
<!-- HTTP -->
|
||||
@@ -3381,6 +3381,7 @@ try_fallback = true;
|
||||
allow_username_mismatch = true;
|
||||
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
|
||||
selector_map = "/etc/rspamd/dkim_selectors.map";
|
||||
use_esld = false;
|
||||
]]>
|
||||
</content>
|
||||
</file>
|
||||
@@ -3872,7 +3873,7 @@ MaxLoginAttempts 3
|
||||
BanLog /var/log/proftpd/ban.log
|
||||
BanTable /etc/proftpd/ban.tab
|
||||
BanMessage "User %u was banned."
|
||||
BanOnEvent ClientConnectRate 5/00:00:02 12:00:00 "Stop connecting frequently"
|
||||
BanOnEvent ClientConnectRate 10/00:00:02 02:00:00 "Stop connecting frequently"
|
||||
BanOnEvent MaxLoginAttempts 3/00:30:00 12:00:00
|
||||
BanOnEvent AnonRejectPasswords 1/01:00:00 99:99:99
|
||||
BanControlsACLs all allow user root
|
||||
@@ -3953,7 +3954,7 @@ no
|
||||
</content>
|
||||
</file>
|
||||
<file name="/etc/pure-ftpd/db/mysql.conf" chown="root:0"
|
||||
chmod="0644" backup="true">
|
||||
chmod="0640" backup="true">
|
||||
<content><![CDATA[
|
||||
##############################################
|
||||
# #
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<default for="nginx" settinggroup="system" varname="apacheconf_htpasswddir" value="/etc/nginx/froxlor-htpasswd/"></default>
|
||||
<default for="nginx" settinggroup="system" varname="apachereload_command" value="service nginx reload"></default>
|
||||
<default for="nginx" settinggroup="system" varname="letsencryptacmeconf" value="/etc/nginx/acme.conf"></default>
|
||||
<default for="nginx" settinggroup="phpfpm" varname="fastcgi_ipcdir" value="/var/run/nginx/"></default>
|
||||
<default for="nginx" settinggroup="phpfpm" varname="fastcgi_ipcdir" value="/var/run/php/"></default>
|
||||
</defaults>
|
||||
<services>
|
||||
<!-- HTTP -->
|
||||
@@ -2054,6 +2054,7 @@ try_fallback = true;
|
||||
allow_username_mismatch = true;
|
||||
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
|
||||
selector_map = "/etc/rspamd/dkim_selectors.map";
|
||||
use_esld = false;
|
||||
]]>
|
||||
</content>
|
||||
</file>
|
||||
@@ -2121,7 +2122,7 @@ action = "no action";
|
||||
<service type="ftp" title="{{lng.admin.configfiles.ftp}}">
|
||||
<!-- Proftpd -->
|
||||
<daemon name="proftpd" title="ProFTPd" default="true">
|
||||
<install><![CDATA[apt-get install proftpd-basic proftpd-mod-mysql proftpd-mod-crypto]]></install>
|
||||
<install><![CDATA[apt-get install proftpd-basic proftpd-mod-mysql proftpd-mod-crypto proftpd-mod-wrap]]></install>
|
||||
<file name="/etc/proftpd/create-cert.sh" chown="root:0"
|
||||
chmod="0700">
|
||||
<content><![CDATA[#!/bin/bash
|
||||
@@ -2547,7 +2548,7 @@ MaxLoginAttempts 3
|
||||
BanLog /var/log/proftpd/ban.log
|
||||
BanTable /etc/proftpd/ban.tab
|
||||
BanMessage "User %u was banned."
|
||||
BanOnEvent ClientConnectRate 5/00:00:02 12:00:00 "Stop connecting frequently"
|
||||
BanOnEvent ClientConnectRate 10/00:00:02 02:00:00 "Stop connecting frequently"
|
||||
BanOnEvent MaxLoginAttempts 3/00:30:00 12:00:00
|
||||
BanOnEvent AnonRejectPasswords 1/01:00:00 99:99:99
|
||||
BanControlsACLs all allow user root
|
||||
@@ -2628,7 +2629,7 @@ no
|
||||
</content>
|
||||
</file>
|
||||
<file name="/etc/pure-ftpd/db/mysql.conf" chown="root:0"
|
||||
chmod="0644" backup="true">
|
||||
chmod="0640" backup="true">
|
||||
<content><![CDATA[
|
||||
##############################################
|
||||
# #
|
||||
|
||||
@@ -46,7 +46,7 @@ return [
|
||||
'label' => lng('login.password'),
|
||||
'type' => 'password',
|
||||
'mandatory' => true,
|
||||
'autocomplete' => 'off',
|
||||
'autocomplete' => 'new-password',
|
||||
'next_to' => [
|
||||
'admin_password_suggestion' => [
|
||||
'next_to_prefix' => lng('customer.generated_pwd') . ':',
|
||||
|
||||
@@ -52,7 +52,7 @@ return [
|
||||
'admin_password' => [
|
||||
'label' => lng('login.password') . ' (' . lng('panel.emptyfornochanges') . ')',
|
||||
'type' => 'password',
|
||||
'autocomplete' => 'off',
|
||||
'autocomplete' => 'new-password',
|
||||
'visible' => $result['adminid'] != $userinfo['userid'],
|
||||
'next_to' => [
|
||||
'admin_password_suggestion' => [
|
||||
|
||||
@@ -58,7 +58,7 @@ return [
|
||||
'new_customer_password' => [
|
||||
'label' => lng('login.password'),
|
||||
'type' => 'password',
|
||||
'autocomplete' => 'off',
|
||||
'autocomplete' => 'new-password',
|
||||
'placeholder' => lng('admin.password_default_msg'),
|
||||
'next_to' => [
|
||||
'new_customer_password_suggestion' => [
|
||||
|
||||
@@ -63,7 +63,7 @@ return [
|
||||
'new_customer_password' => [
|
||||
'label' => lng('login.password') . ' (' . lng('panel.emptyfornochanges') . ')',
|
||||
'type' => 'password',
|
||||
'autocomplete' => 'off',
|
||||
'autocomplete' => 'new-password',
|
||||
'next_to' => [
|
||||
'new_customer_password_suggestion' => [
|
||||
'next_to_prefix' => lng('customer.generated_pwd') . ':',
|
||||
|
||||
@@ -193,7 +193,7 @@ return [
|
||||
'label' => lng('admin.domain_sslenabled'),
|
||||
'type' => 'checkbox',
|
||||
'value' => '1',
|
||||
'checked' => !empty($ssl_ipsandports)
|
||||
'checked' => !empty(Settings::Get('system.defaultsslip'))
|
||||
],
|
||||
'no_ssl_available_info' => [
|
||||
'visible' => empty($ssl_ipsandports),
|
||||
|
||||
@@ -213,6 +213,10 @@ return [
|
||||
'type' => 'hidden',
|
||||
'value' => '0'
|
||||
],
|
||||
'emaildomainverified' => [
|
||||
'type' => 'hidden',
|
||||
'value' => '0'
|
||||
],
|
||||
]
|
||||
],
|
||||
'section_bssl' => [
|
||||
@@ -433,34 +437,36 @@ return [
|
||||
'section_d' => [
|
||||
'title' => lng('admin.nameserversettings'),
|
||||
'image' => 'icons/domain_edit.png',
|
||||
'visible' => Settings::Get('system.bind_enable') == '1' && $userinfo['change_serversettings'] == '1',
|
||||
'visible' => ($userinfo['change_serversettings'] == '1' && Settings::Get('system.bind_enable') == '1') || ($result['isemaildomain'] == '1' && (Settings::Get('spf.use_spf') == '1' || Settings::Get('dmarc.use_dmarc') == '1') || Settings::Get('antispam.activated') == '1' && $result['dkim'] == '1' && $result['dkim_pubkey'] != ''),
|
||||
'fields' => [
|
||||
'isbinddomain' => [
|
||||
'visible' => $userinfo['change_serversettings'] == '1' && Settings::Get('system.bind_enable') == '1',
|
||||
'label' => lng('admin.createzonefile'),
|
||||
'type' => 'checkbox',
|
||||
'value' => '1',
|
||||
'checked' => $result['isbinddomain']
|
||||
],
|
||||
'zonefile' => [
|
||||
'visible' => $userinfo['change_serversettings'] == '1' && Settings::Get('system.bind_enable') == '1',
|
||||
'label' => lng('admin.custombindzone'),
|
||||
'desc' => lng('admin.bindzonewarning'),
|
||||
'type' => 'text',
|
||||
'value' => $result['zonefile']
|
||||
],
|
||||
'spf_entry' => [
|
||||
'visible' => (Settings::Get('system.bind_enable') == '0' && Settings::Get('spf.use_spf') == '1' && $result['isemaildomain'] == '1'),
|
||||
'visible' => (Settings::Get('spf.use_spf') == '1' && $result['isemaildomain'] == '1'),
|
||||
'label' => lng('antispam.required_spf_dns'),
|
||||
'type' => 'longtext',
|
||||
'value' => (string)(new \Froxlor\Dns\DnsEntry('@', 'TXT', \Froxlor\Dns\Dns::encloseTXTContent(Settings::Get('spf.spf_entry'))))
|
||||
],
|
||||
'dmarc_entry' => [
|
||||
'visible' => (Settings::Get('system.bind_enable') == '0' && Settings::Get('dmarc.use_dmarc') == '1' && $result['isemaildomain'] == '1'),
|
||||
'visible' => (Settings::Get('dmarc.use_dmarc') == '1' && $result['isemaildomain'] == '1'),
|
||||
'label' => lng('antispam.required_dmarc_dns'),
|
||||
'type' => 'longtext',
|
||||
'value' => (string)(new \Froxlor\Dns\DnsEntry('_dmarc', 'TXT', \Froxlor\Dns\Dns::encloseTXTContent(Settings::Get('dmarc.dmarc_entry'))))
|
||||
],
|
||||
'dkim_entry' => [
|
||||
'visible' => (Settings::Get('system.bind_enable') == '0' && Settings::Get('antispam.activated') == '1' && $result['dkim'] == '1' && $result['dkim_pubkey'] != ''),
|
||||
'visible' => (Settings::Get('antispam.activated') == '1' && $result['dkim'] == '1' && $result['dkim_pubkey'] != ''),
|
||||
'label' => lng('antispam.required_dkim_dns'),
|
||||
'type' => 'longtext',
|
||||
'value' => (string)(new \Froxlor\Dns\DnsEntry('dkim' . $result['dkim_id'] . '._domainkey', 'TXT', \Froxlor\Dns\Dns::encloseTXTContent('v=DKIM1; k=rsa; p='.trim($result['dkim_pubkey']))))
|
||||
|
||||
@@ -47,13 +47,14 @@ return [
|
||||
'values' => $domainips
|
||||
],
|
||||
'alias' => [
|
||||
'visible' => $alias_check == '0',
|
||||
'visible' => $alias_check == '0' && (int)$result['email_only'] == 0,
|
||||
'label' => lng('domains.aliasdomain'),
|
||||
'type' => 'select',
|
||||
'select_var' => $domains,
|
||||
'selected' => $result['aliasdomain']
|
||||
],
|
||||
'path' => [
|
||||
'visible' => (int)$result['email_only'] == 0,
|
||||
'label' => lng('panel.path'),
|
||||
'desc' => (Settings::Get('panel.pathedit') != 'Dropdown' ? lng('panel.pathDescriptionSubdomain').(Settings::Get('system.documentroot_use_default_value') == 1 ? lng('panel.pathDescriptionEx') : '') : null),
|
||||
'type' => $pathSelect['type'],
|
||||
@@ -63,13 +64,13 @@ return [
|
||||
'note' => $pathSelect['note'] ?? '',
|
||||
],
|
||||
'url' => [
|
||||
'visible' => Settings::Get('panel.pathedit') == 'Dropdown',
|
||||
'visible' => Settings::Get('panel.pathedit') == 'Dropdown' && (int)$result['email_only'] == 0,
|
||||
'label' => lng('panel.urloverridespath'),
|
||||
'type' => 'text',
|
||||
'value' => $urlvalue
|
||||
],
|
||||
'redirectcode' => [
|
||||
'visible' => Settings::Get('customredirect.enabled') == '1',
|
||||
'visible' => Settings::Get('customredirect.enabled') == '1' && (int)$result['email_only'] == 0,
|
||||
'label' => lng('domains.redirectifpathisurl'),
|
||||
'desc' => lng('domains.redirectifpathisurlinfo'),
|
||||
'type' => 'select',
|
||||
@@ -77,7 +78,7 @@ return [
|
||||
'selected' => $def_code
|
||||
],
|
||||
'selectserveralias' => [
|
||||
'visible' => ($result['parentdomainid'] == '0' && $userinfo['subdomains'] != '0') || $result['parentdomainid'] != '0',
|
||||
'visible' => (($result['parentdomainid'] == '0' && $userinfo['subdomains'] != '0') || $result['parentdomainid'] != '0') && (int)$result['email_only'] == 0,
|
||||
'label' => lng('admin.selectserveralias'),
|
||||
'desc' => lng('admin.selectserveralias_desc'),
|
||||
'type' => 'select',
|
||||
@@ -85,27 +86,28 @@ return [
|
||||
'selected' => $serveraliasoptions_selected
|
||||
],
|
||||
'isemaildomain' => [
|
||||
'visible' => ($result['subcanemaildomain'] == '1' || $result['subcanemaildomain'] == '2') && $result['parentdomainid'] != '0',
|
||||
'visible' => (($result['subcanemaildomain'] == '1' || $result['subcanemaildomain'] == '2') && $result['parentdomainid'] != '0') && (int)$result['email_only'] == 0,
|
||||
'label' => 'Emaildomain',
|
||||
'type' => 'checkbox',
|
||||
'value' => '1',
|
||||
'checked' => $result['isemaildomain']
|
||||
],
|
||||
'openbasedir_path' => [
|
||||
'visible' => $result['openbasedir'] == '1',
|
||||
'visible' => $result['openbasedir'] == '1' && (int)$result['email_only'] == 0,
|
||||
'label' => lng('domain.openbasedirpath'),
|
||||
'type' => 'select',
|
||||
'select_var' => $openbasedir,
|
||||
'selected' => $result['openbasedir_path']
|
||||
],
|
||||
'phpsettingid' => [
|
||||
'visible' => ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && count($phpconfigs) > 0 && $userinfo['phpenabled'] == '1' && $result['phpenabled'] == '1',
|
||||
'visible' => ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && count($phpconfigs) > 0 && $userinfo['phpenabled'] == '1' && $result['phpenabled'] == '1' && (int)$result['email_only'] == 0,
|
||||
'label' => lng('admin.phpsettings.title'),
|
||||
'type' => 'select',
|
||||
'select_var' => $phpconfigs,
|
||||
'selected' => $result['phpsettingid']
|
||||
],
|
||||
'speciallogfile' => [
|
||||
'visible' => (int)$result['email_only'] == 0,
|
||||
'label' => lng('admin.speciallogfile.title'),
|
||||
'desc' => lng('admin.speciallogfile.description'),
|
||||
'type' => 'checkbox',
|
||||
@@ -139,7 +141,7 @@ return [
|
||||
'section_bssl' => [
|
||||
'title' => lng('admin.webserversettings_ssl'),
|
||||
'image' => 'icons/domain_edit.png',
|
||||
'visible' => Settings::Get('system.use_ssl') == '1' && $ssl_ipsandports && Domain::domainHasSslIpPort($result['id']),
|
||||
'visible' => Settings::Get('system.use_ssl') == '1' && $ssl_ipsandports && Domain::domainHasSslIpPort($result['id']) && (int)$result['email_only'] == 0,
|
||||
'fields' => [
|
||||
'sslenabled' => [
|
||||
'label' => lng('admin.domain_sslenabled'),
|
||||
@@ -194,6 +196,7 @@ return [
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'buttons' => ((int)$result['email_only'] == 1) ? [] : null
|
||||
]
|
||||
];
|
||||
|
||||
@@ -43,7 +43,7 @@ return [
|
||||
'email_password' => [
|
||||
'label' => lng('login.password'),
|
||||
'type' => 'password',
|
||||
'autocomplete' => 'off',
|
||||
'autocomplete' => 'new-password',
|
||||
'next_to' => [
|
||||
'email_password_suggestion' => [
|
||||
'next_to_prefix' => lng('customer.generated_pwd') . ':',
|
||||
|
||||
@@ -43,7 +43,7 @@ return [
|
||||
'email_password' => [
|
||||
'label' => lng('login.password'),
|
||||
'type' => 'password',
|
||||
'autocomplete' => 'off',
|
||||
'autocomplete' => 'new-password',
|
||||
'mandatory' => true,
|
||||
'next_to' => [
|
||||
'email_password_suggestion' => [
|
||||
|
||||
@@ -89,56 +89,49 @@ return [
|
||||
]
|
||||
]
|
||||
],
|
||||
'mail_catchall' => [
|
||||
'iscatchall' => [
|
||||
'visible' => Settings::Get('catchall.catchall_enabled') == '1',
|
||||
'label' => lng('emails.catchall'),
|
||||
'type' => 'label',
|
||||
'value' => ((int)$result['iscatchall'] == 0 ? lng('panel.no') : lng('panel.yes')),
|
||||
'next_to' => [
|
||||
'add_link' => [
|
||||
'type' => 'link',
|
||||
'href' => $filename . '?page=' . $page . '&domainid=' . $result['domainid'] . '&action=togglecatchall&id=' . $result['id'],
|
||||
'label' => '<i class="fa-solid fa-arrow-right-arrow-left"></i> ' . lng('panel.toggle'),
|
||||
'classes' => 'btn btn-sm btn-secondary'
|
||||
]
|
||||
]
|
||||
],
|
||||
'spam_tag_level' => [
|
||||
'label' => lng('antispam.spam_tag_level'),
|
||||
'type' => 'text',
|
||||
'string_regexp' => '/^\d{1,}(\.\d{1,2})?$/',
|
||||
'value' => $result['spam_tag_level']
|
||||
],
|
||||
'spam_kill_level' => [
|
||||
'label' => lng('antispam.spam_kill_level'),
|
||||
'type' => 'text',
|
||||
'string_regexp' => '/^\d{1,}(\.\d{1,2})?$/',
|
||||
'value' => $result['spam_kill_level']
|
||||
'type' => 'checkbox',
|
||||
'value' => '1',
|
||||
'checked' => (int)$result['iscatchall'],
|
||||
],
|
||||
'bypass_spam' => [
|
||||
'visible' => Settings::Get('antispam.activated') == '1' && (int)Settings::Get('antispam.default_bypass_spam') <= 2,
|
||||
'label' => lng('antispam.bypass_spam'),
|
||||
'type' => 'label',
|
||||
'value' => ((int)$result['bypass_spam'] == 0 ? lng('panel.no') : lng('panel.yes')),
|
||||
'next_to' => [
|
||||
'add_link' => [
|
||||
'type' => 'link',
|
||||
'href' => $filename . '?page=' . $page . '&domainid=' . $result['domainid'] . '&action=togglebypass&id=' . $result['id'],
|
||||
'label' => '<i class="fa-solid fa-arrow-right-arrow-left"></i> ' . lng('panel.toggle'),
|
||||
'classes' => 'btn btn-sm btn-secondary'
|
||||
]
|
||||
]
|
||||
'type' => 'checkbox',
|
||||
'value' => '1',
|
||||
'checked' => (int)$result['bypass_spam'],
|
||||
],
|
||||
'spam_tag_level' => [
|
||||
'visible' => Settings::Get('antispam.activated') == '1',
|
||||
'label' => lng('antispam.spam_tag_level'),
|
||||
'type' => 'number',
|
||||
'min' => 0,
|
||||
'step' => 0.1,
|
||||
'value' => $result['spam_tag_level'],
|
||||
],
|
||||
'rewrite_subject' => [
|
||||
'visible' => Settings::Get('antispam.activated') == '1' && (int)Settings::Get('antispam.default_spam_rewrite_subject') <= 2,
|
||||
'label' => lng('antispam.rewrite_subject'),
|
||||
'type' => 'checkbox',
|
||||
'value' => '1',
|
||||
'checked' => (int)$result['rewrite_subject'],
|
||||
],
|
||||
'spam_kill_level' => [
|
||||
'visible' => Settings::Get('antispam.activated') == '1',
|
||||
'label' => lng('antispam.spam_kill_level'),
|
||||
'desc' => lng('panel.use_checkbox_to_disable'),
|
||||
'type' => 'textul',
|
||||
'step' => 0.1,
|
||||
'value' => $result['spam_kill_level']
|
||||
],
|
||||
'policy_greylist' => [
|
||||
'visible' => Settings::Get('antispam.activated') == '1' && (int)Settings::Get('antispam.default_policy_greylist') <= 2,
|
||||
'label' => lng('antispam.policy_greylist'),
|
||||
'type' => 'label',
|
||||
'value' => ((int)$result['policy_greylist'] == 0 ? lng('panel.no') : lng('panel.yes')),
|
||||
'next_to' => [
|
||||
'add_link' => [
|
||||
'type' => 'link',
|
||||
'href' => $filename . '?page=' . $page . '&domainid=' . $result['domainid'] . '&action=togglegreylist&id=' . $result['id'],
|
||||
'label' => '<i class="fa-solid fa-arrow-right-arrow-left"></i> ' . lng('panel.toggle'),
|
||||
'classes' => 'btn btn-sm btn-secondary'
|
||||
]
|
||||
]
|
||||
'type' => 'checkbox',
|
||||
'value' => '1',
|
||||
'checked' => (int)$result['policy_greylist'],
|
||||
],
|
||||
'mail_fwds' => [
|
||||
'label' => lng('emails.forwarders') . ' (' . $forwarders_count . ')',
|
||||
|
||||
@@ -54,7 +54,7 @@ return [
|
||||
'directory_password' => [
|
||||
'label' => lng('login.password'),
|
||||
'type' => 'password',
|
||||
'autocomplete' => 'off',
|
||||
'autocomplete' => 'new-password',
|
||||
'mandatory' => true,
|
||||
'next_to' => [
|
||||
'directory_password_suggestion' => [
|
||||
|
||||
@@ -49,7 +49,7 @@ return [
|
||||
'directory_password' => [
|
||||
'label' => lng('login.password'),
|
||||
'type' => 'password',
|
||||
'autocomplete' => 'off',
|
||||
'autocomplete' => 'new-password',
|
||||
'next_to' => [
|
||||
'directory_password_suggestion' => [
|
||||
'next_to_prefix' => lng('customer.generated_pwd') . ':',
|
||||
|
||||
@@ -58,7 +58,7 @@ return [
|
||||
'ftp_password' => [
|
||||
'label' => lng('login.password'),
|
||||
'type' => 'password',
|
||||
'autocomplete' => 'off',
|
||||
'autocomplete' => 'new-password',
|
||||
'mandatory' => true,
|
||||
'next_to' => [
|
||||
'ftp_password_suggestion' => [
|
||||
|
||||
@@ -51,7 +51,7 @@ return [
|
||||
'label' => lng('login.password'),
|
||||
'desc' => lng('ftp.editpassdescription'),
|
||||
'type' => 'password',
|
||||
'autocomplete' => 'off',
|
||||
'autocomplete' => 'new-password',
|
||||
'next_to' => [
|
||||
'ftp_password_suggestion' => [
|
||||
'next_to_prefix' => lng('customer.generated_pwd') . ':',
|
||||
|
||||
@@ -45,7 +45,7 @@ return [
|
||||
'mysql_password' => [
|
||||
'label' => lng('login.password'),
|
||||
'type' => 'password',
|
||||
'autocomplete' => 'off',
|
||||
'autocomplete' => 'new-password',
|
||||
'mandatory' => true,
|
||||
'next_to' => [
|
||||
'mysql_password_suggestion' => [
|
||||
|
||||
@@ -52,7 +52,7 @@ return [
|
||||
'mysql_password' => [
|
||||
'label' => lng('changepassword.new_password_ifnotempty'),
|
||||
'type' => 'password',
|
||||
'autocomplete' => 'off',
|
||||
'autocomplete' => 'new-password',
|
||||
'next_to' => [
|
||||
'mysql_password_suggestion' => [
|
||||
'next_to_prefix' => lng('customer.generated_pwd') . ':',
|
||||
|
||||
@@ -34,7 +34,7 @@ return [
|
||||
'mysql_password' => [
|
||||
'label' => lng('login.password'),
|
||||
'type' => 'password',
|
||||
'autocomplete' => 'off',
|
||||
'autocomplete' => 'new-password',
|
||||
'mandatory' => true,
|
||||
'next_to' => [
|
||||
'mysql_password_suggestion' => [
|
||||
|
||||
@@ -61,7 +61,7 @@ function lng(string $identifier, array $arguments = [])
|
||||
* @param string|null $session
|
||||
* @return mixed|string|null
|
||||
*/
|
||||
function old(string $identifier, string $default = null, string $session = null)
|
||||
function old(string $identifier, ?string $default, ?string $session = null)
|
||||
{
|
||||
if ($session && isset($_SESSION[$session])) {
|
||||
return $_SESSION[$session][$identifier] ?? $default;
|
||||
|
||||
@@ -374,7 +374,7 @@ if (CurrentUser::hasSession()) {
|
||||
'domain' => UI::getCookieHost(),
|
||||
'secure' => UI::requestIsHttps(),
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict'
|
||||
'samesite' => 'Lax'
|
||||
];
|
||||
setcookie(session_name(), $_COOKIE[session_name()], $cookie_params);
|
||||
} else {
|
||||
|
||||
@@ -110,6 +110,12 @@ return [
|
||||
'class' => 'text-center',
|
||||
'callback' => [Text::class, 'boolean'],
|
||||
],
|
||||
'type_2fa' => [
|
||||
'label' => lng('2fa.type_2fa'),
|
||||
'field' => 'type_2fa',
|
||||
'class' => 'text-center',
|
||||
'callback' => [Text::class, 'type2fa'],
|
||||
],
|
||||
],
|
||||
'visible_columns' => Listing::getVisibleColumnsForListing('admin_list', [
|
||||
'loginname',
|
||||
|
||||
@@ -149,6 +149,12 @@ return [
|
||||
'class' => 'text-center',
|
||||
'callback' => [Text::class, 'boolean'],
|
||||
],
|
||||
'c.type_2fa' => [
|
||||
'label' => lng('2fa.type_2fa'),
|
||||
'field' => 'type_2fa',
|
||||
'class' => 'text-center',
|
||||
'callback' => [Text::class, 'type2fa'],
|
||||
],
|
||||
],
|
||||
'visible_columns' => Listing::getVisibleColumnsForListing('customer_list', [
|
||||
'c.name',
|
||||
|
||||
@@ -33,9 +33,9 @@ return [
|
||||
'title' => lng('menue.email.emailsoverview'),
|
||||
'icon' => 'fa-solid fa-envelope',
|
||||
'self_overview' => ['section' => 'email', 'page' => 'overview'],
|
||||
'default_sorting' => ['d.domain' => 'asc'],
|
||||
'default_sorting' => ['d.domain_ace' => 'asc'],
|
||||
'columns' => [
|
||||
'd.domain' => [
|
||||
'd.domain_ace' => [
|
||||
'label' => 'Domain',
|
||||
'field' => 'domain',
|
||||
],
|
||||
@@ -56,7 +56,7 @@ return [
|
||||
],
|
||||
],
|
||||
'visible_columns' => Listing::getVisibleColumnsForListing('emaildomain_list', [
|
||||
'd.domain',
|
||||
'd.domain_ace',
|
||||
'addresses',
|
||||
'accounts',
|
||||
'forwarder',
|
||||
|
||||
@@ -57,3 +57,4 @@ const TABLE_PANEL_PLANS = 'panel_plans';
|
||||
const TABLE_API_KEYS = 'api_keys';
|
||||
const TABLE_PANEL_USERCOLUMNS = 'panel_usercolumns';
|
||||
const TABLE_PANEL_LOGINLINKS = 'panel_loginlinks';
|
||||
const TABLE_PANEL_2FA_TOKENS = 'panel_2fa_tokens';
|
||||
|
||||
@@ -1143,7 +1143,8 @@ Atentament, el vostre administrador'
|
||||
]
|
||||
],
|
||||
'message' => [
|
||||
'norecipients' => 'No s\'ha enviat cap correu electrònic perquè no hi ha destinataris a la base de dades'
|
||||
'norecipients' => 'No s\'ha enviat cap correu electrònic perquè no hi ha destinataris a la base de dades',
|
||||
'success' => 'Missatge enviat correctament als destinataris de %s',
|
||||
],
|
||||
'mysql' => [
|
||||
'databasename' => 'Usuari/Nom de la base de dades',
|
||||
@@ -2169,7 +2170,6 @@ Atentament, el vostre administrador'
|
||||
'issuer' => 'Emissor'
|
||||
],
|
||||
'success' => [
|
||||
'messages_success' => 'Missatge enviat correctament als destinataris de %s',
|
||||
'success' => 'Informació',
|
||||
'clickheretocontinue' => 'Feu clic aquí per a continuar',
|
||||
'settingssaved' => 'La configuració s\'ha guardat correctament.',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user