git subrepo commit (merge) mailcow/src/mailcow-dockerized

subrepo: subdir:   "mailcow/src/mailcow-dockerized"
  merged:   "32243e56"
upstream: origin:   "https://github.com/mailcow/mailcow-dockerized.git"
  branch:   "master"
  commit:   "e2b4b6f6"
git-subrepo: version:  "0.4.3"
  origin:   "???"
  commit:   "???"
Change-Id: I51e2016ef5ab88a8b0bdc08551b18f48ceef0aa5
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/functions.inc.php b/mailcow/src/mailcow-dockerized/data/web/inc/functions.inc.php
index 142a9fe..7ac0af5 100644
--- a/mailcow/src/mailcow-dockerized/data/web/inc/functions.inc.php
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/functions.inc.php
@@ -258,7 +258,7 @@
   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`, `app_password` FROM `sasl_log`

+        $stmt = $pdo->prepare('SELECT `real_rip`, MAX(`datetime`) as `datetime`, `service`, `app_password`, MAX(`app_passwd`.`name`) as `app_password_name` FROM `sasl_log`

           LEFT OUTER JOIN `app_passwd` on `sasl_log`.`app_password` = `app_passwd`.`id`

           WHERE `username` = :username

             AND HOUR(TIMEDIFF(NOW(), `datetime`)) < :sasl_limit_days

@@ -807,10 +807,11 @@
   }

   return false;

 }

-function check_login($user, $pass) {

+function check_login($user, $pass, $app_passwd_data = false) {

   global $pdo;

   global $redis;

   global $imap_server;

+

   if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {

     $_SESSION['return'][] =  array(

       'type' => 'danger',

@@ -819,6 +820,8 @@
     );

     return false;

   }

+

+  // Validate admin

   $user = strtolower(trim($user));

   $stmt = $pdo->prepare("SELECT `password` FROM `admin`

       WHERE `superadmin` = '1'

@@ -854,6 +857,8 @@
       }

     }

   }

+

+  // Validate domain admin

   $stmt = $pdo->prepare("SELECT `password` FROM `admin`

       WHERE `superadmin` = '0'

       AND `active`='1'

@@ -888,6 +893,8 @@
       }

     }

   }

+

+  // Validate mailbox user

   $stmt = $pdo->prepare("SELECT `password` FROM `mailbox`

       INNER JOIN domain on mailbox.domain = domain.domain

       WHERE `kind` NOT REGEXP 'location|thing|group'

@@ -896,6 +903,32 @@
         AND `username` = :user");

   $stmt->execute(array(':user' => $user));

   $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

+  if ($app_passwd_data['eas'] === true) {

+    $stmt = $pdo->prepare("SELECT `app_passwd`.`password` as `password`, `app_passwd`.`id` as `app_passwd_id` FROM `app_passwd`

+        INNER JOIN `mailbox` ON `mailbox`.`username` = `app_passwd`.`mailbox`

+        INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain`

+        WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group'

+          AND `mailbox`.`active` = '1'

+          AND `domain`.`active` = '1'

+          AND `app_passwd`.`active` = '1'

+          AND `app_passwd`.`eas_access` = '1'

+          AND `app_passwd`.`mailbox` = :user");

+    $stmt->execute(array(':user' => $user));

+    $rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC));

+  }

+  elseif ($app_passwd_data['dav'] === true) {

+    $stmt = $pdo->prepare("SELECT `app_passwd`.`password` as `password`, `app_passwd`.`id` as `app_passwd_id` FROM `app_passwd`

+        INNER JOIN `mailbox` ON `mailbox`.`username` = `app_passwd`.`mailbox`

+        INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain`

+        WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group'

+          AND `mailbox`.`active` = '1'

+          AND `domain`.`active` = '1'

+          AND `app_passwd`.`active` = '1'

+          AND `app_passwd`.`dav_access` = '1'

+          AND `app_passwd`.`mailbox` = :user");

+    $stmt->execute(array(':user' => $user));

+    $rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC));

+  }

   foreach ($rows as $row) {

     if (verify_hash($row['password'], $pass) !== false) {

       unset($_SESSION['ldelay']);

@@ -904,9 +937,20 @@
         'log' => array(__FUNCTION__, $user, '*'),

         'msg' => array('logged_in_as', $user)

       );

+      if ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) {

+        $service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV';

+        $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)");

+        $stmt->execute(array(

+          ':service' => $service,

+          ':app_id' => $row['app_passwd_id'],

+          ':username' => $user,

+          ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'])

+        ));

+      }

       return "user";

     }

   }

+

   if (!isset($_SESSION['ldelay'])) {

     $_SESSION['ldelay'] = "0";

     $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);

@@ -917,11 +961,13 @@
     $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);

     error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);

   }

+

   $_SESSION['return'][] =  array(

     'type' => 'danger',

     'log' => array(__FUNCTION__, $user, '*'),

     'msg' => 'login_failed'

   );

+

   sleep($_SESSION['ldelay']);

   return false;

 }

@@ -1222,8 +1268,8 @@
     case "totp":

       $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];

       if ($tfa->verifyCode($_POST['totp_secret'], $_POST['totp_confirm_token']) === true) {

-        $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");

-        $stmt->execute(array(':username' => $username));

+        //$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");

+        //$stmt->execute(array(':username' => $username));

         $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `secret`, `active`) VALUES (?, ?, 'totp', ?, '1')");

         $stmt->execute(array($username, $key_id, $_POST['totp_secret']));

         $_SESSION['return'][] =  array(

@@ -1610,15 +1656,17 @@
           AND `authmech` = 'totp'

           AND `active`='1'");

       $stmt->execute(array(':username' => $username));

-      $row = $stmt->fetch(PDO::FETCH_ASSOC);

-      if ($tfa->verifyCode($row['secret'], $_POST['token']) === true) {

-        $_SESSION['tfa_id'] = $row['id'];

-        $_SESSION['return'][] =  array(

-          'type' => 'success',

-          'log' => array(__FUNCTION__, $username, '*'),

-          'msg' => 'verified_totp_login'

-        );

-        return true;

+      $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

+      foreach ($rows as $row) {

+        if ($tfa->verifyCode($row['secret'], $_POST['token']) === true) {

+          $_SESSION['tfa_id'] = $row['id'];

+          $_SESSION['return'][] =  array(

+            'type' => 'success',

+            'log' => array(__FUNCTION__, $username, '*'),

+            'msg' => 'verified_totp_login'

+          );

+          return true;

+        }

       }

       $_SESSION['return'][] =  array(

         'type' => 'danger',