diff --git a/.github/workflows/close_old_issues_and_prs.yml b/.github/workflows/close_old_issues_and_prs.yml index 64002617..21ab3a8e 100644 --- a/.github/workflows/close_old_issues_and_prs.yml +++ b/.github/workflows/close_old_issues_and_prs.yml @@ -14,7 +14,7 @@ jobs: pull-requests: write steps: - name: Mark/Close Stale Issues and Pull Requests 🗑️ - uses: actions/stale@v7.0.0 + uses: actions/stale@v8.0.0 with: repo-token: ${{ secrets.STALE_ACTION_PAT }} days-before-stale: 60 diff --git a/data/Dockerfiles/dockerapi/dockerapi.py b/data/Dockerfiles/dockerapi/dockerapi.py index 9e699c22..6d3b1012 100644 --- a/data/Dockerfiles/dockerapi/dockerapi.py +++ b/data/Dockerfiles/dockerapi/dockerapi.py @@ -380,7 +380,12 @@ class DockerUtils: if 'maildir' in request_json: for container in self.docker_client.containers.list(filters={"id": container_id}): sane_name = re.sub(r'\W+', '', request_json['maildir']) - cmd = ["/bin/bash", "-c", "if [[ -d '/var/vmail/" + request_json['maildir'].replace("'", "'\\''") + "' ]]; then /bin/mv '/var/vmail/" + request_json['maildir'].replace("'", "'\\''") + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"] + vmail_name = request_json['maildir'].replace("'", "'\\''") + index_name = request_json['maildir'].split("/") + index_name = index_name[1].replace("'", "'\\''") + "@" + index_name[0].replace("'", "'\\''") + cmd_vmail = "if [[ -d '/var/vmail/" + vmail_name + "' ]]; then /bin/mv '/var/vmail/" + vmail_name + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi" + cmd_vmail_index = "if [[ -d '/var/vmail_index/" + index_name + "' ]]; then /bin/mv '/var/vmail_index/" + index_name + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "_index'; fi" + cmd = ["/bin/bash", "-c", cmd_vmail + " && " + cmd_vmail_index] maildir_cleanup = container.exec_run(cmd, user='vmail') return exec_run_handler('generic', maildir_cleanup) # api call: container_post - post_action: exec - cmd: rspamd - task: worker_password diff --git a/data/Dockerfiles/netfilter/server.py b/data/Dockerfiles/netfilter/server.py index 0b0e2a41..698137bf 100644 --- a/data/Dockerfiles/netfilter/server.py +++ b/data/Dockerfiles/netfilter/server.py @@ -64,28 +64,40 @@ def refreshF2boptions(): global f2boptions global quit_now global exit_code + + f2boptions = {} + if not r.get('F2B_OPTIONS'): - f2boptions = {} - f2boptions['ban_time'] = int - f2boptions['max_attempts'] = int - f2boptions['retry_window'] = int - f2boptions['netban_ipv4'] = int - f2boptions['netban_ipv6'] = int - f2boptions['ban_time'] = r.get('F2B_BAN_TIME') or 1800 - f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') or 10 - f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') or 600 - f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') or 32 - f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') or 128 - r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False)) + f2boptions['ban_time'] = r.get('F2B_BAN_TIME') + f2boptions['max_ban_time'] = r.get('F2B_MAX_BAN_TIME') + f2boptions['ban_time_increment'] = r.get('F2B_BAN_TIME_INCREMENT') + f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') + f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') + f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') + f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') else: try: - f2boptions = {} f2boptions = json.loads(r.get('F2B_OPTIONS')) except ValueError: print('Error loading F2B options: F2B_OPTIONS is not json') quit_now = True exit_code = 2 + verifyF2boptions(f2boptions) + r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False)) + +def verifyF2boptions(f2boptions): + verifyF2boption(f2boptions,'ban_time', 1800) + verifyF2boption(f2boptions,'max_ban_time', 10000) + verifyF2boption(f2boptions,'ban_time_increment', True) + verifyF2boption(f2boptions,'max_attempts', 10) + verifyF2boption(f2boptions,'retry_window', 600) + verifyF2boption(f2boptions,'netban_ipv4', 32) + verifyF2boption(f2boptions,'netban_ipv6', 128) + +def verifyF2boption(f2boptions, f2boption, f2bdefault): + f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault + def refreshF2bregex(): global f2bregex global quit_now @@ -147,6 +159,7 @@ def ban(address): global lock refreshF2boptions() BAN_TIME = int(f2boptions['ban_time']) + BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment']) MAX_ATTEMPTS = int(f2boptions['max_attempts']) RETRY_WINDOW = int(f2boptions['retry_window']) NETBAN_IPV4 = '/' + str(f2boptions['netban_ipv4']) @@ -174,20 +187,16 @@ def ban(address): net = ipaddress.ip_network((address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False) net = str(net) - if not net in bans or time.time() - bans[net]['last_attempt'] > RETRY_WINDOW: - bans[net] = { 'attempts': 0 } - active_window = RETRY_WINDOW - else: - active_window = time.time() - bans[net]['last_attempt'] + if not net in bans: + bans[net] = {'attempts': 0, 'last_attempt': 0, 'ban_counter': 0} bans[net]['attempts'] += 1 bans[net]['last_attempt'] = time.time() - active_window = time.time() - bans[net]['last_attempt'] - if bans[net]['attempts'] >= MAX_ATTEMPTS: cur_time = int(round(time.time())) - logCrit('Banning %s for %d minutes' % (net, BAN_TIME / 60)) + NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter'] + logCrit('Banning %s for %d minutes' % (net, NET_BAN_TIME / 60 )) if type(ip) is ipaddress.IPv4Address: with lock: chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW') @@ -206,7 +215,7 @@ def ban(address): rule.target = target if rule not in chain.rules: chain.insert_rule(rule) - r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + BAN_TIME) + r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME) else: logWarn('%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)) @@ -238,7 +247,8 @@ def unban(net): r.hdel('F2B_ACTIVE_BANS', '%s' % net) r.hdel('F2B_QUEUE_UNBAN', '%s' % net) if net in bans: - del bans[net] + bans[net]['attempts'] = 0 + bans[net]['ban_counter'] += 1 def permBan(net, unban=False): global lock @@ -332,7 +342,7 @@ def watch(): logWarn('%s matched rule id %s (%s)' % (addr, rule_id, item['data'])) ban(addr) except Exception as ex: - logWarn('Error reading log line from pubsub') + logWarn('Error reading log line from pubsub: %s' % ex) quit_now = True exit_code = 2 @@ -366,6 +376,8 @@ def snat4(snat_target): chain.insert_rule(new_rule) else: for position, rule in enumerate(chain.rules): + if not hasattr(rule.target, 'parameter'): + continue match = all(( new_rule.get_src() == rule.get_src(), new_rule.get_dst() == rule.get_dst(), @@ -425,6 +437,8 @@ def autopurge(): time.sleep(10) refreshF2boptions() BAN_TIME = int(f2boptions['ban_time']) + MAX_BAN_TIME = int(f2boptions['max_ban_time']) + BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment']) MAX_ATTEMPTS = int(f2boptions['max_attempts']) QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN') if QUEUE_UNBAN: @@ -432,7 +446,9 @@ def autopurge(): unban(str(net)) for net in bans.copy(): if bans[net]['attempts'] >= MAX_ATTEMPTS: - if time.time() - bans[net]['last_attempt'] > BAN_TIME: + NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter'] + TIME_SINCE_LAST_ATTEMPT = time.time() - bans[net]['last_attempt'] + if TIME_SINCE_LAST_ATTEMPT > NET_BAN_TIME or TIME_SINCE_LAST_ATTEMPT > MAX_BAN_TIME: unban(net) def isIpNetwork(address): diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index c8713e04..0ff47206 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.1-fpm-alpine3.17 +FROM php:8.2-fpm-alpine3.17 LABEL maintainer "Andre Peters " # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced @@ -12,7 +12,7 @@ ARG MEMCACHED_PECL_VERSION=3.2.0 # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced ARG REDIS_PECL_VERSION=5.3.7 # renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced -ARG COMPOSER_VERSION=2.5.4 +ARG COMPOSER_VERSION=2.5.5 RUN apk add -U --no-cache autoconf \ aspell-dev \ @@ -52,6 +52,7 @@ RUN apk add -U --no-cache autoconf \ libxpm-dev \ libzip \ libzip-dev \ + linux-headers \ make \ mysql-client \ openldap-dev \ @@ -75,7 +76,7 @@ RUN apk add -U --no-cache autoconf \ --with-webp \ --with-xpm \ --with-avif \ - && docker-php-ext-install -j 4 exif gd gettext intl ldap opcache pcntl pdo pdo_mysql pspell soap sockets zip bcmath gmp \ + && docker-php-ext-install -j 4 exif gd gettext intl ldap opcache pcntl pdo pdo_mysql pspell soap sockets sysvsem zip bcmath gmp \ && docker-php-ext-configure imap --with-imap --with-imap-ssl \ && docker-php-ext-install -j 4 imap \ && curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \ @@ -99,6 +100,7 @@ RUN apk add -U --no-cache autoconf \ libxml2-dev \ libxpm-dev \ libzip-dev \ + linux-headers \ make \ openldap-dev \ pcre-dev \ diff --git a/data/assets/nextcloud/nextcloud.conf b/data/assets/nextcloud/nextcloud.conf index 3755c4a7..eda2c779 100644 --- a/data/assets/nextcloud/nextcloud.conf +++ b/data/assets/nextcloud/nextcloud.conf @@ -24,7 +24,7 @@ server { add_header X-Download-Options "noopen" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Permitted-Cross-Domain-Policies "none" always; - add_header X-Robots-Tag "none" always; + add_header X-Robots-Tag "noindex, nofollow" always; add_header X-XSS-Protection "1; mode=block" always; fastcgi_hide_header X-Powered-By; diff --git a/data/conf/rspamd/local.d/composites.conf b/data/conf/rspamd/local.d/composites.conf index 337a2eb1..02ff955b 100644 --- a/data/conf/rspamd/local.d/composites.conf +++ b/data/conf/rspamd/local.d/composites.conf @@ -8,7 +8,7 @@ VIRUS_FOUND { } # Bad policy from free mail providers FREEMAIL_POLICY_FAILURE { - expression = "-g+:policies & !DMARC_POLICY_ALLOW & !MAILLIST & ( FREEMAIL_ENVFROM | FREEMAIL_FROM ) & !WHITELISTED_FWD_HOST"; + expression = "FREEMAIL_FROM & !DMARC_POLICY_ALLOW & !MAILLIST& !WHITELISTED_FWD_HOST & -g+:policies"; score = 16.0; } # Applies to freemail with undisclosed recipients diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index 97a34e9e..2c042c30 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -62,7 +62,7 @@ SOGoFirstDayOfWeek = "1"; SOGoSieveFolderEncoding = "UTF-8"; - SOGoPasswordChangeEnabled = YES; + SOGoPasswordChangeEnabled = NO; SOGoSentFolderName = "Sent"; SOGoMailShowSubscribedFoldersOnly = NO; NGImap4ConnectionStringSeparator = "/"; diff --git a/data/web/api/openapi.yaml b/data/web/api/openapi.yaml index 5e07c4b3..65bd1211 100644 --- a/data/web/api/openapi.yaml +++ b/data/web/api/openapi.yaml @@ -3176,8 +3176,10 @@ paths: example: attr: ban_time: "86400" + ban_time_increment: "1" blacklist: "10.100.6.5/32,10.100.8.4/32" max_attempts: "5" + max_ban_time: "86400" netban_ipv4: "24" netban_ipv6: "64" retry_window: "600" @@ -3191,11 +3193,17 @@ paths: description: the backlisted ips or hostnames separated by comma type: string ban_time: - description: the time a ip should be banned + description: the time an ip should be banned type: number + ban_time_increment: + description: if the time of the ban should increase each time + type: boolean max_attempts: description: the maximum numbe of wrong logins before a ip is banned type: number + max_ban_time: + description: the maximum time an ip should be banned + type: number netban_ipv4: description: the networks mask to ban for ipv4 type: number @@ -4113,10 +4121,12 @@ paths: response: value: ban_time: 604800 + ban_time_increment: 1 blacklist: |- 45.82.153.37/32 92.118.38.52/32 max_attempts: 1 + max_ban_time: 604800 netban_ipv4: 32 netban_ipv6: 128 perm_bans: diff --git a/data/web/inc/functions.fail2ban.inc.php b/data/web/inc/functions.fail2ban.inc.php index 2a7f11e8..2c4aa41d 100644 --- a/data/web/inc/functions.fail2ban.inc.php +++ b/data/web/inc/functions.fail2ban.inc.php @@ -239,7 +239,9 @@ function fail2ban($_action, $_data = null) { $is_now = fail2ban('get'); if (!empty($is_now)) { $ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']); + $ban_time_increment = (isset($_data['ban_time_increment']) && $_data['ban_time_increment'] == "1") ? 1 : 0; $max_attempts = intval((isset($_data['max_attempts'])) ? $_data['max_attempts'] : $is_now['max_attempts']); + $max_ban_time = intval((isset($_data['max_ban_time'])) ? $_data['max_ban_time'] : $is_now['max_ban_time']); $retry_window = intval((isset($_data['retry_window'])) ? $_data['retry_window'] : $is_now['retry_window']); $netban_ipv4 = intval((isset($_data['netban_ipv4'])) ? $_data['netban_ipv4'] : $is_now['netban_ipv4']); $netban_ipv6 = intval((isset($_data['netban_ipv6'])) ? $_data['netban_ipv6'] : $is_now['netban_ipv6']); @@ -256,6 +258,8 @@ function fail2ban($_action, $_data = null) { } $f2b_options = array(); $f2b_options['ban_time'] = ($ban_time < 60) ? 60 : $ban_time; + $f2b_options['ban_time_increment'] = ($ban_time_increment == 1) ? true : false; + $f2b_options['max_ban_time'] = ($max_ban_time < 60) ? 60 : $max_ban_time; $f2b_options['netban_ipv4'] = ($netban_ipv4 < 8) ? 8 : $netban_ipv4; $f2b_options['netban_ipv6'] = ($netban_ipv6 < 8) ? 8 : $netban_ipv6; $f2b_options['netban_ipv4'] = ($netban_ipv4 > 32) ? 32 : $netban_ipv4; diff --git a/data/web/js/site/debug.js b/data/web/js/site/debug.js index e0b9a5ab..55b6660b 100644 --- a/data/web/js/site/debug.js +++ b/data/web/js/site/debug.js @@ -1181,7 +1181,7 @@ jQuery(function($){ if (table = $('#' + log_table).DataTable()) { var heading = $('#' + log_table).closest('.card').find('.card-header'); - var load_rows = (table.page.len() + 1) + '-' + (table.page.len() + new_nrows) + var load_rows = (table.data().length + 1) + '-' + (table.data().length + new_nrows) $.get('/api/v1/get/logs/' + log_url + '/' + load_rows).then(function(data){ if (data.length === undefined) { mailcow_alert_box(lang.no_new_rows, "info"); return; } diff --git a/data/web/lang/lang.da-dk.json b/data/web/lang/lang.da-dk.json index 6ff8e9b7..5846181b 100644 --- a/data/web/lang/lang.da-dk.json +++ b/data/web/lang/lang.da-dk.json @@ -83,7 +83,7 @@ "private_comment": "Privat kommentar", "public_comment": "Offentlig kommentar", "quota_mb": "Kvota (Mb)", - "relay_all": "Send alle modtagere videre", + "relay_all": "Besvar alle modtager", "relay_all_info": "↪ Hvis du vælger ikke at videresende alle modtagere, skal du tilføje et (\"blind\") postkasse til hver enkelt modtager, der skal videresendes.", "relay_domain": "Send dette domæne videre", "relay_transport_info": "
Info
Du kan definere transportkort til en tilpasset destination for dette domæne. Hvis ikke indstillet, foretages der et MX-opslag.", @@ -104,7 +104,10 @@ "timeout2": "Timeout for forbindelse til lokal vært", "username": "Brugernavn", "validate": "Bekræft", - "validation_success": "Valideret med succes" + "validation_success": "Valideret med succes", + "bcc_dest_format": "BCC-destination skal være en enkelt gyldig e-mail-adresse.
Hvis du har brug for at sende en kopi til flere adresser, kan du oprette et alias og bruge det her.", + "app_passwd_protocols": "Tilladte protokoller for app adgangskode", + "tags": "Tag's" }, "admin": { "access": "Adgang", @@ -313,7 +316,8 @@ "verify": "Verificere", "yes": "✓", "ip_check_opt_in": "Opt-In for brug af tredjepartstjeneste ipv4.mailcow.email og ipv6.mailcow.email til at finde eksterne IP-adresser.", - "queue_unban": "unban" + "queue_unban": "unban", + "admins": "Administratorer" }, "danger": { "access_denied": "Adgang nægtet eller ugyldig formular data", @@ -1044,7 +1048,7 @@ "spamfilter_table_empty": "Intet data at vise", "spamfilter_table_remove": "slet", "spamfilter_table_rule": "Regl", - "spamfilter_wl": "Hvisliste", + "spamfilter_wl": "Hvidliste", "spamfilter_wl_desc": "Hvidlistede e-mail-adresser til aldrig at klassificeres som spam. Wildcards kan bruges. Et filter anvendes kun på direkte aliaser (aliaser med en enkelt målpostkasse) eksklusive catch-aliaser og selve en postkasse.", "spamfilter_yellow": "Gul: denne besked kan være spam, vil blive tagget som spam og flyttes til din junk-mappe", "status": "Status", diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index 8ff1cf06..4bd4b3fa 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -175,10 +175,12 @@ "empty": "Keine Einträge vorhanden", "excludes": "Diese Empfänger ausschließen", "f2b_ban_time": "Bannzeit in Sekunden", + "f2b_ban_time_increment": "Bannzeit erhöht sich mit jedem Bann", "f2b_blacklist": "Blacklist für Netzwerke und Hosts", "f2b_filter": "Regex-Filter", "f2b_list_info": "Ein Host oder Netzwerk auf der Blacklist wird immer eine Whitelist-Einheit überwiegen. Die Aktualisierung der Liste dauert einige Sekunden.", "f2b_max_attempts": "Max. Versuche", + "f2b_max_ban_time": "Maximale Bannzeit in Sekunden", "f2b_netban_ipv4": "Netzbereich für IPv4-Banns (8-32)", "f2b_netban_ipv6": "Netzbereich für IPv6-Banns (8-128)", "f2b_parameters": "Fail2ban-Parameter", diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index bfac011e..df83987c 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -177,10 +177,12 @@ "empty": "No results", "excludes": "Excludes these recipients", "f2b_ban_time": "Ban time (s)", + "f2b_ban_time_increment": "Ban time is incremented with each ban", "f2b_blacklist": "Blacklisted networks/hosts", "f2b_filter": "Regex filters", "f2b_list_info": "A blacklisted host or network will always outweigh a whitelist entity. List updates will take a few seconds to be applied.", "f2b_max_attempts": "Max. attempts", + "f2b_max_ban_time": "Max. ban time (s)", "f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)", "f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)", "f2b_parameters": "Fail2ban parameters", diff --git a/data/web/lang/lang.es-es.json b/data/web/lang/lang.es-es.json index d9c3bfd3..e56e6bdd 100644 --- a/data/web/lang/lang.es-es.json +++ b/data/web/lang/lang.es-es.json @@ -141,9 +141,11 @@ "empty": "Sin resultados", "excludes": "Excluye a estos destinatarios", "f2b_ban_time": "Tiempo de restricción (s)", + "f2b_ban_time_increment": "Tiempo de restricción se incrementa con cada restricción", "f2b_blacklist": "Redes y hosts en lista negra", "f2b_list_info": "Un host o red en lista negra siempre superará a una entidad de la lista blanca. Las actualizaciones de la lista tardarán unos segundos en aplicarse.", "f2b_max_attempts": "Max num. de intentos", + "f2b_max_ban_time": "Max tiempo de restricción (s)", "f2b_netban_ipv4": "Tamaño de subred IPv4 para aplicar la restricción (8-32)", "f2b_netban_ipv6": "Tamaño de subred IPv6 para aplicar la restricción (8-128)", "f2b_parameters": "Parametros Fail2ban", diff --git a/data/web/lang/lang.fr-fr.json b/data/web/lang/lang.fr-fr.json index 4db773e0..d64f62f7 100644 --- a/data/web/lang/lang.fr-fr.json +++ b/data/web/lang/lang.fr-fr.json @@ -24,7 +24,7 @@ "spam_policy": "Liste Noire/Liste Blanche", "spam_score": "Score SPAM", "syncjobs": "Tâches de synchronisation", - "tls_policy": "Police TLS", + "tls_policy": "Politique TLS", "unlimited_quota": "Quota illimité pour les boites de courriel", "domain_desc": "Modifier la description du domaine", "domain_relayhost": "Changer le relais pour un domaine", @@ -106,7 +106,8 @@ "validate": "Valider", "validation_success": "Validation réussie", "bcc_dest_format": "La destination Cci doit être une seule adresse e-mail valide.
Si vous avez besoin d'envoyer une copie à plusieurs adresses, créez un alias et utilisez-le ici.", - "tags": "Etiquettes" + "tags": "Etiquettes", + "app_passwd_protocols": "Protocoles autorisés pour le mot de passe de l'application" }, "admin": { "access": "Accès", @@ -171,11 +172,13 @@ "edit": "Editer", "empty": "Aucun résultat", "excludes": "Exclure ces destinataires", - "f2b_ban_time": "Durée du bannissement(s)", + "f2b_ban_time": "Durée du bannissement (s)", + "f2b_ban_time_increment": "Durée du bannissement est augmentée à chaque bannissement", "f2b_blacklist": "Réseaux/Domaines sur Liste Noire", "f2b_filter": "Filtre(s) Regex", "f2b_list_info": "Un hôte ou un réseau sur liste noire l'emportera toujours sur une entité de liste blanche. L'application des mises à jour de liste prendra quelques secondes.", "f2b_max_attempts": "Nb max. de tentatives", + "f2b_max_ban_time": "Max. durée du bannissement (s)", "f2b_netban_ipv4": "Taille du sous-réseau IPv4 pour l'application du bannissement (8-32)", "f2b_netban_ipv6": "Taille du sous-réseau IPv6 pour l'application du bannissement (8-128)", "f2b_parameters": "Paramètres Fail2ban", @@ -321,7 +324,9 @@ "admins": "Administrateurs", "api_read_only": "Accès lecture-seule", "password_policy_lowerupper": "Doit contenir des caractères minuscules et majuscules", - "password_policy_numbers": "Doit contenir au moins un chiffre" + "password_policy_numbers": "Doit contenir au moins un chiffre", + "ip_check": "Vérification IP", + "ip_check_disabled": "La vérification IP est désactivée. Vous pouvez l'activer sous
Système > Configuration > Options > Personnaliser" }, "danger": { "access_denied": "Accès refusé ou données de formulaire non valides", @@ -440,7 +445,12 @@ "username_invalid": "Le nom d'utilisateur %s ne peut pas être utilisé", "validity_missing": "Veuillez attribuer une période de validité", "value_missing": "Veuillez fournir toutes les valeurs", - "yotp_verification_failed": "La vérification Yubico OTP a échoué : %s" + "yotp_verification_failed": "La vérification Yubico OTP a échoué : %s", + "webauthn_authenticator_failed": "L'authentificateur selectionné est introuvable", + "demo_mode_enabled": "Le mode de démonstration est activé", + "template_exists": "La template %s existe déja", + "template_id_invalid": "Le numéro de template %s est invalide", + "template_name_invalid": "Le nom de la template est invalide" }, "debug": { "chart_this_server": "Graphique (ce serveur)", @@ -578,7 +588,7 @@ "unchanged_if_empty": "Si non modifié, laisser en blanc", "username": "Nom d'utilisateur", "validate_save": "Valider et sauver", - "lookup_mx": "La destination est une expression régulière qui doit correspondre avec le nom du MX (.*google\\.com pour acheminer tout le courrier destiné à un MX se terminant par google.com via ce saut).", + "lookup_mx": "La destination est une expression régulière qui doit correspondre avec le nom du MX (.*google\\.com pour acheminer tout le courrier destiné à un MX se terminant par google.com via ce saut)", "mailbox_relayhost_info": "S'applique uniquement à la boîte aux lettres et aux alias directs, remplace le relayhost du domaine." }, "footer": { @@ -1081,9 +1091,12 @@ "username": "Nom d'utilisateur", "verify": "Vérification", "waiting": "En attente", - "week": "Semaine", + "week": "semaine", "weekly": "Hebdomadaire", - "weeks": "semaines" + "weeks": "semaines", + "months": "mois", + "year": "année", + "years": "années" }, "warning": { "cannot_delete_self": "Impossible de supprimer l’utilisateur connecté", diff --git a/data/web/lang/lang.it-it.json b/data/web/lang/lang.it-it.json index d8d6978c..4d21547c 100644 --- a/data/web/lang/lang.it-it.json +++ b/data/web/lang/lang.it-it.json @@ -175,10 +175,12 @@ "empty": "Nessun risultato", "excludes": "Esclude questi destinatari", "f2b_ban_time": "Tempo di blocco (s)", + "f2b_ban_time_increment": "Tempo di blocco aumenta ad ogni blocco", "f2b_blacklist": "Host/reti in blacklist", "f2b_filter": "Filtri Regex", "f2b_list_info": "Un host oppure una rete in blacklist, avrà sempre un peso maggiore rispetto ad una in whitelist. L'aggiornamento della lista richiede alcuni secondi per la sua entrata in azione.", "f2b_max_attempts": "Tentativi massimi", + "f2b_max_ban_time": "Tempo massimo di blocco (s)", "f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)", "f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)", "f2b_parameters": "Parametri Fail2ban", diff --git a/data/web/lang/lang.nl-nl.json b/data/web/lang/lang.nl-nl.json index 774627ca..4c2ea0b1 100644 --- a/data/web/lang/lang.nl-nl.json +++ b/data/web/lang/lang.nl-nl.json @@ -168,10 +168,12 @@ "empty": "Geen resultaten", "excludes": "Exclusief", "f2b_ban_time": "Verbanningstijd (s)", + "f2b_ban_time_increment": "Verbanningstijd wordt verhoogd met elk verbanning", "f2b_blacklist": "Netwerken/hosts op de blacklist", "f2b_filter": "Regex-filters", "f2b_list_info": "Een host of netwerk op de blacklist staat altijd boven eenzelfde op de whitelist. Het doorvoeren van wijzigingen kan enkele seconden in beslag nemen.", "f2b_max_attempts": "Maximaal aantal pogingen", + "f2b_max_ban_time": "Maximaal verbanningstijd (s)", "f2b_netban_ipv4": "Voer de IPv4-subnetgrootte in waar de verbanning van kracht moet zijn (8-32)", "f2b_netban_ipv6": "Voer de IPv6-subnetgrootte in waar de verbanning van kracht moet zijn (8-128)", "f2b_parameters": "Fail2ban", diff --git a/data/web/templates/admin/tab-config-f2b.twig b/data/web/templates/admin/tab-config-f2b.twig index bbd3e367..c15fb72f 100644 --- a/data/web/templates/admin/tab-config-f2b.twig +++ b/data/web/templates/admin/tab-config-f2b.twig @@ -12,6 +12,14 @@ +
+ + +
+
+ + +
diff --git a/docker-compose.yml b/docker-compose.yml index 40d22ce0..a2d40cd8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -76,7 +76,7 @@ services: - clamd rspamd-mailcow: - image: mailcow/rspamd:1.92 + image: mailcow/rspamd:1.93 stop_grace_period: 30s depends_on: - dovecot-mailcow @@ -106,7 +106,7 @@ services: - rspamd php-fpm-mailcow: - image: mailcow/phpfpm:1.82 + image: mailcow/phpfpm:1.83 command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" depends_on: - redis-mailcow @@ -169,7 +169,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.115 + image: mailcow/sogo:1.116 environment: - DBNAME=${DBNAME} - DBUSER=${DBUSER} @@ -191,7 +191,7 @@ services: volumes: - ./data/hooks/sogo:/hooks:Z - ./data/conf/sogo/:/etc/sogo/:z - - ./data/web/inc/init_db.inc.php:/init_db.inc.php:Z + - ./data/web/inc/init_db.inc.php:/init_db.inc.php:z - ./data/conf/sogo/custom-favicon.ico:/usr/lib/GNUstep/SOGo/WebServerResources/img/sogo.ico:z - ./data/conf/sogo/custom-theme.js:/usr/lib/GNUstep/SOGo/WebServerResources/js/theme.js:z - ./data/conf/sogo/custom-sogo.js:/usr/lib/GNUstep/SOGo/WebServerResources/js/custom-sogo.js:z @@ -425,7 +425,7 @@ services: - acme netfilter-mailcow: - image: mailcow/netfilter:1.51 + image: mailcow/netfilter:1.52 stop_grace_period: 30s depends_on: - dovecot-mailcow @@ -510,7 +510,7 @@ services: - watchdog dockerapi-mailcow: - image: mailcow/dockerapi:2.01 + image: mailcow/dockerapi:2.02 security_opt: - label=disable restart: always diff --git a/generate_config.sh b/generate_config.sh index 89af0f64..23e7036b 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -205,8 +205,8 @@ DBUSER=mailcow # Please use long, random alphanumeric strings (A-Za-z0-9) -DBPASS=$(LC_ALL=C /dev/null | head -c 28) +DBROOT=$(LC_ALL=C /dev/null | head -c 28) # ------------------------------ # HTTP/S Bindings diff --git a/helper-scripts/mailcow-reset-admin.sh b/helper-scripts/mailcow-reset-admin.sh index ee95d3e9..ea8a4a43 100755 --- a/helper-scripts/mailcow-reset-admin.sh +++ b/helper-scripts/mailcow-reset-admin.sh @@ -19,7 +19,7 @@ read -r -p "Are you sure you want to reset the mailcow administrator account? [y response=${response,,} # tolower if [[ "$response" =~ ^(yes|y)$ ]]; then echo -e "\nWorking, please wait..." - random=$( /dev/null | head -c${1:-16}) password=$(docker exec -it $(docker ps -qf name=dovecot-mailcow) doveadm pw -s SSHA256 -p ${random} | tr -d '\r') docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM admin WHERE username='admin';" docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM domain_admins WHERE username='admin';" diff --git a/helper-scripts/nextcloud.sh b/helper-scripts/nextcloud.sh index 9377ddbb..71c9a73f 100755 --- a/helper-scripts/nextcloud.sh +++ b/helper-scripts/nextcloud.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # renovate: datasource=github-releases depName=nextcloud/server versioning=semver extractVersion=^v(?.*)$ -NEXTCLOUD_VERSION=25.0.4 +NEXTCLOUD_VERSION=26.0.0 echo -ne "Checking prerequisites..." sleep 1 @@ -122,7 +122,7 @@ elif [[ ${NC_INSTALL} == "y" ]]; then && chmod +x ./data/web/nextcloud/occ echo -e "\033[33mCreating 'nextcloud' database...\033[0m" - NC_DBPASS=$( /dev/null | head -c 28) NC_DBUSER=nextcloud NC_DBNAME=nextcloud @@ -138,7 +138,7 @@ elif [[ ${NC_INSTALL} == "y" ]]; then echo "" echo -e "\033[33mInstalling Nextcloud...\033[0m" - ADMIN_NC_PASS=$( /dev/null | head -c 28) echo -ne "[1/4] Setting correct permissions for www-data" docker exec -it $(docker ps -f name=php-fpm-mailcow -q) /bin/bash -c "chown -R www-data:www-data /web/nextcloud" diff --git a/update.sh b/update.sh index 34d17354..73ee975c 100755 --- a/update.sh +++ b/update.sh @@ -176,18 +176,19 @@ remove_obsolete_nginx_ports() { } detect_docker_compose_command(){ -if ! [ "${DOCKER_COMPOSE_VERSION}" == "native" ] && ! [ "${DOCKER_COMPOSE_VERSION}" == "standalone" ]; then +if ! [[ "${DOCKER_COMPOSE_VERSION}" =~ ^(native|standalone)$ ]]; then if docker compose > /dev/null 2>&1; then if docker compose version --short | grep "2." > /dev/null 2>&1; then DOCKER_COMPOSE_VERSION=native COMPOSE_COMMAND="docker compose" echo -e "\e[31mFound Docker Compose Plugin (native).\e[0m" echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m" + sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=native/' $SCRIPT_DIR/mailcow.conf sleep 2 echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m" else echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" - echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" exit 1 fi elif docker-compose > /dev/null 2>&1; then @@ -197,26 +198,60 @@ if ! [ "${DOCKER_COMPOSE_VERSION}" == "native" ] && ! [ "${DOCKER_COMPOSE_VERSIO COMPOSE_COMMAND="docker-compose" echo -e "\e[31mFound Docker Compose Standalone.\e[0m" echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m" + sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=standalone/' $SCRIPT_DIR/mailcow.conf sleep 2 echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m" else echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" - echo -e "\e[31mPlease update/install regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease update/install regarding to this doc site: https://docs.mailcow.email/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" exit 1 fi fi else echo -e "\e[31mCannot find Docker Compose.\e[0m" - echo -e "\e[31mPlease install it regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" exit 1 fi elif [ "${DOCKER_COMPOSE_VERSION}" == "native" ]; then COMPOSE_COMMAND="docker compose" + # Check if Native Compose works and has not been deleted + if ! $COMPOSE_COMMAND > /dev/null 2>&1; then + # IF it not exists/work anymore try the other command + COMPOSE_COMMAND="docker-compose" + if ! $COMPOSE_COMMAND > /dev/null 2>&1 || ! $COMPOSE_COMMAND --version | grep "^2." > /dev/null 2>&1; then + # IF it cannot find Standalone in > 2.X, then script stops + echo -e "\e[31mCannot find Docker Compose or the Version is lower then 2.X.X.\e[0m" + echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" + exit 1 + fi + # If it finds the standalone Plugin it will use this instead and change the mailcow.conf Variable accordingly + echo -e "\e[31mFound different Docker Compose Version then declared in mailcow.conf!\e[0m" + echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable from native to standalone\e[0m" + sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=standalone/' $SCRIPT_DIR/mailcow.conf + sleep 2 + fi + elif [ "${DOCKER_COMPOSE_VERSION}" == "standalone" ]; then COMPOSE_COMMAND="docker-compose" + # Check if Standalone Compose works and has not been deleted + if ! $COMPOSE_COMMAND > /dev/null 2>&1 && ! $COMPOSE_COMMAND --version > /dev/null 2>&1 | grep "^2." > /dev/null 2>&1; then + # IF it not exists/work anymore try the other command + COMPOSE_COMMAND="docker compose" + if ! $COMPOSE_COMMAND > /dev/null 2>&1; then + # IF it cannot find Native in > 2.X, then script stops + echo -e "\e[31mCannot find Docker Compose.\e[0m" + echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" + exit 1 + fi + # If it finds the native Plugin it will use this instead and change the mailcow.conf Variable accordingly + echo -e "\e[31mFound different Docker Compose Version then declared in mailcow.conf!\e[0m" + echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable from standalone to native\e[0m" + sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=native/' $SCRIPT_DIR/mailcow.conf + sleep 2 + fi fi } @@ -326,8 +361,12 @@ while (($#)); do echo -e "\e[32mRunning in forced mode...\e[0m" FORCE=y ;; + -d|--dev) + echo -e "\e[32mRunning in Developer mode...\e[0m" + DEV=y + ;; --help|-h) - echo './update.sh [-c|--check, --ours, --gc, --nightly, --prefetch, --skip-start, --skip-ping-check, --stable, -f|--force, -h|--help] + echo './update.sh [-c|--check, --ours, --gc, --nightly, --prefetch, --skip-start, --skip-ping-check, --stable, -f|--force, -d|--dev, -h|--help] -c|--check - Check for updates and exit (exit codes => 0: update available, 3: no updates) --ours - Use merge strategy option "ours" to solve conflicts in favor of non-mailcow code (local changes over remote changes), not recommended! @@ -338,6 +377,7 @@ while (($#)); do --skip-ping-check - Skip ICMP Check to public DNS resolvers (Use it only if you´ve blocked any ICMP Connections to your mailcow machine) --stable - Switch your mailcow updates to the stable (master) branch. Default unless you changed it with --nightly. -f|--force - Force update, do not ask questions + -d|--dev - Enables Developer Mode (No Checkout of update.sh for tests) ' exit 1 esac @@ -597,7 +637,7 @@ for option in ${CONFIG_ARRAY[@]}; do echo "Adding new option \"${option}\" to mailcow.conf" echo '# Password hash algorithm' >> mailcow.conf echo '# Only certain password hash algorithm are supported. For a fully list of supported schemes,' >> mailcow.conf - echo '# see https://mailcow.github.io/mailcow-dockerized-docs/models/model-passwd/' >> mailcow.conf + echo '# see https://docs.mailcow.email/models/model-passwd/' >> mailcow.conf echo "MAILCOW_PASS_SCHEME=BLF-CRYPT" >> mailcow.conf fi elif [[ ${option} == "ADDITIONAL_SERVER_NAMES" ]]; then @@ -617,7 +657,7 @@ for option in ${CONFIG_ARRAY[@]}; do echo '# Optional: Leave empty for none' >> mailcow.conf echo '# This value is only used on first order!' >> mailcow.conf echo '# Setting it at a later point will require the following steps:' >> mailcow.conf - echo '# https://mailcow.github.io/mailcow-dockerized-docs/troubleshooting/debug-reset_tls/' >> mailcow.conf + echo '# https://docs.mailcow.email/troubleshooting/debug-reset_tls/' >> mailcow.conf echo 'ACME_CONTACT=' >> mailcow.conf fi elif [[ ${option} == "WEBAUTHN_ONLY_TRUSTED_VENDORS" ]]; then @@ -727,15 +767,17 @@ elif [ $NEW_BRANCH == "nightly" ] && [ $CURRENT_BRANCH != "nightly" ]; then git checkout -f ${BRANCH} fi -echo -e "\e[32mChecking for newer update script...\e[0m" -SHA1_1=$(sha1sum update.sh) -git fetch origin #${BRANCH} -git checkout origin/${BRANCH} update.sh -SHA1_2=$(sha1sum update.sh) -if [[ ${SHA1_1} != ${SHA1_2} ]]; then - echo "update.sh changed, please run this script again, exiting." - chmod +x update.sh - exit 2 +if [ ! $DEV ]; then + echo -e "\e[32mChecking for newer update script...\e[0m" + SHA1_1=$(sha1sum update.sh) + git fetch origin #${BRANCH} + git checkout origin/${BRANCH} update.sh + SHA1_2=$(sha1sum update.sh) + if [[ ${SHA1_1} != ${SHA1_2} ]]; then + echo "update.sh changed, please run this script again, exiting." + chmod +x update.sh + exit 2 + fi fi if [ ! $FORCE ]; then @@ -902,9 +944,6 @@ else echo -e "\e[33mCannot determine current git repository version...\e[0m" fi -# Set DOCKER_COMPOSE_VERSION -sed -i 's/^DOCKER_COMPOSE_VERSION=$/DOCKER_COMPOSE_VERSION='$DOCKER_COMPOSE_VERSION'/g' mailcow.conf - if [[ ${SKIP_START} == "y" ]]; then echo -e "\e[33mNot starting mailcow, please run \"$COMPOSE_COMMAND up -d --remove-orphans\" to start mailcow.\e[0m" else