Compare commits

..
Author SHA1 Message Date
FreddleSpl0it 281cf93db3 Merge pull request #7174 from mailcow/staging
Update 2026-03b
2026-03-31 09:57:16 +02:00
FreddleSpl0it f399c07c85 Merge pull request #7137 from mailcow/staging
Hotfix 2026-03a
2026-03-13 14:15:04 +01:00
FreddleSpl0it a693325fe6 Merge pull request #7135 from mailcow/staging
Update 2026-03a
2026-03-13 13:16:32 +01:00
FreddleSpl0it 9ad84eee92 Merge pull request #7113 from mailcow/staging
Hotfix 2026-03
2026-03-10 13:50:33 +01:00
FreddleSpl0it b59869b720 Merge pull request #7109 from mailcow/staging
Hotfix 2026-03
2026-03-10 10:39:43 +01:00
FreddleSpl0it 7515bef66c Merge pull request #7104 from mailcow/staging
Hotfix 2026-03
2026-03-10 10:05:39 +01:00
FreddleSpl0it b84ba8ded1 Merge pull request #7101 from mailcow/staging
Update 2026-03
2026-03-10 08:08:29 +01:00
FreddleSpl0it 4845928e7a Merge pull request #7027 from mailcow/staging
[Hotfix] Update 2026-01
2026-01-29 10:31:23 +01:00
FreddleSpl0it 4ccfedd6b3 Merge pull request #7024 from mailcow/staging
🐄🛡️ January 2026 Update | Limited EAS/DAV Access and Restricted Alias Sending
2026-01-29 07:25:09 +01:00
Ashitaka e8d9315d4a Merge pull request #6905 from Ashitaka57/6646-pbkdf2-sha512-verify-hash
Support for PBKDF2-SHA512 hash algorithm in verify_hash() (FreeIPA compatibility) (issue 6646)
2025-12-12 14:08:21 +01:00
DerLinkman d977ddb501 backup: add image prefetch function to verify latest image is used 2025-12-12 14:07:57 +01:00
DerLinkman e76f5237ed ofelia: revert fixed cron syntax for sa-rules download 2025-12-12 14:07:47 +01:00
CopilotandDerLinkman c11ed5dd1e Prevent duplicate/plaintext login announcement rendering (#6963)
* Initial plan

* Fix duplicate login announcement display

Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
2025-12-12 14:07:36 +01:00
DerLinkman 4ef65fc382 Merge pull request #6948 from mailcow/staging
2025-12
2025-12-09 13:29:15 +01:00
102 changed files with 3493 additions and 7596 deletions

No files matched your search

@@ -14,7 +14,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Mark/Close Stale Issues and Pull Requests 🗑️ - name: Mark/Close Stale Issues and Pull Requests 🗑️
uses: actions/stale@v11.0.0 uses: actions/stale@v10.2.0
with: with:
repo-token: ${{ secrets.STALE_ACTION_PAT }} repo-token: ${{ secrets.STALE_ACTION_PAT }}
days-before-stale: 60 days-before-stale: 60
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
- "watchdog-mailcow" - "watchdog-mailcow"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
- name: Setup Docker - name: Setup Docker
run: | run: |
curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh
+2 -2
View File
@@ -8,11 +8,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v7 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Run the Action - name: Run the Action
uses: devops-infra/action-pull-request@v1.4.0 uses: devops-infra/action-pull-request@v1.0.2
with: with:
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }} github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}} title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
packages: write packages: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v6
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v4 uses: docker/setup-qemu-action@v4
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v6
- name: Generate postscreen_access.cidr - name: Generate postscreen_access.cidr
run: | run: |
-2
View File
@@ -64,8 +64,6 @@ data/web/templates/cache/*
!data/web/templates/cache/.gitkeep !data/web/templates/cache/.gitkeep
data/web/.well-known/acme-challenge data/web/.well-known/acme-challenge
data/web/css/build/0081-custom-mailcow.css data/web/css/build/0081-custom-mailcow.css
data/web/css/themes/*/
!data/web/css/themes/servercow/
data/web/inc/vars.local.inc.php data/web/inc/vars.local.inc.php
data/web/inc/app_info.inc.php data/web/inc/app_info.inc.php
data/web/nextcloud*/ data/web/nextcloud*/
-5
View File
@@ -23,15 +23,10 @@ A big thank you to everyone supporting us on GitHub Sponsors—your contribution
<a href="https://www.maehdros.com/" target=_blank><img <a href="https://www.maehdros.com/" target=_blank><img
src="https://avatars.githubusercontent.com/u/173894712" height="58" src="https://avatars.githubusercontent.com/u/173894712" height="58"
/></a> /></a>
<a href="https://garske-systems.de/" target=_blank><img
src="https://garske-systems.de/img/logo_min.png" height="58"
/></a>
### 50$/Month Sponsors ### 50$/Month Sponsors
<a href="https://github.com/vnukhr" target=_blank><img <a href="https://github.com/vnukhr" target=_blank><img
src="https://avatars.githubusercontent.com/u/7805987?s=52&v=4" height="58" src="https://avatars.githubusercontent.com/u/7805987?s=52&v=4" height="58"
/></a><a href="https://github.com/humiico" target=_blank><img
src="https://avatars.githubusercontent.com/u/110134705?s=52&v=4" height="58"
/></a> /></a>
## Info, documentation and support ## Info, documentation and support
-21
View File
@@ -249,33 +249,12 @@ while true; do
done <<< "${SQL_DOMAINS}" done <<< "${SQL_DOMAINS}"
if [[ ${ONLY_MAILCOW_HOSTNAME} != "y" ]]; then if [[ ${ONLY_MAILCOW_HOSTNAME} != "y" ]]; then
# Fetch all domains with an active MTA-STS policy once.
unset MTA_STS_ACTIVE_DOMAINS
declare -A MTA_STS_ACTIVE_DOMAINS
if [[ ${AUTODISCOVER_SAN} == "y" ]]; then
SQL_MTA_STS_DOMAINS=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM mta_sts WHERE active = 1" -Bs)
if [[ $? -eq 0 ]]; then
while read mta_sts_domain; do
if [[ -z "${mta_sts_domain}" ]]; then
# ignore empty lines
continue
fi
MTA_STS_ACTIVE_DOMAINS["${mta_sts_domain}"]=1
done <<< "${SQL_MTA_STS_DOMAINS}"
fi
fi
for SQL_DOMAIN in "${SQL_DOMAIN_ARR[@]}"; do for SQL_DOMAIN in "${SQL_DOMAIN_ARR[@]}"; do
unset VALIDATED_CONFIG_DOMAINS_SUBDOMAINS unset VALIDATED_CONFIG_DOMAINS_SUBDOMAINS
declare -a VALIDATED_CONFIG_DOMAINS_SUBDOMAINS declare -a VALIDATED_CONFIG_DOMAINS_SUBDOMAINS
for SUBDOMAIN in "${ADDITIONAL_WC_ARR[@]}"; do for SUBDOMAIN in "${ADDITIONAL_WC_ARR[@]}"; do
FULL_SUBDOMAIN="${SUBDOMAIN}.${SQL_DOMAIN}" FULL_SUBDOMAIN="${SUBDOMAIN}.${SQL_DOMAIN}"
# Skip mta-sts subdomain unless MTA-STS is enabled (active) for this domain
if [[ "${SUBDOMAIN}" == "mta-sts" && -z "${MTA_STS_ACTIVE_DOMAINS[${SQL_DOMAIN}]}" ]]; then
log_f "MTA-STS is not enabled for ${SQL_DOMAIN} - skipping mta-sts subdomain certificate"
continue
fi
# Skip if subdomain matches MAILCOW_HOSTNAME # Skip if subdomain matches MAILCOW_HOSTNAME
if [[ "${FULL_SUBDOMAIN}" == "${MAILCOW_HOSTNAME}" ]]; then if [[ "${FULL_SUBDOMAIN}" == "${MAILCOW_HOSTNAME}" ]]; then
continue continue
+3 -3
View File
@@ -1,7 +1,7 @@
FROM alpine:3.24 AS builder FROM alpine:3.21 AS builder
WORKDIR /src WORKDIR /src
ENV CLAMD_VERSION=1.4.6 ENV CLAMD_VERSION=1.4.2
RUN apk upgrade --no-cache \ RUN apk upgrade --no-cache \
&& apk add --update --no-cache \ && apk add --update --no-cache \
@@ -68,7 +68,7 @@ RUN wget -P /src https://www.clamav.net/downloads/production/clamav-${CLAMD_VERS
"/clamav/etc/clamav/clamav-milter.conf.sample" > "/clamav/etc/clamav/clamav-milter.conf" || exit 1 "/clamav/etc/clamav/clamav-milter.conf.sample" > "/clamav/etc/clamav/clamav-milter.conf" || exit 1
FROM alpine:3.24 FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
+1 -1
View File
@@ -1,4 +1,4 @@
FROM nginx:1.30.4-alpine FROM nginx:alpine
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
ENV PIP_BREAK_SYSTEM_PACKAGES=1 ENV PIP_BREAK_SYSTEM_PACKAGES=1
+10 -15
View File
@@ -1,4 +1,4 @@
FROM php:8.2-fpm-alpine3.24 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>"
@@ -7,13 +7,13 @@ ARG APCU_PECL_VERSION=5.1.28
# 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.1 ARG IMAGICK_PECL_VERSION=3.8.1
# 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.2.0 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.4.0 ARG MEMCACHED_PECL_VERSION=3.4.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>.*)$
ARG REDIS_PECL_VERSION=6.3.0 ARG REDIS_PECL_VERSION=6.3.0
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$ # renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
ARG COMPOSER_VERSION=2.10.2 ARG COMPOSER_VERSION=2.9.5
RUN apk add -U --no-cache autoconf \ RUN apk add -U --no-cache autoconf \
aspell-dev \ aspell-dev \
@@ -47,7 +47,6 @@ RUN apk add -U --no-cache autoconf \
libressl-dev \ libressl-dev \
librsvg \ librsvg \
libtool \ libtool \
libwebp \
libwebp-dev \ libwebp-dev \
libxml2-dev \ libxml2-dev \
libxpm \ libxpm \
@@ -64,16 +63,13 @@ RUN apk add -U --no-cache autoconf \
samba-client \ samba-client \
zlib-dev \ zlib-dev \
tzdata \ tzdata \
&& docker-php-source extract \ && pecl install APCu-${APCU_PECL_VERSION} \
# PECL extensions from their GitHub tags (pecl.php.net is not reliable enough for builds) && pecl install imagick-${IMAGICK_PECL_VERSION} \
&& mkdir -p /usr/src/php/ext/apcu /usr/src/php/ext/imagick /usr/src/php/ext/mailparse /usr/src/php/ext/memcached /usr/src/php/ext/redis \ && pecl install mailparse-${MAILPARSE_PECL_VERSION} \
&& curl -fsSL https://github.com/krakjoe/apcu/archive/refs/tags/v${APCU_PECL_VERSION}.tar.gz | tar -xz --strip-components=1 -C /usr/src/php/ext/apcu \ && pecl install memcached-${MEMCACHED_PECL_VERSION} \
&& curl -fsSL https://github.com/Imagick/imagick/archive/refs/tags/${IMAGICK_PECL_VERSION}.tar.gz | tar -xz --strip-components=1 -C /usr/src/php/ext/imagick \ && pecl install redis-${REDIS_PECL_VERSION} \
&& sed -i "s/@PACKAGE_VERSION@/${IMAGICK_PECL_VERSION}/" /usr/src/php/ext/imagick/php_imagick.h \ && docker-php-ext-enable apcu imagick memcached mailparse redis \
&& curl -fsSL https://github.com/php/pecl-mail-mailparse/archive/refs/tags/v${MAILPARSE_PECL_VERSION}.tar.gz | tar -xz --strip-components=1 -C /usr/src/php/ext/mailparse \ && pecl clear-cache \
&& curl -fsSL https://github.com/php-memcached-dev/php-memcached/archive/refs/tags/v${MEMCACHED_PECL_VERSION}.tar.gz | tar -xz --strip-components=1 -C /usr/src/php/ext/memcached \
&& curl -fsSL https://github.com/phpredis/phpredis/archive/refs/tags/${REDIS_PECL_VERSION}.tar.gz | tar -xz --strip-components=1 -C /usr/src/php/ext/redis \
&& docker-php-ext-install -j 4 apcu imagick mailparse memcached redis \
&& docker-php-ext-configure intl \ && docker-php-ext-configure intl \
&& docker-php-ext-configure exif \ && docker-php-ext-configure exif \
&& docker-php-ext-configure gd --with-freetype=/usr/include/ \ && docker-php-ext-configure gd --with-freetype=/usr/include/ \
@@ -87,7 +83,6 @@ RUN apk add -U --no-cache autoconf \
&& curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \ && curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \
&& mv composer.phar /usr/local/bin/composer \ && mv composer.phar /usr/local/bin/composer \
&& chmod +x /usr/local/bin/composer \ && chmod +x /usr/local/bin/composer \
&& docker-php-source delete \
&& apk del --purge autoconf \ && apk del --purge autoconf \
aspell-dev \ aspell-dev \
cyrus-sasl-dev \ cyrus-sasl-dev \
+3 -3
View File
@@ -1,17 +1,17 @@
FROM golang:1.26-trixie AS builder FROM golang:1.25-bookworm AS builder
WORKDIR /src WORKDIR /src
ENV CGO_ENABLED=0 \ ENV CGO_ENABLED=0 \
GO111MODULE=on \ GO111MODULE=on \
NOOPT=1 \ NOOPT=1 \
VERSION=1.14.0 VERSION=1.8.22
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:trixie-slim 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
@@ -1,4 +1,4 @@
@version: 4.8 @version: 3.38
@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: 4.8 @version: 3.38
@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 -1
View File
@@ -1,4 +1,4 @@
FROM debian:trixie-slim FROM debian:bookworm-slim
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
@@ -1,4 +1,4 @@
@version: 4.8 @version: 3.38
@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 {
+2 -2
View File
@@ -1,4 +1,4 @@
@version: 4.8 @version: 3.38
@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 -1
View File
@@ -2,7 +2,7 @@ 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
ARG RSPAMD_VER=rspamd_4.2.1-1~b7a16ae ARG RSPAMD_VER=rspamd_3.14.3-1~236eb65
ARG CODENAME=trixie ARG CODENAME=trixie
ENV LC_ALL=C ENV LC_ALL=C
+3 -3
View File
@@ -1,6 +1,6 @@
# SOGo built from source to enable security patch application # SOGo built from source to enable security patch application
# Repository: https://github.com/Alinto/sogo # Repository: https://github.com/Alinto/sogo
# Version: SOGo-5.12.11 # Version: SOGo-5.12.4
# #
# Applied security patches: # Applied security patches:
# - # -
@@ -12,8 +12,8 @@ FROM debian:bookworm
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 SOGO_VERSION=SOGo-5.12.11 ARG SOGO_VERSION=SOGo-5.12.5
ARG SOPE_VERSION=SOPE-5.12.11 ARG SOPE_VERSION=SOPE-5.12.5
# Security patches to apply (space-separated commit hashes) # Security patches to apply (space-separated commit hashes)
ARG SOGO_SECURITY_PATCHES="" ARG SOGO_SECURITY_PATCHES=""
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$ # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
+1 -5
View File
@@ -2,15 +2,11 @@ FROM alpine:3.23
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
# install unbound from alpine:edge to get security patches
# minimum version is pinned
RUN apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/main "unbound>=1.26.1-r0"
# install other packages from regular alpine stable repo
RUN apk add --update --no-cache \ RUN apk add --update --no-cache \
curl \ curl \
bind-tools \ bind-tools \
coreutils \ coreutils \
unbound \
bash \ bash \
openssl \ openssl \
drill \ drill \
+1 -10
View File
@@ -32,17 +32,8 @@ 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 " .. tostring(c) .. " for user " .. request.user) dovecot.i_info("HTTP request failed with " .. 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
+10
View File
@@ -1,3 +1,13 @@
# global_sieve_before script # global_sieve_before script
# global_sieve_before -> user sieve_before (mailcow UI) -> user sieve_after (mailcow UI) -> global_sieve_after # global_sieve_before -> user sieve_before (mailcow UI) -> user sieve_after (mailcow UI) -> global_sieve_after
require ["mailbox", "fileinto"];
if header :contains ["Chat-Version"] [""] {
if mailboxexists "DeltaChat" {
fileinto "DeltaChat";
} else {
fileinto :create "DeltaChat";
}
stop;
}
-7
View File
@@ -42,19 +42,12 @@ http {
https https; https https;
} }
map $server_port $sogo_auth_internal {
65510 "1";
default "";
}
{% if HTTP_REDIRECT %} {% if HTTP_REDIRECT %}
# HTTP to HTTPS redirect # HTTP to HTTPS redirect
server { server {
root /web; root /web;
listen {{ HTTP_PORT }} default_server; listen {{ HTTP_PORT }} default_server;
{% if ENABLE_IPV6 %}
listen [::]:{{ HTTP_PORT }} default_server; listen [::]:{{ HTTP_PORT }} default_server;
{%endif%}
server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* mta-sts.* {{ ADDITIONAL_SERVER_NAMES | join(' ') }}; server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* mta-sts.* {{ ADDITIONAL_SERVER_NAMES | join(' ') }};
@@ -116,9 +116,6 @@ location ~ \.php$ {
include /etc/nginx/fastcgi_params; include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param HTTP_X_REAL_IP $remote_addr;
# trusted internal-auth marker; empty for external clients (see nginx.conf map)
fastcgi_param SOGO_AUTH_INTERNAL $sogo_auth_internal;
fastcgi_read_timeout 3600; fastcgi_read_timeout 3600;
fastcgi_send_timeout 3600; fastcgi_send_timeout 3600;
} }
-2
View File
@@ -1,5 +1,3 @@
max_execution_time = 3600 max_execution_time = 3600
max_input_time = 3600 max_input_time = 3600
memory_limit = 512M memory_limit = 512M
; not used by mailcow, disabled as hardening (PHP security release 2026-10-21)
session.upload_progress.enabled = Off
+4 -2
View File
@@ -37,7 +37,7 @@ postscreen_access_list = permit_mynetworks,
cidr:/opt/postfix/conf/postscreen_access.cidr, cidr:/opt/postfix/conf/postscreen_access.cidr,
tcp:127.0.0.1:10027 tcp:127.0.0.1:10027
postscreen_bare_newline_enable = no postscreen_bare_newline_enable = no
postscreen_denylist_action = drop postscreen_blacklist_action = drop
postscreen_cache_cleanup_interval = 24h postscreen_cache_cleanup_interval = 24h
postscreen_cache_map = proxy:btree:$data_directory/postscreen_cache postscreen_cache_map = proxy:btree:$data_directory/postscreen_cache
postscreen_dnsbl_action = enforce postscreen_dnsbl_action = enforce
@@ -107,6 +107,8 @@ smtpd_sender_restrictions = reject_authenticated_sender_login_mismatch,
reject_unknown_sender_domain reject_unknown_sender_domain
smtpd_soft_error_limit = 3 smtpd_soft_error_limit = 3
smtpd_tls_auth_only = yes smtpd_tls_auth_only = yes
smtpd_tls_dh1024_param_file = /etc/ssl/mail/dhparams.pem
smtpd_tls_eecdh_grade = auto
smtpd_tls_exclude_ciphers = ECDHE-RSA-RC4-SHA, RC4, aNULL, DES-CBC3-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA smtpd_tls_exclude_ciphers = ECDHE-RSA-RC4-SHA, RC4, aNULL, DES-CBC3-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA
smtpd_tls_loglevel = 1 smtpd_tls_loglevel = 1
@@ -150,7 +152,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:QUERYwithTLSRPT smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf socketmap:inet:postfix-tlspol:8642:QUERY
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 "*"
+2
View File
@@ -29,6 +29,7 @@ smtps inet n - n - - smtpd
# TLS protocol can be modified by setting submission_smtpd_tls_mandatory_protocols in extra.cf # TLS protocol can be modified by setting submission_smtpd_tls_mandatory_protocols in extra.cf
submission inet n - n - - smtpd submission inet n - n - - smtpd
-o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
-o smtpd_enforce_tls=yes
-o smtpd_tls_security_level=encrypt -o smtpd_tls_security_level=encrypt
-o smtpd_tls_mandatory_protocols=$submission_smtpd_tls_mandatory_protocols -o smtpd_tls_mandatory_protocols=$submission_smtpd_tls_mandatory_protocols
-o tls_preempt_cipherlist=yes -o tls_preempt_cipherlist=yes
@@ -37,6 +38,7 @@ submission inet n - n - - smtpd
10587 inet n - n - - smtpd 10587 inet n - n - - smtpd
-o smtpd_upstream_proxy_protocol=haproxy -o smtpd_upstream_proxy_protocol=haproxy
-o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
-o smtpd_enforce_tls=yes
-o smtpd_tls_security_level=encrypt -o smtpd_tls_security_level=encrypt
-o smtpd_tls_mandatory_protocols=$submission_smtpd_tls_mandatory_protocols -o smtpd_tls_mandatory_protocols=$submission_smtpd_tls_mandatory_protocols
-o tls_preempt_cipherlist=yes -o tls_preempt_cipherlist=yes
+48 -120
View File
@@ -1,8 +1,7 @@
# Whitelist generated by Postwhite v3.4 on Thu Oct 1 00:35:20 UTC 2026 # Whitelist generated by Postwhite v3.4 on Sun Mar 1 00:29:01 UTC 2026
# https://github.com/stevejenkins/postwhite/ # https://github.com/stevejenkins/postwhite/
# 2246 total rules # 2174 total rules
2a00:1450:4000::/36 permit 2a00:1450:4000::/36 permit
2a00:1450:4864::/56 permit
2a01:111:f400::/48 permit 2a01:111:f400::/48 permit
2a01:111:f403:2800::/53 permit 2a01:111:f403:2800::/53 permit
2a01:111:f403:8000::/51 permit 2a01:111:f403:8000::/51 permit
@@ -29,15 +28,11 @@
2a01:b747:3004:200::/56 permit 2a01:b747:3004:200::/56 permit
2a01:b747:3005:200::/56 permit 2a01:b747:3005:200::/56 permit
2a01:b747:3006:200::/56 permit 2a01:b747:3006:200::/56 permit
2a01:b747:3007::/56 permit
2a02:a60:0:5::/64 permit 2a02:a60:0:5::/64 permit
2a0f:f640::/56 permit 2a0f:f640::/56 permit
2c0f:fb50:4000::/36 permit 2c0f:fb50:4000::/36 permit
2c0f:fb50:4864::/56 permit
2.207.151.32/27 permit
2.207.151.53 permit 2.207.151.53 permit
2.207.217.30 permit 2.207.217.30 permit
2.207.223.160/27 permit
3.64.237.68 permit 3.64.237.68 permit
3.65.3.180 permit 3.65.3.180 permit
3.70.123.177 permit 3.70.123.177 permit
@@ -58,14 +53,13 @@
8.25.196.0/23 permit 8.25.196.0/23 permit
8.36.116.0/24 permit 8.36.116.0/24 permit
8.39.54.0/23 permit 8.39.54.0/23 permit
8.39.54.240/29 permit
8.39.54.250/31 permit 8.39.54.250/31 permit
8.39.144.0/24 permit 8.39.144.0/24 permit
8.40.222.0/23 permit 8.40.222.0/23 permit
8.40.222.240/29 permit
8.40.222.250/31 permit 8.40.222.250/31 permit
8.47.10.9 permit
12.130.86.238 permit 12.130.86.238 permit
13.107.213.51 permit
13.107.246.51 permit
13.108.16.0/20 permit 13.108.16.0/20 permit
13.110.208.0/21 permit 13.110.208.0/21 permit
13.110.209.0/24 permit 13.110.209.0/24 permit
@@ -125,6 +119,7 @@
23.249.208.0/20 permit 23.249.208.0/20 permit
23.251.224.0/19 permit 23.251.224.0/19 permit
23.253.141.0/24 permit 23.253.141.0/24 permit
23.253.182.0/23 permit
23.253.182.103 permit 23.253.182.103 permit
23.253.183.145 permit 23.253.183.145 permit
23.253.183.146 permit 23.253.183.146 permit
@@ -146,7 +141,24 @@
27.123.206.80/28 permit 27.123.206.80/28 permit
31.47.251.17 permit 31.47.251.17 permit
31.186.239.0/24 permit 31.186.239.0/24 permit
34.2.64.0/19 permit 34.2.64.0/22 permit
34.2.68.0/23 permit
34.2.70.0/23 permit
34.2.71.64/26 permit
34.2.72.0/22 permit
34.2.75.0/26 permit
34.2.78.0/23 permit
34.2.80.0/23 permit
34.2.82.0/23 permit
34.2.84.0/24 permit
34.2.84.64/26 permit
34.2.85.0/24 permit
34.2.85.64/26 permit
34.2.86.0/23 permit
34.2.88.0/23 permit
34.2.90.0/23 permit
34.2.92.0/23 permit
34.2.94.0/23 permit
34.70.158.162 permit 34.70.158.162 permit
34.74.74.140 permit 34.74.74.140 permit
34.83.159.189 permit 34.83.159.189 permit
@@ -173,7 +185,6 @@
35.205.92.9 permit 35.205.92.9 permit
35.228.216.85 permit 35.228.216.85 permit
35.242.169.159 permit 35.242.169.159 permit
37.187.220.204 permit
37.188.97.188 permit 37.188.97.188 permit
37.218.248.47 permit 37.218.248.47 permit
37.218.249.47 permit 37.218.249.47 permit
@@ -247,8 +258,8 @@
46.228.39.112/31 permit 46.228.39.112/31 permit
46.243.88.174 permit 46.243.88.174 permit
46.243.88.175 permit 46.243.88.175 permit
46.243.88.176/30 permit 46.243.88.176 permit
46.243.95.96/27 permit 46.243.88.177 permit
46.243.95.179 permit 46.243.95.179 permit
46.243.95.180 permit 46.243.95.180 permit
50.16.246.183 permit 50.16.246.183 permit
@@ -266,8 +277,6 @@
50.56.130.221 permit 50.56.130.221 permit
50.56.130.222 permit 50.56.130.222 permit
50.112.246.219 permit 50.112.246.219 permit
50.152.146.25 permit
51.83.17.38 permit
52.1.14.157 permit 52.1.14.157 permit
52.5.230.59 permit 52.5.230.59 permit
52.6.74.205 permit 52.6.74.205 permit
@@ -358,9 +367,7 @@
64.127.115.252 permit 64.127.115.252 permit
64.132.88.0/23 permit 64.132.88.0/23 permit
64.132.92.0/24 permit 64.132.92.0/24 permit
64.181.136.241 permit
64.181.194.190 permit 64.181.194.190 permit
64.181.213.254 permit
64.207.219.7 permit 64.207.219.7 permit
64.207.219.8 permit 64.207.219.8 permit
64.207.219.9 permit 64.207.219.9 permit
@@ -446,11 +453,7 @@
66.218.75.252/31 permit 66.218.75.252/31 permit
66.218.75.254 permit 66.218.75.254 permit
66.220.144.128/25 permit 66.220.144.128/25 permit
66.220.144.178 permit
66.220.144.179 permit
66.220.155.0/24 permit 66.220.155.0/24 permit
66.220.155.178 permit
66.220.155.179 permit
66.220.157.0/25 permit 66.220.157.0/25 permit
66.231.80.0/20 permit 66.231.80.0/20 permit
66.240.227.0/24 permit 66.240.227.0/24 permit
@@ -515,8 +518,6 @@
69.162.98.0/24 permit 69.162.98.0/24 permit
69.169.224.0/20 permit 69.169.224.0/20 permit
69.171.232.0/24 permit 69.171.232.0/24 permit
69.171.232.180 permit
69.171.232.181 permit
69.171.244.0/23 permit 69.171.244.0/23 permit
70.42.149.35 permit 70.42.149.35 permit
72.3.185.0/24 permit 72.3.185.0/24 permit
@@ -638,8 +639,6 @@
77.238.189.148/30 permit 77.238.189.148/30 permit
79.135.106.0/24 permit 79.135.106.0/24 permit
79.135.107.0/24 permit 79.135.107.0/24 permit
80.225.160.128/25 permit
81.88.38.0/23 permit
81.169.146.243 permit 81.169.146.243 permit
81.169.146.245 permit 81.169.146.245 permit
81.169.146.246 permit 81.169.146.246 permit
@@ -658,9 +657,6 @@
82.165.159.45 permit 82.165.159.45 permit
82.165.159.130 permit 82.165.159.130 permit
82.165.159.131 permit 82.165.159.131 permit
84.8.68.0/25 permit
84.8.192.128/25 permit
84.8.224.128/25 permit
85.9.206.169 permit 85.9.206.169 permit
85.9.210.45 permit 85.9.210.45 permit
85.158.136.0/21 permit 85.158.136.0/21 permit
@@ -724,7 +720,6 @@
96.43.148.64/28 permit 96.43.148.64/28 permit
96.43.148.64/31 permit 96.43.148.64/31 permit
96.43.151.64/28 permit 96.43.151.64/28 permit
98.77.0.0/16 permit
98.97.248.0/21 permit 98.97.248.0/21 permit
98.136.44.181 permit 98.136.44.181 permit
98.136.44.182/31 permit 98.136.44.182/31 permit
@@ -1220,6 +1215,8 @@
104.43.243.237 permit 104.43.243.237 permit
104.44.112.128/25 permit 104.44.112.128/25 permit
104.47.0.0/17 permit 104.47.0.0/17 permit
104.130.96.0/28 permit
104.130.122.0/23 permit
106.10.144.64/27 permit 106.10.144.64/27 permit
106.10.144.100/31 permit 106.10.144.100/31 permit
106.10.144.103 permit 106.10.144.103 permit
@@ -1372,7 +1369,6 @@
117.120.16.0/21 permit 117.120.16.0/21 permit
119.42.242.52/31 permit 119.42.242.52/31 permit
119.42.242.156 permit 119.42.242.156 permit
121.244.91.31 permit
121.244.91.48 permit 121.244.91.48 permit
121.244.91.52 permit 121.244.91.52 permit
122.15.156.182 permit 122.15.156.182 permit
@@ -1412,20 +1408,6 @@
129.146.88.28 permit 129.146.88.28 permit
129.146.147.105 permit 129.146.147.105 permit
129.146.236.58 permit 129.146.236.58 permit
129.148.135.0/25 permit
129.148.148.0/25 permit
129.148.164.0/25 permit
129.148.180.0/25 permit
129.148.215.0/25 permit
129.149.6.0/25 permit
129.149.22.0/25 permit
129.149.38.0/25 permit
129.149.52.0/25 permit
129.149.68.0/25 permit
129.149.84.0/25 permit
129.149.100.0/25 permit
129.149.118.0/25 permit
129.149.126.0/25 permit
129.151.67.221 permit 129.151.67.221 permit
129.153.62.216 permit 129.153.62.216 permit
129.153.104.71 permit 129.153.104.71 permit
@@ -1438,21 +1420,18 @@
129.159.22.159 permit 129.159.22.159 permit
129.159.87.137 permit 129.159.87.137 permit
129.213.195.191 permit 129.213.195.191 permit
130.35.116.0/25 permit
130.61.9.72 permit 130.61.9.72 permit
130.162.39.83 permit 130.162.39.83 permit
130.162.194.23 permit
130.248.172.0/24 permit 130.248.172.0/24 permit
130.248.173.0/24 permit 130.248.173.0/24 permit
131.186.12.0/25 permit
131.253.30.0/24 permit 131.253.30.0/24 permit
131.253.121.0/26 permit 131.253.121.0/26 permit
132.145.13.209 permit 132.145.13.209 permit
132.226.26.225 permit 132.226.26.225 permit
132.226.49.32 permit 132.226.49.32 permit
132.226.56.24 permit 132.226.56.24 permit
134.98.248.128/25 permit 134.128.64.0/19 permit
134.128.64.0/18 permit 134.128.96.0/19 permit
134.170.27.8 permit 134.170.27.8 permit
134.170.113.0/26 permit 134.170.113.0/26 permit
134.170.141.64/26 permit 134.170.141.64/26 permit
@@ -1481,10 +1460,6 @@
136.147.182.0/24 permit 136.147.182.0/24 permit
136.147.224.0/20 permit 136.147.224.0/20 permit
136.179.50.206 permit 136.179.50.206 permit
136.248.224.128/25 permit
136.248.232.128/25 permit
138.1.108.0/25 permit
138.1.170.0/24 permit
139.60.152.0/22 permit 139.60.152.0/22 permit
139.138.35.44 permit 139.138.35.44 permit
139.138.46.121 permit 139.138.46.121 permit
@@ -1493,7 +1468,6 @@
139.138.57.55 permit 139.138.57.55 permit
139.138.58.119 permit 139.138.58.119 permit
139.167.79.86 permit 139.167.79.86 permit
139.177.108.0/25 permit
139.180.17.0/24 permit 139.180.17.0/24 permit
140.238.148.191 permit 140.238.148.191 permit
141.148.55.217 permit 141.148.55.217 permit
@@ -1524,14 +1498,14 @@
146.20.14.105 permit 146.20.14.105 permit
146.20.14.106 permit 146.20.14.106 permit
146.20.14.107 permit 146.20.14.107 permit
146.20.112.0/26 permit
146.20.113.0/24 permit
146.20.191.0/24 permit
146.20.215.0/24 permit 146.20.215.0/24 permit
146.20.215.182 permit 146.20.215.182 permit
146.88.28.0/24 permit 146.88.28.0/24 permit
146.148.116.76 permit 146.148.116.76 permit
147.154.32.0/25 permit 147.154.32.0/25 permit
147.154.63.0/24 permit
147.154.126.0/24 permit
147.154.191.0/24 permit
147.243.1.47 permit 147.243.1.47 permit
147.243.1.48 permit 147.243.1.48 permit
147.243.1.153 permit 147.243.1.153 permit
@@ -1539,23 +1513,16 @@
147.243.128.26 permit 147.243.128.26 permit
148.105.0.0/16 permit 148.105.0.0/16 permit
148.105.8.0/21 permit 148.105.8.0/21 permit
148.116.32.128/25 permit
149.72.0.0/16 permit 149.72.0.0/16 permit
149.72.234.184 permit 149.72.234.184 permit
149.72.248.236 permit 149.72.248.236 permit
149.97.173.180 permit 149.97.173.180 permit
149.118.160.128/25 permit
150.136.21.199 permit 150.136.21.199 permit
150.171.109.178 permit
150.230.98.160 permit 150.230.98.160 permit
151.145.38.14 permit 151.145.38.14 permit
152.67.105.195 permit 152.67.105.195 permit
152.69.165.251 permit
152.69.200.236 permit 152.69.200.236 permit
152.70.155.126 permit 152.70.155.126 permit
155.248.135.128/25 permit
155.248.140.0/25 permit
155.248.148.0/25 permit
155.248.208.51 permit 155.248.208.51 permit
155.248.220.138 permit 155.248.220.138 permit
155.248.234.149 permit 155.248.234.149 permit
@@ -1564,16 +1531,10 @@
157.58.30.128/25 permit 157.58.30.128/25 permit
157.58.196.96/29 permit 157.58.196.96/29 permit
157.58.249.3 permit 157.58.249.3 permit
157.137.32.128/25 permit
157.137.96.128/25 permit
157.151.208.65 permit 157.151.208.65 permit
157.255.1.64/29 permit 157.255.1.64/29 permit
158.101.211.207 permit 158.101.211.207 permit
158.247.16.0/20 permit 158.247.16.0/20 permit
158.247.100.0/25 permit
159.13.4.0/25 permit
159.13.33.181 permit
159.26.176.0/20 permit
159.92.154.0/24 permit 159.92.154.0/24 permit
159.92.155.0/24 permit 159.92.155.0/24 permit
159.92.157.0/24 permit 159.92.157.0/24 permit
@@ -1601,15 +1562,12 @@
159.183.121.182 permit 159.183.121.182 permit
159.183.129.172 permit 159.183.129.172 permit
160.1.62.192 permit 160.1.62.192 permit
161.33.94.110 permit
161.33.228.244 permit
161.38.192.0/20 permit 161.38.192.0/20 permit
161.38.204.0/22 permit 161.38.204.0/22 permit
161.71.32.0/19 permit 161.71.32.0/19 permit
161.71.64.0/20 permit 161.71.64.0/20 permit
162.88.4.0/23 permit 162.88.4.0/23 permit
162.88.8.0/24 permit 162.88.8.0/24 permit
162.88.24.0/23 permit
162.88.24.0/24 permit 162.88.24.0/24 permit
162.88.25.0/24 permit 162.88.25.0/24 permit
162.88.36.0/24 permit 162.88.36.0/24 permit
@@ -1627,13 +1585,11 @@
164.152.23.32 permit 164.152.23.32 permit
164.152.25.241 permit 164.152.25.241 permit
164.177.132.168/30 permit 164.177.132.168/30 permit
165.1.100.0/25 permit
165.173.128.0/24 permit 165.173.128.0/24 permit
165.173.180.0/24 permit 165.173.180.1 permit
165.173.180.250/31 permit 165.173.180.250/31 permit
165.173.182.0/24 permit
165.173.182.250/31 permit 165.173.182.250/31 permit
165.173.189.205 permit 166.78.68.0/22 permit
166.78.68.221 permit 166.78.68.221 permit
166.78.69.169 permit 166.78.69.169 permit
166.78.69.170 permit 166.78.69.170 permit
@@ -1651,12 +1607,9 @@
167.89.75.126 permit 167.89.75.126 permit
167.89.75.136 permit 167.89.75.136 permit
167.89.75.164 permit 167.89.75.164 permit
167.89.101.2 permit
167.89.101.192/28 permit
167.220.67.232/29 permit 167.220.67.232/29 permit
168.107.248.128/25 permit
168.110.160.128/25 permit
168.110.248.128/25 permit
168.129.184.128/25 permit
168.129.248.128/25 permit
168.138.5.36 permit 168.138.5.36 permit
168.138.73.51 permit 168.138.73.51 permit
168.138.77.31 permit 168.138.77.31 permit
@@ -1673,7 +1626,6 @@
169.148.144.0/25 permit 169.148.144.0/25 permit
169.148.144.10 permit 169.148.144.10 permit
169.148.146.0/23 permit 169.148.146.0/23 permit
169.148.174.10 permit
169.148.175.3 permit 169.148.175.3 permit
169.148.179.3 permit 169.148.179.3 permit
169.148.188.0/24 permit 169.148.188.0/24 permit
@@ -1692,7 +1644,6 @@
173.203.81.39 permit 173.203.81.39 permit
173.224.161.128/25 permit 173.224.161.128/25 permit
173.224.165.0/26 permit 173.224.165.0/26 permit
173.224.166.48/28 permit
174.36.84.8/29 permit 174.36.84.8/29 permit
174.36.84.16/29 permit 174.36.84.16/29 permit
174.36.84.32/29 permit 174.36.84.32/29 permit
@@ -1707,8 +1658,6 @@
175.41.215.51 permit 175.41.215.51 permit
176.32.105.0/24 permit 176.32.105.0/24 permit
176.32.127.0/24 permit 176.32.127.0/24 permit
176.94.34.32/27 permit
176.94.34.64/27 permit
178.236.10.128/26 permit 178.236.10.128/26 permit
182.50.76.0/22 permit 182.50.76.0/22 permit
182.50.78.64/28 permit 182.50.78.64/28 permit
@@ -1790,36 +1739,26 @@
188.125.85.234/31 permit 188.125.85.234/31 permit
188.125.85.236/31 permit 188.125.85.236/31 permit
188.125.85.238 permit 188.125.85.238 permit
188.165.51.139 permit
188.172.128.0/20 permit 188.172.128.0/20 permit
192.0.64.0/18 permit 192.0.64.0/18 permit
192.9.166.184 permit
192.18.139.154 permit 192.18.139.154 permit
192.18.145.36 permit 192.18.145.36 permit
192.18.152.58 permit 192.18.152.58 permit
192.22.32.128/25 permit
192.22.96.128/25 permit
192.22.160.128/25 permit
192.22.224.128/25 permit
192.28.128.0/18 permit 192.28.128.0/18 permit
192.29.24.0/25 permit
192.29.44.0/25 permit
192.29.72.0/25 permit
192.29.88.0/25 permit
192.29.103.128/25 permit 192.29.103.128/25 permit
192.29.134.0/25 permit
192.29.151.128/25 permit
192.29.172.0/25 permit
192.29.178.0/25 permit
192.29.200.0/25 permit
192.29.216.0/25 permit
192.29.232.0/25 permit
192.29.248.0/25 permit
192.30.252.0/22 permit 192.30.252.0/22 permit
192.161.144.0/20 permit 192.161.144.0/20 permit
192.162.87.0/24 permit 192.162.87.0/24 permit
192.237.158.0/23 permit
192.237.159.42 permit 192.237.159.42 permit
192.237.159.43 permit 192.237.159.43 permit
192.254.112.0/20 permit 192.254.112.0/20 permit
192.254.112.60 permit
192.254.112.98/31 permit
192.254.113.10 permit
192.254.113.101 permit
192.254.114.176 permit
193.109.254.0/23 permit 193.109.254.0/23 permit
193.122.128.100 permit 193.122.128.100 permit
193.123.56.63 permit 193.123.56.63 permit
@@ -1859,6 +1798,7 @@
198.21.0.0/21 permit 198.21.0.0/21 permit
198.37.144.0/20 permit 198.37.144.0/20 permit
198.37.152.186 permit 198.37.152.186 permit
198.61.254.0/23 permit
198.61.254.21 permit 198.61.254.21 permit
198.61.254.231 permit 198.61.254.231 permit
198.178.234.57 permit 198.178.234.57 permit
@@ -1945,7 +1885,6 @@
204.14.232.0/21 permit 204.14.232.0/21 permit
204.14.232.64/28 permit 204.14.232.64/28 permit
204.14.234.64/28 permit 204.14.234.64/28 permit
204.75.18.128/27 permit
204.75.142.0/24 permit 204.75.142.0/24 permit
204.92.114.187 permit 204.92.114.187 permit
204.92.114.203 permit 204.92.114.203 permit
@@ -1953,9 +1892,7 @@
204.141.32.0/23 permit 204.141.32.0/23 permit
204.141.42.0/23 permit 204.141.42.0/23 permit
204.216.164.202 permit 204.216.164.202 permit
204.220.90.0/23 permit 204.220.160.0/21 permit
204.220.92.0/22 permit
204.220.160.0/20 permit
204.220.168.0/21 permit 204.220.168.0/21 permit
204.220.176.0/20 permit 204.220.176.0/20 permit
204.220.181.105 permit 204.220.181.105 permit
@@ -1990,9 +1927,6 @@
207.211.30.128/25 permit 207.211.30.128/25 permit
207.211.31.0/25 permit 207.211.31.0/25 permit
207.211.41.113 permit 207.211.41.113 permit
207.211.132.0/25 permit
207.211.147.87 permit
207.211.159.110 permit
207.218.90.0/24 permit 207.218.90.0/24 permit
207.218.90.122 permit 207.218.90.122 permit
207.250.68.0/24 permit 207.250.68.0/24 permit
@@ -2000,8 +1934,6 @@
208.43.21.28/30 permit 208.43.21.28/30 permit
208.43.21.64/29 permit 208.43.21.64/29 permit
208.43.21.72/30 permit 208.43.21.72/30 permit
208.56.9.224 permit
208.56.13.196 permit
208.64.132.0/22 permit 208.64.132.0/22 permit
208.71.40.63 permit 208.71.40.63 permit
208.71.40.64/31 permit 208.71.40.64/31 permit
@@ -2028,7 +1960,6 @@
208.71.42.214 permit 208.71.42.214 permit
208.72.249.240/29 permit 208.72.249.240/29 permit
208.75.120.0/22 permit 208.75.120.0/22 permit
208.76.62.0/23 permit
208.76.62.0/24 permit 208.76.62.0/24 permit
208.76.63.0/24 permit 208.76.63.0/24 permit
208.82.237.96/29 permit 208.82.237.96/29 permit
@@ -2042,6 +1973,7 @@
209.43.22.0/28 permit 209.43.22.0/28 permit
209.46.117.168 permit 209.46.117.168 permit
209.46.117.179 permit 209.46.117.179 permit
209.61.151.0/24 permit
209.61.151.236 permit 209.61.151.236 permit
209.61.151.249 permit 209.61.151.249 permit
209.61.151.251 permit 209.61.151.251 permit
@@ -2188,7 +2120,6 @@
216.136.168.80/28 permit 216.136.168.80/28 permit
216.139.64.0/19 permit 216.139.64.0/19 permit
216.145.221.0/24 permit 216.145.221.0/24 permit
216.146.32.0/23 permit
216.146.32.0/24 permit 216.146.32.0/24 permit
216.146.33.0/24 permit 216.146.33.0/24 permit
216.198.0.0/18 permit 216.198.0.0/18 permit
@@ -2209,7 +2140,6 @@
223.165.120.0/23 permit 223.165.120.0/23 permit
2001:0868:0100:0600::/64 permit 2001:0868:0100:0600::/64 permit
2001:4860:4000::/36 permit 2001:4860:4000::/36 permit
2001:4860:4864::/56 permit
2001:748:100:40::2:0/112 permit 2001:748:100:40::2:0/112 permit
2001:748:400:1300::3 permit 2001:748:400:1300::3 permit
2001:748:400:1300::4 permit 2001:748:400:1300::4 permit
@@ -2227,13 +2157,10 @@
2001:748:400:3301::3 permit 2001:748:400:3301::3 permit
2001:748:400:3301::4 permit 2001:748:400:3301::4 permit
2404:6800:4000::/36 permit 2404:6800:4000::/36 permit
2404:6800:4864::/56 permit
2603:1061:14:1d0::1 permit
2607:13c0:0001:0000:0000:0000:0000:7000/116 permit 2607:13c0:0001:0000:0000:0000:0000:7000/116 permit
2607:13c0:0002:0000:0000:0000:0000:1000/116 permit 2607:13c0:0002:0000:0000:0000:0000:1000/116 permit
2607:13c0:0004:0000:0000:0000:0000:0000/116 permit 2607:13c0:0004:0000:0000:0000:0000:0000/116 permit
2607:f8b0:4000::/36 permit 2607:f8b0:4000::/36 permit
2607:f8b0:4864::/56 permit
2620:109:c003:104::/64 permit 2620:109:c003:104::/64 permit
2620:109:c003:104::215 permit 2620:109:c003:104::215 permit
2620:109:c006:104::/64 permit 2620:109:c006:104::/64 permit
@@ -2245,7 +2172,8 @@
2620:10d:c09c:400::8:1 permit 2620:10d:c09c:400::8:1 permit
2620:119:50c0:207::/64 permit 2620:119:50c0:207::/64 permit
2620:119:50c0:207::215 permit 2620:119:50c0:207::215 permit
2620:1ec:46::51 permit
2620:1ec:bdf::51 permit
2800:3f0:4000::/36 permit 2800:3f0:4000::/36 permit
2800:3f0:4864::/56 permit
49.12.4.251 permit # checks.mailcow.email 49.12.4.251 permit # checks.mailcow.email
2a01:4f8:c17:7906::10 permit # checks.mailcow.email 2a01:4f8:c17:7906::10 permit # checks.mailcow.email
@@ -3,7 +3,8 @@ rules {
backend = "http"; backend = "http";
url = "http://nginx:9081/pipe.php"; url = "http://nginx:9081/pipe.php";
selector = "reject_no_global_bl"; selector = "reject_no_global_bl";
formatter = "multipart"; formatter = "default";
meta_headers = true;
} }
RLINFO { RLINFO {
backend = "http"; backend = "http";
@@ -15,7 +16,8 @@ rules {
backend = "http"; backend = "http";
url = "http://nginx:9081/pushover.php"; url = "http://nginx:9081/pushover.php";
selector = "mailcow_rcpt"; selector = "mailcow_rcpt";
formatter = "multipart"; formatter = "json";
meta_headers = true;
} }
} }
+4 -6
View File
@@ -856,8 +856,6 @@ rspamd_config:register_symbol({
local seen_cte local seen_cte
local newline_s = newline(task) local newline_s = newline(task)
-- use the CTE add_text_footer actually applied
local new_cte = rewrite.new_cte or 'quoted-printable'
local function rewrite_ct_cb(name, hdr) local function rewrite_ct_cb(name, hdr)
if rewrite.need_rewrite_ct then if rewrite.need_rewrite_ct then
@@ -879,13 +877,13 @@ rspamd_config:register_symbol({
return return
elseif name:lower() == 'content-transfer-encoding' then elseif name:lower() == 'content-transfer-encoding' then
out[#out + 1] = string.format('%s: %s', out[#out + 1] = string.format('%s: %s',
'Content-Transfer-Encoding', new_cte) 'Content-Transfer-Encoding', 'quoted-printable')
-- update Content-Transfer-Encoding header -- update Content-Transfer-Encoding header
task:set_milter_reply({ task:set_milter_reply({
remove_headers = {['Content-Transfer-Encoding'] = 0}, remove_headers = {['Content-Transfer-Encoding'] = 0},
}) })
task:set_milter_reply({ task:set_milter_reply({
add_headers = {['Content-Transfer-Encoding'] = new_cte} add_headers = {['Content-Transfer-Encoding'] = 'quoted-printable'}
}) })
seen_cte = true seen_cte = true
return return
@@ -897,7 +895,7 @@ rspamd_config:register_symbol({
task:headers_foreach(rewrite_ct_cb, {full = true}) task:headers_foreach(rewrite_ct_cb, {full = true})
if not seen_cte and rewrite.need_rewrite_ct then if not seen_cte and rewrite.need_rewrite_ct then
out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', new_cte) out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', 'quoted-printable')
end end
-- End of headers -- End of headers
@@ -916,7 +914,7 @@ rspamd_config:register_symbol({
out_parts[#out_parts + 1] = o out_parts[#out_parts + 1] = o
out_parts[#out_parts + 1] = newline_s out_parts[#out_parts + 1] = newline_s
else else
local removePrefix = "--\x0D\x0AContent-" local removePrefix = "--\x0D\x0AContent-Type"
if string.lower(string.sub(tostring(o[1]), 1, string.len(removePrefix))) == string.lower(removePrefix) then if string.lower(string.sub(tostring(o[1]), 1, string.len(removePrefix))) == string.lower(removePrefix) then
o[1] = string.sub(tostring(o[1]), string.len("--\x0D\x0A") + 1) o[1] = string.sub(tostring(o[1]), string.len("--\x0D\x0A") + 1)
end end
+39 -34
View File
@@ -32,42 +32,47 @@ function parse_email($email) {
$a = strrpos($email, '@'); $a = strrpos($email, '@');
return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
} }
// rspamd metadata_exporter (multipart formatter): if (!function_exists('getallheaders')) {
// - $_POST['metadata'] JSON with the rspamd metadata function getallheaders() {
// - $_FILES['message'] raw RFC822 message if (!is_array($_SERVER)) {
if (empty($_POST['metadata']) || !isset($_FILES['message']) || $_FILES['message']['error'] !== UPLOAD_ERR_OK) { return array();
error_log("QUARANTINE: missing multipart parts from rspamd" . PHP_EOL); }
http_response_code(400); $headers = array();
exit; foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') {
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
}
}
return $headers;
}
} }
$meta = json_decode($_POST['metadata'], true); $raw_data_content = file_get_contents('php://input');
if (!is_array($meta)) {
error_log("QUARANTINE: cannot decode metadata JSON" . PHP_EOL);
http_response_code(400);
exit;
}
$raw_data_content = file_get_contents($_FILES['message']['tmp_name']);
$raw_data = mb_convert_encoding($raw_data_content, 'HTML-ENTITIES', "UTF-8"); $raw_data = mb_convert_encoding($raw_data_content, 'HTML-ENTITIES', "UTF-8");
$raw_size = (int)$_FILES['message']['size']; $headers = getallheaders();
$qid = $meta['qid'] ?? 'unknown'; $qid = $headers['X-Rspamd-Qid'];
$subject = $meta['subject'] ?? ''; $fuzzy = $headers['X-Rspamd-Fuzzy'];
$score = $meta['score'] ?? 0; $subject = iconv_mime_decode($headers['X-Rspamd-Subject']);
$rcpts = $meta['rcpt'] ?? array(); $score = $headers['X-Rspamd-Score'];
$user = $meta['user'] ?? 'unknown'; $rcpts = $headers['X-Rspamd-Rcpt'];
$ip = $meta['ip'] ?? 'unknown'; $user = $headers['X-Rspamd-User'];
$action = $meta['action'] ?? 'no action'; $ip = $headers['X-Rspamd-Ip'];
$sender = $meta['from'] ?? ''; $action = $headers['X-Rspamd-Action'];
$symbols = json_encode($meta['symbols'] ?? array()); $sender = $headers['X-Rspamd-From'];
$fuzzy = json_encode(is_array($meta['fuzzy'] ?? null) ? $meta['fuzzy'] : array()); $symbols = $headers['X-Rspamd-Symbols'];
$raw_size = (int)$_SERVER['CONTENT_LENGTH'];
if (empty($sender)) { if (empty($sender)) {
error_log("QUARANTINE: Unknown sender, assuming empty-env-from@localhost" . PHP_EOL); error_log("QUARANTINE: Unknown sender, assuming empty-env-from@localhost" . PHP_EOL);
$sender = 'empty-env-from@localhost'; $sender = 'empty-env-from@localhost';
} }
if ($fuzzy == 'unknown') {
$fuzzy = '[]';
}
try { try {
$max_size = (int)$redis->Get('Q_MAX_SIZE'); $max_size = (int)$redis->Get('Q_MAX_SIZE');
if (($max_size * 1048576) < $raw_size) { if (($max_size * 1048576) < $raw_size) {
@@ -89,7 +94,7 @@ catch (RedisException $e) {
$rcpt_final_mailboxes = array(); $rcpt_final_mailboxes = array();
// Loop through all rcpts // Loop through all rcpts
foreach ($rcpts as $rcpt) { foreach (json_decode($rcpts, true) as $rcpt) {
// Remove tag // Remove tag
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
@@ -153,12 +158,12 @@ foreach ($rcpts as $rcpt) {
// Loop through all found gotos // Loop through all found gotos
foreach ($gotos_array as $index => &$goto) { foreach ($gotos_array as $index => &$goto) {
error_log("RCPT RESOLVER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL); error_log("RCPT RESOVLER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL);
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');"); $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');");
$stmt->execute(array(':goto' => $goto)); $stmt->execute(array(':goto' => $goto));
$username = $stmt->fetch(PDO::FETCH_ASSOC)['username']; $username = $stmt->fetch(PDO::FETCH_ASSOC)['username'];
if (!empty($username)) { if (!empty($username)) {
error_log("RCPT RESOLVER: http pipe: mailbox found: " . $username . PHP_EOL); error_log("RCPT RESOVLER: http pipe: mailbox found: " . $username . PHP_EOL);
// Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate // Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate
if (!in_array($username, $rcpt_final_mailboxes)) { if (!in_array($username, $rcpt_final_mailboxes)) {
$rcpt_final_mailboxes[] = $username; $rcpt_final_mailboxes[] = $username;
@@ -167,21 +172,21 @@ foreach ($rcpts as $rcpt) {
else { else {
$parsed_goto = parse_email($goto); $parsed_goto = parse_email($goto);
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) { if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
error_log("RCPT RESOLVER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL); error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
} }
else { else {
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'"); $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'");
$stmt->execute(array(':goto' => $goto)); $stmt->execute(array(':goto' => $goto));
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
if ($goto_branch) { if ($goto_branch) {
error_log("RCPT RESOLVER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
$goto_branch_array = explode(',', $goto_branch); $goto_branch_array = explode(',', $goto_branch);
} else { } else {
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'"); $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
$stmt->execute(array(':domain' => $parsed_goto['domain'])); $stmt->execute(array(':domain' => $parsed_goto['domain']));
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
if ($goto_branch) { if ($goto_branch) {
error_log("RCPT RESOLVER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL); error_log("RCPT RESOVLER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL);
$goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch); $goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch);
} }
} }
@@ -203,11 +208,11 @@ foreach ($rcpts as $rcpt) {
// Force exit if loop cannot be solved // Force exit if loop cannot be solved
// Postfix does not allow for alias loops, so this should never happen. // Postfix does not allow for alias loops, so this should never happen.
$loop_c++; $loop_c++;
error_log("RCPT RESOLVER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL); error_log("RCPT RESOVLER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL);
} }
} }
catch (PDOException $e) { catch (PDOException $e) {
error_log("RCPT RESOLVER: " . $e->getMessage() . PHP_EOL); error_log("RCPT RESOVLER: " . $e->getMessage() . PHP_EOL);
http_response_code(502); http_response_code(502);
exit; exit;
} }
+33 -29
View File
@@ -32,46 +32,50 @@ function parse_email($email) {
$a = strrpos($email, '@'); $a = strrpos($email, '@');
return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
} }
// rspamd metadata_exporter (multipart formatter): metadata JSON arrives as $_POST['metadata']. if (!function_exists('getallheaders')) {
if (empty($_POST['metadata'])) { function getallheaders() {
error_log("NOTIFY: missing metadata part from rspamd" . PHP_EOL); if (!is_array($_SERVER)) {
http_response_code(400); return array();
exit; }
$headers = array();
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') {
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
}
}
return $headers;
}
} }
$meta = json_decode($_POST['metadata'], true); $headers = getallheaders();
if (!is_array($meta)) { $json_body = json_decode(file_get_contents('php://input'));
error_log("NOTIFY: cannot decode metadata JSON" . PHP_EOL);
http_response_code(400);
exit;
}
$qid = $meta['qid'] ?? 'unknown'; $qid = $headers['X-Rspamd-Qid'];
$rcpts = $meta['rcpt'] ?? array(); $rcpts = $headers['X-Rspamd-Rcpt'];
$sender = $meta['from'] ?? ''; $sender = $headers['X-Rspamd-From'];
$ip = $meta['ip'] ?? 'unknown'; $ip = $headers['X-Rspamd-Ip'];
$subject = $meta['subject'] ?? ''; $subject = iconv_mime_decode($headers['X-Rspamd-Subject']);
$messageid= $meta['message_id'] ?? ''; $messageid= $json_body->message_id;
$priority = 0; $priority = 0;
$symbols_array = $meta['symbols'] ?? array(); $symbols_array = json_decode($headers['X-Rspamd-Symbols'], true);
if (is_array($symbols_array)) { if (is_array($symbols_array)) {
foreach ($symbols_array as $symbol) { foreach ($symbols_array as $symbol) {
if (($symbol['name'] ?? null) == 'HAS_X_PRIO_ONE') { if ($symbol['name'] == 'HAS_X_PRIO_ONE') {
$priority = 1; $priority = 1;
break; break;
} }
} }
} }
$sender_address = $meta['header_from'][0] ?? ''; $sender_address = $json_body->header_from[0];
$sender_name = '-'; $sender_name = '-';
if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $sender_address, $matches)) { if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $sender_address, $matches)) {
$sender_address = $matches['address']; $sender_address = $matches['address'];
$sender_name = trim($matches['name'], '"\' '); $sender_name = trim($matches['name'], '"\' ');
} }
$to_address = $meta['header_to'][0] ?? ''; $to_address = $json_body->header_to[0];
$to_name = '-'; $to_name = '-';
if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $to_address, $matches)) { if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $to_address, $matches)) {
$to_address = $matches['address']; $to_address = $matches['address'];
@@ -81,7 +85,7 @@ if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $to_address, $matches)) {
$rcpt_final_mailboxes = array(); $rcpt_final_mailboxes = array();
// Loop through all rcpts // Loop through all rcpts
foreach ($rcpts as $rcpt) { foreach (json_decode($rcpts, true) as $rcpt) {
// Remove tag // Remove tag
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
@@ -139,12 +143,12 @@ foreach ($rcpts as $rcpt) {
// Loop through all found gotos // Loop through all found gotos
foreach ($gotos_array as $index => &$goto) { foreach ($gotos_array as $index => &$goto) {
error_log("RCPT RESOLVER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL); error_log("RCPT RESOVLER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL);
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');"); $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');");
$stmt->execute(array(':goto' => $goto)); $stmt->execute(array(':goto' => $goto));
$username = $stmt->fetch(PDO::FETCH_ASSOC)['username']; $username = $stmt->fetch(PDO::FETCH_ASSOC)['username'];
if (!empty($username)) { if (!empty($username)) {
error_log("RCPT RESOLVER: http pipe: mailbox found: " . $username . PHP_EOL); error_log("RCPT RESOVLER: http pipe: mailbox found: " . $username . PHP_EOL);
// Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate // Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate
if (!in_array($username, $rcpt_final_mailboxes)) { if (!in_array($username, $rcpt_final_mailboxes)) {
$rcpt_final_mailboxes[] = $username; $rcpt_final_mailboxes[] = $username;
@@ -153,21 +157,21 @@ foreach ($rcpts as $rcpt) {
else { else {
$parsed_goto = parse_email($goto); $parsed_goto = parse_email($goto);
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) { if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
error_log("RCPT RESOLVER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL); error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
} }
else { else {
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'"); $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'");
$stmt->execute(array(':goto' => $goto)); $stmt->execute(array(':goto' => $goto));
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
if ($goto_branch) { if ($goto_branch) {
error_log("RCPT RESOLVER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
$goto_branch_array = explode(',', $goto_branch); $goto_branch_array = explode(',', $goto_branch);
} else { } else {
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'"); $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
$stmt->execute(array(':domain' => $parsed_goto['domain'])); $stmt->execute(array(':domain' => $parsed_goto['domain']));
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
if ($goto_branch) { if ($goto_branch) {
error_log("RCPT RESOLVER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL); error_log("RCPT RESOVLER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL);
$goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch); $goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch);
} }
} }
@@ -189,11 +193,11 @@ foreach ($rcpts as $rcpt) {
// Force exit if loop cannot be solved // Force exit if loop cannot be solved
// Postfix does not allow for alias loops, so this should never happen. // Postfix does not allow for alias loops, so this should never happen.
$loop_c++; $loop_c++;
error_log("RCPT RESOLVER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL); error_log("RCPT RESOVLER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL);
} }
} }
catch (PDOException $e) { catch (PDOException $e) {
error_log("RCPT RESOLVER: " . $e->getMessage() . PHP_EOL); error_log("RCPT RESOVLER: " . $e->getMessage() . PHP_EOL);
http_response_code(502); http_response_code(502);
exit; exit;
} }
-2
View File
@@ -115,8 +115,6 @@ $template_data = [
'logo_dark_specs' => customize('get', 'main_logo_dark_specs'), 'logo_dark_specs' => customize('get', 'main_logo_dark_specs'),
'ip_check' => customize('get', 'ip_check'), 'ip_check' => customize('get', 'ip_check'),
'custom_login' => customize('get', 'custom_login'), 'custom_login' => customize('get', 'custom_login'),
'ui_themes' => customize('get', 'ui_themes'),
'ui_theme' => customize('get', 'ui_theme'),
'password_complexity' => password_complexity('get'), 'password_complexity' => password_complexity('get'),
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'], 'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
'cors_settings' => $cors_settings, 'cors_settings' => $cors_settings,
+3 -29
View File
@@ -185,9 +185,6 @@ paths:
example: example:
username: info@domain.tld username: info@domain.tld
domain: domain.tld domain: domain.tld
description: my time limited alias
validity: 8760
permanent: false
properties: properties:
username: username:
description: 'the mailbox an alias should be created for' description: 'the mailbox an alias should be created for'
@@ -195,15 +192,6 @@ paths:
domain: domain:
description: "the domain" description: "the domain"
type: string type: string
description:
description: "a description for the alias, defaults to an empty string"
type: string
validity:
description: "how many hours the alias stays valid, 1 to 87600, defaults to 8760 (one year)"
type: integer
permanent:
description: "keep the alias after it expired, defaults to false"
type: boolean
type: object type: object
summary: Create time limited alias summary: Create time limited alias
/api/v1/add/app-passwd: /api/v1/add/app-passwd:
@@ -1084,7 +1072,6 @@ paths:
password2: "*" password2: "*"
quota: "3072" quota: "3072"
force_pw_update: "1" force_pw_update: "1"
force_tfa: "1"
tls_enforce_in: "1" tls_enforce_in: "1"
tls_enforce_out: "1" tls_enforce_out: "1"
tags: ["tag1", "tag2"] tags: ["tag1", "tag2"]
@@ -1131,7 +1118,6 @@ paths:
password2: atedismonsin password2: atedismonsin
quota: "3072" quota: "3072"
force_pw_update: "1" force_pw_update: "1"
force_tfa: "1"
tls_enforce_in: "1" tls_enforce_in: "1"
tls_enforce_out: "1" tls_enforce_out: "1"
tags: ["tag1", "tag2"] tags: ["tag1", "tag2"]
@@ -1165,9 +1151,6 @@ paths:
force_pw_update: force_pw_update:
description: forces the user to update its password on first login description: forces the user to update its password on first login
type: boolean type: boolean
force_tfa:
description: force 2FA enrollment at login
type: boolean
tls_enforce_in: tls_enforce_in:
description: force inbound email tls encryption description: force inbound email tls encryption
type: boolean type: boolean
@@ -2527,7 +2510,7 @@ paths:
description: >- description: >-
Using this endpoint you can perform actions on quarantine items. It is possible to release Using this endpoint you can perform actions on quarantine items. It is possible to release
emails from quarantine into to the inbox, or learn them as ham to improve Rspamd filtering. emails from quarantine into to the inbox, or learn them as ham to improve Rspamd filtering.
You must provide the quarantine item IDs. You can get the IDs using the GET method. You must provide the quarantine item IDs. You can get the IDs using the GET method.
operationId: Edit mails in Quarantine operationId: Edit mails in Quarantine
requestBody: requestBody:
content: content:
@@ -3431,7 +3414,6 @@ paths:
- mailbox - mailbox
- active: "1" - active: "1"
force_pw_update: "0" force_pw_update: "0"
force_tfa: "0"
name: Full name name: Full name
password: "*" password: "*"
password2: "*" password2: "*"
@@ -3482,7 +3464,6 @@ paths:
attr: attr:
active: "1" active: "1"
force_pw_update: "0" force_pw_update: "0"
force_tfa: "0"
name: Full name name: Full name
authsource: mailcow authsource: mailcow
password: "" password: ""
@@ -3506,9 +3487,6 @@ paths:
force_pw_update: force_pw_update:
description: force user to change password on next login description: force user to change password on next login
type: boolean type: boolean
force_tfa:
description: force 2FA enrollment at login
type: boolean
name: name:
description: Full name of the mailbox user description: Full name of the mailbox user
type: string type: string
@@ -4903,7 +4881,6 @@ paths:
- active: "1" - active: "1"
attributes: attributes:
force_pw_update: "0" force_pw_update: "0"
force_tfa: "0"
mailbox_format: "maildir:" mailbox_format: "maildir:"
quarantine_notification: never quarantine_notification: never
sogo_access: "1" sogo_access: "1"
@@ -4920,7 +4897,6 @@ paths:
quota: 3221225472 quota: 3221225472
quota_used: 0 quota_used: 0
rl: false rl: false
sender_acl: ["otherbox@doman3.tld", "@aliasdomain.tld"]
spam_aliases: 0 spam_aliases: 0
username: info@doman3.tld username: info@doman3.tld
tags: ["tag1", "tag2"] tags: ["tag1", "tag2"]
@@ -5829,7 +5805,6 @@ paths:
- active: "1" - active: "1"
attributes: attributes:
force_pw_update: "0" force_pw_update: "0"
force_tfa: "0"
mailbox_format: "maildir:" mailbox_format: "maildir:"
quarantine_notification: never quarantine_notification: never
sogo_access: "1" sogo_access: "1"
@@ -5847,7 +5822,6 @@ paths:
quota: 3221225472 quota: 3221225472
quota_used: 0 quota_used: 0
rl: false rl: false
sender_acl: ["otherbox@domain3.tld", "@aliasdomain.tld"]
spam_aliases: 0 spam_aliases: 0
username: info@domain3.tld username: info@domain3.tld
tags: ["tag1", "tag2"] tags: ["tag1", "tag2"]
@@ -5870,7 +5844,7 @@ paths:
response: response:
value: value:
- type: "success" - type: "success"
log: ["cors", "edit", {"allowed_origins": ["*", "https://mail.mailcow.tld"], "allowed_methods": ["POST", "GET", "DELETE", "PUT"]}] log: ["cors", "edit", {"allowed_origins": ["*", "mail.mailcow.tld"], "allowed_methods": ["POST", "GET", "DELETE", "PUT"]}]
msg: "cors_headers_edited" msg: "cors_headers_edited"
description: OK description: OK
headers: { } headers: { }
@@ -5887,7 +5861,7 @@ paths:
schema: schema:
example: example:
attr: attr:
allowed_origins: ["*", "https://mail.mailcow.tld"] allowed_origins: ["*", "mail.mailcow.tld"]
allowed_methods: ["POST", "GET", "DELETE", "PUT"] allowed_methods: ["POST", "GET", "DELETE", "PUT"]
properties: properties:
attr: attr:
+9 -8
View File
@@ -1,10 +1,10 @@
/*! /*!
* Bootstrap-select v1.14.0-beta3 (https://developer.snapappointments.com/bootstrap-select) * Bootstrap-select v1.14.0-beta2 (https://developer.snapappointments.com/bootstrap-select)
* *
* Copyright 2012-2022 SnapAppointments, LLC * Copyright 2012-2021 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE) * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/ */
@-webkit-keyframes bs-notify-fadeOut { @-webkit-keyframes bs-notify-fadeOut {
0% { 0% {
opacity: 0.9; opacity: 0.9;
@@ -35,7 +35,8 @@ select.selectpicker {
display: none !important; display: none !important;
} }
.bootstrap-select { .bootstrap-select {
width: 220px; width: 220px \0;
/*IE9 and below*/
vertical-align: middle; vertical-align: middle;
} }
.bootstrap-select > .dropdown-toggle { .bootstrap-select > .dropdown-toggle {
+5 -5
View File
@@ -17,15 +17,15 @@ table.dataTable>tbody>tr.child ul.dtr-details>li {
} }
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover, table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover,
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover { table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover {
background-color: var(--mc-dtr-hover-bg); background-color: #5e5e5e;
} }
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before, table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before, table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before,
table.dataTable td.dt-control:before { table.dataTable td.dt-control:before {
background-color: var(--mc-dtr-bg) !important; background-color: #979797 !important;
border: 1.5px solid var(--mc-dtr-border) !important; border: 1.5px solid #616161 !important;
border-radius: 2px !important; border-radius: 2px !important;
color: var(--mc-dtr-color); color: #fff;
height: 1em; height: 1em;
width: 1em; width: 1em;
line-height: 1.25em; line-height: 1.25em;
@@ -37,7 +37,7 @@ table.dataTable td.dt-control:before {
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before, table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before, table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before,
table.dataTable td.dt-control:before { table.dataTable td.dt-control:before {
background-color: var(--mc-dtr-active-bg) !important; background-color: #979797 !important;
} }
table.dataTable.table-striped>tbody>tr>td { table.dataTable.table-striped>tbody>tr>td {
vertical-align: middle; vertical-align: middle;
+32 -82
View File
@@ -34,44 +34,11 @@
url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff') format('woff'); url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff') format('woff');
} }
/* theme tokens, override in css/themes/<name>/theme.css */
:root {
--mc-font-family: "Noto Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
--mc-font-size: 10.5pt;
--mc-body-bg: #fbfbfb;
--mc-primary: #0F688D;
--mc-primary-border: #0d526d;
--mc-info: #148DBC;
--mc-info-border: #127ea8;
--mc-border: #dfdfdf;
--mc-surface: #fff;
--mc-surface-alt: #f7f7f7;
--mc-muted: #959595;
--mc-hr: #ccc;
--mc-progress-bg: #d5d5d5;
--mc-placeholder: rgb(137 137 137);
--mc-btn-outline-color: #000000;
--mc-btn-outline-border: #cfcfcf;
--mc-btn-outline-hover-bg: #f0f0f0;
--mc-btn-outline-active-bg: #f0f0f0;
--mc-btn-light-active-color: #fff;
--mc-btn-light-active-bg: #555;
--mc-btn-light-active-border: #4d4d4d;
--mc-dtr-bg: #979797;
--mc-dtr-border: #616161;
--mc-dtr-color: #fff;
--mc-dtr-hover-bg: #5e5e5e;
--mc-dtr-active-bg: #979797;
--mc-login-user: #28b62c;
--mc-login-domainadmin: #ffc107;
--mc-login-admin: #ff4136;
}
body { body {
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: var(--mc-body-bg); background-color: #fbfbfb;
} }
#maxmsgsize { min-width: 80px; } #maxmsgsize { min-width: 80px; }
#slider1 .slider-selection { #slider1 .slider-selection {
@@ -119,17 +86,17 @@ body {
max-height: 1000px max-height: 1000px
} }
.nav-tabs .nav-link, .nav-tabs .nav-link.disabled, .nav-tabs .nav-link.disabled:hover, .nav-tabs .nav-link.disabled:focus { .nav-tabs .nav-link, .nav-tabs .nav-link.disabled, .nav-tabs .nav-link.disabled:hover, .nav-tabs .nav-link.disabled:focus {
border-color: var(--mc-border); border-color: #dfdfdf;
} }
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link { .nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
border-color: var(--mc-border); border-color: #dfdfdf;
border-bottom: 1px solid var(--mc-surface); border-bottom: 1px solid #ffffff;
} }
.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { .nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {
border-color: var(--mc-border); border-color: #dfdfdf;
} }
.nav-tabs { .nav-tabs {
border-bottom: 1px solid var(--mc-border); border-bottom: 1px solid #dfdfdf;
} }
.bi { .bi {
display: inline-block; display: inline-block;
@@ -197,13 +164,13 @@ body.modal-open {
padding-right: inherit !important; padding-right: inherit !important;
} }
body { body {
font-family: var(--mc-font-family); font-family: "Noto Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: var(--mc-font-size); font-size: 10.5pt;
line-height: 1.5; line-height: 1.5;
} }
html { html {
font-family: var(--mc-font-family); font-family: "Noto Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: var(--mc-font-size); font-size: 10.5pt;
line-height: 1.5; line-height: 1.5;
} }
#mailcow-alert { #mailcow-alert {
@@ -239,7 +206,7 @@ legend {
cursor: not-allowed; cursor: not-allowed;
} }
.overlay { .overlay {
background: var(--mc-surface); background: #fff;
position: absolute; position: absolute;
z-index: 10000; z-index: 10000;
top: 0; right: 0; bottom: 0; left: 0; top: 0; right: 0; bottom: 0; left: 0;
@@ -248,6 +215,9 @@ legend {
.bootstrap-select.btn-group .no-results { .bootstrap-select.btn-group .no-results {
display: none; display: none;
} }
.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
color: rgb(197, 197, 197) !important;
}
.haveibeenpwned { .haveibeenpwned {
cursor: pointer; cursor: pointer;
-webkit-user-select: none; -webkit-user-select: none;
@@ -269,7 +239,7 @@ legend {
.footer { .footer {
margin-top: 27px; margin-top: 27px;
margin-bottom: 20px; margin-bottom: 20px;
color: var(--mc-muted); color: #959595;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@@ -367,7 +337,7 @@ code {
overflow-x: auto!important; overflow-x: auto!important;
} }
.well { .well {
border: 1px solid var(--mc-border); border: 1px solid #dfdfdf;
background-color: #f9f9f9; background-color: #f9f9f9;
padding: 10px; padding: 10px;
} }
@@ -386,47 +356,47 @@ button[aria-expanded='true'] > .caret {
} }
.list-group-details { .list-group-details {
background: var(--mc-surface); background: #fff;
} }
.list-group-header { .list-group-header {
background: var(--mc-surface-alt); background: #f7f7f7;
} }
.bg-primary, .alert-primary, .btn-primary { .bg-primary, .alert-primary, .btn-primary {
background-color: var(--mc-primary) !important; background-color: #0F688D !important;
border-color: var(--mc-primary-border) !important; border-color: #0d526d !important;
} }
.bg-info, .alert-info, .btn-info { .bg-info, .alert-info, .btn-info {
background-color: var(--mc-info) !important; background-color: #148DBC !important;
border-color: var(--mc-info-border) !important; border-color: #127ea8 !important;
} }
.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary { .bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
color: var(--mc-placeholder) !important; color: rgb(137 137 137)!important;
} }
.progress { .progress {
height: 16px; height: 16px;
background-color: var(--mc-progress-bg); background-color: #d5d5d5;
} }
.btn-outline-secondary:hover { .btn-outline-secondary:hover {
background-color: var(--mc-btn-outline-hover-bg); background-color: #f0f0f0;
} }
.btn.btn-outline-secondary { .btn.btn-outline-secondary {
color: var(--mc-btn-outline-color) !important; color: #000000 !important;
border-color: var(--mc-btn-outline-border) !important; border-color: #cfcfcf !important;
} }
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show { .btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
background-color: var(--mc-btn-outline-active-bg) !important; background-color: #f0f0f0 !important;
} }
.btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle { .btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle {
color: var(--mc-btn-light-active-color); color: #fff;
background-color: var(--mc-btn-light-active-bg); background-color: #555;
background-image: none; background-image: none;
border-color: var(--mc-btn-light-active-border); border-color: #4d4d4d;
} }
.btn-check:checked+.btn-light:focus, .btn-check:active+.btn-light:focus, .btn-light:active:focus, .btn-light.active:focus, .show>.btn-light.dropdown-toggle:focus, .btn-check:checked+.btn-light:focus, .btn-check:active+.btn-light:focus, .btn-light:active:focus, .btn-light.active:focus, .show>.btn-light.dropdown-toggle:focus,
.btn-check:focus+.btn-light, .btn-light:focus { .btn-check:focus+.btn-light, .btn-light:focus {
@@ -442,26 +412,6 @@ button[aria-expanded='true'] > .caret {
text-decoration: none; text-decoration: none;
} }
/* role marker on login pages, header icons differ too (color blindness) */
.login-card::before {
content: "";
display: block;
height: 5px;
background: var(--mc-login-color);
border-radius: inherit;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.login-user {
--mc-login-color: var(--mc-login-user);
}
.login-domainadmin {
--mc-login-color: var(--mc-login-domainadmin);
}
.login-admin {
--mc-login-color: var(--mc-login-admin);
}
.hr-title { .hr-title {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -473,7 +423,7 @@ button[aria-expanded='true'] > .caret {
.hr-title::after { .hr-title::after {
content: ""; content: "";
flex: 1; flex: 1;
border-bottom: 1px solid var(--mc-hr); border-bottom: 1px solid #ccc;
} }
.hr-title:not(:empty)::before { .hr-title:not(:empty)::before {
File diff suppressed because it is too large. Load diff
+114 -155
View File
@@ -1,103 +1,62 @@
/* theme tokens, override in css/themes/<name>/dark.css */
:root {
--mc-body-bg: #1c1c1e;
--mc-body-color: #f2f2f7;
--mc-surface: #2c2c2e;
--mc-surface-alt: #282828;
--mc-text: #ccc;
--mc-text-soft: #bbb;
--mc-muted: #8e8e93;
--mc-border: #555;
--mc-tab-border: #444444;
--mc-link: #6fc7e9;
--mc-link-hover: #3daedb;
--mc-btn-bg: #5e5e5e;
--mc-btn-border: #4c4c4e;
--mc-input-bg: #424242;
--mc-input-color: #e2e2e2;
--mc-input-border: #999;
--mc-dropdown-bg: #424242;
--mc-table-bg: #3a3a3a;
--mc-table-stripe: #282828;
--mc-table-stripe-row: #424242;
--mc-table-hover: #343434;
--mc-table-border: #5c5c5c;
--mc-progress-bg: #555;
--mc-placeholder: #d4d4d4;
--mc-btn-outline-color: #e0e0e0;
--mc-btn-outline-border: #7a7a7a;
--mc-btn-outline-hover-bg: #5c5c5c;
--mc-btn-outline-active-bg: #7a7a7a;
--mc-btn-light-active-color: #f2f2f7;
--mc-btn-light-active-bg: #242424;
--mc-btn-light-active-border: #1c1c1e;
--mc-btn-dark-bg: #242424;
--mc-input-disabled-bg: #6e6e6e;
--mc-input-disabled-color: #a8a8a8;
--mc-list-header-bg: #444;
--mc-list-details-bg: #555;
--mc-chip-bg: #444;
--mc-table-child-bg: #414141;
--mc-dtr-bg: #7a7a7a;
--mc-dtr-border: #5c5c5c;
--mc-dtr-color: #e0e0e0;
--mc-dtr-hover-bg: #7a7a7a;
--mc-dtr-active-bg: #949494;
}
body { body {
background-color: var(--mc-body-bg); background-color: #1c1c1e;
color: var(--mc-body-color); color: #f2f2f7;
} }
.card { .card {
border: 1px solid var(--mc-surface); border: 1px solid #2c2c2e;
background-color: var(--mc-surface); background-color: #2c2c2e;
} }
legend { legend {
color: var(--mc-body-color); color: #f2f2f7;
} }
.card-header { .card-header {
color: var(--mc-muted); color: #8e8e93;
background-color: var(--mc-body-bg); background-color: #1c1c1e;
border-color: transparent; border-color: transparent;
} }
.card-body { .card-body {
--bs-card-color: var(--mc-text-soft); --bs-card-color: #bbb;
} }
.btn-secondary, .paginate_button, .page-link, .btn-light { .btn-secondary, .paginate_button, .page-link, .btn-light {
color: var(--mc-body-color) !important; color: #f2f2f7 !important;
background-color: var(--mc-btn-bg) !important; background-color: #5e5e5e !important;
border-color: var(--mc-btn-border) !important; border-color: #4c4c4e !important;
} }
.btn-dark { .btn-dark {
color: var(--mc-body-color) !important; color: #f2f2f7 !important;
background-color: var(--mc-btn-dark-bg) !important; background-color: #242424 !important;
border-color: var(--mc-body-bg) !important; border-color: #1c1c1e !important;
}
.btn-secondary:focus, .btn-secondary:hover, .btn-group.open .dropdown-toggle.btn-secondary {
background-color: #444444;
border-color: #4c4c4e !important;
color: #f2f2f7;
} }
.btn-check:checked+.btn-secondary, .btn-check:active+.btn-secondary, .btn-secondary:active, .btn-secondary.active, .show>.btn-secondary.dropdown-toggle { .btn-check:checked+.btn-secondary, .btn-check:active+.btn-secondary, .btn-secondary:active, .btn-secondary.active, .show>.btn-secondary.dropdown-toggle {
border-color: var(--mc-btn-bg) !important; border-color: #5e5e5e !important;
} }
.alert-secondary { .alert-secondary {
color: var(--mc-body-color) !important; color: #f2f2f7 !important;
background-color: var(--mc-btn-bg) !important; background-color: #5e5e5e !important;
border-color: var(--mc-btn-border) !important; border-color: #4c4c4e !important;
} }
.bg-secondary { .bg-secondary {
color: var(--mc-body-color) !important; color: #f2f2f7 !important;
background-color: var(--mc-btn-bg) !important; background-color: #5e5e5e !important;
} }
.alert-secondary, .alert-secondary a, .alert-secondary .alert-link { .alert-secondary, .alert-secondary a, .alert-secondary .alert-link {
color: var(--mc-body-color); color: #f2f2f7;
} }
.page-item.active .page-link { .page-item.active .page-link {
@@ -106,18 +65,18 @@ legend {
} }
.btn-secondary:focus, .btn-secondary:hover, .btn-group.open .dropdown-toggle.btn-secondary { .btn-secondary:focus, .btn-secondary:hover, .btn-group.open .dropdown-toggle.btn-secondary {
background-color: var(--mc-btn-bg); background-color: #5e5e5e;
border-color: var(--mc-btn-border) !important; border-color: #4c4c4e !important;
color: var(--mc-body-color); color: #f2f2f7;
} }
.btn-secondary:disabled, .btn-secondary.disabled { .btn-secondary:disabled, .btn-secondary.disabled {
border-color: var(--mc-btn-bg) !important; border-color: #5e5e5e !important;
} }
.modal-content { .modal-content {
--bs-modal-color: var(--mc-text-soft); --bs-modal-color: #bbb;
background-color: var(--mc-surface); background-color: #2c2c2e;
} }
.modal-header { .modal-header {
@@ -125,7 +84,7 @@ legend {
} }
.modal-title { .modal-title {
color: var(--mc-text-soft); color: #bbb;
} }
.modal .btn-close { .modal .btn-close {
@@ -133,12 +92,12 @@ legend {
} }
.navbar.bg-light { .navbar.bg-light {
background-color: var(--mc-body-bg) !important; background-color: #1c1c1e !important;
border-color: var(--mc-surface); border-color: #2c2c2e;
} }
.nav-link { .nav-link {
color: var(--mc-muted) !important; color: #8e8e93 !important;
} }
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link { .nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
@@ -146,23 +105,23 @@ legend {
} }
.nav-tabs, .nav-tabs .nav-link { .nav-tabs, .nav-tabs .nav-link {
border-color: var(--mc-tab-border) !important; border-color: #444444 !important;
} }
.nav-tabs .nav-link:not(.disabled):hover, .nav-tabs .nav-link:not(.disabled):focus, .nav-tabs .nav-link.active { .nav-tabs .nav-link:not(.disabled):hover, .nav-tabs .nav-link:not(.disabled):focus, .nav-tabs .nav-link.active {
border-bottom-color: var(--mc-body-bg) !important; border-bottom-color: #1c1c1e !important;
} }
.card .nav-tabs .nav-link:not(.disabled):hover, .card .nav-tabs .nav-link:not(.disabled):focus, .card .nav-tabs .nav-link.active { .card .nav-tabs .nav-link:not(.disabled):hover, .card .nav-tabs .nav-link:not(.disabled):focus, .card .nav-tabs .nav-link.active {
border-bottom-color: var(--mc-surface) !important; border-bottom-color: #2c2c2e !important;
} }
.table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr { .table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr {
color: var(--mc-body-color) !important; color: #f2f2f7 !important;
} }
.dropdown-menu { .dropdown-menu {
background-color: var(--mc-dropdown-bg); background-color: #424242;
border: 1px solid #282828; border: 1px solid #282828;
} }
.dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover { .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover {
@@ -173,79 +132,79 @@ legend {
color: #fff; color: #fff;
} }
.bootstrap-select>.dropdown-toggle.bs-placeholder, .bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary { .bootstrap-select>.dropdown-toggle.bs-placeholder, .bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
color: var(--mc-placeholder) !important; color: #d4d4d4 !important;
} }
tbody tr { tbody tr {
color: var(--mc-text); color: #ccc;
} }
.navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:focus, .navbar-default .navbar-nav>.open>a:hover { .navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:focus, .navbar-default .navbar-nav>.open>a:hover {
color: var(--mc-text); color: #ccc;
} }
.navbar-default .navbar-nav>.active>a, .navbar-default .navbar-nav>.active>a:focus, .navbar-default .navbar-nav>.active>a:hover { .navbar-default .navbar-nav>.active>a, .navbar-default .navbar-nav>.active>a:focus, .navbar-default .navbar-nav>.active>a:hover {
color: var(--mc-text); color: #ccc;
} }
.list-group-item { .list-group-item {
background-color: var(--mc-surface-alt); background-color: #282828;
border: 1px solid var(--mc-border); border: 1px solid #555;
} }
.table-striped>tbody>tr:nth-of-type(odd) { .table-striped>tbody>tr:nth-of-type(odd) {
background-color: var(--mc-table-stripe-row); background-color: #424242;
} }
table.dataTable>tbody>tr.child ul.dtr-details>li { table.dataTable>tbody>tr.child ul.dtr-details>li {
border-bottom: 1px solid rgba(255, 255, 255, 0.13); border-bottom: 1px solid rgba(255, 255, 255, 0.13);
} }
.label.label-last-login { .label.label-last-login {
color: var(--mc-text) !important; color: #ccc !important;
background-color: #555 !important; background-color: #555 !important;
} }
.progress { .progress {
background-color: var(--mc-progress-bg); background-color: #555;
} }
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { .h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
color: var(--mc-text); color: #ccc;
} }
div.numberedtextarea-number { div.numberedtextarea-number {
color: #999; color: #999;
} }
.well { .well {
border: 1px solid var(--mc-border); border: 1px solid #555;
background-color: var(--mc-surface-alt); background-color: #282828;
} }
pre { pre {
color: var(--mc-text); color: #ccc;
background-color: var(--mc-surface-alt); background-color: #282828;
border: 1px solid var(--mc-border); border: 1px solid #555;
} }
.form-control { .form-control {
background-color: transparent; background-color: transparent;
} }
input.form-control, textarea.form-control { input.form-control, textarea.form-control {
color: var(--mc-input-color) !important; color: #e2e2e2 !important;
background-color: var(--mc-input-bg) !important; background-color: #424242 !important;
border: 1px solid var(--mc-input-border); border: 1px solid #999;
} }
input.form-control:focus, textarea.form-control { input.form-control:focus, textarea.form-control {
background-color: var(--mc-input-bg) !important; background-color: #424242 !important;
} }
input.form-control:disabled, textarea.form-disabled { input.form-control:disabled, textarea.form-disabled {
color: var(--mc-input-disabled-color) !important; color: #a8a8a8 !important;
background-color: var(--mc-input-disabled-bg) !important; background-color: #6e6e6e !important;
} }
.input-group-addon { .input-group-addon {
color: var(--mc-text); color: #ccc;
background-color: var(--mc-input-bg) !important; background-color: #424242 !important;
border: 1px solid var(--mc-input-border); border: 1px solid #999;
} }
.input-group-text { .input-group-text {
color: var(--mc-text); color: #ccc;
background-color: #1c1c1c; background-color: #1c1c1c;
} }
.list-group-item { .list-group-item {
color: var(--mc-text); color: #ccc;
} }
.dropdown-item { .dropdown-item {
color: var(--mc-text); color: #ccc;
} }
.dropdown-item:hover { .dropdown-item:hover {
color: #616161 !important; color: #616161 !important;
@@ -255,9 +214,9 @@ input.form-control:disabled, textarea.form-disabled {
background-color: #007aff; background-color: #007aff;
} }
.form-select { .form-select {
color: var(--mc-input-color)!important; color: #e2e2e2!important;
background-color: var(--mc-input-bg)!important; background-color: #424242!important;
border: 1px solid var(--mc-input-border); border: 1px solid #999;
} }
.responsive-tabs .card-header button[data-bs-toggle="collapse"] { .responsive-tabs .card-header button[data-bs-toggle="collapse"] {
@@ -281,15 +240,15 @@ input.form-control:disabled, textarea.form-disabled {
a { a {
color: var(--mc-link); color: #6fc7e9;
text-decoration: underline; text-decoration: underline;
} }
a:hover { a:hover {
color: var(--mc-link-hover); color: #3daedb;
} }
.breadcrumb-item.active { .breadcrumb-item.active {
color: var(--mc-text); color: #ccc;
} }
@@ -314,11 +273,11 @@ a:hover {
background-color: #3a3a3a; background-color: #3a3a3a;
} }
.accordion-button { .accordion-button {
color: var(--mc-text-soft); color: #bbb;
background-color: #2c2c2c; background-color: #2c2c2c;
} }
.accordion-button:not(.collapsed) { .accordion-button:not(.collapsed) {
color: var(--mc-link); color: #6fc7e9;
background-color: #2c2c2c; background-color: #2c2c2c;
} }
.accordion-button::after { .accordion-button::after {
@@ -341,15 +300,15 @@ a:hover {
} }
.tag-box { .tag-box {
background-color: var(--mc-surface-alt); background-color: #282828;
border: 1px solid var(--mc-border); border: 1px solid #555;
} }
.tag-input { .tag-input {
color: #fff; color: #fff;
background-color: var(--mc-surface-alt); background-color: #282828;
} }
.tag-add { .tag-add {
color: var(--mc-text); color: #ccc;
} }
.tag-add:hover { .tag-add:hover {
color: #d1d1d1; color: #d1d1d1;
@@ -359,19 +318,19 @@ a:hover {
color: #fff; color: #fff;
} }
.btn-outline-secondary:hover { .btn-outline-secondary:hover {
background-color: var(--mc-btn-outline-hover-bg); background-color: #5c5c5c;
} }
.btn.btn-outline-secondary { .btn.btn-outline-secondary {
color: var(--mc-btn-outline-color) !important; color: #e0e0e0 !important;
border-color: var(--mc-btn-outline-border) !important; border-color: #7a7a7a !important;
} }
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show { .btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
background-color: var(--mc-btn-outline-active-bg) !important; background-color: #7a7a7a !important;
} }
.btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle { .btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle {
color: var(--mc-btn-light-active-color) !important; color: #f2f2f7 !important;
background-color: var(--mc-btn-light-active-bg) !important; background-color: #242424 !important;
border-color: var(--mc-btn-light-active-border) !important; border-color: #1c1c1e !important;
} }
.btn-input-missing, .btn-input-missing,
.btn-input-missing:hover, .btn-input-missing:hover,
@@ -389,16 +348,16 @@ a:hover {
} }
.list-group-details { .list-group-details {
background: var(--mc-list-details-bg); background: #555;
} }
.list-group-header { .list-group-header {
background: var(--mc-list-header-bg); background: #444;
} }
span.mail-address-item { span.mail-address-item {
background-color: var(--mc-chip-bg); background-color: #444;
border-radius: 4px; border-radius: 4px;
border: 1px solid var(--mc-border); border: 1px solid #555;
padding: 2px 7px; padding: 2px 7px;
display: inline-block; display: inline-block;
margin: 2px 6px 2px 0; margin: 2px 6px 2px 0;
@@ -406,52 +365,52 @@ span.mail-address-item {
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover, table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover,
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover { table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover {
background-color: var(--mc-dtr-hover-bg) !important; background-color: #7a7a7a !important;
} }
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before, table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before { table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before {
background-color: var(--mc-dtr-bg) !important; background-color: #7a7a7a !important;
border: 1.5px solid var(--mc-dtr-border) !important; border: 1.5px solid #5c5c5c !important;
color: var(--mc-dtr-color) !important; color: #e0e0e0 !important;
} }
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before, table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before { table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before {
background-color: var(--mc-dtr-active-bg); background-color: #949494;
} }
table.dataTable.dtr-inline.collapsed>tbody>tr>td.child, table.dataTable.dtr-inline.collapsed>tbody>tr>td.child,
table.dataTable.dtr-inline.collapsed>tbody>tr>th.child, table.dataTable.dtr-inline.collapsed>tbody>tr>th.child,
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty { table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty {
background-color: var(--mc-table-child-bg); background-color: #414141;
} }
table.table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr { table.table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr {
color: var(--mc-text) !important; color: #ccc !important;
} }
.table-secondary { .table-secondary {
--bs-table-bg: #282828; --bs-table-bg: #282828;
--bs-table-striped-bg: #343434; --bs-table-striped-bg: #343434;
--bs-table-striped-color: var(--mc-body-color); --bs-table-striped-color: #f2f2f7;
--bs-table-active-bg: #4c4c4c; --bs-table-active-bg: #4c4c4c;
--bs-table-active-color: var(--mc-body-color); --bs-table-active-color: #f2f2f7;
--bs-table-hover-bg: #3a3a3a; --bs-table-hover-bg: #3a3a3a;
--bs-table-hover-color: var(--mc-body-color); --bs-table-hover-color: #f2f2f7;
color: var(--mc-text); color: #ccc;
border-color: #3a3a3a; border-color: #3a3a3a;
} }
.table-light { .table-light {
--bs-table-bg: #3a3a3a; --bs-table-bg: #3a3a3a;
--bs-table-striped-bg: #444444; --bs-table-striped-bg: #444444;
--bs-table-striped-color: var(--mc-body-color); --bs-table-striped-color: #f2f2f7;
--bs-table-active-bg: #5c5c5c; --bs-table-active-bg: #5c5c5c;
--bs-table-active-color: var(--mc-body-color); --bs-table-active-color: #f2f2f7;
--bs-table-hover-bg: #4c4c4c; --bs-table-hover-bg: #4c4c4c;
--bs-table-hover-color: var(--mc-body-color); --bs-table-hover-color: #f2f2f7;
color: var(--mc-text); color: #ccc;
border-color: #4c4c4c; border-color: #4c4c4c;
} }
@@ -471,23 +430,23 @@ table.table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr {
.table-striped>tbody>tr:nth-of-type(odd)>td, .table-striped>tbody>tr:nth-of-type(odd)>td,
.table-striped>tbody>tr:nth-of-type(odd)>th { .table-striped>tbody>tr:nth-of-type(odd)>th {
background-color: var(--mc-table-stripe); background-color: #282828;
} }
.table-hover>tbody>tr:hover { .table-hover>tbody>tr:hover {
background-color: var(--mc-table-hover); background-color: #343434;
} }
.table>:not(caption)>*>* { .table>:not(caption)>*>* {
border-color: var(--mc-table-border); border-color: #5c5c5c;
--bs-table-color-state:var(--mc-text-soft); --bs-table-color-state:#bbb;
--bs-table-bg: var(--mc-table-bg); --bs-table-bg: #3a3a3a;
} }
.text-muted { .text-muted {
--bs-secondary-color: var(--mc-muted); --bs-secondary-color: #8e8e93;
} }
input::placeholder { input::placeholder {
color: var(--mc-muted) !important; color: #8e8e93 !important;
} }
.form-select { .form-select {
-212
View File
@@ -1,212 +0,0 @@
/* servercow theme, dark mode, bundled after mailcow core dark mode */
:root {
--sc-brown-hover: #806b55;
--sc-brown-active: #8a745c;
--sc-brown-soft: rgba(201, 180, 154, .18);
--sc-brown-tint: rgba(201, 180, 154, .08);
--sc-link: #c9b49a;
--sc-danger-text: #ff8a8a;
--sc-ink: #ece7e1;
--sc-muted: #a89f94;
--sc-placeholder: #8a8177;
--sc-bg: #171513;
--sc-surface: #211e1b;
--sc-cap: #2a2622;
--sc-addon: #2a2622;
--sc-line: #463f38;
--sc-divider: #34302b;
--sc-cap-line: #3d3732;
--sc-stripe: rgba(255, 255, 255, .025);
--sc-hover: rgba(201, 180, 154, .08);
--sc-focus-border: #8a7864;
--sc-focus: rgba(201, 180, 154, .25);
--sc-shadow: 0 0 10px 0 rgba(0, 0, 0, .5);
--bs-body-color: #ece7e1;
--bs-body-bg: #171513;
/* core dark tokens, see css/themes/mailcow-darkmode.css */
--mc-body-bg: var(--sc-bg);
--mc-body-color: var(--sc-ink);
--mc-surface: var(--sc-surface);
--mc-surface-alt: var(--sc-cap);
--mc-text: var(--sc-ink);
--mc-text-soft: var(--sc-ink);
--mc-muted: var(--sc-muted);
--mc-border: var(--sc-divider);
--mc-tab-border: var(--sc-divider);
--mc-link: var(--sc-link);
--mc-link-hover: #e0cfba;
--mc-btn-bg: var(--sc-cap);
--mc-btn-border: var(--sc-line);
--mc-input-bg: var(--sc-bg);
--mc-input-color: var(--sc-ink);
--mc-input-border: var(--sc-line);
--mc-dropdown-bg: var(--sc-surface);
--mc-table-bg: transparent;
--mc-table-stripe: var(--sc-stripe);
--mc-table-stripe-row: transparent;
--mc-table-hover: var(--sc-hover);
--mc-table-border: var(--sc-divider);
--mc-progress-bg: var(--sc-cap);
--mc-placeholder: var(--sc-placeholder);
--mc-hr: var(--sc-divider);
--mc-btn-outline-color: var(--sc-ink);
--mc-btn-outline-border: var(--sc-line);
--mc-btn-outline-hover-bg: var(--sc-brown-tint);
--mc-btn-outline-active-bg: var(--sc-brown);
--mc-btn-light-active-color: #fff;
--mc-btn-light-active-bg: var(--sc-brown);
--mc-btn-light-active-border: var(--sc-brown);
--mc-btn-dark-bg: var(--sc-brown);
--mc-input-disabled-bg: var(--sc-cap);
--mc-input-disabled-color: var(--sc-muted);
--mc-list-header-bg: var(--sc-cap);
--mc-list-details-bg: var(--sc-surface);
--mc-chip-bg: var(--sc-cap);
--mc-table-child-bg: var(--sc-surface);
--mc-dtr-bg: var(--sc-cap);
--mc-dtr-border: var(--sc-line);
--mc-dtr-color: var(--sc-link);
--mc-dtr-hover-bg: var(--sc-hover);
--mc-dtr-active-bg: var(--sc-brown-soft);
}
/* navbar */
.navbar.bg-light {
background-color: var(--sc-surface) !important;
border-bottom-color: var(--sc-green);
}
.navbar-light .navbar-nav .nav-link {
color: var(--sc-ink) !important;
}
.navbar-light .navbar-nav .nav-link:hover,
.navbar-light .navbar-nav .nav-link:focus,
.navbar-light .navbar-nav .nav-link.active,
.navbar-light .navbar-nav .show > .nav-link {
color: var(--sc-link) !important;
}
.dropdown-menu {
border-color: var(--sc-cap-line);
}
.dropdown-item:hover, .dropdown-item:focus {
color: var(--sc-link) !important;
}
/* cards and modals */
.card-header {
color: var(--sc-ink);
background-color: var(--sc-cap);
border-color: var(--sc-cap-line);
}
.modal-content {
background-color: var(--sc-surface);
}
.modal-header {
border-color: var(--sc-cap-line);
}
.input-group-text {
background-color: var(--sc-addon);
}
.form-control::file-selector-button {
color: var(--sc-ink);
background-color: var(--sc-cap);
border-color: var(--sc-line);
}
.form-control:hover:not(:disabled):not([readonly])::file-selector-button {
color: var(--sc-link);
background-color: var(--sc-line);
}
.form-check-input:not(:checked) {
background-color: var(--sc-bg);
border-color: var(--sc-line);
}
kbd {
color: var(--sc-ink);
background-color: var(--sc-cap);
}
/* buttons */
.btn-secondary:hover, .btn-secondary:focus, .btn-light:hover, .btn-light:focus, .page-link:hover {
color: var(--sc-link) !important;
background-color: var(--sc-line) !important;
}
.btn-check:checked + .btn-secondary, .btn-secondary.active, .show > .btn-secondary.dropdown-toggle,
.page-item.active .page-link {
color: #fff !important;
background-color: var(--sc-brown) !important;
border-color: var(--sc-brown) !important;
}
/* tabs */
.nav-tabs .nav-link {
color: var(--sc-ink) !important;
border-bottom-color: transparent !important;
}
.nav-tabs .nav-link:not(.disabled):hover, .nav-tabs .nav-link:not(.disabled):focus,
.card .nav-tabs .nav-link:not(.disabled):hover, .card .nav-tabs .nav-link:not(.disabled):focus {
color: var(--sc-link) !important;
border-bottom-color: transparent !important;
}
.nav-tabs .nav-link.active, .card .nav-tabs .nav-link.active {
color: var(--sc-link) !important;
border-bottom-color: var(--sc-link) !important;
}
/* semantic text */
.text-success {
color: var(--sc-green) !important;
}
.text-warning {
color: var(--sc-warning) !important;
}
/* alerts, opaque dark tints (also used for floating notifications) */
.alert-primary {
color: #e6d9c9 !important;
background-color: #3c342b !important;
border-color: #5e4f3e !important;
}
.alert-info {
color: #cfe9f6 !important;
background-color: #2f3334 !important;
border-color: #44535a !important;
}
.alert-success {
color: #cdeea0;
background-color: #2e3419;
border-color: #405515;
}
.alert-danger {
color: #f9c4c4;
background-color: #402a28;
border-color: #693a38;
}
.alert-warning {
color: #f5d496;
background-color: #3c2f19;
border-color: #654815;
}
.alert-secondary, .alert-light {
color: var(--sc-ink) !important;
background-color: var(--sc-cap) !important;
border-color: var(--sc-divider) !important;
}
.alert .btn-close {
filter: invert(1) grayscale(100%) brightness(200%);
}
/* slider */
.noUi-target {
background: var(--sc-bg);
border-color: var(--sc-line);
box-shadow: none;
}
.noUi-handle {
background: var(--sc-cap);
border-color: var(--sc-line);
box-shadow: none;
}
.noUi-handle::before, .noUi-handle::after {
background: var(--sc-line);
}
@@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -1,93 +0,0 @@
Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
-584
View File
@@ -1,584 +0,0 @@
/* servercow theme for mailcow, based on the servercow customer portal */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
font-display: swap;
src: local(''),
url('/css/themes/servercow/fonts/poppins-v24-latin_latin-ext-regular.woff2') format('woff2'),
url('/css/themes/servercow/fonts/poppins-v24-latin_latin-ext-regular.woff') format('woff');
}
@font-face {
font-family: 'Poppins';
font-style: italic;
font-weight: 400;
font-display: swap;
src: local(''),
url('/css/themes/servercow/fonts/poppins-v24-latin_latin-ext-italic.woff2') format('woff2'),
url('/css/themes/servercow/fonts/poppins-v24-latin_latin-ext-italic.woff') format('woff');
}
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 500;
font-display: swap;
src: local(''),
url('/css/themes/servercow/fonts/poppins-v24-latin_latin-ext-500.woff2') format('woff2'),
url('/css/themes/servercow/fonts/poppins-v24-latin_latin-ext-500.woff') format('woff');
}
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 600;
font-display: swap;
src: local(''),
url('/css/themes/servercow/fonts/poppins-v24-latin_latin-ext-600.woff2') format('woff2'),
url('/css/themes/servercow/fonts/poppins-v24-latin_latin-ext-600.woff') format('woff');
}
@font-face {
font-family: 'Roboto Slab';
font-style: normal;
font-weight: 400;
font-display: swap;
src: local(''),
url('/css/themes/servercow/fonts/roboto-slab-v36-cyrillic_greek_latin-regular.woff2') format('woff2'),
url('/css/themes/servercow/fonts/roboto-slab-v36-cyrillic_greek_latin-regular.woff') format('woff');
}
@font-face {
font-family: 'Roboto Slab';
font-style: normal;
font-weight: 500;
font-display: swap;
src: local(''),
url('/css/themes/servercow/fonts/roboto-slab-v36-cyrillic_greek_latin-500.woff2') format('woff2'),
url('/css/themes/servercow/fonts/roboto-slab-v36-cyrillic_greek_latin-500.woff') format('woff');
}
/* tokens */
:root {
--sc-brown: #6F5D49;
--sc-brown-hover: #5e4f3e;
--sc-brown-active: #594a3a;
--sc-brown-soft: rgba(111, 93, 73, .2);
--sc-brown-tint: rgba(111, 93, 73, .08);
--sc-link: #6F5D49;
--sc-green: #8AD608;
--sc-blue: #96D0EC;
--sc-warning: #e3950b;
--sc-danger: #ef6f6f;
--sc-danger-hover: #e85e5e;
--sc-danger-text: #c43c3c;
--sc-ink: #212529;
--sc-muted: #6c757d;
--sc-placeholder: #6c757d;
--sc-bg: #F6F8F8;
--sc-surface: #ffffff;
--sc-cap: #f7f7f7;
--sc-addon: #e9ecef;
--sc-line: #ced4da;
--sc-divider: #dee2e6;
--sc-cap-line: rgba(0, 0, 0, .175);
--sc-stripe: rgba(111, 93, 73, .04);
--sc-hover: rgba(111, 93, 73, .08);
--sc-focus-border: #b7aea4;
--sc-focus: rgba(111, 93, 73, .25);
--sc-shadow: 0 0 10px 0 rgba(134, 134, 134, .3);
--sc-font: 'Poppins', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
--sc-font-head: 'Roboto Slab', Georgia, serif;
--bs-body-color: #212529;
--bs-body-bg: #F6F8F8;
--bs-body-font-family: var(--sc-font);
/* core tokens, see css/build/014-mailcow.css */
--mc-font-family: var(--sc-font);
--mc-font-size: 10pt;
--mc-body-bg: var(--sc-bg);
--mc-primary: var(--sc-brown);
--mc-primary-border: var(--sc-brown);
--mc-info: var(--sc-blue);
--mc-info-border: var(--sc-blue);
--mc-border: var(--sc-divider);
--mc-surface: var(--sc-surface);
--mc-surface-alt: var(--sc-cap);
--mc-muted: var(--sc-muted);
--mc-hr: var(--sc-divider);
--mc-progress-bg: var(--sc-addon);
--mc-placeholder: var(--sc-placeholder);
--mc-btn-outline-color: var(--sc-ink);
--mc-btn-outline-border: var(--sc-line);
--mc-btn-outline-hover-bg: var(--sc-brown-tint);
--mc-btn-outline-active-bg: var(--sc-brown);
--mc-btn-light-active-color: #fff;
--mc-btn-light-active-bg: var(--sc-brown);
--mc-btn-light-active-border: var(--sc-brown);
--mc-dtr-bg: var(--sc-surface);
--mc-dtr-border: var(--sc-line);
--mc-dtr-color: var(--sc-brown);
--mc-dtr-hover-bg: var(--sc-brown-tint);
--mc-dtr-active-bg: var(--sc-brown-soft);
--mc-login-user: var(--sc-green);
--mc-login-domainadmin: #f5c400;
--mc-login-admin: var(--sc-danger);
}
/* base */
body {
color: var(--sc-ink);
-webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6, legend, .modal-title {
font-family: var(--sc-font-head);
font-weight: 500;
color: var(--sc-ink);
}
a {
color: var(--sc-link);
}
a:hover {
color: var(--sc-brown-hover);
}
.text-muted, .text-secondary {
color: var(--sc-muted) !important;
}
::selection {
background: var(--sc-brown-soft);
}
hr {
border-color: var(--sc-divider);
opacity: 1;
}
::placeholder {
color: var(--sc-placeholder) !important;
}
/* navbar, white with green line */
.navbar.bg-light {
background-color: var(--sc-surface) !important;
border: 0;
border-bottom: 5px solid var(--sc-green);
}
.navbar-light .navbar-nav .nav-link {
color: var(--sc-ink);
}
.navbar-light .navbar-nav .nav-link:hover,
.navbar-light .navbar-nav .nav-link:focus,
.navbar-light .navbar-nav .nav-link.active,
.navbar-light .navbar-nav .show > .nav-link {
color: var(--sc-brown);
}
@media (min-width: 992px) {
.navbar-nav .nav-link {
height: 60px;
}
}
.dropdown-menu {
border: 1px solid var(--sc-cap-line);
border-radius: 0;
box-shadow: var(--sc-shadow);
}
.dropdown-item {
color: var(--sc-ink);
}
.dropdown-item:hover, .dropdown-item:focus {
color: var(--sc-brown) !important;
background-color: var(--sc-brown-soft);
}
.dropdown-item.active, .dropdown-item:active {
color: #fff;
background-color: var(--sc-brown);
}
/* cards, white with soft shadow */
.card {
border: 0 !important;
border-radius: 0;
background-color: var(--sc-surface);
box-shadow: var(--sc-shadow);
}
.card-header, .card-header:first-child {
font-family: var(--sc-font-head);
font-weight: 500;
font-size: 1.1rem;
color: var(--sc-ink);
background-color: var(--sc-cap);
border-bottom: 1px solid var(--sc-cap-line);
border-radius: 0;
padding: .5rem 1rem;
}
.card-header .btn, .card-header .form-control, .card-header .form-select {
font-family: var(--sc-font);
font-size: .875rem;
}
.card-footer {
background-color: var(--sc-cap);
border-top: 1px solid var(--sc-cap-line);
border-radius: 0;
}
.card .card {
box-shadow: none;
border: 1px solid var(--sc-divider) !important;
}
/* colored headers become a top line */
.card:has(> .card-header.bg-danger) {
border-top: 4px solid var(--sc-danger) !important;
}
.card-header.bg-danger, .card-header.bg-success, .card-header.bg-primary, .card-header.bg-info {
color: var(--sc-ink) !important;
background-color: var(--sc-cap) !important;
}
.list-group-item {
border-color: var(--sc-divider);
}
/* modals */
.modal-content {
border: 0;
border-radius: 0;
box-shadow: 0 8px 40px rgba(0, 0, 0, .2);
}
.modal-header {
background-color: var(--sc-cap);
border-bottom: 1px solid var(--sc-cap-line);
border-radius: 0;
}
.modal-footer {
border-top: 1px solid var(--sc-divider);
}
/* buttons, flat and square */
.btn {
border-width: 1px;
border-radius: 0;
font-weight: 400;
transition: none;
}
.btn:not(.disabled):hover,
.btn:not(.disabled):active {
margin-top: 0;
border-bottom-width: 1px;
}
.btn:focus, .btn-check:focus + .btn {
box-shadow: 0 0 0 .2rem var(--sc-focus);
}
.btn-primary, .btn-primary:hover, .btn-primary:focus, .btn-primary:active {
color: #fff;
}
.btn-primary:hover, .btn-primary:focus, .btn-primary:active {
background-color: var(--sc-brown-hover) !important;
border-color: var(--sc-brown-active) !important;
}
/* main call to action, portal primary */
.btn-success, .btn-success:not(.disabled):hover, .btn-success:not(.disabled):active, .btn-success:focus,
.btn-success:disabled, .btn-success.disabled {
color: #fff;
background-color: var(--sc-brown);
border-color: var(--sc-brown);
}
.btn-success:hover, .btn-success:active, .btn-success:focus {
background-color: var(--sc-brown-hover);
border-color: var(--sc-brown-active);
}
.btn-danger, .btn-danger:not(.disabled):hover, .btn-danger:not(.disabled):active, .btn-danger:focus,
.btn-danger:disabled, .btn-danger.disabled {
color: #000;
background-color: var(--sc-danger);
border-color: var(--sc-danger);
}
.btn-danger:hover, .btn-danger:active, .btn-danger:focus {
background-color: var(--sc-danger-hover);
border-color: var(--sc-danger-hover);
}
.btn-warning, .btn-warning:not(.disabled):hover, .btn-warning:not(.disabled):active, .btn-warning:focus {
color: #000;
background-color: var(--sc-warning);
border-color: var(--sc-warning);
}
.bg-info, .alert-info, .btn-info, .badge.bg-info > a {
color: #000 !important;
}
.btn-secondary, .btn-secondary:not(.disabled):hover, .btn-secondary:not(.disabled):active, .btn-secondary:focus,
.btn-light, .btn-light:not(.disabled):hover, .btn-light:not(.disabled):active, .btn-light:focus {
color: var(--sc-ink);
background-color: var(--sc-surface);
border-color: var(--sc-line);
}
.btn-secondary:hover, .btn-secondary:active, .btn-secondary:focus,
.btn-light:hover, .btn-light:focus {
color: var(--sc-brown);
background-color: var(--sc-brown-tint);
border-color: var(--sc-line);
}
.btn-check:checked + .btn-secondary, .btn-secondary.active, .show > .btn-secondary.dropdown-toggle {
color: #fff;
background-color: var(--sc-brown);
border-color: var(--sc-brown);
}
.btn-check:checked + .btn-outline-secondary, .btn-outline-secondary.active {
color: #fff !important;
border-color: var(--sc-brown) !important;
}
.btn-outline-primary {
color: var(--sc-brown);
border-color: var(--sc-brown);
}
.btn-outline-primary:hover {
color: #fff;
background-color: var(--sc-brown);
border-color: var(--sc-brown);
}
/* active toggle, e.g. tagging options */
.btn-dark, .btn-dark:hover, .btn-dark:focus, .btn-dark:active {
color: #fff;
background-color: var(--sc-brown);
border-color: var(--sc-brown);
}
.btn-xs, .btn-group-xs > .btn {
border-radius: 0;
}
/* forms */
.form-control, .form-select {
border-color: var(--sc-line);
border-radius: 0;
box-shadow: none;
color: var(--sc-ink);
background-color: var(--sc-surface);
}
.form-control:focus, .form-select:focus {
border-color: var(--sc-focus-border);
box-shadow: 0 0 0 .25rem var(--sc-focus);
}
.form-control::file-selector-button {
color: var(--sc-ink);
background-color: var(--sc-addon);
border-color: var(--sc-line);
}
.form-control:hover:not(:disabled):not([readonly])::file-selector-button {
color: var(--sc-brown);
background-color: var(--sc-brown-soft);
}
.input-group-text {
color: var(--sc-ink);
background-color: var(--sc-addon);
border-color: var(--sc-line);
border-radius: 0;
}
.input-group > :not(:first-child), .input-group > :not(:last-child) {
border-radius: 0;
}
.form-check-input {
border-color: var(--sc-line);
}
.form-check-input:checked {
background-color: var(--sc-brown);
border-color: var(--sc-brown);
}
.form-check-input:focus {
border-color: var(--sc-focus-border);
box-shadow: 0 0 0 .25rem var(--sc-focus);
}
.bootstrap-select > .dropdown-toggle {
border-radius: 0;
}
.bootstrap-select > .dropdown-toggle.bs-placeholder,
.bootstrap-select > .dropdown-toggle.bs-placeholder:hover,
.bootstrap-select > .dropdown-toggle.bs-placeholder:focus,
.bootstrap-select > .dropdown-toggle.bs-placeholder:active {
color: var(--mc-placeholder);
}
.bootstrap-select .dropdown-menu li a.active, .bootstrap-select .dropdown-menu li a:active {
color: #fff;
background-color: var(--sc-brown);
}
div.numberedtextarea-number {
color: var(--sc-muted);
}
/* nav tabs */
.nav-tabs {
border-bottom: 1px solid var(--sc-divider);
}
.nav-tabs .nav-link,
.nav-tabs .nav-link.disabled, .nav-tabs .nav-link.disabled:hover, .nav-tabs .nav-link.disabled:focus {
margin-top: 0;
color: var(--sc-ink);
border: 0;
border-bottom: 3px solid transparent;
border-radius: 0;
background: none;
}
.nav-tabs .nav-link:not(.disabled):hover, .nav-tabs .nav-link:not(.disabled):focus {
padding-bottom: .5rem;
color: var(--sc-brown);
}
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link,
.nav-tabs .nav-link:not(.disabled).active {
padding-bottom: .5rem;
color: var(--sc-brown);
background: none;
border-bottom: 3px solid var(--sc-brown);
}
.nav-pills .nav-link {
border-radius: 0;
color: var(--sc-ink);
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
background-color: var(--sc-brown);
}
/* tables */
.table {
--bs-table-bg: transparent;
--bs-table-striped-bg: var(--sc-stripe);
--bs-table-striped-color: var(--sc-ink);
--bs-table-hover-color: var(--sc-ink);
--bs-table-hover-bg: var(--sc-hover);
color: var(--sc-ink);
border-color: var(--sc-divider);
}
.table > thead > tr > th, table.dataTable thead th {
color: var(--sc-ink);
font-weight: 600;
border-bottom: 1px solid var(--sc-line);
}
table.dataTable.table-striped > tbody > tr.odd > * {
box-shadow: inset 0 0 0 9999px var(--sc-stripe);
}
table.dataTable.table-hover > tbody > tr:hover > * {
box-shadow: inset 0 0 0 9999px var(--sc-hover);
}
.table > :not(:first-child) {
border-top: 0;
}
.dataTables_wrapper .dataTables_filter input,
.dataTables_wrapper .dataTables_length select {
border: 1px solid var(--sc-line);
border-radius: 0;
}
.pagination > li > a, .pagination > li > span, .pagination .page-link,
.pagination > li > a:hover, .pagination > li > a:focus, .pagination > li > a:active {
top: 0;
color: var(--sc-brown);
font-weight: 400;
text-transform: none;
background-color: var(--sc-surface);
border: 1px solid var(--sc-divider);
border-radius: 0 !important;
}
.pagination > li > a:hover, .pagination > li > a:focus {
background-color: var(--sc-brown-tint);
}
.pagination > .disabled > a, .pagination > .disabled > a:hover, .pagination > .disabled > a:active,
.page-item.disabled .page-link {
color: var(--sc-muted);
background-color: var(--sc-surface);
border-width: 1px;
border-color: var(--sc-divider);
}
.page-item.active .page-link {
color: #fff;
background-color: var(--sc-brown);
border-color: var(--sc-brown);
}
.breadcrumb {
border: 1px solid var(--sc-divider);
border-radius: 0;
}
/* alerts like the portal */
.alert {
border: 1px solid;
border-radius: 0;
}
.alert-primary {
color: #43382c !important;
background-color: #e2dfdb !important;
border-color: #d4cec8 !important;
}
.alert-info {
color: #3c535e !important;
background-color: #eaf6fb !important;
border-color: #e0f1f9 !important;
}
.alert-success {
color: #375603;
background-color: #e8f7ce;
border-color: #dcf3b5;
}
.alert-danger {
color: #8f4343;
background-color: #fce2e2;
border-color: #fad4d4;
}
.alert-warning {
color: #885907;
background-color: #f9eace;
border-color: #f7dfb6;
}
.alert-secondary, .alert-light {
color: var(--sc-ink);
background-color: var(--sc-cap);
border-color: var(--sc-divider);
}
.alert .alert-link {
color: inherit;
}
.alert .btn-close {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");
opacity: .5;
}
/* badges and semantic colors, readable text on top */
.badge {
font-weight: 500;
border-radius: 0;
}
.bg-success {
color: #000 !important;
background-color: var(--sc-green) !important;
}
.bg-warning {
color: #000 !important;
background-color: var(--sc-warning) !important;
}
.bg-danger {
color: #000 !important;
background-color: var(--sc-danger) !important;
}
.badge.bg-secondary, .badge.bg-light {
color: var(--sc-ink);
background-color: var(--sc-addon) !important;
}
.text-success {
color: #4f8a0b !important;
}
.text-danger {
color: var(--sc-danger-text) !important;
}
.text-warning {
color: #9a6100 !important;
}
.progress {
border-radius: 0;
}
.progress-bar {
color: #000;
background-color: var(--sc-green);
}
.progress-bar.bg-danger, .progress-bar-danger {
color: #000;
background-color: var(--sc-danger) !important;
}
.progress-bar-warning {
color: #000;
background-color: var(--sc-warning) !important;
}
/* footer */
.footer, .footer a {
color: var(--sc-muted);
}
.footer hr {
border-color: var(--sc-divider);
}
-4
View File
@@ -1,4 +0,0 @@
{
"name": "servercow",
"description": "Based on the servercow theme"
}
+7 -27
View File
@@ -380,13 +380,13 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
else { else {
$state = state_nomatch; $state = state_nomatch;
} }
$state .= '<br />' . htmlspecialchars($current[$data_field[$current['type']]]); $state .= '<br />' . $current[$data_field[$current['type']]];
} }
if ($current['type'] == 'TXT' && if ($current['type'] == 'TXT' &&
stripos($current['txt'], 'v=dmarc') === 0 && stripos($current['txt'], 'v=dmarc') === 0 &&
$record[2] == $dmarc_link) { $record[2] == $dmarc_link) {
$current['txt'] = str_replace(' ', '', $current['txt']); $current['txt'] = str_replace(' ', '', $current['txt']);
$state = htmlspecialchars($current[$data_field[$current['type']]]) . state_optional; $state = $current[$data_field[$current['type']]] . state_optional;
} }
elseif ($current['type'] == 'TXT' && elseif ($current['type'] == 'TXT' &&
stripos($current['txt'], 'v=spf') === 0 && stripos($current['txt'], 'v=spf') === 0 &&
@@ -396,7 +396,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
if (in_array($ip, $rslt) && in_array(expand_ipv6($ip6), $rslt)) { if (in_array($ip, $rslt) && in_array(expand_ipv6($ip6), $rslt)) {
$state = state_good; $state = state_good;
} }
$state .= '<br />' . htmlspecialchars($current[$data_field[$current['type']]]) . state_optional; $state .= '<br />' . $current[$data_field[$current['type']]] . state_optional;
} }
elseif ($current['type'] == 'TXT' && elseif ($current['type'] == 'TXT' &&
stripos($current['txt'], 'v=dkim') === 0 && stripos($current['txt'], 'v=dkim') === 0 &&
@@ -426,7 +426,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
if ($state == state_nomatch) { if ($state == state_nomatch) {
$state = array(); $state = array();
foreach ($currents as $current) { foreach ($currents as $current) {
$state[] = htmlspecialchars($current[$data_field[$current['type']]]); $state[] = $current[$data_field[$current['type']]];
} }
$state = implode('<br />', $state); $state = implode('<br />', $state);
} }
@@ -436,22 +436,12 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
<td>%s</td> <td>%s</td>
<td class="dns-found">%s</td> <td class="dns-found">%s</td>
<td class="dns-recommended">%s</td> <td class="dns-recommended">%s</td>
</tr>', htmlspecialchars($record[0]), htmlspecialchars($record[1]), $record[2], $state); </tr>', $record[0], $record[1], $record[2], $state);
$record[3] = explode('<br />', $state); $record[3] = explode('<br />', $state);
} }
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))) {
@@ -472,32 +462,22 @@ 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 .= $val; $dns_data .= str_replace($domain, $domain . '.', $val);
} }
} }
} }
?> ?>
</table> </table>
<a id='download-zonefile' class="btn btn-sm btn-secondary visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline mb-4" style="margin-top:10px" data-zonefile="<?=base64_encode($dns_data);?>" download='<?=htmlspecialchars($_GET['domain']);?>.txt' type='text/csv'>Download</a> <a id='download-zonefile' class="btn btn-sm btn-secondary visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline mb-4" style="margin-top:10px" data-zonefile="<?=base64_encode($dns_data);?>" download='<?=$_GET['domain'];?>.txt' type='text/csv'>Download</a>
<script> <script>
var zonefile_dl_link = document.getElementById('download-zonefile'); var zonefile_dl_link = document.getElementById('download-zonefile');
var zonefile = atob(zonefile_dl_link.getAttribute('data-zonefile')); var zonefile = atob(zonefile_dl_link.getAttribute('data-zonefile'));
-67
View File
@@ -1,67 +0,0 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
header('Content-Type: application/json');
// Admins only
if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] !== 'admin') {
http_response_code(403);
echo json_encode(array('status' => 'error', 'message' => 'access denied'));
exit;
}
$owner = $GLOBALS['MAILCOW_GIT_OWNER'];
$repo = $GLOBALS['MAILCOW_GIT_REPO'];
$current = $GLOBALS['MAILCOW_GIT_VERSION'];
// Cache key is bound to the running version, so an update invalidates it naturally
$cache_key = 'MAILCOW_UPDATE_CHECK/' . $current;
// Serve cached result if present (one GitHub call per TTL, not one per page load)
$cached = $redis->get($cache_key);
if ($cached !== false) {
echo $cached;
exit;
}
// GitHub requires a User-Agent and rate-limits unauthenticated requests to 60/h per IP
function github_get($url) {
$ch = curl_init($url);
curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERAGENT => 'mailcow-update-check',
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => array('Accept: application/vnd.github+json'),
));
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($body === false || $code !== 200) return false;
$json = json_decode($body, true);
return is_array($json) ? $json : false;
}
$latest = github_get("https://api.github.com/repos/{$owner}/{$repo}/releases/latest");
$release = github_get("https://api.github.com/repos/{$owner}/{$repo}/releases/tags/{$current}");
// Any failure (rate limit, offline, unexpected payload) -> honest error, cached briefly
if ($latest === false || $release === false ||
empty($latest['tag_name']) || empty($latest['created_at']) || empty($release['created_at'])) {
$result = json_encode(array('status' => 'error', 'message' => 'could not reach GitHub API'));
$redis->setex($cache_key, 300, $result);
echo $result;
exit;
}
$date_latest = strtotime($latest['created_at']);
$date_current = strtotime($release['created_at']);
if ($date_latest <= $date_current) {
$result = json_encode(array('status' => 'no_update'));
} else {
$result = json_encode(array('status' => 'update_available', 'latest_tag' => $latest['tag_name']));
}
// Cache the positive result for an hour
$redis->setex($cache_key, 3600, $result);
echo $result;
-68
View File
@@ -233,38 +233,6 @@ function customize($_action, $_item, $_data = null) {
'msg' => 'custom_login_modified' 'msg' => 'custom_login_modified'
); );
break; break;
case 'ui_theme':
$ui_theme = is_array($_data['ui_theme']) ? (string)reset($_data['ui_theme']) : (string)$_data['ui_theme'];
if ($ui_theme !== '' && !array_key_exists($ui_theme, customize('get', 'ui_themes'))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => 'ui_theme_invalid'
);
return false;
}
try {
if ($ui_theme === '') {
$redis->del('UI_THEME');
}
else {
$redis->set('UI_THEME', $ui_theme);
}
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => array('redis_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => 'ui_theme_modified'
);
break;
} }
break; break;
case 'delete': case 'delete':
@@ -434,42 +402,6 @@ function customize($_action, $_item, $_data = null) {
return false; return false;
} }
break; break;
case 'ui_themes':
// theme folders in css/themes/<name>/ with a theme.css
$themes = array();
foreach (glob('/web/css/themes/*/theme.css') as $theme_css) {
$theme_dir = dirname($theme_css);
$theme = basename($theme_dir);
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $theme)) {
continue;
}
$meta = json_decode(@file_get_contents($theme_dir . '/theme.json'), true);
$themes[$theme] = array(
'name' => (!empty($meta['name'])) ? $meta['name'] : $theme,
'description' => (!empty($meta['description'])) ? $meta['description'] : '',
'dark' => file_exists($theme_dir . '/dark.css')
);
}
ksort($themes);
return $themes;
break;
case 'ui_theme':
try {
$ui_theme = $redis->get('UI_THEME');
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => array('redis_error', $e)
);
return false;
}
if (empty($ui_theme) || !preg_match('/^[a-zA-Z0-9_-]+$/', $ui_theme) || !file_exists('/web/css/themes/' . $ui_theme . '/theme.css')) {
return '';
}
return $ui_theme;
break;
} }
break; break;
} }
+3 -14
View File
@@ -429,25 +429,14 @@ function domain_admin_sso($_action, $_data) {
switch ($_action) { switch ($_action) {
case 'check': case 'check':
$token = preg_replace('/[^a-zA-Z0-9-]/', '', $_data); $token = $_data;
$stmt = $pdo->prepare("SELECT `t1`.`username` FROM `da_sso` AS `t1` JOIN `admin` AS `t2` ON `t1`.`username` = `t2`.`username` WHERE `t1`.`token` = :token AND `t1`.`created` > DATE_SUB(NOW(), INTERVAL '30' SECOND) AND `t2`.`active` = 1 AND `t2`.`superadmin` = 0;"); $stmt = $pdo->prepare("SELECT `t1`.`username` FROM `da_sso` AS `t1` JOIN `admin` AS `t2` ON `t1`.`username` = `t2`.`username` WHERE `t1`.`token` = :token AND `t1`.`created` > DATE_SUB(NOW(), INTERVAL '30' SECOND) AND `t2`.`active` = 1 AND `t2`.`superadmin` = 0;");
$stmt->execute(array( $stmt->execute(array(
':token' => $token ':token' => preg_replace('/[^a-zA-Z0-9-]/', '', $token)
)); ));
$return = $stmt->fetch(PDO::FETCH_ASSOC); $return = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($return['username'])) { return empty($return['username']) ? false : $return['username'];
return false;
}
// single-use: consume the token; if a concurrent request already used it, deny
$del = $pdo->prepare("DELETE FROM `da_sso` WHERE `token` = :token");
$del->execute(array(
':token' => $token
));
if ($del->rowCount() < 1) {
return false;
}
return $return['username'];
case 'issue': case 'issue':
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
+14 -83
View File
@@ -53,39 +53,6 @@ function valid_network($network) {
function valid_hostname($hostname) { function valid_hostname($hostname) {
return filter_var($hostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME); return filter_var($hostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME);
} }
// Validates a browser-style Origin: scheme://host[:port], no path/query/fragment/credentials
function valid_origin($origin) {
$parts = parse_url($origin);
if ($parts === false || !isset($parts['scheme']) || !isset($parts['host'])) {
return false;
}
if (!in_array(strtolower($parts['scheme']), array('http', 'https'), true)) {
return false;
}
if (isset($parts['user']) || isset($parts['pass']) || isset($parts['path']) || isset($parts['query']) || isset($parts['fragment'])) {
return false;
}
$host = $parts['host'];
$host_without_brackets = trim($host, '[]');
if (!valid_hostname($host_without_brackets) && !filter_var($host_without_brackets, FILTER_VALIDATE_IP)) {
return false;
}
// Reject anything that doesn't round-trip to the exact same origin (e.g. stray trailing slash)
$port = isset($parts['port']) ? ':' . $parts['port'] : '';
$rebuilt = strtolower($parts['scheme']) . '://' . strtolower($host) . $port;
return strtolower($origin) === $rebuilt;
}
// Bring an origin into the exact form a browser sends it (scheme://host[:port], lowercase).
function normalize_cors_origin($origin) {
$origin = trim($origin);
if ($origin === '*') {
return '*';
}
if ($origin !== '' && !preg_match('~^[a-z][a-z0-9+.-]*://~i', $origin)) {
$origin = 'https://' . $origin;
}
return valid_origin($origin) ? strtolower($origin) : false;
}
// Thanks to https://stackoverflow.com/a/49373789 // Thanks to https://stackoverflow.com/a/49373789
// Validates exact ip matches and ip-in-cidr, ipv4 and ipv6 // Validates exact ip matches and ip-in-cidr, ipv4 and ipv6
function ip_acl($ip, $networks) { function ip_acl($ip, $networks) {
@@ -591,7 +558,7 @@ function logger($_data = false) {
$type = $return['type']; $type = $return['type'];
$msg = null; $msg = null;
if (isset($return['msg'])) { if (isset($return['msg'])) {
$msg = json_encode($return['msg'], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); $msg = json_encode($return['msg'], JSON_UNESCAPED_UNICODE);
} }
$call = null; $call = null;
if (isset($return['log'])) { if (isset($return['log'])) {
@@ -633,18 +600,6 @@ 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)) {
@@ -2328,13 +2283,10 @@ function cors($action, $data = null) {
return false; return false;
} }
$allowed_origins = isset($data['allowed_origins']) ? $data['allowed_origins'] : array(getBaseURL()); $allowed_origins = isset($data['allowed_origins']) ? $data['allowed_origins'] : array($_SERVER['SERVER_NAME']);
$allowed_origins = !is_array($allowed_origins) ? array_filter(array_map('trim', explode("\n", $allowed_origins))) : $allowed_origins; $allowed_origins = !is_array($allowed_origins) ? array_filter(array_map('trim', explode("\n", $allowed_origins))) : $allowed_origins;
foreach ($allowed_origins as &$origin) { foreach ($allowed_origins as $origin) {
if ($origin === '*') { if (!filter_var($origin, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) && $origin != '*') {
continue;
}
if (!valid_origin($origin)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $action, $data), 'log' => array(__FUNCTION__, $action, $data),
@@ -2342,10 +2294,7 @@ function cors($action, $data = null) {
); );
return false; return false;
} }
// browsers always send a lowercase scheme/host in the Origin header, so normalize to match
$origin = strtolower($origin);
} }
unset($origin);
$allowed_methods = isset($data['allowed_methods']) ? $data['allowed_methods'] : array('GET', 'POST', 'PUT', 'DELETE'); $allowed_methods = isset($data['allowed_methods']) ? $data['allowed_methods'] : array('GET', 'POST', 'PUT', 'DELETE');
$allowed_methods = !is_array($allowed_methods) ? array_map('trim', preg_split( "/( |,|;|\n)/", $allowed_methods)) : $allowed_methods; $allowed_methods = !is_array($allowed_methods) ? array_map('trim', preg_split( "/( |,|;|\n)/", $allowed_methods)) : $allowed_methods;
@@ -2393,35 +2342,26 @@ function cors($action, $data = null) {
); );
} }
$cors_settings = !$cors_settings ? array('allowed_origins' => getBaseURL(), 'allowed_methods' => 'GET, POST, PUT, DELETE') : $cors_settings; $cors_settings = !$cors_settings ? array('allowed_origins' => $_SERVER['SERVER_NAME'], 'allowed_methods' => 'GET, POST, PUT, DELETE') : $cors_settings;
$cors_settings['allowed_origins'] = empty($cors_settings['allowed_origins']) ? getBaseURL() : $cors_settings['allowed_origins']; $cors_settings['allowed_origins'] = empty($cors_settings['allowed_origins']) ? $_SERVER['SERVER_NAME'] : $cors_settings['allowed_origins'];
$cors_settings['allowed_methods'] = empty($cors_settings['allowed_methods']) ? 'GET, POST, PUT, DELETE, OPTION' : $cors_settings['allowed_methods']; $cors_settings['allowed_methods'] = empty($cors_settings['allowed_methods']) ? 'GET, POST, PUT, DELETE, OPTION' : $cors_settings['allowed_methods'];
return $cors_settings; return $cors_settings;
break; break;
case "set_headers": case "set_headers":
$cors_settings = cors('get'); $cors_settings = cors('get');
// normalize the stored list; it may still hold bare hostnames written before origins were validated as origins
$allowed_origins = array_filter(array_map('normalize_cors_origin', explode(',', $cors_settings['allowed_origins'])));
$origin = isset($_SERVER['HTTP_ORIGIN']) ? normalize_cors_origin($_SERVER['HTTP_ORIGIN']) : false;
// check if requested origin is in allowed origins // check if requested origin is in allowed origins
$allow_origin = null; $allowed_origins = explode(', ', $cors_settings['allowed_origins']);
if (in_array('*', $allowed_origins, true)) { $cors_settings['allowed_origins'] = $allowed_origins[0];
$allow_origin = '*'; if (in_array('*', $allowed_origins)){
} else if ($origin !== false && in_array($origin, $allowed_origins, true)) { $cors_settings['allowed_origins'] = '*';
$allow_origin = $origin; } else if (array_key_exists('HTTP_ORIGIN', $_SERVER) && in_array($_SERVER['HTTP_ORIGIN'], $allowed_origins)) {
$cors_settings['allowed_origins'] = $_SERVER['HTTP_ORIGIN'];
} }
// always allow OPTIONS for preflight request // always allow OPTIONS for preflight request
$cors_settings["allowed_methods"] = empty($cors_settings["allowed_methods"]) ? 'OPTIONS' : $cors_settings["allowed_methods"] . ', ' . 'OPTIONS'; $cors_settings["allowed_methods"] = empty($cors_settings["allowed_methods"]) ? 'OPTIONS' : $cors_settings["allowed_methods"] . ', ' . 'OPTIONS';
// a disallowed origin gets no Access-Control-Allow-Origin at all; echoing a different origin than the requesting one tells a browser nothing header('Access-Control-Allow-Origin: ' . $cors_settings['allowed_origins']);
if ($allow_origin !== null) {
header('Access-Control-Allow-Origin: ' . $allow_origin);
}
if ($allow_origin !== '*') {
// the response depends on the request origin, keep caches from mixing them up
header('Vary: Origin', false);
}
header('Access-Control-Allow-Methods: '. $cors_settings['allowed_methods']); header('Access-Control-Allow-Methods: '. $cors_settings['allowed_methods']);
header('Access-Control-Allow-Headers: Accept, Content-Type, X-Api-Key, Origin'); header('Access-Control-Allow-Headers: Accept, Content-Type, X-Api-Key, Origin');
@@ -3563,16 +3503,7 @@ 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 {
// Send a deep link to the login page for its area instead of the user login at /, header('Location: /');
// 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();
} }
+13 -57
View File
@@ -41,15 +41,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
else { else {
$username = $_SESSION['mailcow_cc_username']; $username = $_SESSION['mailcow_cc_username'];
} }
if (isset($_data["validity"])) { if (isset($_data["validity"]) && !filter_var($_data["validity"], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 87600)))) {
if (!filter_var($_data["validity"], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 87600)))) { $_SESSION['return'][] = array(
$_SESSION['return'][] = array( 'type' => 'danger',
'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => 'validity_missing'
'msg' => 'validity_missing' );
); return false;
return false;
}
} }
else { else {
// Default to 1 yr // Default to 1 yr
@@ -62,8 +60,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$permanent = 0; $permanent = 0;
} }
$domain = $_data['domain']; $domain = $_data['domain'];
// spamalias.description is NOT NULL, an absent description must not become a null insert $description = $_data['description'];
$description = $_data['description'] ?? '';
$valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain']; $valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain'];
$valid_alias_domains = user_get_alias_details($username)['alias_domains']; $valid_alias_domains = user_get_alias_details($username)['alias_domains'];
if (!empty($valid_alias_domains)) { if (!empty($valid_alias_domains)) {
@@ -750,18 +747,6 @@ 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");
@@ -2727,19 +2712,6 @@ 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',
@@ -3533,6 +3505,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
// Track affected mailboxes for SOGo update // Track affected mailboxes for SOGo update
$update_sogo_mailboxes[] = $username; $update_sogo_mailboxes[] = $username;
} }
return true;
break; break;
case 'mailbox_rename': case 'mailbox_rename':
$domain = $_data['domain']; $domain = $_data['domain'];
@@ -3855,7 +3828,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame']; $attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame'];
$attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : $is_now['rl_value']; $attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : $is_now['rl_value'];
$attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : $is_now['force_pw_update']; $attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : $is_now['force_pw_update'];
$attr["force_tfa"] = isset($_data['force_tfa']) ? intval($_data['force_tfa']) : $is_now['force_tfa'];
$attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : $is_now['sogo_access']; $attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : $is_now['sogo_access'];
$attr["active"] = isset($_data['active']) ? intval($_data['active']) : $is_now['active']; $attr["active"] = isset($_data['active']) ? intval($_data['active']) : $is_now['active'];
$attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in']; $attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in'];
@@ -5348,14 +5320,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$mailboxdata['rl_scope'] = 'domain'; $mailboxdata['rl_scope'] = 'domain';
} }
$mailboxdata['is_relayed'] = $row['backupmx']; $mailboxdata['is_relayed'] = $row['backupmx'];
// Internal send-as ACL for this mailbox, mirrors the sender_acl field accepted by edit/mailbox
$mailboxdata['sender_acl'] = array();
$stmt = $pdo->prepare("SELECT `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `external` = '0'");
$stmt->execute(array(':username' => $_data));
$sender_acl_rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($sender_acl_rows as $sender_acl_row) {
$mailboxdata['sender_acl'][] = $sender_acl_row['send_as'];
}
} }
$stmt = $pdo->prepare("SELECT `tag_name` $stmt = $pdo->prepare("SELECT `tag_name`
FROM `tags_mailbox` WHERE `username`= :username"); FROM `tags_mailbox` WHERE `username`= :username");
@@ -5683,11 +5647,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt->execute(array( $stmt->execute(array(
':username' => $username ':username' => $username
)); ));
// bind LIKE pattern + escape LIKE wildcards so the value matches literally (no SQLi, no over-match) $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $username . "/%' OR `c_uid` = :username");
$c_object_like = '%/' . addcslashes($username, '\\%_') . '/%';
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE :c_object_like OR `c_uid` = :username");
$stmt->execute(array( $stmt->execute(array(
':c_object_like' => $c_object_like,
':username' => $username ':username' => $username
)); ));
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
@@ -6083,11 +6044,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt->execute(array( $stmt->execute(array(
':username' => $username ':username' => $username
)); ));
// bind LIKE pattern + escape LIKE wildcards so the value matches literally (no SQLi, no over-match) $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . str_replace('%', '\%', $username) . "/%' OR `c_uid` = :username");
$c_object_like = '%/' . addcslashes($username, '\\%_') . '/%';
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE :c_object_like OR `c_uid` = :username");
$stmt->execute(array( $stmt->execute(array(
':c_object_like' => $c_object_like,
':username' => $username ':username' => $username
)); ));
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
@@ -6169,6 +6127,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
// Track affected mailboxes for SOGo update // Track affected mailboxes for SOGo update
$update_sogo_mailboxes[] = $username; $update_sogo_mailboxes[] = $username;
} }
return true;
break; break;
case 'mailbox_templates': case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
@@ -6243,11 +6202,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt->execute(array( $stmt->execute(array(
':username' => $name ':username' => $name
)); ));
// bind LIKE pattern + escape LIKE wildcards so the value matches literally (no SQLi, no over-match) $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $name . "/%' OR `c_uid` = :username");
$c_object_like = '%/' . addcslashes($name, '\\%_') . '/%';
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE :c_object_like OR `c_uid` = :username");
$stmt->execute(array( $stmt->execute(array(
':c_object_like' => $c_object_like,
':username' => $name ':username' => $name
)); ));
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
+8 -12
View File
@@ -22,7 +22,7 @@ function quarantine($_action, $_data = null) {
return false; return false;
} }
$stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt` $stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
WHERE `qhash` = :hash WHERE `qhash` = :hash
AND user_acl.quarantine = 1 AND user_acl.quarantine = 1
AND rcpt IN (SELECT username FROM mailbox)'); AND rcpt IN (SELECT username FROM mailbox)');
$stmt->execute(array(':hash' => $hash)); $stmt->execute(array(':hash' => $hash));
@@ -65,7 +65,7 @@ function quarantine($_action, $_data = null) {
return false; return false;
} }
$stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt` $stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
WHERE `qhash` = :hash WHERE `qhash` = :hash
AND `user_acl`.`quarantine` = 1 AND `user_acl`.`quarantine` = 1
AND `username` IN (SELECT `username` FROM `mailbox`)'); AND `username` IN (SELECT `username` FROM `mailbox`)');
$stmt->execute(array(':hash' => $hash)); $stmt->execute(array(':hash' => $hash));
@@ -170,8 +170,6 @@ function quarantine($_action, $_data = null) {
} }
elseif ($release_format == 'raw') { elseif ($release_format == 'raw') {
$detail_row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $detail_row['msg']); $detail_row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $detail_row['msg']);
// dot-stuffing per RFC5321 4.5.2: escape leading dots
$detail_row['msg'] = preg_replace('~^\.~m', '..', $detail_row['msg']);
$postfix_talk = array( $postfix_talk = array(
array('220', 'HELO quarantine' . chr(10)), array('220', 'HELO quarantine' . chr(10)),
array('250', 'MAIL FROM: ' . $sender . chr(10)), array('250', 'MAIL FROM: ' . $sender . chr(10)),
@@ -182,7 +180,7 @@ function quarantine($_action, $_data = null) {
array('221', '') array('221', '')
); );
// Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php // Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php
$smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1); $smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1);
if (!$smtp_connection) { if (!$smtp_connection) {
logger(array('return' => array( logger(array('return' => array(
array( array(
@@ -194,7 +192,7 @@ function quarantine($_action, $_data = null) {
return false; return false;
} }
for ($i=0; $i < count($postfix_talk); $i++) { for ($i=0; $i < count($postfix_talk); $i++) {
$smtp_resource = fgets($smtp_connection, 256); $smtp_resource = fgets($smtp_connection, 256);
if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) { if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) {
$ret = substr($smtp_resource, 0, 3); $ret = substr($smtp_resource, 0, 3);
$ret = (empty($ret)) ? '-' : $ret; $ret = (empty($ret)) ? '-' : $ret;
@@ -467,19 +465,17 @@ function quarantine($_action, $_data = null) {
} }
elseif ($release_format == 'raw') { elseif ($release_format == 'raw') {
$row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $row['msg']); $row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $row['msg']);
// dot-stuffing per RFC5321 4.5.2: escape leading dots
$row['msg'] = preg_replace('~^\.~m', '..', $row['msg']);
$postfix_talk = array( $postfix_talk = array(
array('220', 'HELO quarantine' . chr(10)), array('220', 'HELO quarantine' . chr(10)),
array('250', 'MAIL FROM: ' . $sender . chr(10)), array('250', 'MAIL FROM: ' . $sender . chr(10)),
array('250', 'RCPT TO: ' . $row['rcpt'] . chr(10)), array('250', 'RCPT TO: ' . $row['rcpt'] . chr(10)),
array('250', 'DATA' . chr(10)), array('250', 'DATA' . chr(10)),
array('354', $row['msg'] . chr(10) . '.' . chr(10)), array('354', str_replace("\n.", '', $row['msg']) . chr(10) . '.' . chr(10)),
array('250', 'QUIT' . chr(10)), array('250', 'QUIT' . chr(10)),
array('221', '') array('221', '')
); );
// Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php // Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php
$smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1); $smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1);
if (!$smtp_connection) { if (!$smtp_connection) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'warning', 'type' => 'warning',
@@ -489,7 +485,7 @@ function quarantine($_action, $_data = null) {
return false; return false;
} }
for ($i=0; $i < count($postfix_talk); $i++) { for ($i=0; $i < count($postfix_talk); $i++) {
$smtp_resource = fgets($smtp_connection, 256); $smtp_resource = fgets($smtp_connection, 256);
if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) { if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) {
$ret = substr($smtp_resource, 0, 3); $ret = substr($smtp_resource, 0, 3);
$ret = (empty($ret)) ? '-' : $ret; $ret = (empty($ret)) ? '-' : $ret;
@@ -837,7 +833,7 @@ function quarantine($_action, $_data = null) {
))); )));
return false; return false;
} }
$stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE `qhash` = :hash'); $stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE `qhash` = :hash');
$stmt->execute(array(':hash' => $hash)); $stmt->execute(array(':hash' => $hash));
return $stmt->fetch(PDO::FETCH_ASSOC); return $stmt->fetch(PDO::FETCH_ASSOC);
break; break;
-15
View File
@@ -22,9 +22,6 @@ if (preg_match("/debug/i", $_SERVER['REQUEST_URI'])) {
if ($_SERVER['REQUEST_URI'] == '/') { if ($_SERVER['REQUEST_URI'] == '/') {
$css_minifier->add('/web/css/site/index.css'); $css_minifier->add('/web/css/site/index.css');
} }
if (file_exists('/web/css/themes/'.$UI_THEME.'/theme.css')) {
$css_minifier->add('/web/css/themes/'.$UI_THEME.'/theme.css');
}
$hash = $css_minifier->getDataHash(); $hash = $css_minifier->getDataHash();
$CSSPath = '/tmp/' . $hash . '.css'; $CSSPath = '/tmp/' . $hash . '.css';
@@ -33,17 +30,6 @@ if(!file_exists($CSSPath)) {
cleanupCSS($hash); cleanupCSS($hash);
} }
// dark mode bundle, core dark mode + theme dark.css
$dark_minifier = new CSSminifierExtended('/web/css/themes/mailcow-darkmode.css');
if (file_exists('/web/css/themes/'.$UI_THEME.'/dark.css')) {
$dark_minifier->add('/web/css/themes/'.$UI_THEME.'/dark.css');
}
$DarkCSSPath = '/tmp/' . $hash . '-' . $dark_minifier->getDataHash() . '.css';
if(!file_exists($DarkCSSPath)) {
$dark_minifier->minify($DarkCSSPath);
}
$dark_css_path = '/cache/' . basename($DarkCSSPath);
$mailcow_apps_processed = $MAILCOW_APPS; $mailcow_apps_processed = $MAILCOW_APPS;
$app_links = customize('get', 'app_links'); $app_links = customize('get', 'app_links');
$app_links_processed = $app_links; $app_links_processed = $app_links;
@@ -91,7 +77,6 @@ $globalVariables = [
'dual_login' => @$_SESSION['dual-login'], 'dual_login' => @$_SESSION['dual-login'],
'ui_texts' => $UI_TEXTS, 'ui_texts' => $UI_TEXTS,
'css_path' => '/cache/'.basename($CSSPath), 'css_path' => '/cache/'.basename($CSSPath),
'dark_css_path' => $dark_css_path,
'logo' => customize('get', 'main_logo'), 'logo' => customize('get', 'main_logo'),
'logo_dark' => customize('get', 'main_logo_dark'), 'logo_dark' => customize('get', 'main_logo_dark'),
'available_languages' => $AVAILABLE_LANGUAGES, 'available_languages' => $AVAILABLE_LANGUAGES,
+2 -5
View File
@@ -4,7 +4,7 @@ function init_db_schema()
try { try {
global $pdo; global $pdo;
$db_version = "18082026_1200"; $db_version = "19022026_1220";
$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,8 +719,7 @@ 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(
@@ -1465,8 +1464,6 @@ function init_db_schema()
"pop3_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['pop3_access']), "pop3_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['pop3_access']),
"smtp_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['smtp_access']), "smtp_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['smtp_access']),
"sieve_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sieve_access']), "sieve_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sieve_access']),
"eas_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['eas_access']),
"dav_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['dav_access']),
"acl_spam_alias" => 1, "acl_spam_alias" => 1,
"acl_tls_policy" => 1, "acl_tls_policy" => 1,
"acl_spam_score" => 1, "acl_spam_score" => 1,
+1 -7
View File
@@ -319,16 +319,10 @@ $css_minifier = new CSSminifierExtended();
$css_dir = array_diff(scandir('/web/css/build'), array('..', '.')); $css_dir = array_diff(scandir('/web/css/build'), array('..', '.'));
// get customized ui data // get customized ui data
$UI_TEXTS = customize('get', 'ui_texts'); $UI_TEXTS = customize('get', 'ui_texts');
// theme selected in admin UI overrides $UI_THEME
if ($ui_theme_selected = customize('get', 'ui_theme')) {
$UI_THEME = $ui_theme_selected;
}
// minify bootstrap theme // minify bootstrap theme
if (file_exists('/web/css/themes/'.$UI_THEME.'/bootstrap.css')) if (file_exists('/web/css/themes/'.$UI_THEME.'-bootstrap.css'))
$css_minifier->add('/web/css/themes/'.$UI_THEME.'/bootstrap.css');
elseif (file_exists('/web/css/themes/'.$UI_THEME.'-bootstrap.css'))
$css_minifier->add('/web/css/themes/'.$UI_THEME.'-bootstrap.css'); $css_minifier->add('/web/css/themes/'.$UI_THEME.'-bootstrap.css');
else else
$css_minifier->add('/web/css/themes/lumen-bootstrap.css'); $css_minifier->add('/web/css/themes/lumen-bootstrap.css');
-7
View File
@@ -112,7 +112,6 @@ $AVAILABLE_LANGUAGES = array(
'sv-se' => 'Svenska (Swedish)', 'sv-se' => 'Svenska (Swedish)',
'tr-tr' => 'Türkçe (Turkish)', 'tr-tr' => 'Türkçe (Turkish)',
'uk-ua' => 'Українська (Ukrainian)', 'uk-ua' => 'Українська (Ukrainian)',
'uz-uz' => 'Oʻzbekcha (Uzbek)',
'vi-vn' => 'Tiếng Việt (Vietnamese)', 'vi-vn' => 'Tiếng Việt (Vietnamese)',
'zh-cn' => '简体中文 (Simplified Chinese)', 'zh-cn' => '简体中文 (Simplified Chinese)',
'zh-tw' => '繁體中文 (Traditional Chinese)', 'zh-tw' => '繁體中文 (Traditional Chinese)',
@@ -121,8 +120,6 @@ $AVAILABLE_LANGUAGES = array(
// default theme is lumen // default theme is lumen
// additional themes can be found here: https://bootswatch.com/ // additional themes can be found here: https://bootswatch.com/
// copy them to data/web/css/themes/{THEME-NAME}-bootstrap.css // copy them to data/web/css/themes/{THEME-NAME}-bootstrap.css
// or use a theme folder data/web/css/themes/{THEME-NAME}/ (theme.css, optional dark.css, bootstrap.css, theme.json)
// theme folders can be selected in System > Configuration > Customize, which overrides $UI_THEME
$UI_THEME = "lumen"; $UI_THEME = "lumen";
// Show DKIM private keys - false by default // Show DKIM private keys - false by default
@@ -248,10 +245,6 @@ $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
+3 -3
View File
@@ -1,7 +1,7 @@
/*! /*!
* Bootstrap v5.3.8 (https://getbootstrap.com/) * Bootstrap v5.1.3 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/ */
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,(t,e)=>`#${CSS.escape(e)}`)),t),s=t=>null==t?`${t}`:Object.prototype.toString.call(t).match(/\s([a-z]+)/i)[1].toLowerCase(),o=t=>{t.dispatchEvent(new Event(i))},r=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),a=t=>r(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,l=t=>{if(!r(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",()=>{for(const t of p)t()}),p.push(e)):e()},_=(t,e=[],i=t)=>"function"==typeof t?t.call(...e):i,b=(t,e,n=!0)=>{if(!n)return void _(t);const s=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),_(t))};e.addEventListener(i,a),setTimeout(()=>{r||o(e)},s)},v=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,A=/::\d+$/,E={};let T=1;const C={mouseenter:"mouseover",mouseleave:"mouseout"},O=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function x(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function k(t){const e=x(t);return t.uidEvent=e,E[e]=E[e]||{},E[e]}function L(t,e,i=null){return Object.values(t).find(t=>t.callable===e&&t.delegationSelector===i)}function S(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=N(t);return O.has(o)||(o=t),[n,s,o]}function D(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=S(e,i,n);if(e in C){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=k(t),c=l[a]||(l[a]={}),h=L(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=x(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return j(s,{delegateTarget:r}),n.oneOff&&P.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return j(n,{delegateTarget:t}),i.oneOff&&P.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function $(t,e,i,n,s){const o=L(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function I(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&$(t,e,i,r.callable,r.delegationSelector)}function N(t){return t=t.replace(w,""),C[t]||t}const P={on(t,e,i,n){D(t,e,i,n,!1)},one(t,e,i,n){D(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=k(t),c=l[rLine truncated !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t="transitionend",e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=(t,e,i)=>{Object.keys(i).forEach((n=>{const s=i[n],r=e[n],a=r&&o(r)?"element":null==(l=r)?`${l}`:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}))},l=t=>!(!o(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",(()=>{p.forEach((t=>t()))})),p.push(e)):e()},_=t=>{"function"==typeof t&&t()},b=(e,i,n=!0)=>{if(!n)return void _(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),_(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},v=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,E=/::\d+$/,A={};let T=1;const O={mouseenter:"mouseover",mouseleave:"mouseout"},C=/^(mouseenter|mouseleave)/i,k=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function x(t){const e=L(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function D(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;s<o;s++){const o=t[n[s]];if(o.originalHandler===e&&o.delegationSelector===i)return o}return null}function S(t,e,i){const n="string"==typeof e,s=n?i:e;let o=P(t);return k.has(o)||(o=t),[n,s,o]}function N(t,e,i,n,s){if("string"!=typeof e||!t)return;if(i||(i=n,n=null),C.test(e)){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=S(e,i,n),l=x(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=L(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&j.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&j.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function I(t,e,i,n,s){const o=D(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function P(t){return t=t.replace(w,""),O[t]||t}const j={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!Line truncated
//# sourceMappingURL=bootstrap.bundle.min.js.map //# sourceMappingURL=bootstrap.bundle.min.js.map
+1 -4
View File
@@ -124,9 +124,6 @@ $(document).ready(function() {
var lastTab = localStorage.getItem(key); var lastTab = localStorage.getItem(key);
if (lastTab) { if (lastTab) {
$('[data-bs-target="#' + lastTab + '"]').click(); $('[data-bs-target="#' + lastTab + '"]').click();
var dropdown = $('[data-bs-target="#' + lastTab + '"]').closest('.dropdown');
dropdown.find('.dropdown-menu').removeClass('show');
dropdown.find('.dropdown-toggle').attr('aria-expanded', 'false');
var tab = $('[id^="' + lastTab + '"]'); var tab = $('[id^="' + lastTab + '"]');
$(tab).find('.card-body.collapse:first').collapse('show'); $(tab).find('.card-body.collapse:first').collapse('show');
} }
@@ -350,7 +347,7 @@ $(document).ready(function() {
if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_dark.png'); if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_dark.png');
localStorage.setItem('mailcow_theme', 'light'); localStorage.setItem('mailcow_theme', 'light');
}else{ }else{
$('head').append('<link id="dark-mode-theme" rel="stylesheet" type="text/css" href="' + mailcow_dark_css + '">'); $('head').append('<link id="dark-mode-theme" rel="stylesheet" type="text/css" href="/css/themes/mailcow-darkmode.css">');
$('#dark-mode-toggle').prop('checked', true); $('#dark-mode-toggle').prop('checked', true);
$('.main-logo').addClass('d-none'); $('.main-logo').addClass('d-none');
$('.main-logo-dark').removeClass('d-none'); $('.main-logo-dark').removeClass('d-none');
+33 -27
View File
@@ -30,7 +30,7 @@ $(document).ready(function() {
// create host cpu and mem charts // create host cpu and mem charts
createHostCpuAndMemChart(); createHostCpuAndMemChart();
// check for new version // check for new version
if (mailcow_info.branch === "master" && mailcow_cc_role === "admin"){ if (mailcow_info.branch === "master"){
check_update(mailcow_info.version_tag, mailcow_info.project_url); check_update(mailcow_info.version_tag, mailcow_info.project_url);
} }
$("#mailcow_version").click(function(){ $("#mailcow_version").click(function(){
@@ -1089,8 +1089,6 @@ jQuery(function($){
return str; return str;
}).join('<br>\n'); }).join('<br>\n');
item.subject = escapeHtml(item.subject); item.subject = escapeHtml(item.subject);
if (item.sender_mime != null) item.sender_mime = escapeHtml(item.sender_mime);
if (item['message-id'] != null) item['message-id'] = escapeHtml(item['message-id']);
var scan_time = item.time_real.toFixed(3); var scan_time = item.time_real.toFixed(3);
if (item.time_virtual) { if (item.time_virtual) {
scan_time += ' / ' + item.time_virtual.toFixed(3); scan_time += ' / ' + item.time_virtual.toFixed(3);
@@ -1169,7 +1167,6 @@ jQuery(function($){
if (item === null) { return true; } if (item === null) { return true; }
item.user = escapeHtml(item.user); item.user = escapeHtml(item.user);
item.call = escapeHtml(item.call); item.call = escapeHtml(item.call);
if (item.msg) item.msg = escapeHtml(item.msg);
item.task = '<code>' + item.task + '</code>'; item.task = '<code>' + item.task + '</code>';
item.type = '<span class="badge fs-6 bg-' + item.type + '">' + item.type + '</span>'; item.type = '<span class="badge fs-6 bg-' + item.type + '">' + item.type + '</span>';
}); });
@@ -1177,7 +1174,6 @@ jQuery(function($){
$.each(data, function (i, item) { $.each(data, function (i, item) {
if (item === null) { return true; } if (item === null) { return true; }
item.username = escapeHtml(item.username); item.username = escapeHtml(item.username);
item.real_rip = escapeHtml(item.real_rip);
item.service = '<div class="badge fs-6 bg-secondary">' + item.service.toUpperCase() + '</div>'; item.service = '<div class="badge fs-6 bg-secondary">' + item.service.toUpperCase() + '</div>';
}); });
} else if (table == 'general_syslog') { } else if (table == 'general_syslog') {
@@ -1216,11 +1212,6 @@ jQuery(function($){
}); });
} else if (table == 'rllog') { } else if (table == 'rllog') {
$.each(data, function (i, item) { $.each(data, function (i, item) {
if (item.header_from != null) item.header_from = escapeHtml(item.header_from);
if (item.header_subject != null) item.header_subject = escapeHtml(item.header_subject);
if (item.from != null) item.from = escapeHtml(item.from);
if (item.rcpt != null) item.rcpt = escapeHtml(item.rcpt);
if (item.message_id != null) item.message_id = escapeHtml(item.message_id);
if (item.user == null) { if (item.user == null) {
item.user = "none"; item.user = "none";
} }
@@ -1678,26 +1669,41 @@ function createHostCpuAndMemChart(){
function check_update(current_version, github_repo_url){ function check_update(current_version, github_repo_url){
if (!current_version || !github_repo_url) return false; if (!current_version || !github_repo_url) return false;
var github_account = github_repo_url.split("/")[3];
var github_repo_name = github_repo_url.split("/")[4];
window.fetch("/inc/ajax/update_check.php", {method:'GET',cache:'no-cache'}).then(function(response) { // get details about latest release
if (!response.ok) throw new Error("update check returned " + response.status); window.fetch("https://api.github.com/repos/"+github_account+"/"+github_repo_name+"/releases/latest", {method:'GET',cache:'no-cache'}).then(function(response) {
return response.json(); return response.json();
}).then(function(data) { }).then(function(latest_data) {
if (data.status === "no_update") { // get details about current release
$("#mailcow_update").removeClass("text-warning text-danger").addClass("text-success"); window.fetch("https://api.github.com/repos/"+github_account+"/"+github_repo_name+"/releases/tags/"+current_version, {method:'GET',cache:'no-cache'}).then(function(response) {
$("#mailcow_update").html("<b>" + lang_debug.no_update_available + "</b>"); return response.json();
} else if (data.status === "update_available") { }).then(function(current_data) {
$("#mailcow_update").removeClass("text-danger text-success").addClass("text-warning"); // compare releases
$("#mailcow_update").html(lang_debug.update_available + ` <a href="#" id="mailcow_update_changelog">`+data.latest_tag+`</a>`); var date_current = new Date(current_data.created_at);
$("#mailcow_update_changelog").click(function(){ var date_latest = new Date(latest_data.created_at);
if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin") if (date_latest.getTime() <= date_current.getTime()){
return; // no update available
$("#mailcow_update").removeClass("text-warning text-danger").addClass("text-success");
$("#mailcow_update").html("<b>" + lang_debug.no_update_available + "</b>");
} else {
// update available
$("#mailcow_update").removeClass("text-danger text-success").addClass("text-warning");
$("#mailcow_update").html(lang_debug.update_available + ` <a href="#" id="mailcow_update_changelog">`+latest_data.tag_name+`</a>`);
$("#mailcow_update_changelog").click(function(){
if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin")
return;
showVersionModal("New Release " + data.latest_tag, data.latest_tag); showVersionModal("New Release " + latest_data.tag_name, latest_data.tag_name);
}) })
} else { }
throw new Error(data.message || "update check failed"); }).catch(err => {
} // err
console.log(err);
$("#mailcow_update").removeClass("text-success text-warning").addClass("text-danger");
$("#mailcow_update").html("<b>"+ lang_debug.update_failed +"</b>");
});
}).catch(err => { }).catch(err => {
// err // err
console.log(err); console.log(err);
-12
View File
@@ -424,11 +424,6 @@ $(document).ready(function() {
} else { } else {
$('#force_pw_update').prop('checked', false); $('#force_pw_update').prop('checked', false);
} }
if (template.force_tfa == 1){
$('#force_tfa').prop('checked', true);
} else {
$('#force_tfa').prop('checked', false);
}
if (template.sogo_access == 1){ if (template.sogo_access == 1){
$('#sogo_access').prop('checked', true); $('#sogo_access').prop('checked', true);
} else { } else {
@@ -996,7 +991,6 @@ jQuery(function($){
'style="min-width:2em;width:' + item.percent_in_use + '%">' + item.percent_in_use + '%' + '</div></div>' 'style="min-width:2em;width:' + item.percent_in_use + '%">' + item.percent_in_use + '%' + '</div></div>'
}; };
item.username = escapeHtml(item.username); item.username = escapeHtml(item.username);
item.name = escapeHtml(item.name);
if (Array.isArray(item.tags)){ if (Array.isArray(item.tags)){
var tags = ''; var tags = '';
@@ -1248,7 +1242,6 @@ jQuery(function($){
item.attributes.eas_access = '<i class="text-' + (item.attributes.eas_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.eas_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.eas_access == 1 ? '1' : '0') + '</span></i>'; item.attributes.eas_access = '<i class="text-' + (item.attributes.eas_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.eas_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.eas_access == 1 ? '1' : '0') + '</span></i>';
item.attributes.dav_access = '<i class="text-' + (item.attributes.dav_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.dav_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.dav_access == 1 ? '1' : '0') + '</span></i>'; item.attributes.dav_access = '<i class="text-' + (item.attributes.dav_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.dav_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.dav_access == 1 ? '1' : '0') + '</span></i>';
item.attributes.sogo_access = '<i class="text-' + (item.attributes.sogo_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sogo_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sogo_access == 1 ? '1' : '0') + '</span></i>'; item.attributes.sogo_access = '<i class="text-' + (item.attributes.sogo_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sogo_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sogo_access == 1 ? '1' : '0') + '</span></i>';
item.attributes.force_tfa = '<i class="text-' + (item.attributes.force_tfa == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.force_tfa == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.force_tfa == 1 ? '1' : '0') + '</span></i>';
if (item.attributes.quarantine_notification === 'never') { if (item.attributes.quarantine_notification === 'never') {
item.attributes.quarantine_notification = lang.never; item.attributes.quarantine_notification = lang.never;
} else if (item.attributes.quarantine_notification === 'hourly') { } else if (item.attributes.quarantine_notification === 'hourly') {
@@ -1392,11 +1385,6 @@ jQuery(function($){
return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>'; return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
} }
}, },
{
title: lang.force_tfa,
data: 'attributes.force_tfa',
defaultContent: ''
},
{ {
title: lang_edit.ratelimit, title: lang_edit.ratelimit,
data: 'attributes.ratelimit', data: 'attributes.ratelimit',
+4 -8
View File
@@ -102,21 +102,18 @@ jQuery(function($){
{ {
title: 'ID', title: 'ID',
data: 'id', data: 'id',
defaultContent: '', defaultContent: ''
render: $.fn.dataTable.render.text()
}, },
{ {
title: lang.qid, title: lang.qid,
data: 'qid', data: 'qid',
defaultContent: '', defaultContent: ''
render: $.fn.dataTable.render.text()
}, },
{ {
title: lang.sender, title: lang.sender,
data: 'sender', data: 'sender',
className: 'senders-mw220', className: 'senders-mw220',
defaultContent: '', defaultContent: ''
render: $.fn.dataTable.render.text()
}, },
{ {
title: lang.subj, title: lang.subj,
@@ -131,8 +128,7 @@ jQuery(function($){
{ {
title: lang.rcpt, title: lang.rcpt,
data: 'rcpt', data: 'rcpt',
defaultContent: '', defaultContent: ''
render: $.fn.dataTable.render.text()
}, },
{ {
title: lang.danger, title: lang.danger,
+5 -8
View File
@@ -48,13 +48,13 @@ jQuery(function($){
url: "/api/v1/get/mailq/all", url: "/api/v1/get/mailq/all",
dataSrc: function(data){ dataSrc: function(data){
$.each(data, function (i, item) { $.each(data, function (i, item) {
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="mailqitems" name="multi_select" value="' + escapeHtml(item.queue_id) + '" />'; item.chkbox = '<input type="checkbox" class="form-check-input" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />';
rcpts = $.map(item.recipients, function(i) { rcpts = $.map(item.recipients, function(i) {
return escapeHtml(i); return escapeHtml(i);
}); });
item.recipients = rcpts.join('<hr style="margin:1px!important">'); item.recipients = rcpts.join('<hr style="margin:1px!important">');
item.action = '<div class="btn-group">' + item.action = '<div class="btn-group">' +
'<a href="#" data-bs-toggle="modal" data-bs-target="#showQueuedMsg" data-queue-id="' + escapeHtml(item.queue_id) + '" class="btn btn-xs btn-secondary">' + lang.show_message + '</a>' + '<a href="#" data-bs-toggle="modal" data-bs-target="#showQueuedMsg" data-queue-id="' + encodeURI(item.queue_id) + '" class="btn btn-xs btn-secondary">' + lang.show_message + '</a>' +
'</div>'; '</div>';
}); });
return data; return data;
@@ -79,14 +79,12 @@ jQuery(function($){
{ {
title: 'QID', title: 'QID',
data: 'queue_id', data: 'queue_id',
defaultContent: '', defaultContent: ''
render: $.fn.dataTable.render.text()
}, },
{ {
title: 'Queue', title: 'Queue',
data: 'queue_name', data: 'queue_name',
defaultContent: '', defaultContent: ''
render: $.fn.dataTable.render.text()
}, },
{ {
title: lang_admin.arrival_time, title: lang_admin.arrival_time,
@@ -108,8 +106,7 @@ jQuery(function($){
{ {
title: lang_admin.sender, title: lang_admin.sender,
data: 'sender', data: 'sender',
defaultContent: '', defaultContent: ''
render: $.fn.dataTable.render.text()
}, },
{ {
title: lang_admin.recipients, title: lang_admin.recipients,
-3
View File
@@ -2038,9 +2038,6 @@ if (isset($_GET['query'])) {
case "custom_login": case "custom_login":
process_edit_return(customize('edit', 'custom_login', $attr)); process_edit_return(customize('edit', 'custom_login', $attr));
break; break;
case "ui_theme":
process_edit_return(customize('edit', 'ui_theme', $attr));
break;
case "self": case "self":
if ($_SESSION['mailcow_cc_role'] == "domainadmin") { if ($_SESSION['mailcow_cc_role'] == "domainadmin") {
process_edit_return(domain_admin('edit', $attr)); process_edit_return(domain_admin('edit', $attr));
-17
View File
@@ -1,17 +0,0 @@
{
"acl": {
"login_as": "E-poçt qutusu istifadəçisi olaraq daxil ol",
"alias_domains": "Alternativ domenlər əlavə et",
"app_passwds": "Tətbiq parollarını idarə et",
"bcc_maps": "BCC yönləndirmə xəritələri",
"delimiter_action": "Ayırıcı əməliyyatı",
"domain_desc": "Domen təsvirini dəyiş",
"domain_relayhost": "Domen üçün relay serveri dəyiş",
"eas_reset": "EAS cihazlarını sıfırla",
"extend_sender_acl": "Göndərən ACL-ni xarici ünvanlarla genişləndirməyə icazə ver",
"filters": "Filtrlər",
"mailbox_relayhost": "E-poçt qutusu üçün relay serveri dəyiş",
"prohibited": "ACL tərəfindən məhdudlaşdırılıb",
"protocol_access": "Protokol girişini dəyiş"
}
}
+14 -25
View File
@@ -1,7 +1,6 @@
{ {
"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)",
@@ -14,7 +13,7 @@
"mailbox_relayhost": "Relayhost für eine Mailbox setzen", "mailbox_relayhost": "Relayhost für eine Mailbox setzen",
"prohibited": "Untersagt durch Richtlinie", "prohibited": "Untersagt durch Richtlinie",
"protocol_access": "Ändern der erlaubten Protokolle", "protocol_access": "Ändern der erlaubten Protokolle",
"pushover": "Pushover-Berechtigung", "pushover": "Pushover",
"pw_reset": "Verwalten der E-Mail zur Passwortwiederherstellung erlauben", "pw_reset": "Verwalten der E-Mail zur Passwortwiederherstellung erlauben",
"quarantine": "Quarantäne-Aktionen", "quarantine": "Quarantäne-Aktionen",
"quarantine_attachments": "Anhänge aus Quarantäne", "quarantine_attachments": "Anhänge aus Quarantäne",
@@ -97,7 +96,7 @@
"relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> Transport-Maps können erstellt werden, um individuelle Ziele für eine Relay-Domain zu definieren.", "relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> Transport-Maps können erstellt werden, um individuelle Ziele für eine Relay-Domain zu definieren.",
"relay_unknown_only": "Nur nicht-lokale Mailboxen relayen. Existente Mailboxen werden weiterhin lokal zugestellt.", "relay_unknown_only": "Nur nicht-lokale Mailboxen relayen. Existente Mailboxen werden weiterhin lokal zugestellt.",
"relayhost_wrapped_tls_info": "Bitte <b>keine</b> \"TLS-wrapped Ports\" verwenden (etwa SMTPS via Port 465/tcp).<br>\r\nDer Transport wird stattdessen STARTTLS anfordern, um TLS zu verwenden. TLS kann unter \"TLS Policy Maps\" erzwungen werden.", "relayhost_wrapped_tls_info": "Bitte <b>keine</b> \"TLS-wrapped Ports\" verwenden (etwa SMTPS via Port 465/tcp).<br>\r\nDer Transport wird stattdessen STARTTLS anfordern, um TLS zu verwenden. TLS kann unter \"TLS Policy Maps\" erzwungen werden.",
"select": "Bitte auswählen...", "select": "Bitte auswählen",
"select_domain": "Bitte zuerst eine Domain auswählen", "select_domain": "Bitte zuerst eine Domain auswählen",
"sieve_desc": "Kurze Beschreibung", "sieve_desc": "Kurze Beschreibung",
"sieve_type": "Filtertyp", "sieve_type": "Filtertyp",
@@ -151,7 +150,7 @@
"arrival_time": "Ankunftszeit (Serverzeit)", "arrival_time": "Ankunftszeit (Serverzeit)",
"authed_user": "Auth. Benutzer", "authed_user": "Auth. Benutzer",
"ays": "Soll der Vorgang wirklich ausgeführt werden?", "ays": "Soll der Vorgang wirklich ausgeführt werden?",
"ban_list_info": "Übersicht ausgesperrter Netzwerke: <b>Netzwerk (verbleibende Bannzeit) - [Aktionen]</b>.<br />IPs, die zum Entsperren eingereiht werden, verlassen die Liste aktiver Banns nach wenigen Sekunden.<br />Rote Labels kennzeichnen aktive permanente Sperren durch Denylisting.", "ban_list_info": "Übersicht ausgesperrter Netzwerke: <b>Netzwerk (verbleibende Bannzeit) - [Aktionen]</b>.<br />IPs, die zum Entsperren eingereiht werden, verlassen die Liste aktiver Banns nach wenigen Sekunden.<br />Rote Labels sind Indikatoren für aktive Allowlist-Einträge.",
"change_logo": "Logo ändern", "change_logo": "Logo ändern",
"configuration": "Konfiguration", "configuration": "Konfiguration",
"convert_html_to_text": "Konvertiere HTML zu reinem Text", "convert_html_to_text": "Konvertiere HTML zu reinem Text",
@@ -395,11 +394,6 @@
"ui_header_announcement_type_info": "Info", "ui_header_announcement_type_info": "Info",
"ui_header_announcement_type_warning": "Wichtig", "ui_header_announcement_type_warning": "Wichtig",
"ui_texts": "UI-Label und Texte", "ui_texts": "UI-Label und Texte",
"ui_theme": "Theme",
"ui_theme_dark": "Enthält eine Darkmode-Variante",
"ui_theme_default": "Standard",
"ui_theme_info": "Themes sind Ordner in <code>data/web/css/themes/&lt;name&gt;/</code> mit folgenden Dateien:<ul class=\"mb-0\"><li><code>theme.css</code> (erforderlich)</li><li><code>dark.css</code> (optional, Darkmode)</li><li><code>bootstrap.css</code> (optional, ersetzt das Bootstrap-Basistheme)</li><li><code>theme.json</code> (optional, <code>name</code> und <code>description</code>)</li></ul>",
"ui_theme_none": "Keine Themes gefunden.",
"unban_pending": "ausstehend", "unban_pending": "ausstehend",
"unchanged_if_empty": "Unverändert, wenn leer", "unchanged_if_empty": "Unverändert, wenn leer",
"upload": "Hochladen", "upload": "Hochladen",
@@ -437,7 +431,7 @@
"comment_too_long": "Kommentarfeld darf maximal 160 Zeichen enthalten", "comment_too_long": "Kommentarfeld darf maximal 160 Zeichen enthalten",
"cors_invalid_method": "Allow-Methods enthält eine ungültige Methode", "cors_invalid_method": "Allow-Methods enthält eine ungültige Methode",
"cors_invalid_origin": "Allow-Origins enthält eine ungültige Origin", "cors_invalid_origin": "Allow-Origins enthält eine ungültige Origin",
"defquota_empty": "Standard-Quota darf nicht 0 sein.", "defquota_empty": "Standard-Quota darf nicht 0 sein",
"demo_mode_enabled": "Demo Mode ist aktiviert", "demo_mode_enabled": "Demo Mode ist aktiviert",
"description_invalid": "Ressourcenbeschreibung für %s ist ungültig", "description_invalid": "Ressourcenbeschreibung für %s ist ungültig",
"dkim_domain_or_sel_exists": "Ein DKIM-Key für die Domain \"%s\" existiert und wird nicht überschrieben", "dkim_domain_or_sel_exists": "Ein DKIM-Key für die Domain \"%s\" existiert und wird nicht überschrieben",
@@ -449,7 +443,6 @@
"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",
@@ -462,13 +455,13 @@
"global_map_write_error": "Kann globale Map ID %s nicht schreiben: %s", "global_map_write_error": "Kann globale Map ID %s nicht schreiben: %s",
"goto_empty": "Eine Alias-Adresse muss auf mindestens eine gültige Ziel-Adresse zeigen", "goto_empty": "Eine Alias-Adresse muss auf mindestens eine gültige Ziel-Adresse zeigen",
"goto_invalid": "Ziel-Adresse %s ist ungültig", "goto_invalid": "Ziel-Adresse %s ist ungültig",
"ham_learn_error": "Ham-Lernfehler: %s", "ham_learn_error": "Ham Lernfehler: %s",
"iam_test_connection": "Verbindung fehlgeschlagen", "iam_test_connection": "Verbindung fehlgeschlagen",
"imagick_exception": "Fataler Bildverarbeitungsfehler", "imagick_exception": "Fataler Bildverarbeitungsfehler",
"img_dimensions_exceeded": "Grafik überschreitet die maximale Bildgröße", "img_dimensions_exceeded": "Grafik überschreitet die maximale Bildgröße",
"img_invalid": "Grafik konnte nicht validiert werden", "img_invalid": "Grafik konnte nicht validiert werden",
"img_size_exceeded": "Grafik überschreitet die maximale Dateigröße", "img_size_exceeded": "Grafik überschreitet die maximale Dateigröße",
"img_tmp_missing": "Grafik konnte nicht validiert werden: Temporärer Datei nicht gefunden.", "img_tmp_missing": "Grafik konnte nicht validiert werden: Erstellung temporärer Datei fehlgeschlagen.",
"invalid_bcc_map_type": "Ungültiger BCC-Map-Typ", "invalid_bcc_map_type": "Ungültiger BCC-Map-Typ",
"invalid_destination": "Ziel-Format \"%s\" ist ungültig", "invalid_destination": "Ziel-Format \"%s\" ist ungültig",
"invalid_filter_type": "Ungültiger Filtertyp", "invalid_filter_type": "Ungültiger Filtertyp",
@@ -481,7 +474,7 @@
"invalid_reset_token": "Ungültiger Rücksetz-Token", "invalid_reset_token": "Ungültiger Rücksetz-Token",
"ip_list_empty": "Liste erlaubter IPs darf nicht leer sein", "ip_list_empty": "Liste erlaubter IPs darf nicht leer sein",
"is_alias": "%s lautet bereits eine Alias-Adresse", "is_alias": "%s lautet bereits eine Alias-Adresse",
"is_alias_or_mailbox": "Eine Mailbox, ein Alias oder eine sich aus einer Alias-Domain ergebende Adresse mit dem Namen %s ist bereits vorhanden.", "is_alias_or_mailbox": "Eine Mailbox, ein Alias oder eine sich aus einer Alias-Domain ergebende Adresse mit dem Namen %s ist bereits vorhanden",
"is_spam_alias": "%s lautet bereits eine temporäre Alias-Adresse (Spam-Alias-Adresse)", "is_spam_alias": "%s lautet bereits eine temporäre Alias-Adresse (Spam-Alias-Adresse)",
"last_key": "Letzter Key kann nicht gelöscht werden, bitte stattdessen die 2FA deaktivieren.", "last_key": "Letzter Key kann nicht gelöscht werden, bitte stattdessen die 2FA deaktivieren.",
"login_failed": "Anmeldung fehlgeschlagen", "login_failed": "Anmeldung fehlgeschlagen",
@@ -555,7 +548,6 @@
"webauthn_authenticator_failed": "Der ausgewählte Authenticator wurde nicht gefunden", "webauthn_authenticator_failed": "Der ausgewählte Authenticator wurde nicht gefunden",
"webauthn_publickey_failed": "Zu dem ausgewählten Authenticator wurde kein Publickey hinterlegt", "webauthn_publickey_failed": "Zu dem ausgewählten Authenticator wurde kein Publickey hinterlegt",
"webauthn_username_failed": "Der ausgewählte Authenticator gehört zu einem anderen Konto", "webauthn_username_failed": "Der ausgewählte Authenticator gehört zu einem anderen Konto",
"ui_theme_invalid": "Theme nicht gefunden",
"unknown": "Ein unbekannter Fehler trat auf", "unknown": "Ein unbekannter Fehler trat auf",
"unknown_tfa_method": "Unbekannte TFA-Methode", "unknown_tfa_method": "Unbekannte TFA-Methode",
"unlimited_quota_acl": "Unendliche Quota untersagt durch ACL", "unlimited_quota_acl": "Unendliche Quota untersagt durch ACL",
@@ -667,7 +659,7 @@
"delete1": "Lösche Nachricht nach Übertragung vom Quell-Server", "delete1": "Lösche Nachricht nach Übertragung vom Quell-Server",
"delete2": "Lösche Nachrichten von Ziel-Server, die nicht auf Quell-Server vorhanden sind", "delete2": "Lösche Nachrichten von Ziel-Server, die nicht auf Quell-Server vorhanden sind",
"delete2duplicates": "Lösche Duplikate im Ziel", "delete2duplicates": "Lösche Duplikate im Ziel",
"delete_ays": "Bitte den Löschvorgang bestätigen.", "delete_ays": "Soll der Löschvorgang wirklich ausgeführt werden?",
"description": "Beschreibung", "description": "Beschreibung",
"disable_login": "Login verbieten (Mails werden weiterhin angenommen)", "disable_login": "Login verbieten (Mails werden weiterhin angenommen)",
"domain": "Domain bearbeiten", "domain": "Domain bearbeiten",
@@ -736,7 +728,6 @@
"mta_sts_mx": "MX-Server", "mta_sts_mx": "MX-Server",
"mta_sts_mx_info": "Erlaubt das Senden nur an explizit aufgeführte Mailserver-Hostnamen; der sendende MTA überprüft, ob der DNS-MX-Hostname mit der Richtlinienliste übereinstimmt, und erlaubt die Zustellung nur mit einem gültigen TLS-Zertifikat (schützt vor MITM).", "mta_sts_mx_info": "Erlaubt das Senden nur an explizit aufgeführte Mailserver-Hostnamen; der sendende MTA überprüft, ob der DNS-MX-Hostname mit der Richtlinienliste übereinstimmt, und erlaubt die Zustellung nur mit einem gültigen TLS-Zertifikat (schützt vor MITM).",
"mta_sts_mx_notice": "Es können mehrere MX-Server angegeben werden (durch Kommas getrennt).", "mta_sts_mx_notice": "Es können mehrere MX-Server angegeben werden (durch Kommas getrennt).",
"mta_sts_active_info": "Wenn nicht angehakt, wird die MTA-STS-Richtlinie nicht veröffentlicht und für die mta-sts-Subdomain kein Zertifikat über ACME angefordert.",
"multiple_bookings": "Mehrfaches Buchen", "multiple_bookings": "Mehrfaches Buchen",
"nexthop": "Next Hop", "nexthop": "Next Hop",
"none_inherit": "Keine Auswahl / Erben", "none_inherit": "Keine Auswahl / Erben",
@@ -787,7 +778,7 @@
"spam_score": "Einen benutzerdefiniterten Spam-Score festlegen", "spam_score": "Einen benutzerdefiniterten Spam-Score festlegen",
"subfolder2": "Ziel-Ordner<br><small>(leer = kein Unterordner)</small>", "subfolder2": "Ziel-Ordner<br><small>(leer = kein Unterordner)</small>",
"syncjob": "Sync-Job bearbeiten", "syncjob": "Sync-Job bearbeiten",
"target_address": "Ziel-Adresse(n)<small>(kommasepariert)</small>", "target_address": "Ziel-Adresse(n)",
"target_domain": "Ziel-Domain", "target_domain": "Ziel-Domain",
"timeout1": "Timeout für Verbindung zum Remote-Host", "timeout1": "Timeout für Verbindung zum Remote-Host",
"timeout2": "Timeout für Verbindung zum lokalen Host", "timeout2": "Timeout für Verbindung zum lokalen Host",
@@ -969,7 +960,7 @@
"private_comment": "Privater Kommentar", "private_comment": "Privater Kommentar",
"public_comment": "Öffentlicher Kommentar", "public_comment": "Öffentlicher Kommentar",
"q_add_header": "bei Mail in Junk-Ordner", "q_add_header": "bei Mail in Junk-Ordner",
"q_all": " bei Reject und Mail in Junk-Ordner", "q_all": "bei Reject und Mail in Junk-Ordner",
"q_reject": "bei Reject", "q_reject": "bei Reject",
"quarantine_category": "Quarantäne-Benachrichtigungskategorie", "quarantine_category": "Quarantäne-Benachrichtigungskategorie",
"quarantine_notification": "Quarantäne-Benachrichtigung", "quarantine_notification": "Quarantäne-Benachrichtigung",
@@ -1043,8 +1034,7 @@
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentifizierungsproblem", "syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentifizierungsproblem",
"syncjob_EXIT_OVERQUOTA": "Ziel Mailbox ist über dem Limit", "syncjob_EXIT_OVERQUOTA": "Ziel Mailbox ist über dem Limit",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Kann keine Verbindung zum Zielserver herstellen", "syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Kann keine Verbindung zum Zielserver herstellen",
"iam": "Identitätsanbieter", "iam": "Identitätsanbieter"
"force_tfa": "2-Faktor-Authentifizierung"
}, },
"oauth2": { "oauth2": {
"access_denied": "Bitte als Mailbox-Nutzer einloggen, um den Zugriff via OAuth2 zu erlauben.", "access_denied": "Bitte als Mailbox-Nutzer einloggen, um den Zugriff via OAuth2 zu erlauben.",
@@ -1096,7 +1086,7 @@
"rspamd_result": "Rspamd-Ergebnis", "rspamd_result": "Rspamd-Ergebnis",
"sender": "Sender (SMTP)", "sender": "Sender (SMTP)",
"sender_header": "Sender (\"From\"-Header)", "sender_header": "Sender (\"From\"-Header)",
"settings_info": "Maximale Anzahl der zurückgehaltenen E-Mails (pro Mailbox): %s<br>Maximale Größe einer zu speichernden E-Mail: %s MiB", "settings_info": "Maximale Anzahl der zurückgehaltenen E-Mails: %s<br>Maximale Größe einer zu speichernden E-Mail: %s MiB",
"show_item": "Details", "show_item": "Details",
"spam": "Spam", "spam": "Spam",
"spam_score": "Bewertung", "spam_score": "Bewertung",
@@ -1213,7 +1203,6 @@
"tls_policy_map_entry_deleted": "TLS-Richtlinie mit der ID %s wurde gelöscht", "tls_policy_map_entry_deleted": "TLS-Richtlinie mit der ID %s wurde gelöscht",
"tls_policy_map_entry_saved": "TLS-Richtlinieneintrag \"%s\" wurde gespeichert", "tls_policy_map_entry_saved": "TLS-Richtlinieneintrag \"%s\" wurde gespeichert",
"ui_texts": "Änderungen an UI-Texten", "ui_texts": "Änderungen an UI-Texten",
"ui_theme_modified": "Theme wurde erfolgreich gespeichert",
"upload_success": "Datei wurde erfolgreich hochgeladen", "upload_success": "Datei wurde erfolgreich hochgeladen",
"verified_fido2_login": "FIDO2-Anmeldung verifiziert", "verified_fido2_login": "FIDO2-Anmeldung verifiziert",
"verified_totp_login": "TOTP-Anmeldung verifiziert", "verified_totp_login": "TOTP-Anmeldung verifiziert",
@@ -1239,7 +1228,7 @@
"set_tfa": "Konfiguriere Zwei-Faktor-Authentifizierungsmethode", "set_tfa": "Konfiguriere Zwei-Faktor-Authentifizierungsmethode",
"start_webauthn_validation": "Starte Validierung", "start_webauthn_validation": "Starte Validierung",
"tfa": "Zwei-Faktor-Authentifizierung", "tfa": "Zwei-Faktor-Authentifizierung",
"tfa_token_invalid": "2FA-Token ungültig", "tfa_token_invalid": "TFA-Token ungültig!",
"totp": "Time-based-OTP (Google Authenticator etc.)", "totp": "Time-based-OTP (Google Authenticator etc.)",
"u2f_deprecated": "Es sieht so aus als wurde der Schlüssel mit der alten U2F Methode registriert. Wir werden die Zwei-Faktor-Authentifizierung deaktivieren und deinen Schlüssel löschen.", "u2f_deprecated": "Es sieht so aus als wurde der Schlüssel mit der alten U2F Methode registriert. Wir werden die Zwei-Faktor-Authentifizierung deaktivieren und deinen Schlüssel löschen.",
"u2f_deprecated_important": "Bitte registriere den Schlüssel im Adminbereich mit der neuen WebAuthn Methode.", "u2f_deprecated_important": "Bitte registriere den Schlüssel im Adminbereich mit der neuen WebAuthn Methode.",
@@ -1368,7 +1357,7 @@
"shared_aliases_desc": "Geteilte Alias-Adressen werden nicht bei benutzerdefinierten Einstellungen, wie die des Spam-Filters oder der Verschlüsselungsrichtlinie, berücksichtigt. Entsprechende Spam-Filter können lediglich von einem Administrator vorgenommen werden.", "shared_aliases_desc": "Geteilte Alias-Adressen werden nicht bei benutzerdefinierten Einstellungen, wie die des Spam-Filters oder der Verschlüsselungsrichtlinie, berücksichtigt. Entsprechende Spam-Filter können lediglich von einem Administrator vorgenommen werden.",
"show_sieve_filters": "Zeige aktiven Filter des Benutzers", "show_sieve_filters": "Zeige aktiven Filter des Benutzers",
"sogo_profile_reset": "SOGo-Profil zurücksetzen", "sogo_profile_reset": "SOGo-Profil zurücksetzen",
"sogo_profile_reset_help": "Das Profil wird inklusive <b>aller Kalender- und Kontaktdaten unwiederbringlich gelöscht</b>.", "sogo_profile_reset_help": "Das Profil wird inklusive <b>aller</b> Kalender- und Kontaktdaten <b>unwiederbringlich gelöscht</b>.",
"sogo_profile_reset_now": "Profil jetzt zurücksetzen", "sogo_profile_reset_now": "Profil jetzt zurücksetzen",
"spam_aliases": "Spam E-Mail-Aliasse", "spam_aliases": "Spam E-Mail-Aliasse",
"spam_aliases_info": "Ein Spam-Alias ist eine temporäre E-Mailadresse, die benutzt werden kann, um eine echte E-Mail Adressen zu schützen. <br>Optional kann eine Ablaufzeit gesetzt werden, sodass der Alias nach dem definierten Zeitraum automatisch deaktiviert wird, was missbrauchte oder geleakte Adressen effektiv entsorgt.", "spam_aliases_info": "Ein Spam-Alias ist eine temporäre E-Mailadresse, die benutzt werden kann, um eine echte E-Mail Adressen zu schützen. <br>Optional kann eine Ablaufzeit gesetzt werden, sodass der Alias nach dem definierten Zeitraum automatisch deaktiviert wird, was missbrauchte oder geleakte Adressen effektiv entsorgt.",
+1 -12
View File
@@ -1,7 +1,6 @@
{ {
"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",
@@ -405,11 +404,6 @@
"ui_header_announcement_type_info": "Info", "ui_header_announcement_type_info": "Info",
"ui_header_announcement_type_warning": "Important", "ui_header_announcement_type_warning": "Important",
"ui_texts": "UI labels and texts", "ui_texts": "UI labels and texts",
"ui_theme": "Theme",
"ui_theme_dark": "Includes a dark mode variant",
"ui_theme_default": "Default",
"ui_theme_info": "Themes are folders in <code>data/web/css/themes/&lt;name&gt;/</code> with the following files:<ul class=\"mb-0\"><li><code>theme.css</code> (required)</li><li><code>dark.css</code> (optional, dark mode)</li><li><code>bootstrap.css</code> (optional, replaces the Bootstrap base theme)</li><li><code>theme.json</code> (optional, <code>name</code> and <code>description</code>)</li></ul>",
"ui_theme_none": "No themes found.",
"unban_pending": "unban pending", "unban_pending": "unban pending",
"unchanged_if_empty": "If unchanged leave blank", "unchanged_if_empty": "If unchanged leave blank",
"upload": "Upload", "upload": "Upload",
@@ -450,7 +444,6 @@
"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",
@@ -559,7 +552,6 @@
"webauthn_authenticator_failed": "The selected authenticator was not found", "webauthn_authenticator_failed": "The selected authenticator was not found",
"webauthn_publickey_failed": "No public key was stored for the selected authenticator", "webauthn_publickey_failed": "No public key was stored for the selected authenticator",
"webauthn_username_failed": "The selected authenticator belongs to another account", "webauthn_username_failed": "The selected authenticator belongs to another account",
"ui_theme_invalid": "Theme not found",
"unknown": "An unknown error occurred", "unknown": "An unknown error occurred",
"unknown_tfa_method": "Unknown TFA method", "unknown_tfa_method": "Unknown TFA method",
"unlimited_quota_acl": "Unlimited quota prohibited by ACL", "unlimited_quota_acl": "Unlimited quota prohibited by ACL",
@@ -736,7 +728,6 @@
"mta_sts_mx": "MX server", "mta_sts_mx": "MX server",
"mta_sts_mx_info": "Allows sending only to explicitly listed mail server hostnames; the sending MTA checks if the DNS MX hostname matches the policy list, and only allows delivery with a valid TLS certificate (guards against MITM).", "mta_sts_mx_info": "Allows sending only to explicitly listed mail server hostnames; the sending MTA checks if the DNS MX hostname matches the policy list, and only allows delivery with a valid TLS certificate (guards against MITM).",
"mta_sts_mx_notice": "Multiple MX servers can be specified (separated by commas).", "mta_sts_mx_notice": "Multiple MX servers can be specified (separated by commas).",
"mta_sts_active_info": "If unchecked, the MTA-STS policy will not be published and no certificate will be requested for the mta-sts subdomain via ACME.",
"multiple_bookings": "Multiple bookings", "multiple_bookings": "Multiple bookings",
"none_inherit": "None / Inherit", "none_inherit": "None / Inherit",
"nexthop": "Next hop", "nexthop": "Next hop",
@@ -938,7 +929,6 @@
"filters": "Filters", "filters": "Filters",
"fname": "Full name", "fname": "Full name",
"force_pw_update": "Force password update at next login", "force_pw_update": "Force password update at next login",
"force_tfa": "TFA",
"gal": "Global Address List", "gal": "Global Address List",
"goto_ham": "Learn as <b>ham</b>", "goto_ham": "Learn as <b>ham</b>",
"goto_spam": "Learn as <b>spam</b>", "goto_spam": "Learn as <b>spam</b>",
@@ -1096,7 +1086,7 @@
"rspamd_result": "Rspamd result", "rspamd_result": "Rspamd result",
"sender": "Sender (SMTP)", "sender": "Sender (SMTP)",
"sender_header": "Sender (\"From\" header)", "sender_header": "Sender (\"From\" header)",
"settings_info": "Maximum amount of elements to be quarantined (per mailbox): %s<br>Maximum email size: %s MiB", "settings_info": "Maximum amount of elements to be quarantined: %s<br>Maximum email size: %s MiB",
"show_item": "Show item", "show_item": "Show item",
"spam": "Spam", "spam": "Spam",
"spam_score": "Score", "spam_score": "Score",
@@ -1220,7 +1210,6 @@
"tls_policy_map_entry_deleted": "TLS policy map ID %s has been deleted", "tls_policy_map_entry_deleted": "TLS policy map ID %s has been deleted",
"tls_policy_map_entry_saved": "TLS policy map entry \"%s\" has been saved", "tls_policy_map_entry_saved": "TLS policy map entry \"%s\" has been saved",
"ui_texts": "Saved changes to UI texts", "ui_texts": "Saved changes to UI texts",
"ui_theme_modified": "Theme was saved successfully",
"upload_success": "File uploaded successfully", "upload_success": "File uploaded successfully",
"verified_fido2_login": "Verified FIDO2 login", "verified_fido2_login": "Verified FIDO2 login",
"verified_totp_login": "Verified TOTP login", "verified_totp_login": "Verified TOTP login",
+6 -17
View File
@@ -111,8 +111,7 @@
"app_passwd_protocols": "Protocoles autorisés pour le mot de passe de l'application", "app_passwd_protocols": "Protocoles autorisés pour le mot de passe de l'application",
"dry": "Simuler la synchronisation", "dry": "Simuler la synchronisation",
"internal": "Interne", "internal": "Interne",
"internal_info": "Les alias internes sont accessibles uniquement depuis le domaine ou les alias du domaine.", "internal_info": "Les alias internes sont accessibles uniquement depuis le domaine ou les alias du domaine."
"sender_allowed": "Autoriser l'envoi sous cet alias"
}, },
"admin": { "admin": {
"access": "Accès", "access": "Accès",
@@ -557,9 +556,7 @@
"max_age_invalid": "L'âge maximum %s est invalide", "max_age_invalid": "L'âge maximum %s est invalide",
"mode_invalid": "Le mode %s est invalide", "mode_invalid": "Le mode %s est invalide",
"mx_invalid": "L'enregistrement MX %s est invalide", "mx_invalid": "L'enregistrement MX %s est invalide",
"version_invalid": "La version %s est invalide", "version_invalid": "La version %s est invalide"
"tfa_removal_blocked": "L’authentification à deux facteurs ne peut pas être supprimée, car elle est requise pour votre compte.",
"quarantine_category_invalid": "La catégorie de quarantaine doit être l’une des suivantes : add_header, reject, all\""
}, },
"debug": { "debug": {
"chart_this_server": "Graphique (ce serveur)", "chart_this_server": "Graphique (ce serveur)",
@@ -758,9 +755,7 @@
"mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> est un standard qui oblige la délivrance des courriels entre les serveurs de courriels à utiliser TLS avec des certificats valides. <br>Il est utilisé quand <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> n'est pas possible à cause d'un manque ou d'un non support de DNSSEC.<br><b>Note</b> : Si le domaine du destinataire supporte DANE avec DNSSEC, DANE est <b>toujours</b> préféré – MTA-STS sert seulement en secours.", "mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> est un standard qui oblige la délivrance des courriels entre les serveurs de courriels à utiliser TLS avec des certificats valides. <br>Il est utilisé quand <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> n'est pas possible à cause d'un manque ou d'un non support de DNSSEC.<br><b>Note</b> : Si le domaine du destinataire supporte DANE avec DNSSEC, DANE est <b>toujours</b> préféré – MTA-STS sert seulement en secours.",
"mta_sts_mode_info": "Il y a trois modes parmi lesquels choisir :<ul><li><em>testing</em> – la politique est seulement surveillée, les violations n'ont pas d'impact.</li><li><em>enforce</em> – la politique est appliquée strictement, les connexions sans TLS valide sont rejetées.</li><li><em>none</em> – la politique est publiée mais non appliquée.</li></ul>", "mta_sts_mode_info": "Il y a trois modes parmi lesquels choisir :<ul><li><em>testing</em> – la politique est seulement surveillée, les violations n'ont pas d'impact.</li><li><em>enforce</em> – la politique est appliquée strictement, les connexions sans TLS valide sont rejetées.</li><li><em>none</em> – la politique est publiée mais non appliquée.</li></ul>",
"mta_sts_max_age_info": "Durée en secondes pendant laquelle les serveurs de courriel peuvent mettre en cache cette politique avant de revérifier.", "mta_sts_max_age_info": "Durée en secondes pendant laquelle les serveurs de courriel peuvent mettre en cache cette politique avant de revérifier.",
"mta_sts_mx_info": "Autoriser l'envoi uniquement aux noms d'hôtes des serveurs de courriels indiqués explicitement ; le MTA émetteur vérifie si le nom d'hôte DNS du MX correspond à la liste de la politique, et autorise la délivrance seulement avec un certificat TLS valide (protège contre le MITM).", "mta_sts_mx_info": "Autoriser l'envoi uniquement aux noms d'hôtes des serveurs de courriels indiqués explicitement ; le MTA émetteur vérifie si le nom d'hôte DNS du MX correspond à la liste de la politique, et autorise la délivrance seulement avec un certificat TLS valide (protège contre le MITM)."
"sender_allowed": "Autoriser l'envoi sous cet alias",
"sender_allowed_info": "Si cette option est désactivée, cet alias peut uniquement recevoir des courriels. Utilisez les ACL d’expéditeur pour autoriser certaines boîtes aux lettres à envoyer des messages via cet alias."
}, },
"footer": { "footer": {
"cancel": "Annuler", "cancel": "Annuler",
@@ -993,8 +988,7 @@
"recipient": "Destinataire", "recipient": "Destinataire",
"open_logs": "Afficher les journaux", "open_logs": "Afficher les journaux",
"iam": "Fournisseur d'identité", "iam": "Fournisseur d'identité",
"internal": "Interne", "internal": "Interne"
"force_tfa": "TFA"
}, },
"oauth2": { "oauth2": {
"access_denied": "Veuillez vous connecter en tant que propriétaire de la boîte de réception pour accorder l’accès via Oauth2.", "access_denied": "Veuillez vous connecter en tant que propriétaire de la boîte de réception pour accorder l’accès via Oauth2.",
@@ -1195,11 +1189,7 @@
"yubi_otp": "Authentification OTP Yubico", "yubi_otp": "Authentification OTP Yubico",
"authenticators": "Authentificateurs", "authenticators": "Authentificateurs",
"u2f_deprecated_important": "Veuillez enregistrer votre clé dans le panneau d'administration avec la nouvelle méthode WebAuthn.", "u2f_deprecated_important": "Veuillez enregistrer votre clé dans le panneau d'administration avec la nouvelle méthode WebAuthn.",
"u2f_deprecated": "Il semble que votre clé ait été enregistrée à l'aide de la méthode U2F obsolète. Nous allons désactiver l'authentification à deux facteurs pour vous et supprimer votre clé.", "u2f_deprecated": "Il semble que votre clé ait été enregistrée à l'aide de la méthode U2F obsolète. Nous allons désactiver l'authentification à deux facteurs pour vous et supprimer votre clé."
"force_tfa": "Imposer l'authentification à deux facteurs lors de la connexion",
"force_tfa_info": "L’utilisateur doit configurer l’authentification à deux facteurs avant de pouvoir accéder à l’interface.",
"setup_title": "Authentification à deux facteurs requise",
"setup_required": "Votre compte nécessite l’authentification à deux facteurs. Veuillez configurer une méthode 2FA pour continuer."
}, },
"fido2": { "fido2": {
"set_fn": "Définir un nom", "set_fn": "Définir un nom",
@@ -1388,8 +1378,7 @@
"forever": "Pour toujours", "forever": "Pour toujours",
"spam_aliases_info": "Un alias de spam est une adresse de courriel temporaire qui peut être utilisée pour protéger les véritables adresses de courriel. <br> De manière optionnelle, une durée d'expiration peut être définie afin que l'alias soit automatiquement désactivé après la période définie, éliminant ainsi les adresses étant abusées ou ayant fuité.", "spam_aliases_info": "Un alias de spam est une adresse de courriel temporaire qui peut être utilisée pour protéger les véritables adresses de courriel. <br> De manière optionnelle, une durée d'expiration peut être définie afin que l'alias soit automatiquement désactivé après la période définie, éliminant ainsi les adresses étant abusées ou ayant fuité.",
"authentication": "Authentification", "authentication": "Authentification",
"protocols": "Protocoles", "protocols": "Protocoles"
"pw_update_required": "Votre compte nécessite un changement de mot de passe. Veuillez définir un nouveau mot de passe pour continuer."
}, },
"warning": { "warning": {
"cannot_delete_self": "Impossible de supprimer l’utilisateur connecté", "cannot_delete_self": "Impossible de supprimer l’utilisateur connecté",
+2 -5
View File
@@ -109,10 +109,7 @@
"bcc_dest_format": "Il destinatario in copia nascosta deve essere un singolo indirizzo email.<br>Se si vuole spedire una copia del messaggio a più destinatari, bisogna creare un alias ed utilizzarlo per questa opzione.", "bcc_dest_format": "Il destinatario in copia nascosta deve essere un singolo indirizzo email.<br>Se si vuole spedire una copia del messaggio a più destinatari, bisogna creare un alias ed utilizzarlo per questa opzione.",
"app_passwd_protocols": "Protocolli consentiti per la password dell'app", "app_passwd_protocols": "Protocolli consentiti per la password dell'app",
"tags": "Tag", "tags": "Tag",
"dry": "Simula sincronizzazione", "dry": "Simula sincronizzazione"
"internal": "Interno",
"internal_info": "Gli alias interni sono accessibili solo dal proprio dominio o dai domini alias.",
"sender_allowed": "Consenti l'invio con questo alias"
}, },
"admin": { "admin": {
"access": "Accedi", "access": "Accedi",
@@ -134,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": "Assegnazioni di dominio", "admin_domains": "Assengazioni di dominio",
"admins": "Amministratori", "admins": "Amministratori",
"admins_ldap": "Amministratori LDAP", "admins_ldap": "Amministratori LDAP",
"advanced_settings": "Impostazioni avanzate", "advanced_settings": "Impostazioni avanzate",
+18 -40
View File
@@ -13,7 +13,7 @@
"domain_relayhost": "Mainīt domēna relayhost", "domain_relayhost": "Mainīt domēna relayhost",
"eas_reset": "EAS ierīču atiestatīšana", "eas_reset": "EAS ierīču atiestatīšana",
"extend_sender_acl": "Ļauj paplašināt sūtītāja ACL ar ārējām adresēm", "extend_sender_acl": "Ļauj paplašināt sūtītāja ACL ar ārējām adresēm",
"login_as": "Pieteikties kā pastkastes lietotājam", "login_as": "Pieteikšanās kā pastkastes lietotājam",
"mailbox_relayhost": "Pasta kastītes relayhost maiņa", "mailbox_relayhost": "Pasta kastītes relayhost maiņa",
"prohibited": "Aizliegts ar ACL", "prohibited": "Aizliegts ar ACL",
"protocol_access": "Protokola piekļuves maiņa", "protocol_access": "Protokola piekļuves maiņa",
@@ -91,12 +91,12 @@
"activate_api": "Aktivizēt API", "activate_api": "Aktivizēt API",
"active": "Aktīvs", "active": "Aktīvs",
"add": "Pievienot", "add": "Pievienot",
"add_domain_admin": "Pievienot domēna pārvaldītāju", "add_domain_admin": "Pievienot domēna administratoru",
"add_forwarding_host": "Pievienot pāradresācijas hostu", "add_forwarding_host": "Pievienot pāradresācijas hostu",
"add_relayhost": "Pievienot Relayhost", "add_relayhost": "Pievienot Relayhost",
"add_row": "Pievienot rindu", "add_row": "Pievienot rindu",
"admin": "Pārvaldītājs", "admin": "Administrators",
"admin_details": "Labot informāciju par pārvaldītāju", "admin_details": "Labot administratora detaļas",
"admin_domains": "Domēna uzdevumi", "admin_domains": "Domēna uzdevumi",
"api_allow_from": "Atļaut API piekļuvi no šīm IP", "api_allow_from": "Atļaut API piekļuvi no šīm IP",
"api_key": "API atslēga", "api_key": "API atslēga",
@@ -114,7 +114,7 @@
"dkim_keys": "ARC/DKIM atslēgas", "dkim_keys": "ARC/DKIM atslēgas",
"dkim_private_key": "Privāta atslēga", "dkim_private_key": "Privāta atslēga",
"domain": "Domēns", "domain": "Domēns",
"domain_admins": "Domēna pārvaldītāji", "domain_admins": "Domēna administratori",
"edit": "Labot", "edit": "Labot",
"empty": "Nav iznākuma", "empty": "Nav iznākuma",
"f2b_ban_time": "Aizlieguma laiks (s)", "f2b_ban_time": "Aizlieguma laiks (s)",
@@ -188,14 +188,7 @@
"quarantine_max_score": "Atmest paziņojumu, ja e-pasta ziņojuma mēstuļu novērtējums ir augstāks par šo vērtību:<br><small>Noklusējums ir 9999.0</small>", "quarantine_max_score": "Atmest paziņojumu, ja e-pasta ziņojuma mēstuļu novērtējums ir augstāks par šo vērtību:<br><small>Noklusējums ir 9999.0</small>",
"options": "Iespējas", "options": "Iespējas",
"password_reset_settings": "Paroļu atkopes iestatījumi", "password_reset_settings": "Paroļu atkopes iestatījumi",
"password_settings": "Paroļu iestatījumi", "password_settings": "Paroļu iestatījumi"
"add_admin": "Pievienot pārvaldītāju",
"admins": "Pārvaldītāji",
"admins_ldap": "LDAP pārvaldītāji",
"admin_quicklink": "Paslēpt ātro saiti uz pārvaldītāju pieteikšanās lapu",
"domain_admin": "Domēna pārvaldītājs",
"domainadmin_quicklink": "Paslēpt ātro saiti uz domēna pārvaldītāju pieteikšanās lapu",
"user_quicklink": "Paslēpt ātro saiti uz lietotāju pieteikšanās lapu"
}, },
"danger": { "danger": {
"access_denied": "Piekļuve liegta, vai nepareizi dati", "access_denied": "Piekļuve liegta, vai nepareizi dati",
@@ -251,10 +244,7 @@
"app_passwd_id_invalid": "Lietotnes paroles Id %s ir nederīgs", "app_passwd_id_invalid": "Lietotnes paroles Id %s ir nederīgs",
"img_dimensions_exceeded": "Attēls pārsniedz lielāko pieļaujamo attēla lielumu", "img_dimensions_exceeded": "Attēls pārsniedz lielāko pieļaujamo attēla lielumu",
"img_size_exceeded": "Attēls pārsniedz lielāko pieļaujamo datnes lielumu", "img_size_exceeded": "Attēls pārsniedz lielāko pieļaujamo datnes lielumu",
"version_invalid": "Versija %s ir nederīga", "version_invalid": "Versija %s ir nederīga"
"generic_server_error": "Atgadījās neparedzēta servera kļūda. Lūgums sazināties ar pārvaldītāju.",
"password_reset_na": "Paroļu atkope šobrīd nav pieejama. Lūgums sazināties ar pārvaldītāju.",
"recovery_email_failed": "Nevarēja nosūtīt atkopes e-pasta ziņojumu. Lūgums sazināties ar pārvaldītāju."
}, },
"diagnostics": { "diagnostics": {
"cname_from_a": "Vērtība, kas iegūta no A/AAAA ieraksta. Tas tiek atbalstīts tik ilgi, kamēr ieraksts norāda uz pareizo resursu.", "cname_from_a": "Vērtība, kas iegūta no A/AAAA ieraksta. Tas tiek atbalstīts tik ilgi, kamēr ieraksts norāda uz pareizo resursu.",
@@ -276,7 +266,7 @@
"delete2duplicates": "Izdzēst atkārtojošos vienumus galamērķī", "delete2duplicates": "Izdzēst atkārtojošos vienumus galamērķī",
"description": "Apraksts", "description": "Apraksts",
"domain": "Labot domēnu", "domain": "Labot domēnu",
"domain_admin": "Labot domēna pārvaldītāju", "domain_admin": "Labot domēna administratoru",
"domain_quota": "Domēna kvota", "domain_quota": "Domēna kvota",
"domains": "Domēni", "domains": "Domēni",
"dont_check_sender_acl": "Atspējot sūtītāju pārbaudi domēnam %s (+ aizstājdomēni)", "dont_check_sender_acl": "Atspējot sūtītāju pārbaudi domēnam %s (+ aizstājdomēni)",
@@ -339,8 +329,7 @@
"app_passwd": "Lietotnes parole", "app_passwd": "Lietotnes parole",
"mta_sts_version": "Versija", "mta_sts_version": "Versija",
"mta_sts_version_info": "Norāda MTA-STS standarta versiju – pašreiz ir derīga tikai <code>STSv1</code>.", "mta_sts_version_info": "Norāda MTA-STS standarta versiju – pašreiz ir derīga tikai <code>STSv1</code>.",
"sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Sūtītāja pārbaude ir atspējota</span>", "sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Sūtītāja pārbaude ir atspējota</span>"
"admin": "Labot pārvaldītāju"
}, },
"footer": { "footer": {
"cancel": "Atcelt", "cancel": "Atcelt",
@@ -373,14 +362,7 @@
"username": "Lietotājvārds", "username": "Lietotājvārds",
"fido2_webauthn": "FIDO/WebAuthn pieteikšanās", "fido2_webauthn": "FIDO/WebAuthn pieteikšanās",
"mobileconfig_info": "Lūgums pieteikties kā pastkastes lietotājam, lai lejupielādētu pieprasīto Apple savienojuma profilu.", "mobileconfig_info": "Lūgums pieteikties kā pastkastes lietotājam, lai lejupielādētu pieprasīto Apple savienojuma profilu.",
"other_logins": "vai pieteikties ar", "other_logins": "vai pieslēgties ar"
"forgot_password": "> Aizmirsta parole?",
"login_linkstext": "Nepareiza pieteikšanās?",
"login_domainadmintext": "Pieteikties kā domēna pārvaldītājam",
"login_admintext": "Pieteikties kā pārvaldītājam",
"login_user": "Lietotu pieteikšanās",
"login_dadmin": "Domēna pārvaldītāju pieteikšanās",
"login_admin": "Pārvaldītāju pieteikšanās"
}, },
"mailbox": { "mailbox": {
"action": "Rīcība", "action": "Rīcība",
@@ -415,7 +397,7 @@
"description": "Apraksts", "description": "Apraksts",
"dkim_key_length": "DKIM atslēgas garums (bits)", "dkim_key_length": "DKIM atslēgas garums (bits)",
"domain": "Domēns", "domain": "Domēns",
"domain_admins": "Domēna pārvaldītāji", "domain_admins": "Domēna administratori",
"domain_aliases": "Domēna aizstājvārdi", "domain_aliases": "Domēna aizstājvārdi",
"domain_quota": "Kvota", "domain_quota": "Kvota",
"domain_quota_total": "Kopējais domēna ierobežojums", "domain_quota_total": "Kopējais domēna ierobežojums",
@@ -525,7 +507,7 @@
"imap_smtp_server_auth_info": "Lūgums izmantot pilnu e-pasta adresi un PLAIN autentificēšanās mehānismu.<br>\nPieteikšanās dati tiks šifrēti ar servera puses obligātu šifrēšanu." "imap_smtp_server_auth_info": "Lūgums izmantot pilnu e-pasta adresi un PLAIN autentificēšanās mehānismu.<br>\nPieteikšanās dati tiks šifrēti ar servera puses obligātu šifrēšanu."
}, },
"success": { "success": {
"admin_modified": "Pārvaldītāja izmaiņas tika saglabātas", "admin_modified": "Izmaiņas administrātoram ir saglabātas",
"alias_added": "Aizstājadrese %s (%d) tika pievienota", "alias_added": "Aizstājadrese %s (%d) tika pievienota",
"alias_domain_removed": "Aizstājdomēns %s tika noņemts", "alias_domain_removed": "Aizstājdomēns %s tika noņemts",
"alias_modified": "Aizstājadreses izmaiņas %s tika saglabātas", "alias_modified": "Aizstājadreses izmaiņas %s tika saglabātas",
@@ -536,9 +518,9 @@
"dkim_added": "DKIM atslēga saglabāta", "dkim_added": "DKIM atslēga saglabāta",
"dkim_removed": "DKIM atslēga %s ir noņemta", "dkim_removed": "DKIM atslēga %s ir noņemta",
"domain_added": "Pievienots domēns %s", "domain_added": "Pievienots domēns %s",
"domain_admin_added": "Tika pievienots domēna pārvaldītājs %s", "domain_admin_added": "Domēna administrātors %s pievienots",
"domain_admin_modified": "Domēna pārvaldītāja %s izmaiņas tika saglabātas", "domain_admin_modified": "Izmaiņas domēna administrātoram %s ir saglabātas",
"domain_admin_removed": "Tika noņemts domēna pārvaldītājs %s", "domain_admin_removed": "Domēna administrators %s tika noņemts",
"domain_modified": "Izmaiņas domēnam %s ir saglabātas", "domain_modified": "Izmaiņas domēnam %s ir saglabātas",
"domain_removed": "Domēns %s ir noņemts", "domain_removed": "Domēns %s ir noņemts",
"eas_reset": "ActiveSync ierīces priekš lietotāja %s tika atiestatītas", "eas_reset": "ActiveSync ierīces priekš lietotāja %s tika atiestatītas",
@@ -566,9 +548,7 @@
"verified_yotp_login": "Apliecināta Yubico OTP pieteikšanās", "verified_yotp_login": "Apliecināta Yubico OTP pieteikšanās",
"app_passwd_removed": "Noņemta lietotnes parole ar Id %s", "app_passwd_removed": "Noņemta lietotnes parole ar Id %s",
"app_passwd_added": "Pievienota jauna lietotnes parole", "app_passwd_added": "Pievienota jauna lietotnes parole",
"f2b_banlist_refreshed": "Liegumu saraksta Id tika sekmīgi atsvaidzināts.", "f2b_banlist_refreshed": "Liegumu saraksta Id tika sekmīgi atsvaidzināts."
"admin_added": "Tika pievienots pārvaldītājs %s",
"admin_removed": "Tika noņemts pārvaldītājs %s"
}, },
"tfa": { "tfa": {
"api_register": "%s izmanto Yubico Cloud API. Lūdzu iegūstiet API atslēgu priekš Jūsu atslēgas<a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">here</a>", "api_register": "%s izmanto Yubico Cloud API. Lūdzu iegūstiet API atslēgu priekš Jūsu atslēgas<a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">here</a>",
@@ -589,8 +569,7 @@
"waiting_usb_auth": "<i>Gaida USB ierīci...</i><br><br>Lūdzu, tagad nospiežiet pogu uz Jūsu WebAuthn USB ierīces.", "waiting_usb_auth": "<i>Gaida USB ierīci...</i><br><br>Lūdzu, tagad nospiežiet pogu uz Jūsu WebAuthn USB ierīces.",
"waiting_usb_register": "<i>Gaida USB ierīci...</i><br><br>Lūgums augstāk ievadīt savu paroli un apstiprināt reģistrēšanos ar USB ierīces pogas nospiešanu.", "waiting_usb_register": "<i>Gaida USB ierīci...</i><br><br>Lūgums augstāk ievadīt savu paroli un apstiprināt reģistrēšanos ar USB ierīces pogas nospiešanu.",
"yubi_otp": "Yubico OTP autentifikators", "yubi_otp": "Yubico OTP autentifikators",
"authenticators": "Autentificētāji", "authenticators": "Autentificētāji"
"u2f_deprecated_important": "Lūgums reģistrēt savu atslēgu pārvaldības lapā ar jauno WebAuthn veidu."
}, },
"user": { "user": {
"action": "Rīcība", "action": "Rīcība",
@@ -722,8 +701,7 @@
"warning": { "warning": {
"domain_added_sogo_failed": "Domēns pievienots, bet neizdevās pārsāknēt SOGO. Lūgums pārbaudīt servera žurnālus.", "domain_added_sogo_failed": "Domēns pievienots, bet neizdevās pārsāknēt SOGO. Lūgums pārbaudīt servera žurnālus.",
"dovecot_restart_failed": "Dovecot neizdevās pārsāknēties. Lūgums pārbaudīt žurnālus", "dovecot_restart_failed": "Dovecot neizdevās pārsāknēties. Lūgums pārbaudīt žurnālus",
"is_not_primary_alias": "Izlaists aizstājvārds %s, kas nav galvenais", "is_not_primary_alias": "Izlaists aizstājvārds %s, kas nav galvenais"
"no_active_admin": "Nevar deaktivēt pēdējo aktīvo pārvaldītāju"
}, },
"oauth2": { "oauth2": {
"access_denied": "Lūgums pieteikties kā pastkastes īpašniekam, lai nodrošinātu piekļuvi ar OAuth2." "access_denied": "Lūgums pieteikties kā pastkastes īpašniekam, lai nodrošinātu piekļuvi ar OAuth2."
+1 -2
View File
@@ -27,8 +27,7 @@
"tls_policy": "Versleutelingsbeleid", "tls_policy": "Versleutelingsbeleid",
"unlimited_quota": "Onbeperkte quota voor mailboxen", "unlimited_quota": "Onbeperkte quota voor mailboxen",
"domain_desc": "Wijzig domeinbeschrijving", "domain_desc": "Wijzig domeinbeschrijving",
"pw_reset": "Toegang om mailcow gebruikers wachtwoord te resetten", "pw_reset": "Toegang om mailcow gebruikers wachtwoord te resetten"
"domain_relayhost": "Verander relayhost voor een domein"
}, },
"add": { "add": {
"activate_filter_warn": "Alle andere filters worden gedeactiveerd zolang deze geactiveerd is.", "activate_filter_warn": "Alle andere filters worden gedeactiveerd zolang deze geactiveerd is.",
+2 -2
View File
@@ -189,7 +189,7 @@
"api_info": "API jest w trakcie prac. Dokumentację można znaleźć pod adresem <a href=\"/api\">/api</a>", "api_info": "API jest w trakcie prac. Dokumentację można znaleźć pod adresem <a href=\"/api\">/api</a>",
"api_key": "klucz API", "api_key": "klucz API",
"api_read_only": "Dostęp tylko do odczytu", "api_read_only": "Dostęp tylko do odczytu",
"api_read_write": "Dostęp do odczytu i zapisu", "api_read_write": "Dostęp tylko do odczytu",
"api_skip_ip_check": "Pomiń sprawdzenie IP dla API", "api_skip_ip_check": "Pomiń sprawdzenie IP dla API",
"app_hide": "Ukryj dla logowania", "app_hide": "Ukryj dla logowania",
"app_links": "Linki aplikacji", "app_links": "Linki aplikacji",
@@ -1226,7 +1226,7 @@
"decimal": ".", "decimal": ".",
"emptyTable": "Brak danych w tabeli", "emptyTable": "Brak danych w tabeli",
"expand_all": "Rozszerz wszystko", "expand_all": "Rozszerz wszystko",
"info": "Wyświetlanie od _START_ do _END_ z _TOTAL_ wpisów", "info": "Wyświetlanie od START do END z TOTAL wpisów",
"infoEmpty": "Wyświetlanie od 0 do 0 z 0 wpisów", "infoEmpty": "Wyświetlanie od 0 do 0 z 0 wpisów",
"infoFiltered": "(filtrowane z _MAX_ suma wpisów)", "infoFiltered": "(filtrowane z _MAX_ suma wpisów)",
"thousands": ",", "thousands": ",",
+32 -48
View File
@@ -38,7 +38,7 @@
"add_domain_only": "Adicionar somente domínio", "add_domain_only": "Adicionar somente domínio",
"add_domain_restart": "Adicionar domínio e reiniciar o SoGo", "add_domain_restart": "Adicionar domínio e reiniciar o SoGo",
"alias_address": "Endereço (s) de alias", "alias_address": "Endereço (s) de alias",
"alias_address_info": "<small>Endereço(s) de e-mail completo(s) ou @example.com, para capturar todas as mensagens de um domínio (separadas por vírgula). <b>Apenas domínios do Mailcow</b>.</small>", "alias_address_info": "<small>Endereço/s de e-mail completo ou @example .com, para capturar todas as mensagens de um domínio (separadas por vírgula). <b> somente domínios mailcow</b>.</small>",
"alias_domain": "Domínio de alias", "alias_domain": "Domínio de alias",
"alias_domain_info": "<small>Somente nomes de domínio válidos (separados por vírgula).</small>", "alias_domain_info": "<small>Somente nomes de domínio válidos (separados por vírgula).</small>",
"app_name": "Nome do aplicativo", "app_name": "Nome do aplicativo",
@@ -111,8 +111,7 @@
"validation_success": "Validado com sucesso", "validation_success": "Validado com sucesso",
"dry": "Simular sincronização", "dry": "Simular sincronização",
"internal": "Interno", "internal": "Interno",
"internal_info": "Aliases internos são acessíveis apenas a partir do próprio domínio ou alias de domínio.", "internal_info": "Aliases internos são acessíveis apenas a partir do próprio domínio ou alias de domínio."
"sender_allowed": "Permitir enviar como este alias"
}, },
"admin": { "admin": {
"access": "Acesso", "access": "Acesso",
@@ -152,13 +151,13 @@
"arrival_time": "Hora de chegada (hora do servidor)", "arrival_time": "Hora de chegada (hora do servidor)",
"authed_user": "Usuário autoritário", "authed_user": "Usuário autoritário",
"ays": "Tem certeza de que deseja continuar?", "ays": "Tem certeza de que deseja continuar?",
"ban_list_info": "Veja abaixo a lista de IPs banidos: <b>rede (tempo restante do banimento) - [ações]</b>.<br />IPs na fila para serem desbanidos serão removidos da lista de banimentos ativos em alguns segundos.<br />Rótulos vermelhos indicam banimentos permanentes ativos na lista de bloqueio.", "ban_list_info": "Veja uma lista de IPs banidos abaixo: <b>rede (tempo restante de banimento) - [ações]</b>. <br />Os IPs na fila para serem desbanidos serão removidos da lista de banimentos ativos em alguns segundos. <br />Rótulos vermelhos indicam proibições permanentes ativas na lista negra.",
"change_logo": "Alterar logotipo", "change_logo": "Alterar logotipo",
"logo_normal_label": "Normal", "logo_normal_label": "Normal",
"logo_dark_label": "Invertido para o modo escuro", "logo_dark_label": "Invertido para o modo escuro",
"configuration": "Configuração", "configuration": "Configuração",
"convert_html_to_text": "Converter HTML em texto sem formatação", "convert_html_to_text": "Converter HTML em texto sem formatação",
"copy_to_clipboard": "Copiado para a área de transferência!", "copy_to_clipboard": "Text copied to clipboard!",
"cors_settings": "Configurações do CORS", "cors_settings": "Configurações do CORS",
"credentials_transport_warning": "<b>Aviso</b>: Adicionar uma nova entrada no mapa de transporte atualizará as credenciais de todas as entradas com uma coluna correspondente do próximo salto.", "credentials_transport_warning": "<b>Aviso</b>: Adicionar uma nova entrada no mapa de transporte atualizará as credenciais de todas as entradas com uma coluna correspondente do próximo salto.",
"customer_id": "ID do cliente", "customer_id": "ID do cliente",
@@ -189,9 +188,9 @@
"excludes": "Exclui esses destinatários", "excludes": "Exclui esses destinatários",
"f2b_ban_time": "Tempo (s) de proibição", "f2b_ban_time": "Tempo (s) de proibição",
"f2b_ban_time_increment": "O tempo de banimento é incrementado com cada banimento", "f2b_ban_time_increment": "O tempo de banimento é incrementado com cada banimento",
"f2b_blacklist": "Redes/hosts na lista de bloqueio", "f2b_blacklist": "Redes/hosts na lista negra",
"f2b_filter": "Filtros Regex", "f2b_filter": "Filtros Regex",
"f2b_list_info": "Um host ou rede na lista de bloqueio sempre terá prioridade sobre uma entidade na lista de permissões. <b>As atualizações das listas levarão alguns segundos para serem aplicadas.</b>", "f2b_list_info": "Um host ou rede na lista negra sempre superará uma entidade na lista branca. <b>As atualizações da lista levarão alguns segundos para serem aplicadas.</b>",
"f2b_manage_external": "Gerenciar Fail2Ban externamente", "f2b_manage_external": "Gerenciar Fail2Ban externamente",
"f2b_manage_external_info": "O Fail2ban ainda manterá a lista de banimentos, mas não definirá ativamente regras para bloquear o tráfego. Use a lista de banimento gerada abaixo para bloquear externamente o tráfego.", "f2b_manage_external_info": "O Fail2ban ainda manterá a lista de banimentos, mas não definirá ativamente regras para bloquear o tráfego. Use a lista de banimento gerada abaixo para bloquear externamente o tráfego.",
"f2b_max_attempts": "Máximo de tentativas", "f2b_max_attempts": "Máximo de tentativas",
@@ -201,7 +200,7 @@
"f2b_parameters": "Parâmetros do Fail2ban", "f2b_parameters": "Parâmetros do Fail2ban",
"f2b_regex_info": "Registros considerados: SoGo, Postfix, Dovecot, PHP-FPM.", "f2b_regex_info": "Registros considerados: SoGo, Postfix, Dovecot, PHP-FPM.",
"f2b_retry_window": "Repita a (s) janela (s) para o máximo de tentativas", "f2b_retry_window": "Repita a (s) janela (s) para o máximo de tentativas",
"f2b_whitelist": "Redes/hosts na lista de permissões", "f2b_whitelist": "Redes/hosts incluídos na lista branca",
"filter_table": "Tabela de filtros", "filter_table": "Tabela de filtros",
"forwarding_hosts": "Anfitriões de encaminhamento", "forwarding_hosts": "Anfitriões de encaminhamento",
"forwarding_hosts_add_hint": "Você pode especificar endereços IPv4/IPv6, redes em notação CIDR, nomes de host (que serão resolvidos para endereços IP) ou nomes de domínio (que serão resolvidos para endereços IP consultando registros SPF ou, na ausência deles, registros MX).", "forwarding_hosts_add_hint": "Você pode especificar endereços IPv4/IPv6, redes em notação CIDR, nomes de host (que serão resolvidos para endereços IP) ou nomes de domínio (que serão resolvidos para endereços IP consultando registros SPF ou, na ausência deles, registros MX).",
@@ -223,7 +222,7 @@
"includes": "Inclua esses destinatários", "includes": "Inclua esses destinatários",
"ip_check": "Verificação de IP", "ip_check": "Verificação de IP",
"ip_check_disabled": "A verificação de IP está desativada. Você pode ativá-lo em <br><strong>Sistema > Configuração > Opções > Personalizar</strong>", "ip_check_disabled": "A verificação de IP está desativada. Você pode ativá-lo em <br><strong>Sistema > Configuração > Opções > Personalizar</strong>",
"ip_check_opt_in": "Opte por usar o serviço de terceiros <strong>ipv4.mailcow.email</strong> e <strong>ipv6.mailcow.email</strong> para resolver endereços IP externos.", "ip_check_opt_in": "Opte por usar o serviço de terceiros <strong>ipv4.mailcow.email.</strong> e <strong>ipv6.mailcow.email</strong> para resolver endereços IP externos.",
"is_mx_based": "Baseado em MX", "is_mx_based": "Baseado em MX",
"last_applied": "Aplicado pela última vez", "last_applied": "Aplicado pela última vez",
"license_info": "Uma licença não é necessária, mas ajuda no desenvolvimento.<br><a href=\"https://www.servercow.de/mailcow? Lang=en#sal\" target=\"_blank\" alt=\"SAL order\">Registre seu GUID aqui</a> ou <a href=\"https://www.servercow.de/mailcow? Lang=en#support\" target=\"_blank\" alt=\"Support order\">comprar suporte para sua instalação de mailcow.</a>", "license_info": "Uma licença não é necessária, mas ajuda no desenvolvimento.<br><a href=\"https://www.servercow.de/mailcow? Lang=en#sal\" target=\"_blank\" alt=\"SAL order\">Registre seu GUID aqui</a> ou <a href=\"https://www.servercow.de/mailcow? Lang=en#support\" target=\"_blank\" alt=\"Support order\">comprar suporte para sua instalação de mailcow.</a>",
@@ -245,7 +244,7 @@
"oauth2_add_client": "Adicionar cliente OAuth2", "oauth2_add_client": "Adicionar cliente OAuth2",
"oauth2_client_id": "ID do cliente", "oauth2_client_id": "ID do cliente",
"oauth2_client_secret": "Segredo do cliente", "oauth2_client_secret": "Segredo do cliente",
"oauth2_info": "A implementação do OAuth2 suporta o tipo de concessão \"Código de Autorização\" e emite tokens de atualização.<br>\nO servidor também emite automaticamente novos tokens de atualização após um token de atualização ter sido usado.<br>\nO escopo padrão é <i>profile</i>. Somente usuários de caixa de correio podem ser autenticados com o OAuth2. Se o parâmetro de escopo for omitido, o padrão será <i>profile</i>.<br>\nO parâmetro <i>state</i> deve ser enviado pelo cliente como parte da solicitação de autorização.<br><br>\nCaminhos para solicitações à API OAuth2: <br>\n\n<ul>\n <li>Ponto de extremidade de autorização: <code>/oauth/authorize</code></li>\n <li>Endpoint do token: <code>/oauth/token</code></li>\n <li>Página de recursos: <code>/oauth/profile</code></li>\n</ul>\n\nRegenerar o segredo do cliente não expirará os códigos de autorização existentes, mas impedirá a renovação do token.<br><br>\nA revogação dos tokens de cliente causará o encerramento imediato de todas as sessões ativas. Todos os clientes precisarão se autenticar novamente.", "oauth2_info": "A implementação OAuth2 suporta o tipo de concessão \"Código de Autorização\" e emite tokens de atualização.<br>\nO servidor também emite automaticamente novos tokens de atualização, depois que um token de atualização foi usado.<br><br>\n&#8226; O escopo padrão é <i>perfil</i>. Somente usuários com caixa de e-mail podem ser autenticados contra o OAuth2. Se o parâmetro de escopo for omitido, ele voltará para <i>perfil</i>.<br>\nCaminhos para solicitações OAuth2 API: <br>\n<ul>\n<li>Endpoint de autorização: <code>/oauth/authorize</code></li>\n<li>Endpoint token: <code>/oauth/token</code></li>\n<li>Página de recursos: <code>/oauth/profile</code></li>\n</ul>\nRegenerar o segredo do cliente não expirará os códigos de autorização existentes, mas eles não renovarão seu token.<br><br>\nA revogação dos tokens do cliente causará o término imediato de todas as sessões ativas. Todos os clientes precisam se autenticar novamente.",
"oauth2_redirect_uri": "URI de redirecionamento", "oauth2_redirect_uri": "URI de redirecionamento",
"oauth2_renew_secret": "Gere um novo segredo de cliente", "oauth2_renew_secret": "Gere um novo segredo de cliente",
"oauth2_revoke_tokens": "Revogar todos os tokens do cliente", "oauth2_revoke_tokens": "Revogar todos os tokens do cliente",
@@ -321,8 +320,8 @@
"rspamd_com_settings": "Um nome de configuração será gerado automaticamente, veja os exemplos de predefinições abaixo. Para obter mais detalhes, consulte a documentação <a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">do Rspamd</a>", "rspamd_com_settings": "Um nome de configuração será gerado automaticamente, veja os exemplos de predefinições abaixo. Para obter mais detalhes, consulte a documentação <a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">do Rspamd</a>",
"rspamd_global_filters": "Mapas de filtro globais", "rspamd_global_filters": "Mapas de filtro globais",
"rspamd_global_filters_agree": "Eu vou ter cuidado!", "rspamd_global_filters_agree": "Eu vou ter cuidado!",
"rspamd_global_filters_info": "Os mapas de filtros globais contêm diferentes tipos de listas globais de bloqueio e de permissões.", "rspamd_global_filters_info": "Os mapas de filtros globais contêm diferentes tipos de listas negras e brancas globais.",
"rspamd_global_filters_regex": "Os nomes explicam sua finalidade. Todo o conteúdo deve conter uma expressão regular válida no formato \"/padrão/opções\" (por exemplo, <code>/.[+@domain.tld](mailto:+@domain.tld)/i</code>).<br>\nEmbora verificações básicas sejam executadas em cada linha de regex, a funcionalidade do Rspamd pode ser comprometida se ele não conseguir ler a sintaxe corretamente.<br>\nO Rspamd tentará ler o conteúdo do mapa quando ele for alterado. Se você tiver problemas, <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">reinicie o Rspamd</a> para forçar o recarregamento do mapa.<br>Elementos na lista de bloqueio são excluídos da quarentena.", "rspamd_global_filters_regex": "Seus nomes explicam seu propósito. <code>Todo o conteúdo deve conter uma expressão regular válida no formato “/padrão/opções” (por exemplo, /. + @domain\\ .tld/i</code>). <br>\r\n Embora verificações rudimentares estejam sendo executadas em cada linha de regex, a funcionalidade do Rspamd pode ser interrompida se não conseguir ler a sintaxe corretamente. <br>\r\n O Rspamd tentará ler o conteúdo do mapa quando alterado. Se você tiver problemas, <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">reinicie o Rspamd</a> para forçar o recarregamento do mapa. <br>Os elementos da lista negra são excluídos da quarentena.",
"rspamd_settings_map": "Mapa de configurações do Rspamd", "rspamd_settings_map": "Mapa de configurações do Rspamd",
"sal_level": "Nível de humor", "sal_level": "Nível de humor",
"save": "Salvar alterações", "save": "Salvar alterações",
@@ -557,9 +556,7 @@
"mode_invalid": "Modo %s é inválido", "mode_invalid": "Modo %s é inválido",
"mx_invalid": "Registro MX %s é inválido", "mx_invalid": "Registro MX %s é inválido",
"required_data_missing": "Dados obrigatórios %s estão ausentes", "required_data_missing": "Dados obrigatórios %s estão ausentes",
"version_invalid": "Versão %s é inválida", "version_invalid": "Versão %s é inválida"
"tfa_removal_blocked": "A autenticação de dois fatores não pode ser removida, pois é obrigatória para a sua conta.",
"quarantine_category_invalid": "A categoria da quarentena deve ser uma das seguintes: add_header, reject, all"
}, },
"datatables": { "datatables": {
"collapse_all": "Recolher tudo", "collapse_all": "Recolher tudo",
@@ -730,7 +727,7 @@
"pushover_verify": "Verifique as credenciais", "pushover_verify": "Verifique as credenciais",
"quota_mb": "Cota (MiB)", "quota_mb": "Cota (MiB)",
"quota_warning_bcc": "Aviso de cota BCC", "quota_warning_bcc": "Aviso de cota BCC",
"quota_warning_bcc_info": "Os avisos serão enviados em cópias separadas para os seguintes destinatários. O assunto será precedido pelo nome de usuário correspondente entre colchetes, por exemplo: <code>Aviso de cota (user@example.com)</code>.", "quota_warning_bcc_info": "Os avisos serão enviados em cópias separadas para os seguintes destinatários. O assunto será sufixado pelo nome de usuário correspondente entre colchetes, por exemplo: <code>Aviso de cota (</code>user@example.com).",
"ratelimit": "Limite de taxa", "ratelimit": "Limite de taxa",
"redirect_uri": "URL de redirecionamento/retorno de chamada", "redirect_uri": "URL de redirecionamento/retorno de chamada",
"relay_all": "Retransmita todos os destinatários", "relay_all": "Retransmita todos os destinatários",
@@ -749,17 +746,17 @@
"sieve_desc": "Breve descrição", "sieve_desc": "Breve descrição",
"sieve_type": "Tipo de filtro", "sieve_type": "Tipo de filtro",
"skipcrossduplicates": "Ignore mensagens duplicadas entre pastas (primeiro a chegar, primeiro a ser servido)", "skipcrossduplicates": "Ignore mensagens duplicadas entre pastas (primeiro a chegar, primeiro a ser servido)",
"sogo_access": "Encaminhamento direto para o SOGo", "sogo_access": "Encaminhamento direto para o SOGoo",
"sogo_access_info": "Após o login, o usuário é automaticamente redirecionado para o SOGo.", "sogo_access_info": "Depois de fazer login, o usuário é automaticamente redirecionado para o SOGo.",
"sogo_visible": "O alias é visível no SoGo", "sogo_visible": "O alias é visível no SoGo",
"sogo_visible_info": "Essa opção afeta somente objetos, que podem ser exibidos no SoGo (endereços de alias compartilhados ou não compartilhados apontando para pelo menos uma mailbox local). Se estiver oculto, um alias não aparecerá como remetente selecionável no SoGo.", "sogo_visible_info": "Essa opção afeta somente objetos, que podem ser exibidos no SoGo (endereços de alias compartilhados ou não compartilhados apontando para pelo menos uma mailbox local). Se estiver oculto, um alias não aparecerá como remetente selecionável no SoGo.",
"spam_alias": "Crie ou altere endereços de alias com limite de tempo", "spam_alias": "Crie ou altere endereços de alias com limite de tempo",
"spam_filter": "Filtro de spam", "spam_filter": "Filtro de spam",
"spam_policy": "Adicionar ou remover itens da lista de permissões/bloqueio", "spam_policy": "Adicionar ou remover itens da lista branca/negra",
"spam_score": "Defina uma pontuação de spam personalizada", "spam_score": "Defina uma pontuação de spam personalizada",
"subfolder2": "Sincronizar na subpasta no destino <br><small>(vazio = não usar subpasta</small>)", "subfolder2": "Sincronizar na subpasta no destino <br><small>(vazio = não usar subpasta</small>)",
"syncjob": "Editar tarefa de sincronização", "syncjob": "Editar tarefa de sincronização",
"target_address": "Ir para o(s) endereço(s) <small>(separados por vírgula)</small>", "target_address": "<small>Ir para endereço/es (separados por vírgula)</small>",
"target_domain": "Domínio de destino", "target_domain": "Domínio de destino",
"timeout1": "Tempo limite para conexão com o host remoto", "timeout1": "Tempo limite para conexão com o host remoto",
"timeout2": "Tempo limite para conexão com o host local", "timeout2": "Tempo limite para conexão com o host local",
@@ -784,10 +781,7 @@
"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_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": "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_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).", "mta_sts_mx_notice": "Múltiplos servidores MX podem ser especificados (separados por vírgulas)."
"sender_allowed": "Permitir enviar como este alias",
"sender_allowed_info": "Se desativado, este alias poderá apenas receber e-mails. Use a ACL de remetente para substituir essa configuração e conceder a caixas de correio específicas permissão para enviar.",
"mta_sts_active_info": "Se esta opção estiver desmarcada, a política MTA-STS não será publicada e nenhum certificado será solicitado via ACME para o subdomínio mta-sts."
}, },
"fido2": { "fido2": {
"confirm": "Confirme", "confirm": "Confirme",
@@ -808,7 +802,7 @@
"cancel": "Cancelar", "cancel": "Cancelar",
"confirm_delete": "Confirme a exclusão", "confirm_delete": "Confirme a exclusão",
"delete_now": "Excluir agora", "delete_now": "Excluir agora",
"delete_these_items": "Por favor, confirme as alterações feitas no seguinte ID de objeto.", "delete_these_items": "Confirme suas alterações no seguinte ID de objeto",
"hibp_check": "Verifique em haveibeenpwned.com", "hibp_check": "Verifique em haveibeenpwned.com",
"hibp_nok": "Combinado! Essa é uma senha potencialmente perigosa!", "hibp_nok": "Combinado! Essa é uma senha potencialmente perigosa!",
"hibp_ok": "Nenhuma combinação encontrada.", "hibp_ok": "Nenhuma combinação encontrada.",
@@ -976,7 +970,7 @@
"recipient_map_new": "Novo destinatário", "recipient_map_new": "Novo destinatário",
"recipient_map_new_info": "O destino do mapa do destinatário deve ser um endereço de e-mail válido ou um nome de domínio.", "recipient_map_new_info": "O destino do mapa do destinatário deve ser um endereço de e-mail válido ou um nome de domínio.",
"recipient_map_old": "Destinatário original", "recipient_map_old": "Destinatário original",
"recipient_map_old_info": "O destino original do mapa de destinatário deve ser um endereço de e-mail válido ou um nome de domínio.", "recipient_map_old_info": "O destino original do mapa de um destinatário deve ser um endereço de e-mail válido ou um nome de domínio.",
"recipient_maps": "Mapas de destinatários", "recipient_maps": "Mapas de destinatários",
"relay_all": "Retransmita todos os destinatários", "relay_all": "Retransmita todos os destinatários",
"relay_unknown": "Retransmitir mailboxes desconhecidas", "relay_unknown": "Retransmitir mailboxes desconhecidas",
@@ -1035,8 +1029,7 @@
"weekly": "Semanalmente", "weekly": "Semanalmente",
"yes": "✓", "yes": "✓",
"iam": "Provedor de Identidade", "iam": "Provedor de Identidade",
"internal": "Interno", "internal": "Interno"
"force_tfa": "A2F"
}, },
"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.",
@@ -1068,7 +1061,7 @@
"notified": "Notificado", "notified": "Notificado",
"qhandler_success": "Solicitação enviada com sucesso para o sistema. Agora você pode fechar a janela.", "qhandler_success": "Solicitação enviada com sucesso para o sistema. Agora você pode fechar a janela.",
"qid": "Respand AID", "qid": "Respand AID",
"qinfo": "O sistema de quarentena irá salvar no banco de dados os e-mails rejeitados (o remetente <em>não</em> terá a impressão de que o e-mail foi entregue), bem como os e-mails que são entregues como cópia na pasta de spam de uma mailbox.\n<br>“Aprender como spam e excluir” irá classificar a mensagem como spam por meio do Teorema de Bayes e também calcular hashes fuzzy para bloquear mensagens semelhantes no futuro.\n<br>Esteja ciente de que o aprendizado de múltiplas mensagens pode, dependendo do seu sistema, levar tempo. <br>Elementos na lista de bloqueio são excluídos da quarentena.", "qinfo": "O sistema de quarentena salvará as mensagens rejeitadas no banco de dados (o remetente <em>não</em> terá a impressão de uma mensagem entregue), bem como as mensagens, que são entregues como cópia na pasta Lixo eletrônico de uma mailbox.\n <br>“Aprenda como spam e exclua” aprenderá uma mensagem como spam por meio do Teorema de Bayes e também calculará hashes difusos para negar mensagens semelhantes no futuro.\n <br>Esteja ciente de que aprender várias mensagens pode ser demorado, dependendo do seu sistema. <br>Os elementos da lista negra são excluídos da quarentena.",
"qitem": "Item de quarentena", "qitem": "Item de quarentena",
"quarantine": "Quarentena", "quarantine": "Quarentena",
"quick_actions": "Ações", "quick_actions": "Ações",
@@ -1088,7 +1081,7 @@
"rspamd_result": "Resultado do Rspamd", "rspamd_result": "Resultado do Rspamd",
"sender": "Remetente (SMTP)", "sender": "Remetente (SMTP)",
"sender_header": "Remetente (cabeçalho “De”)", "sender_header": "Remetente (cabeçalho “De”)",
"settings_info": "Número máximo de elementos em quarentena (por caixa de correio): %s <br>Tamanho máximo do e-mail: %s MiB", "settings_info": "Quantidade máxima de elementos a serem colocados em quarentena: %s <br>Tamanho máximo do e-mail: %s MiB",
"show_item": "Mostrar item", "show_item": "Mostrar item",
"spam": "Spam", "spam": "Spam",
"spam_score": "Ponto", "spam_score": "Ponto",
@@ -1244,12 +1237,7 @@
"webauthn": "Autenticação WebAuthn", "webauthn": "Autenticação WebAuthn",
"waiting_usb_auth": "<i>Aguardando o dispositivo USB...</i> <br><br>Toque no botão no seu dispositivo USB agora.", "waiting_usb_auth": "<i>Aguardando o dispositivo USB...</i> <br><br>Toque no botão no seu dispositivo USB agora.",
"waiting_usb_register": "<i>Aguardando o dispositivo USB...</i> <br><br>Digite sua senha acima e confirme seu registro tocando no botão no seu dispositivo USB.", "waiting_usb_register": "<i>Aguardando o dispositivo USB...</i> <br><br>Digite sua senha acima e confirme seu registro tocando no botão no seu dispositivo USB.",
"yubi_otp": "Autenticação Yubico OTP", "yubi_otp": "Autenticação Yubico OTP"
"force_tfa": "Forçar o cadastro de 2FA no login",
"force_tfa_info": "O usuário será obrigado a configurar a autenticação de dois fatores antes de acessar o painel.",
"setup_title": "Autenticação de Dois Fatores Obrigatória",
"setup_required": "Sua conta exige autenticação de dois fatores. Configure um método de 2FA para continuar.",
"cancel_setup": "Cancelar e sair"
}, },
"user": { "user": {
"action": "Ação", "action": "Ação",
@@ -1288,7 +1276,7 @@
"delete_ays": "Confirme o processo de exclusão.", "delete_ays": "Confirme o processo de exclusão.",
"direct_aliases": "Endereços de alias diretos", "direct_aliases": "Endereços de alias diretos",
"direct_aliases_desc": "Os endereços de alias diretos são afetados pelo filtro de spam e pelas configurações da política TLS.", "direct_aliases_desc": "Os endereços de alias diretos são afetados pelo filtro de spam e pelas configurações da política TLS.",
"direct_protocol_access": "Este usuário da mailbox possui <b>acesso direto e externo</b> aos seguintes protocolos e aplicações. Esta configuração é controlada pelo seu administrador. Senhas de aplicativo podem ser criadas para conceder acesso a protocolos e aplicações específicos.<br>O botão “Webmail” fornece login único (SSO) para o SOGo e está sempre disponível.", "direct_protocol_access": "Esse usuário da mailbox tem <b>acesso externo direto</b> aos seguintes protocolos e aplicativos. Essa configuração é controlada pelo administrador. As senhas de aplicativos podem ser criadas para conceder acesso a protocolos e aplicativos individuais. <br>O botão “Login no webmail” fornece login único no SoGo e está sempre disponível.",
"eas_reset": "Redefinir o cache do dispositivo ActiveSync", "eas_reset": "Redefinir o cache do dispositivo ActiveSync",
"eas_reset_help": "Em muitos casos, uma redefinição do cache do dispositivo ajudará a recuperar um perfil quebrado do ActiveSync. <br><b>Atenção:</b> Todos os elementos serão baixados novamente!", "eas_reset_help": "Em muitos casos, uma redefinição do cache do dispositivo ajudará a recuperar um perfil quebrado do ActiveSync. <br><b>Atenção:</b> Todos os elementos serão baixados novamente!",
"eas_reset_now": "Reinicie agora", "eas_reset_now": "Reinicie agora",
@@ -1364,12 +1352,12 @@
"sogo_profile_reset": "Redefinir perfil SoGo", "sogo_profile_reset": "Redefinir perfil SoGo",
"sogo_profile_reset_help": "Isso destruirá o perfil SoGo de um usuário e <b>excluirá todos os dados de contato e calendário irrecuperáveis</b>.", "sogo_profile_reset_help": "Isso destruirá o perfil SoGo de um usuário e <b>excluirá todos os dados de contato e calendário irrecuperáveis</b>.",
"sogo_profile_reset_now": "Redefina o perfil agora", "sogo_profile_reset_now": "Redefina o perfil agora",
"spam_aliases": "Aliases de e-mail de spam", "spam_aliases": "Aliases de e-mail temporários",
"spam_score_reset": "Redefinir para o padrão do servidor", "spam_score_reset": "Redefinir para o padrão do servidor",
"spamfilter": "Filtro de spam", "spamfilter": "Filtro de spam",
"spamfilter_behavior": "Avaliação", "spamfilter_behavior": "Avaliação",
"spamfilter_bl": "Lista de bloqueio", "spamfilter_bl": "Lista negra",
"spamfilter_bl_desc": "Endereços de e-mail na lista de bloqueio para <b>sempre</b> classificar como spam e rejeitados. E-mails rejeitados <b>não</b> serão copiados para a quarentena. Coringas podem ser utilizados. O filtro é aplicado apenas a aliases diretos (aliases com uma única mailbox de destino), excluindo aliases catch-all e a própria mailbox.", "spamfilter_bl_desc": "Endereços de e-mail na lista negra para <b>sempre</b> serem classificados como spam e rejeitados. E-mails rejeitados <b>não</b> serão copiados para a quarentena. Podem ser usados curingas. Um filtro só é aplicado a aliases diretos (aliases com uma única caixa de correio de destino), excluindo aliases abrangentes e a própria mailbox.",
"spamfilter_default_score": "Valores padrão", "spamfilter_default_score": "Valores padrão",
"spamfilter_green": "Verde: esta mensagem não é spam", "spamfilter_green": "Verde: esta mensagem não é spam",
"spamfilter_hint": "O primeiro valor descreve a “pontuação baixa de spam”, o segundo representa a “alta pontuação de spam”.", "spamfilter_hint": "O primeiro valor descreve a “pontuação baixa de spam”, o segundo representa a “alta pontuação de spam”.",
@@ -1380,8 +1368,8 @@
"spamfilter_table_empty": "Não há dados para exibir", "spamfilter_table_empty": "Não há dados para exibir",
"spamfilter_table_remove": "remover", "spamfilter_table_remove": "remover",
"spamfilter_table_rule": "Regra", "spamfilter_table_rule": "Regra",
"spamfilter_wl": "Lista de permissões", "spamfilter_wl": "Lista branca",
"spamfilter_wl_desc": "Endereços de e-mail na lista de permissões são configurados para <b>nunca</b> serem classificados como spam. Coringas podem ser utilizados. O filtro é aplicado apenas a aliases diretos (aliases com uma única mailbox de destino), excluindo aliases catch-all e a própria mailbox.", "spamfilter_wl_desc": "Os endereços de e-mail incluídos na lista branca são programados para <b>nunca</b> serem classificados como spam. Podem ser usados curingas. Um filtro só é aplicado a aliases diretos (aliases com uma única mailbox de destino), excluindo aliases abrangentes e a própria mailbox.",
"spamfilter_yellow": "Amarelo: esta mensagem pode ser spam, será marcada como spam e movida para sua pasta de lixo eletrônico", "spamfilter_yellow": "Amarelo: esta mensagem pode ser spam, será marcada como spam e movida para sua pasta de lixo eletrônico",
"status": "Status", "status": "Status",
"sync_jobs": "Trabalhos de sincronização", "sync_jobs": "Trabalhos de sincronização",
@@ -1395,7 +1383,7 @@
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Não é possível se conectar ao servidor remoto", "syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Não é possível se conectar ao servidor remoto",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nome de usuário ou senha incorretos", "syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nome de usuário ou senha incorretos",
"tag_handling": "Definir o tratamento para e-mails marcados", "tag_handling": "Definir o tratamento para e-mails marcados",
"tag_help_example": "Exemplo de endereço de e-mail com tag: eu+Facebook</b>@exemplo.org", "tag_help_example": "Exemplo de um endereço de e-mail marcado: me <b>+Facebook</b> @example .org",
"tag_help_explain": "Na subpasta: uma nova subpasta com o nome da tag será criada abaixo da CAIXA DE ENTRADA (“Caixa de entrada/Facebook”). <br>\r\nNo assunto: o nome das tags será anexado ao assunto do e-mail, por exemplo: “[Facebook] Minhas notícias”.", "tag_help_explain": "Na subpasta: uma nova subpasta com o nome da tag será criada abaixo da CAIXA DE ENTRADA (“Caixa de entrada/Facebook”). <br>\r\nNo assunto: o nome das tags será anexado ao assunto do e-mail, por exemplo: “[Facebook] Minhas notícias”.",
"tag_in_none": "Não faça nada", "tag_in_none": "Não faça nada",
"tag_in_subfolder": "Na subpasta", "tag_in_subfolder": "Na subpasta",
@@ -1420,11 +1408,7 @@
"authentication": "Autenticação", "authentication": "Autenticação",
"overview": "Visão geral", "overview": "Visão geral",
"protocols": "Protocolos", "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).", "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)."
"expire_never": "Nunca expirar",
"forever": "Para sempre",
"pw_update_required": "Sua conta exige a alteração de senha. Defina uma nova senha para continuar.",
"spam_aliases_info": "Um alias de spam é um endereço de e-mail temporário que pode ser usado para proteger endereços de e-mail reais. <br>Opcionalmente, pode-se definir um tempo de expiração para que o alias seja automaticamente desativado após o período definido, descartando efetivamente endereços que tenham sido abusados ou vazados."
}, },
"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",
+7 -19
View File
@@ -29,8 +29,7 @@
"extend_sender_acl": "Dovoli razširitev pošiljateljevega ACL z zunanjimi e-poštnimi naslovi", "extend_sender_acl": "Dovoli razširitev pošiljateljevega ACL z zunanjimi e-poštnimi naslovi",
"quarantine_category": "Spremeni kategorijo obvestil o karanteni", "quarantine_category": "Spremeni kategorijo obvestil o karanteni",
"syncjobs": "Sinhronizacijska opravila", "syncjobs": "Sinhronizacijska opravila",
"pw_reset": "Dovoli ponastavitev uporabniškega gesla mailcow", "pw_reset": "Dovoli ponastavitev uporabniškega gesla mailcow"
"alias_external_goto": "Dovoli vzdevke z zunanjimi domenami goto"
}, },
"add": { "add": {
"active": "Aktivno", "active": "Aktivno",
@@ -413,12 +412,7 @@
"task": "Naloga", "task": "Naloga",
"user_link": "Uporabniška povezava", "user_link": "Uporabniška povezava",
"iam_realm": "Realm", "iam_realm": "Realm",
"needs_restart": "potreben je ponovni zagon", "needs_restart": "potreben je ponovni zagon"
"ui_theme": "Tema",
"ui_theme_dark": "Vključuje različico s temnim načinom",
"ui_theme_default": "Privzeto",
"ui_theme_info": "Teme so mape v <code>data/web/css/themes/&lt;name&gt;/</code>, ki vsebujejo naslednje datoteke:<ul class=\"mb-0\"><li><code>theme.css</code> (obvezna)</li><li><code>dark.css</code> (neobvezna, temni način)</li><li><code>bootstrap.css</code> (neobvezna, nadomesti osnovno temo Bootstrap)</li><li><code>theme.json</code> (neobvezna, <code>name</code> in <code>description</code>)</li></ul>",
"ui_theme_none": "Tem ni bilo mogoče najti."
}, },
"danger": { "danger": {
"alias_goto_identical": "Vzdevek in ciljni naslov se ne smeta ujemati", "alias_goto_identical": "Vzdevek in ciljni naslov se ne smeta ujemati",
@@ -564,10 +558,7 @@
"mode_invalid": "Način %s ni veljaven", "mode_invalid": "Način %s ni veljaven",
"mx_invalid": "Zapis MX %s je neveljaven", "mx_invalid": "Zapis MX %s je neveljaven",
"version_invalid": "Različica %s je neveljavna", "version_invalid": "Različica %s je neveljavna",
"tfa_removal_blocked": "Dvofaktorske avtentikacije ni mogoče odstraniti, ker je obvezna za vaš račun.", "tfa_removal_blocked": "Dvofaktorske avtentikacije ni mogoče odstraniti, ker je obvezna za vaš račun."
"quarantine_category_invalid": "Kategorija karantene mora biti ena od: add_header, reject, all",
"external_goto_denied": "Zunanji naslov goto %s ni dovoljen",
"ui_theme_invalid": "Tema ni najdena"
}, },
"debug": { "debug": {
"containers_info": "Informacije o zabojniku", "containers_info": "Informacije o zabojniku",
@@ -793,8 +784,7 @@
"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.",
@@ -1010,8 +1000,7 @@
"weekly": "Tedensko", "weekly": "Tedensko",
"sieve_info": "Na uporabnika lahko shranite več filtrov, vendar je lahko hkrati aktiven le en predfilter in en postfilter.<br>\nVsak filter bo obdelan v opisanem vrstnem redu. Niti neuspešen skript niti izdan ukaz »keep;« ne bosta ustavila obdelave nadaljnjih skript. Spremembe globalnih skriptov sita bodo sprožile ponovni zagon Dovecota.<br><br>Globalni predfilter sita &#8226; Predfilter &#8226; Uporabniški skripti &#8226; Postfilter &#8226; Globalni postfilter sita", "sieve_info": "Na uporabnika lahko shranite več filtrov, vendar je lahko hkrati aktiven le en predfilter in en postfilter.<br>\nVsak filter bo obdelan v opisanem vrstnem redu. Niti neuspešen skript niti izdan ukaz »keep;« ne bosta ustavila obdelave nadaljnjih skript. Spremembe globalnih skriptov sita bodo sprožile ponovni zagon Dovecota.<br><br>Globalni predfilter sita &#8226; Predfilter &#8226; Uporabniški skripti &#8226; Postfilter &#8226; Globalni postfilter sita",
"tls_policy_maps_info": "Ta preslikava pravilnikov preglasi pravila odhodnega prenosa TLS neodvisno od uporabnikovih nastavitev pravilnikov TLS.<br>\n Za več informacij preverite <a href=\"http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps\" target=\"_blank\">dokumentacijo »smtp_tls_policy_maps«</a>.", "tls_policy_maps_info": "Ta preslikava pravilnikov preglasi pravila odhodnega prenosa TLS neodvisno od uporabnikovih nastavitev pravilnikov TLS.<br>\n Za več informacij preverite <a href=\"http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps\" target=\"_blank\">dokumentacijo »smtp_tls_policy_maps«</a>.",
"internal": "Notranje", "internal": "Notranje"
"force_tfa": "TFA"
}, },
"fido2": { "fido2": {
"known_ids": "Znani ID-ji", "known_ids": "Znani ID-ji",
@@ -1087,7 +1076,7 @@
"rspamd_result": "Rezultat Rspamd", "rspamd_result": "Rezultat Rspamd",
"sender": "Pošiljatelj (SMTP)", "sender": "Pošiljatelj (SMTP)",
"sender_header": "Pošiljatelj (glava »Od«)", "sender_header": "Pošiljatelj (glava »Od«)",
"settings_info": "Največje število elementov za karanteno (na poštni predal): %s<br>Največja velikost e-pošte: %s MiB", "settings_info": "Največje število elementov za karanteno: %s<br>Največja velikost e-pošte: %s MiB",
"show_item": "Prikaži element", "show_item": "Prikaži element",
"spam": "Neželena pošta", "spam": "Neželena pošta",
"subj": "Zadeva", "subj": "Zadeva",
@@ -1212,8 +1201,7 @@
"cors_headers_edited": "Nastavitve CORS so bile shranjene", "cors_headers_edited": "Nastavitve CORS so bile shranjene",
"item_released": "Izdan je bil element %s", "item_released": "Izdan je bil element %s",
"saved_settings": "Shranjene nastavitve", "saved_settings": "Shranjene nastavitve",
"learned_ham": "Uspešno naučen ID %s kot zaželjen", "learned_ham": "Uspešno naučen ID %s kot zaželjen"
"ui_theme_modified": "Tema je bila uspešno shranjena"
}, },
"start": { "start": {
"imap_smtp_server_auth_info": "Prosimo, uporabite svoj celoten e-poštni naslov in mehanizem za preverjanje pristnosti PLAIN.<br>\nVaši podatki za prijavo bodo šifrirani z obveznim šifriranjem na strani strežnika.", "imap_smtp_server_auth_info": "Prosimo, uporabite svoj celoten e-poštni naslov in mehanizem za preverjanje pristnosti PLAIN.<br>\nVaši podatki za prijavo bodo šifrirani z obveznim šifriranjem na strani strežnika.",
File diff suppressed because it is too large. Load diff
+5 -22
View File
@@ -268,10 +268,10 @@
"includes": "Bao gồm những người nhận này", "includes": "Bao gồm những người nhận này",
"ip_check": "Kiểm tra IP", "ip_check": "Kiểm tra IP",
"ip_check_disabled": "Kiểm tra IP đã bị vô hiệu hóa. Bạn có thể bật nó trong<br> <strong>Hệ thống > Cấu hình > Tùy chọn > Tùy chỉnh</strong>", "ip_check_disabled": "Kiểm tra IP đã bị vô hiệu hóa. Bạn có thể bật nó trong<br> <strong>Hệ thống > Cấu hình > Tùy chọn > Tùy chỉnh</strong>",
"ip_check_opt_in": "Đăng ký tham gia sử dụng dịch vụ bên thứ ba dùng <strong>ipv4.mailcow.email</strong> và <strong>ipv6.mailcow.email</strong> để phân giải địa chỉ IP bên ngoài.", "ip_check_opt_in": "Chọn tham gia sử dụng dịch vụ bên thứ ba <strong>ipv4.mailcow.email</strong> và <strong>ipv6.mailcow.email</strong> để phân giải địa chỉ IP bên ngoài.",
"is_mx_based": "Dựa trên MX", "is_mx_based": "Dựa trên MX",
"last_applied": "Áp dụng lần cuối", "last_applied": "Áp dụng lần cuối",
"license_info": "Giấy phép tuy không bắt buộc nhưng giúp phát triển thêm.<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"Đặt hàng SAL\">Đăng ký GUID của bạn tại đây</a> hoặc <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Đặt hàng hỗ trợ\">mua hỗ trợ cho cài đặt mailcow của bạn.</a>", "license_info": "Giấy phép không bắt buộc nhưng giúp phát triển thêm.<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"Đặt hàng SAL\">Đăng ký GUID của bạn tại đây</a> hoặc <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Đặt hàng hỗ trợ\">mua hỗ trợ cho cài đặt mailcow của bạn.</a>",
"link": "Liên kết", "link": "Liên kết",
"loading": "Vui lòng đợi...", "loading": "Vui lòng đợi...",
"login_time": "Thời gian đăng nhập", "login_time": "Thời gian đăng nhập",
@@ -556,9 +556,7 @@
"validity_missing": "Vui lòng gán thời hạn hiệu lực", "validity_missing": "Vui lòng gán thời hạn hiệu lực",
"value_missing": "Vui lòng cung cấp tất cả các giá trị", "value_missing": "Vui lòng cung cấp tất cả các giá trị",
"version_invalid": "Phiên bản %s không hợp lệ", "version_invalid": "Phiên bản %s không hợp lệ",
"yotp_verification_failed": "Xác thực Yubico OTP thất bại: %s", "yotp_verification_failed": "Xác thực Yubico OTP thất bại: %s"
"tfa_removal_blocked": "Xác thực hai yếu tố không thể bị xóa vì đây là yêu cầu bắt buộc đối với tài khoản của bạn.",
"quarantine_category_invalid": "Danh mục cách ly phải là một trong các loại sau : add_header, reject, all."
}, },
"datatables": { "datatables": {
"collapse_all": "Thu gọn tất cả", "collapse_all": "Thu gọn tất cả",
@@ -581,9 +579,7 @@
"aria": { "aria": {
"sortAscending": ": kích hoạt để sắp xếp cột tăng dần", "sortAscending": ": kích hoạt để sắp xếp cột tăng dần",
"sortDescending": ": kích hoạt để sắp xếp cột giảm dần" "sortDescending": ": kích hoạt để sắp xếp cột giảm dần"
}, }
"decimal": ".",
"thousands": ","
}, },
"debug": { "debug": {
"architecture": "Kiến trúc", "architecture": "Kiến trúc",
@@ -697,19 +693,6 @@
"internal_info": "Bí danh nội bộ chỉ có thể truy cập từ tên miền sở hữu hoặc tên miền bí danh.", "internal_info": "Bí danh nội bộ chỉ có thể truy cập từ tên miền sở hữu hoặc tên miền bí danh.",
"kind": "Loại", "kind": "Loại",
"last_modified": "Sửa đổi lần cuối", "last_modified": "Sửa đổi lần cuối",
"lookup_mx": "Đích là một biểu thức chính quy để khớp với tên MX (<code>.*.google.com</code> để định tuyến tất cả thư nhắm đến MX kết thúc bằng google.com qua bước nhảy này)", "lookup_mx": "Đích là một biểu thức chính quy để khớp với tên MX (<code>.*.google.com</code> để định tuyến tất cả thư nhắm đến MX kết thúc bằng google.com qua bước nhảy này)"
"sender_allowed": "Cho phép gửi đi bằng bí danh",
"sender_allowed_info": "Nếu bị vô hiệu hóa, bí danh này chỉ có thể nhận thư. Sử dụng ACL của người gửi để ghi đè và cấp quyền gửi cho các hộp thư cụ thể.",
"mailbox": "Chỉnh sửa hộp thư",
"mailbox_quota_def": "Hạn mức hộp thư mặc định",
"mailbox_relayhost_info": "Chỉ áp dụng cho hộp thư và các bí danh trực tiếp, thao tác này sẽ ghi đè lên máy chủ chuyển tiếp tên miền.",
"mailbox_rename": "Đổi tên hộp thư",
"mailbox_rename_agree": "Tôi đã tạo bản sao lưu.",
"mailbox_rename_warning": "QUAN TRỌNG! Hãy tạo bản sao lưu trước khi đổi tên hộp thư.",
"mailbox_rename_alias": "Tạo tự động bí danh",
"mailbox_rename_title": "Tên mới của hộp thư cục bộ",
"max_aliases": "Số lượng Bí danh tối đa",
"max_mailboxes": "Số lượng hộp thư tối đa có thể có",
"max_quota": "Dung lượng tối đa cho mỗi hộp thư (MiB)"
} }
} }
+7 -22
View File
@@ -29,8 +29,7 @@
"syncjobs": "同步任务", "syncjobs": "同步任务",
"tls_policy": "TLS 策略", "tls_policy": "TLS 策略",
"unlimited_quota": "无限邮箱容量配额", "unlimited_quota": "无限邮箱容量配额",
"pw_reset": "允许重置mailcow使用者密码", "pw_reset": "允许重置mailcow使用者密码"
"alias_external_goto": "允许带有外部目标地址的别名"
}, },
"add": { "add": {
"activate_filter_warn": "当“启用”选项被勾选后,其它所有的过滤器都会被禁用。", "activate_filter_warn": "当“启用”选项被勾选后,其它所有的过滤器都会被禁用。",
@@ -112,8 +111,7 @@
"validation_success": "验证成功", "validation_success": "验证成功",
"dry": "模拟同步(Dry run)", "dry": "模拟同步(Dry run)",
"internal_info": "内部的别名只能在域内部或者别名域内部访问。", "internal_info": "内部的别名只能在域内部或者别名域内部访问。",
"internal": "内部的", "internal": "内部的"
"sender_allowed": "允许以该别名发送"
}, },
"admin": { "admin": {
"access": "权限管理", "access": "权限管理",
@@ -557,10 +555,7 @@
"max_age_invalid": "最大有效时间 %s 无效", "max_age_invalid": "最大有效时间 %s 无效",
"mode_invalid": "模式 %s 无效", "mode_invalid": "模式 %s 无效",
"mx_invalid": "MX 记录 %s 无效", "mx_invalid": "MX 记录 %s 无效",
"version_invalid": "版本 %s 无效", "version_invalid": "版本 %s 无效"
"tfa_removal_blocked": "无法移除两步验证,你的账户必须使用两步验证。",
"external_goto_denied": "不允许使用外部目标地址 %s",
"quarantine_category_invalid": "隔离分类必须是 add_header, reject, all 的其中一个"
}, },
"debug": { "debug": {
"chart_this_server": "图表 (此服务器)", "chart_this_server": "图表 (此服务器)",
@@ -759,10 +754,7 @@
"mta_sts_max_age_info": "接收方邮件服务器可缓存该策略的时长(秒),超出后需重新获取策略。", "mta_sts_max_age_info": "接收方邮件服务器可缓存该策略的时长(秒),超出后需重新获取策略。",
"mta_sts_mx": "MX 服务器", "mta_sts_mx": "MX 服务器",
"mta_sts_mx_info": "仅允许向明确列出的邮件服务器发送邮件;发送方 MTA 会验证 DNS MX 记录的主机名是否与策略列表匹配,并仅允许携带有效 TLS 证书的投递(可防范中间人攻击)。", "mta_sts_mx_info": "仅允许向明确列出的邮件服务器发送邮件;发送方 MTA 会验证 DNS MX 记录的主机名是否与策略列表匹配,并仅允许携带有效 TLS 证书的投递(可防范中间人攻击)。",
"mta_sts_mx_notice": "可配置多个 MX 服务器(以逗号分隔)。", "mta_sts_mx_notice": "可配置多个 MX 服务器(以逗号分隔)。"
"sender_allowed": "允许以此别名发送",
"sender_allowed_info": "如果禁用了,此别名只能接收邮件。使用发件者 ACL 来覆盖此选项并赋予指定的邮箱发送权限。",
"mta_sts_active_info": "如果未选中,MTA-STS 策略不会被公布并且不会通过 ACME 为 MTA-STS 子域名请求证书。"
}, },
"fido2": { "fido2": {
"confirm": "确认", "confirm": "确认",
@@ -1010,8 +1002,7 @@
"templates": "模板", "templates": "模板",
"template": "模板", "template": "模板",
"iam": "身份提供者(IDP)", "iam": "身份提供者(IDP)",
"internal": "内部的", "internal": "内部的"
"force_tfa": "两步验证"
}, },
"oauth2": { "oauth2": {
"access_denied": "请作为邮箱所有者登录以使用 OAuth2 授权。", "access_denied": "请作为邮箱所有者登录以使用 OAuth2 授权。",
@@ -1219,12 +1210,7 @@
"waiting_usb_auth": "<i>等待 USB 设备中...</i><br><br>现在请触碰你的 WebAuthn USB 设备上的按钮。", "waiting_usb_auth": "<i>等待 USB 设备中...</i><br><br>现在请触碰你的 WebAuthn USB 设备上的按钮。",
"waiting_usb_register": "<i>等待 USB 设备中...</i><br><br>请在上方输入你的密码并请触碰你的 WebAuthn USB 设备上的按钮以确认注册该 WebAuthn 设备。", "waiting_usb_register": "<i>等待 USB 设备中...</i><br><br>请在上方输入你的密码并请触碰你的 WebAuthn USB 设备上的按钮以确认注册该 WebAuthn 设备。",
"yubi_otp": "Yubico OTP 认证", "yubi_otp": "Yubico OTP 认证",
"authenticators": "验证器(Authenticators)", "authenticators": "验证器(Authenticators)"
"force_tfa": "在登录时强制启用 2FA",
"force_tfa_info": "在访问面板前用户会被要求设置两步验证。",
"setup_title": "需要设置两步验证",
"setup_required": "你的账户需要设置两步验证。请添加一个两步验证方法以继续。",
"cancel_setup": "取消并注销"
}, },
"user": { "user": {
"action": "操作", "action": "操作",
@@ -1398,8 +1384,7 @@
"overview": "概览", "overview": "概览",
"expire_never": "永不过期", "expire_never": "永不过期",
"forever": "永久", "forever": "永久",
"spam_aliases_info": "垃圾邮件别名是一种临时电子邮件地址,可用于保护真实电子邮件地址。<br>还可以选择设置过期时间,以便在设定的时间后自动停用别名,从而有效地销毁被滥用或泄露的地址。", "spam_aliases_info": "垃圾邮件别名是一种临时电子邮件地址,可用于保护真实电子邮件地址。<br>还可以选择设置过期时间,以便在设定的时间后自动停用别名,从而有效地销毁被滥用或泄露的地址。"
"pw_update_required": "你的账户需要修改一次密码。请设置一个新密码以继续。"
}, },
"warning": { "warning": {
"cannot_delete_self": "不能删除已登录的用户", "cannot_delete_self": "不能删除已登录的用户",
-1
View File
@@ -65,7 +65,6 @@ if (isset($_GET['app_password'])) {
$attr['protocols'][] = 'dav_access'; $attr['protocols'][] = 'dav_access';
} }
app_passwd("add", $attr); app_passwd("add", $attr);
$password = htmlspecialchars($password, ENT_NOQUOTES);
} else { } else {
$app_password = false; $app_password = false;
} }
-6
View File
@@ -4,7 +4,6 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
if (!isset($_SESSION['mailcow_cc_role'])) { if (!isset($_SESSION['mailcow_cc_role'])) {
$_SESSION['oauth2_request'] = $_SERVER['REQUEST_URI']; $_SESSION['oauth2_request'] = $_SERVER['REQUEST_URI'];
header('Location: /?oauth'); header('Location: /?oauth');
exit;
} }
$request = OAuth2\Request::createFromGlobals(); $request = OAuth2\Request::createFromGlobals();
@@ -25,11 +24,6 @@ if (!isset($_POST['authorized'])) {
exit; exit;
} }
if (empty($_SESSION['mailcow_cc_username'])) {
header('Location: /?oauth');
exit;
}
// print the authorization code if the user has authorized your client // print the authorization code if the user has authorized your client
$is_authorized = ($_POST['authorized'] == '1'); $is_authorized = ($_POST['authorized'] == '1');
$oauth2_server->handleAuthorizeRequest($request, $response, $is_authorized, $_SESSION['mailcow_cc_username']); $oauth2_server->handleAuthorizeRequest($request, $response, $is_authorized, $_SESSION['mailcow_cc_username']);
+2 -11
View File
@@ -8,12 +8,8 @@ $ALLOW_ADMIN_EMAIL_LOGIN = (preg_match(
$session_var_user_allowed = 'sogo-sso-user-allowed'; $session_var_user_allowed = 'sogo-sso-user-allowed';
$session_var_pass = 'sogo-sso-pass'; $session_var_pass = 'sogo-sso-pass';
// only the internal nginx auth_request loopback (127.0.0.1:65510) sets this;
// external clients can never supply it (bare fastcgi param, not an HTTP header)
$is_internal_auth = (($_SERVER['SOGO_AUTH_INTERNAL'] ?? '') === '1');
// validate credentials for basic auth requests // validate credentials for basic auth requests
if ($is_internal_auth && isset($_SERVER['PHP_AUTH_USER'])) { if (isset($_SERVER['PHP_AUTH_USER'])) {
// load prerequisites only when required // load prerequisites only when required
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
@@ -54,11 +50,6 @@ elseif (isset($_GET['login'])) {
(($_SESSION['acl']['login_as'] == "1" && $ALLOW_ADMIN_EMAIL_LOGIN !== 0) || ($is_dual === false && $login == $_SESSION['mailcow_cc_username']))) { (($_SESSION['acl']['login_as'] == "1" && $ALLOW_ADMIN_EMAIL_LOGIN !== 0) || ($is_dual === false && $login == $_SESSION['mailcow_cc_username']))) {
if (filter_var($login, FILTER_VALIDATE_EMAIL)) { if (filter_var($login, FILTER_VALIDATE_EMAIL)) {
if (user_get_alias_details($login) !== false) { if (user_get_alias_details($login) !== false) {
// enforce tenant boundary
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $login)) {
header("Location: /");
exit;
}
// Block SOGo access if pending actions (2FA setup, password update) // Block SOGo access if pending actions (2FA setup, password update)
if (!empty($_SESSION['pending_tfa_setup']) || !empty($_SESSION['pending_pw_update'])) { if (!empty($_SESSION['pending_tfa_setup']) || !empty($_SESSION['pending_pw_update'])) {
header("Location: /"); header("Location: /");
@@ -89,7 +80,7 @@ elseif (isset($_GET['login'])) {
exit; exit;
} }
// only check for admin-login on sogo GUI requests // only check for admin-login on sogo GUI requests
elseif ($is_internal_auth && isset($_SERVER['HTTP_X_ORIGINAL_URI']) && strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9), "/SOGo/so/") === 0) { elseif (isset($_SERVER['HTTP_X_ORIGINAL_URI']) && strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9), "/SOGo/so/") === 0) {
// this is an nginx auth_request call, we check for existing sogo-sso session variables // this is an nginx auth_request call, we check for existing sogo-sso session variables
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php';
if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/inc/vars.local.inc.php')) { if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/inc/vars.local.inc.php')) {
@@ -7,33 +7,7 @@
<span class="d-none d-md-block">{{ lang.admin.customize }}</span> <span class="d-none d-md-block">{{ lang.admin.customize }}</span>
</div> </div>
<div id="collapse-tab-config-customize" class="card-body collapse" data-bs-parent="#admin-content"> <div id="collapse-tab-config-customize" class="card-body collapse" data-bs-parent="#admin-content">
<legend><i class="bi bi-palette"></i> {{ lang.admin.ui_theme }}</legend><hr /> <legend><i class="bi bi-file-image"></i> {{ lang.admin.change_logo }}</legend><hr />
<div class="text-muted mb-3">{{ lang.admin.ui_theme_info|raw }}</div>
<form class="form" data-id="ui_theme" role="form" method="post">
<div class="row">
<div class="col-12 col-md-6 col-lg-4">
<p><select data-width="100%" id="ui_theme" name="ui_theme" class="selectpicker show-tick">
<option value="" {% if not ui_theme %}selected{% endif %}>{{ lang.admin.ui_theme_default }}</option>
{% for key, theme in ui_themes %}
<option value="{{ key }}" {% if ui_theme == key %}selected{% endif %}>{{ theme.name }}</option>
{% endfor %}
</select></p>
</div>
</div>
{% if ui_themes %}
<ul class="list-unstyled text-muted small mb-4">
{% for key, theme in ui_themes %}
<li><b>{{ theme.name }}</b> <code>{{ key }}</code>{% if theme.dark %} <i class="bi bi-moon-fill" title="{{ lang.admin.ui_theme_dark }}"></i>{% endif %}{% if theme.description %} - {{ theme.description }}{% endif %}</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted small mb-4">{{ lang.admin.ui_theme_none }}</p>
{% endif %}
<p><div class="btn-group">
<button class="btn btn-sm btn-xs-half d-block d-sm-inline btn-success" data-action="edit_selected" data-item="admin" data-id="ui_theme" data-api-url='edit/ui_theme' data-api-attr='{}' href="#"><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
</div></p>
</form>
<legend style="padding-top:20px"><i class="bi bi-file-image"></i> {{ lang.admin.change_logo }}</legend><hr />
<p class="text-muted">{{ lang.admin.logo_info }}</p> <p class="text-muted">{{ lang.admin.logo_info }}</p>
<form class="form-inline" role="form" method="post" enctype="multipart/form-data"> <form class="form-inline" role="form" method="post" enctype="multipart/form-data">
<div class="mb-4"> <div class="mb-4">
+2 -3
View File
@@ -6,9 +6,9 @@
<div class="row mb-4" style="margin-top: 60px"> <div class="row mb-4" style="margin-top: 60px">
<div class="col-12 col-md-7 col-lg-6 col-xl-5 ms-auto me-auto"> <div class="col-12 col-md-7 col-lg-6 col-xl-5 ms-auto me-auto">
<div class="card login-card login-admin"> <div class="card">
<div class="card-header d-flex align-items-center text-break"> <div class="card-header d-flex align-items-center text-break">
<i class="bi bi-shield-lock-fill me-2"></i> {{ lang.login.login_admin }} <i class="bi bi-person-fill me-2"></i> {{ lang.login.login_admin }}
<div class="ms-auto form-check form-switch my-auto d-flex align-items-center"> <div class="ms-auto form-check form-switch my-auto d-flex align-items-center">
<label class="form-check-label"><i class="bi bi-moon-fill"></i></label> <label class="form-check-label"><i class="bi bi-moon-fill"></i></label>
<input class="form-check-input ms-2" type="checkbox" id="dark-mode-toggle"> <input class="form-check-input ms-2" type="checkbox" id="dark-mode-toggle">
@@ -49,7 +49,6 @@
<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">
+1 -6
View File
@@ -10,7 +10,6 @@
<link rel="stylesheet" href="{{ css_path }}"> <link rel="stylesheet" href="{{ css_path }}">
<script> <script>
var mailcow_dark_css = '{{ dark_css_path }}';
// check if darkmode is preferred by OS or set by localStorage // check if darkmode is preferred by OS or set by localStorage
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches && localStorage.getItem("mailcow_theme") !== "light" || if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches && localStorage.getItem("mailcow_theme") !== "light" ||
localStorage.getItem("mailcow_theme") === "dark") { localStorage.getItem("mailcow_theme") === "dark") {
@@ -19,7 +18,7 @@
link.id = 'dark-mode-theme'; link.id = 'dark-mode-theme';
link.rel = 'stylesheet'; link.rel = 'stylesheet';
link.type = 'text/css'; link.type = 'text/css';
link.href = mailcow_dark_css; link.href = '/css/themes/mailcow-darkmode.css';
head.appendChild(link); head.appendChild(link);
} }
</script> </script>
@@ -474,10 +473,6 @@ 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);
} }
}, },
+2 -3
View File
@@ -6,9 +6,9 @@
<div class="row mb-4" style="margin-top: 60px"> <div class="row mb-4" style="margin-top: 60px">
<div class="col-12 col-md-7 col-lg-6 col-xl-5 ms-auto me-auto"> <div class="col-12 col-md-7 col-lg-6 col-xl-5 ms-auto me-auto">
<div class="card login-card login-domainadmin"> <div class="card">
<div class="card-header d-flex align-items-center text-break"> <div class="card-header d-flex align-items-center text-break">
<i class="bi bi-globe2 me-2"></i> {{ lang.login.login_dadmin }} <i class="bi bi-person-fill me-2"></i> {{ lang.login.login_dadmin }}
<div class="ms-auto form-check form-switch my-auto d-flex align-items-center"> <div class="ms-auto form-check form-switch my-auto d-flex align-items-center">
<label class="form-check-label"><i class="bi bi-moon-fill"></i></label> <label class="form-check-label"><i class="bi bi-moon-fill"></i></label>
<input class="form-check-input ms-2" type="checkbox" id="dark-mode-toggle"> <input class="form-check-input ms-2" type="checkbox" id="dark-mode-toggle">
@@ -49,7 +49,6 @@
<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">
+1 -1
View File
@@ -340,7 +340,7 @@
<div class="row mb-4"> <div class="row mb-4">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><i style="font-size: 16px; cursor: pointer;" class="bi bi-patch-question-fill m-2 ms-0" data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="bottom" title="{{ lang.edit.mta_sts_active_info|raw }}"></i><input type="checkbox" class="form-check-input" value="1" name="active"{% if mta_sts.active == '1' %} checked{% endif %}> {{ lang.edit.active }}</label> <label><input type="checkbox" class="form-check-input" value="1" name="active"{% if mta_sts.active == '1' %} checked{% endif %}> {{ lang.edit.active }}</label>
</div> </div>
</div> </div>
</div> </div>
+1 -1
View File
@@ -23,7 +23,7 @@
<div class="row mb-4"> <div class="row mb-4">
<label class="control-label col-sm-2" for="script_data">Script:</label> <label class="control-label col-sm-2" for="script_data">Script:</label>
<div class="col-sm-10"> <div class="col-sm-10">
<textarea spellcheck="false" autocorrect="off" autocapitalize="none" class="form-control textarea-code" rows="20" id="script_data" name="script_data" required>{{ result.script_data }}</textarea> <textarea spellcheck="false" autocorrect="off" autocapitalize="none" class="form-control textarea-code" rows="20" id="script_data" name="script_data" required>{{ result.script_data|raw }}</textarea>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
@@ -8,7 +8,6 @@
<input type="hidden" value="default" name="sender_acl"> <input type="hidden" value="default" name="sender_acl">
<input type="hidden" value="0" name="force_pw_update"> <input type="hidden" value="0" name="force_pw_update">
<input type="hidden" value="0" name="force_tfa">
<input type="hidden" value="0" name="sogo_access"> <input type="hidden" value="0" name="sogo_access">
<input type="hidden" value="0" name="protocol_access"> <input type="hidden" value="0" name="protocol_access">
@@ -166,14 +165,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row">
<div class="offset-sm-2 col-sm-10">
<div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="force_tfa" id="force_tfa"{% if template.attributes.force_tfa == '1' %} checked{% endif %}> {{ lang.tfa.force_tfa }}</label>
<small class="text-muted">{{ lang.tfa.force_tfa_info }}</small>
</div>
</div>
</div>
{% if not skip_sogo %} {% if not skip_sogo %}
<div class="row"> <div class="row">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
+1 -1
View File
@@ -267,7 +267,6 @@
<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">
@@ -280,6 +279,7 @@
{% 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">
+2 -2
View File
@@ -7,12 +7,12 @@
<li class="nav-item dropdown" role="presentation"> <li class="nav-item dropdown" role="presentation">
<a class="nav-link dropdown-toggle active" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.domains }}</a> <a class="nav-link dropdown-toggle active" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.domains }}</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><button class="dropdown-item active" aria-selected="true" aria-controls="tab-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domains">{{ lang.mailbox.domains }}</button></li> <li><button class="dropdown-item" aria-selected="false" aria-controls="tab-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domains">{{ lang.mailbox.domains }}</button></li>
<li><button class="dropdown-item" aria-selected="false" aria-controls="tab-templates-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-templates-domains">{{ lang.mailbox.templates }}</button></li> <li><button class="dropdown-item" aria-selected="false" aria-controls="tab-templates-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-templates-domains">{{ lang.mailbox.templates }}</button></li>
</ul> </ul>
</li> </li>
{% else %} {% else %}
<li class="nav-item" role="presentation"><button class="nav-link active" aria-selected="true" aria-controls="tab-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domains">{{ lang.mailbox.domains }}</button></li> <li class="nav-item" role="presentation"><button class="nav-link" aria-controls="tab-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domains">{{ lang.mailbox.domains }}</button></li>
{% endif %} {% endif %}
{% if mailcow_cc_role == 'admin' %} {% if mailcow_cc_role == 'admin' %}
<li class="nav-item dropdown" role="presentation"> <li class="nav-item dropdown" role="presentation">
+1 -2
View File
@@ -6,7 +6,7 @@
<div class="row mb-4" style="margin-top: 60px"> <div class="row mb-4" style="margin-top: 60px">
<div class="col-12 col-md-7 col-lg-6 col-xl-5 ms-auto me-auto"> <div class="col-12 col-md-7 col-lg-6 col-xl-5 ms-auto me-auto">
<div class="card login-card login-user"> <div class="card">
<div class="card-header d-flex align-items-center text-break"> <div class="card-header d-flex align-items-center text-break">
<i class="bi bi-person-fill me-2"></i> {{ lang.login.login_user }} <i class="bi bi-person-fill me-2"></i> {{ lang.login.login_user }}
<div class="ms-auto form-check form-switch my-auto d-flex align-items-center"> <div class="ms-auto form-check form-switch my-auto d-flex align-items-center">
@@ -56,7 +56,6 @@
<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;">
Loaded 100 of 102 files, more files were not shown because too many files have changed in this diff. Show more