1
0
mirror of https://github.com/mailcow/mailcow-dockerized.git synced 2026-01-03 12:09:16 +00:00

Merge remote-tracking branch 'origin/staging' into nightly

This commit is contained in:
FreddleSpl0it
2024-08-15 12:45:52 +02:00
25 changed files with 958 additions and 127 deletions

View File

@@ -903,13 +903,17 @@ function update_sogo_static_view($mailbox = null) {
function edit_user_account($_data) {
global $lang;
global $pdo;
$_data_log = $_data;
!isset($_data_log['user_new_pass']) ?: $_data_log['user_new_pass'] = '*';
!isset($_data_log['user_new_pass2']) ?: $_data_log['user_new_pass2'] = '*';
!isset($_data_log['user_old_pass']) ?: $_data_log['user_old_pass'] = '*';
$username = $_SESSION['mailcow_cc_username'];
$role = $_SESSION['mailcow_cc_role'];
$password_old = $_data['user_old_pass'];
$pw_recovery_email = $_data['pw_recovery_email'];
if (filter_var($username, FILTER_VALIDATE_EMAIL === false) || $role != 'user') {
$_SESSION['return'][] = array(
'type' => 'danger',
@@ -918,20 +922,24 @@ function edit_user_account($_data) {
);
return false;
}
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `username` = :user AND authsource = 'mailcow'");
$stmt->execute(array(':user' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!verify_hash($row['password'], $password_old)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
if (!empty($_data['user_new_pass']) && !empty($_data['user_new_pass2'])) {
// edit password
if (!empty($password_old) && !empty($_data['user_new_pass']) && !empty($_data['user_new_pass2'])) {
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `username` = :user AND authsource = 'mailcow'");
$stmt->execute(array(':user' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!verify_hash($row['password'], $password_old)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$password_new = $_data['user_new_pass'];
$password_new2 = $_data['user_new_pass2'];
if (password_check($password_new, $password_new2) !== true) {
@@ -946,8 +954,29 @@ function edit_user_account($_data) {
':password_hashed' => $password_hashed,
':username' => $username
));
update_sogo_static_view();
}
update_sogo_static_view();
// edit password recovery email
elseif (isset($pw_recovery_email)) {
if (!isset($_SESSION['acl']['pw_reset']) || $_SESSION['acl']['pw_reset'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
$pw_recovery_email = (!filter_var($pw_recovery_email, FILTER_VALIDATE_EMAIL)) ? '' : $pw_recovery_email;
$stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email)
WHERE `username` = :username AND authsource = 'mailcow'");
$stmt->execute(array(
':recovery_email' => $pw_recovery_email,
':username' => $username
));
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
@@ -2648,6 +2677,385 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
break;
}
}
function reset_password($action, $data = null) {
global $pdo;
global $redis;
global $mailcow_hostname;
global $PW_RESET_TOKEN_LIMIT;
global $PW_RESET_TOKEN_LIFETIME;
$_data_log = $data;
if (isset($_data_log['new_password'])) $_data_log['new_password'] = '*';
if (isset($_data_log['new_password2'])) $_data_log['new_password2'] = '*';
switch ($action) {
case 'check':
$token = $data;
$stmt = $pdo->prepare("SELECT `t1`.`username` FROM `reset_password` AS `t1` JOIN `mailbox` AS `t2` ON `t1`.`username` = `t2`.`username` WHERE `t1`.`token` = :token AND `t1`.`created` > DATE_SUB(NOW(), INTERVAL :lifetime MINUTE) AND `t2`.`active` = 1 AND `t2`.`authsource` = 'mailcow';");
$stmt->execute(array(
':token' => preg_replace('/[^a-zA-Z0-9-]/', '', $token),
':lifetime' => $PW_RESET_TOKEN_LIFETIME
));
$return = $stmt->fetch(PDO::FETCH_ASSOC);
return empty($return['username']) ? false : $return['username'];
break;
case 'issue':
$username = $data;
// perform cleanup
$stmt = $pdo->prepare("DELETE FROM `reset_password` WHERE created < DATE_SUB(NOW(), INTERVAL :lifetime MINUTE);");
$stmt->execute(array(':lifetime' => $PW_RESET_TOKEN_LIFETIME));
if (filter_var($username, FILTER_VALIDATE_EMAIL) === false) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$pw_reset_notification = reset_password('get_notification', 'raw');
if (!$pw_reset_notification) return false;
if (empty($pw_reset_notification['from']) || empty($pw_reset_notification['subject'])) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'password_reset_na'
);
return false;
}
$stmt = $pdo->prepare("SELECT * FROM `mailbox`
WHERE `username` = :username AND authsource = 'mailcow'");
$stmt->execute(array(':username' => $username));
$mailbox_data = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($mailbox_data)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'password_reset_invalid_user'
);
return false;
}
$mailbox_attr = json_decode($mailbox_data['attributes'], true);
if (empty($mailbox_attr['recovery_email']) || filter_var($mailbox_attr['recovery_email'], FILTER_VALIDATE_EMAIL) === false) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => "password_reset_invalid_user"
);
return false;
}
$stmt = $pdo->prepare("SELECT * FROM `reset_password`
WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
$generated_token_count = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($generated_token_count >= $PW_RESET_TOKEN_LIMIT) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => "reset_token_limit_exceeded"
);
return false;
}
$token = implode('-', array(
strtoupper(bin2hex(random_bytes(3))),
strtoupper(bin2hex(random_bytes(3))),
strtoupper(bin2hex(random_bytes(3))),
strtoupper(bin2hex(random_bytes(3))),
strtoupper(bin2hex(random_bytes(3)))
));
$stmt = $pdo->prepare("INSERT INTO `reset_password` (`username`, `token`)
VALUES (:username, :token)");
$stmt->execute(array(
':username' => $username,
':token' => $token
));
$reset_link = getBaseURL() . "/reset-password?token=" . $token;
$request_date = new DateTime();
$locale_date = locale_get_default();
$date_formatter = new IntlDateFormatter(
$locale_date,
IntlDateFormatter::FULL,
IntlDateFormatter::FULL
);
$formatted_request_date = $date_formatter->format($request_date);
// set template vars
// subject
$pw_reset_notification['subject'] = str_replace('{{hostname}}', $mailcow_hostname, $pw_reset_notification['subject']);
$pw_reset_notification['subject'] = str_replace('{{link}}', $reset_link, $pw_reset_notification['subject']);
$pw_reset_notification['subject'] = str_replace('{{username}}', $username, $pw_reset_notification['subject']);
$pw_reset_notification['subject'] = str_replace('{{username2}}', $mailbox_attr['recovery_email'], $pw_reset_notification['subject']);
$pw_reset_notification['subject'] = str_replace('{{date}}', $formatted_request_date, $pw_reset_notification['subject']);
$pw_reset_notification['subject'] = str_replace('{{token_lifetime}}', $PW_RESET_TOKEN_LIFETIME, $pw_reset_notification['subject']);
// text
$pw_reset_notification['text_tmpl'] = str_replace('{{hostname}}', $mailcow_hostname, $pw_reset_notification['text_tmpl']);
$pw_reset_notification['text_tmpl'] = str_replace('{{link}}', $reset_link, $pw_reset_notification['text_tmpl']);
$pw_reset_notification['text_tmpl'] = str_replace('{{username}}', $username, $pw_reset_notification['text_tmpl']);
$pw_reset_notification['text_tmpl'] = str_replace('{{username2}}', $mailbox_attr['recovery_email'], $pw_reset_notification['text_tmpl']);
$pw_reset_notification['text_tmpl'] = str_replace('{{date}}', $formatted_request_date, $pw_reset_notification['text_tmpl']);
$pw_reset_notification['text_tmpl'] = str_replace('{{token_lifetime}}', $PW_RESET_TOKEN_LIFETIME, $pw_reset_notification['text_tmpl']);
// html
$pw_reset_notification['html_tmpl'] = str_replace('{{hostname}}', $mailcow_hostname, $pw_reset_notification['html_tmpl']);
$pw_reset_notification['html_tmpl'] = str_replace('{{link}}', $reset_link, $pw_reset_notification['html_tmpl']);
$pw_reset_notification['html_tmpl'] = str_replace('{{username}}', $username, $pw_reset_notification['html_tmpl']);
$pw_reset_notification['html_tmpl'] = str_replace('{{username2}}', $mailbox_attr['recovery_email'], $pw_reset_notification['html_tmpl']);
$pw_reset_notification['html_tmpl'] = str_replace('{{date}}', $formatted_request_date, $pw_reset_notification['html_tmpl']);
$pw_reset_notification['html_tmpl'] = str_replace('{{token_lifetime}}', $PW_RESET_TOKEN_LIFETIME, $pw_reset_notification['html_tmpl']);
$email_sent = reset_password('send_mail', array(
"from" => $pw_reset_notification['from'],
"to" => $mailbox_attr['recovery_email'],
"subject" => $pw_reset_notification['subject'],
"text" => $pw_reset_notification['text_tmpl'],
"html" => $pw_reset_notification['html_tmpl']
));
if (!$email_sent){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => "recovery_email_failed"
);
return false;
}
list($localPart, $domainPart) = explode('@', $mailbox_attr['recovery_email']);
if (strlen($localPart) > 1) {
$maskedLocalPart = $localPart[0] . str_repeat('*', strlen($localPart) - 1);
} else {
$maskedLocalPart = "*";
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => array("recovery_email_sent", $maskedLocalPart . '@' . $domainPart)
);
return array(
"username" => $username,
"issue" => "success"
);
break;
case 'reset':
$token = $data['token'];
$new_password = $data['new_password'];
$new_password2 = $data['new_password2'];
$username = $data['username'];
$check_tfa = $data['check_tfa'];
if (!$username || !$token) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'invalid_reset_token'
);
return false;
}
# check new password
if (!password_check($new_password, $new_password2)) {
return false;
}
if ($check_tfa){
// check for tfa authenticators
$authenticators = get_tfa($username);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
$_SESSION['pending_mailcow_cc_username'] = $username;
$_SESSION['pending_pw_reset_token'] = $token;
$_SESSION['pending_pw_new_password'] = $new_password;
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
$_SESSION['return'][] = array(
'type' => 'info',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'awaiting_tfa_confirmation'
);
return false;
}
}
# set new password
$password_hashed = hash_password($new_password);
$stmt = $pdo->prepare("UPDATE `mailbox` SET
`password` = :password_hashed,
`attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW())
WHERE `username` = :username AND authsource = 'mailcow'");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username' => $username
));
// perform cleanup
$stmt = $pdo->prepare("DELETE FROM `reset_password` WHERE `username` = :username;");
$stmt->execute(array(
':username' => $username
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'password_changed_success'
);
return true;
break;
case 'get_notification':
$type = $data;
try {
$settings['from'] = $redis->Get('PW_RESET_FROM');
$settings['subject'] = $redis->Get('PW_RESET_SUBJ');
$settings['html_tmpl'] = $redis->Get('PW_RESET_HTML');
$settings['text_tmpl'] = $redis->Get('PW_RESET_TEXT');
if (empty($settings['html_tmpl']) && empty($settings['text_tmpl'])) {
$settings['html_tmpl'] = file_get_contents("/tpls/pw_reset_html.tpl");
$settings['text_tmpl'] = file_get_contents("/tpls/pw_reset_text.tpl");
}
if ($type != "raw") {
$settings['html_tmpl'] = htmlspecialchars($settings['html_tmpl']);
$settings['text_tmpl'] = htmlspecialchars($settings['text_tmpl']);
}
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => array('redis_error', $e)
);
return false;
}
return $settings;
break;
case 'send_mail':
$from = $data['from'];
$to = $data['to'];
$text = $data['text'];
$html = $data['html'];
$subject = $data['subject'];
if (!filter_var($from, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'from_invalid'
);
return false;
}
if (!filter_var($to, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'to_invalid'
);
return false;
}
if (empty($subject)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'subject_empty'
);
return false;
}
if (empty($text)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'text_empty'
);
return false;
}
ini_set('max_execution_time', 0);
ini_set('max_input_time', 0);
$mail = new PHPMailer;
$mail->Timeout = 10;
$mail->SMTPOptions = array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
)
);
$mail->isSMTP();
$mail->Host = 'postfix-mailcow';
$mail->SMTPAuth = false;
$mail->Port = 25;
$mail->setFrom($from);
$mail->Subject = $subject;
$mail->CharSet ="UTF-8";
if (!empty($html)) {
$mail->Body = $html;
$mail->AltBody = $text;
}
else {
$mail->Body = $text;
}
$mail->XMailer = 'MooMail';
$mail->AddAddress($to);
if (!$mail->send()) {
return false;
}
$mail->ClearAllRecipients();
return true;
break;
}
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
switch ($action) {
case 'edit_notification':
$subject = $data['subject'];
$from = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $data['from']);
$from = (!filter_var($from, FILTER_VALIDATE_EMAIL)) ? "" : $from;
$subject = (empty($subject)) ? "" : $subject;
$text = (empty($data['text_tmpl'])) ? "" : $data['text_tmpl'];
$html = (empty($data['html_tmpl'])) ? "" : $data['html_tmpl'];
try {
$redis->Set('PW_RESET_FROM', $from);
$redis->Set('PW_RESET_SUBJ', $subject);
$redis->Set('PW_RESET_HTML', $html);
$redis->Set('PW_RESET_TEXT', $text);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => array('redis_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'saved_settings'
);
break;
}
}
function clear_session(){
session_regenerate_id(true);
session_unset();

View File

@@ -184,6 +184,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'msg' => 'global_filter_written'
);
return true;
break;
case 'filter':
$sieve = new Sieve\SieveParser();
if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) {
@@ -1266,6 +1267,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$_data['quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
$_data['quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
$_data['app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
$_data['pw_reset'] = (in_array('pw_reset', $_data['acl'])) ? 1 : 0;
} else {
$_data['spam_alias'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_spam_alias']);
$_data['tls_policy'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_tls_policy']);
@@ -1281,14 +1283,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$_data['quarantine_notification'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_notification']);
$_data['quarantine_category'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_category']);
$_data['app_passwds'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_app_passwds']);
$_data['pw_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_pw_reset']);
}
try {
$stmt = $pdo->prepare("INSERT INTO `user_acl`
(`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`,
`pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`)
`pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`, `pw_reset`)
VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset,
:pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds) ");
:pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds, :pw_reset) ");
$stmt->execute(array(
':username' => $username,
':spam_alias' => $_data['spam_alias'],
@@ -1304,7 +1307,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':quarantine_attachments' => $_data['quarantine_attachments'],
':quarantine_notification' => $_data['quarantine_notification'],
':quarantine_category' => $_data['quarantine_category'],
':app_passwds' => $_data['app_passwds']
':app_passwds' => $_data['app_passwds'],
':pw_reset' => $_data['pw_reset']
));
}
catch (PDOException $e) {
@@ -1638,6 +1642,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
$attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
$attr['acl_pw_reset'] = (in_array('pw_reset', $_data['acl'])) ? 1 : 0;
} else {
$_data['acl'] = (array)$_data['acl'];
$attr['acl_spam_alias'] = 0;
@@ -2927,26 +2932,27 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
}
if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
(int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']);
(int)$sogo_access = (isset($_data['sogo_access']) && isset($_SESSION['acl']['sogo_access']) && $_SESSION['acl']['sogo_access'] == "1") ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']);
(int)$imap_access = (isset($_data['imap_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']);
(int)$pop3_access = (isset($_data['pop3_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']);
(int)$smtp_access = (isset($_data['smtp_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']);
(int)$sieve_access = (isset($_data['sieve_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']);
(int)$relayhost = (isset($_data['relayhost']) && isset($_SESSION['acl']['mailbox_relayhost']) && $_SESSION['acl']['mailbox_relayhost'] == "1") ? 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'];
$domain = $is_now['domain'];
$quota_b = $quota_m * 1048576;
$password = (!empty($_data['password'])) ? $_data['password'] : null;
$password2 = (!empty($_data['password2'])) ? $_data['password2'] : null;
$tags = (is_array($_data['tags']) ? $_data['tags'] : array());
$attribute_hash = (!empty($_data['attribute_hash'])) ? $_data['attribute_hash'] : '';
$authsource = $is_now['authsource'];
(int)$sogo_access = (isset($_data['sogo_access']) && isset($_SESSION['acl']['sogo_access']) && $_SESSION['acl']['sogo_access'] == "1") ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']);
(int)$imap_access = (isset($_data['imap_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']);
(int)$pop3_access = (isset($_data['pop3_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']);
(int)$smtp_access = (isset($_data['smtp_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']);
(int)$sieve_access = (isset($_data['sieve_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']);
(int)$relayhost = (isset($_data['relayhost']) && isset($_SESSION['acl']['mailbox_relayhost']) && $_SESSION['acl']['mailbox_relayhost'] == "1") ? 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'];
$domain = $is_now['domain'];
$quota_b = $quota_m * 1048576;
$password = (!empty($_data['password'])) ? $_data['password'] : null;
$password2 = (!empty($_data['password2'])) ? $_data['password2'] : null;
$tags = (is_array($_data['tags']) ? $_data['tags'] : array());
$attribute_hash = (!empty($_data['attribute_hash'])) ? $_data['attribute_hash'] : '';
$authsource = $is_now['authsource'];
if (in_array($_data['authsource'], array('mailcow', 'keycloak', 'generic-oidc', 'ldap'))){
$authsource = $_data['authsource'];
}
$pw_recovery_email = (isset($_data['pw_recovery_email']) && $authsource == 'mailcow') ? $_data['pw_recovery_email'] : $is_now['attributes']['recovery_email'];
}
else {
$_SESSION['return'][] = array(
@@ -3199,35 +3205,47 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':address' => $username,
':active' => $active
));
$stmt = $pdo->prepare("UPDATE `mailbox` SET
`active` = :active,
`name`= :name,
`quota` = :quota_b,
`authsource` = :authsource,
`attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update),
`attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access),
`attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access),
`attributes` = JSON_SET(`attributes`, '$.sieve_access', :sieve_access),
`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`, '$.attribute_hash', :attribute_hash)
WHERE `username` = :username");
$stmt->execute(array(
':active' => $active,
':name' => $name,
':quota_b' => $quota_b,
':attribute_hash' => $attribute_hash,
':force_pw_update' => $force_pw_update,
':sogo_access' => $sogo_access,
':imap_access' => $imap_access,
':pop3_access' => $pop3_access,
':sieve_access' => $sieve_access,
':smtp_access' => $smtp_access,
':relayhost' => $relayhost,
':username' => $username,
':authsource' => $authsource
));
try {
$stmt = $pdo->prepare("UPDATE `mailbox` SET
`active` = :active,
`name`= :name,
`quota` = :quota_b,
`authsource` = :authsource,
`attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update),
`attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access),
`attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access),
`attributes` = JSON_SET(`attributes`, '$.sieve_access', :sieve_access),
`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`, '$.recovery_email', :recovery_email),
`attributes` = JSON_SET(`attributes`, '$.attribute_hash', :attribute_hash)
WHERE `username` = :username");
$stmt->execute(array(
':active' => $active,
':name' => $name,
':quota_b' => $quota_b,
':attribute_hash' => $attribute_hash,
':force_pw_update' => $force_pw_update,
':sogo_access' => $sogo_access,
':imap_access' => $imap_access,
':pop3_access' => $pop3_access,
':sieve_access' => $sieve_access,
':smtp_access' => $smtp_access,
':recovery_email' => $pw_recovery_email,
':relayhost' => $relayhost,
':username' => $username,
':authsource' => $authsource
));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
return false;
}
// save tags
foreach($tags as $index => $tag){
if (empty($tag)) continue;
@@ -3413,6 +3431,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
$attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
$attr['acl_pw_reset'] = (in_array('pw_reset', $_data['acl'])) ? 1 : 0;
} else {
foreach ($is_now as $key => $value){
$attr[$key] = $is_now[$key];

View File

@@ -3,7 +3,7 @@ function init_db_schema() {
try {
global $pdo;
$db_version = "31072024_1012";
$db_version = "15082024_1212";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -484,6 +484,7 @@ function init_db_schema() {
"quarantine_notification" => "TINYINT(1) NOT NULL DEFAULT '1'",
"quarantine_category" => "TINYINT(1) NOT NULL DEFAULT '1'",
"app_passwds" => "TINYINT(1) NOT NULL DEFAULT '1'",
"pw_reset" => "TINYINT(1) NOT NULL DEFAULT '1'",
),
"keys" => array(
"primary" => array(
@@ -709,6 +710,19 @@ function init_db_schema() {
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"reset_password" => array(
"cols" => array(
"username" => "VARCHAR(255) NOT NULL",
"token" => "VARCHAR(255) NOT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
),
"keys" => array(
"primary" => array(
"" => array("token", "created")
),
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"imapsync" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",

View File

@@ -38,6 +38,26 @@ if (!empty($_GET['sso_token'])) {
}
}
if (isset($_POST["pw_reset_request"]) && !empty($_POST['username'])) {
reset_password("issue", $_POST['username']);
header("Location: /");
exit;
}
if (isset($_POST["pw_reset"])) {
$username = reset_password("check", $_POST['token']);
$reset_result = reset_password("reset", array(
'new_password' => $_POST['new_password'],
'new_password2' => $_POST['new_password2'],
'token' => $_POST['token'],
'username' => $username,
'check_tfa' => True
));
if ($reset_result){
header("Location: /");
exit;
}
}
if (isset($_POST["verify_tfa_login"])) {
if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST)) {
if ($_SESSION['pending_mailcow_cc_role'] == "admin") {
@@ -61,15 +81,31 @@ if (isset($_POST["verify_tfa_login"])) {
die();
}
elseif ($_SESSION['pending_mailcow_cc_role'] == "user") {
set_user_loggedin_session($_SESSION['pending_mailcow_cc_username']);
$user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']);
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) {
header("Location: /SOGo/so/{$_SESSION['mailcow_cc_username']}");
if (isset($_SESSION['pending_pw_reset_token']) && isset($_SESSION['pending_pw_new_password'])) {
reset_password("reset", array(
'new_password' => $_SESSION['pending_pw_new_password'],
'new_password2' => $_SESSION['pending_pw_new_password'],
'token' => $_SESSION['pending_pw_reset_token'],
'username' => $_SESSION['pending_mailcow_cc_username']
));
unset($_SESSION['pending_pw_reset_token']);
unset($_SESSION['pending_pw_new_password']);
unset($_SESSION['pending_mailcow_cc_username']);
unset($_SESSION['pending_tfa_methods']);
header("Location: /");
die();
} else {
header("Location: /user");
die();
set_user_loggedin_session($_SESSION['pending_mailcow_cc_username']);
$user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']);
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) {
header("Location: /SOGo/so/{$_SESSION['mailcow_cc_username']}");
die();
} else {
header("Location: /user");
die();
}
}
}
}
@@ -80,11 +116,13 @@ if (isset($_POST["verify_tfa_login"])) {
}
if (isset($_GET["cancel_tfa_login"])) {
unset($_SESSION['pending_mailcow_cc_username']);
unset($_SESSION['pending_mailcow_cc_role']);
unset($_SESSION['pending_tfa_methods']);
unset($_SESSION['pending_pw_reset_token']);
unset($_SESSION['pending_pw_new_password']);
unset($_SESSION['pending_mailcow_cc_username']);
unset($_SESSION['pending_mailcow_cc_role']);
unset($_SESSION['pending_tfa_methods']);
header("Location: /");
header("Location: /");
}
if (isset($_POST["quick_release"])) {

View File

@@ -212,6 +212,12 @@ $MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format'] = 'maildir:';
// Show last IMAP and POP3 logins
$SHOW_LAST_LOGIN = true;
// Maximum number of password reset tokens that can be generated at once per user
$PW_RESET_TOKEN_LIMIT = 3;
// Maximum time in minutes a password reset token is valid
$PW_RESET_TOKEN_LIFETIME = 15;
// UV flag handling in FIDO2/WebAuthn - defaults to false to allow iOS logins
// true = required
// false = preferred