瀏覽代碼

Merge pull request #5162 from mailcow/staging

Update 2023-04
Niklas Meyer 2 年之前
父節點
當前提交
028ef22878

+ 1 - 1
.github/workflows/close_old_issues_and_prs.yml

@@ -14,7 +14,7 @@ jobs:
       pull-requests: write
       pull-requests: write
     steps:
     steps:
       - name: Mark/Close Stale Issues and Pull Requests 🗑️
       - name: Mark/Close Stale Issues and Pull Requests 🗑️
-        uses: actions/stale@v7.0.0
+        uses: actions/stale@v8.0.0
         with:
         with:
           repo-token: ${{ secrets.STALE_ACTION_PAT }}
           repo-token: ${{ secrets.STALE_ACTION_PAT }}
           days-before-stale: 60
           days-before-stale: 60

+ 6 - 1
data/Dockerfiles/dockerapi/dockerapi.py

@@ -380,7 +380,12 @@ class DockerUtils:
     if 'maildir' in request_json:
     if 'maildir' in request_json:
       for container in self.docker_client.containers.list(filters={"id": container_id}):
       for container in self.docker_client.containers.list(filters={"id": container_id}):
         sane_name = re.sub(r'\W+', '', request_json['maildir'])
         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')
         maildir_cleanup = container.exec_run(cmd, user='vmail')
         return exec_run_handler('generic', maildir_cleanup)
         return exec_run_handler('generic', maildir_cleanup)
   # api call: container_post - post_action: exec - cmd: rspamd - task: worker_password
   # api call: container_post - post_action: exec - cmd: rspamd - task: worker_password

+ 41 - 25
data/Dockerfiles/netfilter/server.py

@@ -64,28 +64,40 @@ def refreshF2boptions():
   global f2boptions
   global f2boptions
   global quit_now
   global quit_now
   global exit_code
   global exit_code
+
+  f2boptions = {}
+
   if not r.get('F2B_OPTIONS'):
   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:
   else:
     try:
     try:
-      f2boptions = {}
       f2boptions = json.loads(r.get('F2B_OPTIONS'))
       f2boptions = json.loads(r.get('F2B_OPTIONS'))
     except ValueError:
     except ValueError:
       print('Error loading F2B options: F2B_OPTIONS is not json')
       print('Error loading F2B options: F2B_OPTIONS is not json')
       quit_now = True
       quit_now = True
       exit_code = 2
       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():
 def refreshF2bregex():
   global f2bregex
   global f2bregex
   global quit_now
   global quit_now
@@ -147,6 +159,7 @@ def ban(address):
   global lock
   global lock
   refreshF2boptions()
   refreshF2boptions()
   BAN_TIME = int(f2boptions['ban_time'])
   BAN_TIME = int(f2boptions['ban_time'])
+  BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment'])
   MAX_ATTEMPTS = int(f2boptions['max_attempts'])
   MAX_ATTEMPTS = int(f2boptions['max_attempts'])
   RETRY_WINDOW = int(f2boptions['retry_window'])
   RETRY_WINDOW = int(f2boptions['retry_window'])
   NETBAN_IPV4 = '/' + str(f2boptions['netban_ipv4'])
   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 = ipaddress.ip_network((address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False)
   net = str(net)
   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]['attempts'] += 1
   bans[net]['last_attempt'] = time.time()
   bans[net]['last_attempt'] = time.time()
 
 
-  active_window = time.time() - bans[net]['last_attempt']
-
   if bans[net]['attempts'] >= MAX_ATTEMPTS:
   if bans[net]['attempts'] >= MAX_ATTEMPTS:
     cur_time = int(round(time.time()))
     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:
     if type(ip) is ipaddress.IPv4Address:
       with lock:
       with lock:
         chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW')
         chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW')
