Compare commits

..
Author SHA1 Message Date
milkmakerandMatjaž Tekavec 7c9f374998 [Web] Updated lang.si-si.json
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2026-11-12 18:36:50 +00:00
FreddleSpl0it 1a53d8f4c2 Merge pull request #7642 from mailcow/fix/update-self-restart
[Update] Restart update.sh automatically after it updated itself
2026-11-10 19:52:17 +01:00
FreddleSpl0it 18bc6dc98d [Update] Restart update.sh automatically after it updated itself 2026-11-10 19:50:48 +01:00
FreddleSpl0it 9825c8d290 Merge pull request #7641 from mailcow/fix/7436
[Update.sh] Keep ENABLE_IPV6 as set, do not disable IPv6 on hosts with a public IPv6 address
2026-11-10 17:09:56 +01:00
FreddleSpl0it dc880d861c [Update] Keep ENABLE_IPV6 as set, do not disable IPv6 on hosts with a public IPv6 address 2026-11-10 17:04:50 +01:00
FreddleSpl0it a03fd8aaad Merge pull request #7500 from philippderdiedas/fix/update-sh-masquerade-bridge
[Update] Fix update.sh deleting mailcow's own MASQUERADE rule
2026-11-10 15:39:48 +01:00
FreddleSpl0it e3a90718c2 Merge pull request #7516 from wbilalz400/fix/webauthn-forced-enrollment-empty-credential
[Web] Fix forced WebAuthn TFA enrollment storing an empty credential
2026-11-10 15:21:30 +01:00
FreddleSpl0it 54a85ba439 Merge pull request #7540 from pReya/fix/table-ui-polish
[Web] Table UI polish (icon centering, tag heights, sort headers)
2026-11-10 14:07:39 +01:00
FreddleSpl0it 4ced82d554 Merge pull request #7548 from junkerderprovinz/fix/idp-ignore-ssl-error-key
[Web] Fix ignore_ssl_error key in identity provider settings
2026-11-10 13:55:15 +01:00
FreddleSpl0it f83e02b740 Merge pull request #7640 from mailcow/fix/7562
[Rspamd] Remove the URLhaus and MalwareBazaar maps retired by abuse.ch
2026-11-10 13:51:45 +01:00
FreddleSpl0it 04c9413ed9 [Rspamd] Remove the URLhaus and MalwareBazaar maps retired by abuse.ch 2026-11-10 13:50:48 +01:00
FreddleSpl0it 5e98d8e279 Merge pull request #7581 from peterschwps/fix/trim-logs-redis-password
[Dovecot] Mask Redis password in trim_logs output
2026-11-10 13:43:36 +01:00
FreddleSpl0it 4caecf77e0 Merge pull request #7610 from woodfairy/fix/brotli-cve-2026-111497
[Nginx][Clamd][Postfix] Rebuild images for brotli CVE-2026-111497 (CISA KEV)
2026-11-10 13:40:04 +01:00
FreddleSpl0it 37d304d075 Merge pull request #7544 from jorgeavlobo/fix/keycloak-admin-token-introspect
[Web] Keycloak: skip introspection of an expired admin token and reuse the new one
2026-11-10 01:45:16 +01:00
FreddleSpl0it 25086a9b3f Merge pull request #7488 from apio-sys/fix/rspamd-dqs-dbl-full-rule
[Rspamd] write full DBL rule when SPAMHAUS_DQS_KEY is set
2026-11-10 01:42:29 +01:00
FreddleSpl0it 7a3f1cbd41 Merge pull request #7530 from ThxGiving/fix/eas-auth-challenge
[Nginx] Send Basic auth challenge for unauthenticated ActiveSync requests
2026-11-10 01:39:28 +01:00
FreddleSpl0it 616717bc0d Merge pull request #7639 from mailcow/fix/7580
[Rspamd] Remove MX check overrides that rspamd ignores
2026-11-10 01:28:42 +01:00
FreddleSpl0it 6fd271ee64 [Rspamd] Remove MX check overrides that rspamd ignores 2026-11-10 01:27:52 +01:00
FreddleSpl0it 7459cc046a Merge pull request #7638 from mailcow/fix/7579
[Web] Template lists: sort size columns numerically
2026-11-10 01:08:36 +01:00
FreddleSpl0it 58dad73eee [Web] Template lists: sort size columns by bytes 2026-11-10 01:07:32 +01:00
FreddleSpl0it b21acd4787 Merge pull request #7637 from mailcow/fix/7557
[Web] Domain admins saw sender aliases of other domains
2026-11-10 01:02:39 +01:00
FreddleSpl0it 4ab954dcd1 [Web] Domain admins: show sender ACL and alias entries of their own domains only 2026-11-10 01:01:22 +01:00
FreddleSpl0it eefc17274e Merge pull request #7636 from mailcow/fix/7545
[Rspamd] WHITELISTED_FWD_HOST was skipped at the reject threshold
2026-11-10 00:57:45 +01:00
FreddleSpl0it 4878840bdd [Rspamd] Forwarding hosts: check them as prefilter and scan the mail completely 2026-11-10 00:56:53 +01:00
FreddleSpl0it 869bcb3c42 Merge pull request #7635 from mailcow/fix/7518
[Rspamd] Domain footer broke DKIM for mail without Content-Transfer-Encoding
2026-11-09 23:36:42 +01:00
FreddleSpl0it 0bcde13500 [Rspamd] Domain footer: add the Content-Transfer-Encoding header to the delivered mail 2026-11-09 23:34:57 +01:00
FreddleSpl0it 867b9dddab Merge pull request #7634 from mailcow/fix/7503
[Web] Keycloak: accept Base64-encoded mailcow_password
2026-11-09 23:30:20 +01:00
FreddleSpl0it a4326e6642 [Web] Keycloak Mailpassword Flow: accept Base64-encoded password hashes 2026-11-09 23:28:53 +01:00
FreddleSpl0it c5e89a9e03 Merge pull request #7633 from mailcow/fix/7501
[Web] Forwarding hosts: store IPv6 addresses in canonical form
2026-11-09 23:25:40 +01:00
FreddleSpl0it 45d31b3279 [Web] Forwarding hosts: store IPv6 addresses in canonical form 2026-11-09 23:24:39 +01:00
FreddleSpl0it 24780ea5fb Merge pull request #7632 from mailcow/fix/7489
[Rspamd] Quarantine stored nothing without Q_EXCLUDE_DOMAINS
2026-11-09 23:22:11 +01:00
FreddleSpl0it 38b031e10c [Rspamd] Quarantine: store mail when Q_EXCLUDE_DOMAINS is not set 2026-11-09 23:21:14 +01:00
FreddleSpl0it e2fb833ceb Merge pull request #7631 from mailcow/fix/7482
[Web] Autodiscover: no ActiveSync when autodiscoverType is imap
2026-11-09 23:18:50 +01:00
FreddleSpl0it e27cb83326 [Web] Autodiscover: no ActiveSync when autodiscoverType is imap 2026-11-09 23:17:55 +01:00
FreddleSpl0it f810cecd3a Merge pull request #7630 from mailcow/fix/7476
[Web] force_tfa did not prevent removing 2FA
2026-11-09 23:14:38 +01:00
FreddleSpl0it cfad223824 [Web] force_tfa: read the flag with JSON_VALUE 2026-11-09 23:13:26 +01:00
FreddleSpl0it 6d20463acb Merge pull request #7629 from mailcow/fix/7454
[Nginx] Edit and quarantine links: allow a trailing slash
2026-11-09 23:09:05 +01:00
FreddleSpl0it 1a42c19050 [Nginx] Accept a trailing slash in /edit and /qhandler links 2026-11-09 23:08:14 +01:00
FreddleSpl0it b9f8eb4c31 Merge pull request #7628 from mailcow/fix/7449
[Postfix-TLSPol] Update to 1.14.5
2026-11-09 23:05:35 +01:00
FreddleSpl0it ce4150f921 [Postfix-TLSPol] Update to 1.14.5 2026-11-09 23:04:33 +01:00
FreddleSpl0it 7cc8cac705 Merge pull request #7627 from mailcow/fix/7318
[Rspamd] Mails between 50 and 100 MiB were deferred forever
2026-11-09 23:01:43 +01:00
FreddleSpl0it 3fd1d017cf [Rspamd] Raise max_message above the Postfix message size limit 2026-11-09 23:00:38 +01:00
FreddleSpl0it 8597954f91 Merge pull request #7626 from mailcow/fix/7179
[Rspamd] Sub-addressing: read the tag from the original SMTP recipient
2026-11-09 22:55:05 +01:00
FreddleSpl0it 2b4ffe8808 [Rspamd] Sub-addressing: read the tag from the original SMTP recipient 2026-11-09 22:53:07 +01:00
FreddleSpl0it 1911847c3b Merge pull request #7625 from mailcow/fix/6931
[Web] Update "Last modified" when tags of a domain or mailbox change
2026-11-09 22:48:21 +01:00
FreddleSpl0it 497872080b [Web] Update "Last modified" when tags of a domain or mailbox change 2026-11-09 22:46:15 +01:00
FreddleSpl0it a0dc3447f8 Merge pull request #7624 from mailcow/fix/6853
[Web][Dovecot] Mailbox list and API: show the last SOGo login
2026-11-09 22:42:27 +01:00
FreddleSpl0it e0640949a2 [Web][Dovecot] Show the last SOGo login in the mailbox list and API 2026-11-09 22:39:42 +01:00
milkmaker bf744413bd Translations update from Weblate (#7623)
* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>
2026-11-09 21:41:36 +01:00
FreddleSpl0it 262de81da1 Merge pull request #7621 from mailcow/fix/7606
[Dockerapi] Group IMAP grants: set ACLs only on existing folders
2026-11-09 19:43:38 +01:00
FreddleSpl0it 5f13bb2046 [Dockerapi] Group IMAP grants: set ACLs only on existing folders 2026-11-09 19:41:17 +01:00
woodfairy 0a0686ef19 [Nginx][Clamd][Postfix] Rebuild images for brotli CVE-2026-111497
The current nginx, clamd and postfix images ship a brotli decoder in the
affected range 1.0.3 - 1.2.0 (CVE-2026-111497, CVSS 9.8, listed in CISA KEV):

- nginx:1.30.6-2   brotli-libs 1.2.0-r1 (fixed in Alpine 3.24: 1.2.1-r0)
- clamd:1.4.6-1    brotli-libs 1.2.0-r1 (fixed in Alpine 3.24: 1.2.1-r0)
- postfix:3.10.12-1 libbrotli1 1.1.0-2+b7 (fixed in trixie-security:
  1.1.0-2+deb13u1, DSA-6582-1)

All other mailcow images already contain a fixed version.

clamd and postfix only need a rebuild, which pulls the fixed packages.
The postfix rebuild also picks up Debian's postfix 3.10.13, hence the
new tag postfix:3.10.13-1. The
nginx image never upgraded the packages of its base image, so it only got
fixes when nginx:<version>-alpine was republished. It now runs
apk upgrade like clamd and acme do.
2026-11-08 00:49:55 +01:00
Peter Schwips 24b91f6089 [Dovecot] Mask Redis password in trim_logs output 2026-10-30 23:54:21 +01:00
Junker der Provinzanddiko 5705a48724 [Web] Fix ignore_ssl_error key in identity provider settings
The edit action stores the setting as ignore_ssl_error, but the get
action cast and defaulted ignore_ssl_errors. The stored value was
returned as a string and LDAP setups got an extra ignore_ssl_errors
field that was always false.

Co-authored-by: diko <108140792+admins-little-helper@users.noreply.github.com>
2026-10-24 20:05:10 +02:00
Jorge Lobo 2f76230daf [Web] Keycloak: skip introspection of expired admin token 2026-10-23 00:52:03 +01:00
Moritz Stückler 498434ee3d [Web] Even out spacing of the expanded table row
DataTables removes the top padding of the first entry in the expanded
row, but mailcow's 0.5em padding left 7px under the last entry, so the
list had 7px of space above and 15px below and the last entry (often
Tags) looked taller than the others. Drop the last entry's bottom
padding as well.

Signed-off-by: Moritz Stückler <moritz@bitbetter.de>
2026-10-22 13:10:49 +02:00
Moritz Stückler f662af4c50 [Web] Even out badge and tag sizes in tables
- Use the normal cursor on non-orderable headers. The expand column
  header kept a pointer cursor from sorting_asc although clicking it
  does nothing.
- Let icons in badges inherit the badge font size, like .btn .bi, and
  keep their glyph from adding line height. The global 12pt icon size
  made e.g. the "SSO @" badge in Last mail login taller than the IMAP
  and SMTP badges next to it. Tag badges keep their 12px icon.
- Drop the vertical margin of tag badges inside tables. It is meant for
  the tag editor in edit forms and made the Tags entry of the expanded
  row taller than the others.
- Center tag badge captions: the 12px tag icon stretched the 10.5px
  badge line and pushed the caption below center.

Signed-off-by: Moritz Stückler <moritz@bitbetter.de>
2026-10-22 13:06:00 +02:00
Moritz Stückler 8d4895a54d [Web] Align the table expand control and row checkbox
- Center the +/- glyph in the expand control: its line-height (1.25em)
  was larger than the 1em box, pushing the glyph down. Derive the box's
  margin-top from its size so it is centered in the row (desktop and
  the larger mobile variant).
- Hide sort arrows on non-orderable headers. The default initial order
  targets column 0, the expand column, which got a sorting_asc arrow
  and arrow padding although it cannot be sorted.
- Center row checkboxes level with the expand control by using
  Bootstrap's 0.25em margin-top instead of a fixed 7px.

Signed-off-by: Moritz Stückler <moritz@bitbetter.de>
2026-10-22 12:43:18 +02:00
Moritz Stückler 81d6b1f547 [Web] Clearer captions for domain table columns
Caption the "RL" column "Rate limit" using the existing translated
edit.ratelimit string, and shorten "Max. size of a mailbox" to
"Max. mailbox size" (en-gb only; the key is only used for this column).

Signed-off-by: Moritz Stückler <moritz@bitbetter.de>
2026-10-22 12:43:18 +02:00
Sebastian Müller 8db3cae826 [Nginx] Send Basic auth challenge for unauthenticated ActiveSync requests
Outlook for iOS/Android connects through Microsoft's sync service, which
sends each EAS command without credentials first and only retries with
Basic auth after a 401 + WWW-Authenticate challenge. SOGo answers these
unauthenticated POSTs (Provision, FolderSync, Sync, ...) with 403, so the
Outlook apps never get past Provision and show an empty mailbox (#5846).

Answer requests without an Authorization header with a Basic challenge
in nginx before they reach SOGo. Clients that send credentials up front
are not affected.
2026-10-20 20:48:30 +02:00
Bilal Zahid d93f32072e fix(web): post WebAuthn TFA enrollment through the JSON API
The forced TFA enrollment form submitted the raw credential JSON in the
'token' field, which set_tfa()'s webauthn case never reads. It expects the
parsed \['registration'] object that only the JSON API builds, so the
form POST inserted a row with an empty keyHandle and a NULL publicKey while
still reporting success. Every WebAuthn 2FA login then failed, because
webauthn-tfa-get-args sent an empty credential id in allowCredentials.

- base.twig: POST the attestation to /api/v1/add/webauthn-tfa-registration
  like the regular WebAuthn modal does
- functions.inc.php: refuse incomplete WebAuthn registrations
- lang.en-gb.json: message for the new guard
2026-10-13 19:25:17 +05:00
Philipp Harms 7b658fa65c [Update] Fix update.sh deleting mailcow's own MASQUERADE rule
The "Checking for conflicting bridges" step extracts the bridge name with
`cut -d':' -f2`, which yields " br-mailcow" (leading space). Since the
variable got quoted in `grep -v "${MAILCOW_BRIDGE}"`, the leading space is
significant and never matches "!br-mailcow" in the `iptables -L` output, so
the rule `-s <IPV4_NETWORK>.0/24 ! -o br-mailcow -j MASQUERADE` itself gets
deleted. The mailcow network loses outbound NAT, unbound turns unhealthy and
dependent containers stay in "Created" until Docker is restarted.

Strip whitespace/quotes from the bridge name, match the interface as a whole
token, and skip the cleanup if the bridge name could not be determined.
2026-10-02 18:53:12 +02:00
Joris Le BlanschandClaude Opus 5.5 9f934af3f2 [Rspamd] write full DBL rule when SPAMHAUS_DQS_KEY is set
The generated "DBL" block only set rbl and disable_monitoring, assuming
it would merge into the stock rule. It is included with override=true,
so it replaced the stock rule instead: DBL lost all its checks (urls,
emails, dkim, helo, rdns, replyto), no_ip, selectors and returncodes,
and rspamd fell back to querying the client IP against DBL.

Write the full stock rule pointed at the DQS zone, like the ZRD block.

Fixes #7485

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 21:20:23 +02:00
34 changed files with 681 additions and 155 deletions

No files matched your search

+46 -21
View File
@@ -3,6 +3,11 @@
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY! # THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!! # DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
# Global IPv6 address that is no unique local address (fc00::/7, e.g. Docker bridges)
_has_public_ipv6() {
ip -6 -o addr show scope global 2>/dev/null | awk '{print $4}' | grep -qviE '^f[cd]'
}
# 1) Check if the host supports IPv6 # 1) Check if the host supports IPv6
get_ipv6_support() { get_ipv6_support() {
# ---- helper: probe external IPv6 connectivity without DNS ---- # ---- helper: probe external IPv6 connectivity without DNS ----
@@ -37,6 +42,9 @@ get_ipv6_support() {
if _probe_ipv6_connectivity; then if _probe_ipv6_connectivity; then
DETECTED_IPV6=true DETECTED_IPV6=true
echo -e "IPv6 detected on host – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}" echo -e "IPv6 detected on host – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
elif _has_public_ipv6; then
DETECTED_IPV6=true
echo -e "${YELLOW}External IPv6 connectivity test failed, but the host has a public IPv6 address – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
else else
DETECTED_IPV6=false DETECTED_IPV6=false
echo -e "${YELLOW}Default IPv6 route present but external IPv6 connectivity failed – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}" echo -e "${YELLOW}Default IPv6 route present but external IPv6 connectivity failed – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
@@ -192,9 +200,31 @@ EOF
fi fi
} }
_set_enable_ipv6() {
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
sed -i "s/^ENABLE_IPV6=.*/ENABLE_IPV6=$1/" "$MAILCOW_CONF"
else
echo "ENABLE_IPV6=$1" >> "$MAILCOW_CONF"
fi
else
export IPV6_BOOL=$1
fi
}
_warn_ipv6_disabled() {
_has_public_ipv6 || return 0
ip -6 route show default 2>/dev/null | grep -qE '^default' || return 0
grep -Eq '"userland-proxy"[[:space:]]*:[[:space:]]*false' /etc/docker/daemon.json 2>/dev/null && return 0
echo -e "${LIGHT_RED}WARNING: ENABLE_IPV6=false, but this host has a public IPv6 address.${NC}"
echo -e "${YELLOW}Docker forwards IPv6 connections to mailcow with an internal source address, which Postfix trusts. This can make your server an open relay over IPv6.${NC}"
echo -e "${YELLOW}Set ENABLE_IPV6=true in mailcow.conf, or set \"userland-proxy\": false in /etc/docker/daemon.json and restart Docker.${NC}"
echo -e "${YELLOW}See https://docs.mailcow.email/post_installation/firststeps-disable_ipv6/${NC}"
sleep 5
}
# 3) Main wrapper for generate_config.sh and update.sh # 3) Main wrapper for generate_config.sh and update.sh
configure_ipv6() { configure_ipv6() {
# detect manual override if mailcow.conf is present
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2) MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2)
elif [[ -z "$MAILCOW_CONF" ]] && [[ -n "${ENABLE_IPV6:-}" ]]; then elif [[ -z "$MAILCOW_CONF" ]] && [[ -n "${ENABLE_IPV6:-}" ]]; then
@@ -203,19 +233,23 @@ configure_ipv6() {
MANUAL_SETTING="" MANUAL_SETTING=""
fi fi
if [[ "${MANUAL_SETTING,,}" == "false" ]]; then
_set_enable_ipv6 false
echo "ENABLE_IPV6=false is set, skipping Docker IPv6 configuration."
_warn_ipv6_disabled
echo "IPv6 configuration complete: ENABLE_IPV6=false"
return
fi
get_ipv6_support get_ipv6_support
# if user manually set it, check for mismatch if [[ "$DETECTED_IPV6" != "true" && "${MANUAL_SETTING,,}" == "true" ]] && _has_public_ipv6; then
echo -e "${YELLOW}Keeping ENABLE_IPV6=true, the host has a public IPv6 address.${NC}"
DETECTED_IPV6=true
fi
if [[ "$DETECTED_IPV6" != "true" ]]; then if [[ "$DETECTED_IPV6" != "true" ]]; then
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then _set_enable_ipv6 false
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=false/' "$MAILCOW_CONF"
else
echo "ENABLE_IPV6=false" >> "$MAILCOW_CONF"
fi
else
export IPV6_BOOL=false
fi
echo "Skipping Docker IPv6 configuration because host does not support IPv6." echo "Skipping Docker IPv6 configuration because host does not support IPv6."
echo "Make sure to check if your docker daemon.json does not include \"enable_ipv6\": true if you do not want IPv6." echo "Make sure to check if your docker daemon.json does not include \"enable_ipv6\": true if you do not want IPv6."
echo "IPv6 configuration complete: ENABLE_IPV6=false" echo "IPv6 configuration complete: ENABLE_IPV6=false"
@@ -224,16 +258,7 @@ configure_ipv6() {
fi fi
docker_daemon_edit docker_daemon_edit
_set_enable_ipv6 true
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=true/' "$MAILCOW_CONF"
else
echo "ENABLE_IPV6=true" >> "$MAILCOW_CONF"
fi
else
export IPV6_BOOL=true
fi
echo "IPv6 configuration complete: ENABLE_IPV6=true" echo "IPv6 configuration complete: ENABLE_IPV6=true"
} }
@@ -586,8 +586,8 @@ class DockerApi:
if not user or id in ('user=', 'group='): if not user or id in ('user=', 'group='):
return Response(content=json.dumps({ 'type': 'danger', 'msg': 'user and id required' }, indent=4), media_type="application/json") return Response(content=json.dumps({ 'type': 'danger', 'msg': 'user and id required' }, indent=4), media_type="application/json")
action = 'doveadm acl set -u "$ACL_USER" -- "$folder" "$ACL_ID" $ACL_RIGHTS' if rights else 'doveadm acl delete -u "$ACL_USER" -- "$folder" "$ACL_ID"' action = 'doveadm acl set -u "$ACL_USER" -- "$folder" "$ACL_ID" $ACL_RIGHTS' if rights else 'doveadm acl delete -u "$ACL_USER" -- "$folder" "$ACL_ID"'
script = ('set -o pipefail; doveadm mailbox list -u "$ACL_USER" | while IFS= read -r folder; do ' script = ('set -o pipefail; doveadm -f tab mailbox status -u "$ACL_USER" guid "*" | tail -n +2 | cut -f1 | while IFS= read -r folder; do '
'case "$folder" in Shared/*) continue;; esac; ' + action + ' || exit 1; done && doveadm acl recalc -u "$ACL_USER"') 'case "$folder" in Shared|Shared/*) continue;; esac; ' + action + ' || exit 1; done && doveadm acl recalc -u "$ACL_USER"')
acl_return = container.exec_run(["/bin/bash", "-c", script], acl_return = container.exec_run(["/bin/bash", "-c", script],
environment={ 'ACL_USER': user, 'ACL_ID': id, 'ACL_RIGHTS': ' '.join(rights) }) environment={ 'ACL_USER': user, 'ACL_ID': id, 'ACL_RIGHTS': ' '.join(rights) })
return self.exec_run_handler('generic', acl_return) return self.exec_run_handler('generic', acl_return)
+1 -1
View File
@@ -4,7 +4,7 @@ catch_non_zero() {
${CMD} > /dev/null ${CMD} > /dev/null
EC=$? EC=$?
if [ ${EC} -ne 0 ]; then if [ ${EC} -ne 0 ]; then
echo "Command ${CMD} failed to execute, exit code was ${EC}" echo "Command ${CMD//${REDISPASS}/***} failed to execute, exit code was ${EC}"
fi fi
} }
source /source_env.sh source /source_env.sh
+2 -1
View File
@@ -3,7 +3,8 @@ LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
ENV PIP_BREAK_SYSTEM_PACKAGES=1 ENV PIP_BREAK_SYSTEM_PACKAGES=1
RUN apk add --no-cache nginx \ RUN apk upgrade --no-cache && \
apk add --no-cache nginx \
python3 \ python3 \
py3-pip && \ py3-pip && \
pip install --upgrade pip && \ pip install --upgrade pip && \
+1 -1
View File
@@ -4,7 +4,7 @@ WORKDIR /src
ENV CGO_ENABLED=0 \ ENV CGO_ENABLED=0 \
GO111MODULE=on \ GO111MODULE=on \
NOOPT=1 \ NOOPT=1 \
VERSION=1.14.0 VERSION=1.14.5
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 && \
+26 -1
View File
@@ -277,9 +277,34 @@ if [[ ! -z ${SPAMHAUS_DQS_KEY} ]]; then
} }
} }
"DBL" { "DBL" {
# override the defaults for DBL defined in modules.d/rbl.conf # full copy of the DBL rule from modules.d/rbl.conf, pointed at DQS:
# the override include replaces the stock rule instead of merging into it
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net"; rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
disable_monitoring = true; disable_monitoring = true;
checks = ["emails", "dkim", "helo", "rdns", "replyto", "urls", "content_urls"];
no_ip = true;
ignore_defaults = true;
exclude_users = false;
emails_domainonly = true;
selector = {
from_smtp = "from('smtp'):domain";
from_mime = "from('mime'):domain";
mid = "header(Message-Id).regexp('@([^\.]+\.[^>]+)').last";
}
returncodes = {
DBL_SPAM = "127.0.1.2";
DBL_PHISH = "127.0.1.4";
DBL_MALWARE = "127.0.1.5";
DBL_BOTNET = "127.0.1.6";
DBL_ABUSE = "127.0.1.102";
DBL_ABUSE_REDIR = "127.0.1.103";
DBL_ABUSE_PHISH = "127.0.1.104";
DBL_ABUSE_MALWARE = "127.0.1.105";
DBL_ABUSE_BOTNET = "127.0.1.106";
DBL_PROHIBIT = "127.0.1.255";
DBL_BLOCKED_OPENRESOLVER = "127.255.255.254";
DBL_BLOCKED = "127.255.255.255";
}
} }
"ZRD" { "ZRD" {
ignore_defaults = true; ignore_defaults = true;
+2 -2
View File
@@ -116,8 +116,8 @@ if ($result === false){
exit; exit;
} }
if ($result) { if ($result) {
error_log('MAILCOWAUTH: User auth for user ' . $post['username'] . " with service " . $post['service'] . " from IP " . $client_ip); error_log('MAILCOWAUTH: User auth for user ' . $post['username'] . " with service " . $service . " from IP " . $client_ip);
set_sasl_log($post['username'], $client_ip, $post['service']); set_sasl_log($post['username'], $client_ip, $service);
} }
} }
@@ -94,11 +94,11 @@ location / {
} }
location /qhandler { location /qhandler {
rewrite ^/qhandler/(.*)/(.*) /qhandler.php?action=$1&hash=$2; rewrite ^/qhandler/([^/]+)/(.+?)/?$ /qhandler.php?action=$1&hash=$2;
} }
location /edit { location /edit {
rewrite ^/edit/(.*)/(.*) /edit.php?$1=$2; rewrite ^/edit/([^/]+)/(.+?)/?$ /edit.php?$1=$2;
} }
location ~ ^/api/v1/(.*)$ { location ~ ^/api/v1/(.*)$ {
@@ -187,6 +187,12 @@ location /sogo-auth-verify {
} }
location ^~ /Microsoft-Server-ActiveSync { location ^~ /Microsoft-Server-ActiveSync {
# Outlook for iOS/Android (Microsoft's sync service) sends EAS commands without credentials
# and only authenticates after a 401 Basic challenge. SOGo answers those with 403, so challenge here.
if ($http_authorization = "") {
add_header WWW-Authenticate 'Basic realm="SOGo"' always;
return 401;
}
auth_request /sogo-auth-verify; auth_request /sogo-auth-verify;
auth_request_set $user $upstream_http_x_user; auth_request_set $user $upstream_http_x_user;
auth_request_set $auth $upstream_http_x_auth; auth_request_set $auth $upstream_http_x_auth;
-4
View File
@@ -1,7 +1,3 @@
MX_IMPLICIT {
expression = "MX_GOOD & MX_MISSING";
score = -0.01;
}
VIRUS_FOUND { VIRUS_FOUND {
expression = "CLAM_VIRUS & !MAILCOW_WHITE"; expression = "CLAM_VIRUS & !MAILCOW_WHITE";
score = 2000.0; score = 2000.0;
-18
View File
@@ -23,24 +23,6 @@ symbols {
} }
} }
group "MX" {
"MX_INVALID" {
score = 0.5;
description = "No connectable MX";
one_shot = true;
}
"MX_MISSING" {
score = 2.0;
description = "No MX record";
one_shot = true;
}
"MX_GOOD" {
score = -0.01;
description = "MX was ok";
one_shot = true;
}
}
group "reputation" { group "reputation" {
symbols = { symbols = {
"IP_REPUTATION_HAM" { "IP_REPUTATION_HAM" {
-20
View File
@@ -5,12 +5,6 @@ RCPT_MAILCOW_DOMAIN {
symbols_set = ["RCPT_MAILCOW_DOMAIN"]; symbols_set = ["RCPT_MAILCOW_DOMAIN"];
} }
WHITELISTED_FWD_HOST {
type = "ip";
map = "redis://WHITELISTED_FWD_HOST";
symbols_set = ["WHITELISTED_FWD_HOST"];
}
BULK_HEADER { BULK_HEADER {
type = "content"; type = "content";
map = "${LOCAL_CONFDIR}/custom/bulk_header.map"; map = "${LOCAL_CONFDIR}/custom/bulk_header.map";
@@ -151,20 +145,6 @@ BAD_LANG {
score = 5.0; score = 5.0;
} }
BAZAAR_ABUSE_CH {
type = "selector";
selector = "attachments(hex,md5)";
map = "https://bazaar.abuse.ch/export/txt/md5/recent/";
score = 10.0;
}
URLHAUS_ABUSE_CH {
type = "selector";
selector = "urls";
map = "https://urlhaus.abuse.ch/downloads/text_online/";
score = 10.0;
}
SMTP_LIMITED_ACCESS { SMTP_LIMITED_ACCESS {
type = "user"; type = "user";
map = "redis://SMTP_LIMITED_ACCESS"; map = "redis://SMTP_LIMITED_ACCESS";
-1
View File
@@ -1,6 +1,5 @@
timeout = 8.0; timeout = 8.0;
symbol_bad_mx = "MX_INVALID"; symbol_bad_mx = "MX_INVALID";
symbol_no_mx = "MX_MISSING";
symbol_good_mx = "MX_GOOD"; symbol_good_mx = "MX_GOOD";
expire = 86400; expire = 86400;
key_prefix = "rmx"; key_prefix = "rmx";
+2
View File
@@ -9,3 +9,5 @@ disable_monitoring = true;
# instead of silently accepting the message without further processing. # instead of silently accepting the message without further processing.
soft_reject_on_timeout = true; soft_reject_on_timeout = true;
local_addrs = /etc/rspamd/custom/mailcow_networks.map; local_addrs = /etc/rspamd/custom/mailcow_networks.map;
# Must not be lower than message_size_limit in postfix main.cf
max_message = 134217728;
+63 -6
View File
@@ -362,6 +362,61 @@ rspamd_config:register_symbol({
priority = 19 priority = 19
}) })
rspamd_config:register_symbol({
name = 'WHITELISTED_FWD_HOST',
type = 'prefilter',
score = 0.0,
callback = function(task)
local rspamd_logger = require "rspamd_logger"
local redis_params = rspamd_parse_redis_server('fwd_host')
local ip = task:get_from_ip()
if ip == nil or not ip:is_valid() then
return false
end
local from_ip_string = tostring(ip)
local ip_check_table = {'WHITELISTED_FWD_HOST', from_ip_string}
local maxbits = 128
local minbits = 7
if ip:get_version() == 4 then
maxbits = 32
minbits = 8
end
for i=maxbits,minbits,-1 do
table.insert(ip_check_table, ip:apply_mask(i):to_string() .. "/" .. i)
end
local function fwd_host_cb(err, data)
if err or type(data) ~= 'table' then
rspamd_logger.infox(rspamd_config, "fwd_host query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err)
return
end
for _,v in pairs(data) do
if type(v) == 'string' then
task:insert_result('WHITELISTED_FWD_HOST', 1.0, from_ip_string)
task:set_flag('pass_all')
return
end
end
end
local redis_ret = rspamd_redis_make_request(task,
redis_params, -- connect params
'WHITELISTED_FWD_HOST', -- hash key
false, -- is write
fwd_host_cb, --callback
'HMGET', -- command
ip_check_table -- arguments
)
if not redis_ret then
rspamd_logger.infox(rspamd_config, "cannot check fwd_host redis map")
end
end,
priority = 18
})
rspamd_config:register_symbol({ rspamd_config:register_symbol({
name = 'TLS_HEADER', name = 'TLS_HEADER',
type = 'postfilter', type = 'postfilter',
@@ -390,9 +445,8 @@ rspamd_config:register_symbol({
local rspamd_logger = require "rspamd_logger" local rspamd_logger = require "rspamd_logger"
local redis_params = rspamd_parse_redis_server('taghandler') local redis_params = rspamd_parse_redis_server('taghandler')
local rspamd_http = require "rspamd_http" local rspamd_http = require "rspamd_http"
local rcpts = task:get_recipients('smtp') local rcpts = task:get_recipients({'smtp', 'orig'})
local lua_util = require "lua_util" local lua_util = require "lua_util"
local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
local function remove_moo_tag() local function remove_moo_tag()
local moo_tag_header = task:get_header('X-Moo-Tag', false) local moo_tag_header = task:get_header('X-Moo-Tag', false)
@@ -417,8 +471,9 @@ rspamd_config:register_symbol({
-- Check if recipient has a tag (contains '+') -- Check if recipient has a tag (contains '+')
local tag = nil local tag = nil
if tagged_rcpt ~= nil then local base_user, tag_part = rcpt_user:match('^([^%+]+)%+(.+)$')
tag = tagged_rcpt if base_user and tag_part then
tag = tag_part
rspamd_logger.infox("TAG_MOO: found tag in recipient: %s (base: %s, tag: %s)", rcpt_addr, base_user, tag) rspamd_logger.infox("TAG_MOO: found tag in recipient: %s (base: %s, tag: %s)", rcpt_addr, base_user, tag)
end end
@@ -498,8 +553,7 @@ rspamd_config:register_symbol({
else else
rspamd_logger.infox("TAG_MOO: user wants subject modified for tagged mail") rspamd_logger.infox("TAG_MOO: user wants subject modified for tagged mail")
local sbj = task:get_header('Subject') or '' local sbj = task:get_header('Subject') or ''
local tag_value = tag[1] and tag[1].options and tag[1].options[1] or '' new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag_value .. '] ' .. sbj)) .. '?='
task:set_milter_reply({ task:set_milter_reply({
remove_headers = { remove_headers = {
['Subject'] = 1, ['Subject'] = 1,
@@ -907,6 +961,9 @@ rspamd_config:register_symbol({
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', new_cte)
task:set_milter_reply({
add_headers = {['Content-Transfer-Encoding'] = new_cte}
})
end end
-- End of headers -- End of headers
+1 -1
View File
@@ -109,7 +109,7 @@ foreach ($rcpts as $rcpt) {
} }
// Skip if domain is excluded // Skip if domain is excluded
if (in_array($parsed_rcpt['domain'], $exclude_domains)) { if (is_array($exclude_domains) && in_array($parsed_rcpt['domain'], $exclude_domains)) {
error_log(sprintf("QUARANTINE: Skipped domain %s", $parsed_rcpt['domain']) . PHP_EOL); error_log(sprintf("QUARANTINE: Skipped domain %s", $parsed_rcpt['domain']) . PHP_EOL);
continue; continue;
} }
+1 -1
View File
@@ -8,7 +8,7 @@ if(file_exists('inc/vars.local.inc.php')) {
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config); $autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
header('Content-type: application/json'); header('Content-type: application/json');
if (strtolower($_GET['Protocol']) == 'activesync' && getenv('SKIP_SOGO') != "y") { if (strtolower($_GET['Protocol']) == 'activesync' && $autodiscover_config['autodiscoverType'] == 'activesync' && getenv('SKIP_SOGO') != "y") {
echo '{"Protocol":"ActiveSync","Url":"' . $autodiscover_config['activesync']['url'] . '"}'; echo '{"Protocol":"ActiveSync","Url":"' . $autodiscover_config['activesync']['url'] . '"}';
} }
elseif (strtolower($_GET['Protocol']) == 'autodiscoverv1') { elseif (strtolower($_GET['Protocol']) == 'autodiscoverv1') {
+1 -1
View File
@@ -31,7 +31,7 @@ error_reporting(0);
$data = trim(file_get_contents("php://input")); $data = trim(file_get_contents("php://input"));
if (strpos($data, 'autodiscover/outlook/responseschema') !== false) { if (strpos($data, 'autodiscover/outlook/responseschema') !== false && $autodiscover_config['autodiscoverType'] == 'activesync') {
$autodiscover_config['autodiscoverType'] = 'imap'; $autodiscover_config['autodiscoverType'] = 'imap';
if ($autodiscover_config['useEASforOutlook'] == 'yes' && if ($autodiscover_config['useEASforOutlook'] == 'yes' &&
// Office for macOS does not support EAS // Office for macOS does not support EAS
+25 -2
View File
@@ -15,6 +15,10 @@ table.dataTable>tbody>tr.child ul.dtr-details>li {
border-bottom: 1px solid rgba(0, 0, 0, 0.129); border-bottom: 1px solid rgba(0, 0, 0, 0.129);
padding: 0.5em 0; padding: 0.5em 0;
} }
/* like the first entry has no top padding, the last one needs no bottom padding */
table.dataTable>tbody>tr.child ul.dtr-details>li:last-child {
padding-bottom: 0;
}
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: var(--mc-dtr-hover-bg);
@@ -28,7 +32,8 @@ table.dataTable td.dt-control:before {
color: var(--mc-dtr-color); color: var(--mc-dtr-color);
height: 1em; height: 1em;
width: 1em; width: 1em;
line-height: 1.25em; line-height: 1em;
margin-top: calc(-0.5em - 1.5px);
border-radius: 0px; border-radius: 0px;
box-shadow: none; box-shadow: none;
font-size: 14px; font-size: 14px;
@@ -39,11 +44,29 @@ 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: var(--mc-dtr-active-bg) !important;
} }
/* The default initial order targets column 0, which is usually the non-orderable expand column */
table.dataTable thead > tr > th.sorting_disabled.sorting_asc,
table.dataTable thead > tr > th.sorting_disabled.sorting_desc {
padding-right: 0.5rem;
cursor: auto;
}
table.dataTable thead > tr > th.sorting_disabled.sorting_asc:before,
table.dataTable thead > tr > th.sorting_disabled.sorting_asc:after,
table.dataTable thead > tr > th.sorting_disabled.sorting_desc:before,
table.dataTable thead > tr > th.sorting_disabled.sorting_desc:after {
display: none;
}
table.dataTable.table-striped>tbody>tr>td { table.dataTable.table-striped>tbody>tr>td {
vertical-align: middle; vertical-align: middle;
} }
table.dataTable.table-striped>tbody>tr>td>input[type="checkbox"] { table.dataTable.table-striped>tbody>tr>td>input[type="checkbox"] {
margin-top: 7px; /* centers the 1em checkbox in the 1.5em line, level with the expand control */
margin-top: 0.25em;
}
/* the vertical margin is meant for the tag editor in edit forms, in tables it only adds height */
table.dataTable .tag-badge {
margin-top: 0;
margin-bottom: 0;
} }
td.dtr-col-lg { td.dtr-col-lg {
min-width: 350px; min-width: 350px;
+8
View File
@@ -139,6 +139,13 @@ body {
display: inline-block; display: inline-block;
font-size: inherit; font-size: inherit;
} }
.badge .bi {
font-size: inherit;
}
/* keeps icons from making a badge taller than one with text only */
.badge .bi::before {
line-height: 0;
}
.btn-group-xs > .btn, .btn-xs { .btn-group-xs > .btn, .btn-xs {
padding: .25rem .4rem; padding: .25rem .4rem;
font-size: .875rem; font-size: .875rem;
@@ -343,6 +350,7 @@ code {
} }
.tag-badge .bi { .tag-badge .bi {
font-size: 12px; font-size: 12px;
line-height: 0;
} }
.tag-badge.btn-badge:hover { .tag-badge.btn-badge:hover {
filter: brightness(0.9); filter: brightness(0.9);
+1 -1
View File
@@ -191,7 +191,7 @@
height: 2rem; height: 2rem;
width: 2rem; width: 2rem;
line-height: 2rem; line-height: 2rem;
margin-top: -15px; margin-top: calc(-1rem - 1.5px);
} }
li .dtr-data { li .dtr-data {
+7
View File
@@ -601,6 +601,13 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){
// validate mailcow_password // validate mailcow_password
$mailcow_password = $user_res['attributes']['mailcow_password'][0]; $mailcow_password = $user_res['attributes']['mailcow_password'][0];
// Keycloak returns binary LDAP attributes like userPassword Base64 encoded
if (substr($mailcow_password, 0, 1) != '{') {
$decoded_password = base64_decode($mailcow_password, true);
if ($decoded_password !== false && substr($decoded_password, 0, 1) == '{') {
$mailcow_password = $decoded_password;
}
}
if (!verify_hash($mailcow_password, $pass)) { if (!verify_hash($mailcow_password, $pass)) {
return false; return false;
} }
+7
View File
@@ -35,6 +35,13 @@ function fwdhost($_action, $_data = null) {
); );
return false; return false;
} }
$hosts = array_unique(array_map(function ($host) {
$net = explode('/', $host, 2);
if (filter_var($net[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$net[0] = inet_ntop(inet_pton($net[0]));
}
return implode('/', $net);
}, $hosts));
foreach ($hosts as $host) { foreach ($hosts as $host) {
try { try {
$redis->hSet('WHITELISTED_FWD_HOST', $host, $source); $redis->hSet('WHITELISTED_FWD_HOST', $host, $source);
+31 -7
View File
@@ -1394,6 +1394,18 @@ function set_tfa($_data) {
case "webauthn": case "webauthn":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"]; $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
// Refuse incomplete WebAuthn registrations. Without this guard a request
// that does not contain the parsed credential data (e.g. a plain form
// POST) would silently insert an empty keyHandle while reporting success.
if (empty($_data['registration']) || empty($_data['registration']->credentialId)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'webauthn_registration_failed'
);
return false;
}
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`) $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`)
VALUES (?, ?, 'webauthn', ?, ?, ?, ?, '1')"); VALUES (?, ?, 'webauthn', ?, ?, ?, ?, '1')");
$stmt->execute(array( $stmt->execute(array(
@@ -1416,11 +1428,11 @@ function set_tfa($_data) {
// Block TFA removal if force_tfa policy is active // Block TFA removal if force_tfa policy is active
$is_forced_tfa = false; $is_forced_tfa = false;
if ($_SESSION['mailcow_cc_role'] === 'user') { if ($_SESSION['mailcow_cc_role'] === 'user') {
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?"); $stmt_check = $pdo->prepare("SELECT JSON_VALUE(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?");
$stmt_check->execute(array($username)); $stmt_check->execute(array($username));
$is_forced_tfa = ($stmt_check->fetchColumn() == '1'); $is_forced_tfa = ($stmt_check->fetchColumn() == '1');
} else { } else {
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?"); $stmt_check = $pdo->prepare("SELECT JSON_VALUE(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?");
$stmt_check->execute(array($username)); $stmt_check->execute(array($username));
$is_forced_tfa = ($stmt_check->fetchColumn() == '1'); $is_forced_tfa = ($stmt_check->fetchColumn() == '1');
} }
@@ -1687,11 +1699,11 @@ function unset_tfa_key($_data) {
// Block key removal if force_tfa policy is active // Block key removal if force_tfa policy is active
$is_forced_tfa = false; $is_forced_tfa = false;
if ($_SESSION['mailcow_cc_role'] === 'user') { if ($_SESSION['mailcow_cc_role'] === 'user') {
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?"); $stmt_check = $pdo->prepare("SELECT JSON_VALUE(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?");
$stmt_check->execute(array($username)); $stmt_check->execute(array($username));
$is_forced_tfa = ($stmt_check->fetchColumn() == '1'); $is_forced_tfa = ($stmt_check->fetchColumn() == '1');
} else { } else {
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?"); $stmt_check = $pdo->prepare("SELECT JSON_VALUE(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?");
$stmt_check->execute(array($username)); $stmt_check->execute(array($username));
$is_forced_tfa = ($stmt_check->fetchColumn() == '1'); $is_forced_tfa = ($stmt_check->fetchColumn() == '1');
} }
@@ -2420,7 +2432,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
case "use_ssl": case "use_ssl":
case "use_tls": case "use_tls":
case "login_provisioning": case "login_provisioning":
case "ignore_ssl_errors": case "ignore_ssl_error":
$settings[$row["key"]] = boolval($row["value"]); $settings[$row["key"]] = boolval($row["value"]);
break; break;
default: default:
@@ -2444,7 +2456,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
if ($settings["authsource"] == "ldap"){ if ($settings["authsource"] == "ldap"){
$settings['use_ssl'] = !isset($settings['use_ssl']) ? false : $settings['use_ssl']; $settings['use_ssl'] = !isset($settings['use_ssl']) ? false : $settings['use_ssl'];
$settings['use_tls'] = !isset($settings['use_tls']) ? false : $settings['use_tls']; $settings['use_tls'] = !isset($settings['use_tls']) ? false : $settings['use_tls'];
$settings['ignore_ssl_errors'] = !isset($settings['ignore_ssl_errors']) ? false : $settings['ignore_ssl_errors']; $settings['ignore_ssl_error'] = !isset($settings['ignore_ssl_error']) ? false : $settings['ignore_ssl_error'];
} }
return $settings; return $settings;
break; break;
@@ -3106,7 +3118,17 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
case "get-keycloak-admin-token": case "get-keycloak-admin-token":
// get access_token for service account of mailcow client // get access_token for service account of mailcow client
if ($iam_settings['authsource'] !== 'keycloak') return false; if ($iam_settings['authsource'] !== 'keycloak') return false;
if (isset($iam_settings['access_token'])) { // only introspect a cached access_token that is not (about to be) expired,
// introspecting an expired token makes Keycloak log INTROSPECT_TOKEN_ERROR
$token_not_expired = false;
if (!empty($iam_settings['access_token'])) {
$token_parts = explode('.', $iam_settings['access_token']);
if (count($token_parts) === 3) {
$token_payload = json_decode(base64_decode(strtr($token_parts[1], '-_', '+/')), true);
$token_not_expired = is_array($token_payload) && isset($token_payload['exp']) && intval($token_payload['exp']) > time() + 30;
}
}
if ($token_not_expired) {
// check if access_token is valid // check if access_token is valid
$url = "{$iam_settings['server_url']}/realms/{$iam_settings['realm']}/protocol/openid-connect/token/introspect"; $url = "{$iam_settings['server_url']}/realms/{$iam_settings['realm']}/protocol/openid-connect/token/introspect";
$req = http_build_query(array( $req = http_build_query(array(
@@ -3157,6 +3179,8 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
':key' => 'access_token', ':key' => 'access_token',
':value' => $res['access_token'] ':value' => $res['access_token']
)); ));
// keep the cached token in sync, so subsequent calls in the same run reuse it
$iam_settings['access_token'] = $res['access_token'];
return $res['access_token']; return $res['access_token'];
break; break;
} }
+44 -1
View File
@@ -2524,6 +2524,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':domain' => $domain, ':domain' => $domain,
':tag_name' => $tag, ':tag_name' => $tag,
)); ));
$stmt = $pdo->prepare("UPDATE `domain` SET `modified` = NOW() WHERE `domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
} }
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@@ -2692,6 +2696,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':domain' => $domain, ':domain' => $domain,
':tag_name' => $tag, ':tag_name' => $tag,
)); ));
$stmt = $pdo->prepare("UPDATE `domain` SET `modified` = NOW() WHERE `domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
} }
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@@ -3170,6 +3178,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':username' => $username, ':username' => $username,
':tag_name' => $tag, ':tag_name' => $tag,
)); ));
$stmt = $pdo->prepare("UPDATE `mailbox` SET `modified` = NOW() WHERE `username` = :username");
$stmt->execute(array(
':username' => $username,
));
} catch (Exception $e) { } catch (Exception $e) {
} }
} }
@@ -3988,6 +4000,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt->execute(array(':goto' => '(^|,)'.preg_quote($_data, '/').'($|,)')); $stmt->execute(array(':goto' => '(^|,)'.preg_quote($_data, '/').'($|,)'));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($rows)) { while ($row = array_shift($rows)) {
if (!hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['address'])) {
continue;
}
// Keep old array for backward compatibility // Keep old array for backward compatibility
$data['fixed_sender_aliases'][] = $row['address']; $data['fixed_sender_aliases'][] = $row['address'];
// Split into allowed/blocked for proper display // Split into allowed/blocked for proper display
@@ -4923,6 +4938,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
else if ($SaslLogs['service'] == 'SSO') { else if ($SaslLogs['service'] == 'SSO') {
$last_sso_login = strtotime($SaslLogs['datetime']); $last_sso_login = strtotime($SaslLogs['datetime']);
} }
else if ($SaslLogs['service'] == 'SOGO') {
$last_sogo_login = strtotime($SaslLogs['datetime']);
}
} }
if (!isset($last_imap_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { if (!isset($last_imap_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_imap_login = 0; $last_imap_login = 0;
@@ -4936,10 +4954,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
if (!isset($last_sso_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { if (!isset($last_sso_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_sso_login = 0; $last_sso_login = 0;
} }
if (!isset($last_sogo_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_sogo_login = 0;
}
$mailboxdata['last_imap_login'] = $last_imap_login; $mailboxdata['last_imap_login'] = $last_imap_login;
$mailboxdata['last_smtp_login'] = $last_smtp_login; $mailboxdata['last_smtp_login'] = $last_smtp_login;
$mailboxdata['last_pop3_login'] = $last_pop3_login; $mailboxdata['last_pop3_login'] = $last_pop3_login;
$mailboxdata['last_sso_login'] = $last_sso_login; $mailboxdata['last_sso_login'] = $last_sso_login;
$mailboxdata['last_sogo_login'] = $last_sogo_login;
if (!isset($_extra) || $_extra != 'reduced') { if (!isset($_extra) || $_extra != 'reduced') {
$rl = ratelimit('get', 'mailbox', $_data); $rl = ratelimit('get', 'mailbox', $_data);
@@ -4977,7 +4999,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt->execute(array(':username' => $_data)); $stmt->execute(array(':username' => $_data));
$sender_acl_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $sender_acl_rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($sender_acl_rows as $sender_acl_row) { foreach ($sender_acl_rows as $sender_acl_row) {
$mailboxdata['sender_acl'][] = $sender_acl_row['send_as']; $send_as = $sender_acl_row['send_as'];
if ($_SESSION['mailcow_cc_role'] == 'domainadmin' && $send_as != '*') {
$in_scope = (substr($send_as, 0, 1) == '@')
? hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], substr($send_as, 1))
: hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $send_as);
if (!$in_scope) {
continue;
}
}
$mailboxdata['sender_acl'][] = $send_as;
} }
} }
$stmt = $pdo->prepare("SELECT `tag_name` $stmt = $pdo->prepare("SELECT `tag_name`
@@ -5916,6 +5947,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':domain' => $domain, ':domain' => $domain,
':tag_name' => $tag, ':tag_name' => $tag,
)); ));
if ($stmt->rowCount() > 0) {
$stmt = $pdo->prepare("UPDATE `domain` SET `modified` = NOW() WHERE `domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
}
} }
} }
@@ -5968,6 +6005,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':username' => $username, ':username' => $username,
':tag_name' => $tag, ':tag_name' => $tag,
)); ));
if ($stmt->rowCount() > 0) {
$stmt = $pdo->prepare("UPDATE `mailbox` SET `modified` = NOW() WHERE `username` = :username");
$stmt->execute(array(
':username' => $username,
));
}
} }
} }
+14 -7
View File
@@ -614,7 +614,7 @@ jQuery(function($){
defaultContent: '' defaultContent: ''
}, },
{ {
title: 'RL', title: lang_edit.ratelimit,
data: 'rl', data: 'rl',
searchable: false, searchable: false,
orderable: false, orderable: false,
@@ -715,10 +715,6 @@ jQuery(function($){
$.each(json, function (i, item) { $.each(json, function (i, item) {
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="domain_template" name="multi_select" value="' + encodeURIComponent(item.id) + '" />'; item.chkbox = '<input type="checkbox" class="form-check-input" data-id="domain_template" name="multi_select" value="' + encodeURIComponent(item.id) + '" />';
item.attributes.def_quota_for_mbox = humanFileSize(item.attributes.def_quota_for_mbox);
item.attributes.max_quota_for_mbox = humanFileSize(item.attributes.max_quota_for_mbox);
item.attributes.max_quota_for_domain = humanFileSize(item.attributes.max_quota_for_domain);
item.template = escapeHtml(item.template); item.template = escapeHtml(item.template);
if (item.attributes.rl_frame === "s"){ if (item.attributes.rl_frame === "s"){
item.attributes.rl_frame = lang_rl.second; item.attributes.rl_frame = lang_rl.second;
@@ -801,16 +797,25 @@ jQuery(function($){
title: lang.mailbox_defquota, title: lang.mailbox_defquota,
data: 'attributes.def_quota_for_mbox', data: 'attributes.def_quota_for_mbox',
defaultContent: '', defaultContent: '',
render: function (data, type) {
return type === 'sort' || type === 'type' ? data : humanFileSize(data);
}
}, },
{ {
title: lang.max_quota, title: lang.max_quota,
data: 'attributes.max_quota_for_mbox', data: 'attributes.max_quota_for_mbox',
defaultContent: '', defaultContent: '',
render: function (data, type) {
return type === 'sort' || type === 'type' ? data : humanFileSize(data);
}
}, },
{ {
title: lang.domain_quota_total, title: lang.domain_quota_total,
data: 'attributes.max_quota_for_domain', data: 'attributes.max_quota_for_domain',
defaultContent: '', defaultContent: '',
render: function (data, type) {
return type === 'sort' || type === 'type' ? data : humanFileSize(data);
}
}, },
{ {
title: lang.gal, title: lang.gal,
@@ -932,6 +937,7 @@ jQuery(function($){
item.last_mail_login = (item.attributes.imap_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">IMAP @ ' + unix_time_format(Number(item.last_imap_login)) + '</div><br>' : '') + item.last_mail_login = (item.attributes.imap_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">IMAP @ ' + unix_time_format(Number(item.last_imap_login)) + '</div><br>' : '') +
(item.attributes.pop3_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">POP3 @ ' + unix_time_format(Number(item.last_pop3_login)) + '</div><br>' : '') + (item.attributes.pop3_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">POP3 @ ' + unix_time_format(Number(item.last_pop3_login)) + '</div><br>' : '') +
(item.attributes.smtp_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">SMTP @ ' + unix_time_format(Number(item.last_smtp_login)) + '</div><br>' : '') + (item.attributes.smtp_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">SMTP @ ' + unix_time_format(Number(item.last_smtp_login)) + '</div><br>' : '') +
'<div class="text-start badge bg-info mb-2" style="min-width: 70px;">SOGO @ ' + unix_time_format(Number(item.last_sogo_login)) + '</div><br>' +
'<div class="text-start badge bg-info" style="min-width: 70px;">SSO @ ' + unix_time_format(Number(item.last_sso_login)) + '</div>'; '<div class="text-start badge bg-info" style="min-width: 70px;">SSO @ ' + unix_time_format(Number(item.last_sso_login)) + '</div>';
/* /*
if (!item.rl) { if (!item.rl) {
@@ -1244,8 +1250,6 @@ jQuery(function($){
item.attributes.rl_value = (!item.attributes.rl_value) ? "∞" : escapeHtml(item.attributes.rl_value); item.attributes.rl_value = (!item.attributes.rl_value) ? "∞" : escapeHtml(item.attributes.rl_value);
item.attributes.ratelimit = item.attributes.rl_value + " " + item.attributes.rl_frame; item.attributes.ratelimit = item.attributes.rl_value + " " + item.attributes.rl_frame;
item.attributes.quota = humanFileSize(item.attributes.quota);
item.attributes.tls_enforce_in = '<i class="text-' + (item.attributes.tls_enforce_in == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_in == 1 ? '1' : '0') + '</span></i>'; item.attributes.tls_enforce_in = '<i class="text-' + (item.attributes.tls_enforce_in == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_in == 1 ? '1' : '0') + '</span></i>';
item.attributes.tls_enforce_out = '<i class="text-' + (item.attributes.tls_enforce_out == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_out == 1 ? '1' : '0') + '</span></i>'; item.attributes.tls_enforce_out = '<i class="text-' + (item.attributes.tls_enforce_out == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_out == 1 ? '1' : '0') + '</span></i>';
item.attributes.pop3_access = '<i class="text-' + (item.attributes.pop3_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.pop3_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.pop3_access == 1 ? '1' : '0') + '</span></i>'; item.attributes.pop3_access = '<i class="text-' + (item.attributes.pop3_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.pop3_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.pop3_access == 1 ? '1' : '0') + '</span></i>';
@@ -1332,6 +1336,9 @@ jQuery(function($){
title: lang.domain_quota, title: lang.domain_quota,
data: 'attributes.quota', data: 'attributes.quota',
defaultContent: '', defaultContent: '',
render: function (data, type) {
return type === 'sort' || type === 'type' ? data : humanFileSize(data);
}
}, },
{ {
title: lang.tls_enforce_in, title: lang.tls_enforce_in,
+6 -2
View File
@@ -1087,10 +1087,14 @@ if (isset($_GET['query'])) {
$data['mailboxes'][$mailbox][] = $mailbox; $data['mailboxes'][$mailbox][] = $mailbox;
$user_alias_details = user_get_alias_details($mailbox); $user_alias_details = user_get_alias_details($mailbox);
foreach ($user_alias_details['direct_aliases'] as $k => $v) { foreach ($user_alias_details['direct_aliases'] as $k => $v) {
$data['mailboxes'][$mailbox][] = $k; if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], substr(strrchr($k, '@'), 1))) {
$data['mailboxes'][$mailbox][] = $k;
}
} }
foreach ($user_alias_details['shared_aliases'] as $k => $v) { foreach ($user_alias_details['shared_aliases'] as $k => $v) {
$data['mailboxes'][$mailbox][] = $k; if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], substr(strrchr($k, '@'), 1))) {
$data['mailboxes'][$mailbox][] = $k;
}
} }
} }
} }
+2 -1
View File
@@ -1127,7 +1127,7 @@
"mailbox_defaults_info": "Define default settings for new mailboxes.", "mailbox_defaults_info": "Define default settings for new mailboxes.",
"mailbox_defquota": "Default mailbox size", "mailbox_defquota": "Default mailbox size",
"mailbox_templates": "Mailbox Templates", "mailbox_templates": "Mailbox Templates",
"mailbox_quota": "Max. size of a mailbox", "mailbox_quota": "Max. mailbox size",
"mailboxes": "Mailboxes", "mailboxes": "Mailboxes",
"max_aliases": "Max. aliases", "max_aliases": "Max. aliases",
"max_mailboxes": "Max. possible mailboxes", "max_mailboxes": "Max. possible mailboxes",
@@ -1429,6 +1429,7 @@
"u2f_deprecated": "It seems that your Key was registered using the deprecated U2F method. We will deactivate Two-Factor-Authenticaiton for you and delete your Key.", "u2f_deprecated": "It seems that your Key was registered using the deprecated U2F method. We will deactivate Two-Factor-Authenticaiton for you and delete your Key.",
"u2f_deprecated_important": "Please register your Key in the admin panel with the new WebAuthn method.", "u2f_deprecated_important": "Please register your Key in the admin panel with the new WebAuthn method.",
"webauthn": "WebAuthn authentication", "webauthn": "WebAuthn authentication",
"webauthn_registration_failed": "WebAuthn registration failed. The credential could not be verified, please try again.",
"waiting_usb_auth": "<i>Waiting for USB device...</i><br><br>Please tap the button on your USB device now.", "waiting_usb_auth": "<i>Waiting for USB device...</i><br><br>Please tap the button on your USB device now.",
"waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your registration by tapping the button on your USB device.", "waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your registration by tapping the button on your USB device.",
"yubi_otp": "Yubico OTP authentication" "yubi_otp": "Yubico OTP authentication"
-5
View File
@@ -1074,7 +1074,6 @@
"verified_webauthn_login": "Inicio de sesión WebAuthn verificado", "verified_webauthn_login": "Inicio de sesión WebAuthn verificado",
"verified_yotp_login": "Inicio de sesión Yubico OTP verificado", "verified_yotp_login": "Inicio de sesión Yubico OTP verificado",
"admin_added": "Se agregó el administrador %s", "admin_added": "Se agregó el administrador %s",
"admin_api_modified": "Se guardaron los cambios en la API",
"admin_removed": "Se eliminó el administrador %s", "admin_removed": "Se eliminó el administrador %s",
"app_links": "Se guardaron los cambios en los enlaces de aplicaciones", "app_links": "Se guardaron los cambios en los enlaces de aplicaciones",
"app_passwd_added": "Se agregó una nueva contraseña de aplicación", "app_passwd_added": "Se agregó una nueva contraseña de aplicación",
@@ -1333,10 +1332,6 @@
"sogo_profile_reset_help": "Esto destruirá el perfil de SOGo del usuario y <b>eliminará de forma irrecuperable todos sus contactos y datos de calendario</b>.", "sogo_profile_reset_help": "Esto destruirá el perfil de SOGo del usuario y <b>eliminará de forma irrecuperable todos sus contactos y datos de calendario</b>.",
"spam_score_reset": "Restablecer al valor predeterminado del servidor", "spam_score_reset": "Restablecer al valor predeterminado del servidor",
"spamfilter_table_domain_policy": "n/d (política del dominio)", "spamfilter_table_domain_policy": "n/d (política del dominio)",
"syncjob_check_log": "Revisar registro",
"syncjob_EX_OK": "Correcto",
"syncjob_EXIT_OVERQUOTA": "El buzón de destino superó su cuota",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Usuario o contraseña incorrectos",
"tfa_info": "La autenticación de dos factores ayuda a proteger su cuenta. Si la activa, necesitará contraseñas de aplicación para iniciar sesión en aplicaciones o servicios que no admiten la autenticación de dos factores (p. ej., clientes de correo)." "tfa_info": "La autenticación de dos factores ayuda a proteger su cuenta. Si la activa, necesitará contraseñas de aplicación para iniciar sesión en aplicaciones o servicios que no admiten la autenticación de dos factores (p. ej., clientes de correo)."
}, },
"warning": { "warning": {
-1
View File
@@ -95,7 +95,6 @@
"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",
"internal": "Interno", "internal": "Interno",
"internal_info": "Gli alias interni sono accessibili solo dal proprio dominio o dai domini alias.", "internal_info": "Gli alias interni sono accessibili solo dal proprio dominio o dai domini alias.",
"sender_allowed": "Consenti l'invio con questo alias" "sender_allowed": "Consenti l'invio con questo alias"
+327 -11
View File
@@ -30,7 +30,8 @@
"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" "alias_external_goto": "Dovoli vzdevke z zunanjimi domenami goto",
"mailbox_groups": "Skupine"
}, },
"add": { "add": {
"active": "Aktivno", "active": "Aktivno",
@@ -361,7 +362,7 @@
"iam_default_template": "Privzeta predloga", "iam_default_template": "Privzeta predloga",
"iam_default_template_description": "Če uporabniku ni dodeljena nobena predloga, bo privzeta predloga uporabljena za ustvarjanje nabiralnika, ne pa za posodabljanje nabiralnika.", "iam_default_template_description": "Če uporabniku ni dodeljena nobena predloga, bo privzeta predloga uporabljena za ustvarjanje nabiralnika, ne pa za posodabljanje nabiralnika.",
"iam_description": "Konfigurirajte zunanjega ponudnika za preverjanje pristnosti<br>Uporabnikovi poštni nabiralniki bodo samodejno ustvarjeni ob prvi prijavi, če je nastavljeno preslikavanje atributov.", "iam_description": "Konfigurirajte zunanjega ponudnika za preverjanje pristnosti<br>Uporabnikovi poštni nabiralniki bodo samodejno ustvarjeni ob prvi prijavi, če je nastavljeno preslikavanje atributov.",
"iam_extra_permission": "Za delovanje naslednjih nastavitev potrebuje odjemalec mailcow v Keycloaku <code>Servisni račun</code> in dovoljenje za <code>ogled uporabnikov</code>.", "iam_extra_permission": "Da bi spodnje nastavitve delovale, odjemalec mailcow v sistemu Keycloak potrebuje <code>servisni račun</code> (service account) in dovoljenje za <code>view-users</code> (pri sinhronizaciji skupin tudi <code>query-groups</code>).",
"iam_host": "Gostitelj", "iam_host": "Gostitelj",
"iam_host_info": "Vnesite enega ali več gostiteljev LDAP, ločenih z vejicami.", "iam_host_info": "Vnesite enega ali več gostiteljev LDAP, ločenih z vejicami.",
"iam_import_users": "Uvozi uporabnike", "iam_import_users": "Uvozi uporabnike",
@@ -401,7 +402,104 @@
"ui_theme_dark": "Vključuje različico s temnim načinom", "ui_theme_dark": "Vključuje različico s temnim načinom",
"ui_theme_default": "Privzeto", "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_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." "ui_theme_none": "Tem ni bilo mogoče najti.",
"api_key_add": "Dodaj API ključ",
"api_keys": "API ključi",
"api_networks": "Dovoljeni naslovi IP/omrežja",
"api_service_key": "Ključ storitve iz datoteke mailcow.conf; spremeniti ga je mogoče le tam.",
"api_service_key_info": "Ta ključ API je določen v datoteki mailcow.conf (API_KEY, API_KEY_READ_ONLY, API_ALLOW_FROM) in ga je mogoče spremeniti le tam.",
"iam_attribute_field_info": "Neobvezno. Atribut LDAP, katerega vrednost izbere predlogo v spodnjem preslikovanju.",
"iam_disabled_filter": "Onemogočen filter",
"iam_disabled_filter_info": "Filter LDAP za onemogočene uporabnike. Računi v imeniku Active Directory, ki imajo v atributu userAccountControl nastavljeno vrednost ACCOUNTDISABLE, se vedno štejejo za onemogočene.",
"iam_encryption": "Šifriranje",
"iam_encryption_info": "StartTLS uporablja običajna vrata (389). SSL (LDAPS) uporablja vrata 636, pri čemer se vrata 389 samodejno spremenijo.",
"iam_encryption_none": "Nič",
"iam_filter_info": "LDAP-filter za uporabnike, ki se lahko prijavijo in so sinhronizirani, npr. <code>(objectClass=person)</code>.",
"iam_group_autocreate": "Ustvari skupine",
"iam_group_autocreate_info": "Za vsako skupino IdP, ki še nima poštnega predala, ustvari skupino s poštnim predalom. Če je naslov skupine prost, postane ta naslov njen naslov.",
"iam_group_basedn": "DN osnove za skupine",
"iam_group_default_domain": "Privzeta domena skupine",
"iam_group_default_domain_info": "Domena novih skupin, katerih e-pošta ni v domeni mailcow.",
"iam_group_filter": "Filter skupin",
"iam_group_member_attribute": "Atribut člana",
"iam_group_member_attribute_info": "Samo za \"Atribut članstva v skupinah\", npr. member ali uniqueMember.",
"iam_group_member_mode": "Članstva",
"iam_group_member_mode_ad_chain": "Active Directory, vključno z ugnezdenimi skupinami",
"iam_group_member_mode_member": "Atribut članstva v skupinah",
"iam_group_member_mode_memberof": "memberOf uporabnikov",
"iam_group_name_attribute": "Atribut imena skupine",
"iam_group_paths": "Pot skupine",
"iam_group_paths_info": "Z vejicami ločene poti do skupin v Keycloaku: <code>/Sales</code> ali <code>/mailcow/*</code> za vse skupine pod <code>/mailcow</code>. Odjemalec mailcow potrebuje dovoljenje <code>query-groups</code>.",
"iam_group_sync": "Sinhronizacija skupin",
"iam_group_sync_info": "Sinhronizira člane skupin iz ponudnika identitete (IdP) s skupinami poštnih predalov (Poštni predali → Skupine). Člani, dodani v sistemu mailcow, ostanejo.",
"iam_id_attribute": "ID atribut",
"iam_id_attribute_info": "Stalen ID uporabnika. Prazno = objectGUID, entryUUID, nsUniqueId ali ipaUniqueID. Sprememba povzroči ponovno povezovanje vseh poštnih predalov na podlagi e-poštnega naslova.",
"iam_import_users_info": "Ustvari poštne predale za vse uporabnike ponudnika identitete (IdP) na podlagi ustrezne predloge, ne da bi bilo treba čakati na njihovo prvo prijavo.",
"iam_login_provisioning_info": "Ob prvi prijavi uporabnika ustvari poštni predal z ustrezno ali privzeto predlogo.",
"iam_mapping_add": "Dodaj preslikavo",
"iam_mapping_info": "Vrednost uporabniškega atributa določa predlogo (Keycloak in OIDC: <code>mailcow_template</code>). Predloge skupin imajo prednost.",
"iam_offboarding": "Zaključek sodelovanja",
"iam_offboarding_info": "Uporabniki, ki so onemogočeni v IdP ali pa so manjkali v dveh popolnih sinhronizacijah, se ne morejo več prijaviti. Njihov poštni nabiralnik še vedno prejema pošto. Ponovno so omogočeni samo poštni nabiralniki, ki jih je sinhronizacija onemogočila.",
"iam_offboarding_limit": "Sprememba meje (%)",
"iam_offboarding_limit_info": "Sinhronizacijski postopki, ki bi onemogočili več nabiralnikov ali odstranili več članstev v skupinah, kot je določeno s to omejitvijo (vsaj 5), so blokirani.",
"iam_periodic_full_sync_info": "Posodobi ime in predlogo obstoječih poštnih predalov na podlagi podatkov iz ponudnika identitete (IdP).",
"iam_redirect_url_add": "Dodaj URL za preusmeritev",
"iam_section_connection": "Povezava",
"iam_section_groups": "Skupine",
"iam_section_login": "Prijava",
"iam_section_sync": "Sinhronizacija",
"iam_section_templates": "Predloge za poštne predale",
"iam_section_users": "Uporabniki",
"iam_sync_accounts": "Poštni predali IdP",
"iam_sync_changes": "Spremembe",
"iam_sync_count_conflicts": "Konflikti",
"iam_sync_count_creates": "Ustvarjeno",
"iam_sync_count_deactivate": "Prijava onemogočena",
"iam_sync_count_errors": "Napake",
"iam_sync_count_found": "Ponovno najdeno",
"iam_sync_count_group_creates": "Ustvarjene skupine",
"iam_sync_count_group_renames": "Skupine preimenovane",
"iam_sync_count_links": "Povezano",
"iam_sync_count_membership_removals": "Članstva odstranjena",
"iam_sync_count_memberships": "Skupine z novimi člani",
"iam_sync_count_missing": "Manjka",
"iam_sync_count_orphaned_groups": "Skupine, ki niso v IdP",
"iam_sync_count_release": "Sproščeno",
"iam_sync_count_restore": "Prijava omogočena",
"iam_sync_count_skipped": "Preskočeno",
"iam_sync_count_updates": "Posodobljeno",
"iam_sync_details": "Podrobnosti",
"iam_sync_force": "Zaženi brez omejitev",
"iam_sync_force_confirm": "Želite zagnati sinhronizacijo brez omejitve števila sprememb? Nabiralniki so lahko onemogočeni, članstva v skupinah pa odstranjena.",
"iam_sync_idp_disabled": "Onemogočeno s strani IdP",
"iam_sync_linked": "Povezano",
"iam_sync_missing": "Manjka v IdP",
"iam_sync_mode": "Način",
"iam_sync_mode_force": "Brez omejitev",
"iam_sync_mode_preview": "Predogled",
"iam_sync_mode_run": "Zaženi",
"iam_sync_no_changes": "Ni sprememb",
"iam_sync_none": "Ni še bilo izvedenih sinhronizacij.",
"iam_sync_off": "Sinhronizacija ni aktivna. Omogočite uvoz, periodično sinhronizacijo, postopek ob zaključku sodelovanja (offboarding) ali sinhronizacijo skupin.",
"iam_sync_preview": "Predogled",
"iam_sync_result": "Rezultat",
"iam_sync_run": "Zaženi zdaj",
"iam_sync_started": "Začeto",
"iam_sync_status": "Stanje sinhronizacije",
"iam_sync_status_blocked": "Blokirano",
"iam_sync_status_error": "Napaka",
"iam_sync_status_info": "Izvajanje se začne v eni minuti. Predogled prikaže spremembe, ne da bi jih uveljavil.",
"iam_sync_status_ok": "OK",
"iam_sync_status_pending": "V čakalni vrsti",
"iam_sync_status_running": "V teku",
"iam_username_field_info": "Atribut z e-poštnim naslovom postane ime poštnega predala. Privzeto: mail.",
"description": "Opis",
"unban_all": "Odblokiraj vse",
"syncjobs": "Opravila sinhronizacije",
"syncjob_max_parallel": "Največje število vzporednih sinhronizacijskih procesov",
"syncjob_max_parallel_info": "Koliko opravil sinhronizacije lahko poteka hkrati (1–32).",
"syncjob_max_kb_per_second": "Omejitev pasovne širine na proces (KB/s)",
"syncjob_max_kb_per_second_info": "0 = neomejeno. Omejuje vsak postopek sinhronizacije (v KB/s); omejitev za posamezno opravilo (v bajtih/s) je navzgor omejena s to vrednostjo. Skupna vrednost ≈ ta vrednost × največje število vzporednih postopkov."
}, },
"danger": { "danger": {
"alias_goto_identical": "Vzdevek in ciljni naslov se ne smeta ujemati", "alias_goto_identical": "Vzdevek in ciljni naslov se ne smeta ujemati",
@@ -550,7 +648,50 @@
"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", "quarantine_category_invalid": "Kategorija karantene mora biti ena od: add_header, reject, all",
"external_goto_denied": "Zunanji naslov goto %s ni dovoljen", "external_goto_denied": "Zunanji naslov goto %s ni dovoljen",
"ui_theme_invalid": "Tema ni najdena" "ui_theme_invalid": "Tema ni najdena",
"alias_group_managed": "Vzdevek %s pripada skupini in ga je mogoče spremeniti le tam.",
"api_key_access_invalid": "Neveljavna vrsta dostopa do API-ja; dovoljene vrednosti so ro in rw",
"api_key_description_invalid": "Opis ključa API ne sme presegati 255 znakov",
"api_key_network_invalid": "Neveljaven naslov IP ali omrežje: %s",
"api_key_service_locked": "Ključ API %s je določen v datoteki mailcow.conf in ga je mogoče spremeniti le tam",
"group_address_invalid": "Skupinski naslov %s je neveljaven ali pa ni v domeni skupine",
"group_description_invalid": "Opis skupine ne sme presegati 255 znakov.",
"group_grant_invalid": "Neveljavna delitev za poštni predal %s",
"group_member_idp": "%s je član prek ponudnika identitete in ga je mogoče odstraniti le tam",
"group_member_invalid": "%s ni poštni predal in ne more biti član skupine",
"group_name_invalid": "Ime skupine ni veljavno (1–100 znakov)",
"group_priority_invalid": "Prednostna naloga mora biti številka",
"group_send_as_invalid": "Naslov za pošiljanje »%s« je neveljaven ali neznan",
"iam_sync_mode_invalid": "Neveljaven način sinhronizacije",
"iam_sync_unavailable": "Ponudnika identitete ni mogoče sinhronizirati (samo Keycloak in LDAP)",
"tfa_login_locked": "Preveč neuspešnih poskusov prijave z dvostopenjskim preverjanjem; poskusite znova pozneje",
"iam_unavailable": "Ponudnik identitete ni dosegljiv; poskusite znova pozneje.",
"refresh_login_failed": "Seje ni bilo mogoče osvežiti. Prosimo, prijavite se znova.",
"imapsync_source_in_use": "Vir sinhronizacije %s še vedno uporablja opravilo sinhronizacije in ga ni mogoče izbrisati",
"imapsync_source_used_by_others": "Strežnika vira sinhronizacije %s ni mogoče spremeniti, saj ga uporabljajo opravila sinhronizacije drugih poštnih predalov",
"imapsync_source_token_refresh_failed": "Osvežitev žetona OAuth za %s ni uspela",
"imapsync_source_scope_empty": "Izberite vsaj eno domeno za izbrano vidnost",
"imapsync_source_unavailable": "Izbrani vir sinhronizacije ni na voljo",
"imapsync_source_oauth_connect_required": "Pred shranjevanjem tega opravila sinhronizacije se prijavite pri ponudniku.",
"imapsync_source_name_invalid": "Neveljavno ime vira sinhronizacije",
"imapsync_source_host_invalid": "Neveljavno ime gostitelja ali naslov IP",
"imapsync_source_port_invalid": "Neveljavna vrata",
"imapsync_source_enc_invalid": "Neveljaven način šifriranja",
"imapsync_source_auth_invalid": "Neveljaven način preverjanja pristnosti",
"imapsync_custom_param_invalid": "Parametri po meri vsebujejo nedovoljeno možnost za imapsync: %s",
"imapsync_source_oauth_required": "OAuth2 zahteva končno točko za žetone, ID odjemalca, skrivnost odjemalca in obseg.",
"imapsync_source_oauth_endpoint_invalid": "Neveljavna končna točka OAuth2: zahtevan je naslov URL s protokolom https:// in veljavnim imenom gostitelja (viri, ki jih ni ustvaril skrbnik, potrebujejo javno dostopnega gostitelja)",
"imapsync_source_oauth_extra_invalid": "Dodatni parametri OAuth2 morajo biti objekt JSON s preprostimi vrednostmi, npr. {\"vir\": \"https://example.com\"}",
"imapsync_source_tenant_missing": "Vnesite najemnika Microsoft 365",
"imapsync_source_oauth_invalid": "Neveljavna konfiguracija OAuth2",
"imapsync_max_parallel_invalid": "Število vzporednih procesov mora biti med 1 in 32",
"imapsync_source_description_invalid": "Opis ne sme presegati 255 znakov.",
"imapsync_source_refresh_unsupported": "Ročno osveževanje žetonov je na voljo samo za vire poverilnic odjemalcev",
"imapsync_custom_params_format": "Prilagojene parametre je treba poslati kot seznam parov možnost/vrednost",
"imapsync_custom_param_value": "Neveljavna vrednost za možnost %s programa imapsync (manjkajoča vrednost, vrednost, podana pri zastavici, ali nedovoljena vrednost)",
"imapsync_job_running": "Opravilo sinhronizacije %s se trenutno izvaja, zato ga ni mogoče načrtovati za naslednji izvedbeni termin",
"imapsync_user1_invalid": "Neveljavno oddaljeno uporabniško ime",
"imapsync_max_kb_per_second_invalid": "Omejitev pasovne širine mora biti med 0 in 10485760 KB/s"
}, },
"debug": { "debug": {
"containers_info": "Informacije o zabojniku", "containers_info": "Informacije o zabojniku",
@@ -764,7 +905,13 @@
"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." "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.",
"iam_idp_disabled": "Prijava onemogočena zaradi sinhronizacije z IdP",
"iam_link": "Povezava do IdP",
"iam_link_info": "Poštni predal je povezan s tem uporabnikom IdP. Če je bil naslov dodeljen drugemu uporabniku IdP, povezavo odstranite; ponovna povezava se vzpostavi prek e-pošte.",
"iam_missing_since": "Manjka v IdP od",
"iam_not_linked": "Še ni povezano",
"iam_unlink": "Odstrani povezavo"
}, },
"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.",
@@ -896,7 +1043,7 @@
"mailbox_defaults": "Privzete nastavitve", "mailbox_defaults": "Privzete nastavitve",
"mailbox_defquota": "Privzeta velikost poštnega nabiralnika", "mailbox_defquota": "Privzeta velikost poštnega nabiralnika",
"mailbox_templates": "Predloge poštnih nabiralnikov", "mailbox_templates": "Predloge poštnih nabiralnikov",
"mailbox_quota": "Največja velikost poštnega nabiralnika", "mailbox_quota": "Največja velikost poštnega predala",
"mailboxes": "Poštni nabiralniki", "mailboxes": "Poštni nabiralniki",
"max_aliases": "Največje število vzdevkov", "max_aliases": "Največje število vzdevkov",
"max_mailboxes": "Največje možno število poštnih predalov", "max_mailboxes": "Največje možno število poštnih predalov",
@@ -971,7 +1118,9 @@
"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" "force_tfa": "TFA",
"shared_mailbox": "Skupni poštni predal",
"shared_mailbox_info": "Nima lastne prijave (IMAP, SMTP, SOGo, ActiveSync, gesla za aplikacije, uporabniški vmesnik mailcow). Prejema e-pošto, člani skupin pa do nje dostopajo prek deljenih map. Geslo ni potrebno."
}, },
"fido2": { "fido2": {
"known_ids": "Znani ID-ji", "known_ids": "Znani ID-ji",
@@ -1172,7 +1321,21 @@
"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" "ui_theme_modified": "Tema je bila uspešno shranjena",
"api_key_added": "Ključ API %s je bil dodan",
"api_key_deleted": "Ključ API %s je bil izbrisan",
"api_key_modified": "Spremembe ključa API %s so bile shranjene",
"group_added": "Skupina %s je bila dodana",
"group_modified": "Spremembe skupine %s so bile shranjene",
"group_removed": "Skupina %s je bila odstranjena",
"iam_link_removed": "Povezava IdP za %s je bila odstranjena",
"iam_sync_requested": "Postopek sinhronizacije je v čakalni vrsti; začel se bo v eni minuti.",
"imapsync_source_added": "Vir sinhronizacije %s je bil dodan",
"imapsync_source_modified": "Vir sinhronizacije %s je bil shranjen",
"imapsync_source_deleted": "Vir sinhronizacije %s je bil izbrisan",
"imapsync_source_token_refreshed": "Žeton OAuth za %s je bil osvežen",
"max_parallel_saved": "Nastavitve opravila sinhronizacije so shranjene",
"imapsync_run_next_done": "%s opravilo(-a) za sinhronizacijo premaknjeno(-a) na začetek čakalne vrste"
}, },
"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.",
@@ -1209,7 +1372,8 @@
"force_tfa_info": "Uporabnik bo moral pred dostopom do nadzorne plošče nastaviti dvofaktorsko overjanje.", "force_tfa_info": "Uporabnik bo moral pred dostopom do nadzorne plošče nastaviti dvofaktorsko overjanje.",
"setup_title": "Zahtevana je dvofaktorska avtentikacija", "setup_title": "Zahtevana je dvofaktorska avtentikacija",
"setup_required": "Vaš račun zahteva dvofaktorsko overjanje. Za nadaljevanje nastavite metodo dvofaktorske overitve.", "setup_required": "Vaš račun zahteva dvofaktorsko overjanje. Za nadaljevanje nastavite metodo dvofaktorske overitve.",
"cancel_setup": "Prekliči in se odjavi" "cancel_setup": "Prekliči in se odjavi",
"webauthn_registration_failed": "Registracija WebAuthn ni uspela. Verodajnice ni bilo mogoče preveriti; poskusite znova."
}, },
"ratelimit": { "ratelimit": {
"disabled": "Onemogočeno", "disabled": "Onemogočeno",
@@ -1247,7 +1411,103 @@
"timeout1": "Časovna omejitev za povezavo do oddaljenega gostitelja", "timeout1": "Časovna omejitev za povezavo do oddaljenega gostitelja",
"timeout2": "Časovna omejitev za povezavo do lokalnega gostitelja", "timeout2": "Časovna omejitev za povezavo do lokalnega gostitelja",
"maxbytespersecond": "Največ bajtov na sekundo <br><small>(0 = neomejeno)</small>", "maxbytespersecond": "Največ bajtov na sekundo <br><small>(0 = neomejeno)</small>",
"custom_params": "Parametri po meri" "custom_params": "Parametri po meri",
"sources_panel": "Viri za sinhronizacijo",
"sources_hint": "Viri sinhronizacije določajo strežnike IMAP (in način njihove overitve), ki jih je mogoče ponovno uporabiti pri opravilih sinhronizacije. Vsak vir določite enkrat, nato pa ga pri ustvarjanju opravil sinhronizacije izberite s spustnega seznama.",
"source_add": "Dodaj vir",
"source": "Vir",
"source_name": "Ime",
"source_description": "Opis",
"source_scope": "Vidljivost",
"source_scope_all": "Vsi uporabniki",
"source_scope_domain": "Specifične domene",
"source_scope_user": "Specifični uporabniki",
"source_domains": "Domene",
"source_users": "Uporabniki",
"source_select": "Izberi vir",
"source_auth_type": "Overjanje",
"source_oauth_block": "Konfiguracija OAuth2",
"source_token_status": "Žeton veljaven do",
"source_token_error": "Napaka pri zadnji osvežitvi",
"source_refresh_now": "Osveži žeton zdaj",
"source_secret_unchanged_hint": "Pustite prazno, da ohranite shranjeno skrivnost.",
"source_oauth_flow": "Postopek OAuth2",
"source_oauth_flow_app": "Aplikacija (poverilnice odjemalca)",
"source_oauth_flow_user": "Prijava uporabnika (koda za avtorizacijo)",
"source_flow_badge_app": "Poverilnice stranke",
"source_flow_badge_user": "Prijava uporabnika",
"token_state_issued": "Žeton izdan",
"token_state_pending": "Žeton v čakanju",
"token_state_failed": "Žeton ni uspel",
"source_authorize_endpoint": "Avtorizacija končne točke",
"source_userinfo_endpoint": "Končna točka za podatke o uporabniku",
"source_redirect_uri": "URI za preusmeritev",
"source_redirect_uri_hint": "Registrirajte ta URI v aplikaciji ponudnika (npr. Azure). Prijava prek ponudnika deluje le, če se mailcow odpre prek tega imena gostitelja.",
"syncjob_oauth_connect": "Prijavite se s ponudnikom",
"syncjob_oauth_connected": "Povezano",
"syncjob_OAUTH_TOKEN_NOT_READY": "Žeton OAuth ni pripravljen (znova se prijavite ali počakajte na osvežitev žetona)",
"custom_params_hint": "V vsako vrstico vnesite eno možnost za imapsync. Sprejete so le možnosti s seznama dovoljenih; vrednost lahko vsebuje poljubne znake (presledke, regularne izraze itd.) in se posreduje kot en sam argument.",
"custom_param_option": "Možnost",
"custom_param_value": "Vrednost",
"custom_param_no_value": "vrednost ni potrebna",
"custom_param_add": "Dodaj parameter",
"prio": "Prednostna naloga",
"prio_hint": "Opravila z višjo prioriteto se izvedejo prva. Znotraj iste prioritete imajo prednost opravila, ki še nikoli niso bila izvedena, sledijo jim tista, ki so bila izvedena najmanj nedavno (0 = običajno).",
"oauth_popup_title": "Prijavite se s ponudnikom",
"oauth_popup_success": "Povezano. To okno lahko zaprete.",
"oauth_popup_failed": "Prijava ni uspela. To okno lahko zaprete.",
"oauth_error_access_denied": "Nimate dovoljenja za upravljanje opravil sinhronizacije",
"oauth_error_mailbox_denied": "Ni dostopa do izbranega lokalnega poštnega predala",
"oauth_error_source_unavailable": "Vir sinhronizacije ni na voljo ali pa ni konfiguriran za prijavo prek ponudnika",
"oauth_error_provider_denied": "Ponudnik je zavrnil prijavo",
"oauth_error_invalid_state": "Seja za prijavo je potekla ali pa ni veljavna; poskusite znova",
"oauth_error_token_failed": "Ponudnik ni izdal žetona (preverite ID odjemalca, skrivnost in URI za preusmeritev)",
"oauth_error_no_identity": "Ponudnik ni vrnil e-poštnega naslova za prijavljeni račun",
"user2_label": "Lokalni poštni predal (cilj)",
"user1_label": "Uporabniško ime na oddaljeni strani (vir)",
"password1_label": "Geslo za oddaljeni dostop (vir)",
"no_sources": "Na voljo ni vira za sinhronizacijo. Najprej dodajte vir za sinhronizacijo.",
"source_list_error": "Virov za sinhronizacijo ni bilo mogoče naložiti",
"source_not_visible": "(ni vidno)",
"oauth_waiting": "Čakanje na ponudnika…",
"oauth_popup_blocked": "Okno za prijavo je bilo blokirano. Za to spletno mesto omogočite pojavna okna in poskusite znova.",
"oauth_select_mailbox": "Najprej izberite lokalni poštni predal",
"oauth_error_unknown": "Prijava ni uspela",
"oauth_hint": "Oddaljeno uporabniško ime se pridobi iz računa, ki je bil uporabljen za prijavo pri ponudniku.",
"token_state_none": "Žeton v čakanju",
"custom_params_legacy": "Podedovani parametri (niso bili preneseni)",
"custom_params_legacy_hint": "Te parametre po meri iz prvotnega opravila sinhronizacije ni bilo mogoče pretvoriti, zato so prezrti. Možnosti, ki jih še vedno potrebujete, dodajte z zgornjim urejevalnikom parametrov.",
"custom_params_legacy_badge": "Podedovani parametri",
"custom_param_remove": "Odstrani parameter",
"never_run": "Nikoli",
"status_inactive": "Neaktivno",
"source_server": "Izvorni strežnik",
"id": "ID",
"source_created_by": "Ustvaril/a",
"source_oauth_token_endpoint": "Končna točka za žetone",
"source_oauth_client_id": "ID odjemalca",
"source_oauth_client_secret": "Skrivnost odjemalca",
"source_oauth_scope": "Obseg",
"source_oauth_extra_params": "Dodatni parametri (JSON)",
"source_oauth_extra_params_hint": "Izbirni objekt JSON. Parametri prompt, access_type, login_hint, domain_hint in hd se pošljejo ob prijavi pri ponudniku, vsi ostali pa ob zahtevku za žeton, npr. {\"vir\": \"https://provider.example.com\"}",
"source_presets": "Prednastavitve",
"source_presets_hint": "Izpolni nastavitve strežnika ponudnika. Številni ponudniki (npr. Gmail, iCloud, Yahoo) zahtevajo uporabo gesla za aplikacijo ali omogočanje dostopa prek protokola IMAP v nastavitvah poštnega predala.",
"source_tenant": "Najemnik Microsoft 365",
"source_tenant_hint": "ID imenika (najemnika) ali domena najemnika, npr. contoso.onmicrosoft.com. Vstavi se v končne točke.",
"source_scope_cc_hint": "Viri, ki uporabljajo tok poverilnic odjemalca, so vedno zasebni za svojega ustvarjalca.",
"source_readonly_hint": "Ta vir sinhronizacije je v skupni rabi z vami in ga je mogoče le ogledati.",
"source_verify_cert": "Preverite potrdilo strežnika",
"source_verify_cert_hint": "Za SSL in STARTTLS. Izklopite za strežnike s samopodpisanim ali poteklim potrdilom oziroma s potrdilom, ki se ne ujema z gostiteljem.",
"source_verify_cert_off": "Potrdilo strežnika ni preverjeno",
"copy": "Kopiraj",
"copied": "Kopirano v odložišče",
"source_view": "Prikaži",
"custom_params_options_error": "Seznama dovoljenih možnosti za imapsync ni bilo mogoče naložiti",
"source_missing": "Temu opravilu ni dodeljen vir za sinhronizacijo. Prosimo, izberite vir.",
"oauth_window_closed": "Okno za prijavo se je zaprlo, še preden je prispel rezultat. Če ste postopek prijave zaključili, ponovno naložite stran ali poskusite znova.",
"custom_params_legacy_badge_title": "Stare parametre po meri, ki jih ni bilo mogoče pretvoriti, se prezre. Za podrobnosti odprite stran za urejanje.",
"status_waiting": "Čakanje",
"status_running": "V teku"
}, },
"user": { "user": {
"open_webmail_sso": "Spletna pošta", "open_webmail_sso": "Spletna pošta",
@@ -1424,6 +1684,62 @@
"no_active_admin": "Zadnjega aktivnega skrbnika ni mogoče deaktivirati", "no_active_admin": "Zadnjega aktivnega skrbnika ni mogoče deaktivirati",
"quota_exceeded_scope": "Kvota domene presežena: V tem obsegu domene je mogoče ustvariti le neomejeno število poštnih predalov.", "quota_exceeded_scope": "Kvota domene presežena: V tem obsegu domene je mogoče ustvariti le neomejeno število poštnih predalov.",
"session_token": "Neveljaven žeton obrazca: Neujemanje žetonov", "session_token": "Neveljaven žeton obrazca: Neujemanje žetonov",
"session_ua": "Neveljaven žeton obrazca: Napaka pri preverjanju uporabniškega agenta" "session_ua": "Neveljaven žeton obrazca: Napaka pri preverjanju uporabniškega agenta",
"group_imap_grant_failed": "Dostopa do poštnega predala %s v sistemu Dovecot še ni bilo mogoče nastaviti; ponovni poskus bo izveden vsakih 15 minut",
"group_sogo_init_failed": "Map SOGo za %s še ni bilo mogoče ustvariti; ponovni poskus bo izveden vsakih 15 minut",
"group_subscription_failed": "Naročnina za %s ni uspela; poskus se samodejno se ponovi",
"group_template_failed": "Predloge %s ni bilo mogoče uporabiti na %s",
"group_too_many_members": "Skupina %s ima več kot %s članov. Postfix dostavi pošto na naslov, ki ima največ toliko prejemnikov."
},
"groups": {
"active": "Aktivno",
"add": "Dodaj skupino",
"address": "Skupinski naslov",
"address_hint": "Neobvezno. Pošta na ta naslov se dostavi vsem članom.",
"description": "Opis",
"domain": "Domena",
"effective_template": "Predloga iz skupin",
"grant_add": "Dodaj",
"grant_folder": "ID mape v SOGo, osebna = privzeta mapa",
"grant_owner": "Poštni nabiralnik",
"grant_state_applied": "Aktivno",
"grant_state_failed": "Neuspešno",
"grant_state_pending": "V čakanju",
"grant_subscribe": "Naroči se",
"grant_subscribe_imap_hint": "V roku ene minute naroči mape poštnega predala za vsakega člana, tako da so te vidne v e-poštnih odjemalcih. Pomembno: če se član odjavi, to stanje ostane nespremenjeno",
"grant_subscribe_sogo_hint": "Mapa se v roku ene minute doda v SOGo vsakega člana – vendar le enkrat: če jo član odstrani, ostane odstranjena.",
"group": "Skupina",
"groups": "Skupine",
"idp": "IdP",
"idp_member": "Sinhronizirano iz ponudnika identitet; spremeniti je mogoče le tam",
"idp_ref": "Skupina IdP",
"imap_grants": "Dostop do poštnega predala",
"imap_grants_hint": "Člani odprejo te poštne nabiralnike v svojem poštnem odjemalcu (mapa V skupni rabi/<poštni nabiralnik>). Branje: branje in označevanje kot prebrano. Pisanje: tudi shranjevanje, premikanje in brisanje e-pošte. Polni dostop: tudi brisanje map. Za poštne nabiralnike ekipe brez lastne prijave označite nabiralnik kot skupni nabiralnik.",
"info": "Skupine združujejo nabiralnike. Skupina ima lahko svoj naslov: vzdevek, ki samodejno dostavlja vsem članom. Člani lahko pošiljajo kot drugi naslovi, dostopajo do skupnih nabiralnikov, koledarjev in imenikov ter dobijo predlogo nabiralnika. Člani, sinhronizirani s ponudnikom identitete, so označeni z IdP.",
"internal": "Sprejemaj pošto samo od pošiljateljev iz domene",
"list_error": "Skupin ni bilo mogoče naložiti",
"managed_alias": "Upravlja skupina %s",
"members": "Člani",
"memberships": "Članstva v skupinah",
"name": "Ime",
"no_memberships": "Ni član nobene skupine",
"priority": "Prednostna naloga",
"reapply_template": "Ponovno uporabite predlogo za vse člane",
"rights_full": "Popoln dostop",
"rights_read": "Beri",
"rights_write": "Piši",
"send_as": "Pošlji kot",
"send_as_groups": "Lahko pošlje kot (prek skupin)",
"send_as_hint": "Naslovi (poštni predal ali vzdevek) ali @domena, v imenu katerih lahko člani pošiljajo sporočila – en naslov v vsaki vrstici.",
"sender_allowed": "Člani lahko pošiljajo prek naslova skupine",
"sogo_grants": "Koledarji in imeniki",
"sogo_grants_hint": "Člani dobijo dostop do mape SOGo v poštnem predalu. Z izbiro možnosti »Naroči se« (Subscribe) se mapa samodejno prikaže v njihovem vmesniku SOGo; v nasprotnem primeru se morajo nanjo naročiti sami. Na čakanju: poštni predal še ni odprl vmesnika SOGo.",
"sogo_visible": "Prikaži naslov skupine kot identiteto v SOGo",
"template": "Predloga poštnega predala",
"template_hint": "Velja za vse člane. Če je poštni predal vključen v več skupin, prevzame predlogo skupine z najvišjo prioriteto.",
"template_none": "Brez predloge",
"type_calendar": "Koledar",
"type_contacts": "Imenik",
"view": "Pogled"
} }
} }
+17 -11
View File
@@ -416,18 +416,24 @@ function recursiveBase64StrToArrayBuffer(obj) {
return navigator.credentials.create(json); return navigator.credentials.create(json);
}).then(function(cred) { }).then(function(cred) {
return { return {
id: cred.id, clientDataJSON: arrayBufferToBase64(cred.response.clientDataJSON),
rawId: arrayBufferToBase64(cred.rawId), attestationObject: arrayBufferToBase64(cred.response.attestationObject),
response: { key_id: keyId,
attestationObject: arrayBufferToBase64(cred.response.attestationObject), tfa_method: 'webauthn',
clientDataJSON: arrayBufferToBase64(cred.response.clientDataJSON) confirm_password: ''
},
type: cred.type
}; };
}).then(function(credData) { }).then(JSON.stringify).then(function(credentialData) {
$('#setup_webauthn_register_data').val(JSON.stringify(credData)); // send the attestation to the JSON API, like the regular WebAuthn modal does,
$('#setup_webauthn_reg_form input[name=set_tfa]').val('1'); // so that the credential is parsed and stored correctly
$('#setup_webauthn_reg_form').submit(); return window.fetch('/api/v1/add/webauthn-tfa-registration', {method: 'POST', body: credentialData, cache: 'no-cache'});
}).then(function(response) {
return response.json();
}).then(function(json) {
if (json.success) {
window.location = window.location.href.split('#')[0];
} else {
throw new Error(json.msg);
}
}).catch(function(err) { }).catch(function(err) {
$('#setup_webauthn_return_code').show().text(err.message || 'Registration failed'); $('#setup_webauthn_return_code').show().text(err.message || 'Registration failed');
}); });
+5 -3
View File
@@ -97,9 +97,11 @@
<div class="col-sm-10"> <div class="col-sm-10">
<select data-live-search="true" data-width="100%" style="width:100%" id="editSelectSenderACL" name="sender_acl" size="10" multiple> <select data-live-search="true" data-width="100%" style="width:100%" id="editSelectSenderACL" name="sender_acl" size="10" multiple>
{% for domain in sender_acl_handles.sender_acl_domains.ro %} {% for domain in sender_acl_handles.sender_acl_domains.ro %}
<option data-subtext="Admin" value="{{ domain }}" disabled selected> {% if domain == '*' or mailcow_cc_role == 'admin' %}
{{ lang.edit.dont_check_sender_acl|format(domain) }} <option data-subtext="Admin" value="{{ domain }}" disabled selected>
</option> {{ lang.edit.dont_check_sender_acl|format(domain) }}
</option>
{% endif %}
{% endfor %} {% endfor %}
{% for domain in sender_acl_handles.sender_acl_domains.rw %} {% for domain in sender_acl_handles.sender_acl_domains.rw %}
<option value="{{ domain }}" selected> <option value="{{ domain }}" selected>
+5 -5
View File
@@ -68,7 +68,7 @@ services:
- redis - redis
clamd-mailcow: clamd-mailcow:
image: ghcr.io/mailcow/clamd:1.4.6-1 image: ghcr.io/mailcow/clamd:1.4.6-2
restart: always restart: always
depends_on: depends_on:
unbound-mailcow: unbound-mailcow:
@@ -348,7 +348,7 @@ services:
- dovecot - dovecot
postfix-mailcow: postfix-mailcow:
image: ghcr.io/mailcow/postfix:3.10.12-1 image: ghcr.io/mailcow/postfix:3.10.13-1
depends_on: depends_on:
mysql-mailcow: mysql-mailcow:
condition: service_started condition: service_started
@@ -391,7 +391,7 @@ services:
- postfix - postfix
postfix-tlspol-mailcow: postfix-tlspol-mailcow:
image: ghcr.io/mailcow/postfix-tlspol:1.14.0 image: ghcr.io/mailcow/postfix-tlspol:1.14.5
depends_on: depends_on:
unbound-mailcow: unbound-mailcow:
condition: service_healthy condition: service_healthy
@@ -428,7 +428,7 @@ services:
- php-fpm-mailcow - php-fpm-mailcow
- sogo-mailcow - sogo-mailcow
- rspamd-mailcow - rspamd-mailcow
image: ghcr.io/mailcow/nginx:1.30.6-2 image: ghcr.io/mailcow/nginx:1.30.6-3
dns: dns:
- ${IPV4_NETWORK:-172.22.1}.254 - ${IPV4_NETWORK:-172.22.1}.254
environment: environment:
@@ -612,7 +612,7 @@ services:
- watchdog - watchdog
dockerapi-mailcow: dockerapi-mailcow:
image: ghcr.io/mailcow/dockerapi:2.15 image: ghcr.io/mailcow/dockerapi:2.16
security_opt: security_opt:
- label=disable - label=disable
restart: always restart: always
+26 -15
View File
@@ -18,6 +18,18 @@ if [ ! -f "${PWD}/mailcow.conf" ]; then
fi fi
fi fi
BRANCH="$(cd "${SCRIPT_DIR}" && git rev-parse --abbrev-ref HEAD)" BRANCH="$(cd "${SCRIPT_DIR}" && git rev-parse --abbrev-ref HEAD)"
UPDATE_ARGS=("$@")
SCRIPT_HASH="$(sha256sum "${SCRIPT_DIR}/update.sh" | awk '{print $1}')"
restart_update_script() {
if [[ "${MAILCOW_UPDATE_RESTARTS:-0}" -ge 3 ]]; then
echo -e "\e[31mupdate.sh keeps changing, please run it again.\e[0m"
exit 2
fi
export MAILCOW_UPDATE_RESTARTS=$(( ${MAILCOW_UPDATE_RESTARTS:-0} + 1 ))
echo -e "\e[33m$1, restarting the update script...\e[0m"
exec "${SCRIPT_DIR}/update.sh" "${UPDATE_ARGS[@]}"
}
# Check for --dev flag early to skip _modules update # Check for --dev flag early to skip _modules update
for arg in "$@"; do for arg in "$@"; do
@@ -38,9 +50,9 @@ if [ ! "$DEV" ]; then
MODULES_HASH_BEFORE="EMPTY" MODULES_HASH_BEFORE="EMPTY"
fi fi
echo -e "\e[33mFetching latest _modules from origin/${BRANCH}…\e[0m" echo -e "\e[33mFetching latest _modules and update.sh from origin/${BRANCH}…\e[0m"
git fetch origin "${BRANCH}" git fetch origin "${BRANCH}"
git checkout "origin/${BRANCH}" -- _modules git checkout "origin/${BRANCH}" -- _modules update.sh
if [[ ! -d "${MODULE_DIR}" || -z "$(ls -A "${MODULE_DIR}")" ]]; then if [[ ! -d "${MODULE_DIR}" || -z "$(ls -A "${MODULE_DIR}")" ]]; then
echo -e "\e[31mError: _modules is still missing or empty after fetch!\e[0m" echo -e "\e[31mError: _modules is still missing or empty after fetch!\e[0m"
@@ -50,10 +62,10 @@ if [ ! "$DEV" ]; then
# Calculate hash after fetch # Calculate hash after fetch
MODULES_HASH_AFTER=$(find "${MODULE_DIR}" -type f -exec sha256sum {} \; 2>/dev/null | sort | sha256sum | awk '{print $1}') MODULES_HASH_AFTER=$(find "${MODULE_DIR}" -type f -exec sha256sum {} \; 2>/dev/null | sort | sha256sum | awk '{print $1}')
# Check if modules changed # Check if modules or the script changed
if [[ "${MODULES_HASH_BEFORE}" != "${MODULES_HASH_AFTER}" ]]; then if [[ "${MODULES_HASH_BEFORE}" != "${MODULES_HASH_AFTER}" || "$(sha256sum "${SCRIPT_DIR}/update.sh" | awk '{print $1}')" != "${SCRIPT_HASH}" ]]; then
echo -e "\e[33m_modules have been updated. Please restart the update script.\e[0m" chmod +x "${SCRIPT_DIR}/update.sh"
exit 2 restart_update_script "update.sh or _modules have been updated"
fi fi
else else
echo -e "\e[33mDeveloper mode: Skipping _modules update from git\e[0m" echo -e "\e[33mDeveloper mode: Skipping _modules update from git\e[0m"
@@ -344,15 +356,12 @@ elif [ "$NEW_BRANCH" == "legacy" ] && [ "$CURRENT_BRANCH" != "legacy" ]; then
fi fi
if [ ! "$DEV" ]; then if [ ! "$DEV" ]; then
EXIT_COUNT=0
echo -e "\e[32mChecking for newer update script...\e[0m" echo -e "\e[32mChecking for newer update script...\e[0m"
SHA1_1="$(sha1sum update.sh)"
git fetch origin git fetch origin
git checkout "origin/${BRANCH}" -- update.sh git checkout "origin/${BRANCH}" -- update.sh
SHA1_2="$(sha1sum update.sh)" if [[ "$(sha256sum update.sh | awk '{print $1}')" != "${SCRIPT_HASH}" ]]; then
if [[ "${SHA1_1}" != "${SHA1_2}" ]]; then
chmod +x update.sh chmod +x update.sh
EXIT_COUNT+=1 restart_update_script "update.sh has been updated"
fi fi
fi fi
@@ -374,10 +383,12 @@ if ! $COMPOSE_COMMAND config -q; then
fi fi
echo -e "\e[32mChecking for conflicting bridges...\e[0m" echo -e "\e[32mChecking for conflicting bridges...\e[0m"
MAILCOW_BRIDGE=$($COMPOSE_COMMAND config | grep -i com.docker.network.bridge.name | cut -d':' -f2) MAILCOW_BRIDGE=$($COMPOSE_COMMAND config | grep -i com.docker.network.bridge.name | cut -d':' -f2 | tr -d "[:space:]\"'")
while read NAT_ID; do if [[ -n "${MAILCOW_BRIDGE}" ]]; then
iptables -t nat -D POSTROUTING "$NAT_ID" while read NAT_ID; do
done < <(iptables -L -vn -t nat --line-numbers | grep "$IPV4_NETWORK" | grep -E 'MASQUERADE.*all' | grep -v "${MAILCOW_BRIDGE}" | cut -d' ' -f1) iptables -t nat -D POSTROUTING "$NAT_ID"
done < <(iptables -L -vn -t nat --line-numbers | grep "$IPV4_NETWORK" | grep -E 'MASQUERADE.*all' | grep -vE "[[:space:]]!?${MAILCOW_BRIDGE}[[:space:]]" | cut -d' ' -f1)
fi
DIFF_DIRECTORY=update_diffs DIFF_DIRECTORY=update_diffs
DIFF_FILE=${DIFF_DIRECTORY}/diff_before_update_$(date +"%Y-%m-%d-%H-%M-%S") DIFF_FILE=${DIFF_DIRECTORY}/diff_before_update_$(date +"%Y-%m-%d-%H-%M-%S")