From adc23d86f9b16c77cde48c609c492e4cbaa29335 Mon Sep 17 00:00:00 2001 From: "andre.peters" Date: Sat, 9 Dec 2017 13:17:15 +0100 Subject: [PATCH] Various... --- .gitignore | 1 - data/web/admin.php | 237 ++-- data/web/css/breakpoint.min.css | 1 + data/web/css/debug.css | 37 + data/web/css/quarantaine.css | 37 + data/web/debug.php | 328 ++++++ data/web/img/rspamd_logo.png | Bin 0 -> 4613 bytes data/web/inc/ajax/container_ctrl.php | 53 + data/web/inc/ajax/log_driver.php | 12 + data/web/inc/ajax/qitem_details.php | 83 ++ data/web/inc/ajax/sogo_ctrl.php | 39 - data/web/inc/footer.inc.php | 70 +- data/web/inc/functions.customize.inc.php | 54 +- data/web/inc/functions.docker.inc.php | 49 +- data/web/inc/functions.fail2ban.inc.php | 2 - data/web/inc/functions.inc.php | 203 +++- data/web/inc/functions.mailbox.inc.php | 26 +- data/web/inc/functions.quarantaine.inc.php | 282 +++++ data/web/inc/header.inc.php | 14 +- data/web/inc/init_db.inc.php | 108 +- data/web/inc/lib/composer.json | 3 +- data/web/inc/lib/composer.lock | 106 +- .../inc/lib/vendor/composer/autoload_psr4.php | 1 + .../lib/vendor/composer/autoload_static.php | 8 + .../inc/lib/vendor/composer/installed.json | 200 +++- .../php-mime-mail-parser/LICENSE | 21 + .../php-mime-mail-parser/README.md | 167 +++ .../php-mime-mail-parser/composer.json | 61 + .../php-mime-mail-parser/mailparse-stubs.php | 303 +++++ .../php-mime-mail-parser/phpunit.xml.dist | 6 + .../php-mime-mail-parser/src/Attachment.php | 183 +++ .../php-mime-mail-parser/src/Charset.php | 338 ++++++ .../src/Contracts/CharsetManager.php | 24 + .../php-mime-mail-parser/src/Exception.php | 8 + .../php-mime-mail-parser/src/Parser.php | 893 ++++++++++++++ .../lib/vendor/phpmailer/phpmailer/VERSION | 2 +- .../phpmailer/phpmailer/class.phpmailer.php | 4 +- .../vendor/phpmailer/phpmailer/class.pop3.php | 2 +- .../vendor/phpmailer/phpmailer/class.smtp.php | 4 +- .../vendor/robthree/twofactorauth/.gitignore | 5 +- .../vendor/robthree/twofactorauth/.travis.yml | 13 +- .../.vs/config/applicationhost.config | 1031 +++++++++++++++++ .../vendor/robthree/twofactorauth/README.md | 2 +- .../robthree/twofactorauth/composer.json | 2 +- .../ConvertUnixTimeDotComTimeProvider.php | 2 +- .../lib/Providers/Time/HttpTimeProvider.php | 3 +- .../u2flib-server/src/u2flib_server/U2F.php.1 | 507 -------- data/web/inc/prerequisites.inc.php | 20 +- data/web/inc/sessions.inc.php | 18 + data/web/inc/triggers.inc.php | 12 + data/web/inc/vars.inc.php | 19 +- data/web/index.php | 14 +- data/web/js/admin.js | 350 +----- data/web/js/api.js | 28 +- data/web/js/debug.js | 503 ++++++++ data/web/js/formcache.min.js | 10 + data/web/js/mailbox.js | 6 +- data/web/js/quarantaine.js | 82 ++ data/web/json_api.php | 446 +++++-- data/web/lang/lang.de.php | 93 +- data/web/lang/lang.en.php | 70 +- data/web/mailbox.php | 13 +- data/web/modals/admin.php | 4 +- data/web/modals/debug.php | 6 + data/web/modals/footer.php | 14 +- data/web/modals/mailbox.php | 37 +- data/web/modals/quarantaine.php | 32 + data/web/modals/user.php | 4 +- data/web/quarantaine.php | 56 + data/web/user.php | 17 +- 70 files changed, 6008 insertions(+), 1381 deletions(-) create mode 100644 data/web/css/breakpoint.min.css create mode 100644 data/web/css/debug.css create mode 100644 data/web/css/quarantaine.css create mode 100644 data/web/debug.php create mode 100644 data/web/img/rspamd_logo.png create mode 100644 data/web/inc/ajax/container_ctrl.php create mode 100644 data/web/inc/ajax/log_driver.php create mode 100644 data/web/inc/ajax/qitem_details.php delete mode 100644 data/web/inc/ajax/sogo_ctrl.php create mode 100644 data/web/inc/functions.quarantaine.inc.php create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/LICENSE create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/README.md create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/composer.json create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/mailparse-stubs.php create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/phpunit.xml.dist create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Attachment.php create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Charset.php create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Contracts/CharsetManager.php create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Exception.php create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Parser.php create mode 100644 data/web/inc/lib/vendor/robthree/twofactorauth/.vs/config/applicationhost.config delete mode 100644 data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php.1 create mode 100644 data/web/js/debug.js create mode 100644 data/web/js/formcache.min.js create mode 100644 data/web/js/quarantaine.js create mode 100644 data/web/modals/debug.php create mode 100644 data/web/modals/quarantaine.php create mode 100644 data/web/quarantaine.php diff --git a/.gitignore b/.gitignore index 0d081fc4..b945f8cf 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,3 @@ data/conf/nginx/*.conf data/conf/nginx/*.custom data/conf/nginx/*.bak data/conf/dovecot/extra.conf -data/conf/rspamd/custom/* diff --git a/data/web/admin.php b/data/web/admin.php index 0d7c6466..e1fe74be 100644 --- a/data/web/admin.php +++ b/data/web/admin.php @@ -8,26 +8,8 @@ $tfa_data = get_tfa(); ?>
@@ -58,7 +40,7 @@ $tfa_data = get_tfa();
- +
@@ -96,6 +78,42 @@ $tfa_data = get_tfa();
+ +
+
API
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
@@ -121,17 +139,15 @@ $tfa_data = get_tfa();
-
-
@@ -332,14 +347,13 @@ $tfa_data = get_tfa();
- +
-
@@ -381,6 +395,43 @@ $tfa_data = get_tfa();
+ +
+
Quarantäne
+
+ +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+ +
+ +
+
+
+
@@ -449,10 +500,29 @@ $tfa_data = get_tfa(); endforeach; ?> -
- - -
+

+ + +

+ + + +
+
+ + +
+
+ + +
+
+ + +
+
@@ -460,111 +530,6 @@ $tfa_data = get_tfa(); -
-
-
Postfix -
- - - -
-
-
-
-
-
-
-
-
- -
-
-
Dovecot -
- - - -
-
-
-
-
-
-
-
-
- -
-
-
SOGo -
- - - -
-
-
-
-
-
-
-
-
- - - -
-
-
Fail2ban -
- - - -
-
-
-
-
-
-
-
-
- - -
-
-
Rspamd history -
- - - -
-
-
-
-
-
-
-
-
- -
-
-
Autodiscover -
- - - -
-
-
-
-
-
-
-
-
- li{float:none}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px}.collapse.in{display:block!important}.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}} \ No newline at end of file diff --git a/data/web/css/debug.css b/data/web/css/debug.css new file mode 100644 index 00000000..585d1905 --- /dev/null +++ b/data/web/css/debug.css @@ -0,0 +1,37 @@ +table.footable>tbody>tr.footable-empty>td { + font-size:15px !important; + font-style:italic; +} +.pagination a { + text-decoration: none !important; +} +.panel panel-default { + overflow: visible !important; +} +.table-responsive { + overflow: visible !important; +} +@media screen and (max-width: 767px) { + .table-responsive { + overflow-x: scroll !important; + } +} +.footer-add-item { + display:block; + text-align: center; + font-style: italic; + padding: 10px; + background: #F5F5F5; +} +@media (min-width: 992px) { + .container { + width: 80%; + } +} +.mass-actions-debug { + user-select: none; + padding:10px 0 10px 10px; +} +.inputMissingAttr { + border-color: #FF4136; +} \ No newline at end of file diff --git a/data/web/css/quarantaine.css b/data/web/css/quarantaine.css new file mode 100644 index 00000000..7a5ee761 --- /dev/null +++ b/data/web/css/quarantaine.css @@ -0,0 +1,37 @@ +table.footable>tbody>tr.footable-empty>td { + font-size:15px !important; + font-style:italic; +} +.pagination a { + text-decoration: none !important; +} +.panel panel-default { + overflow: visible !important; +} +.table-responsive { + overflow: visible !important; +} +@media screen and (max-width: 767px) { + .table-responsive { + overflow-x: scroll !important; + } +} +.footer-add-item { + display:block; + text-align: center; + font-style: italic; + padding: 10px; + background: #F5F5F5; +} +@media (min-width: 992px) { + .container { + width: 80%; + } +} +.mass-actions-quarantaine { + user-select: none; + padding:10px 0 10px 10px; +} +.inputMissingAttr { + border-color: #FF4136; +} \ No newline at end of file diff --git a/data/web/debug.php b/data/web/debug.php new file mode 100644 index 00000000..289aa84a --- /dev/null +++ b/data/web/debug.php @@ -0,0 +1,328 @@ + +
+ + + +
+
+
+ +
+
+
+

Rspamd UI

+
+
+
+
+
+
+
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+ Rspamd UI +
+
+
+
+
+ +
+
+
+

Rspamd settings map

+
+
+ +
+
+
+ +
+
+
+

Container information

+
+
+
    + +
  • + + setTimestamp(mktime( + $StartedAt['hour'], + $StartedAt['minute'], + $StartedAt['second'], + $StartedAt['month'], + $StartedAt['day'], + $StartedAt['year'])); + $user_tz = new DateTimeZone(getenv('TZ')); + $date->setTimezone($user_tz); + $started = $date->format('r'); + ?> + (Started on ), + Restart +     +
  • + +
+
+
+
+ +
+
+
Postfix +
+ + + +
+
+
+
+
+
+
+
+
+ +
+
+
Dovecot +
+ + + +
+
+
+
+
+
+
+
+
+ +
+
+
SOGo +
+ + + +
+
+
+
+
+
+
+
+
+ +
+
+
Fail2ban +
+ + + +
+
+
+
+
+
+
+
+
+ +
+
+
Rspamd history +
+ + + +
+
+
+
+
+
+
+
+
+ +
+
+
Autodiscover +
+ + + +
+
+
+
+
+
+
+
+
+ +
+
+
Watchdog +
+ + + +
+
+
+
+
+
+
+
+
+ +
+
+
ACME +
+ + + +
+
+
+
+
+
+
+
+
+ +
+
+
API +
+ + + +
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + + + + diff --git a/data/web/img/rspamd_logo.png b/data/web/img/rspamd_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0e97426dcb4e2d20d2f3b13ec381cccb05e4703b GIT binary patch literal 4613 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 literal 0 HcmV?d00001 diff --git a/data/web/inc/ajax/container_ctrl.php b/data/web/inc/ajax/container_ctrl.php new file mode 100644 index 00000000..d12f5767 --- /dev/null +++ b/data/web/inc/ajax/container_ctrl.php @@ -0,0 +1,53 @@ +OK' : 'Error: ' . $response['msg'] . ''; + if ($response['type'] == "success") { + break; + } + usleep(1500000); + $retry++; + } + echo (!isset($last_response)) ? 'Already running' : $last_response; + } + if ($_GET['action'] == "stop") { + header('Content-Type: text/html; charset=utf-8'); + $retry = 0; + while (docker($_GET['service'], 'info')['State']['Running'] == 1 && $retry <= 3) { + $response = docker($_GET['service'], 'post', 'stop'); + $response = json_decode($response, true); + $last_response = ($response['type'] == "success") ? 'OK' : 'Error: ' . $response['msg'] . ''; + if ($response['type'] == "success") { + break; + } + usleep(1500000); + $retry++; + } + echo (!isset($last_response)) ? 'Not running' : $last_response; + } + if ($_GET['action'] == "restart") { + header('Content-Type: text/html; charset=utf-8'); + $response = docker($_GET['service'], 'post', 'restart'); + $response = json_decode($response, true); + $last_response = ($response['type'] == "success") ? 'OK' : 'Error: ' . $response['msg'] . ''; + echo (!isset($last_response)) ? 'Cannot restart container' : $last_response; + } + if ($_GET['action'] == "logs") { + $lines = (empty($_GET['lines']) || !is_numeric($_GET['lines'])) ? 1000 : $_GET['lines']; + header('Content-Type: text/plain; charset=utf-8'); + print_r(preg_split('/\n/', docker($_GET['service'], 'logs', $lines))); + } +} + +?> diff --git a/data/web/inc/ajax/log_driver.php b/data/web/inc/ajax/log_driver.php new file mode 100644 index 00000000..319f672d --- /dev/null +++ b/data/web/inc/ajax/log_driver.php @@ -0,0 +1,12 @@ + diff --git a/data/web/inc/ajax/qitem_details.php b/data/web/inc/ajax/qitem_details.php new file mode 100644 index 00000000..a4b80be1 --- /dev/null +++ b/data/web/inc/ajax/qitem_details.php @@ -0,0 +1,83 @@ + 10485760) { + echo json_encode(array('error' => 'Message size exceeds 10 MiB.')); + exit; + } + if (!empty($mailc['msg'])) { + // Init message array + $data = array(); + // Init parser + $mail_parser = new PhpMimeMailParser\Parser(); + // Load msg to parser + $mail_parser->setText($mailc['msg']); + // Get text/plain content + $data['text_plain'] = $mail_parser->getMessageBody('text'); + // Get subject + $data['subject'] = $mail_parser->getHeader('subject'); + // Get attachments + if (is_dir($tmpdir)) { + rrmdir($tmpdir); + } + mkdir('/tmp/' . $_GET['id']); + $mail_parser->saveAttachments($tmpdir, true); + $atts = $mail_parser->getAttachments(true); + if (count($atts) > 0) { + foreach ($atts as $key => $val) { + $data['attachments'][$key] = array( + // Index + // 0 => file name + // 1 => mime type + // 2 => file size + // 3 => vt link by sha256 + $val->getFilename(), + $val->getContentType(), + filesize($tmpdir . $val->getFilename()), + 'https://www.virustotal.com/file/' . hash_file('SHA256', $tmpdir . $val->getFilename()) . '/analysis/' + ); + } + } + if (isset($_GET['att'])) { + $dl_id = intval($_GET['att']); + $dl_filename = $data['attachments'][$dl_id][0]; + if (!is_dir($tmpdir . $dl_filename) && file_exists($tmpdir . $dl_filename)) { + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Cache-Control: private', false); + header('Content-Type: ' . $data['attachments'][$dl_id][1]); + header('Content-Disposition: attachment; filename="'. $dl_filename . '";'); + header('Content-Transfer-Encoding: binary'); + header('Content-Length: ' . $data['attachments'][$dl_id][2]); + readfile($tmpdir . $dl_filename); + exit; + } + } + echo json_encode($data); + } +} +?> diff --git a/data/web/inc/ajax/sogo_ctrl.php b/data/web/inc/ajax/sogo_ctrl.php deleted file mode 100644 index e238d9c0..00000000 --- a/data/web/inc/ajax/sogo_ctrl.php +++ /dev/null @@ -1,39 +0,0 @@ -OK' : 'Error: ' . $response['msg'] . ''; - if ($response['type'] == "success") { - break; - } - usleep(1500000); - $retry++; - } - echo (!isset($last_response)) ? 'Already running' : $last_response; -} - -if ($_GET['ACTION'] == "stop") { - $retry = 0; - while (docker('sogo-mailcow', 'info')['State']['Running'] == 1 && $retry <= 3) { - $response = docker('sogo-mailcow', 'post', 'stop'); - $response = json_decode($response, true); - $last_response = ($response['type'] == "success") ? 'OK' : 'Error: ' . $response['msg'] . ''; - if ($response['type'] == "success") { - break; - } - usleep(1500000); - $retry++; - } - echo (!isset($last_response)) ? 'Not running' : $last_response; -} - -?> diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php index 3ba758be..a082211b 100644 --- a/data/web/inc/footer.inc.php +++ b/data/web/inc/footer.inc.php @@ -8,6 +8,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/footer.php'; + @@ -26,11 +27,19 @@ $(document).ready(function() { msg = $('').html(message).text(); if (type == 'danger') { auto_hide = 0; + $('#' + localStorage.getItem("add_modal")).modal('show'); + localStorage.removeItem("add_modal"); } else { auto_hide = 5000; } + $.ajax({ + url: '/inc/ajax/log_driver.php', + data: {"type": type,"msg": msg}, + type: "GET" + }); $.notify({message: msg},{z_index: 20000, delay: auto_hide, type: type,placement: {from: "bottom",align: "right"},animate: {enter: 'animated fadeInUp',exit: 'animated fadeOutDown'}}); } + $('[data-cached-form="true"]').formcache({key: $(this).data('id')}); mailcow_alert_box(, ""); @@ -118,13 +127,8 @@ $(document).ready(function() { } }); - // Activate tooltips $(function () { $('[data-toggle="tooltip"]').tooltip() - }) - // Hide alerts after n seconds - $("#alert-fade").fadeTo(7000, 500).slideUp(500, function(){ - $("#alert-fade").alert('close'); }); // Remember last navigation pill @@ -173,36 +177,32 @@ $(document).ready(function() { // Init Bootstrap Selectpicker $('select').selectpicker(); - // Trigger SOGo restart - $('#triggerRestartSogo').click(function(){ - $(this).prop("disabled",true); - $(this).html(' '); - $('#statusTriggerRestartSogo').text('Stopping SOGo workers, this may take a while... '); - $.ajax({ - method: 'get', - url: '/inc/ajax/sogo_ctrl.php', - data: { - 'ajax': true, - 'ACTION': 'stop' - }, - success: function(data) { - $('#statusTriggerRestartSogo').append(data); - $('#statusTriggerRestartSogo').append('
Starting SOGo...'); - $.ajax({ - method: 'get', - url: '/inc/ajax/sogo_ctrl.php', - data: { - 'ajax': true, - 'ACTION': 'start' - }, - success: function(data) { - $('#statusTriggerRestartSogo').append(data); - $('#triggerRestartSogo').html(' '); - } - }); - } + // Trigger container restart + $('#RestartContainer').on('show.bs.modal', function(e) { + var container = $(e.relatedTarget).data('container'); + $('#containerName').text(container); + $('#triggerRestartContainer').click(function(){ + $(this).prop("disabled",true); + $(this).html(' '); + $('#statusTriggerRestartContainer').text('Restarting container, this may take a while... '); + $.ajax({ + method: 'get', + url: '/inc/ajax/container_ctrl.php', + timeout: 3000, + data: { + 'service': container, + 'action': 'restart' + }, + error: function() { + window.location = window.location.href.split("#")[0]; + }, + success: function(data) { + $('#statusTriggerRestartContainer').append(data); + $('#triggerRestartContainer').html(' '); + } + }); }); - }); + }) // CSRF $('').attr('id', 'csrf_token').attr('name', 'csrf_token').appendTo('form'); @@ -216,4 +216,4 @@ $(document).ready(function() { 'danger', - 'msg' => 'Cannot validate image file: Temporary file not found' + 'msg' => $lang['danger']['img_tmp_missing'] ); return false; } @@ -26,7 +26,7 @@ function customize($_action, $_item, $_data = null) { if ($image->valid() !== true) { $_SESSION['return'] = array( 'type' => 'danger', - 'msg' => 'Cannot validate image file' + 'msg' => $lang['danger']['img_invalid'] ); return false; } @@ -35,7 +35,7 @@ function customize($_action, $_item, $_data = null) { catch (ImagickException $e) { $_SESSION['return'] = array( 'type' => 'danger', - 'msg' => 'Cannot validate image file' + 'msg' => $lang['danger']['img_invalid'] ); return false; } @@ -43,7 +43,7 @@ function customize($_action, $_item, $_data = null) { else { $_SESSION['return'] = array( 'type' => 'danger', - 'msg' => 'Invalid mime type' + 'msg' => $lang['danger']['invalid_mime_type'] ); return false; } @@ -59,7 +59,7 @@ function customize($_action, $_item, $_data = null) { } $_SESSION['return'] = array( 'type' => 'success', - 'msg' => 'File uploaded successfully' + 'msg' => $lang['success']['upload_success'] ); break; } @@ -77,7 +77,7 @@ function customize($_action, $_item, $_data = null) { $apps = (array)$_data['app']; $links = (array)$_data['href']; $out = array(); - if (count($apps) == count($links)) {; + if (count($apps) == count($links)) { for ($i = 0; $i < count($apps); $i++) { $out[] = array($apps[$i] => $links[$i]); } @@ -94,7 +94,28 @@ function customize($_action, $_item, $_data = null) { } $_SESSION['return'] = array( 'type' => 'success', - 'msg' => 'Saved changes to app links' + 'msg' => $lang['success']['app_links'] + ); + break; + case 'ui_texts': + $main_name = $_data['main_name']; + $apps_name = $_data['apps_name']; + $help_text = $_data['help_text']; + try { + $redis->set('MAIN_NAME', htmlspecialchars($main_name)); + $redis->set('APPS_NAME', htmlspecialchars($apps_name)); + $redis->set('HELP_TEXT', $help_text); + } + catch (RedisException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Redis: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => $lang['success']['ui_texts'] ); break; } @@ -113,7 +134,7 @@ function customize($_action, $_item, $_data = null) { if ($redis->del('MAIN_LOGO')) { $_SESSION['return'] = array( 'type' => 'success', - 'msg' => 'Reset default logo' + 'msg' => $lang['success']['reset_main_logo'] ); return true; } @@ -155,6 +176,21 @@ function customize($_action, $_item, $_data = null) { return false; } break; + case 'ui_texts': + try { + $data['main_name'] = ($main_name = $redis->get('MAIN_NAME')) ? $main_name : 'mailcow UI'; + $data['apps_name'] = ($apps_name = $redis->get('APPS_NAME')) ? $apps_name : 'mailcow Apps'; + $data['help_text'] = ($help_text = $redis->get('HELP_TEXT')) ? $help_text : false; + return $data; + } + catch (RedisException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Redis: '.$e + ); + return false; + } + break; case 'main_logo_specs': try { $image = new Imagick(); @@ -167,7 +203,7 @@ function customize($_action, $_item, $_data = null) { catch (ImagickException $e) { $_SESSION['return'] = array( 'type' => 'danger', - 'msg' => 'Error: Imagick exception while reading image' + 'msg' => $lang['danger']['imagick_exception'] ); return false; } diff --git a/data/web/inc/functions.docker.inc.php b/data/web/inc/functions.docker.inc.php index a5f2581c..7cd5ed4e 100644 --- a/data/web/inc/functions.docker.inc.php +++ b/data/web/inc/functions.docker.inc.php @@ -1,5 +1,12 @@ 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } $curl = curl_init(); curl_setopt($curl, CURLOPT_HTTPHEADER,array( 'Content-Type: application/json' )); switch($action) { @@ -52,14 +59,44 @@ function docker($service_name, $action, $post_action = null, $post_fields = null return false; } break; + case 'logs': + $container_id = docker($service_name, 'get_id'); + if (ctype_xdigit($container_id)) { + $lines = (empty($attr1) || !is_numeric($attr1)) ? 100 : $attr1; + curl_setopt($curl, CURLOPT_URL, 'http://dockerapi:8080/containers/' . $container_id . '/logs/' . $lines); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 0); + $response = curl_exec($curl); + if ($response === false) { + $err = curl_error($curl); + curl_close($curl); + return $err; + } + else { + curl_close($curl); + if (empty($response)) { + return true; + } + else { + return json_decode($response, true); + } + } + } + else { + return false; + } + break; case 'post': - if (!empty($post_action)) { + if (!empty($attr1)) { $container_id = docker($service_name, 'get_id'); - if (ctype_xdigit($container_id) && ctype_alnum($post_action)) { - curl_setopt($curl, CURLOPT_URL, 'http://dockerapi:8080/containers/' . $container_id . '/' . $post_action); + if (ctype_xdigit($container_id) && ctype_alnum($attr1)) { + curl_setopt($curl, CURLOPT_URL, 'http://dockerapi:8080/containers/' . $container_id . '/' . $attr1); curl_setopt($curl, CURLOPT_POST, 1); - if (!empty($post_fields)) { - curl_setopt( $curl, CURLOPT_POSTFIELDS, json_encode($post_fields)); + if (!empty($attr2)) { + curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($attr2)); + } + if (!empty($extra_headers) && is_array($extra_headers)) { + curl_setopt($curl, CURLOPT_HTTPHEADER, $extra_headers); } curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($curl); diff --git a/data/web/inc/functions.fail2ban.inc.php b/data/web/inc/functions.fail2ban.inc.php index 6c9e1692..e1801be7 100644 --- a/data/web/inc/functions.fail2ban.inc.php +++ b/data/web/inc/functions.fail2ban.inc.php @@ -1,5 +1,4 @@ prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '✘') AS `aliases` FROM `alias` + $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '✘') AS `shared_aliases` FROM `alias` WHERE `goto` REGEXP :username_goto AND `address` NOT LIKE '@%' + AND `goto` != :username_goto2 AND `address` != :username_address"); - $stmt->execute(array(':username_goto' => '(^|,)'.$username.'($|,)', ':username_address' => $username)); + $stmt->execute(array( + ':username_goto' => '(^|,)'.$username.'($|,)', + ':username_goto2' => $username, + ':username_address' => $username + )); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($run)) { - $data['aliases'] = $row['aliases']; + $data['shared_aliases'] = $row['shared_aliases']; + } + $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '✘') AS `direct_aliases` FROM `alias` + WHERE `goto` = :username_goto + AND `address` != :username_address"); + $stmt->execute( + array( + ':username_goto' => $username, + ':username_address' => $username + )); + $run = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($run)) { + $data['direct_aliases'] = $row['direct_aliases']; } $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ', '), '✘') AS `ad_alias` FROM `mailbox` LEFT OUTER JOIN `alias_domain` on `target_domain` = `domain` @@ -851,6 +868,135 @@ function verify_tfa_login($username, $token) { } return false; } +function admin_api($action, $data = null) { + global $pdo; + global $lang; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + switch ($action) { + case "edit": + $regen_key = $data['admin_api_regen_key']; + $active = (isset($data['active'])) ? 1 : 0; + $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $data['allow_from'])); + foreach ($allow_from as $key => $val) { + if (!filter_var($val, FILTER_VALIDATE_IP)) { + unset($allow_from[$key]); + continue; + } + } + $allow_from = implode(',', array_unique(array_filter($allow_from))); + if (empty($allow_from)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'List of allowed IPs cannot be empty' + ); + return false; + } + $api_key = implode('-', array( + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))) + )); + $stmt = $pdo->prepare("INSERT INTO `api` (`username`, `api_key`, `active`, `allow_from`) + SELECT `username`, :api_key, :active, :allow_from FROM `admin` WHERE `superadmin`='1' AND `active`='1' + ON DUPLICATE KEY UPDATE `active` = :active_u, `allow_from` = :allow_from_u ;"); + $stmt->execute(array( + ':api_key' => $api_key, + ':active' => $active, + ':active_u' => $active, + ':allow_from' => $allow_from, + ':allow_from_u' => $allow_from + )); + break; + case "regen_key": + $api_key = implode('-', array( + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))) + )); + $stmt = $pdo->prepare("UPDATE `api` SET `api_key` = :api_key WHERE `username` IN + (SELECT `username` FROM `admin` WHERE `superadmin`='1' AND `active`='1')"); + $stmt->execute(array( + ':api_key' => $api_key + )); + break; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['admin_modified']) + ); +} +function rspamd_ui($action, $data = null) { + global $lang; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + switch ($action) { + case "edit": + $rspamd_ui_pass = $data['rspamd_ui_pass']; + $rspamd_ui_pass2 = $data['rspamd_ui_pass2']; + if (empty($rspamd_ui_pass) || empty($rspamd_ui_pass2)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Password cannot be empty' + ); + return false; + } + if ($rspamd_ui_pass != $rspamd_ui_pass2) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Passwords do not match' + ); + return false; + } + if (strlen($rspamd_ui_pass) < 6) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Please use at least 6 characters for your password' + ); + return false; + } + $docker_return = docker('rspamd-mailcow', 'post', 'exec', array('cmd' => 'worker_password', 'raw' => $rspamd_ui_pass), array('Content-Type: application/json')); + if ($docker_return_array = json_decode($docker_return, true)) { + if ($docker_return_array['type'] == 'success') { + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => 'Rspamd UI password set successfully' + ); + return true; + } + else { + $_SESSION['return'] = array( + 'type' => $docker_return_array['type'], + 'msg' => $docker_return_array['msg'] + ); + return false; + } + } + else { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Unknown error' + ); + return false; + } + break; + } + +} function get_admin_details() { // No parameter to be given, only one admin should exist global $pdo; @@ -860,8 +1006,10 @@ function get_admin_details() { return false; } try { - $stmt = $pdo->prepare("SELECT `username`, `modified`, `created` FROM `admin` WHERE `superadmin`='1' AND active='1'"); - $stmt->execute(); + $stmt = $pdo->query("SELECT `admin`.`username`, `api`.`active` AS `api_active`, `api`.`api_key`, `api`.`allow_from` FROM `admin` + INNER JOIN `api` ON `admin`.`username` = `api`.`username` + WHERE `admin`.`superadmin`='1' + AND `admin`.`active`='1'"); $data = $stmt->fetch(PDO::FETCH_ASSOC); } catch(PDOException $e) { @@ -932,6 +1080,51 @@ function get_logs($container, $lines = false) { return $data_array; } } + if ($container == "watchdog-mailcow") { + if (!is_numeric($lines)) { + list ($from, $to) = explode('-', $lines); + $data = $redis->lRange('WATCHDOG_LOG', intval($from), intval($to)); + } + else { + $data = $redis->lRange('WATCHDOG_LOG', 0, intval($lines)); + } + if ($data) { + foreach ($data as $json_line) { + $data_array[] = json_decode($json_line, true); + } + return $data_array; + } + } + if ($container == "acme-mailcow") { + if (!is_numeric($lines)) { + list ($from, $to) = explode('-', $lines); + $data = $redis->lRange('ACME_LOG', intval($from), intval($to)); + } + else { + $data = $redis->lRange('ACME_LOG', 0, intval($lines)); + } + if ($data) { + foreach ($data as $json_line) { + $data_array[] = json_decode($json_line, true); + } + return $data_array; + } + } + if ($container == "api-mailcow") { + if (!is_numeric($lines)) { + list ($from, $to) = explode('-', $lines); + $data = $redis->lRange('API_LOG', intval($from), intval($to)); + } + else { + $data = $redis->lRange('API_LOG', 0, intval($lines)); + } + if ($data) { + foreach ($data as $json_line) { + $data_array[] = json_decode($json_line, true); + } + return $data_array; + } + } if ($container == "fail2ban-mailcow") { if (!is_numeric($lines)) { list ($from, $to) = explode('-', $lines); diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index d1410f97..054a9499 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -490,9 +490,20 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { if (in_array($address, $gotos)) { continue; } + $domain = idn_to_ascii(substr(strstr($address, '@'), 1)); + $local_part = strstr($address, '@', true); + $address = $local_part.'@'.$domain; $stmt = $pdo->prepare("SELECT `address` FROM `alias` - WHERE `address`= :address"); - $stmt->execute(array(':address' => $address)); + WHERE `address`= :address OR `address` IN ( + SELECT `username` FROM `mailbox`, `alias_domain` + WHERE ( + `alias_domain`.`alias_domain` = :address_d + AND `mailbox`.`username` = CONCAT(:address_l, '@', alias_domain.target_domain)))"); + $stmt->execute(array( + ':address' => $address, + ':address_l' => $local_part, + ':address_d' => $domain + )); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); if ($num_results != 0) { $_SESSION['return'] = array( @@ -501,9 +512,6 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { ); return false; } - $domain = idn_to_ascii(substr(strstr($address, '@'), 1)); - $local_part = strstr($address, '@', true); - $address = $local_part.'@'.$domain; $domaindata = mailbox('get', 'domain_details', $domain); if (is_array($domaindata) && $domaindata['aliases_left'] == "0") { $_SESSION['return'] = array( @@ -722,7 +730,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { } $active = intval($_data['active']); $quota_b = ($quota_m * 1048576); - $maildir = $domain . "/" . $local_part . "/mail-" . time() . "/"; + $maildir = $domain . "/" . $local_part . "/mails/"; if (!is_valid_domain_name($domain)) { $_SESSION['return'] = array( 'type' => 'danger', @@ -2302,7 +2310,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { } else { try { - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role"); + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND (`domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role)"); $stmt->execute(array( ':username' => $_SESSION['mailcow_cc_username'], ':role' => $_SESSION['mailcow_cc_role'], @@ -3360,7 +3368,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { )); $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :domain"); $stmt->execute(array( - ':domain' => '%@'.$domain, + ':domain' => $domain, )); } catch (PDOException $e) { @@ -3484,7 +3492,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { )); $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :alias_domain"); $stmt->execute(array( - ':domain' => '%@'.$alias_domain, + ':domain' => $alias_domain, )); } catch (PDOException $e) { diff --git a/data/web/inc/functions.quarantaine.inc.php b/data/web/inc/functions.quarantaine.inc.php new file mode 100644 index 00000000..4b9e6b00 --- /dev/null +++ b/data/web/inc/functions.quarantaine.inc.php @@ -0,0 +1,282 @@ + 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + foreach ($ids as $id) { + if (!is_numeric($id)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + try { + $stmt = $pdo->prepare('SELECT `rcpt` FROM `quarantaine` WHERE `id` = :id'); + $stmt->execute(array(':id' => $id)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt'])) { + try { + $stmt = $pdo->prepare("DELETE FROM `quarantaine` WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + else { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + } + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['items_deleted'], implode(', ', $ids)) + ); + break; + case 'edit': + if (!isset($_SESSION['acl']['quarantaine']) || $_SESSION['acl']['quarantaine'] != "1" ) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + // Edit settings + if ($_data['action'] == 'settings') { + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $retention_size = $_data['retention_size']; + $max_size = $_data['max_size']; + $exclude_domains = (array)$_data['exclude_domains']; + try { + $redis->Set('Q_RETENTION_SIZE', intval($retention_size)); + $redis->Set('Q_MAX_SIZE', intval($max_size)); + $redis->Set('Q_EXCLUDE_DOMAINS', json_encode($exclude_domains)); + } + catch (RedisException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Redis: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => 'Saved settings' + ); + } + // Release item + elseif ($_data['action'] == 'release') { + if (!is_array($_data['id'])) { + $ids = array(); + $ids[] = $_data['id']; + } + else { + $ids = $_data['id']; + } + foreach ($ids as $id) { + if (!is_numeric($id)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + try { + $stmt = $pdo->prepare('SELECT `msg`, `qid`, `sender`, `rcpt` FROM `quarantaine` WHERE `id` = :id'); + $stmt->execute(array(':id' => $id)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + } + $sender = (isset($row['sender'])) ? $row['sender'] : 'sender-unknown@rspamd'; + try { + $mail = new PHPMailer(true); + $mail->isSMTP(); + $mail->SMTPDebug = 0; + $mail->SMTPOptions = array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ) + ); + if (!empty(gethostbynamel('postfix-mailcow'))) { + $postfix = 'apostfix-mailcow'; + } + if (!empty(gethostbynamel('postfix'))) { + $postfix = 'postfix'; + } + else { + $_SESSION['return'] = array( + 'type' => 'warning', + 'msg' => sprintf($lang['danger']['release_send_failed'], 'Cannot determine Postfix host') + ); + return false; + } + $mail->Host = $postfix; + $mail->Port = 590; + $mail->setFrom($sender); + $mail->CharSet = 'UTF-8'; + $mail->Subject = sprintf($lang['quarantaine']['release_subject'], $row['qid']); + $mail->addAddress($row['rcpt']); + $mail->IsHTML(false); + $msg_tmpf = tempnam("/tmp", $row['qid']); + file_put_contents($msg_tmpf, $row['msg']); + $mail->addAttachment($msg_tmpf, $row['qid'] . '.eml'); + $mail->Body = sprintf($lang['quarantaine']['release_body']); + $mail->send(); + unlink($msg_tmpf); + } + catch (phpmailerException $e) { + unlink($msg_tmpf); + $_SESSION['return'] = array( + 'type' => 'warning', + 'msg' => sprintf($lang['danger']['release_send_failed'], $e->errorMessage()) + ); + return false; + } + try { + $stmt = $pdo->prepare("DELETE FROM `quarantaine` WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => $lang['success']['items_released'] + ); + } + return true; + break; + case 'get': + try { + if ($_SESSION['mailcow_cc_role'] == "user") { + $stmt = $pdo->prepare('SELECT `id`, `qid`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantaine` WHERE `rcpt` = :mbox'); + $stmt->execute(array(':mbox' => $_SESSION['mailcow_cc_username'])); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $q_meta[] = $row; + } + } + else { + foreach (mailbox('get', 'mailboxes') as $mbox) { + $stmt = $pdo->prepare('SELECT `id`, `qid`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantaine` WHERE `rcpt` = :mbox'); + $stmt->execute(array(':mbox' => $mbox)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $q_meta[] = $row; + } + } + } + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + } + return $q_meta; + break; + case 'settings': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + try { + $settings['exclude_domains'] = json_decode($redis->Get('Q_EXCLUDE_DOMAINS'), true); + $settings['max_size'] = $redis->Get('Q_MAX_SIZE'); + $settings['retention_size'] = $redis->Get('Q_RETENTION_SIZE'); + } + catch (RedisException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Redis: '.$e + ); + return false; + } + return $settings; + break; + case 'details': + if (!is_numeric($_data) || empty($_data)) { + return false; + } + try { + $stmt = $pdo->prepare('SELECT `rcpt`, `symbols`, `msg`, `domain` FROM `quarantaine` WHERE `id`= :id'); + $stmt->execute(array(':id' => $_data)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt'])) { + return $row; + } + return false; + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + } + return false; + break; + } +} \ No newline at end of file diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php index cedd07ca..c8dbe4b1 100644 --- a/data/web/inc/header.inc.php +++ b/data/web/inc/header.inc.php @@ -15,6 +15,7 @@ + @@ -27,6 +28,8 @@ ' : null; ?> ' : null; ?> ' : null; ?> +' : null; ?> +' : null; ?> @@ -35,7 +38,6 @@
diff --git a/data/web/modals/admin.php b/data/web/modals/admin.php index bf17296c..3a387540 100644 --- a/data/web/modals/admin.php +++ b/data/web/modals/admin.php @@ -13,7 +13,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {