1
0
mirror of https://github.com/mailcow/mailcow-dockerized.git synced 2025-12-13 18:06:01 +00:00

Merge pull request #6838 from mailcow/staging

Update 2025-10
This commit is contained in:
FreddleSpl0it
2025-10-15 08:11:08 +02:00
committed by GitHub
14 changed files with 203 additions and 55 deletions

View File

@@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
backend=iptables backend=nftables
nft list table ip filter &>/dev/null nft list table ip filter &>/dev/null
nftables_found=$? nftables_found=$?

View File

@@ -449,6 +449,11 @@ if __name__ == '__main__':
tables = NFTables(chain_name, logger) tables = NFTables(chain_name, logger)
else: else:
logger.logInfo('Using IPTables backend') logger.logInfo('Using IPTables backend')
logger.logWarn(
"DEPRECATION: iptables-legacy is deprecated and will be removed in future releases. "
"Please switch to nftables on your host to ensure complete compatibility."
)
time.sleep(5)
tables = IPTables(chain_name, logger) tables = IPTables(chain_name, logger)
clear() clear()

View File

@@ -1,5 +1,6 @@
import time import time
import json import json
import datetime
class Logger: class Logger:
def __init__(self): def __init__(self):
@@ -8,17 +9,28 @@ class Logger:
def set_redis(self, redis): def set_redis(self, redis):
self.r = redis self.r = redis
def _format_timestamp(self):
# Local time with milliseconds
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def log(self, priority, message): def log(self, priority, message):
tolog = {} # build redis-friendly dict
tolog['time'] = int(round(time.time())) tolog = {
tolog['priority'] = priority 'time': int(round(time.time())), # keep raw timestamp for Redis
tolog['message'] = message 'priority': priority,
print(message) 'message': message
}
# print human-readable message with timestamp
ts = self._format_timestamp()
print(f"{ts} {priority.upper()}: {message}", flush=True)
# also push JSON to Redis if connected
if self.r is not None: if self.r is not None:
try: try:
self.r.lpush('NETFILTER_LOG', json.dumps(tolog, ensure_ascii=False)) self.r.lpush('NETFILTER_LOG', json.dumps(tolog, ensure_ascii=False))
except Exception as ex: except Exception as ex:
print('Failed logging to redis: %s' % (ex)) print(f'{ts} WARN: Failed logging to redis: {ex}', flush=True)
def logWarn(self, message): def logWarn(self, message):
self.log('warn', message) self.log('warn', message)
@@ -27,4 +39,4 @@ class Logger:
self.log('crit', message) self.log('crit', message)
def logInfo(self, message): def logInfo(self, message):
self.log('info', message) self.log('info', message)

View File

@@ -3,11 +3,11 @@ FROM php:8.2-fpm-alpine3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?<version>.*)$ # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?<version>.*)$
ARG APCU_PECL_VERSION=5.1.26 ARG APCU_PECL_VERSION=5.1.27
# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$ # renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$
ARG IMAGICK_PECL_VERSION=3.8.0 ARG IMAGICK_PECL_VERSION=3.8.0
# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$ # renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$
ARG MAILPARSE_PECL_VERSION=3.1.8 ARG MAILPARSE_PECL_VERSION=3.1.9
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$ # renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$
ARG MEMCACHED_PECL_VERSION=3.3.0 ARG MEMCACHED_PECL_VERSION=3.3.0
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$ # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$

View File

@@ -2,7 +2,7 @@ FROM debian:bookworm-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
ARG RSPAMD_VER=rspamd_3.12.1-1~6dbfca2fa ARG RSPAMD_VER=rspamd_3.13.2-1~8bf602278
ARG CODENAME=bookworm ARG CODENAME=bookworm
ENV LC_ALL=C ENV LC_ALL=C
@@ -14,8 +14,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
dnsutils \ dnsutils \
netcat-traditional \ netcat-traditional \
wget \ wget \
redis-tools \ redis-tools \
procps \ procps \
nano \ nano \
lua-cjson \ lua-cjson \
&& arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \ && arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \

View File

@@ -1,7 +1,15 @@
; NOTE: Restart phpfpm on ANY manual changes to PHP files!
; opcache
opcache.enable=1 opcache.enable=1
opcache.enable_cli=1 opcache.enable_cli=1
opcache.interned_strings_buffer=16 opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000 opcache.max_accelerated_files=10000
opcache.memory_consumption=128 opcache.memory_consumption=128
opcache.save_comments=1 opcache.save_comments=1
opcache.revalidate_freq=1 opcache.revalidate_freq=120
opcache.validate_timestamps=0
; JIT
opcache.jit=1255
opcache.jit_buffer_size=8M

View File

@@ -7,6 +7,8 @@ if(file_exists('inc/vars.local.inc.php')) {
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php';
$default_autodiscover_config = $autodiscover_config; $default_autodiscover_config = $autodiscover_config;
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config); $autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);

View File

@@ -1006,7 +1006,7 @@ function edit_user_account($_data) {
update_sogo_static_view(); update_sogo_static_view();
} }
// edit password recovery email // edit password recovery email
elseif (isset($pw_recovery_email)) { elseif (!empty($password_old) && isset($pw_recovery_email)) {
if (!isset($_SESSION['acl']['pw_reset']) || $_SESSION['acl']['pw_reset'] != "1" ) { if (!isset($_SESSION['acl']['pw_reset']) || $_SESSION['acl']['pw_reset'] != "1" ) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
@@ -1016,6 +1016,21 @@ function edit_user_account($_data) {
return false; return false;
} }
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `username` = :user AND authsource = 'mailcow'");
$stmt->execute(array(':user' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!verify_hash($row['password'], $password_old)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$pw_recovery_email = (!filter_var($pw_recovery_email, FILTER_VALIDATE_EMAIL)) ? '' : $pw_recovery_email; $pw_recovery_email = (!filter_var($pw_recovery_email, FILTER_VALIDATE_EMAIL)) ? '' : $pw_recovery_email;
$stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email) $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email)
WHERE `username` = :username AND authsource = 'mailcow'"); WHERE `username` = :username AND authsource = 'mailcow'");

View File

@@ -12,7 +12,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
$user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']); $user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']);
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false; $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual && getenv('SKIP_SOGO') != "y") { if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual && getenv('SKIP_SOGO') != "y") {
header("Location: /SOGo/so/{$_SESSION['mailcow_cc_username']}"); header("Location: /SOGo/so/");
} else { } else {
header("Location: /user"); header("Location: /user");
} }

View File

@@ -97,7 +97,7 @@ jQuery(function($){
var datetime = new Date(item.datetime.replace(/-/g, "/")); var datetime = new Date(item.datetime.replace(/-/g, "/"));
var local_datetime = datetime.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}); var local_datetime = datetime.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
var service = '<div class="badge bg-secondary">' + item.service.toUpperCase() + '</div>'; var service = '<div class="badge bg-secondary">' + item.service.toUpperCase() + '</div>';
var app_password = item.app_password ? ' <a href="/edit/app-passwd/' + item.app_password + '"><i class="bi bi-app-indicator"></i> ' + escapeHtml(item.app_password_name || "App") + '</a>' : ''; var app_password = item.app_password ? ' <a href="/edit/app-passwd/' + item.app_password + '"><i class="bi bi-key-fill"></i><span class="ms-1">' + escapeHtml(item.app_password_name || "App") + '</span></a>' : '';
var real_rip = item.real_rip.startsWith("Web") ? item.real_rip : '<a href="https://bgp.tools/prefix/' + item.real_rip + '" target="_blank">' + item.real_rip + "</a>"; var real_rip = item.real_rip.startsWith("Web") ? item.real_rip : '<a href="https://bgp.tools/prefix/' + item.real_rip + '" target="_blank">' + item.real_rip + "</a>";
var ip_location = item.location ? ' <span class="flag-icon flag-icon-' + item.location.toLowerCase() + '"></span>' : ''; var ip_location = item.location ? ' <span class="flag-icon flag-icon-' + item.location.toLowerCase() + '"></span>' : '';
var ip_data = real_rip + ip_location + app_password; var ip_data = real_rip + ip_location + app_password;
@@ -105,10 +105,9 @@ jQuery(function($){
$(".last-sasl-login").append(` $(".last-sasl-login").append(`
<li class="list-group-item d-flex justify-content-between align-items-start"> <li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto d-flex flex-column"> <div class="ms-2 me-auto d-flex flex-column">
<div class="fw-bold">` + real_rip + `</div> <div class="fw-bold">` + ip_location + real_rip + `</div>
<small class="fst-italic mt-2">` + service + ` ` + local_datetime + `</small> <small class="fst-italic mt-2">` + service + ` ` + local_datetime + `</small>` + app_password + `
</div> </div>
<span>` + ip_location + `</span>
</li> </li>
`); `);
}) })

View File

