mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-08-19 21:43:18 +00:00
Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8259a21704 | ||
|
|
ba16c3563c | ||
|
|
d80b15b06c | ||
|
|
ed4fe41d7a | ||
|
|
6b9d875773 | ||
|
|
4d5b9d1c80 | ||
|
|
8abf453e1c | ||
|
|
8bd9e12a73 | ||
|
|
c665d430de | ||
|
|
2195a674ff | ||
|
|
089c44aee1 | ||
|
|
9cd16f0001 | ||
|
|
90ca1bf25a | ||
|
|
e5d0ed8c4c | ||
|
|
4cba448671 | ||
|
|
5408bc809d | ||
|
|
bd5c18b145 | ||
|
|
0f06c8e563 | ||
|
|
fffe1aec3d | ||
|
|
ddcce811c1 |
@@ -1,17 +1,17 @@
|
|||||||
FROM golang:1.25-bookworm AS builder
|
FROM golang:1.26-trixie AS builder
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
ENV CGO_ENABLED=0 \
|
ENV CGO_ENABLED=0 \
|
||||||
GO111MODULE=on \
|
GO111MODULE=on \
|
||||||
NOOPT=1 \
|
NOOPT=1 \
|
||||||
VERSION=1.8.22
|
VERSION=1.11.0
|
||||||
|
|
||||||
RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \
|
RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \
|
||||||
cd /src/postfix-tlspol && \
|
cd /src/postfix-tlspol && \
|
||||||
scripts/build.sh build-only
|
scripts/build.sh build-only
|
||||||
|
|
||||||
|
|
||||||
FROM debian:bookworm-slim
|
FROM debian:trixie-slim
|
||||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@version: 3.38
|
@version: 4.8
|
||||||
@include "scl.conf"
|
@include "scl.conf"
|
||||||
options {
|
options {
|
||||||
chain_hostnames(off);
|
chain_hostnames(off);
|
||||||
@@ -7,7 +7,7 @@ options {
|
|||||||
dns_cache(no);
|
dns_cache(no);
|
||||||
use_fqdn(no);
|
use_fqdn(no);
|
||||||
owner("root"); group("adm"); perm(0640);
|
owner("root"); group("adm"); perm(0640);
|
||||||
stats_freq(0);
|
stats(freq(0));
|
||||||
bad_hostname("^gconfd$");
|
bad_hostname("^gconfd$");
|
||||||
};
|
};
|
||||||
source s_src {
|
source s_src {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@version: 3.38
|
@version: 4.8
|
||||||
@include "scl.conf"
|
@include "scl.conf"
|
||||||
options {
|
options {
|
||||||
chain_hostnames(off);
|
chain_hostnames(off);
|
||||||
@@ -7,7 +7,7 @@ options {
|
|||||||
dns_cache(no);
|
dns_cache(no);
|
||||||
use_fqdn(no);
|
use_fqdn(no);
|
||||||
owner("root"); group("adm"); perm(0640);
|
owner("root"); group("adm"); perm(0640);
|
||||||
stats_freq(0);
|
stats(freq(0));
|
||||||
bad_hostname("^gconfd$");
|
bad_hostname("^gconfd$");
|
||||||
};
|
};
|
||||||
source s_src {
|
source s_src {
|
||||||
|
|||||||
@@ -32,8 +32,17 @@ function auth_password_verify(request, password)
|
|||||||
-- Returning PASSDB_RESULT_PASSWORD_MISMATCH will reset the user's auth cache entry.
|
-- Returning PASSDB_RESULT_PASSWORD_MISMATCH will reset the user's auth cache entry.
|
||||||
-- Returning PASSDB_RESULT_INTERNAL_FAILURE keeps the existing cache entry,
|
-- Returning PASSDB_RESULT_INTERNAL_FAILURE keeps the existing cache entry,
|
||||||
-- even if the TTL has expired. Useful to avoid cache eviction during backend issues.
|
-- even if the TTL has expired. Useful to avoid cache eviction during backend issues.
|
||||||
|
|
||||||
|
-- On a network-level failure (nginx unreachable, DNS failure, timeout) https.request
|
||||||
|
-- returns nil plus an error string, so c is not a numeric HTTP status code. Treat this
|
||||||
|
-- as a backend outage and keep the cache entry, rather than wiping it as a mismatch.
|
||||||
|
if type(c) ~= "number" then
|
||||||
|
dovecot.i_info("HTTP request to auth backend failed with " .. tostring(c) .. " for user " .. request.user)
|
||||||
|
return dovecot.auth.PASSDB_RESULT_INTERNAL_FAILURE, "Upstream unreachable"
|
||||||
|
end
|
||||||
|
|
||||||
if c ~= 200 and c ~= 401 then
|
if c ~= 200 and c ~= 401 then
|
||||||
dovecot.i_info("HTTP request failed with " .. c .. " for user " .. request.user)
|
dovecot.i_info("HTTP request failed with " .. tostring(c) .. " for user " .. request.user)
|
||||||
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Upstream error"
|
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Upstream error"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ smtp_sasl_auth_enable = yes
|
|||||||
smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf
|
smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf
|
||||||
smtp_sasl_security_options =
|
smtp_sasl_security_options =
|
||||||
smtp_sasl_mechanism_filter = plain, login
|
smtp_sasl_mechanism_filter = plain, login
|
||||||
smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf socketmap:inet:postfix-tlspol:8642:QUERY
|
smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf socketmap:inet:postfix-tlspol:8642:QUERYwithTLSRPT
|
||||||
smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre
|
smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre
|
||||||
mail_name = Postcow
|
mail_name = Postcow
|
||||||
# local_transport map catches local destinations and prevents routing local dests when the next map would route "*"
|
# local_transport map catches local destinations and prevents routing local dests when the next map would route "*"
|
||||||
|
|||||||
@@ -442,6 +442,16 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
|||||||
|
|
||||||
unset($record);
|
unset($record);
|
||||||
|
|
||||||
|
// Make a hostname RHS absolute so it is not read relative to $ORIGIN.
|
||||||
|
// Already-absolute names, the SRV root target "." and IP addresses are left alone.
|
||||||
|
$absolutize = function($host) {
|
||||||
|
$host = trim($host);
|
||||||
|
if ($host === '' || $host === '.' || substr($host, -1) === '.' || filter_var($host, FILTER_VALIDATE_IP)) {
|
||||||
|
return $host;
|
||||||
|
}
|
||||||
|
return $host . '.';
|
||||||
|
};
|
||||||
|
|
||||||
$dns_data = sprintf("\$ORIGIN %s.\n", $domain);
|
$dns_data = sprintf("\$ORIGIN %s.\n", $domain);
|
||||||
foreach ($records as $record) {
|
foreach ($records as $record) {
|
||||||
if ($domain == substr($record[0], -strlen($domain))) {
|
if ($domain == substr($record[0], -strlen($domain))) {
|
||||||
@@ -462,16 +472,26 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
|||||||
$val = str_replace(state_optional, '', $val);
|
$val = str_replace(state_optional, '', $val);
|
||||||
$val = str_replace(state_good, '', $val);
|
$val = str_replace(state_good, '', $val);
|
||||||
if (strlen($val) > 0) {
|
if (strlen($val) > 0) {
|
||||||
|
// these are all TXT values, their RHS is a character string, not a name
|
||||||
$vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val);
|
$vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if ($record[1] == 'MX' || $record[1] == 'CNAME') {
|
||||||
|
$val = $absolutize($val);
|
||||||
|
}
|
||||||
|
elseif ($record[1] == 'SRV') {
|
||||||
|
// format here is "target port"; only the target is a name
|
||||||
|
$parts = explode(' ', $val, 2);
|
||||||
|
$parts[0] = $absolutize($parts[0]);
|
||||||
|
$val = implode(' ', $parts);
|
||||||
|
}
|
||||||
$vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val);
|
$vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($vals as $val) {
|
foreach ($vals as $val) {
|
||||||
$dns_data .= str_replace($domain, $domain . '.', $val);
|
$dns_data .= $val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -633,6 +633,18 @@ function logger($_data = false) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function is_local_mailcow_domain($domain) {
|
||||||
|
// True if domain is a locally managed, active primary or alias domain
|
||||||
|
global $pdo;
|
||||||
|
$domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46);
|
||||||
|
if (empty($domain)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("SELECT 1 FROM `domain` WHERE `domain` = :d AND `active` = 1
|
||||||
|
UNION SELECT 1 FROM `alias_domain` WHERE `alias_domain` = :d2 AND `active` = 1 LIMIT 1");
|
||||||
|
$stmt->execute(array(':d' => $domain, ':d2' => $domain));
|
||||||
|
return (bool)$stmt->fetchColumn();
|
||||||
|
}
|
||||||
function hasDomainAccess($username, $role, $domain) {
|
function hasDomainAccess($username, $role, $domain) {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
if (empty($domain) || !is_valid_domain_name($domain)) {
|
if (empty($domain) || !is_valid_domain_name($domain)) {
|
||||||
@@ -3551,7 +3563,16 @@ function protect_route($allowed_roles = ['admin', 'domainadmin', 'user'], $redir
|
|||||||
if (isset($redirects['unauthenticated'])) {
|
if (isset($redirects['unauthenticated'])) {
|
||||||
header('Location: ' . $redirects['unauthenticated']);
|
header('Location: ' . $redirects['unauthenticated']);
|
||||||
} else {
|
} else {
|
||||||
header('Location: /');
|
// Send a deep link to the login page for its area instead of the user login at /,
|
||||||
|
// e.g. /admin/dashboard -> /admin rather than /
|
||||||
|
$request_uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
|
||||||
|
if (strpos($request_uri, '/admin/') === 0) {
|
||||||
|
header('Location: /admin');
|
||||||
|
} elseif (strpos($request_uri, '/domainadmin/') === 0) {
|
||||||
|
header('Location: /domainadmin');
|
||||||
|
} else {
|
||||||
|
header('Location: /');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -750,6 +750,18 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);
|
$goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);
|
||||||
$goto_local_part = strstr($goto, '@', true);
|
$goto_local_part = strstr($goto, '@', true);
|
||||||
$goto = $goto_local_part.'@'.$goto_domain;
|
$goto = $goto_local_part.'@'.$goto_domain;
|
||||||
|
// Deny external goto domains: global switch (all roles) overrides the per-DA ACL
|
||||||
|
if (($GLOBALS['ALIAS_DISABLE_EXTERNAL_DOMAINS'] === true ||
|
||||||
|
(isset($_SESSION['acl']['alias_external_goto']) && $_SESSION['acl']['alias_external_goto'] != "1")) &&
|
||||||
|
!is_local_mailcow_domain($goto_domain)) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => array('external_goto_denied', htmlspecialchars($goto))
|
||||||
|
);
|
||||||
|
unset($gotos[$i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
|
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
|
||||||
WHERE `kind` REGEXP 'location|thing|group'
|
WHERE `kind` REGEXP 'location|thing|group'
|
||||||
AND `username`= :goto");
|
AND `username`= :goto");
|
||||||
@@ -2715,6 +2727,19 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
unset($gotos[$i]);
|
unset($gotos[$i]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// Deny external goto domains: global switch (all roles) overrides the per-DA ACL
|
||||||
|
$goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);
|
||||||
|
if (($GLOBALS['ALIAS_DISABLE_EXTERNAL_DOMAINS'] === true ||
|
||||||
|
(isset($_SESSION['acl']['alias_external_goto']) && $_SESSION['acl']['alias_external_goto'] != "1")) &&
|
||||||
|
!is_local_mailcow_domain($goto_domain)) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => array('external_goto_denied', htmlspecialchars($goto))
|
||||||
|
);
|
||||||
|
unset($gotos[$i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if ($goto == $address) {
|
if ($goto == $address) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ function init_db_schema()
|
|||||||
try {
|
try {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
|
|
||||||
$db_version = "19022026_1220";
|
$db_version = "18082026_1200";
|
||||||
|
|
||||||
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
||||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
@@ -719,7 +719,8 @@ function init_db_schema()
|
|||||||
"alias_domains" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
"alias_domains" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||||
"mailbox_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
"mailbox_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||||
"domain_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
"domain_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||||
"domain_desc" => "TINYINT(1) NOT NULL DEFAULT '0'"
|
"domain_desc" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||||
|
"alias_external_goto" => "TINYINT(1) NOT NULL DEFAULT '1'"
|
||||||
),
|
),
|
||||||
"keys" => array(
|
"keys" => array(
|
||||||
"primary" => array(
|
"primary" => array(
|
||||||
|
|||||||
@@ -246,6 +246,10 @@ $PW_RESET_TOKEN_LIMIT = 3;
|
|||||||
// Maximum time in minutes a password reset token is valid
|
// Maximum time in minutes a password reset token is valid
|
||||||
$PW_RESET_TOKEN_LIFETIME = 15;
|
$PW_RESET_TOKEN_LIFETIME = 15;
|
||||||
|
|
||||||
|
// Globally forbid aliases with external (non-local) goto domains for ALL roles (incl. admins),
|
||||||
|
// overriding the per-domain-admin da_acl. false = defer to da_acl.
|
||||||
|
$ALIAS_DISABLE_EXTERNAL_DOMAINS = false;
|
||||||
|
|
||||||
// UV flag handling in FIDO2/WebAuthn - defaults to false to allow iOS logins
|
// UV flag handling in FIDO2/WebAuthn - defaults to false to allow iOS logins
|
||||||
// true = required
|
// true = required
|
||||||
// false = preferred
|
// false = preferred
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"acl": {
|
"acl": {
|
||||||
"alias_domains": "Alias-Domains hinzufügen",
|
"alias_domains": "Alias-Domains hinzufügen",
|
||||||
|
"alias_external_goto": "Aliase mit externen Ziel-Domains erlauben",
|
||||||
"app_passwds": "App-Passwörter verwalten",
|
"app_passwds": "App-Passwörter verwalten",
|
||||||
"bcc_maps": "BCC-Maps",
|
"bcc_maps": "BCC-Maps",
|
||||||
"delimiter_action": "Delimiter-Aktionen (tags)",
|
"delimiter_action": "Delimiter-Aktionen (tags)",
|
||||||
@@ -443,6 +444,7 @@
|
|||||||
"domain_not_found": "Domain %s nicht gefunden",
|
"domain_not_found": "Domain %s nicht gefunden",
|
||||||
"domain_quota_m_in_use": "Domain-Speicherplatzlimit muss größer oder gleich %d MiB sein",
|
"domain_quota_m_in_use": "Domain-Speicherplatzlimit muss größer oder gleich %d MiB sein",
|
||||||
"extended_sender_acl_denied": "Keine Rechte zum Setzen von externen Absenderadressen",
|
"extended_sender_acl_denied": "Keine Rechte zum Setzen von externen Absenderadressen",
|
||||||
|
"external_goto_denied": "Externe Ziel-Adresse %s ist nicht erlaubt",
|
||||||
"extra_acl_invalid": "Externe Absenderadresse \"%s\" ist ungültig",
|
"extra_acl_invalid": "Externe Absenderadresse \"%s\" ist ungültig",
|
||||||
"extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain",
|
"extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain",
|
||||||
"fido2_verification_failed": "FIDO2-Verifizierung fehlgeschlagen: %s",
|
"fido2_verification_failed": "FIDO2-Verifizierung fehlgeschlagen: %s",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"acl": {
|
"acl": {
|
||||||
"alias_domains": "Add alias domains",
|
"alias_domains": "Add alias domains",
|
||||||
|
"alias_external_goto": "Allow aliases with external goto domains",
|
||||||
"app_passwds": "Manage app passwords",
|
"app_passwds": "Manage app passwords",
|
||||||
"bcc_maps": "BCC maps",
|
"bcc_maps": "BCC maps",
|
||||||
"delimiter_action": "Delimiter action",
|
"delimiter_action": "Delimiter action",
|
||||||
@@ -444,6 +445,7 @@
|
|||||||
"domain_not_found": "Domain %s not found",
|
"domain_not_found": "Domain %s not found",
|
||||||
"domain_quota_m_in_use": "Domain quota must be greater or equal to %s MiB",
|
"domain_quota_m_in_use": "Domain quota must be greater or equal to %s MiB",
|
||||||
"extended_sender_acl_denied": "missing ACL to set external sender addresses",
|
"extended_sender_acl_denied": "missing ACL to set external sender addresses",
|
||||||
|
"external_goto_denied": "External goto address %s is not allowed",
|
||||||
"extra_acl_invalid": "External sender address \"%s\" is invalid",
|
"extra_acl_invalid": "External sender address \"%s\" is invalid",
|
||||||
"extra_acl_invalid_domain": "External sender \"%s\" uses an invalid domain",
|
"extra_acl_invalid_domain": "External sender \"%s\" uses an invalid domain",
|
||||||
"fido2_verification_failed": "FIDO2 verification failed: %s",
|
"fido2_verification_failed": "FIDO2 verification failed: %s",
|
||||||
|
|||||||
@@ -131,7 +131,7 @@
|
|||||||
"additional_rows": " righe aggiuntive inserite",
|
"additional_rows": " righe aggiuntive inserite",
|
||||||
"admin": "Amministratore",
|
"admin": "Amministratore",
|
||||||
"admin_details": "Modifica impostazioni amministratore",
|
"admin_details": "Modifica impostazioni amministratore",
|
||||||
"admin_domains": "Assengazioni di dominio",
|
"admin_domains": "Assegnazioni di dominio",
|
||||||
"admins": "Amministratori",
|
"admins": "Amministratori",
|
||||||
"admins_ldap": "Amministratori LDAP",
|
"admins_ldap": "Amministratori LDAP",
|
||||||
"advanced_settings": "Impostazioni avanzate",
|
"advanced_settings": "Impostazioni avanzate",
|
||||||
|
|||||||
@@ -785,7 +785,8 @@
|
|||||||
"internal": "Notranje",
|
"internal": "Notranje",
|
||||||
"internal_info": "Notranji vzdevki so dostopni samo iz lastne domene ali vzdevkov domen.",
|
"internal_info": "Notranji vzdevki so dostopni samo iz lastne domene ali vzdevkov domen.",
|
||||||
"sender_allowed": "Dovoli pošiljanje kot ta vzdevek",
|
"sender_allowed": "Dovoli pošiljanje kot ta vzdevek",
|
||||||
"sender_allowed_info": "Če je onemogočeno, lahko ta vzdevek samo prejema pošto. Za preglasitev in dodelitev dovoljenja za pošiljanje določenim poštnim predalom uporabite seznam za nadzor dostopa pošiljatelja."
|
"sender_allowed_info": "Če je onemogočeno, lahko ta vzdevek samo prejema pošto. Za preglasitev in dodelitev dovoljenja za pošiljanje določenim poštnim predalom uporabite seznam za nadzor dostopa pošiljatelja.",
|
||||||
|
"mta_sts_active_info": "Če ni označeno, pravilnik MTA-STS ne bo objavljen in za poddomeno mta-sts ne bo zahtevano nobeno potrdilo prek ACME."
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"restart_container_info": "<b>Pomembno:</b> Eleganten ponovni zagon lahko traja nekaj časa, zato počakajte, da se konča.",
|
"restart_container_info": "<b>Pomembno:</b> Eleganten ponovni zagon lahko traja nekaj časa, zato počakajte, da se konča.",
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-text"><i class="bi bi-lock-fill"></i></div>
|
<div class="input-group-text"><i class="bi bi-lock-fill"></i></div>
|
||||||
<input name="pass_user" type="password" id="pass_user" class="form-control" placeholder="{{ lang.login.password }}" required="" autocomplete="current-password">
|
<input name="pass_user" type="password" id="pass_user" class="form-control" placeholder="{{ lang.login.password }}" required="" autocomplete="current-password">
|
||||||
|
<button type="button" class="input-group-text" style="cursor:pointer;" onclick="var i=document.getElementById('pass_user'),ic=this.querySelector('i');if(i.type==='password'){i.type='text';ic.className='bi bi-eye-slash';}else{i.type='password';ic.className='bi bi-eye';}" tabindex="-1" aria-label="Toggle password visibility"><i class="bi bi-eye"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-between mt-4" style="position: relative">
|
<div class="d-flex justify-content-between mt-4" style="position: relative">
|
||||||
|
|||||||
@@ -473,6 +473,10 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
var msg = (data && data[0] && data[0].msg) ? data[0].msg : 'Password change failed.';
|
var msg = (data && data[0] && data[0].msg) ? data[0].msg : 'Password change failed.';
|
||||||
|
// the API returns raw language keys, resolve them like the alert box does
|
||||||
|
if (Object.prototype.hasOwnProperty.call(lang_danger, msg)) {
|
||||||
|
msg = lang_danger[msg];
|
||||||
|
}
|
||||||
$('#changePWAlert').show().text(msg);
|
$('#changePWAlert').show().text(msg);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-text"><i class="bi bi-lock-fill"></i></div>
|
<div class="input-group-text"><i class="bi bi-lock-fill"></i></div>
|
||||||
<input name="pass_user" type="password" id="pass_user" class="form-control" placeholder="{{ lang.login.password }}" required="" autocomplete="current-password">
|
<input name="pass_user" type="password" id="pass_user" class="form-control" placeholder="{{ lang.login.password }}" required="" autocomplete="current-password">
|
||||||
|
<button type="button" class="input-group-text" style="cursor:pointer;" onclick="var i=document.getElementById('pass_user'),ic=this.querySelector('i');if(i.type==='password'){i.type='text';ic.className='bi bi-eye-slash';}else{i.type='password';ic.className='bi bi-eye';}" tabindex="-1" aria-label="Toggle password visibility"><i class="bi bi-eye"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-between mt-4" style="position: relative">
|
<div class="d-flex justify-content-between mt-4" style="position: relative">
|
||||||
|
|||||||
@@ -267,6 +267,7 @@
|
|||||||
<small class="text-muted">{{ lang.admin.password_reset_info }}</small>
|
<small class="text-muted">{{ lang.admin.password_reset_info }}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div data-acl="{{ acl.extend_sender_acl }}" class="row mb-4">
|
<div data-acl="{{ acl.extend_sender_acl }}" class="row mb-4">
|
||||||
<label class="control-label col-sm-2" for="extended_sender_acl">{{ lang.edit.extended_sender_acl }}</label>
|
<label class="control-label col-sm-2" for="extended_sender_acl">{{ lang.edit.extended_sender_acl }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
@@ -279,7 +280,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label class="control-label col-sm-2" for="protocol_access">{{ lang.edit.allowed_protocols }}</label>
|
<label class="control-label col-sm-2" for="protocol_access">{{ lang.edit.allowed_protocols }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-text"><i class="bi bi-lock-fill"></i></div>
|
<div class="input-group-text"><i class="bi bi-lock-fill"></i></div>
|
||||||
<input name="pass_user" type="password" id="pass_user" class="form-control" placeholder="{{ lang.login.password }}" required="" autocomplete="current-password">
|
<input name="pass_user" type="password" id="pass_user" class="form-control" placeholder="{{ lang.login.password }}" required="" autocomplete="current-password">
|
||||||
|
<button type="button" class="input-group-text" style="cursor:pointer;" onclick="var i=document.getElementById('pass_user'),ic=this.querySelector('i');if(i.type==='password'){i.type='text';ic.className='bi bi-eye-slash';}else{i.type='password';ic.className='bi bi-eye';}" tabindex="-1" aria-label="Toggle password visibility"><i class="bi bi-eye"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 text-muted" style="font-size: 0.9rem;">
|
<div class="mt-2 text-muted" style="font-size: 0.9rem;">
|
||||||
|
|||||||
+1
-1
@@ -382,7 +382,7 @@ services:
|
|||||||
- postfix
|
- postfix
|
||||||
|
|
||||||
postfix-tlspol-mailcow:
|
postfix-tlspol-mailcow:
|
||||||
image: ghcr.io/mailcow/postfix-tlspol:1.8.23
|
image: ghcr.io/mailcow/postfix-tlspol:1.11.0
|
||||||
depends_on:
|
depends_on:
|
||||||
unbound-mailcow:
|
unbound-mailcow:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|||||||
Reference in New Issue
Block a user