From 14d2b3d7632881d1cb208dce7641a5a153bd2fcf Mon Sep 17 00:00:00 2001 From: Michael Kuron Date: Sun, 9 Jul 2017 10:01:27 +0200 Subject: [PATCH 01/23] DNS diagnostics page --- data/web/inc/header.inc.php | 5 + data/web/lang/lang.de.php | 7 + data/web/lang/lang.en.php | 7 + diagnostics.php | 272 ++++++++++++++++++++++++++++++++++++ 4 files changed, 291 insertions(+) create mode 100644 diagnostics.php diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php index 533711de..8e5b6d07 100644 --- a/data/web/inc/header.inc.php +++ b/data/web/inc/header.inc.php @@ -77,6 +77,11 @@ > + > + > diff --git a/data/web/lang/lang.de.php b/data/web/lang/lang.de.php index eef13b3f..732171cc 100644 --- a/data/web/lang/lang.de.php +++ b/data/web/lang/lang.de.php @@ -214,6 +214,7 @@ $lang['header']['mailcow_settings'] = 'Konfiguration'; $lang['header']['administration'] = 'Administration'; $lang['header']['mailboxes'] = 'Mailboxen'; $lang['header']['user_settings'] = 'Benutzereinstellungen'; +$lang['header']['diagnostics'] = 'Diagnose'; $lang['header']['login'] = 'Anmeldung'; $lang['header']['logged_in_as_logout'] = 'Eingeloggt als %s (abmelden)'; $lang['header']['logged_in_as_logout_dual'] = 'Eingeloggt als %s [%s]'; @@ -495,3 +496,9 @@ $lang['admin']['add_forwarding_host'] = 'Weiterleitungs-Host hinzufügen'; $lang['delete']['remove_forwardinghost_warning'] = 'Warnung: Sie entfernen den Weiterleitungs-Host %s!'; $lang['success']['forwarding_host_removed'] = "Weiterleitungs-Host %s wurde entfernt"; $lang['success']['forwarding_host_added'] = "Weiterleitungs-Host %s wurde hinzugefügt"; +$lang['diagnostics']['dns_records'] = 'DNS-Einträge'; +$lang['diagnostics']['dns_records_24hours'] = 'Bitte beachten Sie, dass es bis zu 24 Stunden dauern kann, bis Änderungen an Ihren DNS-Einträgen als aktueller Status auf dieser Seite dargestellt werden. Diese Seite ist nur als Hilfsmittel gedacht, um die korrekten Werte für DNS-Einträge zu anzuzeigen und zu überprüfen, ob die Daten im DNS hinterlegt sind.'; +$lang['diagnostics']['dns_records_name'] = 'Name'; +$lang['diagnostics']['dns_records_type'] = 'Typ'; +$lang['diagnostics']['dns_records_data'] = 'Korrekte Daten'; +$lang['diagnostics']['dns_records_status'] = 'Aktueller Status'; diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php index 3be72fec..391c7d01 100644 --- a/data/web/lang/lang.en.php +++ b/data/web/lang/lang.en.php @@ -216,6 +216,7 @@ $lang['header']['mailcow_settings'] = 'Configuration'; $lang['header']['administration'] = 'Administration'; $lang['header']['mailboxes'] = 'Mailboxes'; $lang['header']['user_settings'] = 'User settings'; +$lang['header']['diagnostics'] = 'Diagnostics'; $lang['header']['login'] = 'Login'; $lang['header']['logged_in_as_logout'] = 'Logged in as %s (logout)'; $lang['header']['logged_in_as_logout_dual'] = 'Logged in as %s [%s]'; @@ -508,3 +509,9 @@ $lang['admin']['add_forwarding_host'] = 'Add Forwarding Host'; $lang['delete']['remove_forwardinghost_warning'] = 'Warning: You are about to remove the forwarding host %s!'; $lang['success']['forwarding_host_removed'] = "Forwarding host %s has been removed"; $lang['success']['forwarding_host_added'] = "Forwarding host %s has been added"; +$lang['diagnostics']['dns_records'] = 'DNS Records'; +$lang['diagnostics']['dns_records_24hours'] = 'Please note that changes made to DNS may take up to 24 hours to correctly have their current state reflected on this page. It is intended as a way for you to easily see how to configure your DNS records and to check whether all your records are correctly stored in DNS.'; +$lang['diagnostics']['dns_records_name'] = 'Name'; +$lang['diagnostics']['dns_records_type'] = 'Type'; +$lang['diagnostics']['dns_records_data'] = 'Correct Data'; +$lang['diagnostics']['dns_records_status'] = 'Current State'; diff --git a/diagnostics.php b/diagnostics.php new file mode 100644 index 00000000..4de0082e --- /dev/null +++ b/diagnostics.php @@ -0,0 +1,272 @@ + 1) { + $mask = $net[1]; + } + $net = inet_pton($net[0]); + $addr = inet_pton($addr); + $length = strlen($net); // 4 for IPv4, 16 for IPv6 + if (strlen($net) != strlen($addr)) { + return false; + } + if (!isset($mask)) { + $mask = $length * 8; + } + $addr_bin = ''; + $net_bin = ''; + for ($i = 0; $i < $length; ++$i) { + $addr_bin .= str_pad(decbin(ord(substr($addr, $i, $i+1))), 8, '0', STR_PAD_LEFT); + $net_bin .= str_pad(decbin(ord(substr($net, $i, $i+1))), 8, '0', STR_PAD_LEFT); + } + return substr($addr_bin, 0, $mask) == substr($net_bin, 0, $mask); +} + +if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") { +require_once("inc/header.inc.php"); + +$ch = curl_init('http://ipv4.mailcow.email'); +curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); +curl_setopt($ch, CURLOPT_VERBOSE, false); +curl_setopt($ch, CURLOPT_HEADER, false); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); +curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); +$ip = curl_exec($ch); +curl_close($ch); + +$ch = curl_init('http://ipv6.mailcow.email'); +curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); +curl_setopt($ch, CURLOPT_VERBOSE, false); +curl_setopt($ch, CURLOPT_HEADER, false); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); +curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); +$ip6 = curl_exec($ch); +curl_close($ch); + +$ptr = implode('.', array_reverse(explode('.', $ip))) . '.in-addr.arpa'; +if (!empty($ip6)) { + $ip6_full = str_replace('::', str_repeat(':', 9-substr_count($ip6, ':')), $ip6); + $ip6_full = str_replace('::', ':0:', $ip6_full); + $ip6_full = str_replace('::', ':0:', $ip6_full); + $ptr6 = ''; + foreach (explode(':', $ip6_full) as $part) { + $ptr6 .= str_pad($part, 4, '0', STR_PAD_LEFT); + } + $ptr6 = implode('.', array_reverse(str_split($ptr6, 1))) . '.ip6.arpa'; +} + +$https_port = strpos($_SERVER['HTTP_HOST'], ':'); +if ($https_port === FALSE) { + $https_port = 443; +} else { + $https_port = substr($_SERVER['HTTP_HOST'], $https_port+1); +} + +$records = array(); +$records[] = array($mailcow_hostname, 'A', $ip); +$records[] = array($ptr, 'PTR', $mailcow_hostname); +if (!empty($ip6)) { + $records[] = array($mailcow_hostname, 'AAAA', $ip6); + $records[] = array($ptr6, 'PTR', $mailcow_hostname); +} +$domains = mailbox('get', 'domains'); +foreach(mailbox('get', 'domains') as $domain) { + $domains = array_merge($domains, mailbox('get', 'alias_domains', $domain)); +} + +if (!isset($autodiscover_config['sieve'])) { + $autodiscover_config['sieve'] = array('server' => $mailcow_hostname, 'port' => array_pop(explode(':', getenv('SIEVE_PORT')))); +} + +$records[] = array('_25._tcp.' . $autodiscover_config['smtp']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['smtp']['server'], 25, 1)); +$records[] = array('_' . $https_port . '._tcp.' . $mailcow_hostname, 'TLSA', generate_tlsa_digest($mailcow_hostname, $https_port)); +$records[] = array('_' . $autodiscover_config['pop3']['tlsport'] . '._tcp.' . $autodiscover_config['pop3']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['tlsport'], 1)); +$records[] = array('_' . $autodiscover_config['imap']['tlsport'] . '._tcp.' . $autodiscover_config['imap']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['tlsport'], 1)); +$records[] = array('_' . $autodiscover_config['smtp']['port'] . '._tcp.' . $autodiscover_config['smtp']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['smtp']['server'], $autodiscover_config['smtp']['port'])); +$records[] = array('_' . $autodiscover_config['smtp']['tlsport'] . '._tcp.' . $autodiscover_config['smtp']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['smtp']['server'], $autodiscover_config['smtp']['tlsport'], 1)); +$records[] = array('_' . $autodiscover_config['imap']['port'] . '._tcp.' . $autodiscover_config['imap']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['port'])); +$records[] = array('_' . $autodiscover_config['pop3']['port'] . '._tcp.' . $autodiscover_config['pop3']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['port'])); +$records[] = array('_' . $autodiscover_config['sieve']['port'] . '._tcp.' . $autodiscover_config['sieve']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['sieve']['server'], $autodiscover_config['sieve']['port'], 1)); + +foreach ($domains as $domain) { + $records[] = array($domain, 'MX', $mailcow_hostname); + $records[] = array('autodiscover.' . $domain, 'CNAME', $mailcow_hostname); + $records[] = array('_autodiscover._tcp.' . $domain, 'SRV', $mailcow_hostname . ' ' . $https_port); + $records[] = array('autoconfig.' . $domain, 'CNAME', $mailcow_hostname); + $records[] = array($domain, 'TXT', 'v=spf1 mx -all'); + $records[] = array('_dmarc.' . $domain, 'TXT', 'v=DMARC1; p=reject', 'v=DMARC1; p='); + + if (!empty($dkim = dkim('details', $domain))) { + $records[] = array($dkim['dkim_selector'] . '._domainkey.' . $domain, 'TXT', $dkim['dkim_txt']); + } + + $current_records = dns_get_record('_pop3._tcp.' . $domain, DNS_SRV); + if (count($current_records) == 0 || $current_records[0]['target'] != '') { + if ($autodiscover_config['pop3']['tlsport'] != '110') { + $records[] = array('_pop3._tcp.' . $domain, 'SRV', $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['tlsport']); + } + } else { + $records[] = array('_pop3._tcp.' . $domain, 'SRV', '. 0'); + } + $current_records = dns_get_record('_pop3s._tcp.' . $domain, DNS_SRV); + if (count($current_records) == 0 || $current_records[0]['target'] != '') { + if ($autodiscover_config['pop3']['port'] != '995') { + $records[] = array('_pop3s._tcp.' . $domain, 'SRV', $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['port']); + } + } else { + $records[] = array('_pop3s._tcp.' . $domain, 'SRV', '. 0'); + } + if ($autodiscover_config['imap']['tlsport'] != '143') { + $records[] = array('_imap._tcp.' . $domain, 'SRV', $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['tlsport']); + } + if ($autodiscover_config['imap']['port'] != '993') { + $records[] = array('_imaps._tcp.' . $domain, 'SRV', $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['port']); + } + if ($autodiscover_config['smtp']['tlsport'] != '587') { + $records[] = array('_submission._tcp.' . $domain, 'SRV', $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['tlsport']); + } + if ($autodiscover_config['smtp']['port'] != '465') { + $records[] = array('_smtps._tcp.' . $domain, 'SRV', $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['port']); + } + if ($autodiscover_config['sieve']['port'] != '4190') { + $records[] = array('_sieve._tcp.' . $domain, 'SRV', $autodiscover_config['sieve']['server'] . ' ' . $autodiscover_config['sieve']['port']); + } +} + +define('state_good', "✓"); +define('state_missing', "✗"); +define('state_nomatch', "?"); + +$record_types = array( + 'A' => DNS_A, + 'AAAA' => DNS_AAAA, + 'CNAME' => DNS_CNAME, + 'MX' => DNS_MX, + 'PTR' => DNS_PTR, + 'SRV' => DNS_SRV, + 'TXT' => DNS_TXT, +); +$data_field = array( + 'A' => 'ip', + 'AAAA' => 'ipv6', + 'CNAME' => 'target', + 'MX' => 'target', + 'PTR' => 'target', + 'SRV' => 'data', + 'TLSA' => 'data', + 'TXT' => 'txt', +); +?> +
+

+

+
+ + + 0 && count($cname) > 0) { + if ($a[0]['ip'] == $cname[0]['ip']) { + $currents = array(array('host' => $record[0], 'class' => 'IN', 'type' => 'CNAME', 'target' => $record[2])); + + $aaaa = dns_get_record($record[0], DNS_AAAA); + $cname = dns_get_record($record[2], DNS_AAAA); + if (count($aaaa) == 0 || count($cname) == 0 || $aaaa[0]['ipv6'] != $cname[0]['ipv6']) { + $currents[0]['target'] = $aaaa[0]['ipv6']; + } + } else { + $currents = array(array('host' => $record[0], 'class' => 'IN', 'type' => 'CNAME', 'target' => $a[0]['ip'])); + } + } + } + + foreach ($currents as $current) { + $current['type'] == strtoupper($current['type']); + if ($current['type'] != $record[1]) + { + continue; + } + + elseif ($current['type'] == 'TXT' && strpos($record[0], '_dmarc.') === 0) { + $state = state_nomatch; + if (strpos($current[$data_field[$current['type']]], $record[3]) === 0) + $state = state_good . ' (' . current[$data_field[$current['type']]] . ')'; + } + else if ($current['type'] == 'TXT' && strpos($current['txt'], 'v=spf1') === 0) { + $allowed = get_spf_allowed_hosts($record[0]); + $spf_ok = FALSE; + $spf_ok6 = FALSE; + foreach ($allowed as $net) + { + if (in_net($ip, $net)) + $spf_ok = TRUE; + if (in_net($ip6, $net)) + $spf_ok6 = TRUE; + } + if ($spf_ok && (empty($ip6) || $spf_ok6)) + $state = state_good . ' (' . $current[$data_field[$current['type']]] . ')'; + } + else if ($current['type'] != 'TXT' && isset($data_field[$current['type']]) && $state != state_good) { + $state = state_nomatch; + if ($current[$data_field[$current['type']]] == $record[2]) + $state = state_good; + } + } + + if ($state == state_nomatch) { + $state = array(); + foreach ($currents as $current) { + $state[] = $current[$data_field[$current['type']]]; + } + $state = implode('
', $state); + } + + echo sprintf('', $record[0], $record[1], $record[2], $state); +} +?> +
%s%s%s%s
+
+
+ From 98be90c4943bf05f488d7cefc8299283336369df Mon Sep 17 00:00:00 2001 From: Michael Kuron Date: Mon, 10 Jul 2017 21:41:45 +0200 Subject: [PATCH 02/23] Remove SPF and DMARC checks --- diagnostics.php => data/web/diagnostics.php | 57 +++++---------------- 1 file changed, 13 insertions(+), 44 deletions(-) rename diagnostics.php => data/web/diagnostics.php (88%) diff --git a/diagnostics.php b/data/web/diagnostics.php similarity index 88% rename from diagnostics.php rename to data/web/diagnostics.php index 4de0082e..5a199007 100644 --- a/diagnostics.php +++ b/data/web/diagnostics.php @@ -2,28 +2,10 @@ require_once 'inc/prerequisites.inc.php'; require_once 'inc/spf.inc.php'; -function in_net($addr, $net) { - $net = explode('/', $net); - if (count($net) > 1) { - $mask = $net[1]; - } - $net = inet_pton($net[0]); - $addr = inet_pton($addr); - $length = strlen($net); // 4 for IPv4, 16 for IPv6 - if (strlen($net) != strlen($addr)) { - return false; - } - if (!isset($mask)) { - $mask = $length * 8; - } - $addr_bin = ''; - $net_bin = ''; - for ($i = 0; $i < $length; ++$i) { - $addr_bin .= str_pad(decbin(ord(substr($addr, $i, $i+1))), 8, '0', STR_PAD_LEFT); - $net_bin .= str_pad(decbin(ord(substr($net, $i, $i+1))), 8, '0', STR_PAD_LEFT); - } - return substr($addr_bin, 0, $mask) == substr($net_bin, 0, $mask); -} +define('state_good', "✓"); +define('state_missing', "✗"); +define('state_nomatch', "?"); +define('state_optional', "(optional)"); if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") { require_once("inc/header.inc.php"); @@ -96,8 +78,8 @@ foreach ($domains as $domain) { $records[] = array('autodiscover.' . $domain, 'CNAME', $mailcow_hostname); $records[] = array('_autodiscover._tcp.' . $domain, 'SRV', $mailcow_hostname . ' ' . $https_port); $records[] = array('autoconfig.' . $domain, 'CNAME', $mailcow_hostname); - $records[] = array($domain, 'TXT', 'v=spf1 mx -all'); - $records[] = array('_dmarc.' . $domain, 'TXT', 'v=DMARC1; p=reject', 'v=DMARC1; p='); + $records[] = array($domain, 'TXT', 'SPF Record Syntax', state_optional); + $records[] = array('_dmarc.' . $domain, 'TXT', 'DMARC Assistant', state_optional); if (!empty($dkim = dkim('details', $domain))) { $records[] = array($dkim['dkim_selector'] . '._domainkey.' . $domain, 'TXT', $dkim['dkim_txt']); @@ -136,10 +118,6 @@ foreach ($domains as $domain) { } } -define('state_good', "✓"); -define('state_missing', "✗"); -define('state_nomatch', "?"); - $record_types = array( 'A' => DNS_A, 'AAAA' => DNS_AAAA, @@ -224,23 +202,10 @@ foreach ($records as $record) } elseif ($current['type'] == 'TXT' && strpos($record[0], '_dmarc.') === 0) { - $state = state_nomatch; - if (strpos($current[$data_field[$current['type']]], $record[3]) === 0) - $state = state_good . ' (' . current[$data_field[$current['type']]] . ')'; + $state = state_optional . '
' . $current[$data_field[$current['type']]]; } else if ($current['type'] == 'TXT' && strpos($current['txt'], 'v=spf1') === 0) { - $allowed = get_spf_allowed_hosts($record[0]); - $spf_ok = FALSE; - $spf_ok6 = FALSE; - foreach ($allowed as $net) - { - if (in_net($ip, $net)) - $spf_ok = TRUE; - if (in_net($ip6, $net)) - $spf_ok6 = TRUE; - } - if ($spf_ok && (empty($ip6) || $spf_ok6)) - $state = state_good . ' (' . $current[$data_field[$current['type']]] . ')'; + $state = state_optional . '
' . $current[$data_field[$current['type']]]; } else if ($current['type'] != 'TXT' && isset($data_field[$current['type']]) && $state != state_good) { $state = state_nomatch; @@ -249,6 +214,10 @@ foreach ($records as $record) } } + if (isset($record[3]) && $record[3] == state_optional && ($state == state_missing || $state == state_nomatch)) { + $state = state_optional; + } + if ($state == state_nomatch) { $state = array(); foreach ($currents as $current) { @@ -256,7 +225,7 @@ foreach ($records as $record) } $state = implode('
', $state); } - + echo sprintf('%s%s%s%s', $record[0], $record[1], $record[2], $state); } ?> From 651c1cac23d77988521d96283043f476d3361ed8 Mon Sep 17 00:00:00 2001 From: Phoenix Eve Aspacio Date: Thu, 21 Sep 2017 07:22:33 +0800 Subject: [PATCH 03/23] Fixed broken link --- data/web/diagnostics.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/diagnostics.php b/data/web/diagnostics.php index 5a199007..d35c4d47 100644 --- a/data/web/diagnostics.php +++ b/data/web/diagnostics.php @@ -10,7 +10,7 @@ define('state_optional', "(optional)"); if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") { require_once("inc/header.inc.php"); -$ch = curl_init('http://ipv4.mailcow.email'); +$ch = curl_init('http://ip4.mailcow.email'); curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); curl_setopt($ch, CURLOPT_VERBOSE, false); curl_setopt($ch, CURLOPT_HEADER, false); @@ -19,7 +19,7 @@ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); $ip = curl_exec($ch); curl_close($ch); -$ch = curl_init('http://ipv6.mailcow.email'); +$ch = curl_init('http://ip6.mailcow.email'); curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); curl_setopt($ch, CURLOPT_VERBOSE, false); curl_setopt($ch, CURLOPT_HEADER, false); From f067a45bcb2a2c0b75fda17693297829bee64674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Thu, 2 Nov 2017 09:51:58 +0100 Subject: [PATCH 04/23] [SOGo] Should fix some Android sync issues --- data/conf/sogo/sogo.conf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index 86278abc..404fd451 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -43,7 +43,9 @@ SOGoInternalSyncInterval = 30; SOGoMaximumSyncInterval = 354; - SOGoMaximumSyncWindowSize = 100; + // 100 seems to break some Android clients + //SOGoMaximumSyncWindowSize = 100; + // This should do the trick for Outlook 2016 SOGoMaximumSyncResponseSize = 5172; WOWatchDogRequestTimeout = 10; From b32e5adcc5d6e22fed6eefc94f769073978525e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 3 Nov 2017 20:25:38 +0100 Subject: [PATCH 05/23] [Dovecot] sieve_before/after maps in sql, changed dict names --- data/Dockerfiles/dovecot/Dockerfile | 12 ++--- data/Dockerfiles/dovecot/docker-entrypoint.sh | 50 ++++++++++++++++++- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index d7f9cf38..d2cdd0e3 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -3,8 +3,8 @@ LABEL maintainer "Andre Peters " ARG DEBIAN_FRONTEND=noninteractive ENV LC_ALL C -ENV DOVECOT_VERSION 2.2.32 -ENV PIGEONHOLE_VERSION 0.4.20 +ENV DOVECOT_VERSION 2.2.33.2 +ENV PIGEONHOLE_VERSION 0.4.21 RUN apt-get update && apt-get -y install \ automake \ @@ -40,10 +40,11 @@ RUN apt-get update && apt-get -y install \ libtest-pod-perl \ libtest-simple-perl \ libunicode-string-perl \ - libproc-processtable-perl \ + libproc-processtable-perl \ liburi-perl \ lzma-dev \ make \ + procps \ supervisor \ syslog-ng \ syslog-ng-core \ @@ -64,7 +65,8 @@ RUN curl https://www.dovecot.org/releases/2.2/dovecot-$DOVECOT_VERSION.tar.gz | && make -j3 \ && make install \ && make clean \ - && cd .. && rm -rf dovecot-2.2-pigeonhole-$PIGEONHOLE_VERSION + && cd .. \ + && rm -rf dovecot-2.2-pigeonhole-$PIGEONHOLE_VERSION RUN cpanm Data::Uniqid Mail::IMAPClient String::Util RUN echo '* * * * * root /usr/local/bin/imapsync_cron.pl' > /etc/cron.d/imapsync @@ -98,8 +100,6 @@ RUN touch /etc/default/locale RUN apt-get purge -y build-essential automake autotools-dev \ && apt-get autoremove --purge -y -EXPOSE 24 10001 - ENTRYPOINT ["/docker-entrypoint.sh"] CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index 4e9fe14b..9f8f5313 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -15,7 +15,7 @@ sed -i "/^\$DBNAME/c\\\$DBNAME='${DBNAME}';" /usr/local/bin/imapsync_cron.pl DBPASS=$(echo ${DBPASS} | sed 's/"/\\"/g') # Create quota dict for Dovecot -cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql.conf +cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-quota.conf connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" map { pattern = priv/quota/storage @@ -31,8 +31,54 @@ map { } EOF +# Create dict used for sieve pre and postfilters +cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf +connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" +map { + pattern = priv/sieve/name/\$script_name + table = sieve_before + username_field = username + value_field = id + fields { + script_name = \$script_name + } +} +map { + pattern = priv/sieve/data/\$id + table = sieve_before + username_field = username + value_field = script_data + fields { + id = \$id + } +} +EOF + +cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf +connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" +map { + pattern = priv/sieve/name/\$script_name + table = sieve_after + username_field = username + value_field = id + fields { + script_name = \$script_name + } +} +map { + pattern = priv/sieve/data/\$id + table = sieve_after + username_field = username + value_field = script_data + fields { + id = \$id + } +} +EOF + + # Create user and pass dict for Dovecot -cat < /usr/local/etc/dovecot/sql/dovecot-mysql.conf +cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-passdb.conf driver = mysql connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" default_pass_scheme = SSHA256 From 21e20f378686a031197f1a785409beaf003cc421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 3 Nov 2017 20:25:43 +0100 Subject: [PATCH 06/23] [Dovecot] sieve_before/after maps in sql, changed dict names --- data/conf/dovecot/dovecot.conf | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf index 59362958..a9e6f0af 100644 --- a/data/conf/dovecot/dovecot.conf +++ b/data/conf/dovecot/dovecot.conf @@ -32,7 +32,7 @@ passdb { pass = yes } passdb { - args = /usr/local/etc/dovecot/sql/dovecot-mysql.conf + args = /usr/local/etc/dovecot/sql/dovecot-dict-sql-passdb.conf driver = sql } # Set doveadm_password=your-secret-password in data/conf/dovecot/extra.conf (create if missing) @@ -211,7 +211,7 @@ listen = *,[::] ssl_cert = Date: Fri, 3 Nov 2017 20:26:09 +0100 Subject: [PATCH 07/23] [Dockerapi] Return answers in json --- data/Dockerfiles/dockerapi/server.py | 66 ++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/data/Dockerfiles/dockerapi/server.py b/data/Dockerfiles/dockerapi/server.py index aabcaf31..b406b5ee 100644 --- a/data/Dockerfiles/dockerapi/server.py +++ b/data/Dockerfiles/dockerapi/server.py @@ -1,6 +1,7 @@ from flask import Flask from flask_restful import Resource, Api from flask import jsonify +from flask import request from threading import Thread import docker import signal @@ -13,17 +14,23 @@ api = Api(app) class containers_get(Resource): def get(self): containers = {} - for container in docker_client.containers.list(all=True): - containers.update({container.attrs['Id']: container.attrs}) - return containers + try: + for container in docker_client.containers.list(all=True): + containers.update({container.attrs['Id']: container.attrs}) + return containers + except Exception as e: + return jsonify(type='danger', msg=e) class container_get(Resource): def get(self, container_id): if container_id and container_id.isalnum(): - for container in docker_client.containers.list(all=True, filters={"id": container_id}): - return container.attrs + try: + for container in docker_client.containers.list(all=True, filters={"id": container_id}): + return container.attrs + except Exception as e: + return jsonify(type='danger', msg=e) else: - return jsonify(message='No or invalid id defined') + return jsonify(type='danger', msg='no or invalid id defined') class container_post(Resource): def post(self, container_id, post_action): @@ -32,30 +39,51 @@ class container_post(Resource): try: for container in docker_client.containers.list(all=True, filters={"id": container_id}): container.stop() - except: - return 'Error' - else: - return 'OK' + return jsonify(type='success', msg='command completed successfully') + except Exception as e: + return jsonify(type='danger', msg=e) + elif post_action == 'start': try: for container in docker_client.containers.list(all=True, filters={"id": container_id}): container.start() - except: - return 'Error' - else: - return 'OK' + return jsonify(type='success', msg='command completed successfully') + except Exception as e: + return jsonify(type='danger', msg=e) + elif post_action == 'restart': try: for container in docker_client.containers.list(all=True, filters={"id": container_id}): container.restart() - except: - return 'Error' + return jsonify(type='success', msg='command completed successfully') + except Exception as e: + return jsonify(type='danger', msg=e) + + elif post_action == 'exec': + + if not request.json or not 'cmd' in request.json: + return jsonify(type='danger', msg='cmd is missing') + + if request.json['cmd'] == 'sieve_list' and request.json['username']: + try: + for container in docker_client.containers.list(filters={"id": container_id}): + return container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve list -u '" + request.json['username'].replace("'", "'\\''") + "'"], user='vmail') + except Exception as e: + return jsonify(type='danger', msg=e) + elif request.json['cmd'] == 'sieve_print' and request.json['script_name'] and request.json['username']: + try: + for container in docker_client.containers.list(filters={"id": container_id}): + return container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve get -u '" + request.json['username'].replace("'", "'\\''") + "' '" + request.json['script_name'].replace("'", "'\\''") + "'"], user='vmail') + except Exception as e: + return jsonify(type='danger', msg=e) else: - return 'OK' + return jsonify(type='danger', msg='Unknown command') + else: - return jsonify(message='Invalid action') + return jsonify(type='danger', msg='invalid action') + else: - return jsonify(message='Invalid container id or missing action') + return jsonify(type='danger', msg='invalid container id or missing action') class GracefulKiller: kill_now = False From b16684ce204b99d24583da8b6622ad180c870fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 3 Nov 2017 20:26:36 +0100 Subject: [PATCH 08/23] [Rspamd] Slightly reduce map watch interval --- data/conf/rspamd/local.d/options.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/conf/rspamd/local.d/options.inc b/data/conf/rspamd/local.d/options.inc index 09117984..f915a151 100644 --- a/data/conf/rspamd/local.d/options.inc +++ b/data/conf/rspamd/local.d/options.inc @@ -1,7 +1,7 @@ dns { enable_dnssec = true; } -map_watch_interval = 60s; +map_watch_interval = 30s; dns { timeout = 4s; retransmits = 5; From 1ef10f135889985577e1eb34239a214953f64afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 3 Nov 2017 20:27:43 +0100 Subject: [PATCH 09/23] [PHP-FPM] Include net_sieve, test removal of usr/src/php for size --- data/Dockerfiles/phpfpm/Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index fc6552fd..6a1e90a3 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -36,6 +36,7 @@ RUN apk add -U --no-cache libxml2-dev \ && pear install channel://pear.php.net/Net_IDNA2-0.2.0 \ channel://pear.php.net/Auth_SASL-1.1.0 \ Net_IMAP \ + Net_Sieve \ NET_SMTP \ Mail_mime \ && pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} \ @@ -54,7 +55,8 @@ RUN apk add -U --no-cache libxml2-dev \ echo 'opcache.memory_consumption=128'; \ echo 'opcache.save_comments=1'; \ echo 'opcache.revalidate_freq=1'; \ -} > /usr/local/etc/php/conf.d/opcache-recommended.ini +} > /usr/local/etc/php/conf.d/opcache-recommended.ini \ + && rm -rf /usr/src/php* COPY ./docker-entrypoint.sh / From 85d1ee2f49ed350529fb3fd829268c4cdf8dfbd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 3 Nov 2017 20:37:24 +0100 Subject: [PATCH 10/23] [Web] Autodiscover returns given password decoded and trimed; Add sieve pre and post filters to UI; Move ajax called files; Rework log system: 100 entries per default, add more per click; Syncjobs: Do not read log to data attribute --- data/web/admin.php | 56 +- data/web/api.php | 32 - data/web/autodiscover.php | 3 +- data/web/css/footable.bootstrap.min.css | 5 +- data/web/css/mailbox.css | 2 +- data/web/css/numberedtextarea.min.css | 1 + data/web/css/user.css | 7 + data/web/edit.php | 85 +- data/web/inc/ajax/sieve_validation.php | 23 + data/web/inc/ajax/sogo_ctrl.php | 39 + data/web/inc/ajax/syncjob_logs.php | 15 + data/web/inc/footer.inc.php | 16 +- ...sogo_ctrl.php => functions.docker.inc.php} | 39 +- data/web/inc/functions.inc.php | 63 +- data/web/inc/functions.mailbox.inc.php | 376 ++++++++- data/web/inc/header.inc.php | 1 + data/web/inc/init_db.inc.php | 46 +- data/web/inc/lib/sieve/SieveDumpable.php | 7 + data/web/inc/lib/sieve/SieveException.php | 47 ++ .../inc/lib/sieve/SieveKeywordRegistry.php | 233 +++++ data/web/inc/lib/sieve/SieveParser.php | 255 ++++++ data/web/inc/lib/sieve/SieveScanner.php | 145 ++++ data/web/inc/lib/sieve/SieveScript.php | 6 + data/web/inc/lib/sieve/SieveSemantics.php | 611 ++++++++++++++ data/web/inc/lib/sieve/SieveToken.php | 88 ++ data/web/inc/lib/sieve/SieveTree.php | 117 +++ data/web/inc/lib/sieve/extensions/body.xml | 14 + .../extensions/comparator-ascii-numeric.xml | 7 + data/web/inc/lib/sieve/extensions/copy.xml | 9 + data/web/inc/lib/sieve/extensions/date.xml | 28 + .../inc/lib/sieve/extensions/editheader.xml | 22 + .../web/inc/lib/sieve/extensions/envelope.xml | 13 + .../inc/lib/sieve/extensions/environment.xml | 13 + data/web/inc/lib/sieve/extensions/ereject.xml | 11 + .../web/inc/lib/sieve/extensions/fileinto.xml | 9 + .../inc/lib/sieve/extensions/imap4flags.xml | 29 + .../inc/lib/sieve/extensions/imapflags.xml | 21 + data/web/inc/lib/sieve/extensions/index.xml | 17 + data/web/inc/lib/sieve/extensions/notify.xml | 29 + data/web/inc/lib/sieve/extensions/regex.xml | 11 + data/web/inc/lib/sieve/extensions/reject.xml | 11 + .../inc/lib/sieve/extensions/relational.xml | 14 + .../web/inc/lib/sieve/extensions/spamtest.xml | 11 + .../inc/lib/sieve/extensions/spamtestplus.xml | 12 + .../inc/lib/sieve/extensions/subaddress.xml | 8 + .../web/inc/lib/sieve/extensions/vacation.xml | 31 + .../inc/lib/sieve/extensions/variables.xml | 21 + .../inc/lib/sieve/extensions/virustest.xml | 11 + data/web/inc/lib/sieve/keywords.xml | 91 ++ data/web/inc/prerequisites.inc.php | 4 + data/web/inc/sessions.inc.php | 2 + data/web/inc/vars.inc.php | 3 + data/web/js/admin.js | 794 +++++++----------- data/web/js/api.js | 13 +- data/web/js/edit.js | 1 + data/web/js/mailbox.js | 135 ++- data/web/js/numberedtextarea.min.js | 1 + data/web/js/user.js | 47 +- data/web/json_api.php | 219 ++++- data/web/lang/lang.de.php | 24 +- data/web/lang/lang.en.php | 20 + data/web/mailbox.php | 36 +- data/web/modals/mailbox.php | 79 +- data/web/modals/user.php | 23 +- data/web/user.php | 7 + 65 files changed, 3460 insertions(+), 709 deletions(-) delete mode 100644 data/web/api.php create mode 100644 data/web/css/numberedtextarea.min.css create mode 100644 data/web/inc/ajax/sieve_validation.php create mode 100644 data/web/inc/ajax/sogo_ctrl.php create mode 100644 data/web/inc/ajax/syncjob_logs.php rename data/web/inc/{call_sogo_ctrl.php => functions.docker.inc.php} (63%) create mode 100644 data/web/inc/lib/sieve/SieveDumpable.php create mode 100644 data/web/inc/lib/sieve/SieveException.php create mode 100644 data/web/inc/lib/sieve/SieveKeywordRegistry.php create mode 100644 data/web/inc/lib/sieve/SieveParser.php create mode 100644 data/web/inc/lib/sieve/SieveScanner.php create mode 100644 data/web/inc/lib/sieve/SieveScript.php create mode 100644 data/web/inc/lib/sieve/SieveSemantics.php create mode 100644 data/web/inc/lib/sieve/SieveToken.php create mode 100644 data/web/inc/lib/sieve/SieveTree.php create mode 100644 data/web/inc/lib/sieve/extensions/body.xml create mode 100644 data/web/inc/lib/sieve/extensions/comparator-ascii-numeric.xml create mode 100644 data/web/inc/lib/sieve/extensions/copy.xml create mode 100644 data/web/inc/lib/sieve/extensions/date.xml create mode 100644 data/web/inc/lib/sieve/extensions/editheader.xml create mode 100644 data/web/inc/lib/sieve/extensions/envelope.xml create mode 100644 data/web/inc/lib/sieve/extensions/environment.xml create mode 100644 data/web/inc/lib/sieve/extensions/ereject.xml create mode 100644 data/web/inc/lib/sieve/extensions/fileinto.xml create mode 100644 data/web/inc/lib/sieve/extensions/imap4flags.xml create mode 100644 data/web/inc/lib/sieve/extensions/imapflags.xml create mode 100644 data/web/inc/lib/sieve/extensions/index.xml create mode 100644 data/web/inc/lib/sieve/extensions/notify.xml create mode 100644 data/web/inc/lib/sieve/extensions/regex.xml create mode 100644 data/web/inc/lib/sieve/extensions/reject.xml create mode 100644 data/web/inc/lib/sieve/extensions/relational.xml create mode 100644 data/web/inc/lib/sieve/extensions/spamtest.xml create mode 100644 data/web/inc/lib/sieve/extensions/spamtestplus.xml create mode 100644 data/web/inc/lib/sieve/extensions/subaddress.xml create mode 100644 data/web/inc/lib/sieve/extensions/vacation.xml create mode 100644 data/web/inc/lib/sieve/extensions/variables.xml create mode 100644 data/web/inc/lib/sieve/extensions/virustest.xml create mode 100644 data/web/inc/lib/sieve/keywords.xml create mode 100644 data/web/js/numberedtextarea.min.js diff --git a/data/web/admin.php b/data/web/admin.php index a2ec3d94..06af3db9 100644 --- a/data/web/admin.php +++ b/data/web/admin.php @@ -457,12 +457,11 @@ $tfa_data = get_tfa();
-
Postfix +
Postfix
- - + + +
@@ -475,12 +474,11 @@ $tfa_data = get_tfa();
-
Dovecot +
Dovecot
- - + + +
@@ -493,12 +491,11 @@ $tfa_data = get_tfa();
-
SOGo +
SOGo
- - + + +
@@ -511,12 +508,11 @@ $tfa_data = get_tfa();
-
Fail2ban +
Fail2ban
- - + + +
@@ -529,17 +525,16 @@ $tfa_data = get_tfa();
-
Rspamd history +
Rspamd history
- - + + +
-
+
@@ -547,12 +542,11 @@ $tfa_data = get_tfa();
-
Autodiscover +
Autodiscover
- - + + +
diff --git a/data/web/api.php b/data/web/api.php deleted file mode 100644 index d250929d..00000000 --- a/data/web/api.php +++ /dev/null @@ -1,32 +0,0 @@ -thead>tr.footable-filtering>th div.form-group{margin-bottom:0}table.footable,table.footable-details{position:relative;width:100%;border-spacing:0;border-collapse:collapse}table.footable-hide-fouc{display:none}table>tbody>tr>td>span.footable-toggle{margin-right:8px;opacity:.3}table>tbody>tr>td>span.footable-toggle.last-column{margin-left:8px;float:right}table.table-condensed>tbody>tr>td>span.footable-toggle{margin-right:5px}table.footable-details>tbody>tr>th:nth-child(1){min-width:40px;width:120px}table.footable-details>tbody>tr>td:nth-child(2){word-break:break-all}table.footable-details>tbody>tr:first-child>td,table.footable-details>tbody>tr:first-child>th,table.footable-details>tfoot>tr:first-child>td,table.footable-details>tfoot>tr:first-child>th,table.footable-details>thead>tr:first-child>td,table.footable-details>thead>tr:first-child>th{border-top-width:0}table.footable-details.table-bordered>tbody>tr:first-child>td,table.footable-details.table-bordered>tbody>tr:first-child>th,table.footable-details.table-bordered>tfoot>tr:first-child>td,table.footable-details.table-bordered>tfoot>tr:first-child>th,table.footable-details.table-bordered>thead>tr:first-child>td,table.footable-details.table-bordered>thead>tr:first-child>th{border-top-width:1px}div.footable-loader{vertical-align:middle;text-align:center;height:300px;position:relative}div.footable-loader>span.fooicon{display:inline-block;opacity:.3;font-size:30px;line-height:32px;width:32px;height:32px;margin-top:-16px;margin-left:-16px;position:absolute;top:50%;left:50%;-webkit-animation:fooicon-spin-r 2s infinite linear;animation:fooicon-spin-r 2s infinite linear}table.footable>tbody>tr.footable-empty>td{vertical-align:middle;text-align:center;font-size:30px}table.footable>tbody>tr>td,table.footable>tbody>tr>th{display:none}table.footable>tbody>tr.footable-detail-row>td,table.footable>tbody>tr.footable-detail-row>th,table.footable>tbody>tr.footable-empty>td,table.footable>tbody>tr.footable-empty>th{display:table-cell}@-webkit-keyframes fooicon-spin-r{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fooicon-spin-r{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fooicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings'!important;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fooicon:after,.fooicon:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.fooicon-loader:before{content:"\e030"}.fooicon-plus:before{content:"\2b"}.fooicon-minus:before{content:"\2212"}.fooicon-search:before{content:"\e003"}.fooicon-remove:before{content:"\e014"}.fooicon-sort:before{content:"\e150"}.fooicon-sort-asc:before{content:"\e155"}.fooicon-sort-desc:before{content:"\e156"}.fooicon-pencil:before{content:"\270f"}.fooicon-trash:before{content:"\e020"}.fooicon-eye-close:before{content:"\e106"}.fooicon-flash:before{content:"\e162"}.fooicon-cog:before{content:"\e019"}.fooicon-stats:before{content:"\e185"}table.footable>thead>tr.footable-filtering>th{border-bottom-width:1px;font-weight:400}table.footable.footable-filtering-right>thead>tr.footable-filtering>th,table.footable>thead>tr.footable-filtering>th{text-align:right}table.footable.footable-filtering-left>thead>tr.footable-filtering>th{text-align:left}table.footable-paging-center>tfoot>tr.footable-paging>td,table.footable.footable-filtering-center>thead>tr.footable-filtering>th,table.footable>tfoot>tr.footable-paging>td{text-align:center}table.footable>thead>tr.footable-filtering>th div.form-group+div.form-group{margin-top:5px}table.footable>thead>tr.footable-filtering>th div.input-group{width:100%}table.footable>thead>tr.footable-filtering>th ul.dropdown-menu>li>a.checkbox{margin:0;display:block;position:relative}table.footable>thead>tr.footable-filtering>th ul.dropdown-menu>li>a.checkbox>label{display:block;padding-left:20px}table.footable>thead>tr.footable-filtering>th ul.dropdown-menu>li>a.checkbox input[type=checkbox]{position:absolute;margin-left:-20px}@media (min-width:768px){table.footable>thead>tr.footable-filtering>th div.input-group{width:auto}table.footable>thead>tr.footable-filtering>th div.form-group{margin-left:2px;margin-right:2px}table.footable>thead>tr.footable-filtering>th div.form-group+div.form-group{margin-top:0}}table.footable>tbody>tr>td.footable-sortable,table.footable>tbody>tr>th.footable-sortable,table.footable>tfoot>tr>td.footable-sortable,table.footable>tfoot>tr>th.footable-sortable,table.footable>thead>tr>td.footable-sortable,table.footable>thead>tr>th.footable-sortable{position:relative;padding-right:30px;cursor:pointer}td.footable-sortable>span.fooicon,th.footable-sortable>span.fooicon{position:absolute;right:6px;top:50%;margin-top:-7px;opacity:0;transition:opacity .3s ease-in}td.footable-sortable.footable-asc>span.fooicon,td.footable-sortable.footable-desc>span.fooicon,td.footable-sortable:hover>span.fooicon,th.footable-sortable.footable-asc>span.fooicon,th.footable-sortable.footable-desc>span.fooicon,th.footable-sortable:hover>span.fooicon{opacity:1}table.footable-sorting-disabled td.footable-sortable.footable-asc>span.fooicon,table.footable-sorting-disabled td.footable-sortable.footable-desc>span.fooicon,table.footable-sorting-disabled td.footable-sortable:hover>span.fooicon,table.footable-sorting-disabled th.footable-sortable.footable-asc>span.fooicon,table.footable-sorting-disabled th.footable-sortable.footable-desc>span.fooicon,table.footable-sorting-disabled th.footable-sortable:hover>span.fooicon{opacity:0;visibility:hidden}table.footable>tfoot>tr.footable-paging>td>ul.pagination{margin:10px 0 0}table.footable>tfoot>tr.footable-paging>td>span.label{display:inline-block;margin:0 0 10px;padding:4px 10px}table.footable-paging-left>tfoot>tr.footable-paging>td{text-align:left}table.footable-editing-right td.footable-editing,table.footable-editing-right tr.footable-editing,table.footable-paging-right>tfoot>tr.footable-paging>td{text-align:right}ul.pagination>li.footable-page{display:none}ul.pagination>li.footable-page.visible{display:inline}td.footable-editing{width:90px;max-width:90px}table.footable-editing-no-delete td.footable-editing,table.footable-editing-no-edit td.footable-editing,table.footable-editing-no-view td.footable-editing{width:70px;max-width:70px}table.footable-editing-no-delete.footable-editing-no-view td.footable-editing,table.footable-editing-no-edit.footable-editing-no-delete td.footable-editing,table.footable-editing-no-edit.footable-editing-no-view td.footable-editing{width:50px;max-width:50px}table.footable-editing-no-edit.footable-editing-no-delete.footable-editing-no-view td.footable-editing,table.footable-editing-no-edit.footable-editing-no-delete.footable-editing-no-view th.footable-editing{width:0;max-width:0;display:none!important}table.footable-editing-left td.footable-editing,table.footable-editing-left tr.footable-editing{text-align:left}table.footable-editing button.footable-add,table.footable-editing button.footable-hide,table.footable-editing-show button.footable-show,table.footable-editing.footable-editing-always-show button.footable-hide,table.footable-editing.footable-editing-always-show button.footable-show,table.footable-editing.footable-editing-always-show.footable-editing-no-add tr.footable-editing{display:none}table.footable-editing.footable-editing-always-show button.footable-add,table.footable-editing.footable-editing-show button.footable-add,table.footable-editing.footable-editing-show button.footable-hide{display:inline-block} \ No newline at end of file +table.footable-details,table.footable>thead>tr.footable-filtering>th div.form-group{margin-bottom:0}table.footable,table.footable-details{position:relative;width:100%;border-spacing:0;border-collapse:collapse}table.footable-hide-fouc{display:none}table>tbody>tr>td>span.footable-toggle{margin-right:8px;opacity:.3}table>tbody>tr>td>span.footable-toggle.last-column{margin-left:8px;float:right}table.table-condensed>tbody>tr>td>span.footable-toggle{margin-right:5px}table.footable-details>tbody>tr>th:nth-child(1){min-width:40px;width:120px}table.footable-details>tbody>tr>td:nth-child(2){word-break:break-all}table.footable-details>tbody>tr:first-child>td,table.footable-details>tbody>tr:first-child>th,table.footable-details>tfoot>tr:first-child>td,table.footable-details>tfoot>tr:first-child>th,table.footable-details>thead>tr:first-child>td,table.footable-details>thead>tr:first-child>th{border-top-width:0}table.footable-details.table-bordered>tbody>tr:first-child>td,table.footable-details.table-bordered>tbody>tr:first-child>th,table.footable-details.table-bordered>tfoot>tr:first-child>td,table.footable-details.table-bordered>tfoot>tr:first-child>th,table.footable-details.table-bordered>thead>tr:first-child>td,table.footable-details.table-bordered>thead>tr:first-child>th{border-top-width:1px}div.footable-loader{vertical-align:middle;text-align:center;height:300px;position:relative}div.footable-loader>span.fooicon{display:inline-block;opacity:.3;font-size:30px;line-height:32px;width:32px;height:32px;margin-top:-16px;margin-left:-16px;position:absolute;top:50%;left:50%;-webkit-animation:fooicon-spin-r 2s infinite linear;animation:fooicon-spin-r 2s infinite linear}table.footable>tbody>tr.footable-empty>td{vertical-align:middle;text-align:center;font-size:30px}table.footable>tbody>tr>td,table.footable>tbody>tr>th{display:none}table.footable>tbody>tr.footable-detail-row>td,table.footable>tbody>tr.footable-detail-row>th,table.footable>tbody>tr.footable-empty>td,table.footable>tbody>tr.footable-empty>th{display:table-cell}@-webkit-keyframes fooicon-spin-r{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fooicon-spin-r{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fooicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings'!important;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fooicon:after,.fooicon:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.fooicon-loader:before{content:"\e030"}.fooicon-plus:before{content:"\2b"}.fooicon-minus:before{content:"\2212"}.fooicon-search:before{content:"\e003"}.fooicon-remove:before{content:"\e014"}.fooicon-sort:before{content:"\e150"}.fooicon-sort-asc:before{content:"\e155"}.fooicon-sort-desc:before{content:"\e156"}.fooicon-pencil:before{content:"\270f"}.fooicon-trash:before{content:"\e020"}.fooicon-eye-close:before{content:"\e106"}.fooicon-flash:before{content:"\e162"}.fooicon-cog:before{content:"\e019"}.fooicon-stats:before{content:"\e185"}table.footable>thead>tr.footable-filtering>th{border-bottom-width:1px;font-weight:400}table.footable.footable-filtering-right>thead>tr.footable-filtering>th,table.footable>thead>tr.footable-filtering>th{text-align:right}table.footable.footable-filtering-left>thead>tr.footable-filtering>th{text-align:left}table.footable-paging-center>tfoot>tr.footable-paging>td,table.footable.footable-filtering-center>thead>tr.footable-filtering>th,table.footable>tfoot>tr.footable-paging>td{text-align:center}table.footable>thead>tr.footable-filtering>th div.form-group+div.form-group{margin-top:5px}table.footable>thead>tr.footable-filtering>th div.input-group{width:100%}table.footable>thead>tr.footable-filtering>th ul.dropdown-menu>li>a.checkbox{margin:0;display:block;position:relative}table.footable>thead>tr.footable-filtering>th ul.dropdown-menu>li>a.checkbox>label{display:block;padding-left:20px}table.footable>thead>tr.footable-filtering>th ul.dropdown-menu>li>a.checkbox input[type=checkbox]{position:absolute;margin-left:-20px}@media (min-width:768px){table.footable>thead>tr.footable-filtering>th div.input-group{width:auto}table.footable>thead>tr.footable-filtering>th div.form-group{margin-left:2px;margin-right:2px}table.footable>thead>tr.footable-filtering>th div.form-group+div.form-group{margin-top:0}}table.footable>tbody>tr>td.footable-sortable,table.footable>tbody>tr>th.footable-sortable,table.footable>tfoot>tr>td.footable-sortable,table.footable>tfoot>tr>th.footable-sortable,table.footable>thead>tr>td.footable-sortable,table.footable>thead>tr>th.footable-sortable{position:relative;padding-right:30px;cursor:pointer}td.footable-sortable>span.fooicon,th.footable-sortable>span.fooicon{position:absolute;right:6px;top:50%;margin-top:-7px;opacity:0;transition:opacity .3s ease-in}td.footable-sortable.footable-asc>span.fooicon,td.footable-sortable.footable-desc>span.fooicon,td.footable-sortable:hover>span.fooicon,th.footable-sortable.footable-asc>span.fooicon,th.footable-sortable.footable-desc>span.fooicon,th.footable-sortable:hover>span.fooicon{opacity:1}table.footable-sorting-disabled td.footable-sortable.footable-asc>span.fooicon,table.footable-sorting-disabled td.footable-sortable.footable-desc>span.fooicon,table.footable-sorting-disabled td.footable-sortable:hover>span.fooicon,table.footable-sorting-disabled th.footable-sortable.footable-asc>span.fooicon,table.footable-sorting-disabled th.footable-sortable.footable-desc>span.fooicon,table.footable-sorting-disabled th.footable-sortable:hover>span.fooicon{opacity:0;visibility:hidden}table.footable>tfoot>tr.footable-paging>td>ul.pagination{margin:10px 0 0}table.footable>tfoot>tr.footable-paging>td>span.label{display:inline-block;margin:0 0 10px;padding:4px 10px}table.footable-paging-left>tfoot>tr.footable-paging>td{text-align:left}table.footable-editing-right td.footable-editing,table.footable-editing-right tr.footable-editing,table.footable-paging-right>tfoot>tr.footable-paging>td{text-align:right}ul.pagination>li.footable-page{display:none}ul.pagination>li.footable-page.visible{display:inline}td.footable-editing{width:90px;max-width:90px}table.footable-editing-no-delete td.footable-editing,table.footable-editing-no-edit td.footable-editing,table.footable-editing-no-view td.footable-editing{width:70px;max-width:70px}table.footable-editing-no-delete.footable-editing-no-view td.footable-editing,table.footable-editing-no-edit.footable-editing-no-delete td.footable-editing,table.footable-editing-no-edit.footable-editing-no-view td.footable-editing{width:50px;max-width:50px}table.footable-editing-no-edit.footable-editing-no-delete.footable-editing-no-view td.footable-editing,table.footable-editing-no-edit.footable-editing-no-delete.footable-editing-no-view th.footable-editing{width:0;max-width:0;display:none!important}table.footable-editing-left td.footable-editing,table.footable-editing-left tr.footable-editing{text-align:left}table.footable-editing button.footable-add,table.footable-editing button.footable-hide,table.footable-editing-show button.footable-show,table.footable-editing.footable-editing-always-show button.footable-hide,table.footable-editing.footable-editing-always-show button.footable-show,table.footable-editing.footable-editing-always-show.footable-editing-no-add tr.footable-editing{display:none}table.footable-editing.footable-editing-always-show button.footable-add,table.footable-editing.footable-editing-show button.footable-add,table.footable-editing.footable-editing-show button.footable-hide{display:inline-block} +table > tbody > tr > td > span.footable-toggle { + opacity: 0.7; +} \ No newline at end of file diff --git a/data/web/css/mailbox.css b/data/web/css/mailbox.css index 9f07debe..5004cda1 100644 --- a/data/web/css/mailbox.css +++ b/data/web/css/mailbox.css @@ -34,4 +34,4 @@ table.footable>tbody>tr.footable-empty>td { } .inputMissingAttr { border-color: #FF4136; -} +} \ No newline at end of file diff --git a/data/web/css/numberedtextarea.min.css b/data/web/css/numberedtextarea.min.css new file mode 100644 index 00000000..c147b16b --- /dev/null +++ b/data/web/css/numberedtextarea.min.css @@ -0,0 +1 @@ +div.numberedtextarea-wrapper{position:relative}div.numberedtextarea-wrapper textarea{display:block;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}div.numberedtextarea-line-numbers{position:absolute;top:0;left:0;right:0;bottom:0;width:50px;border-right:none;color:rgba(0,0,0,.4);overflow:hidden}div.numberedtextarea-number{padding-right:6px;text-align:right}textarea#script_data{font-family:Monospace} \ No newline at end of file diff --git a/data/web/css/user.css b/data/web/css/user.css index 07d4e745..e24bebe4 100644 --- a/data/web/css/user.css +++ b/data/web/css/user.css @@ -30,3 +30,10 @@ table.footable>tbody>tr.footable-empty>td { .inputMissingAttr { border-color: #FF4136; } +#logText { + white-space: pre-wrap; + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + word-wrap: break-word; +} \ No newline at end of file diff --git a/data/web/edit.php b/data/web/edit.php index fd6682ff..e06a0229 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -484,19 +484,25 @@ if (isset($_SESSION['mailcow_cc_role'])) {
-
- - -
-
- -
-
- +
+
+

Ratelimit

+
+
+
+ +
+
+ +
+
+ +
+
+

Filter

+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + diff --git a/data/web/inc/ajax/sieve_validation.php b/data/web/inc/ajax/sieve_validation.php new file mode 100644 index 00000000..d24a95dc --- /dev/null +++ b/data/web/inc/ajax/sieve_validation.php @@ -0,0 +1,23 @@ + 'danger', 'msg' => 'Script cannot be empty')); + exit(); + } + $sieve->parse($_GET['script']); + } + catch (Exception $e) { + echo json_encode(array('type' => 'danger', 'msg' => $e->getMessage())); + exit(); + } + echo json_encode(array('type' => 'success', 'msg' => $lang['add']['validation_success'])); +} +?> diff --git a/data/web/inc/ajax/sogo_ctrl.php b/data/web/inc/ajax/sogo_ctrl.php new file mode 100644 index 00000000..e238d9c0 --- /dev/null +++ b/data/web/inc/ajax/sogo_ctrl.php @@ -0,0 +1,39 @@ +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/ajax/syncjob_logs.php b/data/web/inc/ajax/syncjob_logs.php new file mode 100644 index 00000000..a0568167 --- /dev/null +++ b/data/web/inc/ajax/syncjob_logs.php @@ -0,0 +1,15 @@ + diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php index 8740612c..9246d230 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'; +