'; + cols += ' | '; + cols += ' | ' + lang_admin.remove_row + ' | '; + } + row.append(cols); + table_id.append(row); + } + $('#mbox_attr_table').on('click', 'tr a', function (e) { + e.preventDefault(); + $(this).parents('tr').remove(); + }); + $('#add_mbox_attr_row').click(function() { + add_table_row($('#mbox_attr_table'), "mbox_attr"); + }); // detect element visibility changes function onVisible(element, callback) { diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index c2b1761d..cc316b71 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -435,7 +435,7 @@ jQuery(function($){ var table = $('#domain_table').DataTable({ responsive: true, processing: true, - serverSide: false, + serverSide: true, stateSave: true, pageLength: pagination_size, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + @@ -447,9 +447,9 @@ jQuery(function($){ }, ajax: { type: "GET", - url: "/api/v1/get/domain/all", + url: "/api/v1/get/domain/datatables", dataSrc: function(json){ - $.each(json, function(i, item) { + $.each(json.data, function(i, item) { item.domain_name = escapeHtml(item.domain_name); item.aliases = item.aliases_in_domain + " / " + item.max_num_aliases_for_domain; @@ -498,7 +498,7 @@ jQuery(function($){ } }); - return json; + return json.data; } }, columns: [ @@ -528,17 +528,20 @@ jQuery(function($){ { title: lang.aliases, data: 'aliases', + searchable: false, defaultContent: '' }, { title: lang.mailboxes, data: 'mailboxes', + searchable: false, responsivePriority: 4, defaultContent: '' }, { title: lang.domain_quota, data: 'quota', + searchable: false, defaultContent: '', render: function (data, type) { data = data.split("/"); @@ -548,6 +551,7 @@ jQuery(function($){ { title: lang.stats, data: 'stats', + searchable: false, defaultContent: '', render: function (data, type) { data = data.split("/"); @@ -557,53 +561,67 @@ jQuery(function($){ { title: lang.mailbox_defquota, data: 'def_quota_for_mbox', + searchable: false, defaultContent: '' }, { title: lang.mailbox_quota, data: 'max_quota_for_mbox', + searchable: false, defaultContent: '' }, { title: 'RL', data: 'rl', + searchable: false, + orderable: false, defaultContent: '' }, { title: lang.backup_mx, data: 'backupmx', + searchable: false, defaultContent: '', - redner: function (data, type){ - return 1==value ? '' : 0==value && ''; + render: function (data, type){ + return 1==data ? '' : 0==data && ''; } }, { title: lang.domain_admins, data: 'domain_admins', + searchable: false, + orderable: false, defaultContent: '', className: 'none' }, { title: lang.created_on, data: 'created', + searchable: false, + orderable: false, defaultContent: '', className: 'none' }, { title: lang.last_modified, data: 'modified', + searchable: false, + orderable: false, defaultContent: '', className: 'none' }, { title: 'Tags', data: 'tags', + searchable: true, + orderable: false, defaultContent: '', className: 'none' }, { title: lang.active, data: 'active', + searchable: false, defaultContent: '', responsivePriority: 6, render: function (data, type) { @@ -613,6 +631,8 @@ jQuery(function($){ { title: lang.action, data: 'action', + searchable: false, + orderable: false, className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right', responsivePriority: 5, defaultContent: '' @@ -844,7 +864,7 @@ jQuery(function($){ var table = $('#mailbox_table').DataTable({ responsive: true, processing: true, - serverSide: false, + serverSide: true, stateSave: true, pageLength: pagination_size, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + @@ -853,13 +873,12 @@ jQuery(function($){ language: lang_datatables, initComplete: function(settings, json){ hideTableExpandCollapseBtn('#tab-mailboxes', '#mailbox_table'); - filterByDomain(json, 8, table); }, ajax: { type: "GET", - url: "/api/v1/get/mailbox/reduced", + url: "/api/v1/get/mailbox/datatables", dataSrc: function(json){ - $.each(json, function (i, item) { + $.each(json.data, function (i, item) { item.quota = { sortBy: item.quota_used, value: item.quota @@ -945,7 +964,7 @@ jQuery(function($){ } }); - return json; + return json.data; } }, columns: [ @@ -975,13 +994,14 @@ jQuery(function($){ { title: lang.domain_quota, data: 'quota.value', + searchable: false, responsivePriority: 8, - defaultContent: '', - orderData: 23 + defaultContent: '' }, { title: lang.last_mail_login, data: 'last_mail_login', + searchable: false, defaultContent: '', responsivePriority: 7, render: function (data, type) { @@ -994,15 +1014,16 @@ jQuery(function($){ { title: lang.last_pw_change, data: 'last_pw_change', + searchable: false, defaultContent: '' }, { title: lang.in_use, data: 'in_use.value', + searchable: false, defaultContent: '', responsivePriority: 9, - className: 'dt-data-w100', - orderData: 24 + className: 'dt-data-w100' }, { title: lang.fname, @@ -1067,6 +1088,7 @@ jQuery(function($){ { title: lang.msg_num, data: 'messages', + searchable: false, defaultContent: '', responsivePriority: 5 }, @@ -1085,12 +1107,14 @@ jQuery(function($){ { title: 'Tags', data: 'tags', + searchable: true, defaultContent: '', className: 'none' }, { title: lang.active, data: 'active', + searchable: false, defaultContent: '', responsivePriority: 4, render: function (data, type) { @@ -1100,22 +1124,12 @@ jQuery(function($){ { title: lang.action, data: 'action', + searchable: false, + orderable: false, className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right', responsivePriority: 6, defaultContent: '' - }, - { - title: "", - data: 'quota.sortBy', - defaultContent: '', - className: "d-none" - }, - { - title: "", - data: 'in_use.sortBy', - defaultContent: '', - className: "d-none" - }, + } ] }); diff --git a/data/web/js/site/qhandler.js b/data/web/js/site/qhandler.js index 8a8471f2..34f5d853 100644 --- a/data/web/js/site/qhandler.js +++ b/data/web/js/site/qhandler.js @@ -40,7 +40,7 @@ jQuery(function($){ if (value.score > 0) highlightClass = 'negative'; else if (value.score < 0) highlightClass = 'positive'; else highlightClass = 'neutral'; - $('#qid_detail_symbols').append('' + value.name + ' (' + value.score + ')'); + $('#qid_detail_symbols').append('' + value.name + ' (' + value.score + ')'); }); $('[data-bs-toggle="tooltip"]').tooltip(); } diff --git a/data/web/json_api.php b/data/web/json_api.php index b375bc8e..28f8cac5 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -15,7 +15,7 @@ function api_log($_data) { continue; } - $value = json_decode($value, true); + $value = json_decode($value, true); if ($value) { if (is_array($value)) unset($value["csrf_token"]); foreach ($value as $key => &$val) { @@ -23,7 +23,7 @@ function api_log($_data) { $val = '*'; } } - $value = json_encode($value); + $value = json_encode($value); } $data_var[] = $data . "='" . $value . "'"; } @@ -44,7 +44,7 @@ function api_log($_data) { 'msg' => 'Redis: '.$e ); return false; - } + } } if (isset($_GET['query'])) { @@ -178,12 +178,12 @@ if (isset($_GET['query'])) { // parse post data $post = trim(file_get_contents('php://input')); if ($post) $post = json_decode($post); - + // process registration data from authenticator try { // decode base64 strings $clientDataJSON = base64_decode($post->clientDataJSON); - $attestationObject = base64_decode($post->attestationObject); + $attestationObject = base64_decode($post->attestationObject); // processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true) $data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $_SESSION['challenge'], false, true); @@ -250,7 +250,7 @@ if (isset($_GET['query'])) { default: process_add_return(mailbox('add', 'domain', $attr)); break; - } + } break; case "resource": process_add_return(mailbox('add', 'resource', $attr)); @@ -470,7 +470,7 @@ if (isset($_GET['query'])) { // false, if only internal is allowed // null, if internal and cross-platform is allowed $createArgs = $WebAuthn->getCreateArgs($_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], 30, false, $GLOBALS['WEBAUTHN_UV_FLAG_REGISTER'], null, $excludeCredentialIds); - + print(json_encode($createArgs)); $_SESSION['challenge'] = $WebAuthn->getChallenge(); return; @@ -503,6 +503,16 @@ if (isset($_GET['query'])) { print(json_encode($getArgs)); $_SESSION['challenge'] = $WebAuthn->getChallenge(); return; + break; + case "fail2ban": + if (!isset($_SESSION['mailcow_cc_role'])){ + switch ($object) { + case 'banlist': + header('Content-Type: text/plain'); + echo fail2ban('banlist', 'get', $extra); + break; + } + } break; } if (isset($_SESSION['mailcow_cc_role'])) { @@ -523,9 +533,50 @@ if (isset($_GET['query'])) { case "domain": switch ($object) { + case "datatables": + $table = ['domain', 'd']; + $primaryKey = 'domain'; + $columns = [ + ['db' => 'domain', 'dt' => 2], + ['db' => 'aliases', 'dt' => 3, 'order_subquery' => "SELECT COUNT(*) FROM `alias` WHERE (`domain`= `d`.`domain` OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = `d`.`domain`)) AND `address` NOT IN (SELECT `username` FROM `mailbox`)"], + ['db' => 'mailboxes', 'dt' => 4, 'order_subquery' => "SELECT COUNT(*) FROM `mailbox` WHERE `mailbox`.`domain` = `d`.`domain` AND (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)"], + ['db' => 'quota', 'dt' => 5, 'order_subquery' => "SELECT COALESCE(SUM(`mailbox`.`quota`), 0) FROM `mailbox` WHERE `mailbox`.`domain` = `d`.`domain` AND (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)"], + ['db' => 'stats', 'dt' => 6, 'dummy' => true, 'order_subquery' => "SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` IN (SELECT `username` FROM `mailbox` WHERE `domain` = `d`.`domain`)"], + ['db' => 'defquota', 'dt' => 7], + ['db' => 'maxquota', 'dt' => 8], + ['db' => 'backupmx', 'dt' => 10], + ['db' => 'tags', 'dt' => 14, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_domain` AS `td` ON `td`.`domain` = `d`.`domain`', 'where_column' => '`td`.`tag_name`']], + ['db' => 'active', 'dt' => 15], + ]; + + require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php'; + global $pdo; + if($_SESSION['mailcow_cc_role'] === 'admin') { + $data = SSP::simple($_GET, $pdo, $table, $primaryKey, $columns); + } elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') { + $data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns, + 'INNER JOIN domain_admins as da ON da.domain = d.domain', + [ + 'condition' => 'da.active = 1 and da.username = :username', + 'bindings' => ['username' => $_SESSION['mailcow_cc_username']] + ]); + } + + if (!empty($data['data'])) { + $domainsData = []; + foreach ($data['data'] as $domain) { + if ($details = mailbox('get', 'domain_details', $domain[2])) { + $domainsData[] = $details; + } + } + $data['data'] = $domainsData; + } + + process_get_return($data); + break; case "all": $tags = null; - if (isset($_GET['tags']) && $_GET['tags'] != '') + if (isset($_GET['tags']) && $_GET['tags'] != '') $tags = explode(',', $_GET['tags']); $domains = mailbox('get', 'domains', null, $tags); @@ -1011,10 +1062,49 @@ if (isset($_GET['query'])) { break; case "mailbox": switch ($object) { + case "datatables": + $table = ['mailbox', 'm']; + $primaryKey = 'username'; + $columns = [ + ['db' => 'username', 'dt' => 2], + ['db' => 'quota', 'dt' => 3], + ['db' => 'last_mail_login', 'dt' => 4, 'dummy' => true, 'order_subquery' => "SELECT MAX(`datetime`) FROM `sasl_log` WHERE `service` != 'SSO' AND `username` = `m`.`username`"], + ['db' => 'last_pw_change', 'dt' => 5, 'dummy' => true, 'order_subquery' => "JSON_EXTRACT(attributes, '$.passwd_update')"], + ['db' => 'in_use', 'dt' => 6, 'dummy' => true, 'order_subquery' => "(SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`) / `m`.`quota`"], + ['db' => 'messages', 'dt' => 17, 'dummy' => true, 'order_subquery' => "SELECT SUM(messages) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`"], + ['db' => 'tags', 'dt' => 20, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_mailbox` AS `tm` ON `tm`.`username` = `m`.`username`', 'where_column' => '`tm`.`tag_name`']], + ['db' => 'active', 'dt' => 21] + ]; + + require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php'; + global $pdo; + if($_SESSION['mailcow_cc_role'] === 'admin') { + $data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns, null, "(`m`.`kind` = '' OR `m`.`kind` = NULL)"); + } elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') { + $data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns, + 'INNER JOIN domain_admins as da ON da.domain = m.domain', + [ + 'condition' => "(`m`.`kind` = '' OR `m`.`kind` = NULL) AND `da`.`active` = 1 AND `da`.`username` = :username", + 'bindings' => ['username' => $_SESSION['mailcow_cc_username']] + ]); + } + + if (!empty($data['data'])) { + $mailboxData = []; + foreach ($data['data'] as $mailbox) { + if ($details = mailbox('get', 'mailbox_details', $mailbox[2])) { + $mailboxData[] = $details; + } + } + $data['data'] = $mailboxData; + } + + process_get_return($data); + break; case "all": case "reduced": $tags = null; - if (isset($_GET['tags']) && $_GET['tags'] != '') + if (isset($_GET['tags']) && $_GET['tags'] != '') $tags = explode(',', $_GET['tags']); if (empty($extra)) $domains = mailbox('get', 'domains'); @@ -1048,7 +1138,7 @@ if (isset($_GET['query'])) { break; default: $tags = null; - if (isset($_GET['tags']) && $_GET['tags'] != '') + if (isset($_GET['tags']) && $_GET['tags'] != '') $tags = explode(',', $_GET['tags']); if ($tags === null) { @@ -1058,7 +1148,7 @@ if (isset($_GET['query'])) { $mailboxes = mailbox('get', 'mailboxes', $object, $tags); if (is_array($mailboxes)) { foreach ($mailboxes as $mailbox) { - if ($details = mailbox('get', 'mailbox_details', $mailbox)) + if ($details = mailbox('get', 'mailbox_details', $mailbox)) $data[] = $details; } } @@ -1324,6 +1414,10 @@ if (isset($_GET['query'])) { break; case "fail2ban": switch ($object) { + case 'banlist': + header('Content-Type: text/plain'); + echo fail2ban('banlist', 'get', $extra); + break; default: $data = fail2ban('get'); process_get_return($data); @@ -1557,15 +1651,15 @@ if (isset($_GET['query'])) { 'solr_size' => $solr_size, 'solr_documents' => $solr_documents )); - break; + break; case "host": if (!$extra){ $stats = docker("host_stats"); echo json_encode($stats); - } + } else if ($extra == "ip") { // get public ips - + $curl = curl_init(); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_POST, 0); @@ -1591,6 +1685,12 @@ if (isset($_GET['query'])) { } } break; + case "spam-score": + $score = mailbox('get', 'spam_score', $object); + if ($score) + $score = array("score" => preg_replace("/\s+/", "", $score)); + process_get_return($score); + break; break; // return no route found if no case is matched default: @@ -1867,8 +1967,6 @@ if (isset($_GET['query'])) { case "quota_notification_bcc": process_edit_return(quota_notification_bcc('edit', $attr)); break; - case "domain-wide-footer": - process_edit_return(mailbox('edit', 'domain_wide_footer', $attr)); break; case "mailq": process_edit_return(mailq('edit', array_merge(array('qid' => $items), $attr))); @@ -1881,6 +1979,9 @@ if (isset($_GET['query'])) { case "template": process_edit_return(mailbox('edit', 'mailbox_templates', array_merge(array('ids' => $items), $attr))); break; + case "custom-attribute": + process_edit_return(mailbox('edit', 'mailbox_custom_attribute', array_merge(array('mailboxes' => $items), $attr))); + break; default: process_edit_return(mailbox('edit', 'mailbox', array_merge(array('username' => $items), $attr))); break; @@ -1900,6 +2001,9 @@ if (isset($_GET['query'])) { case "template": process_edit_return(mailbox('edit', 'domain_templates', array_merge(array('ids' => $items), $attr))); break; + case "footer": + process_edit_return(mailbox('edit', 'domain_wide_footer', array_merge(array('domains' => $items), $attr))); + break; default: process_edit_return(mailbox('edit', 'domain', array_merge(array('domain' => $items), $attr))); break; @@ -1933,7 +2037,14 @@ if (isset($_GET['query'])) { process_edit_return(fwdhost('edit', array_merge(array('fwdhost' => $items), $attr))); break; case "fail2ban": - process_edit_return(fail2ban('edit', array_merge(array('network' => $items), $attr))); + switch ($object) { + case 'banlist': + process_edit_return(fail2ban('banlist', 'refresh', $items)); + break; + default: + process_edit_return(fail2ban('edit', array_merge(array('network' => $items), $attr))); + break; + } break; case "ui_texts": process_edit_return(customize('edit', 'ui_texts', $attr)); @@ -1972,7 +2083,7 @@ if (isset($_GET['query'])) { exit(); } } -if ($_SESSION['mailcow_cc_api'] === true) { +if (array_key_exists('mailcow_cc_api', $_SESSION) && $_SESSION['mailcow_cc_api'] === true) { if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) { unset($_SESSION['return']); } diff --git a/data/web/lang/lang.cs-cz.json b/data/web/lang/lang.cs-cz.json index 1a5213b9..2d0b1014 100644 --- a/data/web/lang/lang.cs-cz.json +++ b/data/web/lang/lang.cs-cz.json @@ -107,7 +107,8 @@ "username": "Uživatelské jméno", "validate": "Ověřit", "validation_success": "Úspěšně ověřeno", - "tags": "Štítky" + "tags": "Štítky", + "dry": "Simulovat synchronizaci" }, "admin": { "access": "Přístupy", @@ -209,7 +210,7 @@ "include_exclude_info": "Ve výchozím nastavení (bez výběru), jsou adresovány všechny mailové schránky", "includes": "Zahrnout tyto přijemce", "ip_check": "Kontrola IP", - "ip_check_disabled": "Kontrola IP je vypnuta. Můžete ji zapnout v