Compare commits

..

10 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
64fe2e6d0d Fix DEV_MODE bypass to work when SESS_REMOTE_UA not set
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
2025-12-12 16:36:32 +00:00
copilot-swe-agent[bot]
f01ada9377 Add isset check for DEV_MODE to prevent undefined index
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
2025-12-12 16:30:39 +00:00
copilot-swe-agent[bot]
ae6420dc80 Fix DEV_MODE access using $GLOBALS instead of global keyword
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
2025-12-12 16:23:16 +00:00
copilot-swe-agent[bot]
a1b0004be9 Add comment explaining User-Agent length validation
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
2025-12-12 16:14:09 +00:00
copilot-swe-agent[bot]
88376566f9 Add validation for User-Agent in DEV_MODE bypass
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
2025-12-12 16:12:37 +00:00
copilot-swe-agent[bot]
89b4676641 Allow User-Agent changes in DEV_MODE for responsive testing
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
2025-12-12 16:11:05 +00:00
copilot-swe-agent[bot]
a710b0e580 Fix line ending issue in functions.inc.php - preserve CRLF
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
2025-12-12 15:59:55 +00:00
copilot-swe-agent[bot]
b3e6891802 Fix session handling - explicitly start session after destroy
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
2025-12-12 15:53:53 +00:00
copilot-swe-agent[bot]
19225b223c Fix User-Agent validation error after session expiry and regeneration
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
2025-12-12 15:51:56 +00:00
copilot-swe-agent[bot]
3e5a58be8f Initial plan 2025-12-12 15:45:29 +00:00
22 changed files with 77 additions and 165 deletions

View File

@@ -246,25 +246,6 @@ while true; do
done
VALIDATED_CONFIG_DOMAINS+=("${VALIDATED_CONFIG_DOMAINS_SUBDOMAINS[*]}")
done
# Fetch alias domains where target domain has MTA-STS enabled
if [[ ${AUTODISCOVER_SAN} == "y" ]]; then
SQL_ALIAS_DOMAINS=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT ad.alias_domain FROM alias_domain ad INNER JOIN mta_sts m ON ad.target_domain = m.domain WHERE ad.active = 1 AND m.active = 1" -Bs)
if [[ $? -eq 0 ]]; then
while read alias_domain; do
if [[ -z "${alias_domain}" ]]; then
# ignore empty lines
continue
fi
# Only add mta-sts subdomain for alias domains
if [[ "mta-sts.${alias_domain}" != "${MAILCOW_HOSTNAME}" ]]; then
if check_domain "mta-sts.${alias_domain}"; then
VALIDATED_CONFIG_DOMAINS+=("mta-sts.${alias_domain}")
fi
fi
done <<< "${SQL_ALIAS_DOMAINS}"
fi
fi
fi
if check_domain ${MAILCOW_HOSTNAME}; then

View File

