[Web] Show users the last known connections for SASL authentication

[Web] Feature: Log SASL authentication
This commit is contained in:
andryyy
2021-06-04 14:29:39 +02:00
parent 51e3521aac
commit 2d55b54904
13 changed files with 329 additions and 59 deletions

View File

@@ -251,20 +251,60 @@ function password_check($password1, $password2) {
return true;
}
function last_login($user) {
function last_login($action, $username) {
global $pdo;
$stmt = $pdo->prepare('SELECT `remote`, `time` FROM `logs`
WHERE JSON_EXTRACT(`call`, "$[0]") = "check_login"
AND JSON_EXTRACT(`call`, "$[1]") = :user
AND `type` = "success" ORDER BY `time` DESC LIMIT 1');
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($row)) {
return $row;
}
else {
return false;
switch ($action) {
case 'get':
if (filter_var($username, FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$stmt = $pdo->prepare('SELECT `real_rip`, MAX(`datetime`) as `datetime`, `service` FROM `sasl_logs`
WHERE `username` = :username
AND `success` = 1
GROUP BY `real_rip`, `service`
ORDER BY `datetime` DESC
LIMIT 5;');
$stmt->execute(array(':username' => $username));
$sasl = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($sasl as $k => $v) {
if (!filter_var($sasl[$k]['real_rip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
$sasl[$k]['real_rip'] = 'Web/EAS/Internal (' . $sasl[$k]['real_rip'] . ')';
}
}
}
else {
$sasl = array();
}
if ($_SESSION['mailcow_cc_role'] == "admin" || $username == $_SESSION['mailcow_cc_username']) {
$stmt = $pdo->prepare('SELECT `remote`, `time` FROM `logs`
WHERE JSON_EXTRACT(`call`, "$[0]") = "check_login"
AND JSON_EXTRACT(`call`, "$[1]") = :username
AND `type` = "success" ORDER BY `time` DESC LIMIT 1 OFFSET 1');
$stmt->execute(array(':username' => $username));
$ui = $stmt->fetch(PDO::FETCH_ASSOC);
}
else {
$ui = array();
}
return array('ui' => $ui, 'sasl' => $sasl);
break;
case 'reset':
if (filter_var($username, FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$stmt = $pdo->prepare('DELETE FROM `sasl_logs`
WHERE `username` = :username
AND `success` = 1;');
$stmt->execute(array(':username' => $username));
}
if ($_SESSION['mailcow_cc_role'] == "admin" || $username == $_SESSION['mailcow_cc_username']) {
$stmt = $pdo->prepare('DELETE FROM `logs`
WHERE JSON_EXTRACT(`call`, "$[0]") = "check_login"
AND JSON_EXTRACT(`call`, "$[1]") = :username
AND `type` = "success"');
$stmt->execute(array(':username' => $username));
}
return true;
break;
}
}
function flush_memcached() {
try {
@@ -1862,6 +1902,26 @@ function get_logs($application, $lines = false) {
return $data;
}
}
if ($application == "sasl") {
if (isset($from) && isset($to)) {
$stmt = $pdo->prepare("SELECT * FROM `sasl_logs` ORDER BY `id` DESC LIMIT :from, :to");
$stmt->execute(array(
':from' => $from - 1,
':to' => $to
));
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
else {
$stmt = $pdo->prepare("SELECT * FROM `sasl_logs` ORDER BY `id` DESC LIMIT :lines");
$stmt->execute(array(
':lines' => $lines + 1,
));
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
if (is_array($data)) {
return $data;
}
}
// Redis
if ($application == "dovecot-mailcow") {
if (isset($from) && isset($to)) {

View File

@@ -3503,18 +3503,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return false;
}
$mailboxdata = array();
$last_imap_login = $redis->Get('last-login/imap/' . $_data);
$last_smtp_login = $redis->Get('last-login/smtp/' . $_data);
$last_pop3_login = $redis->Get('last-login/pop3/' . $_data);
if ($last_imap_login === false || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_imap_login = '0';
}
if ($last_smtp_login === false || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_smtp_login = '0';
}
if ($last_pop3_login === false || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_pop3_login = '0';
}
if (preg_match('/y|yes/i', getenv('MASTER'))) {
$stmt = $pdo->prepare("SELECT
`domain`.`backupmx`,
@@ -3575,10 +3563,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$mailboxdata['quota_used'] = intval($row['bytes']);
$mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100);
$mailboxdata['last_imap_login'] = $last_imap_login;
$mailboxdata['last_smtp_login'] = $last_smtp_login;
$mailboxdata['last_pop3_login'] = $last_pop3_login;
if ($mailboxdata['percent_in_use'] === '- ') {
$mailboxdata['percent_class'] = "info";
}
@@ -3592,11 +3576,43 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$mailboxdata['percent_class'] = "success";
}
// Determine last logins
$stmt = $pdo->prepare("SELECT MAX(`datetime`) AS `datetime`, `service` FROM `sasl_logs`
WHERE `username` = :mailbox
AND `success` = 1
GROUP BY `service` DESC");
$stmt->execute(array(':mailbox' => $_data));
$SaslLogsData = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($SaslLogsData as $SaslLogs) {
if ($SaslLogs['service'] == 'imap') {
$last_imap_login = strtotime($SaslLogs['datetime']);
}
else if ($SaslLogs['service'] == 'smtp') {
$last_smtp_login = strtotime($SaslLogs['datetime']);
}
else if ($SaslLogs['service'] == 'pop3') {
$last_pop3_login = strtotime($SaslLogs['datetime']);
}
}
if (!isset($last_imap_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_imap_login = 0;
}
if (!isset($last_smtp_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_smtp_login = 0;
}
if (!isset($last_pop3_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_pop3_login = 0;
}
$mailboxdata['last_imap_login'] = $last_imap_login;
$mailboxdata['last_smtp_login'] = $last_smtp_login;
$mailboxdata['last_pop3_login'] = $last_pop3_login;
if (!isset($_extra) || $_extra != 'reduced') {
$rl = ratelimit('get', 'mailbox', $_data);
$stmt = $pdo->prepare("SELECT `maxquota`, `quota` FROM `domain` WHERE `domain` = :domain");
$stmt->execute(array(':domain' => $row['domain']));
$DomainQuota = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT IFNULL(COUNT(`active`), 0) AS `pushover_active` FROM `pushover` WHERE `username` = :username AND `active` = 1");
$stmt->execute(array(':username' => $_data));
$PushoverActive = $stmt->fetch(PDO::FETCH_ASSOC);

View File

@@ -3,7 +3,7 @@ function init_db_schema() {
try {
global $pdo;
$db_version = "27052021_2000";
$db_version = "03062021_2320";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -510,6 +510,30 @@ function init_db_schema() {
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"sasl_logs" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"success" => "TINYINT(1) NOT NULL DEFAULT '0'",
"service" => "VARCHAR(32) NOT NULL DEFAULT ''",
"app_password" => "INT",
"username" => "VARCHAR(255) NOT NULL",
"real_rip" => "VARCHAR(64) NOT NULL",
"datetime" => "DATETIME(0) NOT NULL DEFAULT NOW(0)"
),
"keys" => array(
"primary" => array(
"" => array("id")
),
"key" => array(
"username" => array("username"),
"service" => array("service"),
"success" => array("success"),
"datetime" => array("datetime"),
"real_rip" => array("real_rip")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"quota2" => array(
"cols" => array(
"username" => "VARCHAR(255) NOT NULL",

View File

@@ -24,19 +24,16 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
if ($as == "admin") {
$_SESSION['mailcow_cc_username'] = $login_user;
$_SESSION['mailcow_cc_role'] = "admin";
$_SESSION['mailcow_cc_last_login'] = last_login($login_user);
header("Location: /admin");
}
elseif ($as == "domainadmin") {
$_SESSION['mailcow_cc_username'] = $login_user;
$_SESSION['mailcow_cc_role'] = "domainadmin";
$_SESSION['mailcow_cc_last_login'] = last_login($login_user);
header("Location: /mailbox");
}
elseif ($as == "user") {
$_SESSION['mailcow_cc_username'] = $login_user;
$_SESSION['mailcow_cc_role'] = "user";
$_SESSION['mailcow_cc_last_login'] = last_login($login_user);
$http_parameters = explode('&', $_SESSION['index_query_string']);
unset($_SESSION['index_query_string']);
if (in_array('mobileconfig', $http_parameters)) {