Преглед изворни кода

Merge pull request #6468 from mailcow/staging

Update 2025-03b
FreddleSpl0it пре 5 месеци
родитељ
комит
3f493e043d

+ 16 - 0
README.md

@@ -13,6 +13,22 @@ You can also [get a SAL](https://www.servercow.de/mailcow?lang=en#sal) which is
 
 Or just spread the word: moo.
 
+## Many thanks to our GitHub Sponsors ❤️
+A big thank you to everyone supporting us on GitHub Sponsors—your contributions mean the world to us! Special thanks to the following amazing supporters:
+
+### 100$/Month Sponsors
+  <a href="https://www.colba.net/" target=_blank><img
+    src="https://avatars.githubusercontent.com/u/204464723" height="58"
+  /></a>
+  <a href="https://www.maehdros.com/" target=_blank><img
+    src="https://avatars.githubusercontent.com/u/173894712" height="58"
+  /></a>
+
+### 50$/Month Sponsors
+  <a href="https://github.com/vnukhr" target=_blank><img
+    src="https://avatars.githubusercontent.com/u/7805987?s=52&v=4" height="58"
+  /></a>
+
 ## Info, documentation and support
 
 Please see [the official documentation](https://docs.mailcow.email/) for installation and support instructions. 🐄

+ 3 - 8
data/Dockerfiles/sogo/navMailcowBtns.diff

@@ -1,20 +1,15 @@
-59,65d58
-<                ng-show="::!activeUser.isSuperUser"
+60,65d58
 <                var:ng-click="navButtonClick"
 <                ng-href="/user">
 <       <md-icon>build</md-icon>
-<       <md-tooltip><var:string label:value="mailcow"/></md-tooltip>
+<       <md-tooltip>mailcow <var:string label:value="Preferences"/></md-tooltip>
 <     </md-button>
 <     <md-button class="md-icon-button"
 83c76
-<                onclick="document.getElementById('mc_logout').setAttribute('action', '/'); document.getElementById('mc_logout').submit();"
+<                onclick="mc_logout();"
 ---
 >                ng-show="::activeUser.path.logoff.length"
 85c78
 <                ng-href="#">
 ---
 >                ng-href="{{::activeUser.path.logoff}}">
-89,91d81
-<     <form method="POST" id="mc_logout" action="user">
-<       <input type="hidden" name="logout" value="1">
-<     </form>

+ 10 - 5
data/conf/dovecot/auth/mailcowauth.php

@@ -69,29 +69,34 @@ require_once 'functions.acl.inc.php';
 
 $isSOGoRequest = $post['real_rip'] == getenv('IPV4_NETWORK') . '.248';
 $result = false;
-$protocol = $post['protocol'];
 if ($isSOGoRequest) {
-  $protocol = null;
   // This is a SOGo Auth request. First check for SSO password.
   $sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass");
   if ($sogo_sso_pass === $post['password']){
     error_log('MAILCOWAUTH: SOGo SSO auth for user ' . $post['username']);
+    set_sasl_log($post['username'], $post['real_rip'], "SOGO");
     $result = true;
   }
 }
 if ($result === false){
-  $result = apppass_login($post['username'], $post['password'], $protocol, array(
+  $result = apppass_login($post['username'], $post['password'], array($post['service'] => true), array(
     'is_internal' => true,
     'remote_addr' => $post['real_rip']
   ));
-  if ($result) error_log('MAILCOWAUTH: App auth for user ' . $post['username']);
+  if ($result) {
+    error_log('MAILCOWAUTH: App auth for user ' . $post['username']);
+    set_sasl_log($post['username'], $post['real_rip'], $post['service']);
+  }
 }
 if ($result === false){
   // Init Identity Provider
   $iam_provider = identity_provider('init');
   $iam_settings = identity_provider('get');
   $result = user_login($post['username'], $post['password'], array('is_internal' => true));
-  if ($result) error_log('MAILCOWAUTH: User auth for user ' . $post['username']);
+  if ($result) {
+    error_log('MAILCOWAUTH: User auth for user ' . $post['username']);
+    set_sasl_log($post['username'], $post['real_rip'], $post['service']);
+  }
 }
 
 if ($result) {

+ 14 - 9
data/conf/dovecot/auth/passwd-verify.lua

@@ -3,21 +3,20 @@ function auth_password_verify(request, password)
     return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "No such user"
   end
 
-  json = require "cjson"
-  ltn12 = require "ltn12"
-  https = require "ssl.https"
-  https.TIMEOUT = 5
+  local json = require "cjson"
+  local ltn12 = require "ltn12"
+  local https = require "ssl.https"
+  https.TIMEOUT = 30
 
   local req = {
     username = request.user,
     password = password,
     real_rip = request.real_rip,
-    protocol = {}
+    service = request.service
   }
-  req.protocol[request.service] = true
   local req_json = json.encode(req)
-  local res = {} 
-  
+  local res = {}
+
   local b, c = https.request {
     method = "POST",
     url = "https://nginx:9082",
@@ -29,11 +28,17 @@ function auth_password_verify(request, password)
     sink = ltn12.sink.table(res),
     insecure = true
   }
+
+  if c ~= 200 then
+    dovecot.i_info("HTTP request failed with " .. c .. " for user " .. request.user)
+    return dovecot.auth.PASSDB_RESULT_INTERNAL_FAILURE, "Upstream error"
+  end
+
   local api_response = json.decode(table.concat(res))
   if api_response.success == true then
     return dovecot.auth.PASSDB_RESULT_OK, ""
   end
-  
+
   return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Failed to authenticate"
 end
 

+ 1 - 1
data/conf/dovecot/dovecot.conf

@@ -53,7 +53,7 @@ mail_shared_explicit_inbox = yes
 mail_prefetch_count = 30
 passdb {
   driver = lua
-  args = file=/etc/dovecot/auth/passwd-verify.lua blocking=yes cache_key=%u:%w
+  args = file=/etc/dovecot/auth/passwd-verify.lua blocking=yes cache_key=%s:%u:%w
   result_success = return-ok
   result_failure = continue
   result_internalfail = continue

+ 1 - 1
data/conf/phpfpm/crons/keycloak-sync.php

@@ -196,7 +196,7 @@ while (true) {
         logMsg("err", "Could not create user " . $user['email']);
         continue;
       }
-    } else if ($row && intval($iam_settings['periodic_sync']) == 1) {
+    } else if ($row && intval($iam_settings['periodic_sync']) == 1 && $row['authsource'] == "keycloak") {
       if ($mapper_key === false){
         logMsg("warning", "No matching attribute mapping found for user " . $user['email']);
         continue;

+ 1 - 1
data/conf/phpfpm/crons/ldap-sync.php

@@ -168,7 +168,7 @@ foreach ($response as $user) {
       logMsg("err", "Could not create user " . $user[$iam_settings['username_field']][0]);
       continue;
     }
-  } else if ($row && intval($iam_settings['periodic_sync']) == 1) {
+  } else if ($row && intval($iam_settings['periodic_sync']) == 1 && $row['authsource'] == "ldap") {
     if ($mapper_key === false){
       logMsg("warning", "No matching attribute mapping found for user " . $user[$iam_settings['username_field']][0]);
       continue;

+ 75 - 8
data/conf/postfix/postscreen_access.cidr

@@ -1,6 +1,6 @@
-# Whitelist generated by Postwhite v3.4 on Sat Mar  1 00:19:29 UTC 2025
+# Whitelist generated by Postwhite v3.4 on Tue Apr  1 00:20:51 UTC 2025
 # https://github.com/stevejenkins/postwhite/
-# 2000 total rules
+# 2067 total rules
 2a00:1450:4000::/36	permit
 2a01:111:f400::/48	permit
 2a01:111:f403:8000::/50	permit
@@ -26,7 +26,12 @@
 8.20.114.31	permit
 8.25.194.0/23	permit
 8.25.196.0/23	permit
+8.39.54.0/23	permit
+8.39.54.250/31	permit
+8.40.222.0/23	permit
+8.40.222.250/31	permit
 12.130.86.238	permit
+13.107.246.59	permit
 13.110.208.0/21	permit
 13.110.209.0/24	permit
 13.110.216.0/22	permit
@@ -60,8 +65,6 @@
 20.59.80.4/30	permit
 20.63.210.192/28	permit
 20.69.8.108/30	permit
-20.70.246.20	permit
-20.76.201.171	permit
 20.83.222.104/30	permit
 20.88.157.184/30	permit
 20.94.180.64/28	permit
@@ -70,14 +73,11 @@
 20.98.194.68/30	permit
 20.105.209.76/30	permit
 20.107.239.64/30	permit
-20.112.250.133	permit
 20.118.139.208/30	permit
 20.141.10.196	permit
 20.185.214.0/27	permit
 20.185.214.32/27	permit
 20.185.214.64/27	permit
-20.231.239.246	permit
-20.236.44.162	permit
 23.103.224.0/19	permit
 23.249.208.0/20	permit
 23.251.224.0/19	permit
@@ -103,6 +103,24 @@
 27.123.206.80/28	permit
 31.25.48.222	permit
 31.47.251.17	permit
+34.2.64.0/22	permit
+34.2.68.0/23	permit
+34.2.70.0/23	permit
+34.2.71.64/26	permit
+34.2.72.0/22	permit
+34.2.75.0/26	permit
+34.2.78.0/23	permit
+34.2.80.0/23	permit
+34.2.82.0/23	permit
+34.2.84.0/24	permit
+34.2.84.64/26	permit
+34.2.85.0/24	permit
+34.2.85.64/26	permit
+34.2.86.0/23	permit
+34.2.88.0/23	permit
+34.2.90.0/23	permit
+34.2.92.0/23	permit
+34.2.94.0/23	permit
 34.195.217.107	permit
 34.212.163.75	permit
 34.215.104.144	permit
@@ -215,7 +233,6 @@
 52.95.49.88/29	permit
 52.96.91.34	permit
 52.96.111.82	permit
-52.96.172.98	permit
 52.96.214.50	permit
 52.96.222.194	permit
 52.96.222.226	permit
@@ -255,6 +272,7 @@
 54.244.54.130	permit
 54.244.242.0/24	permit
 54.255.61.23	permit
+56.124.6.228	permit
 57.103.64.0/18	permit
 62.13.128.0/24	permit
 62.13.129.128/25	permit
@@ -341,6 +359,7 @@
 65.110.161.77	permit
 65.123.29.213	permit
 65.123.29.220	permit
+65.154.166.0/24	permit
 65.212.180.36	permit
 66.102.0.0/20	permit
 66.119.150.192/26	permit
@@ -1304,6 +1323,9 @@
 117.120.16.0/21	permit
 119.42.242.52/31	permit
 119.42.242.156	permit
+121.244.91.48	permit
+121.244.91.52	permit
+122.15.156.182	permit
 123.126.78.64/29	permit
 124.108.96.24/31	permit
 124.108.96.28/31	permit
@@ -1366,7 +1388,21 @@
 134.170.141.64/26	permit
 134.170.143.0/24	permit
 134.170.174.0/24	permit
+135.84.80.0/24	permit
+135.84.81.0/24	permit
+135.84.82.0/24	permit
+135.84.83.0/24	permit
 135.84.216.0/22	permit
+136.143.160.0/24	permit
+136.143.161.0/24	permit
+136.143.162.0/24	permit
+136.143.176.0/24	permit
+136.143.177.0/24	permit
+136.143.178.49	permit
+136.143.182.0/23	permit
+136.143.184.0/24	permit
+136.143.188.0/24	permit
+136.143.190.0/23	permit
 136.147.128.0/20	permit
 136.147.135.0/24	permit
 136.147.176.0/20	permit
@@ -1381,6 +1417,7 @@
 139.138.46.219	permit
 139.138.57.55	permit
 139.138.58.119	permit
+139.167.79.86	permit
 139.180.17.0/24	permit
 140.238.148.191	permit
 141.148.159.229	permit
@@ -1498,6 +1535,9 @@
 163.114.135.16	permit
 164.152.23.32	permit
 164.177.132.168/30	permit
+165.173.128.0/24	permit
+165.173.180.250/31	permit
+165.173.182.250/31	permit
 166.78.68.0/22	permit
 166.78.68.221	permit
 166.78.69.169	permit
@@ -1526,6 +1566,12 @@
 168.245.12.252	permit
 168.245.46.9	permit
 168.245.127.231	permit
+169.148.129.0/24	permit
+169.148.131.0/24	permit
+169.148.142.10	permit
+169.148.144.0/25	permit
+169.148.144.10	permit
+169.148.146.0/23	permit
 170.10.128.0/24	permit
 170.10.129.0/24	permit
 170.10.132.56/29	permit
@@ -1667,6 +1713,14 @@
 198.61.254.231	permit
 198.178.234.57	permit
 198.244.48.0/20	permit
+198.244.56.107	permit
+198.244.56.108	permit
+198.244.56.109	permit
+198.244.56.111	permit
+198.244.56.112	permit
+198.244.56.113	permit
+198.244.56.114	permit
+198.244.56.115	permit
 198.244.59.30	permit
 198.244.59.33	permit
 198.244.59.35	permit
@@ -1679,7 +1733,15 @@
 199.16.156.0/22	permit
 199.33.145.1	permit
 199.33.145.32	permit
+199.34.22.36	permit
 199.59.148.0/22	permit
+199.67.80.2	permit
+199.67.80.20	permit
+199.67.82.2	permit
+199.67.82.20	permit
+199.67.84.0/24	permit
+199.67.86.0/24	permit
+199.67.88.0/24	permit
 199.101.161.130	permit
 199.101.162.0/25	permit
 199.122.120.0/21	permit
@@ -1736,6 +1798,8 @@
 204.92.114.187	permit
 204.92.114.203	permit
 204.92.114.204/31	permit
+204.141.32.0/23	permit
+204.141.42.0/23	permit
 204.220.160.0/21	permit
 204.220.168.0/21	permit
 204.220.176.0/20	permit
@@ -1988,6 +2052,9 @@
 2603:1030:20e:3::23c	permit
 2603:1030:b:3::152	permit
 2603:1030:c02:8::14	permit
+2607:13c0:0001:0000:0000:0000:0000:7000/116	permit
+2607:13c0:0002:0000:0000:0000:0000:1000/116	permit
+2607:13c0:0004:0000:0000:0000:0000:0000/116	permit
 2607:f8b0:4000::/36	permit
 2620:109:c003:104::/64	permit
 2620:109:c003:104::215	permit

+ 10 - 0
data/conf/sogo/custom-sogo.js

@@ -5,6 +5,16 @@ document.addEventListener('DOMContentLoaded', function () {
         window.location.href = '/user';
     }
 });
+// logout function
+function mc_logout() {
+    fetch("/", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/x-www-form-urlencoded"
+        },
+        body: "logout=1"
+    }).then(() => window.location.href = '/');
+}
 
 // Custom SOGo JS
 

+ 36 - 23
data/web/inc/functions.auth.inc.php

@@ -9,25 +9,52 @@ function check_login($user, $pass, $app_passwd_data = false, $extra = null) {
   // Try validate admin
   if (!isset($role) || $role == "admin") {
     $result = admin_login($user, $pass);
-    if ($result !== false) return $result;
+    if ($result !== false){
+      return $result;
+    }
   }
 
   // Try validate domain admin
   if (!isset($role) || $role == "domain_admin") {
     $result = domainadmin_login($user, $pass);
-    if ($result !== false) return $result;
+    if ($result !== false) {
+      return $result;
+    }
   }
 
-  // Try validate user
-  if (!isset($role) || $role == "user") {
-    $result = user_login($user, $pass);
-    if ($result !== false) return $result;
-  }
 
   // Try validate app password
   if (!isset($role) || $role == "app") {
     $result = apppass_login($user, $pass, $app_passwd_data);
-    if ($result !== false) return $result;
+    if ($result !== false) {
+      if ($app_passwd_data['eas'] === true) {
+        $service = 'EAS';
+      } elseif ($app_passwd_data['dav'] === true) {
+        $service = 'DAV';
+      } else {
+        $service = 'NONE';
+      }
+      $real_rip = ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']);
+      set_sasl_log($user, $real_rip, $service, $pass);
+      return $result;
+    }
+  }
+
+  // Try validate user
+  if (!isset($role) || $role == "user") {
+    $result = user_login($user, $pass);
+    if ($result !== false) {
+      if ($app_passwd_data['eas'] === true) {
+        $service = 'EAS';
+      } elseif ($app_passwd_data['dav'] === true) {
+        $service = 'DAV';
+      } else {
+        $service = 'MAILCOWUI';
+      }
+      $real_rip = ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']);
+      set_sasl_log($user, $real_rip, $service);
+      return $result;
+    }
   }
 
   // skip log and only return false if it's an internal request
@@ -415,21 +442,7 @@ function apppass_login($user, $pass, $app_passwd_data, $extra = null){
 
     // verify password
     if (verify_hash($row['password'], $pass) !== false) {
-      if ($is_internal){
-        $remote_addr = $extra['remote_addr'];
-      } else {
-        $remote_addr = ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']);
-      }
-
-      $service = strtoupper($is_app_passwd);
-      $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)");
-      $stmt->execute(array(
-        ':service' => $service,
-        ':app_id' => $row['app_passwd_id'],
-        ':username' => $user,
-        ':remote_addr' => $remote_addr
-      ));
-
+      $_SESSION['app_passwd_id'] = $row['app_passwd_id'];
       unset($_SESSION['ldelay']);
       return "user";
     }

+ 28 - 0
data/web/inc/functions.inc.php

@@ -350,6 +350,34 @@ function last_login($action, $username, $sasl_limit_days = 7, $ui_offset = 1) {
   }
 
 }
+function set_sasl_log($username, $real_rip, $service){
+  global $pdo;
+
+  try {
+    if (!empty($_SESSION['app_passwd_id'])) {
+      $app_password = $_SESSION['app_passwd_id'];
+    } else {
+      $app_password = 0;
+    }
+
+    $stmt = $pdo->prepare('REPLACE INTO `sasl_log` (`username`, `real_rip`, `service`, `app_password`) VALUES (:username, :real_rip, :service, :app_password)');
+    $stmt->execute(array(
+      ':username' => $username,
+      ':real_rip' => $real_rip,
+      ':service' => $service,
+      ':app_password' => $app_password
+    ));
+  } catch (PDOException $e) {
+    $_SESSION['return'][] =  array(
+      'type' => 'danger',
+      'log' => array(__FUNCTION__, $_data_log),
+      'msg' => array('mysql_error', $e)
+    );
+    return false;
+  }
+
+  return true;
+}
 function flush_memcached() {
   try {
     $m = new Memcached();

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

@@ -681,7 +681,7 @@ jQuery(function($){
     $(this).html('<div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div> ');
     $.ajax({
       type: 'GET',
-      url: 'inc/ajax/transport_check.php',
+      url: '/inc/ajax/transport_check.php',
       dataType: 'text',
       data: $('#test_transport_form').serialize(),
       complete: function (data) {

+ 0 - 6
data/web/js/site/user.js

@@ -90,13 +90,7 @@ jQuery(function($){
           console.log('error reading last logins');
         },
         success: function (data) {
-          $('.last-ui-login').html('');
           $('.last-sasl-login').html('');
-          if (data.ui.time) {
-            $('.last-ui-login').html('<i class="bi bi-person-fill"></i> ' + lang.last_ui_login + ': ' + unix_time_format(data.ui.time));
-          } else {
-            $('.last-ui-login').text(lang.no_last_login);
-          }
           if (data.sasl) {
             $('.last-sasl-login').append('<ul class="list-group">');
             $.each(data.sasl, function (i, item) {

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

@@ -238,7 +238,9 @@
         "iam_username_field": "Username Feld",
         "iam_binddn": "Bind DN",
         "iam_use_ssl": "Benutze SSL",
-        "iam_use_tls": "Benutze TLS",
+        "iam_use_ssl_info": "Wenn SSL aktiviert ist und der Port auf 389 gesetzt wurde, wird dieser automatisch auf 636 geändert.",
+        "iam_use_tls": "Benutze StartTLS",
+        "iam_use_tls_info": "Wenn TLS aktiviert wird, muss der Standardport deines LDAP-Servers (389) verwendet werden. SSL-Ports können dabei nicht verwendet werden.",
         "iam_version": "Version",
         "ignore_ssl_error": "Ignoriere SSL Fehler",
         "import": "Importieren",
@@ -1333,7 +1335,7 @@
         "tag_in_subfolder": "In Unterordner",
         "tag_in_subject": "In Betreff",
         "text": "Text",
-        "tfa_info": "Zwei-Faktor-Authentifizierung hilft dabei, Ihr Konto zu schützen. Wenn Sie sie aktivieren, benötigen Sie möglicherweise App-Passwörter, um sich bei Apps oder Diensten anzumelden, die die Zwei-Faktor-Authentifizierung nicht unterstützen (z.B. Mailclients).",
+        "tfa_info": "Zwei-Faktor-Authentifizierung hilft dabei, Ihr Konto zu schützen. Wenn Sie sie aktivieren, benötigen Sie App-Passwörter, um sich bei Apps oder Diensten anzumelden, die die Zwei-Faktor-Authentifizierung nicht unterstützen (z.B. Mailclients).",
         "title": "Title",
         "tls_enforce_in": "TLS eingehend erzwingen",
         "tls_enforce_out": "TLS ausgehend erzwingen",

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

@@ -245,7 +245,9 @@
         "iam_username_field": "Username Field",
         "iam_binddn": "Bind DN",
         "iam_use_ssl": "Use SSL",
-        "iam_use_tls": "Use TLS",
+        "iam_use_ssl_info": "If enabling SSL, and port is set to 389, it will be automatically overridden to use 636.",
+        "iam_use_tls": "Use StartTLS",
+        "iam_use_tls_info": "If enabling TLS, you must use the default port for your LDAP server (389). SSL ports cannot be used.",
         "iam_version": "Version",
         "ignore_ssl_error": "Ignore SSL Errors",
         "import": "Import",
@@ -1355,7 +1357,7 @@
         "tag_in_subfolder": "In subfolder",
         "tag_in_subject": "In subject",
         "text": "Text",
-        "tfa_info": "Two-factor authentication helps protect your account. If you enable it, you may need app passwords to log in to apps or services that don't support two-factor authentication (e.g. Mailclients).",
+        "tfa_info": "Two-factor authentication helps protect your account. If you enable it, you need app passwords to log in to apps or services that don't support two-factor authentication (e.g. Mailclients).",
         "title": "Title",
         "tls_enforce_in": "Enforce TLS incoming",
         "tls_enforce_out": "Enforce TLS outgoing",

+ 1 - 0
data/web/reset-password.php

@@ -1,5 +1,6 @@
 <?php
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.user.inc.php';
 
 if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
   header('Location: /admin/dashboard');

+ 22 - 6
data/web/templates/admin/tab-config-identity-provider.twig

@@ -392,11 +392,11 @@
           <input type="hidden" name="authsource" value="ldap">
           <div class="row mb-2">
             <div class="col-md-3 d-flex align-items-center justify-content-md-end">
-              <i style="font-size: 16px; cursor: pointer;" class="bi bi-patch-question-fill m-2 ms-0" data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="bottom" title="{{ lang.admin.iam_host_info }}"></i>
+              <i style="font-size: 16px; cursor: pointer;" class="bi bi-patch-question-fill mx-2 ms-0" data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="bottom" title="{{ lang.admin.iam_host_info }}"></i>
               <label class="control-label" for="iam_ldap_host">{{ lang.admin.iam_host }}:</label>
             </div>
             <div class="col-12 col-md-9 col-lg-4 d-flex">
-            <input type="text" class="form-control" id="iam_ldap_host" name="host" value="{{ iam_settings.host }}" required>
+              <input type="text" class="form-control" id="iam_ldap_host" name="host" value="{{ iam_settings.host }}" required>
             </div>
           </div>
           <div class="row mb-2">
@@ -409,21 +409,37 @@
           </div>
           <div class="row mb-2">
             <div class="col-md-3 d-flex align-items-center justify-content-md-end">
+              <i style="font-size: 16px; cursor: pointer;" class="bi bi-patch-question-fill mx-2 ms-0" data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="bottom" title="{{ lang.admin.iam_use_ssl_info }}"></i>
               <label class="control-label">{{ lang.admin.iam_use_ssl }}</label>
             </div>
-            <div class="col-12 col-md-9">
+            <div class="col-12 col-md-9 d-flex align-items-center">
               <div class="form-check form-switch">
-                <input class="form-check-input" type="checkbox" role="switch" name="use_ssl" value="1" {% if iam_settings.use_ssl == 1 %}checked{% endif %}>
+                <input class="form-check-input"
+                       type="checkbox"
+                       role="switch"
+                       id="use_ssl"
+                       name="use_ssl"
+                       value="1"
+                       onchange="if(this.checked) document.getElementById('use_tls').checked = false"
+                       {% if iam_settings.use_ssl == 1 %}checked{% endif %}>
               </div>
             </div>
           </div>
           <div class="row mb-2">
             <div class="col-md-3 d-flex align-items-center justify-content-md-end">
+              <i style="font-size: 16px; cursor: pointer;" class="bi bi-patch-question-fill mx-2 ms-0" data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="bottom" title="{{ lang.admin.iam_use_tls_info }}"></i>
               <label class="control-label">{{ lang.admin.iam_use_tls }}</label>
             </div>
-            <div class="col-12 col-md-9">
+            <div class="col-12 col-md-9 d-flex align-items-center">
               <div class="form-check form-switch">
-                <input class="form-check-input" type="checkbox" role="switch" name="use_tls" value="1" {% if iam_settings.use_tls == 1 %}checked{% endif %}>
+                <input class="form-check-input"
+                       type="checkbox"
+                       role="switch"
+                       id="use_tls"
+                       name="use_tls"
+                       value="1"
+                       onchange="if(this.checked) document.getElementById('use_ssl').checked = false"
+                       {% if iam_settings.use_tls == 1 %}checked{% endif %}>
               </div>
             </div>
           </div>

+ 1 - 2
data/web/templates/user/tab-user-auth.twig

@@ -184,7 +184,7 @@
           </div>
           {% endif %}
         </div>
-        <div class="ms-auto col-xl-3 col-lg-5 col-md-12 col-12 d-flex flex-column well flex-grow-1">
+        <div class="ms-auto col-xl-3 col-lg-5 col-md-12 col-12 d-flex flex-column well flex-grow-1" id="recent-logins">
           <legend class="d-flex">
             <span>{{ lang.user.recent_successful_connections }}</span>
             <div id="spinner-last-login" class="ms-auto my-auto spinner-border spinner-border-sm d-none" role="status">
@@ -192,7 +192,6 @@
             </div>
           </legend>
           <hr>
-          <h6 class="last-ui-login"></h6>
           <div class="d-flex">
             <span class="clear-last-logins mt-auto mb-2">
               {{ lang.user.clear_recent_successful_connections }}

+ 2 - 2
docker-compose.yml

@@ -199,7 +199,7 @@ services:
             - phpfpm
 
     sogo-mailcow:
-      image: ghcr.io/mailcow/sogo:1.131
+      image: ghcr.io/mailcow/sogo:1.133
       environment:
         - DBNAME=${DBNAME}
         - DBUSER=${DBUSER}
@@ -477,7 +477,7 @@ services:
             - acme
 
     netfilter-mailcow:
-      image: ghcr.io/mailcow/netfilter:1.62
+      image: ghcr.io/mailcow/netfilter:1.61
       stop_grace_period: 30s
       restart: always
       privileged: true

+ 1 - 1
helper-scripts/backup_and_restore.sh

@@ -236,7 +236,7 @@ function restore() {
       if [[ $(find "${RESTORE_LOCATION}" \( -name '*x86*' -o -name '*aarch*' \) -exec basename {} \; | sed 's/^\.//' | sed 's/^\.//') == "" ]]; then
         echo -e "\e[33mCould not find a architecture signature of the loaded backup... Maybe the backup was done before the multiarch update?"
         sleep 2
-        echo -e "Continuing anyhow. If rspamd is crashing opon boot try remove the rspamd volume with docker volume rm ${CMPS_PRJ}_rspamd-vol-1 after you've stopped the stack.\e[0m"
+        echo -e "Continuing anyhow. If rspamd is crashing upon boot try remove the rspamd volume with docker volume rm ${CMPS_PRJ}_rspamd-vol-1 after you've stopped the stack.\e[0m"
         sleep 2
         docker stop $(docker ps -qf name=rspamd-mailcow)
         docker run -i --name mailcow-backup --rm \