mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-10-15 14:02:37 +00:00
@@ -42,6 +42,11 @@ http {
|
|||||||
https https;
|
https https;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
map $server_port $sogo_auth_internal {
|
||||||
|
65510 "1";
|
||||||
|
default "";
|
||||||
|
}
|
||||||
|
|
||||||
{% if HTTP_REDIRECT %}
|
{% if HTTP_REDIRECT %}
|
||||||
# HTTP to HTTPS redirect
|
# HTTP to HTTPS redirect
|
||||||
server {
|
server {
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ location ~ \.php$ {
|
|||||||
include /etc/nginx/fastcgi_params;
|
include /etc/nginx/fastcgi_params;
|
||||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
# trusted internal-auth marker; empty for external clients (see nginx.conf map)
|
||||||
|
fastcgi_param SOGO_AUTH_INTERNAL $sogo_auth_internal;
|
||||||
fastcgi_read_timeout 3600;
|
fastcgi_read_timeout 3600;
|
||||||
fastcgi_send_timeout 3600;
|
fastcgi_send_timeout 3600;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -429,14 +429,25 @@ function domain_admin_sso($_action, $_data) {
|
|||||||
|
|
||||||
switch ($_action) {
|
switch ($_action) {
|
||||||
case 'check':
|
case 'check':
|
||||||
$token = $_data;
|
$token = preg_replace('/[^a-zA-Z0-9-]/', '', $_data);
|
||||||
|
|
||||||
$stmt = $pdo->prepare("SELECT `t1`.`username` FROM `da_sso` AS `t1` JOIN `admin` AS `t2` ON `t1`.`username` = `t2`.`username` WHERE `t1`.`token` = :token AND `t1`.`created` > DATE_SUB(NOW(), INTERVAL '30' SECOND) AND `t2`.`active` = 1 AND `t2`.`superadmin` = 0;");
|
$stmt = $pdo->prepare("SELECT `t1`.`username` FROM `da_sso` AS `t1` JOIN `admin` AS `t2` ON `t1`.`username` = `t2`.`username` WHERE `t1`.`token` = :token AND `t1`.`created` > DATE_SUB(NOW(), INTERVAL '30' SECOND) AND `t2`.`active` = 1 AND `t2`.`superadmin` = 0;");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':token' => preg_replace('/[^a-zA-Z0-9-]/', '', $token)
|
':token' => $token
|
||||||
));
|
));
|
||||||
$return = $stmt->fetch(PDO::FETCH_ASSOC);
|
$return = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
return empty($return['username']) ? false : $return['username'];
|
if (empty($return['username'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// single-use: consume the token; if a concurrent request already used it, deny
|
||||||
|
$del = $pdo->prepare("DELETE FROM `da_sso` WHERE `token` = :token");
|
||||||
|
$del->execute(array(
|
||||||
|
':token' => $token
|
||||||
|
));
|
||||||
|
if ($del->rowCount() < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $return['username'];
|
||||||
case 'issue':
|
case 'issue':
|
||||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
|
|||||||
@@ -5658,8 +5658,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username
|
':username' => $username
|
||||||
));
|
));
|
||||||
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $username . "/%' OR `c_uid` = :username");
|
// bind LIKE pattern + escape LIKE wildcards so the value matches literally (no SQLi, no over-match)
|
||||||
|
$c_object_like = '%/' . addcslashes($username, '\\%_') . '/%';
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE :c_object_like OR `c_uid` = :username");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
|
':c_object_like' => $c_object_like,
|
||||||
':username' => $username
|
':username' => $username
|
||||||
));
|
));
|
||||||
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
|
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
|
||||||
@@ -6055,8 +6058,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username
|
':username' => $username
|
||||||
));
|
));
|
||||||
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . str_replace('%', '\%', $username) . "/%' OR `c_uid` = :username");
|
// bind LIKE pattern + escape LIKE wildcards so the value matches literally (no SQLi, no over-match)
|
||||||
|
$c_object_like = '%/' . addcslashes($username, '\\%_') . '/%';
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE :c_object_like OR `c_uid` = :username");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
|
':c_object_like' => $c_object_like,
|
||||||
':username' => $username
|
':username' => $username
|
||||||
));
|
));
|
||||||
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
|
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
|
||||||
@@ -6212,8 +6218,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $name
|
':username' => $name
|
||||||
));
|
));
|
||||||
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $name . "/%' OR `c_uid` = :username");
|
// bind LIKE pattern + escape LIKE wildcards so the value matches literally (no SQLi, no over-match)
|
||||||
|
$c_object_like = '%/' . addcslashes($name, '\\%_') . '/%';
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE :c_object_like OR `c_uid` = :username");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
|
':c_object_like' => $c_object_like,
|
||||||
':username' => $name
|
':username' => $name
|
||||||
));
|
));
|
||||||
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
|
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function quarantine($_action, $_data = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
|
$stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
|
||||||
WHERE `qhash` = :hash
|
WHERE `qhash` = :hash
|
||||||
AND user_acl.quarantine = 1
|
AND user_acl.quarantine = 1
|
||||||
AND rcpt IN (SELECT username FROM mailbox)');
|
AND rcpt IN (SELECT username FROM mailbox)');
|
||||||
$stmt->execute(array(':hash' => $hash));
|
$stmt->execute(array(':hash' => $hash));
|
||||||
@@ -65,7 +65,7 @@ function quarantine($_action, $_data = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
|
$stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
|
||||||
WHERE `qhash` = :hash
|
WHERE `qhash` = :hash
|
||||||
AND `user_acl`.`quarantine` = 1
|
AND `user_acl`.`quarantine` = 1
|
||||||
AND `username` IN (SELECT `username` FROM `mailbox`)');
|
AND `username` IN (SELECT `username` FROM `mailbox`)');
|
||||||
$stmt->execute(array(':hash' => $hash));
|
$stmt->execute(array(':hash' => $hash));
|
||||||
@@ -170,6 +170,8 @@ function quarantine($_action, $_data = null) {
|
|||||||
}
|
}
|
||||||
elseif ($release_format == 'raw') {
|
elseif ($release_format == 'raw') {
|
||||||
$detail_row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $detail_row['msg']);
|
$detail_row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $detail_row['msg']);
|
||||||
|
// dot-stuffing per RFC5321 4.5.2: escape leading dots
|
||||||
|
$detail_row['msg'] = preg_replace('~^\.~m', '..', $detail_row['msg']);
|
||||||
$postfix_talk = array(
|
$postfix_talk = array(
|
||||||
array('220', 'HELO quarantine' . chr(10)),
|
array('220', 'HELO quarantine' . chr(10)),
|
||||||
array('250', 'MAIL FROM: ' . $sender . chr(10)),
|
array('250', 'MAIL FROM: ' . $sender . chr(10)),
|
||||||
@@ -180,7 +182,7 @@ function quarantine($_action, $_data = null) {
|
|||||||
array('221', '')
|
array('221', '')
|
||||||
);
|
);
|
||||||
// Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php
|
// Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php
|
||||||
$smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1);
|
$smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1);
|
||||||
if (!$smtp_connection) {
|
if (!$smtp_connection) {
|
||||||
logger(array('return' => array(
|
logger(array('return' => array(
|
||||||
array(
|
array(
|
||||||
@@ -192,7 +194,7 @@ function quarantine($_action, $_data = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for ($i=0; $i < count($postfix_talk); $i++) {
|
for ($i=0; $i < count($postfix_talk); $i++) {
|
||||||
$smtp_resource = fgets($smtp_connection, 256);
|
$smtp_resource = fgets($smtp_connection, 256);
|
||||||
if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) {
|
if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) {
|
||||||
$ret = substr($smtp_resource, 0, 3);
|
$ret = substr($smtp_resource, 0, 3);
|
||||||
$ret = (empty($ret)) ? '-' : $ret;
|
$ret = (empty($ret)) ? '-' : $ret;
|
||||||
@@ -465,17 +467,19 @@ function quarantine($_action, $_data = null) {
|
|||||||
}
|
}
|
||||||
elseif ($release_format == 'raw') {
|
elseif ($release_format == 'raw') {
|
||||||
$row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $row['msg']);
|
$row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $row['msg']);
|
||||||
|
// dot-stuffing per RFC5321 4.5.2: escape leading dots
|
||||||
|
$row['msg'] = preg_replace('~^\.~m', '..', $row['msg']);
|
||||||
$postfix_talk = array(
|
$postfix_talk = array(
|
||||||
array('220', 'HELO quarantine' . chr(10)),
|
array('220', 'HELO quarantine' . chr(10)),
|
||||||
array('250', 'MAIL FROM: ' . $sender . chr(10)),
|
array('250', 'MAIL FROM: ' . $sender . chr(10)),
|
||||||
array('250', 'RCPT TO: ' . $row['rcpt'] . chr(10)),
|
array('250', 'RCPT TO: ' . $row['rcpt'] . chr(10)),
|
||||||
array('250', 'DATA' . chr(10)),
|
array('250', 'DATA' . chr(10)),
|
||||||
array('354', str_replace("\n.", '', $row['msg']) . chr(10) . '.' . chr(10)),
|
array('354', $row['msg'] . chr(10) . '.' . chr(10)),
|
||||||
array('250', 'QUIT' . chr(10)),
|
array('250', 'QUIT' . chr(10)),
|
||||||
array('221', '')
|
array('221', '')
|
||||||
);
|
);
|
||||||
// Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php
|
// Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php
|
||||||
$smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1);
|
$smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1);
|
||||||
if (!$smtp_connection) {
|
if (!$smtp_connection) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'warning',
|
'type' => 'warning',
|
||||||
@@ -485,7 +489,7 @@ function quarantine($_action, $_data = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for ($i=0; $i < count($postfix_talk); $i++) {
|
for ($i=0; $i < count($postfix_talk); $i++) {
|
||||||
$smtp_resource = fgets($smtp_connection, 256);
|
$smtp_resource = fgets($smtp_connection, 256);
|
||||||
if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) {
|
if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) {
|
||||||
$ret = substr($smtp_resource, 0, 3);
|
$ret = substr($smtp_resource, 0, 3);
|
||||||
$ret = (empty($ret)) ? '-' : $ret;
|
$ret = (empty($ret)) ? '-' : $ret;
|
||||||
@@ -833,7 +837,7 @@ function quarantine($_action, $_data = null) {
|
|||||||
)));
|
)));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE `qhash` = :hash');
|
$stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE `qhash` = :hash');
|
||||||
$stmt->execute(array(':hash' => $hash));
|
$stmt->execute(array(':hash' => $hash));
|
||||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1089,6 +1089,8 @@ jQuery(function($){
|
|||||||
return str;
|
return str;
|
||||||
}).join('<br>\n');
|
}).join('<br>\n');
|
||||||
item.subject = escapeHtml(item.subject);
|
item.subject = escapeHtml(item.subject);
|
||||||
|
if (item.sender_mime != null) item.sender_mime = escapeHtml(item.sender_mime);
|
||||||
|
if (item['message-id'] != null) item['message-id'] = escapeHtml(item['message-id']);
|
||||||
var scan_time = item.time_real.toFixed(3);
|
var scan_time = item.time_real.toFixed(3);
|
||||||
if (item.time_virtual) {
|
if (item.time_virtual) {
|
||||||
scan_time += ' / ' + item.time_virtual.toFixed(3);
|
scan_time += ' / ' + item.time_virtual.toFixed(3);
|
||||||
@@ -1212,6 +1214,11 @@ jQuery(function($){
|
|||||||
});
|
});
|
||||||
} else if (table == 'rllog') {
|
} else if (table == 'rllog') {
|
||||||
$.each(data, function (i, item) {
|
$.each(data, function (i, item) {
|
||||||
|
if (item.header_from != null) item.header_from = escapeHtml(item.header_from);
|
||||||
|
if (item.header_subject != null) item.header_subject = escapeHtml(item.header_subject);
|
||||||
|
if (item.from != null) item.from = escapeHtml(item.from);
|
||||||
|
if (item.rcpt != null) item.rcpt = escapeHtml(item.rcpt);
|
||||||
|
if (item.message_id != null) item.message_id = escapeHtml(item.message_id);
|
||||||
if (item.user == null) {
|
if (item.user == null) {
|
||||||
item.user = "none";
|
item.user = "none";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -996,6 +996,7 @@ jQuery(function($){
|
|||||||
'style="min-width:2em;width:' + item.percent_in_use + '%">' + item.percent_in_use + '%' + '</div></div>'
|
'style="min-width:2em;width:' + item.percent_in_use + '%">' + item.percent_in_use + '%' + '</div></div>'
|
||||||
};
|
};
|
||||||
item.username = escapeHtml(item.username);
|
item.username = escapeHtml(item.username);
|
||||||
|
item.name = escapeHtml(item.name);
|
||||||
|
|
||||||
if (Array.isArray(item.tags)){
|
if (Array.isArray(item.tags)){
|
||||||
var tags = '';
|
var tags = '';
|
||||||
|
|||||||
+11
-2
@@ -8,8 +8,12 @@ $ALLOW_ADMIN_EMAIL_LOGIN = (preg_match(
|
|||||||
$session_var_user_allowed = 'sogo-sso-user-allowed';
|
$session_var_user_allowed = 'sogo-sso-user-allowed';
|
||||||
$session_var_pass = 'sogo-sso-pass';
|
$session_var_pass = 'sogo-sso-pass';
|
||||||
|
|
||||||
|
// only the internal nginx auth_request loopback (127.0.0.1:65510) sets this;
|
||||||
|
// external clients can never supply it (bare fastcgi param, not an HTTP header)
|
||||||
|
$is_internal_auth = (($_SERVER['SOGO_AUTH_INTERNAL'] ?? '') === '1');
|
||||||
|
|
||||||
// validate credentials for basic auth requests
|
// validate credentials for basic auth requests
|
||||||
if (isset($_SERVER['PHP_AUTH_USER'])) {
|
if ($is_internal_auth && isset($_SERVER['PHP_AUTH_USER'])) {
|
||||||
// load prerequisites only when required
|
// load prerequisites only when required
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
||||||
|
|
||||||
@@ -50,6 +54,11 @@ elseif (isset($_GET['login'])) {
|
|||||||
(($_SESSION['acl']['login_as'] == "1" && $ALLOW_ADMIN_EMAIL_LOGIN !== 0) || ($is_dual === false && $login == $_SESSION['mailcow_cc_username']))) {
|
(($_SESSION['acl']['login_as'] == "1" && $ALLOW_ADMIN_EMAIL_LOGIN !== 0) || ($is_dual === false && $login == $_SESSION['mailcow_cc_username']))) {
|
||||||
if (filter_var($login, FILTER_VALIDATE_EMAIL)) {
|
if (filter_var($login, FILTER_VALIDATE_EMAIL)) {
|
||||||
if (user_get_alias_details($login) !== false) {
|
if (user_get_alias_details($login) !== false) {
|
||||||
|
// enforce tenant boundary
|
||||||
|
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $login)) {
|
||||||
|
header("Location: /");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
// Block SOGo access if pending actions (2FA setup, password update)
|
// Block SOGo access if pending actions (2FA setup, password update)
|
||||||
if (!empty($_SESSION['pending_tfa_setup']) || !empty($_SESSION['pending_pw_update'])) {
|
if (!empty($_SESSION['pending_tfa_setup']) || !empty($_SESSION['pending_pw_update'])) {
|
||||||
header("Location: /");
|
header("Location: /");
|
||||||
@@ -80,7 +89,7 @@ elseif (isset($_GET['login'])) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
// only check for admin-login on sogo GUI requests
|
// only check for admin-login on sogo GUI requests
|
||||||
elseif (isset($_SERVER['HTTP_X_ORIGINAL_URI']) && strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9), "/SOGo/so/") === 0) {
|
elseif ($is_internal_auth && isset($_SERVER['HTTP_X_ORIGINAL_URI']) && strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9), "/SOGo/so/") === 0) {
|
||||||
// this is an nginx auth_request call, we check for existing sogo-sso session variables
|
// this is an nginx auth_request call, we check for existing sogo-sso session variables
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php';
|
||||||
if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/inc/vars.local.inc.php')) {
|
if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/inc/vars.local.inc.php')) {
|
||||||
|
|||||||
Reference in New Issue
Block a user