@@ -206,7 +215,7 @@ def ban(address):
         rule.target = target
         rule.target = target
         if rule not in chain.rules:
         if rule not in chain.rules:
           chain.insert_rule(rule)
           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:
   else:
     logWarn('%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net))
     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_ACTIVE_BANS', '%s' % net)
   r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
   r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
   if net in bans:
   if net in bans:
-    del bans[net]
+    bans[net]['attempts'] = 0
+    bans[net]['ban_counter'] += 1
 
 
 def permBan(net, unban=False):
 def permBan(net, unban=False):
   global lock
   global lock
@@ -332,7 +342,7 @@ def watch():
               logWarn('%s matched rule id %s (%s)' % (addr, rule_id, item['data']))
               logWarn('%s matched rule id %s (%s)' % (addr, rule_id, item['data']))
               ban(addr)
               ban(addr)
     except Exception as ex:
     except Exception as ex:
-      logWarn('Error reading log line from pubsub')
+      logWarn('Error reading log line from pubsub: %s' % ex)
       quit_now = True
       quit_now = True
       exit_code = 2
       exit_code = 2
 
 
@@ -366,6 +376,8 @@ def snat4(snat_target):
           chain.insert_rule(new_rule)
           chain.insert_rule(new_rule)
         else:
         else:
           for position, rule in enumerate(chain.rules):
           for position, rule in enumerate(chain.rules):
+            if not hasattr(rule.target, 'parameter'):
+                continue
             match = all((
             match = all((
               new_rule.get_src() == rule.get_src(),
               new_rule.get_src() == rule.get_src(),
               new_rule.get_dst() == rule.get_dst(),
               new_rule.get_dst() == rule.get_dst(),
@@ -425,6 +437,8 @@ def autopurge():
     time.sleep(10)
     time.sleep(10)
     refreshF2boptions()
     refreshF2boptions()
     BAN_TIME = int(f2boptions['ban_time'])
     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'])
     MAX_ATTEMPTS = int(f2boptions['max_attempts'])
     QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN')
     QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN')
     if QUEUE_UNBAN:
     if QUEUE_UNBAN:
@@ -432,7 +446,9 @@ def autopurge():
         unban(str(net))
         unban(str(net))
     for net in bans.copy():
     for net in bans.copy():
       if bans[net]['attempts'] >= MAX_ATTEMPTS:
       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)
           unban(net)
 
 
 def isIpNetwork(address):
 def isIpNetwork(address):

+ 5 - 3
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 <andre.peters@servercow.de>"
 LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
 
 
 # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced
 # 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
 # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced
 ARG REDIS_PECL_VERSION=5.3.7
 ARG REDIS_PECL_VERSION=5.3.7
 # renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced
 # 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 \
 RUN apk add -U --no-cache autoconf \
   aspell-dev \
   aspell-dev \
@@ -52,6 +52,7 @@ RUN apk add -U --no-cache autoconf \
   libxpm-dev \
   libxpm-dev \
   libzip \
   libzip \
   libzip-dev \
   libzip-dev \
+  linux-headers \
   make \
   make \
   mysql-client \
   mysql-client \
   openldap-dev \
   openldap-dev \
@@ -75,7 +76,7 @@ RUN apk add -U --no-cache autoconf \
     --with-webp \
     --with-webp \
     --with-xpm \
     --with-xpm \
     --with-avif \
     --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-configure imap --with-imap --with-imap-ssl \
   && docker-php-ext-install -j 4 imap \
   && docker-php-ext-install -j 4 imap \
   && curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \
   && 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 \
     libxml2-dev \
     libxpm-dev \
     libxpm-dev \
     libzip-dev \
     libzip-dev \
+    linux-headers \
     make \
     make \
     openldap-dev \
     openldap-dev \
     pcre-dev \
     pcre-dev \

+ 1 - 1
data/assets/nextcloud/nextcloud.conf

@@ -24,7 +24,7 @@ server {
   add_header X-Download-Options "noopen" always;
   add_header X-Download-Options "noopen" always;
   add_header X-Frame-Options "SAMEORIGIN" always;
   add_header X-Frame-Options "SAMEORIGIN" always;
   add_header X-Permitted-Cross-Domain-Policies "none" 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;
   add_header X-XSS-Protection "1; mode=block" always;
 
 
   fastcgi_hide_header X-Powered-By;
   fastcgi_hide_header X-Powered-By;

+ 1 - 1
data/conf/rspamd/local.d/composites.conf

@@ -8,7 +8,7 @@ VIRUS_FOUND {
 }
 }
 # Bad policy from free mail providers
 # Bad policy from free mail providers
 FREEMAIL_POLICY_FAILURE {
 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;
   score = 16.0;
 }
 }
 # Applies to freemail with undisclosed recipients
 # Applies to freemail with undisclosed recipients

+ 1 - 1
data/conf/sogo/sogo.conf

@@ -62,7 +62,7 @@
     SOGoFirstDayOfWeek = "1";
     SOGoFirstDayOfWeek = "1";
 
 
     SOGoSieveFolderEncoding = "UTF-8";
     SOGoSieveFolderEncoding = "UTF-8";
-    SOGoPasswordChangeEnabled = YES;
+    SOGoPasswordChangeEnabled = NO;
     SOGoSentFolderName = "Sent";
     SOGoSentFolderName = "Sent";
     SOGoMailShowSubscribedFoldersOnly = NO;
     SOGoMailShowSubscribedFoldersOnly = NO;
     NGImap4ConnectionStringSeparator = "/";
     NGImap4ConnectionStringSeparator = "/";

+ 11 - 1
data/web/api/openapi.yaml

@@ -3176,8 +3176,10 @@ paths:
               example:
               example:
                 attr:
                 attr:
                   ban_time: "86400"
                   ban_time: "86400"
+                  ban_time_increment: "1"
                   blacklist: "10.100.6.5/32,10.100.8.4/32"
                   blacklist: "10.100.6.5/32,10.100.8.4/32"
                   max_attempts: "5"
                   max_attempts: "5"
+                  max_ban_time: "86400"
                   netban_ipv4: "24"
                   netban_ipv4: "24"
                   netban_ipv6: "64"
                   netban_ipv6: "64"
                   retry_window: "600"
                   retry_window: "600"
@@ -3191,11 +3193,17 @@ paths:
                       description: the backlisted ips or hostnames separated by comma
                       description: the backlisted ips or hostnames separated by comma
                       type: string
                       type: string
                     ban_time:
                     ban_time:
-                      description: the time a ip should be banned
+                      description: the time an ip should be banned
                       type: number
                       type: number
+                    ban_time_increment:
+                      description: if the time of the ban should increase each time
+                      type: boolean
                     max_attempts:
                     max_attempts:
                       description: the maximum numbe of wrong logins before a ip is banned
                       description: the maximum numbe of wrong logins before a ip is banned
                       type: number
                       type: number
+                    max_ban_time:
+                      description: the maximum time an ip should be banned
+                      type: number
                     netban_ipv4:
                     netban_ipv4:
                       description: the networks mask to ban for ipv4
                       description: the networks mask to ban for ipv4
                       type: number
                       type: number
@@ -4113,10 +4121,12 @@ paths:
                 response:
                 response:
                   value:
                   value:
                     ban_time: 604800
                     ban_time: 604800
+                    ban_time_increment: 1
                     blacklist: |-
                     blacklist: |-
                       45.82.153.37/32
                       45.82.153.37/32
                       92.118.38.52/32
                       92.118.38.52/32
                     max_attempts: 1
                     max_attempts: 1
+                    max_ban_time: 604800
                     netban_ipv4: 32
                     netban_ipv4: 32
                     netban_ipv6: 128
                     netban_ipv6: 128
                     perm_bans:
                     perm_bans:

+ 4 - 0
data/web/inc/functions.fail2ban.inc.php

@@ -239,7 +239,9 @@ function fail2ban($_action, $_data = null) {
       $is_now = fail2ban('get');
       $is_now = fail2ban('get');
       if (!empty($is_now)) {
       if (!empty($is_now)) {
         $ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']);
         $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_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']);
         $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_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']);
         $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 = array();
       $f2b_options['ban_time'] = ($ban_time < 60) ? 60 : $ban_time;
       $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_ipv4'] = ($netban_ipv4 < 8) ? 8 : $netban_ipv4;
       $f2b_options['netban_ipv6'] = ($netban_ipv6 < 8) ? 8 : $netban_ipv6;
       $f2b_options['netban_ipv6'] = ($netban_ipv6 < 8) ? 8 : $netban_ipv6;
       $f2b_options['netban_ipv4'] = ($netban_ipv4 > 32) ? 32 : $netban_ipv4;
       $f2b_options['netban_ipv4'] = ($netban_ipv4 > 32) ? 32 : $netban_ipv4;

+ 1 - 1
data/web/js/site/debug.js

@@ -1181,7 +1181,7 @@ jQuery(function($){
 
 
     if (table = $('#' + log_table).DataTable()) {
     if (table = $('#' + log_table).DataTable()) {
       var heading = $('#' + log_table).closest('.card').find('.card-header');
       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){
       $.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; }
         if (data.length === undefined) { mailcow_alert_box(lang.no_new_rows, "info"); return; }

+ 8 - 4
data/web/lang/lang.da-dk.json

@@ -83,7 +83,7 @@
         "private_comment": "Privat kommentar",
         "private_comment": "Privat kommentar",
         "public_comment": "Offentlig kommentar",
         "public_comment": "Offentlig kommentar",
         "quota_mb": "Kvota (Mb)",
         "quota_mb": "Kvota (Mb)",
-        "relay_all": "Send alle modtagere videre",
+        "relay_all": "Besvar alle modtager",
         "relay_all_info": "↪ Hvis du vælger <b> ikke </b> at videresende alle modtagere, skal du tilføje et (\"blind\") postkasse til hver enkelt modtager, der skal videresendes.",
         "relay_all_info": "↪ Hvis du vælger <b> ikke </b> 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_domain": "Send dette domæne videre",
         "relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> Du kan definere transportkort til en tilpasset destination for dette domæne. Hvis ikke indstillet, foretages der et MX-opslag.",
         "relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> 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",
         "timeout2": "Timeout for forbindelse til lokal vært",
         "username": "Brugernavn",
         "username": "Brugernavn",
         "validate": "Bekræft",
         "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.<br>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": {
     "admin": {
         "access": "Adgang",
         "access": "Adgang",
@@ -313,7 +316,8 @@
         "verify": "Verificere",
         "verify": "Verificere",
         "yes": "&#10003;",
         "yes": "&#10003;",
         "ip_check_opt_in": "Opt-In for brug af tredjepartstjeneste <strong>ipv4.mailcow.email</strong> og <strong>ipv6.mailcow.email</strong> til at finde eksterne IP-adresser.",
         "ip_check_opt_in": "Opt-In for brug af tredjepartstjeneste <strong>ipv4.mailcow.email</strong> og <strong>ipv6.mailcow.email</strong> til at finde eksterne IP-adresser.",
-        "queue_unban": "unban"
+        "queue_unban": "unban",
+        "admins": "Administratorer"
     },
     },
     "danger": {
     "danger": {
         "access_denied": "Adgang nægtet eller ugyldig formular data",
         "access_denied": "Adgang nægtet eller ugyldig formular data",
@@ -1044,7 +1048,7 @@
         "spamfilter_table_empty": "Intet data at vise",
         "spamfilter_table_empty": "Intet data at vise",
         "spamfilter_table_remove": "slet",
         "spamfilter_table_remove": "slet",
         "spamfilter_table_rule": "Regl",
         "spamfilter_table_rule": "Regl",
-        "spamfilter_wl": "Hvisliste",
+        "spamfilter_wl": "Hvidliste",
         "spamfilter_wl_desc": "Hvidlistede e-mail-adresser til <b>aldrig</b> 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_wl_desc": "Hvidlistede e-mail-adresser til <b>aldrig</b> 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",
         "spamfilter_yellow": "Gul: denne besked kan være spam, vil blive tagget som spam og flyttes til din junk-mappe",
         "status": "Status",
         "status": "Status",

+ 2 - 0
data/web/lang/lang.de-de.json

@@ -175,10 +175,12 @@
         "empty": "Keine Einträge vorhanden",
         "empty": "Keine Einträge vorhanden",
         "excludes": "Diese Empfänger ausschließen",
         "excludes": "Diese Empfänger ausschließen",
         "f2b_ban_time": "Bannzeit in Sekunden",
         "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_blacklist": "Blacklist für Netzwerke und Hosts",
         "f2b_filter": "Regex-Filter",
         "f2b_filter": "Regex-Filter",
         "f2b_list_info": "Ein Host oder Netzwerk auf der Blacklist wird immer eine Whitelist-Einheit überwiegen. <b>Die Aktualisierung der Liste dauert einige Sekunden.</b>",
         "f2b_list_info": "Ein Host oder Netzwerk auf der Blacklist wird immer eine Whitelist-Einheit überwiegen. <b>Die Aktualisierung der Liste dauert einige Sekunden.</b>",
         "f2b_max_attempts": "Max. Versuche",
         "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_ipv4": "Netzbereich für IPv4-Banns (8-32)",
         "f2b_netban_ipv6": "Netzbereich für IPv6-Banns (8-128)",
         "f2b_netban_ipv6": "Netzbereich für IPv6-Banns (8-128)",
         "f2b_parameters": "Fail2ban-Parameter",
         "f2b_parameters": "Fail2ban-Parameter",

+ 2 - 0
data/web/lang/lang.en-gb.json

@@ -177,10 +177,12 @@
         "empty": "No results",
         "empty": "No results",
         "excludes": "Excludes these recipients",
         "excludes": "Excludes these recipients",
         "f2b_ban_time": "Ban time (s)",
         "f2b_ban_time": "Ban time (s)",
+        "f2b_ban_time_increment": "Ban time is incremented with each ban",
         "f2b_blacklist": "Blacklisted networks/hosts",
         "f2b_blacklist": "Blacklisted networks/hosts",
         "f2b_filter": "Regex filters",
         "f2b_filter": "Regex filters",
         "f2b_list_info": "A blacklisted host or network will always outweigh a whitelist entity. <b>List updates will take a few seconds to be applied.</b>",
         "f2b_list_info": "A blacklisted host or network will always outweigh a whitelist entity. <b>List updates will take a few seconds to be applied.</b>",
         "f2b_max_attempts": "Max. attempts",
         "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_ipv4": "IPv4 subnet size to apply ban on (8-32)",
         "f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)",
         "f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)",
         "f2b_parameters": "Fail2ban parameters",
         "f2b_parameters": "Fail2ban parameters",

+ 2 - 0
data/web/lang/lang.es-es.json

@@ -141,9 +141,11 @@
         "empty": "Sin resultados",
         "empty": "Sin resultados",
         "excludes": "Excluye a estos destinatarios",
         "excludes": "Excluye a estos destinatarios",
         "f2b_ban_time": "Tiempo de restricción (s)",
         "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_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. <b>Las actualizaciones de la lista tardarán unos segundos en aplicarse.</b>",
         "f2b_list_info": "Un host o red en lista negra siempre superará a una entidad de la lista blanca. <b>Las actualizaciones de la lista tardarán unos segundos en aplicarse.</b>",
         "f2b_max_attempts": "Max num. de intentos",
         "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_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_netban_ipv6": "Tamaño de subred IPv6 para aplicar la restricción (8-128)",
         "f2b_parameters": "Parametros Fail2ban",
         "f2b_parameters": "Parametros Fail2ban",

+ 21 - 8
data/web/lang/lang.fr-fr.json

@@ -24,7 +24,7 @@
         "spam_policy": "Liste Noire/Liste Blanche",
         "spam_policy": "Liste Noire/Liste Blanche",
         "spam_score": "Score SPAM",
         "spam_score": "Score SPAM",
         "syncjobs": "Tâches de synchronisation",
         "syncjobs": "Tâches de synchronisation",
-        "tls_policy": "Police TLS",
+        "tls_policy": "Politique TLS",
         "unlimited_quota": "Quota illimité pour les boites de courriel",
         "unlimited_quota": "Quota illimité pour les boites de courriel",
         "domain_desc": "Modifier la description du domaine",
         "domain_desc": "Modifier la description du domaine",
         "domain_relayhost": "Changer le relais pour un domaine",
         "domain_relayhost": "Changer le relais pour un domaine",
@@ -106,7 +106,8 @@
         "validate": "Valider",
         "validate": "Valider",
         "validation_success": "Validation réussie",
         "validation_success": "Validation réussie",
         "bcc_dest_format": "La destination Cci doit être une seule adresse e-mail valide.<br>Si vous avez besoin d'envoyer une copie à plusieurs adresses, créez un alias et utilisez-le ici.",
         "bcc_dest_format": "La destination Cci doit être une seule adresse e-mail valide.<br>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": {
     "admin": {
         "access": "Accès",
         "access": "Accès",
@@ -171,11 +172,13 @@
         "edit": "Editer",
         "edit": "Editer",
         "empty": "Aucun résultat",
         "empty": "Aucun résultat",
         "excludes": "Exclure ces destinataires",
         "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_blacklist": "Réseaux/Domaines sur Liste Noire",
         "f2b_filter": "Filtre(s) Regex",
         "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. <b>L'application des mises à jour de liste prendra quelques secondes.</b>",
         "f2b_list_info": "Un hôte ou un réseau sur liste noire l'emportera toujours sur une entité de liste blanche. <b>L'application des mises à jour de liste prendra quelques secondes.</b>",
         "f2b_max_attempts": "Nb max. de tentatives",
         "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_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_netban_ipv6": "Taille du sous-réseau IPv6 pour l'application du bannissement (8-128)",
         "f2b_parameters": "Paramètres Fail2ban",
         "f2b_parameters": "Paramètres Fail2ban",
@@ -321,7 +324,9 @@
         "admins": "Administrateurs",
         "admins": "Administrateurs",
         "api_read_only": "Accès lecture-seule",
         "api_read_only": "Accès lecture-seule",
         "password_policy_lowerupper": "Doit contenir des caractères minuscules et majuscules",
         "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<br> <strong>Système > Configuration > Options > Personnaliser</strong>"
     },
     },
     "danger": {
     "danger": {
         "access_denied": "Accès refusé ou données de formulaire non valides",
         "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é",
         "username_invalid": "Le nom d'utilisateur %s ne peut pas être utilisé",
         "validity_missing": "Veuillez attribuer une période de validité",
         "validity_missing": "Veuillez attribuer une période de validité",
         "value_missing": "Veuillez fournir toutes les valeurs",
         "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": {
     "debug": {
         "chart_this_server": "Graphique (ce serveur)",
         "chart_this_server": "Graphique (ce serveur)",
@@ -578,7 +588,7 @@
         "unchanged_if_empty": "Si non modifié, laisser en blanc",
         "unchanged_if_empty": "Si non modifié, laisser en blanc",
         "username": "Nom d'utilisateur",
         "username": "Nom d'utilisateur",
         "validate_save": "Valider et sauver",
         "validate_save": "Valider et sauver",
-        "lookup_mx": "La destination est une expression régulière qui doit correspondre avec le nom du MX (<code>.*google\\.com</code> 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 (<code>.*google\\.com</code> 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."
         "mailbox_relayhost_info": "S'applique uniquement à la boîte aux lettres et aux alias directs, remplace le relayhost du domaine."
     },
     },
     "footer": {
     "footer": {
@@ -1081,9 +1091,12 @@
         "username": "Nom d'utilisateur",
         "username": "Nom d'utilisateur",
         "verify": "Vérification",
         "verify": "Vérification",
         "waiting": "En attente",
         "waiting": "En attente",
-        "week": "Semaine",
+        "week": "semaine",
         "weekly": "Hebdomadaire",
         "weekly": "Hebdomadaire",
-        "weeks": "semaines"
+        "weeks": "semaines",
+        "months": "mois",
+        "year": "année",
+        "years": "années"
     },
     },
     "warning": {
     "warning": {
         "cannot_delete_self": "Impossible de supprimer l’utilisateur connecté",
         "cannot_delete_self": "Impossible de supprimer l’utilisateur connecté",

+ 2 - 0
data/web/lang/lang.it-it.json

@@ -175,10 +175,12 @@
         "empty": "Nessun risultato",
         "empty": "Nessun risultato",
         "excludes": "Esclude questi destinatari",
         "excludes": "Esclude questi destinatari",
         "f2b_ban_time": "Tempo di blocco (s)",
         "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_blacklist": "Host/reti in blacklist",
         "f2b_filter": "Filtri Regex",
         "f2b_filter": "Filtri Regex",
         "f2b_list_info": "Un host oppure una rete in blacklist, avrà sempre un peso maggiore rispetto ad una in whitelist. <b>L'aggiornamento della lista richiede alcuni secondi per la sua entrata in azione.</b>",
         "f2b_list_info": "Un host oppure una rete in blacklist, avrà sempre un peso maggiore rispetto ad una in whitelist. <b>L'aggiornamento della lista richiede alcuni secondi per la sua entrata in azione.</b>",
         "f2b_max_attempts": "Tentativi massimi",
         "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_ipv4": "IPv4 subnet size to apply ban on (8-32)",
         "f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)",
         "f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)",
         "f2b_parameters": "Parametri Fail2ban",
         "f2b_parameters": "Parametri Fail2ban",

+ 2 - 0
data/web/lang/lang.nl-nl.json

@@ -168,10 +168,12 @@
         "empty": "Geen resultaten",
         "empty": "Geen resultaten",
         "excludes": "Exclusief",
         "excludes": "Exclusief",
         "f2b_ban_time": "Verbanningstijd (s)",
         "f2b_ban_time": "Verbanningstijd (s)",
+        "f2b_ban_time_increment": "Verbanningstijd wordt verhoogd met elk verbanning",
         "f2b_blacklist": "Netwerken/hosts op de blacklist",
         "f2b_blacklist": "Netwerken/hosts op de blacklist",
         "f2b_filter": "Regex-filters",
         "f2b_filter": "Regex-filters",
         "f2b_list_info": "Een host of netwerk op de blacklist staat altijd boven eenzelfde op de whitelist. <b>Het doorvoeren van wijzigingen kan enkele seconden in beslag nemen.</b>",
         "f2b_list_info": "Een host of netwerk op de blacklist staat altijd boven eenzelfde op de whitelist. <b>Het doorvoeren van wijzigingen kan enkele seconden in beslag nemen.</b>",
         "f2b_max_attempts": "Maximaal aantal pogingen",
         "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_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_netban_ipv6": "Voer de IPv6-subnetgrootte in waar de verbanning van kracht moet zijn (8-128)",
         "f2b_parameters": "Fail2ban",
         "f2b_parameters": "Fail2ban",

+ 8 - 0
data/web/templates/admin/tab-config-f2b.twig

@@ -12,6 +12,14 @@
           <label for="f2b_ban_time">{{ lang.admin.f2b_ban_time }}:</label>
           <label for="f2b_ban_time">{{ lang.admin.f2b_ban_time }}:</label>
           <input type="number" class="form-control" id="f2b_ban_time" name="ban_time" value="{{ f2b_data.ban_time }}" required>
           <input type="number" class="form-control" id="f2b_ban_time" name="ban_time" value="{{ f2b_data.ban_time }}" required>
         </div>
         </div>
+        <div class="mb-4">
+          <label for="f2b_max_ban_time">{{ lang.admin.f2b_max_ban_time }}:</label>
+          <input type="number" class="form-control" id="f2b_max_ban_time" name="max_ban_time" value="{{ f2b_data.max_ban_time }}" required>
+        </div>
+        <div class="mb-4">
+          <input class="form-check-input" type="checkbox" value="1" name="ban_time_increment" id="f2b_ban_time_increment" {% if f2b_data.ban_time_increment == 1 %}checked{% endif %}>
+          <label class="form-check-label" for="f2b_ban_time_increment">{{ lang.admin.f2b_ban_time_increment }}</label>
+        </div>
         <div class="mb-4">
         <div class="mb-4">
           <label for="f2b_max_attempts">{{ lang.admin.f2b_max_attempts }}:</label>
           <label for="f2b_max_attempts">{{ lang.admin.f2b_max_attempts }}:</label>
           <input type="number" class="form-control" id="f2b_max_attempts" name="max_attempts" value="{{ f2b_data.max_attempts }}" required>
           <input type="number" class="form-control" id="f2b_max_attempts" name="max_attempts" value="{{ f2b_data.max_attempts }}" required>

+ 6 - 6
docker-compose.yml

@@ -76,7 +76,7 @@ services:
             - clamd
             - clamd
 
 
     rspamd-mailcow:
     rspamd-mailcow:
-      image: mailcow/rspamd:1.92
+      image: mailcow/rspamd:1.93
       stop_grace_period: 30s
       stop_grace_period: 30s
       depends_on:
       depends_on:
         - dovecot-mailcow
         - dovecot-mailcow
@@ -106,7 +106,7 @@ services:
             - rspamd
             - rspamd
 
 
     php-fpm-mailcow:
     php-fpm-mailcow:
-      image: mailcow/phpfpm:1.82
+      image: mailcow/phpfpm:1.83
       command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
       command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
       depends_on:
       depends_on:
         - redis-mailcow
         - redis-mailcow
@@ -169,7 +169,7 @@ services:
             - phpfpm
             - phpfpm
 
 
     sogo-mailcow:
     sogo-mailcow:
-      image: mailcow/sogo:1.115
+      image: mailcow/sogo:1.116
       environment:
       environment:
         - DBNAME=${DBNAME}
         - DBNAME=${DBNAME}
         - DBUSER=${DBUSER}
         - DBUSER=${DBUSER}
@@ -191,7 +191,7 @@ services:
       volumes:
       volumes:
         - ./data/hooks/sogo:/hooks:Z
         - ./data/hooks/sogo:/hooks:Z
         - ./data/conf/sogo/:/etc/sogo/: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-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-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
         - ./data/conf/sogo/custom-sogo.js:/usr/lib/GNUstep/SOGo/WebServerResources/js/custom-sogo.js:z
@@ -425,7 +425,7 @@ services:
             - acme
             - acme
 
 
     netfilter-mailcow:
     netfilter-mailcow:
-      image: mailcow/netfilter:1.51
+      image: mailcow/netfilter:1.52
       stop_grace_period: 30s
       stop_grace_period: 30s
       depends_on:
       depends_on:
         - dovecot-mailcow
         - dovecot-mailcow
@@ -510,7 +510,7 @@ services:
             - watchdog
             - watchdog
 
 
     dockerapi-mailcow:
     dockerapi-mailcow:
-      image: mailcow/dockerapi:2.01
+      image: mailcow/dockerapi:2.02
       security_opt:
       security_opt:
         - label=disable
         - label=disable
       restart: always
       restart: always

+ 2 - 2
generate_config.sh

@@ -205,8 +205,8 @@ DBUSER=mailcow
 
 
 # Please use long, random alphanumeric strings (A-Za-z0-9)
 # Please use long, random alphanumeric strings (A-Za-z0-9)
 
 
-DBPASS=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 | head -c 28)
-DBROOT=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 | head -c 28)
+DBPASS=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2> /dev/null | head -c 28)
+DBROOT=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2> /dev/null | head -c 28)
 
 
 # ------------------------------
 # ------------------------------
 # HTTP/S Bindings
 # HTTP/S Bindings

+ 1 - 1
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
 response=${response,,}    # tolower
 if [[ "$response" =~ ^(yes|y)$ ]]; then
 if [[ "$response" =~ ^(yes|y)$ ]]; then
 	echo -e "\nWorking, please wait..."
 	echo -e "\nWorking, please wait..."
-  random=$(</dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-16})
+  random=$(</dev/urandom tr -dc _A-Z-a-z-0-9 2> /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')
   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 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';"
   docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM domain_admins WHERE username='admin';"

+ 3 - 3
helper-scripts/nextcloud.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 #!/usr/bin/env bash
 # renovate: datasource=github-releases depName=nextcloud/server versioning=semver extractVersion=^v(?<version>.*)$
 # renovate: datasource=github-releases depName=nextcloud/server versioning=semver extractVersion=^v(?<version>.*)$
-NEXTCLOUD_VERSION=25.0.4
+NEXTCLOUD_VERSION=26.0.0
 
 
 echo -ne "Checking prerequisites..."
 echo -ne "Checking prerequisites..."
 sleep 1
 sleep 1
@@ -122,7 +122,7 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
     && chmod +x ./data/web/nextcloud/occ
     && chmod +x ./data/web/nextcloud/occ
 
 
   echo -e "\033[33mCreating 'nextcloud' database...\033[0m"
   echo -e "\033[33mCreating 'nextcloud' database...\033[0m"
-  NC_DBPASS=$(</dev/urandom tr -dc A-Za-z0-9 | head -c 28)
+  NC_DBPASS=$(</dev/urandom tr -dc A-Za-z0-9 2> /dev/null | head -c 28)
   NC_DBUSER=nextcloud
   NC_DBUSER=nextcloud
   NC_DBNAME=nextcloud
   NC_DBNAME=nextcloud
 
 
@@ -138,7 +138,7 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
 
 
   echo ""
   echo ""
   echo -e "\033[33mInstalling Nextcloud...\033[0m"
   echo -e "\033[33mInstalling Nextcloud...\033[0m"
-  ADMIN_NC_PASS=$(</dev/urandom tr -dc A-Za-z0-9 | head -c 28)
+  ADMIN_NC_PASS=$(</dev/urandom tr -dc A-Za-z0-9 2> /dev/null | head -c 28)
 
 
   echo -ne "[1/4] Setting correct permissions for www-data"
   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"
   docker exec -it $(docker ps -f name=php-fpm-mailcow -q) /bin/bash -c "chown -R www-data:www-data /web/nextcloud"

+ 58 - 19
update.sh

@@ -176,18 +176,19 @@ remove_obsolete_nginx_ports() {
 }
 }
 
 
 detect_docker_compose_command(){
 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 > /dev/null 2>&1; then
       if docker compose version --short | grep "2." > /dev/null 2>&1; then
       if docker compose version --short | grep "2." > /dev/null 2>&1; then
         DOCKER_COMPOSE_VERSION=native
         DOCKER_COMPOSE_VERSION=native
         COMPOSE_COMMAND="docker compose"
         COMPOSE_COMMAND="docker compose"
         echo -e "\e[31mFound Docker Compose Plugin (native).\e[0m"
         echo -e "\e[31mFound Docker Compose Plugin (native).\e[0m"
         echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to 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
         sleep 2
         echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m"
         echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m"
       else
       else
         echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" 
         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
         exit 1
       fi
       fi
   elif docker-compose > /dev/null 2>&1; then
   elif docker-compose > /dev/null 2>&1; then
@@ -197,26 +198,60 @@ if ! [ "${DOCKER_COMPOSE_VERSION}" == "native" ] && ! [ "${DOCKER_COMPOSE_VERSIO
         COMPOSE_COMMAND="docker-compose"
         COMPOSE_COMMAND="docker-compose"
         echo -e "\e[31mFound Docker Compose Standalone.\e[0m"
         echo -e "\e[31mFound Docker Compose Standalone.\e[0m"
         echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to 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
         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"
         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
       else
         echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" 
         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
         exit 1
       fi
       fi
     fi
     fi
 
 
   else
   else
     echo -e "\e[31mCannot find Docker Compose.\e[0m" 
     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
     exit 1
   fi
   fi
 
 
 elif [ "${DOCKER_COMPOSE_VERSION}" == "native" ]; then
 elif [ "${DOCKER_COMPOSE_VERSION}" == "native" ]; then
   COMPOSE_COMMAND="docker compose"
   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
 elif [ "${DOCKER_COMPOSE_VERSION}" == "standalone" ]; then
   COMPOSE_COMMAND="docker-compose"
   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
 fi
 }
 }
 
 
@@ -326,8 +361,12 @@ while (($#)); do
       echo -e "\e[32mRunning in forced mode...\e[0m"
       echo -e "\e[32mRunning in forced mode...\e[0m"
       FORCE=y
       FORCE=y
     ;;
     ;;
+    -d|--dev)
+      echo -e "\e[32mRunning in Developer mode...\e[0m"
+      DEV=y
+    ;;
     --help|-h)
     --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)
   -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!
   --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)
   --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.
   --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
   -f|--force           -   Force update, do not ask questions
+  -d|--dev             -   Enables Developer Mode (No Checkout of update.sh for tests)
 '
 '
     exit 1
     exit 1
   esac
   esac
@@ -597,7 +637,7 @@ for option in ${CONFIG_ARRAY[@]}; do
       echo "Adding new option \"${option}\" to mailcow.conf"
       echo "Adding new option \"${option}\" to mailcow.conf"
       echo '# Password hash algorithm' >> 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 '# 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
       echo "MAILCOW_PASS_SCHEME=BLF-CRYPT" >> mailcow.conf
     fi
     fi
   elif [[ ${option} == "ADDITIONAL_SERVER_NAMES" ]]; then
   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 '# Optional: Leave empty for none' >> mailcow.conf
       echo '# This value is only used on first order!' >> 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 '# 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
       echo 'ACME_CONTACT=' >> mailcow.conf
   fi
   fi
   elif [[ ${option} == "WEBAUTHN_ONLY_TRUSTED_VENDORS" ]]; then
   elif [[ ${option} == "WEBAUTHN_ONLY_TRUSTED_VENDORS" ]]; then
@@ -727,15 +767,17 @@ elif [ $NEW_BRANCH == "nightly" ] && [ $CURRENT_BRANCH != "nightly" ]; then
   git checkout -f ${BRANCH}
   git checkout -f ${BRANCH}
 fi
 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
 fi
 
 
 if [ ! $FORCE ]; then
 if [ ! $FORCE ]; then
@@ -902,9 +944,6 @@ else
   echo -e "\e[33mCannot determine current git repository version...\e[0m"
   echo -e "\e[33mCannot determine current git repository version...\e[0m"
 fi
 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
 if [[ ${SKIP_START} == "y" ]]; then
   echo -e "\e[33mNot starting mailcow, please run \"$COMPOSE_COMMAND up -d --remove-orphans\" to start mailcow.\e[0m"
   echo -e "\e[33mNot starting mailcow, please run \"$COMPOSE_COMMAND up -d --remove-orphans\" to start mailcow.\e[0m"
 else
 else