diff --git a/data/conf/dovecot/auth/mailcowauth.php b/data/conf/dovecot/auth/mailcowauth.php index 06c0bd995..d1c7381f6 100644 --- a/data/conf/dovecot/auth/mailcowauth.php +++ b/data/conf/dovecot/auth/mailcowauth.php @@ -80,14 +80,21 @@ if ($isSOGoRequest) { } if ($result === false){ // If it's a SOGo Request, don't check for protocol access - $service = ($isSOGoRequest) ? false : array($post['service'] => true); - $result = apppass_login($post['username'], $post['password'], $service, array( + if ($isSOGoRequest) { + $service = 'SOGO'; + $post['service'] = 'NONE'; + } else { + $service = $post['service']; + } + + $result = apppass_login($post['username'], $post['password'], array( + 'service' => $post['service'], 'is_internal' => true, 'remote_addr' => $post['real_rip'] )); if ($result) { - 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']); + 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); } } if ($result === false){ diff --git a/data/web/autodiscover.php b/data/web/autodiscover.php index 224f94f71..d3cda4004 100644 --- a/data/web/autodiscover.php +++ b/data/web/autodiscover.php @@ -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('eas' => TRUE)); +$login_role = check_login($login_user, $login_pass, array('service' => 'EAS')); if ($login_role === "user") { header("Content-Type: application/xml"); diff --git a/data/web/inc/functions.auth.inc.php b/data/web/inc/functions.auth.inc.php index 059dd4cd9..3903ba642 100644 --- a/data/web/inc/functions.auth.inc.php +++ b/data/web/inc/functions.auth.inc.php @@ -1,10 +1,11 @@ fetch(PDO::FETCH_ASSOC); if (!empty($row)) { - // check if user has access to service (imap, smtp, pop3, sieve) if service is set + // check if user has access to service (imap, smtp, pop3, sieve, dav, eas) if service is set $row['attributes'] = json_decode($row['attributes'], true); - if (isset($service)) { - $key = strtolower($service) . "_access"; + if ($extra['service'] != 'NONE') { + $key = strtolower($extra['service']) . "_access"; if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') { return false; } @@ -253,8 +240,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 (isset($service)) { - $key = strtolower($service) . "_access"; + if ($extra['service'] != 'NONE') { + $key = strtolower($extra['service']) . "_access"; if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') { return false; } @@ -408,7 +395,7 @@ function user_login($user, $pass, $extra = null){ return false; } -function apppass_login($user, $pass, $app_passwd_data, $extra = null){ +function apppass_login($user, $pass, $extra = null){ global $pdo; $is_internal = $extra['is_internal']; @@ -424,20 +411,8 @@ function apppass_login($user, $pass, $app_passwd_data, $extra = null){ return false; } - $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) { + $extra['service'] = !isset($extra['service']) ? 'NONE' : $extra['service']; + if (!$is_internal && $extra['service'] == 'NONE') { return false; } @@ -458,7 +433,7 @@ function apppass_login($user, $pass, $app_passwd_data, $extra = null){ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { - if ($protocol && $row[$protocol . '_access'] != '1'){ + if ($extra['service'] != 'NONE' && $row[strtolower($extra['service']) . '_access'] != '1'){ continue; } diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index d8e4e178a..eb7d82dff 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -1075,6 +1075,8 @@ 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']); @@ -1085,6 +1087,8 @@ 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']); @@ -1103,6 +1107,8 @@ 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']), @@ -1721,12 +1727,16 @@ 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']; @@ -3043,6 +3053,8 @@ 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']; @@ -3052,6 +3064,8 @@ 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']; @@ -3335,6 +3349,8 @@ 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"); @@ -3349,6 +3365,8 @@ 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, @@ -3731,6 +3749,8 @@ 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){ diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index ffaf12093..67e98a5a8 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -4,7 +4,7 @@ function init_db_schema() try { global $pdo; - $db_version = "10312025_0525"; + $db_version = "28012026_1000"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -1394,6 +1394,8 @@ 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;"); diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index deb5da8fa..198e675aa 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -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) == 'user') { + if (check_login($username, $password, array("role" => "user", "service" => "NONE")) == 'user') { return true; } return false; diff --git a/data/web/inc/triggers.admin.inc.php b/data/web/inc/triggers.admin.inc.php index df46a459c..2a02ba511 100644 --- a/data/web/inc/triggers.admin.inc.php +++ b/data/web/inc/triggers.admin.inc.php @@ -44,7 +44,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"], false, array("role" => "admin")); + $as = check_login($login_user, $_POST["pass_user"], array("role" => "admin", "service" => "MAILCOWUI")); if ($as == "admin") { session_regenerate_id(true); diff --git a/data/web/inc/triggers.domainadmin.inc.php b/data/web/inc/triggers.domainadmin.inc.php index a9f913688..764d9009b 100644 --- a/data/web/inc/triggers.domainadmin.inc.php +++ b/data/web/inc/triggers.domainadmin.inc.php @@ -55,7 +55,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"], false, array("role" => "domain_admin")); + $as = check_login($login_user, $_POST["pass_user"], array("role" => "domain_admin", "service" => "MAILCOWUI")); if ($as == "domainadmin") { session_regenerate_id(true); diff --git a/data/web/inc/triggers.user.inc.php b/data/web/inc/triggers.user.inc.php index 36176c694..cc33596f9 100644 --- a/data/web/inc/triggers.user.inc.php +++ b/data/web/inc/triggers.user.inc.php @@ -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"], false, array("role" => "user")); + $as = check_login($login_user, $_POST["pass_user"], array("role" => "user", "service" => "MAILCOWUI")); if ($as == "user") { set_user_loggedin_session($login_user); diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php index f206ad633..6d1965542 100644 --- a/data/web/inc/vars.inc.php +++ b/data/web/inc/vars.inc.php @@ -217,6 +217,12 @@ $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 diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index df61f8720..7010077db 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -352,6 +352,12 @@ $(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 = []; @@ -933,6 +939,8 @@ jQuery(function($){ item.imap_access = ''; item.smtp_access = ''; item.sieve_access = ''; + item.eas_access = ''; + item.dav_access = ''; if (item.attributes.quarantine_notification === 'never') { item.quarantine_notification = lang.never; } else if (item.attributes.quarantine_notification === 'hourly') { @@ -1096,6 +1104,18 @@ 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', @@ -1209,6 +1229,8 @@ jQuery(function($){ item.attributes.imap_access = '' + (item.attributes.imap_access == 1 ? '1' : '0') + ''; item.attributes.smtp_access = '' + (item.attributes.smtp_access == 1 ? '1' : '0') + ''; item.attributes.sieve_access = '' + (item.attributes.sieve_access == 1 ? '1' : '0') + ''; + item.attributes.eas_access = '' + (item.attributes.eas_access == 1 ? '1' : '0') + ''; + item.attributes.dav_access = '' + (item.attributes.dav_access == 1 ? '1' : '0') + ''; item.attributes.sogo_access = '' + (item.attributes.sogo_access == 1 ? '1' : '0') + ''; if (item.attributes.quarantine_notification === 'never') { item.attributes.quarantine_notification = lang.never; @@ -1317,6 +1339,16 @@ 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', diff --git a/data/web/sogo-auth.php b/data/web/sogo-auth.php index 962627baf..2da28d4d4 100644 --- a/data/web/sogo-auth.php +++ b/data/web/sogo-auth.php @@ -12,18 +12,21 @@ $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']; - $is_eas = false; - $is_dav = false; + + // Determine service type for protocol access check + $service = 'NONE'; $original_uri = isset($_SERVER['HTTP_X_ORIGINAL_URI']) ? $_SERVER['HTTP_X_ORIGINAL_URI'] : ''; if (preg_match('/^(\/SOGo|)\/dav.*/', $original_uri) === 1) { - $is_dav = true; + $service = 'DAV'; } elseif (preg_match('/^(\/SOGo|)\/Microsoft-Server-ActiveSync.*/', $original_uri) === 1) { - $is_eas = true; + $service = 'EAS'; } - $login_check = check_login($username, $password, array('dav' => $is_dav, 'eas' => $is_eas)); + + $login_check = check_login($username, $password, array('service' => $service)); if ($login_check === 'user') { header("X-User: $username"); header("X-Auth: Basic ".base64_encode("$username:$password")); @@ -57,7 +60,6 @@ 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, diff --git a/data/web/templates/edit/mailbox-templates.twig b/data/web/templates/edit/mailbox-templates.twig index 65a83cd2a..ddc139586 100644 --- a/data/web/templates/edit/mailbox-templates.twig +++ b/data/web/templates/edit/mailbox-templates.twig @@ -108,6 +108,8 @@ + + diff --git a/data/web/templates/edit/mailbox.twig b/data/web/templates/edit/mailbox.twig index a223a7500..294957ee7 100644 --- a/data/web/templates/edit/mailbox.twig +++ b/data/web/templates/edit/mailbox.twig @@ -281,6 +281,8 @@ + + diff --git a/data/web/templates/modals/mailbox.twig b/data/web/templates/modals/mailbox.twig index 1e8ee53fa..b35c0caa8 100644 --- a/data/web/templates/modals/mailbox.twig +++ b/data/web/templates/modals/mailbox.twig @@ -148,6 +148,8 @@ + + @@ -335,6 +337,8 @@ + + diff --git a/data/web/templates/user/tab-user-auth.twig b/data/web/templates/user/tab-user-auth.twig index bace33489..171545d6c 100644 --- a/data/web/templates/user/tab-user-auth.twig +++ b/data/web/templates/user/tab-user-auth.twig @@ -55,6 +55,8 @@ {% if mailboxdata.attributes.smtp_access == 1 %}