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

subrepo: subdir:   "mailcow/src/mailcow-dockerized"
  merged:   "02ae5285"
upstream: origin:   "https://github.com/mailcow/mailcow-dockerized.git"
  branch:   "master"
  commit:   "649a5c01"
git-subrepo: version:  "0.4.3"
  origin:   "???"
  commit:   "???"
Change-Id: I870ad468fba026cc5abf3c5699ed1e12ff28b32b
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/functions.mailbox.inc.php b/mailcow/src/mailcow-dockerized/data/web/inc/functions.mailbox.inc.php
index fff4190..24e5dab 100644
--- a/mailcow/src/mailcow-dockerized/data/web/inc/functions.mailbox.inc.php
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/functions.mailbox.inc.php
@@ -35,7 +35,7 @@
           else {

             $username = $_SESSION['mailcow_cc_username'];

           }

-          if (!is_numeric($_data["validity"]) || $_data["validity"] > 672) {

+          if (isset($_data["validity"]) && !filter_var($_data["validity"], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 87600)))) {

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

               'type' => 'danger',

               'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),

@@ -43,8 +43,17 @@
             );

             return false;

           }

-          $domain = mailbox('get', 'mailbox_details', $username)['domain'];

-          if (!is_valid_domain_name($domain)) {

+          else {

+            // Default to 1 yr

+            $_data["validity"] = 8760;

+          }

+          $domain = $_data['domain'];

+          $valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain'];

+          $valid_alias_domains = user_get_alias_details($username)['alias_domains'];

+          if (!empty($valid_alias_domains)) {

+            $valid_domains = array_merge($valid_domains, $valid_alias_domains);

+          }

+          if (!in_array($domain, $valid_domains)) {

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

               'type' => 'danger',

               'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),

@@ -52,13 +61,11 @@
             );

             return false;

           }

-          $validity = strtotime("+".$_data["validity"]." hour");

-          $letters = 'abcefghijklmnopqrstuvwxyz1234567890';

-          $random_name = substr(str_shuffle($letters), 0, 24);

+          $validity = strtotime("+" . $_data["validity"] . " hour");

           $stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `goto`, `validity`) VALUES

             (:address, :goto, :validity)");

           $stmt->execute(array(

-            ':address' => $random_name . '@' . $domain,

+            ':address' => readable_random_string(rand(rand(3, 9), rand(3, 9))) . '.' . readable_random_string(rand(rand(3, 9), rand(3, 9))) . '@' . $domain,

             ':goto' => $username,

             ':validity' => $validity

           ));

@@ -441,17 +448,17 @@
             );

             return false;

           }

-          $domain				= idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);

+          $domain       = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);

           $description  = $_data['description'];

           if (empty($description)) {

             $description = $domain;

           }

-          $aliases			= $_data['aliases'];

-          $mailboxes    = $_data['mailboxes'];

-          $defquota			= $_data['defquota'];

-          $maxquota			= $_data['maxquota'];

-          $restart_sogo = $_data['restart_sogo'];

-          $quota				= $_data['quota'];

+          $aliases      = (int)$_data['aliases'];

+          $mailboxes    = (int)$_data['mailboxes'];

+          $defquota     = (int)$_data['defquota'];

+          $maxquota     = (int)$_data['maxquota'];

+          $restart_sogo = (int)$_data['restart_sogo'];

+          $quota        = (int)$_data['quota'];

           if ($defquota > $maxquota) {

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

                 'type' => 'danger',

@@ -673,9 +680,10 @@
                 continue;

               }

             }

+            $gotos = array_unique($gotos);

             $gotos = array_filter($gotos);

             if (empty($gotos)) { return false; }

-            $goto = implode(",", $gotos);

+            $goto = implode(",", (array)$gotos);

           }

           foreach ($addresses as $address) {

             if (empty($address)) {

@@ -928,7 +936,7 @@
           $password     = $_data['password'];

           $password2    = $_data['password2'];

           $name         = ltrim(rtrim($_data['name'], '>'), '<');

-          $quota_m			= intval($_data['quota']);

+          $quota_m      = intval($_data['quota']);

           if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && $quota_m === 0) {

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

               'type' => 'danger',

@@ -948,9 +956,10 @@
           $imap_access = (isset($_data['imap_access'])) ? intval($_data['imap_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);

           $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']);

+          $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']);

-          $quota_b		= ($quota_m * 1048576);

+          $quota_b    = ($quota_m * 1048576);

           $mailbox_attrs = json_encode(

             array(

               'force_pw_update' => strval($force_pw_update),

@@ -960,6 +969,8 @@
               'imap_access' => strval($imap_access),

               'pop3_access' => strval($pop3_access),

               'smtp_access' => strval($smtp_access),

+              'relayhost' => strval($relayhost),

+              'passwd_update' => time(),

               'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']),

               'quarantine_notification' => strval($quarantine_notification),

               'quarantine_category' => strval($quarantine_category)

@@ -989,7 +1000,7 @@
             COUNT(*) as count,

             COALESCE(ROUND(SUM(`quota`)/1048576), 0) as `quota`

               FROM `mailbox`

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

+                WHERE (`kind` = '' OR `kind` = NULL)

                   AND `domain` = :domain");

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

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

@@ -1037,39 +1048,10 @@
             );

             return false;

           }

-          if (!empty($password) && !empty($password2)) {

-            if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {

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

-                'type' => 'danger',

-                'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),

-                'msg' => 'password_complexity'

-              );

-              return false;

-            }

-            if ($password != $password2) {

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

-                'type' => 'danger',

-                'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),

-                'msg' => 'password_mismatch'

-              );

-              return false;

-            }

-            // support pre hashed passwords

-            if (preg_match('/^({SSHA256}|{SSHA}|{SHA512-CRYPT}|{SSHA512}|{MD5-CRYPT}|{PLAIN-MD5})/i', $password)) {

-              $password_hashed = $password;

-            }

-            else {

-              $password_hashed = hash_password($password);

-            }

-          }

-          else {

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

-              'type' => 'danger',

-              'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),

-              'msg' => 'password_empty'

-            );

+          if (password_check($password, $password2) !== true) {

             return false;

           }

+          $password_hashed = hash_password($password);

           if ($MailboxData['count'] >= $DomainData['mailboxes']) {

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

               'type' => 'danger',

@@ -1107,6 +1089,12 @@
             ':mailbox_attrs' => $mailbox_attrs,

             ':active' => $active

           ));

+          $stmt = $pdo->prepare("UPDATE `mailbox` SET

+            `attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW())

+              WHERE `username` = :username");

+          $stmt->execute(array(

+            ':username' => $username

+          ));

           $stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`)

             VALUES (:username, '0', '0') ON DUPLICATE KEY UPDATE `bytes` = '0', `messages` = '0';");

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

@@ -1506,8 +1494,8 @@
               );

               continue;

             }

-            $lowspamlevel	= explode(',', $_data['spam_score'])[0];

-            $highspamlevel	= explode(',', $_data['spam_score'])[1];

+            $lowspamlevel = explode(',', $_data['spam_score'])[0];

+            $highspamlevel  = explode(',', $_data['spam_score'])[1];

             if (!is_numeric($lowspamlevel) || !is_numeric($highspamlevel)) {

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

                 'type' => 'danger',

@@ -1516,6 +1504,9 @@
               );

               continue;

             }

+            if ($lowspamlevel == $highspamlevel) {

+              $highspamlevel = $highspamlevel + 0.1;

+            }

             $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username

               AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')");

             $stmt->execute(array(

@@ -1581,7 +1572,7 @@
             $_SESSION['return'][] = array(

               'type' => 'success',

               'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),

-              'msg' => array('mailbox_modified', htmlspecialchars(implode(', ', $usernames)))

+              'msg' => array('mailbox_modified', htmlspecialchars(implode(', ', (array)$usernames)))

             );

           }

         break;

@@ -1932,6 +1923,52 @@
               );

               continue;

             }

+            if ($_data['expand_alias'] === true || $_data['expand_alias'] == 1) {

+              $stmt = $pdo->prepare("SELECT `address` FROM `alias`

+                WHERE `address` = :address

+                  AND `domain` NOT IN (

+                    SELECT `alias_domain` FROM `alias_domain`

+                  )");

+              $stmt->execute(array(

+                ':address' => $address,

+              ));

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

+              if ($num_results == 0) {

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

+                  'type' => 'warning',

+                  'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),

+                  'msg' => array('is_not_primary_alias', htmlspecialchars($address))

+                );

+                continue;

+              }

+              $stmt = $pdo->prepare("SELECT `goto`, GROUP_CONCAT(CONCAT(SUBSTRING(`alias`.`address`, 1, LOCATE('@', `alias`.`address`) - 1), '@', `alias_domain`.`alias_domain`)) AS `missing_alias`

+                FROM `alias` JOIN `alias_domain` ON `alias_domain`.`target_domain` = `alias`.`domain`

+                    WHERE CONCAT(SUBSTRING(`alias`.`address`, 1, LOCATE('@', `alias`.`address`) - 1), '@', `alias_domain`.`alias_domain`) NOT IN (

+                      SELECT `address` FROM `alias` WHERE `address` != `goto`

+                    )

+                    AND `alias`.`address` NOT IN (

+                      SELECT `address` FROM `alias` WHERE `address` = `goto`

+                    )

+                    AND `address` = :address ;");

+              $stmt->execute(array(

+                ':address' => $address

+              ));

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

+              if (!empty($missing_aliases['missing_alias'])) {

+                mailbox('add', 'alias', array(

+                  'address' => $missing_aliases['missing_alias'],

+                  'goto' => $missing_aliases['goto'],

+                  'sogo_visible' => 1,

+                  'active' => 1

+                ));

+              }

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

+                'type' => 'success',

+                'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),

+                'msg' => array('alias_modified', htmlspecialchars($address))

+              );

+              continue;

+            }

             $domain = idn_to_ascii(substr(strstr($address, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);

             if ($is_now['address'] != $address) {

               $local_part = strstr($address, '@', true);

@@ -2041,8 +2078,9 @@
                   ':address' => $address

                 ));

               }

+              $gotos = array_unique($gotos);

               $gotos = array_filter($gotos);

-              $goto = implode(",", $gotos);

+              $goto = implode(",", (array)$gotos);

             }

             if (!empty($goto)) {

               $stmt = $pdo->prepare("UPDATE `alias` SET

@@ -2096,6 +2134,7 @@
               if (!empty($is_now)) {

                 $gal                  = (isset($_data['gal'])) ? intval($_data['gal']) : $is_now['gal'];

                 $description          = (!empty($_data['description']) && isset($_SESSION['acl']['domain_desc']) && $_SESSION['acl']['domain_desc'] == "1") ? $_data['description'] : $is_now['description'];

+                (int)$relayhost       = (isset($_data['relayhost']) && isset($_SESSION['acl']['domain_relayhost']) && $_SESSION['acl']['domain_relayhost'] == "1") ? intval($_data['relayhost']) : intval($is_now['relayhost']);

               }

               else {

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

@@ -2157,7 +2196,7 @@
                   MAX(COALESCE(ROUND(`quota`/1048576), 0)) AS `biggest_mailbox`,

                   COALESCE(ROUND(SUM(`quota`)/1048576), 0) AS `quota_all`

                     FROM `mailbox`

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

+                      WHERE (`kind` = '' OR `kind` = NULL)

                         AND domain = :domain");

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

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

@@ -2300,6 +2339,7 @@
               (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)$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'];

@@ -2324,11 +2364,6 @@
               );

               return false;

             }

-            $stmt = $pdo->prepare("SELECT `quota`, `maxquota`

-              FROM `domain`

-                WHERE `domain` = :domain");

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

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

             if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {

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

                 'type' => 'danger',

@@ -2337,15 +2372,8 @@
               );

               continue;

             }

-            if ($quota_m > $DomainData['maxquota']) {

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

-                'type' => 'danger',

-                'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),

-                'msg' => array('mailbox_quota_exceeded', $DomainData['maxquota'])

-              );

-              continue;

-            }

-            if (((($is_now['quota_used'] / 1048576) - $quota_m) + $quota_m) > $DomainData['quota']) {

+            $DomainData = mailbox('get', 'domain_details', $domain);

+            if ($quota_m > ($is_now['max_new_quota'] / 1048576)) {

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

                 'type' => 'danger',

                 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),

@@ -2353,6 +2381,14 @@
               );

               continue;

             }

+            if ($quota_m > $DomainData['max_quota_for_mbox']) {

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

+                'type' => 'danger',

+                'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),

+                'msg' => array('mailbox_quota_exceeded', $DomainData['max_quota_for_mbox'])

+              );

+              continue;

+            }

             $extra_acls = array();

             if (isset($_data['extended_sender_acl'])) {

               if (!isset($_SESSION['acl']['extend_sender_acl']) || $_SESSION['acl']['extend_sender_acl'] != "1" ) {

@@ -2539,39 +2575,21 @@
                 ));

               }

             }

-            if (!empty($password) && !empty($password2)) {

-              if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {

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

-                  'type' => 'danger',

-                  'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),

-                  'msg' => 'password_complexity'

-                );

+            if (!empty($password)) {

+              if (password_check($password, $password2) !== true) {

                 continue;

               }

-              if ($password != $password2) {

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

-                  'type' => 'danger',

-                  'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),

-                  'msg' => 'password_mismatch'

-                );

-                continue;

-              }

-              // support pre hashed passwords

-              if (preg_match('/^({SSHA256}|{SSHA}|{SHA512-CRYPT}|{SSHA512}|{MD5-CRYPT}|{PLAIN-MD5})/i', $password)) {

-                $password_hashed = $password;

-              }

-              else {

-                $password_hashed = hash_password($password);

-              }

+              $password_hashed = hash_password($password);

               $stmt = $pdo->prepare("UPDATE `mailbox` SET

-                  `password` = :password_hashed

+                  `password` = :password_hashed,

+                  `attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW())

                     WHERE `username` = :username");

               $stmt->execute(array(

                 ':password_hashed' => $password_hashed,

                 ':username' => $username

               ));

             }

-            // We could either set alias = 1 if alias = 2 or tune the Postfix alias table (that's what we did, TODO: to it the other way)

+            // We could either set alias = 1 if alias = 2 or tune the Postfix alias table (that's what we did, TODO: do it the other way)

             $stmt = $pdo->prepare("UPDATE `alias` SET

                 `active` = :active

                   WHERE `address` = :address");

@@ -2587,6 +2605,7 @@
                 `attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access),

                 `attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access),

                 `attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access),

+                `attributes` = JSON_SET(`attributes`, '$.relayhost', :relayhost),

                 `attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access)

                   WHERE `username` = :username");

             $stmt->execute(array(

@@ -2598,6 +2617,7 @@
               ':imap_access' => $imap_access,

               ':pop3_access' => $pop3_access,

               ':smtp_access' => $smtp_access,

+              ':relayhost' => $relayhost,

               ':username' => $username

             ));

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

@@ -2819,7 +2839,7 @@
             return false;

           }

           elseif (isset($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {

-            $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` = :domain");

+            $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND `domain` = :domain");

             $stmt->execute(array(

               ':domain' => $_data,

             ));

@@ -2829,7 +2849,7 @@
             }

           }

           else {

-            $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND (`domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role)");

+            $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND (`domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role)");

             $stmt->execute(array(

               ':username' => $_SESSION['mailcow_cc_username'],

               ':role' => $_SESSION['mailcow_cc_role'],

@@ -2982,7 +3002,7 @@
             while($field = array_shift($fields)) {

               $shown_fields[] = $field['Field'];

             }

-            $stmt = $pdo->prepare("SELECT " . implode(',', $shown_fields) . ",

+            $stmt = $pdo->prepare("SELECT " . implode(',', (array)$shown_fields) . ",

               `active`

                 FROM `imapsync` WHERE id = :id");

           }

@@ -2997,7 +3017,7 @@
             while($field = array_shift($fields)) {

               $shown_fields[] = $field['Field'];

             }

-            $stmt = $pdo->prepare("SELECT " . implode(',', $shown_fields) . ",

+            $stmt = $pdo->prepare("SELECT " . implode(',', (array)$shown_fields) . ",

               `active`

                 FROM `imapsync` WHERE id = :id");

           }

@@ -3113,7 +3133,9 @@
           }

           $stmt = $pdo->prepare("SELECT `address`,

             `goto`,

-            `validity`

+            `validity`,

+            `created`,

+            `modified`

               FROM `spamalias`

                 WHERE `goto` = :username

                   AND `validity` >= :unixnow");

@@ -3362,10 +3384,10 @@
           if (empty($row)) {

             return false;

           }

-          $stmt = $pdo->prepare("SELECT COUNT(*) AS `count`,

+          $stmt = $pdo->prepare("SELECT COUNT(`username`) AS `count`,

             COALESCE(SUM(`quota`), 0) AS `in_use`

               FROM `mailbox`

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

+                WHERE (`kind` = '' OR `kind` = NULL)

                   AND `domain` = :domain");

           $stmt->execute(array(':domain' => $row['domain']));

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

@@ -3377,7 +3399,7 @@
           $stmt->execute(array(':domain' => $row['domain']));

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

           $rl = ratelimit('get', 'domain', $_data);

-          $domaindata['max_new_mailbox_quota']	= ($row['quota'] * 1048576) - $MailboxDataDomain['in_use'];

+          $domaindata['max_new_mailbox_quota']  = ($row['quota'] * 1048576) - $MailboxDataDomain['in_use'];

           if ($domaindata['max_new_mailbox_quota'] > ($row['maxquota'] * 1048576)) {

             $domaindata['max_new_mailbox_quota'] = ($row['maxquota'] * 1048576);

           }

@@ -3399,7 +3421,7 @@
             $domaindata['msgs_total'] = 0;

           }

           $domaindata['mboxes_in_domain'] = $MailboxDataDomain['count'];

-          $domaindata['mboxes_left'] = $row['mailboxes']	- $MailboxDataDomain['count'];

+          $domaindata['mboxes_left'] = $row['mailboxes']  - $MailboxDataDomain['count'];

           $domaindata['domain_name'] = $row['domain'];

           $domaindata['description'] = $row['description'];

           $domaindata['max_num_aliases_for_domain'] = $row['aliases'];

@@ -3419,7 +3441,7 @@
           $domaindata['relay_all_recipients_int'] = $row['relay_all_recipients'];

           $domaindata['relay_unknown_only'] = $row['relay_unknown_only'];

           $domaindata['relay_unknown_only_int'] = $row['relay_unknown_only'];

-          $stmt = $pdo->prepare("SELECT COUNT(*) AS `alias_count` FROM `alias`

+          $stmt = $pdo->prepare("SELECT COUNT(`address`) AS `alias_count` FROM `alias`

             WHERE (`domain`= :domain OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain2))

               AND `address` NOT IN (

                 SELECT `username` FROM `mailbox`

@@ -3430,7 +3452,7 @@
           ));

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

           (isset($AliasDataDomain['alias_count'])) ? $domaindata['aliases_in_domain'] = $AliasDataDomain['alias_count'] : $domaindata['aliases_in_domain'] = "0";

-          $domaindata['aliases_left'] = $row['aliases']	- $AliasDataDomain['alias_count'];

+          $domaindata['aliases_left'] = $row['aliases'] - $AliasDataDomain['alias_count'];

           if ($_SESSION['mailcow_cc_role'] == "admin")

           {

               $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`username` SEPARATOR ', ') AS domain_admins FROM `domain_admins` WHERE `domain` = :domain");

@@ -3447,19 +3469,6 @@
             return false;

           }

           $mailboxdata = array();

-          $rl = ratelimit('get', 'mailbox', $_data);

-          $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`,

@@ -3473,7 +3482,10 @@
               `attributes`,

               `quota2`.`messages`

                 FROM `mailbox`, `quota2`, `domain`

-                  WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group' AND `mailbox`.`username` = `quota2`.`username` AND `domain`.`domain` = `mailbox`.`domain` AND `mailbox`.`username` = :mailbox");

+                  WHERE (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)

+                    AND `mailbox`.`username` = `quota2`.`username`

+                    AND `domain`.`domain` = `mailbox`.`domain`

+                    AND `mailbox`.`username` = :mailbox");

           }

           else {

             $stmt = $pdo->prepare("SELECT

@@ -3488,53 +3500,29 @@
               `attributes`,

               `quota2replica`.`messages`

                 FROM `mailbox`, `quota2replica`, `domain`

-                  WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group' AND `mailbox`.`username` = `quota2replica`.`username` AND `domain`.`domain` = `mailbox`.`domain` AND `mailbox`.`username` = :mailbox");

+                  WHERE (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)

+                    AND `mailbox`.`username` = `quota2replica`.`username`

+                    AND `domain`.`domain` = `mailbox`.`domain`

+                    AND `mailbox`.`username` = :mailbox");

           }

           $stmt->execute(array(

             ':mailbox' => $_data,

           ));

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

-          $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);

-          $stmt = $pdo->prepare("SELECT COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` = :domain AND `username` != :username");

-          $stmt->execute(array(':domain' => $row['domain'], ':username' => $_data));

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

-          $stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND `validity` >= :unixnow");

-          $stmt->execute(array(':address' => $_data, ':unixnow' => time()));

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

-          $mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use'];

-          if ($mailboxdata['max_new_quota'] > ($DomainQuota['maxquota'] * 1048576)) {

-            $mailboxdata['max_new_quota'] = ($DomainQuota['maxquota'] * 1048576);

-          }

+

           $mailboxdata['username'] = $row['username'];

-          if (!empty($rl)) {

-            $mailboxdata['rl'] = $rl;

-            $mailboxdata['rl_scope'] = 'mailbox';

-          }

-          else {

-            $mailboxdata['rl'] = ratelimit('get', 'domain', $row['domain']);

-            $mailboxdata['rl_scope'] = 'domain';

-          }

-          $mailboxdata['is_relayed'] = $row['backupmx'];

-          $mailboxdata['name'] = $row['name'];

-          $mailboxdata['last_imap_login'] = $last_imap_login;

-          $mailboxdata['last_smtp_login'] = $last_smtp_login;

-          $mailboxdata['last_pop3_login'] = $last_pop3_login;

           $mailboxdata['active'] = $row['active'];

           $mailboxdata['active_int'] = $row['active'];

           $mailboxdata['domain'] = $row['domain'];

+          $mailboxdata['relayhost'] = $row['relayhost'];

+          $mailboxdata['name'] = $row['name'];

           $mailboxdata['local_part'] = $row['local_part'];

           $mailboxdata['quota'] = $row['quota'];

+          $mailboxdata['messages'] = $row['messages'];

           $mailboxdata['attributes'] = json_decode($row['attributes'], true);

           $mailboxdata['quota_used'] = intval($row['bytes']);

           $mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100);

-          $mailboxdata['messages'] = $row['messages'];

-          $mailboxdata['spam_aliases'] = $SpamaliasUsage['sa_count'];

-          $mailboxdata['pushover_active'] = ($PushoverActive['pushover_active'] == 1) ? 1 : 0;

+

           if ($mailboxdata['percent_in_use'] === '- ') {

             $mailboxdata['percent_class'] = "info";

           }

@@ -3547,6 +3535,69 @@
           else {

             $mailboxdata['percent_class'] = "success";

           }

+

+          // Determine last logins

+          $stmt = $pdo->prepare("SELECT MAX(`datetime`) AS `datetime`, `service` FROM `sasl_log`

+            WHERE `username` = :mailbox

+                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);

+            $stmt = $pdo->prepare("SELECT COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND `domain` = :domain AND `username` != :username");

+            $stmt->execute(array(':domain' => $row['domain'], ':username' => $_data));

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

+            $stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND `validity` >= :unixnow");

+            $stmt->execute(array(':address' => $_data, ':unixnow' => time()));

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

+            $mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use'];

+            $mailboxdata['spam_aliases'] = $SpamaliasUsage['sa_count'];

+            $mailboxdata['pushover_active'] = ($PushoverActive['pushover_active'] == 1) ? 1 : 0;

+            if ($mailboxdata['max_new_quota'] > ($DomainQuota['maxquota'] * 1048576)) {

+              $mailboxdata['max_new_quota'] = ($DomainQuota['maxquota'] * 1048576);

+            }

+            if (!empty($rl)) {

+              $mailboxdata['rl'] = $rl;

+              $mailboxdata['rl_scope'] = 'mailbox';

+            }

+            else {

+              $mailboxdata['rl'] = ratelimit('get', 'domain', $row['domain']);

+              $mailboxdata['rl_scope'] = 'domain';

+            }

+            $mailboxdata['is_relayed'] = $row['backupmx'];

+          }

+

           return $mailboxdata;

         break;

         case 'resource_details':

@@ -3826,7 +3877,7 @@
               );

               continue;

             }

-            $domain	= idn_to_ascii(strtolower(trim($domain)), 0, INTL_IDNA_VARIANT_UTS46);

+            $domain = idn_to_ascii(strtolower(trim($domain)), 0, INTL_IDNA_VARIANT_UTS46);

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

               WHERE `domain` = :domain");

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

@@ -4163,6 +4214,14 @@
             $stmt->execute(array(

               ':username' => $username

             ));

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

+            $stmt->execute(array(

+              ':username' => $username,

+            ));

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

+            $stmt->execute(array(

+              ':username' => $username,

+            ));

             $stmt = $pdo->prepare("SELECT `address`, `goto` FROM `alias`

                 WHERE `goto` REGEXP :username");

             $stmt->execute(array(':username' => '(^|,)'.$username.'($|,)'));

@@ -4172,7 +4231,7 @@
               if (($key = array_search($username, $goto_exploded)) !== false) {

                 unset($goto_exploded[$key]);

               }

-              $gotos_rebuild = implode(',', $goto_exploded);

+              $gotos_rebuild = implode(',', (array)$goto_exploded);

               $stmt = $pdo->prepare("UPDATE `alias` SET

                 `goto` = :goto

                   WHERE `address` = :address");