@@ -80,21 +80,14 @@ if ($isSOGoRequest) {
}
if ($result === false){
// If it's a SOGo Request, don't check for protocol access
if ($isSOGoRequest) {
$service = 'SOGO';
$post['service'] = 'NONE';
} else {
$service = $post['service'];
}
$result = apppass_login($post['username'], $post['password'], array(
'service' => $post['service'],
$service = ($isSOGoRequest) ? false : array($post['service'] => true);
$result = apppass_login($post['username'], $post['password'], $service, array(
'is_internal' => true,
'remote_addr' => $post['real_rip']
));
if ($result) {
error_log('MAILCOWAUTH: App auth for user ' . $post['username'] . " with service " . $service . " from IP " . $post['real_rip']);
set_sasl_log($post['username'], $post['real_rip'], $service);
error_log('MAILCOWAUTH: App auth for user ' . $post['username'] . " with service " . $post['service'] . " from IP " . $post['real_rip']);
set_sasl_log($post['username'], $post['real_rip'], $post['service']);
}
}
if ($result === false){

View File

@@ -79,7 +79,7 @@ if (empty($_SERVER['PHP_AUTH_USER']) || empty($_SERVER['PHP_AUTH_PW'])) {
exit(0);
}
$login_role = check_login($login_user, $login_pass, array('service' => 'EAS'));
$login_role = check_login($login_user, $login_pass, array('eas' => TRUE));
if ($login_role === "user") {
header("Content-Type: application/xml");

View File

@@ -129,16 +129,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
);
}
// Check if domain is an alias domain and get target domain's MTA-STS
$alias_domain_details = mailbox('get', 'alias_domain_details', $domain);
$mta_sts_domain = $domain;
if ($alias_domain_details !== false && !empty($alias_domain_details['target_domain'])) {
// This is an alias domain, check target domain for MTA-STS
$mta_sts_domain = $alias_domain_details['target_domain'];
}
$mta_sts = mailbox('get', 'mta_sts', $mta_sts_domain);
$mta_sts = mailbox('get', 'mta_sts', $domain);
if (count($mta_sts) > 0 && $mta_sts['active'] == 1) {
if (!in_array($domain, $alias_domains)) {
$records[] = array(

View File

@@ -1,11 +1,10 @@
<?php
function check_login($user, $pass, $extra = null) {
function check_login($user, $pass, $app_passwd_data = false, $extra = null) {
global $pdo;
global $redis;
$is_internal = $extra['is_internal'];
$role = $extra['role'];
$extra['service'] = !isset($extra['service']) ? 'NONE' : $extra['service'];
// Try validate admin
if (!isset($role) || $role == "admin") {
@@ -26,20 +25,34 @@ function check_login($user, $pass, $extra = null) {
// Try validate app password
if (!isset($role) || $role == "app") {
$result = apppass_login($user, $pass, $extra);
$result = apppass_login($user, $pass, $app_passwd_data);
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, $extra['service'], $pass);
set_sasl_log($user, $real_rip, $service, $pass);
return $result;
}
}
// Try validate user
if (!isset($role) || $role == "user") {
$result = user_login($user, $pass, $extra);
$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, $extra['service']);
set_sasl_log($user, $real_rip, $service);
return $result;
}
}
@@ -180,7 +193,7 @@ function user_login($user, $pass, $extra = null){
global $iam_settings;
$is_internal = $extra['is_internal'];
$extra['service'] = !isset($extra['service']) ? 'NONE' : $extra['service'];
$service = $extra['service'];
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
@@ -223,10 +236,10 @@ function user_login($user, $pass, $extra = null){
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($row)) {
// check if user has access to service (imap, smtp, pop3, sieve, dav, eas) if service is set
// check if user has access to service (imap, smtp, pop3, sieve) if service is set
$row['attributes'] = json_decode($row['attributes'], true);
if ($extra['service'] != 'NONE') {
$key = strtolower($extra['service']) . "_access";
if (isset($service)) {
$key = strtolower($service) . "_access";
if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') {
return false;
}
@@ -240,8 +253,8 @@ function user_login($user, $pass, $extra = null){
// check if user has access to service (imap, smtp, pop3, sieve) if service is set
$row['attributes'] = json_decode($row['attributes'], true);
if ($extra['service'] != 'NONE') {
$key = strtolower($extra['service']) . "_access";
if (isset($service)) {
$key = strtolower($service) . "_access";
if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') {
return false;
}
@@ -395,7 +408,7 @@ function user_login($user, $pass, $extra = null){
return false;
}
function apppass_login($user, $pass, $extra = null){
function apppass_login($user, $pass, $app_passwd_data, $extra = null){
global $pdo;
$is_internal = $extra['is_internal'];
@@ -411,8 +424,20 @@ function apppass_login($user, $pass, $extra = null){
return false;
}
$extra['service'] = !isset($extra['service']) ? 'NONE' : $extra['service'];
if (!$is_internal && $extra['service'] == 'NONE') {
$protocol = false;
if ($app_passwd_data['eas']){
$protocol = 'eas';
} else if ($app_passwd_data['dav']){
$protocol = 'dav';
} else if ($app_passwd_data['smtp']){
$protocol = 'smtp';
} else if ($app_passwd_data['imap']){
$protocol = 'imap';
} else if ($app_passwd_data['sieve']){
$protocol = 'sieve';
} else if ($app_passwd_data['pop3']){
$protocol = 'pop3';
} else if (!$is_internal) {
return false;
}
@@ -433,7 +458,7 @@ function apppass_login($user, $pass, $extra = null){
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
if ($extra['service'] != 'NONE' && $row[strtolower($extra['service']) . '_access'] != '1'){
if ($protocol && $row[$protocol . '_access'] != '1'){
continue;
}

View File

@@ -3397,6 +3397,8 @@ function set_user_loggedin_session($user) {
session_regenerate_id(true);
$_SESSION['mailcow_cc_username'] = $user;
$_SESSION['mailcow_cc_role'] = 'user';
// Update User-Agent after session regeneration to prevent validation errors
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
$sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass");
$_SESSION['sogo-sso-user-allowed'][] = $user;
$_SESSION['sogo-sso-pass'] = $sogo_sso_pass;

View File

@@ -1075,8 +1075,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
$_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
$_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
$_data['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0;
$_data['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0;
}
$active = (isset($_data['active'])) ? intval($_data['active']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['active']);
$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
@@ -1087,8 +1085,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$pop3_access = (isset($_data['pop3_access'])) ? intval($_data['pop3_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
$smtp_access = (isset($_data['smtp_access'])) ? intval($_data['smtp_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
$sieve_access = (isset($_data['sieve_access'])) ? intval($_data['sieve_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
$eas_access = (isset($_data['eas_access'])) ? intval($_data['eas_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['eas_access']);
$dav_access = (isset($_data['dav_access'])) ? intval($_data['dav_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['dav_access']);
$relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : 0;
$quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
$quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
@@ -1107,8 +1103,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'pop3_access' => strval($pop3_access),
'smtp_access' => strval($smtp_access),
'sieve_access' => strval($sieve_access),
'eas_access' => strval($eas_access),
'dav_access' => strval($dav_access),
'relayhost' => strval($relayhost),
'passwd_update' => time(),
'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']),
@@ -1727,16 +1721,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
$attr['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0;
$attr['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0;
}
else {
$attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);
$attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
$attr['smtp_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
$attr['sieve_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
$attr['eas_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['eas_access']);
$attr['dav_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['dav_access']);
}
if (isset($_data['acl'])) {
$_data['acl'] = (array)$_data['acl'];
@@ -3053,8 +3043,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
$_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
$_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
$_data['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0;
$_data['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0;
}
if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
@@ -3064,8 +3052,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
(int)$pop3_access = (isset($_data['pop3_access']) && hasACLAccess("protocol_access")) ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']);
(int)$smtp_access = (isset($_data['smtp_access']) && hasACLAccess("protocol_access")) ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']);
(int)$sieve_access = (isset($_data['sieve_access']) && hasACLAccess("protocol_access")) ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']);
(int)$eas_access = (isset($_data['eas_access']) && hasACLAccess("protocol_access")) ? intval($_data['eas_access']) : intval($is_now['attributes']['eas_access']);
(int)$dav_access = (isset($_data['dav_access']) && hasACLAccess("protocol_access")) ? intval($_data['dav_access']) : intval($is_now['attributes']['dav_access']);
(int)$relayhost = (isset($_data['relayhost']) && hasACLAccess("mailbox_relayhost")) ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']);
(int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576);
$name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name'];
@@ -3349,8 +3335,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access),
`attributes` = JSON_SET(`attributes`, '$.relayhost', :relayhost),
`attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access),
`attributes` = JSON_SET(`attributes`, '$.eas_access', :eas_access),
`attributes` = JSON_SET(`attributes`, '$.dav_access', :dav_access),
`attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email),
`attributes` = JSON_SET(`attributes`, '$.attribute_hash', :attribute_hash)
WHERE `username` = :username");
@@ -3365,8 +3349,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':pop3_access' => $pop3_access,
':sieve_access' => $sieve_access,
':smtp_access' => $smtp_access,
':eas_access' => $eas_access,
':dav_access' => $dav_access,
':recovery_email' => $pw_recovery_email,
':relayhost' => $relayhost,
':username' => $username,
@@ -3749,8 +3731,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
$attr['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0;
$attr['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0;
}
else {
foreach ($is_now as $key => $value){

View File

@@ -4,7 +4,7 @@ function init_db_schema()
try {
global $pdo;
$db_version = "28012026_1000";
$db_version = "10312025_0525";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -1394,8 +1394,6 @@ function init_db_schema()
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.imap_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.imap_access') IS NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.pop3_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.pop3_access') IS NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.smtp_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.smtp_access') IS NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.eas_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.eas_access') IS NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.dav_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.dav_access') IS NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.mailbox_format', \"maildir:\") WHERE JSON_VALUE(`attributes`, '$.mailbox_format') IS NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_notification', \"never\") WHERE JSON_VALUE(`attributes`, '$.quarantine_notification') IS NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_category', \"reject\") WHERE JSON_VALUE(`attributes`, '$.quarantine_category') IS NULL;");

View File

@@ -121,7 +121,7 @@ class mailcowPdo extends OAuth2\Storage\Pdo {
$this->config['user_table'] = 'mailbox';
}
public function checkUserCredentials($username, $password) {
if (check_login($username, $password, array("role" => "user", "service" => "NONE")) == 'user') {
if (check_login($username, $password) == 'user') {
return true;
}
return false;

View File

@@ -43,6 +43,9 @@ if (!isset($_SESSION['SESS_REMOTE_UA'])) {
if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $SESSION_LIFETIME)) {
session_unset();
session_destroy();
session_start();
// After destroying session, we need to reset the User-Agent for the new session
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
}
$_SESSION['LAST_ACTIVITY'] = time();
@@ -134,6 +137,12 @@ function session_check() {
return true;
}
if (!isset($_SESSION['SESS_REMOTE_UA']) || ($_SESSION['SESS_REMOTE_UA'] != $_SERVER['HTTP_USER_AGENT'])) {
// In development mode, allow User-Agent changes (e.g., for responsive testing in dev tools)
// Validate UA is not empty and has reasonable length (most UAs are under 200 chars, 500 is safe upper limit)
if (isset($GLOBALS['DEV_MODE']) && $GLOBALS['DEV_MODE'] && !empty($_SERVER['HTTP_USER_AGENT']) && strlen($_SERVER['HTTP_USER_AGENT']) < 500) {
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
return true;
}
$_SESSION['return'][] = array(
'type' => 'warning',
'msg' => 'session_ua'

View File

@@ -44,12 +44,14 @@ if (isset($_GET["cancel_tfa_login"])) {
if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
$login_user = strtolower(trim($_POST["login_user"]));
$as = check_login($login_user, $_POST["pass_user"], array("role" => "admin", "service" => "MAILCOWUI"));
$as = check_login($login_user, $_POST["pass_user"], false, array("role" => "admin"));
if ($as == "admin") {
session_regenerate_id(true);
$_SESSION['mailcow_cc_username'] = $login_user;
$_SESSION['mailcow_cc_role'] = "admin";
// Update User-Agent after session regeneration to prevent validation errors
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
header("Location: /admin/dashboard");
die();
}

View File

@@ -7,6 +7,8 @@ if (!empty($_GET['sso_token'])) {
session_regenerate_id(true);
$_SESSION['mailcow_cc_username'] = $username;
$_SESSION['mailcow_cc_role'] = 'domainadmin';
// Update User-Agent after session regeneration to prevent validation errors
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
header('Location: /domainadmin/mailbox');
}
}
@@ -55,12 +57,14 @@ if (isset($_GET["cancel_tfa_login"])) {
if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
$login_user = strtolower(trim($_POST["login_user"]));
$as = check_login($login_user, $_POST["pass_user"], array("role" => "domain_admin", "service" => "MAILCOWUI"));
$as = check_login($login_user, $_POST["pass_user"], false, array("role" => "domain_admin"));
if ($as == "domainadmin") {
session_regenerate_id(true);
$_SESSION['mailcow_cc_username'] = $login_user;
$_SESSION['mailcow_cc_role'] = "domainadmin";
// Update User-Agent after session regeneration to prevent validation errors
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
header("Location: /domainadmin/mailbox");
die();
}

View File

@@ -119,7 +119,7 @@ if (isset($_GET["cancel_tfa_login"])) {
if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
$login_user = strtolower(trim($_POST["login_user"]));
$as = check_login($login_user, $_POST["pass_user"], array("role" => "user", "service" => "MAILCOWUI"));
$as = check_login($login_user, $_POST["pass_user"], false, array("role" => "user"));
if ($as == "user") {
set_user_loggedin_session($login_user);

View File

@@ -215,12 +215,6 @@ $MAILBOX_DEFAULT_ATTRIBUTES['smtp_access'] = true;
// Mailbox has sieve access by default
$MAILBOX_DEFAULT_ATTRIBUTES['sieve_access'] = true;
// Mailbox has ActiveSync/EAS access by default
$MAILBOX_DEFAULT_ATTRIBUTES['eas_access'] = true;
// Mailbox has CalDAV/CardDAV (DAV) access by default
$MAILBOX_DEFAULT_ATTRIBUTES['dav_access'] = true;
// Mailbox receives notifications about...
// "add_header" - mail that was put into the Junk folder
// "reject" - mail that was rejected

View File

@@ -352,12 +352,6 @@ $(document).ready(function() {
if (template.sieve_access == 1){
protocol_access.push("sieve");
}
if (template.eas_access == 1){
protocol_access.push("eas");
}
if (template.dav_access == 1){
protocol_access.push("dav");
}
$('#protocol_access').selectpicker('val', protocol_access);
var acl = [];
@@ -939,8 +933,6 @@ jQuery(function($){
item.imap_access = '<i class="text-' + (item.attributes.imap_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.imap_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
item.smtp_access = '<i class="text-' + (item.attributes.smtp_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.smtp_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
item.sieve_access = '<i class="text-' + (item.attributes.sieve_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sieve_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
item.eas_access = '<i class="text-' + (item.attributes.eas_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.eas_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
item.dav_access = '<i class="text-' + (item.attributes.dav_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.dav_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
if (item.attributes.quarantine_notification === 'never') {
item.quarantine_notification = lang.never;
} else if (item.attributes.quarantine_notification === 'hourly') {
@@ -1104,18 +1096,6 @@ jQuery(function($){
defaultContent: '',
className: 'none'
},
{
title: 'EAS',
data: 'eas_access',
defaultContent: '',
className: 'none'
},
{
title: 'DAV',
data: 'dav_access',
defaultContent: '',
className: 'none'
},
{
title: lang.quarantine_notification,
data: 'quarantine_notification',
@@ -1229,8 +1209,6 @@ jQuery(function($){
item.attributes.imap_access = '<i class="text-' + (item.attributes.imap_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.imap_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.imap_access == 1 ? '1' : '0') + '</span></i>';
item.attributes.smtp_access = '<i class="text-' + (item.attributes.smtp_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.smtp_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.smtp_access == 1 ? '1' : '0') + '</span></i>';
item.attributes.sieve_access = '<i class="text-' + (item.attributes.sieve_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sieve_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sieve_access == 1 ? '1' : '0') + '</span></i>';
item.attributes.eas_access = '<i class="text-' + (item.attributes.eas_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.eas_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.eas_access == 1 ? '1' : '0') + '</span></i>';
item.attributes.dav_access = '<i class="text-' + (item.attributes.dav_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.dav_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.dav_access == 1 ? '1' : '0') + '</span></i>';
item.attributes.sogo_access = '<i class="text-' + (item.attributes.sogo_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sogo_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sogo_access == 1 ? '1' : '0') + '</span></i>';
if (item.attributes.quarantine_notification === 'never') {
item.attributes.quarantine_notification = lang.never;
@@ -1339,16 +1317,6 @@ jQuery(function($){
data: 'attributes.sieve_access',
defaultContent: '',
},
{
title: 'EAS',
data: 'attributes.eas_access',
defaultContent: '',
},
{
title: 'DAV',
data: 'attributes.dav_access',
defaultContent: '',
},
{
title: 'SOGO',
data: 'attributes.sogo_access',

View File

@@ -7,30 +7,7 @@ if (!isset($_SERVER['HTTP_HOST']) || strpos($_SERVER['HTTP_HOST'], 'mta-sts.') !
}
$host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']);
$domain = idn_to_ascii(strtolower(str_replace('mta-sts.', '', $host)), 0, INTL_IDNA_VARIANT_UTS46);
// Validate domain or return 404 on error
if ($domain === false || empty($domain)) {
http_response_code(404);
exit;
}
// Check if domain is an alias domain and resolve to target domain
try {
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain");
$stmt->execute(array(':domain' => $domain));
$alias_row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($alias_row !== false && !empty($alias_row['target_domain'])) {
// This is an alias domain, use the target domain for MTA-STS lookup
$domain = $alias_row['target_domain'];
}
} catch (PDOException $e) {
// On database error, return 404
http_response_code(404);
exit;
}
$domain = str_replace('mta-sts.', '', $host);
$mta_sts = mailbox('get', 'mta_sts', $domain);
if (count($mta_sts) == 0 ||

View File

@@ -12,21 +12,18 @@ $session_var_pass = 'sogo-sso-pass';
if (isset($_SERVER['PHP_AUTH_USER'])) {
// load prerequisites only when required
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
$username = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];
// Determine service type for protocol access check
$service = 'NONE';
$is_eas = false;
$is_dav = false;
$original_uri = isset($_SERVER['HTTP_X_ORIGINAL_URI']) ? $_SERVER['HTTP_X_ORIGINAL_URI'] : '';
if (preg_match('/^(\/SOGo|)\/dav.*/', $original_uri) === 1) {
$service = 'DAV';
$is_dav = true;
}
elseif (preg_match('/^(\/SOGo|)\/Microsoft-Server-ActiveSync.*/', $original_uri) === 1) {
$service = 'EAS';
$is_eas = true;
}
$login_check = check_login($username, $password, array('service' => $service));
$login_check = check_login($username, $password, array('dav' => $is_dav, 'eas' => $is_eas));
if ($login_check === 'user') {
header("X-User: $username");
header("X-Auth: Basic ".base64_encode("$username:$password"));
@@ -60,6 +57,7 @@ elseif (isset($_GET['login'])) {
$_SESSION['mailcow_cc_role'] = "user";
}
// update sasl logs
$service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV';
$stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES ('SSO', 0, :username, :remote_addr)");
$stmt->execute(array(
':username' => $login,

View File

@@ -108,8 +108,6 @@
<option value="pop3"{% if template.attributes.pop3_access == '1' %} selected{% endif %}>POP3</option>
<option value="smtp"{% if template.attributes.smtp_access == '1' %} selected{% endif %}>SMTP</option>
<option value="sieve"{% if template.attributes.sieve_access == '1' %} selected{% endif %}>Sieve</option>
<option value="eas"{% if template.attributes.eas_access == '1' %} selected{% endif %}>ActiveSync</option>
<option value="dav"{% if template.attributes.dav_access == '1' %} selected{% endif %}>CalDAV/CardDAV</option>
</select>
</div>
</div>

View File

@@ -281,8 +281,6 @@
<option value="pop3"{% if result.attributes.pop3_access == '1' %} selected{% endif %}>POP3</option>
<option value="smtp"{% if result.attributes.smtp_access == '1' %} selected{% endif %}>SMTP</option>
<option value="sieve"{% if result.attributes.sieve_access == '1' %} selected{% endif %}>Sieve</option>
<option value="eas"{% if result.attributes.eas_access == '1' %} selected{% endif %}>ActiveSync</option>
<option value="dav"{% if result.attributes.dav_access == '1' %} selected{% endif %}>CalDAV/CardDAV</option>
</select>
</div>
</div>

View File

@@ -148,8 +148,6 @@
<option value="pop3">POP3</option>
<option value="smtp">SMTP</option>
<option value="sieve">Sieve</option>
<option value="eas">ActiveSync</option>
<option value="dav">CalDAV/CardDAV</option>
</select>
</div>
</div>
@@ -337,8 +335,6 @@
<option value="pop3" selected>POP3</option>
<option value="smtp" selected>SMTP</option>
<option value="sieve" selected>Sieve</option>
<option value="activesync" selected>ActiveSync</option>
<option value="dav" selected>CalDAV/CardDAV</option>
</select>
</div>
</div>

View File

@@ -55,8 +55,6 @@
{% if mailboxdata.attributes.smtp_access == 1 %}<div class="badge fs-6 bg-success m-2">SMTP <i class="bi bi-check-lg"></i></div>{% else %}<div class="badge fs-6 bg-danger m-2">SMTP <i class="bi bi-x-lg"></i></div>{% endif %}
{% if mailboxdata.attributes.sieve_access == 1 %}<div class="badge fs-6 bg-success m-2">Sieve <i class="bi bi-check-lg"></i></div>{% else %}<div class="badge fs-6 bg-danger m-2">Sieve <i class="bi bi-x-lg"></i></div>{% endif %}
{% if mailboxdata.attributes.pop3_access == 1 %}<div class="badge fs-6 bg-success m-2">POP3 <i class="bi bi-check-lg"></i></div>{% else %}<div class="badge fs-6 bg-danger m-2">POP3 <i class="bi bi-x-lg"></i></div>{% endif %}
{% if mailboxdata.attributes.eas_access == 1 %}<div class="badge fs-6 bg-success m-2">ActiveSync <i class="bi bi-check-lg"></i></div>{% else %}<div class="badge fs-6 bg-danger m-2">ActiveSync <i class="bi bi-x-lg"></i></div>{% endif %}
{% if mailboxdata.attributes.dav_access == 1 %}<div class="badge fs-6 bg-success m-2">CalDAV/CardDAV <i class="bi bi-check-lg"></i></div>{% else %}<div class="badge fs-6 bg-danger m-2">CalDAV/CardDAV <i class="bi bi-x-lg"></i></div>{% endif %}
</div>
</div>
</div>

View File

@@ -465,7 +465,7 @@ services:
condition: service_started
unbound-mailcow:
condition: service_healthy
image: ghcr.io/mailcow/acme:1.95
image: ghcr.io/mailcow/acme:1.94
dns:
- ${IPV4_NETWORK:-172.22.1}.254
environment: