mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-11-13 11:45:23 +00:00
Merge branch 'staging' into nightly
This commit is contained in:
33 files changed
+355
-145
No files matched your search
@@ -601,6 +601,13 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){
|
||||
|
||||
// validate mailcow_password
|
||||
$mailcow_password = $user_res['attributes']['mailcow_password'][0];
|
||||
// Keycloak returns binary LDAP attributes like userPassword Base64 encoded
|
||||
if (substr($mailcow_password, 0, 1) != '{') {
|
||||
$decoded_password = base64_decode($mailcow_password, true);
|
||||
if ($decoded_password !== false && substr($decoded_password, 0, 1) == '{') {
|
||||
$mailcow_password = $decoded_password;
|
||||
}
|
||||
}
|
||||
if (!verify_hash($mailcow_password, $pass)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,13 @@ function fwdhost($_action, $_data = null) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$hosts = array_unique(array_map(function ($host) {
|
||||
$net = explode('/', $host, 2);
|
||||
if (filter_var($net[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
$net[0] = inet_ntop(inet_pton($net[0]));
|
||||
}
|
||||
return implode('/', $net);
|
||||
}, $hosts));
|
||||
foreach ($hosts as $host) {
|
||||
try {
|
||||
$redis->hSet('WHITELISTED_FWD_HOST', $host, $source);
|
||||
|
||||
@@ -1399,6 +1399,18 @@ function set_tfa($_data) {
|
||||
case "webauthn":
|
||||
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
|
||||
|
||||
// Refuse incomplete WebAuthn registrations. Without this guard a request
|
||||
// that does not contain the parsed credential data (e.g. a plain form
|
||||
// POST) would silently insert an empty keyHandle while reporting success.
|
||||
if (empty($_data['registration']) || empty($_data['registration']->credentialId)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_data_log),
|
||||
'msg' => 'webauthn_registration_failed'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`)
|
||||
VALUES (?, ?, 'webauthn', ?, ?, ?, ?, '1')");
|
||||
$stmt->execute(array(
|
||||
@@ -1421,11 +1433,11 @@ function set_tfa($_data) {
|
||||
// Block TFA removal if force_tfa policy is active
|
||||
$is_forced_tfa = false;
|
||||
if ($_SESSION['mailcow_cc_role'] === 'user') {
|
||||
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?");
|
||||
$stmt_check = $pdo->prepare("SELECT JSON_VALUE(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?");
|
||||
$stmt_check->execute(array($username));
|
||||
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
||||
} else {
|
||||
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?");
|
||||
$stmt_check = $pdo->prepare("SELECT JSON_VALUE(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?");
|
||||
$stmt_check->execute(array($username));
|
||||
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
||||
}
|
||||
@@ -1692,11 +1704,11 @@ function unset_tfa_key($_data) {
|
||||
// Block key removal if force_tfa policy is active
|
||||
$is_forced_tfa = false;
|
||||
if ($_SESSION['mailcow_cc_role'] === 'user') {
|
||||
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?");
|
||||
$stmt_check = $pdo->prepare("SELECT JSON_VALUE(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?");
|
||||
$stmt_check->execute(array($username));
|
||||
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
||||
} else {
|
||||
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?");
|
||||
$stmt_check = $pdo->prepare("SELECT JSON_VALUE(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?");
|
||||
$stmt_check->execute(array($username));
|
||||
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
||||
}
|
||||
@@ -2425,7 +2437,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
||||
case "use_ssl":
|
||||
case "use_tls":
|
||||
case "login_provisioning":
|
||||
case "ignore_ssl_errors":
|
||||
case "ignore_ssl_error":
|
||||
$settings[$row["key"]] = boolval($row["value"]);
|
||||
break;
|
||||
default:
|
||||
@@ -2449,7 +2461,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
||||
if ($settings["authsource"] == "ldap"){
|
||||
$settings['use_ssl'] = !isset($settings['use_ssl']) ? false : $settings['use_ssl'];
|
||||
$settings['use_tls'] = !isset($settings['use_tls']) ? false : $settings['use_tls'];
|
||||
$settings['ignore_ssl_errors'] = !isset($settings['ignore_ssl_errors']) ? false : $settings['ignore_ssl_errors'];
|
||||
$settings['ignore_ssl_error'] = !isset($settings['ignore_ssl_error']) ? false : $settings['ignore_ssl_error'];
|
||||
}
|
||||
return $settings;
|
||||
break;
|
||||
@@ -3111,7 +3123,17 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
||||
case "get-keycloak-admin-token":
|
||||
// get access_token for service account of mailcow client
|
||||
if ($iam_settings['authsource'] !== 'keycloak') return false;
|
||||
if (isset($iam_settings['access_token'])) {
|
||||
// only introspect a cached access_token that is not (about to be) expired,
|
||||
// introspecting an expired token makes Keycloak log INTROSPECT_TOKEN_ERROR
|
||||
$token_not_expired = false;
|
||||
if (!empty($iam_settings['access_token'])) {
|
||||
$token_parts = explode('.', $iam_settings['access_token']);
|
||||
if (count($token_parts) === 3) {
|
||||
$token_payload = json_decode(base64_decode(strtr($token_parts[1], '-_', '+/')), true);
|
||||
$token_not_expired = is_array($token_payload) && isset($token_payload['exp']) && intval($token_payload['exp']) > time() + 30;
|
||||
}
|
||||
}
|
||||
if ($token_not_expired) {
|
||||
// check if access_token is valid
|
||||
$url = "{$iam_settings['server_url']}/realms/{$iam_settings['realm']}/protocol/openid-connect/token/introspect";
|
||||
$req = http_build_query(array(
|
||||
@@ -3162,6 +3184,8 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
||||
':key' => 'access_token',
|
||||
':value' => $res['access_token']
|
||||
));
|
||||
// keep the cached token in sync, so subsequent calls in the same run reuse it
|
||||
$iam_settings['access_token'] = $res['access_token'];
|
||||
return $res['access_token'];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2535,6 +2535,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':domain' => $domain,
|
||||
':tag_name' => $tag,
|
||||
));
|
||||
$stmt = $pdo->prepare("UPDATE `domain` SET `modified` = NOW() WHERE `domain` = :domain");
|
||||
$stmt->execute(array(
|
||||
':domain' => $domain,
|
||||
));
|
||||
}
|
||||
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -2706,6 +2710,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':domain' => $domain,
|
||||
':tag_name' => $tag,
|
||||
));
|
||||
$stmt = $pdo->prepare("UPDATE `domain` SET `modified` = NOW() WHERE `domain` = :domain");
|
||||
$stmt->execute(array(
|
||||
':domain' => $domain,
|
||||
));
|
||||
}
|
||||
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -3188,6 +3196,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':username' => $username,
|
||||
':tag_name' => $tag,
|
||||
));
|
||||
$stmt = $pdo->prepare("UPDATE `mailbox` SET `modified` = NOW() WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
));
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
@@ -4011,6 +4023,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$stmt->execute(array(':goto' => '(^|,)'.preg_quote($_data, '/').'($|,)'));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($row = array_shift($rows)) {
|
||||
if (!hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['address'])) {
|
||||
continue;
|
||||
}
|
||||
// Keep old array for backward compatibility
|
||||
$data['fixed_sender_aliases'][] = $row['address'];
|
||||
// Split into allowed/blocked for proper display
|
||||
@@ -4964,6 +4979,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
else if ($SaslLogs['service'] == 'SSO') {
|
||||
$last_sso_login = strtotime($SaslLogs['datetime']);
|
||||
}
|
||||
else if ($SaslLogs['service'] == 'SOGO') {
|
||||
$last_sogo_login = strtotime($SaslLogs['datetime']);
|
||||
}
|
||||
}
|
||||
if (!isset($last_imap_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
|
||||
$last_imap_login = 0;
|
||||
@@ -4977,10 +4995,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
if (!isset($last_sso_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
|
||||
$last_sso_login = 0;
|
||||
}
|
||||
if (!isset($last_sogo_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
|
||||
$last_sogo_login = 0;
|
||||
}
|
||||
$mailboxdata['last_imap_login'] = $last_imap_login;
|
||||
$mailboxdata['last_smtp_login'] = $last_smtp_login;
|
||||
$mailboxdata['last_pop3_login'] = $last_pop3_login;
|
||||
$mailboxdata['last_sso_login'] = $last_sso_login;
|
||||
$mailboxdata['last_sogo_login'] = $last_sogo_login;
|
||||
|
||||
if (!isset($_extra) || $_extra != 'reduced') {
|
||||
$rl = ratelimit('get', 'mailbox', $_data);
|
||||
@@ -5018,7 +5040,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$stmt->execute(array(':username' => $_data));
|
||||
$sender_acl_rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($sender_acl_rows as $sender_acl_row) {
|
||||
$mailboxdata['sender_acl'][] = $sender_acl_row['send_as'];
|
||||
$send_as = $sender_acl_row['send_as'];
|
||||
if ($_SESSION['mailcow_cc_role'] == 'domainadmin' && $send_as != '*') {
|
||||
$in_scope = (substr($send_as, 0, 1) == '@')
|
||||
? hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], substr($send_as, 1))
|
||||
: hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $send_as);
|
||||
if (!$in_scope) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$mailboxdata['sender_acl'][] = $send_as;
|
||||
}
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT `tag_name`
|
||||
@@ -5958,6 +5989,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':domain' => $domain,
|
||||
':tag_name' => $tag,
|
||||
));
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$stmt = $pdo->prepare("UPDATE `domain` SET `modified` = NOW() WHERE `domain` = :domain");
|
||||
$stmt->execute(array(
|
||||
':domain' => $domain,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6010,6 +6047,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':username' => $username,
|
||||
':tag_name' => $tag,
|
||||
));
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$stmt = $pdo->prepare("UPDATE `mailbox` SET `modified` = NOW() WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user