@@ -1,7 +1,7 @@
{ {
"acl": { "acl": {
"alias_domains": "Adicionar domínios alias", "alias_domains": "Adicionar alias de domínios",
"app_passwds": "Gerenciar senhas de aplicativos", "app_passwds": "Gerenciar senhas de app",
"bcc_maps": "Mapas BCC", "bcc_maps": "Mapas BCC",
"delimiter_action": "Ação delimitadora", "delimiter_action": "Ação delimitadora",
"domain_desc": "Alterar descrição do domínio", "domain_desc": "Alterar descrição do domínio",
@@ -9,7 +9,7 @@
"eas_reset": "Redefinir dispositivos EAS", "eas_reset": "Redefinir dispositivos EAS",
"extend_sender_acl": "Permitir estender a ACL do remetente por endereços externos", "extend_sender_acl": "Permitir estender a ACL do remetente por endereços externos",
"filters": "Filtros", "filters": "Filtros",
"login_as": "Faça login como usuário da mailbox", "login_as": "Fazer login como usuário da mailbox",
"mailbox_relayhost": "Alterar relayhost para uma mailbox", "mailbox_relayhost": "Alterar relayhost para uma mailbox",
"prohibited": "Proibido pela ACL", "prohibited": "Proibido pela ACL",
"protocol_access": "Alterar o acesso ao protocolo", "protocol_access": "Alterar o acesso ao protocolo",
@@ -109,7 +109,9 @@
"username": "Nome de usuário", "username": "Nome de usuário",
"validate": "Validar", "validate": "Validar",
"validation_success": "Validado com sucesso", "validation_success": "Validado com sucesso",
"dry": "Simular sincronização" "dry": "Simular sincronização",
"internal": "Interno",
"internal_info": "Aliases internos são acessíveis apenas a partir do próprio domínio ou alias de domínio."
}, },
"admin": { "admin": {
"access": "Acesso", "access": "Acesso",
@@ -364,7 +366,52 @@
"iam_client_secret": "Senha de cliente", "iam_client_secret": "Senha de cliente",
"iam_auth_flow": "Fluxo de autenticação", "iam_auth_flow": "Fluxo de autenticação",
"iam_client_scopes": "Escopo do cliente", "iam_client_scopes": "Escopo do cliente",
"iam_default_template": "Template Padrão" "iam_default_template": "Template Padrão",
"admin_quicklink": "Ocultar link rápido para página de login do administrador",
"app_hide": "Ocultar para login",
"login_page": "Página de login",
"domainadmin_quicklink": "Ocultar link rápido para página de login do administrador de domínio",
"filter": "Filtro",
"force_sso_text": "Se um provedor OIDC externo for configurado, esta opção oculta os formulários de login padrão do mailcow e mostra apenas o botão de single sign-on",
"force_sso": "Desabilitar login do mailcow e mostrar apenas single sign-on",
"iam": "Provedor de identidade",
"iam_attribute_field": "Campo de atributo",
"iam_authorize_url": "Endpoint de autorização",
"iam_auth_flow_info": "Além do fluxo de código de autorização (fluxo padrão no Keycloak), que é usado para login de single sign-on, o mailcow também suporta fluxo de autenticação com credenciais diretas. O fluxo Mailpassword tenta validar as credenciais do usuário usando a API REST do administrador do Keycloak. O mailcow recupera a senha hash do atributo <code>mailcow_password</code>, que é mapeado no Keycloak.",
"iam_basedn": "DN base",
"iam_default_template_description": "Se nenhum template for atribuído a um usuário, o template padrão será usado para criar a caixa de correio, mas não para atualizar a caixa de correio.",
"iam_description": "Configure um provedor externo para autenticação<br>As caixas de correio dos usuários serão criadas automaticamente no primeiro login, desde que um mapeamento de atributos tenha sido definido.",
"iam_extra_permission": "Para que as configurações a seguir funcionem, o cliente mailcow no Keycloak precisa de uma <code>conta de serviço</code> e a permissão para <code>visualizar usuários</code>.",
"iam_host": "Host",
"iam_host_info": "Digite um ou mais hosts LDAP, separados por vírgulas.",
"iam_import_users": "Importar usuários",
"iam_login_provisioning": "Criar usuários automaticamente no login",
"iam_mapping": "Mapeamento de atributos",
"iam_bindpass": "Senha de vinculação",
"iam_periodic_full_sync": "Sincronização completa periódica",
"iam_port": "Porta",
"iam_realm": "Realm",
"iam_redirect_url": "URL de redirecionamento",
"iam_rest_flow": "Fluxo Mailpassword",
"iam_server_url": "URL do servidor",
"iam_sso": "Single sign-on",
"iam_sync_interval": "Intervalo de sincronização/importação (min)",
"iam_test_connection": "Testar conexão",
"iam_token_url": "Endpoint de token",
"iam_userinfo_url": "Endpoint de informações do usuário",
"iam_username_field": "Campo de nome de usuário",
"iam_binddn": "DN de vinculação",
"iam_use_ssl": "Usar SSL",
"iam_use_ssl_info": "Se habilitar SSL e a porta estiver definida como 389, ela será automaticamente substituída para usar 636.",
"iam_use_tls": "Usar StartTLS",
"iam_use_tls_info": "Se habilitar TLS, você deve usar a porta padrão para seu servidor LDAP (389). Portas SSL não podem ser usadas.",
"iam_version": "Versão",
"ignore_ssl_error": "Ignorar erros SSL",
"needs_restart": "precisa reiniciar",
"quicklink_text": "Mostrar ou ocultar links rápidos para outras páginas de login abaixo do formulário de login",
"task": "Tarefa",
"user_link": "Link do usuário",
"user_quicklink": "Ocultar link rápido para página de login do usuário"
}, },
"danger": { "danger": {
"access_denied": "Acesso negado ou dados de formulário inválidos", "access_denied": "Acesso negado ou dados de formulário inválidos",
@@ -501,7 +548,15 @@
"username_invalid": "O nome de usuário %s não pode ser usado", "username_invalid": "O nome de usuário %s não pode ser usado",
"validity_missing": "Por favor, atribua um período de validade", "validity_missing": "Por favor, atribua um período de validade",
"value_missing": "Forneça todos os valores", "value_missing": "Forneça todos os valores",
"yotp_verification_failed": "Falha na verificação do Yubico OTP: %s" "yotp_verification_failed": "Falha na verificação do Yubico OTP: %s",
"authsource_in_use": "O provedor de identidade não pode ser alterado ou excluído pois está sendo usado por um ou mais usuários.",
"generic_server_error": "Ocorreu um erro inesperado no servidor. Entre em contato com seu administrador.",
"iam_test_connection": "Falha na conexão",
"max_age_invalid": "Idade máxima %s é inválida",
"mode_invalid": "Modo %s é inválido",
"mx_invalid": "Registro MX %s é inválido",
"required_data_missing": "Dados obrigatórios %s estão ausentes",
"version_invalid": "Versão %s é inválida"
}, },
"datatables": { "datatables": {
"collapse_all": "Recolher tudo", "collapse_all": "Recolher tudo",
@@ -708,7 +763,25 @@
"title": "Editar objeto", "title": "Editar objeto",
"unchanged_if_empty": "Se inalterado, deixe em branco", "unchanged_if_empty": "Se inalterado, deixe em branco",
"username": "Nome de usuário", "username": "Nome de usuário",
"validate_save": "Valide e salve" "validate_save": "Validar e salvar",
"internal": "Interno",
"internal_info": "Aliases internos são acessíveis apenas a partir do próprio domínio ou domínios alias.",
"mailbox_rename": "Renomear caixa de correio",
"mailbox_rename_agree": "Eu criei um backup.",
"mailbox_rename_warning": "IMPORTANTE! Crie um backup antes de renomear a caixa de correio.",
"mailbox_rename_alias": "Criar alias automaticamente",
"mailbox_rename_title": "Novo nome da caixa de correio local",
"mta_sts": "MTA-STS",
"mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> é um padrão que força a entrega de email entre servidores de email para usar TLS com certificados válidos. <br>É usado quando <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> não é possível devido ao DNSSEC ausente ou não suportado.<br><b>Nota</b>: Se o domínio de recepção suporta DANE com DNSSEC, DANE é <b>sempre</b> preferido MTA-STS atua apenas como fallback.",
"mta_sts_version": "Versão",
"mta_sts_version_info": "Define a versão do padrão MTA-STS atualmente apenas <code>STSv1</code> é válido.",
"mta_sts_mode": "Modo",
"mta_sts_mode_info": "Há três modos para escolher:<ul><li><em>testing</em> política é apenas monitorada, violações não têm impacto.</li><li><em>enforce</em> política é rigorosamente aplicada, conexões sem TLS válido são rejeitadas.</li><li><em>none</em> política é publicada mas não aplicada.</li></ul>",
"mta_sts_max_age": "Idade máxima",
"mta_sts_max_age_info": "Tempo em segundos que servidores de email de recepção podem armazenar esta política em cache até buscar novamente.",
"mta_sts_mx": "Servidor MX",
"mta_sts_mx_info": "Permite envio apenas para nomes de host de servidor de email explicitamente listados; o MTA de envio verifica se o nome do host DNS MX corresponde à lista de políticas e permite entrega apenas com certificado TLS válido (protege contra MITM).",
"mta_sts_mx_notice": "Múltiplos servidores MX podem ser especificados (separados por vírgulas)."
}, },
"fido2": { "fido2": {
"confirm": "Confirme", "confirm": "Confirme",
@@ -771,7 +844,15 @@
"password": "Senha", "password": "Senha",
"reset_password": "Recuperar a senha", "reset_password": "Recuperar a senha",
"request_reset_password": "Solicitar troca de senha", "request_reset_password": "Solicitar troca de senha",
"username": "Nome de usuário" "username": "Nome de usuário",
"login_linkstext": "Login incorreto?",
"login_usertext": "Entrar como usuário",
"login_domainadmintext": "Entrar como administrador de domínio",
"login_admintext": "Entrar como administrador",
"login_user": "Login de usuário",
"login_dadmin": "Login como administrador de domínio",
"login_admin": "Login como administrador",
"email": "Endereço de email"
}, },
"mailbox": { "mailbox": {
"action": "Ação", "action": "Ação",
@@ -946,7 +1027,9 @@
"username": "Nome de usuário", "username": "Nome de usuário",
"waiting": "Esperando", "waiting": "Esperando",
"weekly": "Semanalmente", "weekly": "Semanalmente",
"yes": "✓" "yes": "✓",
"iam": "Provedor de Identidade",
"internal": "Interno"
}, },
"oauth2": { "oauth2": {
"access_denied": "Faça login como proprietário da mailbox para conceder acesso via OAuth2.", "access_denied": "Faça login como proprietário da mailbox para conceder acesso via OAuth2.",
@@ -961,8 +1044,8 @@
"action": "Ação", "action": "Ação",
"atts": "Anexos", "atts": "Anexos",
"check_hash": "Arquivo de pesquisa hash @ VT", "check_hash": "Arquivo de pesquisa hash @ VT",
"confirm": "Confirme", "confirm": "Confirmar",
"confirm_delete": "Confirme a exclusão desse elemento.", "confirm_delete": "Confirmar exclusão desse elemento.",
"danger": "Perigo", "danger": "Perigo",
"deliver_inbox": "Entregar na caixa de entrada", "deliver_inbox": "Entregar na caixa de entrada",
"disabled_by_config": "A configuração atual do sistema desativa a funcionalidade de quarentena. Defina “retenções por mailbox” e um “tamanho máximo” para os elementos de quarentena.", "disabled_by_config": "A configuração atual do sistema desativa a funcionalidade de quarentena. Defina “retenções por mailbox” e um “tamanho máximo” para os elementos de quarentena.",
@@ -1123,12 +1206,15 @@
"verified_fido2_login": "Login FIDO2 verificado", "verified_fido2_login": "Login FIDO2 verificado",
"verified_totp_login": "Login TOTP verificado", "verified_totp_login": "Login TOTP verificado",
"verified_webauthn_login": "Login verificado do WebAuthn", "verified_webauthn_login": "Login verificado do WebAuthn",
"verified_yotp_login": "Login OTP verificado do Yubico" "verified_yotp_login": "Login OTP verificado do Yubico",
"custom_login_modified": "Personalização de login foi salva com sucesso",
"iam_test_connection": "Conexão bem-sucedida",
"mailbox_renamed": "Caixa de correio foi renomeada de %s para %s"
}, },
"tfa": { "tfa": {
"authenticators": "Autenticadores", "authenticators": "Autenticadores",
"api_register": "%s usa a API Yubico Cloud. Obtenha uma chave de API para sua chave <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">aqui</a>", "api_register": "%s usa a API Yubico Cloud. Obtenha uma chave de API para sua chave <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">aqui</a>",
"confirm": "Confirme", "confirm": "Confirmar",
"confirm_totp_token": "Confirme suas alterações inserindo o token gerado", "confirm_totp_token": "Confirme suas alterações inserindo o token gerado",
"delete_tfa": "Desativar o TFA", "delete_tfa": "Desativar o TFA",
"disable_tfa": "Desative o TFA até o próximo login bem-sucedido", "disable_tfa": "Desative o TFA até o próximo login bem-sucedido",
@@ -1141,7 +1227,7 @@
"reload_retry": "- (recarregue o navegador se o erro persistir)", "reload_retry": "- (recarregue o navegador se o erro persistir)",
"scan_qr_code": "Escaneie o código a seguir com seu aplicativo autenticador ou insira o código manualmente.", "scan_qr_code": "Escaneie o código a seguir com seu aplicativo autenticador ou insira o código manualmente.",
"select": "Por favor, selecione", "select": "Por favor, selecione",
"set_tfa": "Defina o método de autenticação de dois fatores", "set_tfa": "Método de autenticação de dois fatores",
"start_webauthn_validation": "Iniciar validação", "start_webauthn_validation": "Iniciar validação",
"tfa": "Autenticação de dois fatores", "tfa": "Autenticação de dois fatores",
"tfa_token_invalid": "Token TFA inválido", "tfa_token_invalid": "Token TFA inválido",
@@ -1318,7 +1404,11 @@
"weeks": "semanas", "weeks": "semanas",
"with_app_password": "com senha do aplicativo", "with_app_password": "com senha do aplicativo",
"year": "ano", "year": "ano",
"years": "anos" "years": "anos",
"authentication": "Autenticação",
"overview": "Visão geral",
"protocols": "Protocolos",
"tfa_info": "A autenticação de dois fatores ajuda a proteger sua conta. Se você habilitá-la, precisará de senhas de aplicativo para fazer login em aplicativos ou serviços que não suportam autenticação de dois fatores (por exemplo, clientes de email)."
}, },
"warning": { "warning": {
"cannot_delete_self": "Não é possível excluir o usuário conectado", "cannot_delete_self": "Não é possível excluir o usuário conectado",

View File

@@ -24,7 +24,7 @@
"sogo_access": "允许管理 SOGo 访问权限", "sogo_access": "允许管理 SOGo 访问权限",
"sogo_profile_reset": "重置 SOGo 个人资料", "sogo_profile_reset": "重置 SOGo 个人资料",
"spam_alias": "临时别名", "spam_alias": "临时别名",
"spam_policy": "名单/名单", "spam_policy": "阻止名单/允许名单",
"spam_score": "垃圾邮件分数", "spam_score": "垃圾邮件分数",
"syncjobs": "同步任务", "syncjobs": "同步任务",
"tls_policy": "TLS 策略", "tls_policy": "TLS 策略",
@@ -149,7 +149,7 @@
"arrival_time": "到达时间 (服务器时间)", "arrival_time": "到达时间 (服务器时间)",
"authed_user": "已认证用户", "authed_user": "已认证用户",
"ays": "确定继续操作?", "ays": "确定继续操作?",
"ban_list_info": "以下为被封禁的 IP 列表: <b>网络 (剩余封禁时间) - [操作]</b>。<br />被取消封禁的 IP 将会在几秒之内从封禁列表中移除<br />红色标签表示因名单而导致的永久封禁。", "ban_list_info": "以下为被封禁的 IP 列表: <b>网络 (剩余封禁时间) - [操作]</b>。<br />被取消封禁的 IP 将会在几秒之内从封禁列表中移除<br />红色标签表示因阻止名单而导致的永久封禁。",
"change_logo": "更改 Logo", "change_logo": "更改 Logo",
"configuration": "配置", "configuration": "配置",
"convert_html_to_text": "将 HTML 转换为纯文本内容", "convert_html_to_text": "将 HTML 转换为纯文本内容",
@@ -181,16 +181,16 @@
"empty": "结果为空", "empty": "结果为空",
"excludes": "除了", "excludes": "除了",
"f2b_ban_time": "封禁时间 (秒)", "f2b_ban_time": "封禁时间 (秒)",
"f2b_blacklist": "网络/主机名单", "f2b_blacklist": "网络/主机阻止名单",
"f2b_filter": "正则表达式过滤器", "f2b_filter": "正则表达式过滤器",
"f2b_list_info": "名单的优先级总是高于名单。 <b>列表更新将会在几秒之后完成。</b>", "f2b_list_info": "阻止名单的优先级总是高于允许名单。 <b>列表更新将会在几秒之后完成。</b>",
"f2b_max_attempts": "最多尝试次数", "f2b_max_attempts": "最多尝试次数",
"f2b_netban_ipv4": "应用封禁的 IPv4 子网大小 (8-32)", "f2b_netban_ipv4": "应用封禁的 IPv4 子网大小 (8-32)",
"f2b_netban_ipv6": "应用封禁的 IPv6 子网大小 (8-128)", "f2b_netban_ipv6": "应用封禁的 IPv6 子网大小 (8-128)",
"f2b_parameters": "Fail2ban 参数", "f2b_parameters": "Fail2ban 参数",
"f2b_regex_info": "将会过滤这些应用的日志: SOGoPostfixDovecot 和 PHP-FPM。", "f2b_regex_info": "将会过滤这些应用的日志: SOGoPostfixDovecot 和 PHP-FPM。",
"f2b_retry_window": "最多尝试次数重试窗口 (秒)", "f2b_retry_window": "最多尝试次数重试窗口 (秒)",
"f2b_whitelist": "网络/主机名单", "f2b_whitelist": "网络/主机允许名单",
"filter_table": "筛选表格", "filter_table": "筛选表格",
"forwarding_hosts": "转发主机", "forwarding_hosts": "转发主机",
"forwarding_hosts_add_hint": "你可以指定 IPv4/IPv6 地址、CIDR 表示的网络、主机名 (解析为 IP 地址),或者邮箱域名 (查询 SPF 记录或 MX 记录并解析为 IP 地址)。", "forwarding_hosts_add_hint": "你可以指定 IPv4/IPv6 地址、CIDR 表示的网络、主机名 (解析为 IP 地址),或者邮箱域名 (查询 SPF 记录或 MX 记录并解析为 IP 地址)。",
@@ -298,8 +298,8 @@
"rspamd_com_settings": "设置名称将会自动生成,请看参考下方的示例预设。查看<a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">Rspamd 文档</a>以了解更多的细节", "rspamd_com_settings": "设置名称将会自动生成,请看参考下方的示例预设。查看<a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">Rspamd 文档</a>以了解更多的细节",
"rspamd_global_filters": "全局过滤规则", "rspamd_global_filters": "全局过滤规则",
"rspamd_global_filters_agree": "我会小心谨慎的!", "rspamd_global_filters_agree": "我会小心谨慎的!",
"rspamd_global_filters_info": "全局过滤规则包含了不同类型的全局名单和名单。", "rspamd_global_filters_info": "全局过滤规则包含了不同类型的全局阻止名单和允许名单。",
"rspamd_global_filters_regex": "它们的名字解释了它们的用途。所有内容必须包含 \"/pattern/options\" 格式的合法表达式 (例如 <code>/.+@domain\\.tld/i</code>)。<br>\r\n 因为仅对正则表达式执行了基本的检查Rspamd 的功能仍可能因正则表达式语法问题出现错误。<br>\r\n Rspamd 会在规则更改后读取其内容。 如果你遇到了问题,<a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">重启 Rspamd</a> 以强制重载规则。<br>名单中的项目会被系统排除。", "rspamd_global_filters_regex": "它们的名字解释了它们的用途。所有内容必须包含 \"/pattern/options\" 格式的合法表达式 (例如 <code>/.+@domain\\.tld/i</code>)。<br>\n 因为仅对正则表达式执行了基本的检查Rspamd 的功能仍可能因正则表达式语法问题出现错误。<br>\n Rspamd 会在规则更改后读取其内容。 如果你遇到了问题,<a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">重启 Rspamd</a> 以强制重载规则。<br>阻止名单中的项目会被系统排除。",
"rspamd_settings_map": "Rspamd 设置", "rspamd_settings_map": "Rspamd 设置",
"sal_level": "Moo 等级", "sal_level": "Moo 等级",
"save": "保存更改", "save": "保存更改",
@@ -409,7 +409,8 @@
"force_sso": "强制要求单点登录SSO", "force_sso": "强制要求单点登录SSO",
"user_link": "自定义链接", "user_link": "自定义链接",
"app_hide": "在登入界面隐藏", "app_hide": "在登入界面隐藏",
"needs_restart": "需要重启" "needs_restart": "需要重启",
"iam_use_tls_info": "如果使用了 TLS必须使用 LDAP 服务器的默认端口389。SSL 的端口不能使用。"
}, },
"danger": { "danger": {
"access_denied": "访问被拒绝或者表单数据无效", "access_denied": "访问被拒绝或者表单数据无效",
@@ -744,7 +745,16 @@
"internal": "内部的", "internal": "内部的",
"internal_info": "内部的别名只能在域内部或者别名域内部访问。", "internal_info": "内部的别名只能在域内部或者别名域内部访问。",
"mta_sts": "邮件传输代理严格传输安全协议MTA-STS", "mta_sts": "邮件传输代理严格传输安全协议MTA-STS",
"mta_sts_version": "版本" "mta_sts_version": "版本",
"mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> 是一项 MTA 标准,它强制要求 MTA 间传输必须使用 TLS 和有效的证书。<br>当因没有 DNSSEC 而无法使用 <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> 时,该标准将被启用。<br><b>注意</b>:若收件域支持基于 DNSSEC 的 DANE 协议,则系统将<b>始终</b>优先采用 DANE —— MTA-STS 仅作为备用机制存在。",
"mta_sts_version_info": "定义 MTA-STS 标准的版本——当前仅 <code>STSv1</code> 为有效版本。",
"mta_sts_mode": "模式",
"mta_sts_mode_info": "提供三种可选模式:<ul><li><em>测试模式</em>——仅监控策略执行情况,违反策略不会产生实际影响。</li><li><em>强制模式</em>——严格执行策略,拒绝所有未使用有效 TLS 加密的连接。</li><li><em>禁用模式</em>——发布策略但不生效。</li></ul>",
"mta_sts_max_age": "最长有效期",
"mta_sts_max_age_info": "接收方邮件服务器可缓存该策略的时长(秒),超出后需重新获取策略。",
"mta_sts_mx": "MX 服务器",
"mta_sts_mx_info": "仅允许向明确列出的邮件服务器发送邮件;发送方 MTA 会验证 DNS MX 记录的主机名是否与策略列表匹配,并仅允许携带有效 TLS 证书的投递(可防范中间人攻击)。",
"mta_sts_mx_notice": "可配置多个 MX 服务器(以逗号分隔)。"
}, },
"fido2": { "fido2": {
"confirm": "确认", "confirm": "确认",
@@ -814,7 +824,8 @@
"login_linkstext": "不是正确的登陆页面?", "login_linkstext": "不是正确的登陆页面?",
"login_usertext": "以用户身份登陆", "login_usertext": "以用户身份登陆",
"login_domainadmintext": "以域管理员身份登陆", "login_domainadmintext": "以域管理员身份登陆",
"login_admintext": "以管理员身份登陆" "login_admintext": "以管理员身份登陆",
"email": "邮箱地址"
}, },
"mailbox": { "mailbox": {
"action": "操作", "action": "操作",
@@ -920,7 +931,7 @@
"recipient_map_new": "新收件人", "recipient_map_new": "新收件人",
"recipient_map_new_info": "收件人映射的目标必须为合法的邮件地址或域名。", "recipient_map_new_info": "收件人映射的目标必须为合法的邮件地址或域名。",
"recipient_map_old": "原收件人", "recipient_map_old": "原收件人",
"recipient_map_old_info": "原收件人必须为合法的邮箱地址。", "recipient_map_old_info": "原收件人必须为合法的邮箱地址或域名。",
"recipient_maps": "收件人映射", "recipient_maps": "收件人映射",
"relay_all": "中继所有收件人", "relay_all": "中继所有收件人",
"remove": "删除", "remove": "删除",
@@ -1023,7 +1034,7 @@
"notified": "已发送通知", "notified": "已发送通知",
"qhandler_success": "已成功向系统发送请求,现在你可以关闭这个窗口了。", "qhandler_success": "已成功向系统发送请求,现在你可以关闭这个窗口了。",
"qid": "Rspamd 队列IDQID", "qid": "Rspamd 队列IDQID",
"qinfo": "隔离系统会把已被拒绝接收的邮件以及作为拷贝发送到垃圾箱的邮件保存到数据库中 (发件人<em>不</em>会知道)。\r\n <br>\"学习为垃圾并删除\" 会根据贝叶斯定理将消息作为垃圾学习并计算其模糊特征以拒绝未来收到相似消息。\r\n <br>请注意,这取决于你的系统资源,学习多个消息可能会花费较长时间。<br>名单中项目会被隔离系统排除。", "qinfo": "隔离系统会把已被拒绝接收的邮件以及作为拷贝发送到垃圾箱的邮件保存到数据库中 (发件人<em>不</em>会知道)。\n <br>\"学习为垃圾并删除\" 会根据贝叶斯定理将消息作为垃圾学习并计算其模糊特征以拒绝未来收到相似消息。\n <br>请注意,这取决于你的系统资源,学习多个消息可能会花费较长时间。<br>阻止名单中项目会被隔离系统排除。",
"qitem": "隔离项目", "qitem": "隔离项目",
"quarantine": "隔离", "quarantine": "隔离",
"quick_actions": "操作", "quick_actions": "操作",
@@ -1314,8 +1325,8 @@
"spam_score_reset": "重置为服务器默认值", "spam_score_reset": "重置为服务器默认值",
"spamfilter": "垃圾邮件过滤器", "spamfilter": "垃圾邮件过滤器",
"spamfilter_behavior": "分数", "spamfilter_behavior": "分数",
"spamfilter_bl": "名单", "spamfilter_bl": "阻止名单",
"spamfilter_bl_desc": "名单中地址<b>总是会</b>被标记为垃圾邮件。被拒绝的邮件<b>不会</b>进入隔离区。此处可以使用通配符 \"*\"。此过滤器也会应用到直接别名 (只指向一个目标邮箱),但不会应用到\"接收所有\"别名和邮箱地址本身。", "spamfilter_bl_desc": "阻止名单中地址<b>总是会</b>被标记为垃圾邮件。被拒绝的邮件<b>不会</b>进入隔离区。此处可以使用通配符 \"*\"。此过滤器也会应用到直接别名 (只指向一个目标邮箱),但不会应用到\"接收所有\"别名和邮箱地址本身。",
"spamfilter_default_score": "默认值", "spamfilter_default_score": "默认值",
"spamfilter_green": "绿色: 此消息不是垃圾邮件", "spamfilter_green": "绿色: 此消息不是垃圾邮件",
"spamfilter_hint": "第一个值表示\"低垃圾邮件分数\",第二个值表示\"高垃圾邮件分数\"。", "spamfilter_hint": "第一个值表示\"低垃圾邮件分数\",第二个值表示\"高垃圾邮件分数\"。",
@@ -1326,8 +1337,8 @@
"spamfilter_table_empty": "数据为空", "spamfilter_table_empty": "数据为空",
"spamfilter_table_remove": "删除", "spamfilter_table_remove": "删除",
"spamfilter_table_rule": "规则", "spamfilter_table_rule": "规则",
"spamfilter_wl": "名单", "spamfilter_wl": "允许名单",
"spamfilter_wl_desc": "名单中地址<b>永远不会</b>被标记为垃圾邮件。此处可以使用通配符 \"*\"。此过滤器也会应用到直接别名 (只指向一个目标邮箱),但不会应用到\"接收所有\"别名和邮箱地址本身。", "spamfilter_wl_desc": "允许名单中地址<b>永远不会</b>被标记为垃圾邮件。此处可以使用通配符 \"*\"。此过滤器也会应用到直接别名 (只指向一个目标邮箱),但不会应用到\"接收所有\"别名和邮箱地址本身。",
"spamfilter_yellow": "黄色: 此为垃圾邮件,会被标记为垃圾邮件并且移入垃圾邮件文件夹", "spamfilter_yellow": "黄色: 此为垃圾邮件,会被标记为垃圾邮件并且移入垃圾邮件文件夹",
"status": "状态", "status": "状态",
"sync_jobs": "同步任务", "sync_jobs": "同步任务",

View File

@@ -326,6 +326,12 @@
<small class="text-muted">{{ lang.user.password_reset_info }}</small> <small class="text-muted">{{ lang.user.password_reset_info }}</small>
</div> </div>
</div> </div>
<div class="row mb-4">
<label class="control-label col-sm-3" for="user_old_pass">{{ lang.user.password_now }}</label>
<div class="col-sm-9">
<input type="password" class="form-control" name="user_old_pass" autocomplete="off" required>
</div>
</div>
<div class="row"> <div class="row">
<div class="offset-sm-3 col-sm-9"> <div class="offset-sm-3 col-sm-9">
<button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="edit_selected" data-id="pw_recovery_change" data-item="null" data-api-url='edit/self' data-api-attr='{}' href="#">{{ lang.user.save }}</button> <button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="edit_selected" data-id="pw_recovery_change" data-item="null" data-api-url='edit/self' data-api-attr='{}' href="#">{{ lang.user.save }}</button>

View File

@@ -42,7 +42,7 @@ services:
- mysql - mysql
redis-mailcow: redis-mailcow:
image: redis:7.4.2-alpine image: redis:7.4.6-alpine
entrypoint: ["/bin/sh","/redis-conf.sh"] entrypoint: ["/bin/sh","/redis-conf.sh"]
volumes: volumes:
- redis-vol-1:/data/ - redis-vol-1:/data/
@@ -84,7 +84,7 @@ services:
- clamd - clamd
rspamd-mailcow: rspamd-mailcow:
image: ghcr.io/mailcow/rspamd:2.3 image: ghcr.io/mailcow/rspamd:2.4
stop_grace_period: 30s stop_grace_period: 30s
depends_on: depends_on:
- dovecot-mailcow - dovecot-mailcow
@@ -117,7 +117,7 @@ services:
- rspamd - rspamd
php-fpm-mailcow: php-fpm-mailcow:
image: ghcr.io/mailcow/phpfpm:1.93 image: ghcr.io/mailcow/phpfpm:1.94
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
depends_on: depends_on:
- redis-mailcow - redis-mailcow
@@ -502,7 +502,7 @@ services:
- acme - acme
netfilter-mailcow: netfilter-mailcow:
image: ghcr.io/mailcow/netfilter:1.62 image: ghcr.io/mailcow/netfilter:1.63
stop_grace_period: 30s stop_grace_period: 30s
restart: always restart: always
privileged: true privileged: true