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.inc.php b/mailcow/src/mailcow-dockerized/data/web/inc/functions.inc.php
index e3f2882..142a9fe 100644
--- a/mailcow/src/mailcow-dockerized/data/web/inc/functions.inc.php
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/functions.inc.php
@@ -13,6 +13,17 @@
return false;
}
}
+function readable_random_string($length = 8) {
+ $string = '';
+ $vowels = array('a', 'e', 'i', 'o', 'u');
+ $consonants = array('b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z');
+ $max = $length / 2;
+ for ($i = 1; $i <= $max; $i++) {
+ $string .= $consonants[rand(0,19)];
+ $string .= $vowels[rand(0,4)];
+ }
+ return $string;
+}
// Validates ips and cidrs
function valid_network($network) {
if (filter_var($network, FILTER_VALIDATE_IP)) {
@@ -88,40 +99,253 @@
// in case default pass scheme is not defined, falling back to BLF-CRYPT.
global $default_pass_scheme;
$pw_hash = NULL;
- switch (strtoupper($default_pass_scheme)) {
- case "SSHA":
- $salt_str = bin2hex(openssl_random_pseudo_bytes(8));
- $pw_hash = "{SSHA}".base64_encode(hash('sha1', $password . $salt_str, true) . $salt_str);
- break;
- case "SSHA256":
- $salt_str = bin2hex(openssl_random_pseudo_bytes(8));
- $pw_hash = "{SSHA256}".base64_encode(hash('sha256', $password . $salt_str, true) . $salt_str);
- break;
- case "SSHA512":
- $salt_str = bin2hex(openssl_random_pseudo_bytes(8));
- $pw_hash = "{SSHA512}".base64_encode(hash('sha512', $password . $salt_str, true) . $salt_str);
- break;
- case "BLF-CRYPT":
- default:
- $pw_hash = "{BLF-CRYPT}" . password_hash($password, PASSWORD_BCRYPT);
- break;
+ // support pre-hashed passwords
+ if (preg_match('/^{(ARGON2I|ARGON2ID|BLF-CRYPT|CLEAR|CLEARTEXT|CRYPT|DES-CRYPT|LDAP-MD5|MD5|MD5-CRYPT|PBKDF2|PLAIN|PLAIN-MD4|PLAIN-MD5|PLAIN-TRUNC|PLAIN-TRUNC|SHA|SHA1|SHA256|SHA256-CRYPT|SHA512|SHA512-CRYPT|SMD5|SSHA|SSHA256|SSHA512)}/i', $password)) {
+ $pw_hash = $password;
+ }
+ else {
+ switch (strtoupper($default_pass_scheme)) {
+ case "SSHA":
+ $salt_str = bin2hex(openssl_random_pseudo_bytes(8));
+ $pw_hash = "{SSHA}".base64_encode(hash('sha1', $password . $salt_str, true) . $salt_str);
+ break;
+ case "SSHA256":
+ $salt_str = bin2hex(openssl_random_pseudo_bytes(8));
+ $pw_hash = "{SSHA256}".base64_encode(hash('sha256', $password . $salt_str, true) . $salt_str);
+ break;
+ case "SSHA512":
+ $salt_str = bin2hex(openssl_random_pseudo_bytes(8));
+ $pw_hash = "{SSHA512}".base64_encode(hash('sha512', $password . $salt_str, true) . $salt_str);
+ break;
+ case "BLF-CRYPT":
+ default:
+ $pw_hash = "{BLF-CRYPT}" . password_hash($password, PASSWORD_BCRYPT);
+ break;
+ }
}
return $pw_hash;
}
-function last_login($user) {
- 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;
+function password_complexity($_action, $_data = null) {
+ global $redis;
+ global $lang;
+ switch ($_action) {
+ case 'edit':
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_data),
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
+ $is_now = password_complexity('get');
+ if (!empty($is_now)) {
+ $length = (isset($_data['length']) && intval($_data['length']) >= 3) ? intval($_data['length']) : $is_now['length'];
+ $chars = (isset($_data['chars'])) ? intval($_data['chars']) : $is_now['chars'];
+ $lowerupper = (isset($_data['lowerupper'])) ? intval($_data['lowerupper']) : $is_now['lowerupper'];
+ $special_chars = (isset($_data['special_chars'])) ? intval($_data['special_chars']) : $is_now['special_chars'];
+ $numbers = (isset($_data['numbers'])) ? intval($_data['numbers']) : $is_now['numbers'];
+ }
+ try {
+ $redis->hMSet('PASSWD_POLICY', [
+ 'length' => $length,
+ 'chars' => $chars,
+ 'special_chars' => $special_chars,
+ 'lowerupper' => $lowerupper,
+ 'numbers' => $numbers
+ ]);
+ }
+ catch (RedisException $e) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_data),
+ 'msg' => array('redis_error', $e)
+ );
+ return false;
+ }
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array(__FUNCTION__, $_action, $_data),
+ 'msg' => 'password_policy_saved'
+ );
+ break;
+ case 'get':
+ try {
+ $length = $redis->hGet('PASSWD_POLICY', 'length');
+ $chars = $redis->hGet('PASSWD_POLICY', 'chars');
+ $special_chars = $redis->hGet('PASSWD_POLICY', 'special_chars');
+ $lowerupper = $redis->hGet('PASSWD_POLICY', 'lowerupper');
+ $numbers = $redis->hGet('PASSWD_POLICY', 'numbers');
+ return array(
+ 'length' => $length,
+ 'chars' => $chars,
+ 'special_chars' => $special_chars,
+ 'lowerupper' => $lowerupper,
+ 'numbers' => $numbers
+ );
+ }
+ catch (RedisException $e) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_data),
+ 'msg' => array('redis_error', $e)
+ );
+ return false;
+ }
+ return false;
+ break;
+ case 'html':
+ $policies = password_complexity('get');
+ foreach ($policies as $name => $value) {
+ if ($value != 0) {
+ $policy_text[] = sprintf($lang['admin']["password_policy_$name"], $value);
+ }
+ }
+ return '<p class="help-block small">- ' . implode('<br>- ', (array)$policy_text) . '</p>';
+ break;
}
- else {
+}
+function password_check($password1, $password2) {
+ $password_complexity = password_complexity('get');
+
+ if (empty($password1) || empty($password2)) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_type),
+ 'msg' => 'password_complexity'
+ );
return false;
}
+
+ if ($password1 != $password2) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_type),
+ 'msg' => 'password_mismatch'
+ );
+ return false;
+ }
+
+ $given_password['length'] = strlen($password1);
+ $given_password['special_chars'] = preg_match('/[^a-zA-Z\d]/', $password1);
+ $given_password['chars'] = preg_match('/[a-zA-Z]/',$password1);
+ $given_password['numbers'] = preg_match('/\d/', $password1);
+ $lower = strlen(preg_replace("/[^a-z]/", '', $password1));
+ $upper = strlen(preg_replace("/[^A-Z]/", '', $password1));
+ $given_password['lowerupper'] = ($lower > 0 && $upper > 0) ? true : false;
+
+ if (
+ ($given_password['length'] < $password_complexity['length']) ||
+ ($password_complexity['special_chars'] == 1 && (intval($given_password['special_chars']) != $password_complexity['special_chars'])) ||
+ ($password_complexity['chars'] == 1 && (intval($given_password['chars']) != $password_complexity['chars'])) ||
+ ($password_complexity['numbers'] == 1 && (intval($given_password['numbers']) != $password_complexity['numbers'])) ||
+ ($password_complexity['lowerupper'] == 1 && (intval($given_password['lowerupper']) != $password_complexity['lowerupper']))
+ ) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_type),
+ 'msg' => 'password_complexity'
+ );
+ return false;
+ }
+
+ return true;
+}
+function last_login($action, $username, $sasl_limit_days = 7) {
+ global $pdo;
+ global $redis;
+ $sasl_limit_days = intval($sasl_limit_days);
+ 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`
+ LEFT OUTER JOIN `app_passwd` on `sasl_log`.`app_password` = `app_passwd`.`id`
+ WHERE `username` = :username
+ AND HOUR(TIMEDIFF(NOW(), `datetime`)) < :sasl_limit_days
+ GROUP BY `real_rip`, `service`, `app_password`
+ ORDER BY `datetime` DESC;');
+ $stmt->execute(array(':username' => $username, ':sasl_limit_days' => ($sasl_limit_days * 24)));
+ $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'] . ')';
+ }
+ elseif (filter_var($sasl[$k]['real_rip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
+ try {
+ $sasl[$k]['location'] = $redis->hGet('IP_SHORTCOUNTRY', $sasl[$k]['real_rip']);
+ }
+ catch (RedisException $e) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_data_log),
+ 'msg' => array('redis_error', $e)
+ );
+ return false;
+ }
+ if (!$sasl[$k]['location']) {
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_URL,"https://dfdata.bella.network/lookup/" . $sasl[$k]['real_rip']);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($curl, CURLOPT_USERAGENT, 'Moocow');
+ curl_setopt($curl, CURLOPT_TIMEOUT, 5);
+ $ip_data = curl_exec($curl);
+ if (!curl_errno($curl)) {
+ $ip_data_array = json_decode($ip_data, true);
+ if ($ip_data_array !== false and !empty($ip_data_array['location']['shortcountry'])) {
+ $sasl[$k]['location'] = $ip_data_array['location']['shortcountry'];
+ try {
+ $redis->hSet('IP_SHORTCOUNTRY', $sasl[$k]['real_rip'], $ip_data_array['location']['shortcountry']);
+ }
+ catch (RedisException $e) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_data_log),
+ 'msg' => array('redis_error', $e)
+ );
+ curl_close($curl);
+ return false;
+ }
+ }
+ }
+ curl_close($curl);
+ }
+ }
+ }
+ }
+ 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_log`
+ WHERE `username` = :username');
+ $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 {
@@ -135,13 +359,13 @@
}
function sys_mail($_data) {
if ($_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
'log' => array(__FUNCTION__),
- 'msg' => 'access_denied'
- );
- return false;
- }
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
$excludes = $_data['mass_exclude'];
$includes = $_data['mass_include'];
$mailboxes = array();
@@ -150,28 +374,28 @@
$mass_html = $_data['mass_html'];
$mass_subject = $_data['mass_subject'];
if (!filter_var($mass_from, FILTER_VALIDATE_EMAIL)) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
'log' => array(__FUNCTION__),
- 'msg' => 'from_invalid'
- );
- return false;
+ 'msg' => 'from_invalid'
+ );
+ return false;
}
if (empty($mass_subject)) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
'log' => array(__FUNCTION__),
- 'msg' => 'subject_empty'
- );
- return false;
+ 'msg' => 'subject_empty'
+ );
+ return false;
}
if (empty($mass_text)) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
'log' => array(__FUNCTION__),
- 'msg' => 'text_empty'
- );
- return false;
+ 'msg' => 'text_empty'
+ );
+ return false;
}
$domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
foreach ($domains as $domain) {
@@ -265,8 +489,14 @@
$task = substr(strtoupper(md5(uniqid(rand(), true))), 0, 6);
foreach ($_data['return'] as $return) {
$type = $return['type'];
- $msg = json_encode($return['msg'], JSON_UNESCAPED_UNICODE);
- $call = json_encode($return['log'], JSON_UNESCAPED_UNICODE);
+ $msg = null;
+ if (isset($return['msg'])) {
+ $msg = json_encode($return['msg'], JSON_UNESCAPED_UNICODE);
+ }
+ $call = null;
+ if (isset($return['log'])) {
+ $call = json_encode($return['log'], JSON_UNESCAPED_UNICODE);
+ }
if (!empty($_SESSION["dual-login"]["username"])) {
$user = $_SESSION["dual-login"]["username"] . ' => ' . $_SESSION['mailcow_cc_username'];
$role = $_SESSION["dual-login"]["role"] . ' => ' . $_SESSION['mailcow_cc_role'];
@@ -303,16 +533,16 @@
}
}
function hasDomainAccess($username, $role, $domain) {
- global $pdo;
- if (!filter_var($username, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
- return false;
- }
- if (empty($domain) || !is_valid_domain_name($domain)) {
- return false;
- }
- if ($role != 'admin' && $role != 'domainadmin') {
- return false;
- }
+ global $pdo;
+ if (!filter_var($username, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
+ return false;
+ }
+ if (empty($domain) || !is_valid_domain_name($domain)) {
+ return false;
+ }
+ if ($role != 'admin' && $role != 'domainadmin') {
+ return false;
+ }
if ($role == 'admin') {
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain` = :domain");
@@ -339,48 +569,49 @@
return true;
}
}
- return false;
+ return false;
}
function hasMailboxObjectAccess($username, $role, $object) {
- global $pdo;
- if (empty($username) || empty($role) || empty($object)) {
- return false;
- }
- if (!filter_var(html_entity_decode(rawurldecode($username)), FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
- return false;
- }
- if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') {
- return false;
- }
- if ($username == $object) {
- return true;
- }
+ global $pdo;
+ if (empty($username) || empty($role) || empty($object)) {
+ return false;
+ }
+ if (!filter_var(html_entity_decode(rawurldecode($username)), FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
+ return false;
+ }
+ if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') {
+ return false;
+ }
+ if ($username == $object) {
+ return true;
+ }
$stmt = $pdo->prepare("SELECT `domain` FROM `mailbox` WHERE `username` = :object");
$stmt->execute(array(':object' => $object));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (isset($row['domain']) && hasDomainAccess($username, $role, $row['domain'])) {
return true;
}
- return false;
+ return false;
}
+// does also verify mailboxes as a mailbox is a alias == goto
function hasAliasObjectAccess($username, $role, $object) {
- global $pdo;
- if (!filter_var(html_entity_decode(rawurldecode($username)), FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
- return false;
- }
- if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') {
- return false;
- }
- if ($username == $object) {
- return true;
- }
+ global $pdo;
+ if (empty($username) || empty($role) || empty($object)) {
+ return false;
+ }
+ if (!filter_var(html_entity_decode(rawurldecode($username)), FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
+ return false;
+ }
+ if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') {
+ return false;
+ }
$stmt = $pdo->prepare("SELECT `domain` FROM `alias` WHERE `address` = :object");
$stmt->execute(array(':object' => $object));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (isset($row['domain']) && hasDomainAccess($username, $role, $row['domain'])) {
return true;
}
- return false;
+ return false;
}
function pem_to_der($pem_key) {
// Need to remove BEGIN/END PUBLIC KEY
@@ -390,9 +621,9 @@
return base64_decode(implode('', $lines));
}
function expand_ipv6($ip) {
- $hex = unpack("H*hex", inet_pton($ip));
- $ip = substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hex['hex']), 0, -1);
- return $ip;
+ $hex = unpack("H*hex", inet_pton($ip));
+ $ip = substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hex['hex']), 0, -1);
+ return $ip;
}
function generate_tlsa_digest($hostname, $port, $starttls = null) {
if (!is_valid_domain_name($hostname)) {
@@ -453,15 +684,20 @@
return 'Error: Cannot read peer certificate';
}
}
-function alertbox_log_parser($_data){
+function alertbox_log_parser($_data) {
global $lang;
if (isset($_data['return'])) {
foreach ($_data['return'] as $return) {
// Get type
$type = $return['type'];
// If a lang[type][msg] string exists, use it as message
- if (is_string($lang[$return['type']][$return['msg']])) {
- $msg = $lang[$return['type']][$return['msg']];
+ if (isset($return['type']) && isset($return['msg']) && !is_array($return['msg'])) {
+ if (isset($lang[$return['type']][$return['msg']])) {
+ $msg = $lang[$return['type']][$return['msg']];
+ }
+ else {
+ $msg = $return['msg'];
+ }
}
// If msg is an array, use first element as language string and run printf on it with remaining array elements
elseif (is_array($return['msg'])) {
@@ -471,9 +707,8 @@
$return['msg']
);
}
- // If none applies, use msg as returned message
else {
- $msg = $return['msg'];
+ $msg = '-';
}
$log_array[] = array('msg' => $msg, 'type' => json_encode($type));
}
@@ -483,100 +718,116 @@
}
return false;
}
+function verify_salted_hash($hash, $password, $algo, $salt_length) {
+ // Decode hash
+ $dhash = base64_decode($hash);
+ // Get first n bytes of binary which equals a SSHA hash
+ $ohash = substr($dhash, 0, $salt_length);
+ // Remove SSHA hash from decoded hash to get original salt string
+ $osalt = str_replace($ohash, '', $dhash);
+ // Check single salted SSHA hash against extracted hash
+ if (hash_equals(hash($algo, $password . $osalt, true), $ohash)) {
+ return true;
+ }
+ return false;
+}
function verify_hash($hash, $password) {
- if (preg_match('/^{SSHA256}/i', $hash)) {
- // Remove tag if any
- $hash = preg_replace('/^{SSHA256}/i', '', $hash);
- // Decode hash
- $dhash = base64_decode($hash);
- // Get first 32 bytes of binary which equals a SHA256 hash
- $ohash = substr($dhash, 0, 32);
- // Remove SHA256 hash from decoded hash to get original salt string
- $osalt = str_replace($ohash, '', $dhash);
- // Check single salted SHA256 hash against extracted hash
- if (hash_equals(hash('sha256', $password . $osalt, true), $ohash)) {
- return true;
- }
- }
- elseif (preg_match('/^{SSHA}/i', $hash)) {
- // Remove tag if any
- $hash = preg_replace('/^{SSHA}/i', '', $hash);
- // Decode hash
- $dhash = base64_decode($hash);
- // Get first 20 bytes of binary which equals a SSHA hash
- $ohash = substr($dhash, 0, 20);
- // Remove SSHA hash from decoded hash to get original salt string
- $osalt = str_replace($ohash, '', $dhash);
- // Check single salted SSHA hash against extracted hash
- if (hash_equals(hash('sha1', $password . $osalt, true), $ohash)) {
- return true;
- }
- }
- elseif (preg_match('/^{PLAIN-MD5}/i', $hash)) {
- $hash = preg_replace('/^{PLAIN-MD5}/i', '', $hash);
- if (md5($password) == $hash) {
- return true;
- }
- }
- elseif (preg_match('/^{SHA512-CRYPT}/i', $hash)) {
- // Remove tag if any
- $hash = preg_replace('/^{SHA512-CRYPT}/i', '', $hash);
- // Decode hash
- preg_match('/\\$6\\$(.*)\\$(.*)/i', $hash, $hash_array);
- $osalt = $hash_array[1];
- $ohash = $hash_array[2];
- if (hash_equals(crypt($password, '$6$' . $osalt . '$'), $hash)) {
- return true;
- }
- }
- elseif (preg_match('/^{SSHA512}/i', $hash)) {
- $hash = preg_replace('/^{SSHA512}/i', '', $hash);
- // Decode hash
- $dhash = base64_decode($hash);
- // Get first 64 bytes of binary which equals a SHA512 hash
- $ohash = substr($dhash, 0, 64);
- // Remove SHA512 hash from decoded hash to get original salt string
- $osalt = str_replace($ohash, '', $dhash);
- // Check single salted SHA512 hash against extracted hash
- if (hash_equals(hash('sha512', $password . $osalt, true), $ohash)) {
- return true;
- }
- }
- elseif (preg_match('/^{MD5-CRYPT}/i', $hash)) {
- $hash = preg_replace('/^{MD5-CRYPT}/i', '', $hash);
- if (password_verify($password, $hash)) {
- return true;
- }
- }
- elseif (preg_match('/^{BLF-CRYPT}/i', $hash)) {
- $hash = preg_replace('/^{BLF-CRYPT}/i', '', $hash);
- if (password_verify($password, $hash)) {
- return true;
+ if (preg_match('/^{(.+)}(.+)/i', $hash, $hash_array)) {
+ $scheme = strtoupper($hash_array[1]);
+ $hash = $hash_array[2];
+ switch ($scheme) {
+ case "ARGON2I":
+ case "ARGON2ID":
+ case "BLF-CRYPT":
+ case "CRYPT":
+ case "DES-CRYPT":
+ case "MD5-CRYPT":
+ case "MD5":
+ case "SHA256-CRYPT":
+ case "SHA512-CRYPT":
+ return password_verify($password, $hash);
+
+ case "CLEAR":
+ case "CLEARTEXT":
+ case "PLAIN":
+ return $password == $hash;
+
+ case "LDAP-MD5":
+ $hash = base64_decode($hash);
+ return hash_equals(hash('md5', $password, true), $hash);
+
+ case "PBKDF2":
+ $components = explode('$', $hash);
+ $salt = $components[2];
+ $rounds = $components[3];
+ $hash = $components[4];
+ return hash_equals(hash_pbkdf2('sha1', $password, $salt, $rounds), $hash);
+
+ case "PLAIN-MD4":
+ return hash_equals(hash('md4', $password), $hash);
+
+ case "PLAIN-MD5":
+ return md5($password) == $hash;
+
+ case "PLAIN-TRUNC":
+ $components = explode('-', $hash);
+ if (count($components) > 1) {
+ $trunc_len = $components[0];
+ $trunc_password = $components[1];
+
+ return substr($password, 0, $trunc_len) == $trunc_password;
+ } else {
+ return $password == $hash;
+ }
+
+ case "SHA":
+ case "SHA1":
+ case "SHA256":
+ case "SHA512":
+ // SHA is an alias for SHA1
+ $scheme = $scheme == "SHA" ? "sha1" : strtolower($scheme);
+ $hash = base64_decode($hash);
+ return hash_equals(hash($scheme, $password, true), $hash);
+
+ case "SMD5":
+ return verify_salted_hash($hash, $password, 'md5', 16);
+
+ case "SSHA":
+ return verify_salted_hash($hash, $password, 'sha1', 20);
+
+ case "SSHA256":
+ return verify_salted_hash($hash, $password, 'sha256', 32);
+
+ case "SSHA512":
+ return verify_salted_hash($hash, $password, 'sha512', 64);
+
+ default:
+ return false;
}
}
return false;
}
function check_login($user, $pass) {
- global $pdo;
- global $redis;
- global $imap_server;
- if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
+ 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',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
- return false;
- }
- $user = strtolower(trim($user));
- $stmt = $pdo->prepare("SELECT `password` FROM `admin`
- WHERE `superadmin` = '1'
- AND `active` = '1'
- AND `username` = :user");
- $stmt->execute(array(':user' => $user));
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- foreach ($rows as $row) {
- if (verify_hash($row['password'], $pass)) {
+ return false;
+ }
+ $user = strtolower(trim($user));
+ $stmt = $pdo->prepare("SELECT `password` FROM `admin`
+ WHERE `superadmin` = '1'
+ AND `active` = '1'
+ AND `username` = :user");
+ $stmt->execute(array(':user' => $user));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ foreach ($rows as $row) {
+ if (verify_hash($row['password'], $pass)) {
if (get_tfa($user)['name'] != "none") {
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "admin";
@@ -601,16 +852,16 @@
);
return "admin";
}
- }
- }
- $stmt = $pdo->prepare("SELECT `password` FROM `admin`
- WHERE `superadmin` = '0'
- AND `active`='1'
- AND `username` = :user");
- $stmt->execute(array(':user' => $user));
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- foreach ($rows as $row) {
- if (verify_hash($row['password'], $pass) !== false) {
+ }
+ }
+ $stmt = $pdo->prepare("SELECT `password` FROM `admin`
+ WHERE `superadmin` = '0'
+ AND `active`='1'
+ AND `username` = :user");
+ $stmt->execute(array(':user' => $user));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ foreach ($rows as $row) {
+ if (verify_hash($row['password'], $pass) !== false) {
if (get_tfa($user)['name'] != "none") {
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "domainadmin";
@@ -635,55 +886,55 @@
);
return "domainadmin";
}
- }
- }
- $stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
+ }
+ }
+ $stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
- WHERE `kind` NOT REGEXP 'location|thing|group'
+ WHERE `kind` NOT REGEXP 'location|thing|group'
AND `mailbox`.`active`='1'
AND `domain`.`active`='1'
AND `username` = :user");
- $stmt->execute(array(':user' => $user));
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- foreach ($rows as $row) {
- if (verify_hash($row['password'], $pass) !== false) {
- unset($_SESSION['ldelay']);
+ $stmt->execute(array(':user' => $user));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ foreach ($rows as $row) {
+ if (verify_hash($row['password'], $pass) !== false) {
+ unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
- return "user";
- }
- }
- if (!isset($_SESSION['ldelay'])) {
- $_SESSION['ldelay'] = "0";
+ return "user";
+ }
+ }
+ if (!isset($_SESSION['ldelay'])) {
+ $_SESSION['ldelay'] = "0";
$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']);
- }
- elseif (!isset($_SESSION['mailcow_cc_username'])) {
- $_SESSION['ldelay'] = $_SESSION['ldelay']+0.5;
+ }
+ elseif (!isset($_SESSION['mailcow_cc_username'])) {
+ $_SESSION['ldelay'] = $_SESSION['ldelay']+0.5;
$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']);
- }
+ 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']);
+ sleep($_SESSION['ldelay']);
return false;
}
function formatBytes($size, $precision = 2) {
- if(!is_numeric($size)) {
- return "0";
- }
- $base = log($size, 1024);
- $suffixes = array(' Byte', ' KiB', ' MiB', ' GiB', ' TiB');
- if ($size == "0") {
- return "0";
- }
- return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
+ if(!is_numeric($size)) {
+ return "0";
+ }
+ $base = log($size, 1024);
+ $suffixes = array(' Byte', ' KiB', ' MiB', ' GiB', ' TiB');
+ if ($size == "0") {
+ return "0";
+ }
+ return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
}
function update_sogo_static_view() {
if (getenv('SKIP_SOGO') == "y") {
@@ -702,15 +953,15 @@
flush_memcached();
}
function edit_user_account($_data) {
- global $lang;
- global $pdo;
+ 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'];
+ $password_old = $_data['user_old_pass'];
if (filter_var($username, FILTER_VALIDATE_EMAIL === false) || $role != 'user') {
$_SESSION['return'][] = array(
'type' => 'danger',
@@ -719,15 +970,11 @@
);
return false;
}
- if (isset($_data['user_new_pass']) && isset($_data['user_new_pass2'])) {
- $password_new = $_data['user_new_pass'];
- $password_new2 = $_data['user_new_pass2'];
- }
- $stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
- WHERE `kind` NOT REGEXP 'location|thing|group'
+ $stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
+ WHERE `kind` NOT REGEXP 'location|thing|group'
AND `username` = :user");
- $stmt->execute(array(':user' => $username));
- $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ $stmt->execute(array(':user' => $username));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!verify_hash($row['password'], $password_old)) {
$_SESSION['return'][] = array(
'type' => 'danger',
@@ -736,59 +983,43 @@
);
return false;
}
- if (isset($password_new) && isset($password_new2)) {
- if (!empty($password_new2) && !empty($password_new)) {
- if ($password_new2 != $password_new) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $_data_log),
- 'msg' => 'password_mismatch'
- );
- return false;
- }
- if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password_new)) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $_data_log),
- 'msg' => 'password_complexity'
- );
- return false;
- }
- $password_hashed = hash_password($password_new);
- try {
- $stmt = $pdo->prepare("UPDATE `mailbox` SET `password` = :password_hashed, `attributes` = JSON_SET(`attributes`, '$.force_pw_update', '0') WHERE `username` = :username");
- $stmt->execute(array(
- ':password_hashed' => $password_hashed,
- ':username' => $username
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $_data_log),
- 'msg' => array('mysql_error', $e)
- );
- return false;
- }
- }
- }
+ if (!empty($_data['user_new_pass']) && !empty($_data['user_new_pass2'])) {
+ $password_new = $_data['user_new_pass'];
+ $password_new2 = $_data['user_new_pass2'];
+ if (password_check($password_new, $password_new2) !== true) {
+ return false;
+ }
+ $password_hashed = hash_password($password_new);
+ $stmt = $pdo->prepare("UPDATE `mailbox` SET `password` = :password_hashed,
+ `attributes` = JSON_SET(`attributes`, '$.force_pw_update', '0'),
+ `attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW())
+ WHERE `username` = :username");
+ $stmt->execute(array(
+ ':password_hashed' => $password_hashed,
+ ':username' => $username
+ ));
+ }
update_sogo_static_view();
- $_SESSION['return'][] = array(
- 'type' => 'success',
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
- 'msg' => array('mailbox_modified', htmlspecialchars($username))
- );
+ 'msg' => array('mailbox_modified', htmlspecialchars($username))
+ );
}
function user_get_alias_details($username) {
- global $pdo;
- $data['direct_aliases'] = false;
- $data['shared_aliases'] = false;
+ global $pdo;
+ global $lang;
+ $data['direct_aliases'] = array();
+ $data['shared_aliases'] = array();
if ($_SESSION['mailcow_cc_role'] == "user") {
- $username = $_SESSION['mailcow_cc_username'];
+ $username = $_SESSION['mailcow_cc_username'];
}
if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
return false;
}
+ if (!hasMailboxObjectAccess($username, $_SESSION['mailcow_cc_role'], $username)) {
+ return false;
+ }
$data['address'] = $username;
$stmt = $pdo->prepare("SELECT `address` AS `shared_aliases`, `public_comment` FROM `alias`
WHERE `goto` REGEXP :username_goto
@@ -803,7 +1034,6 @@
$run = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($run)) {
$data['shared_aliases'][$row['shared_aliases']]['public_comment'] = htmlspecialchars($row['public_comment']);
-
//$data['shared_aliases'][] = $row['shared_aliases'];
}
@@ -829,21 +1059,22 @@
if (empty($row['ad_alias'])) {
continue;
}
- $data['direct_aliases'][$row['ad_alias']]['public_comment'] = '↪ ' . $row['alias_domain'];
+ $data['direct_aliases'][$row['ad_alias']]['public_comment'] = $lang['add']['alias_domain'];
+ $data['alias_domains'][] = $row['alias_domain'];
}
- $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '✘') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%';");
+ $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%';");
$stmt->execute(array(':username' => $username));
$run = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($run)) {
$data['aliases_also_send_as'] = $row['send_as'];
}
- $stmt = $pdo->prepare("SELECT CONCAT_WS(', ', IFNULL(GROUP_CONCAT(DISTINCT `send_as` SEPARATOR ', '), '✘'), GROUP_CONCAT(DISTINCT CONCAT('@',`alias_domain`) SEPARATOR ', ')) AS `send_as` FROM `sender_acl` LEFT JOIN `alias_domain` ON `alias_domain`.`target_domain` = TRIM(LEADING '@' FROM `send_as`) WHERE `logged_in_as` = :username AND `send_as` LIKE '@%';");
+ $stmt = $pdo->prepare("SELECT CONCAT_WS(', ', IFNULL(GROUP_CONCAT(DISTINCT `send_as` SEPARATOR ', '), ''), GROUP_CONCAT(DISTINCT CONCAT('@',`alias_domain`) SEPARATOR ', ')) AS `send_as` FROM `sender_acl` LEFT JOIN `alias_domain` ON `alias_domain`.`target_domain` = TRIM(LEADING '@' FROM `send_as`) WHERE `logged_in_as` = :username AND `send_as` LIKE '@%';");
$stmt->execute(array(':username' => $username));
$run = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($run)) {
$data['aliases_send_as_all'] = $row['send_as'];
}
- $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '✘') as `address` FROM `alias` WHERE `goto` REGEXP :username AND `address` LIKE '@%';");
+ $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '') as `address` FROM `alias` WHERE `goto` REGEXP :username AND `address` LIKE '@%';");
$stmt->execute(array(':username' => '(^|,)'.$username.'($|,)'));
$run = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($run)) {
@@ -852,24 +1083,23 @@
return $data;
}
function is_valid_domain_name($domain_name) {
- if (empty($domain_name)) {
- return false;
- }
- $domain_name = idn_to_ascii($domain_name, 0, INTL_IDNA_VARIANT_UTS46);
- return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name)
- && preg_match("/^.{1,253}$/", $domain_name)
- && preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name));
+ if (empty($domain_name)) {
+ return false;
+ }
+ $domain_name = idn_to_ascii($domain_name, 0, INTL_IDNA_VARIANT_UTS46);
+ return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name)
+ && preg_match("/^.{1,253}$/", $domain_name)
+ && preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name));
}
function set_tfa($_data) {
- global $pdo;
- global $yubi;
- global $u2f;
- global $tfa;
+ global $pdo;
+ global $yubi;
+ global $u2f;
+ global $tfa;
$_data_log = $_data;
!isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*';
$username = $_SESSION['mailcow_cc_username'];
- if ($_SESSION['mailcow_cc_role'] != "domainadmin" &&
- $_SESSION['mailcow_cc_role'] != "admin") {
+ if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
@@ -878,20 +1108,37 @@
return false;
}
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
- WHERE `username` = :user");
- $stmt->execute(array(':user' => $username));
+ WHERE `username` = :username");
+ $stmt->execute(array(':username' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
- if (!verify_hash($row['password'], $_data["confirm_password"])) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $_data_log),
- 'msg' => 'access_denied'
- );
- return false;
+ $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
+ if (!empty($num_results)) {
+ if (!verify_hash($row['password'], $_data["confirm_password"])) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_data_log),
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
}
-
- switch ($_data["tfa_method"]) {
- case "yubi_otp":
+ $stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
+ WHERE `username` = :username");
+ $stmt->execute(array(':username' => $username));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
+ if (!empty($num_results)) {
+ if (!verify_hash($row['password'], $_data["confirm_password"])) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_data_log),
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
+ }
+ switch ($_data["tfa_method"]) {
+ case "yubi_otp":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
$yubico_id = $_data['yubico_id'];
$yubico_key = $_data['yubico_key'];
@@ -904,55 +1151,55 @@
);
return false;
}
- if (!ctype_alnum($_data["otp_token"]) || strlen($_data["otp_token"]) != 44) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
+ if (!ctype_alnum($_data["otp_token"]) || strlen($_data["otp_token"]) != 44) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
- 'msg' => 'tfa_token_invalid'
- );
- return false;
- }
+ 'msg' => 'tfa_token_invalid'
+ );
+ return false;
+ }
$yauth = $yubi->verify($_data["otp_token"]);
if (PEAR::isError($yauth)) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('yotp_verification_failed', $yauth->getMessage())
- );
- return false;
+ );
+ return false;
}
- try {
+ try {
// We could also do a modhex translation here
$yubico_modhex_id = substr($_data["otp_token"], 0, 12);
$stmt = $pdo->prepare("DELETE FROM `tfa`
WHERE `username` = :username
AND (`authmech` != 'yubi_otp')
OR (`authmech` = 'yubi_otp' AND `secret` LIKE :modhex)");
- $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id));
- $stmt = $pdo->prepare("INSERT INTO `tfa` (`key_id`, `username`, `authmech`, `active`, `secret`) VALUES
- (:key_id, :username, 'yubi_otp', '1', :secret)");
- $stmt->execute(array(':key_id' => $key_id, ':username' => $username, ':secret' => $yubico_id . ':' . $yubico_key . ':' . $yubico_modhex_id));
- }
- catch (PDOException $e) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
+ $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id));
+ $stmt = $pdo->prepare("INSERT INTO `tfa` (`key_id`, `username`, `authmech`, `active`, `secret`) VALUES
+ (:key_id, :username, 'yubi_otp', '1', :secret)");
+ $stmt->execute(array(':key_id' => $key_id, ':username' => $username, ':secret' => $yubico_id . ':' . $yubico_key . ':' . $yubico_modhex_id));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
- 'msg' => array('mysql_error', $e)
- );
- return false;
- }
- $_SESSION['return'][] = array(
- 'type' => 'success',
+ 'msg' => array('mysql_error', $e)
+ );
+ return false;
+ }
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
- 'msg' => array('object_modified', htmlspecialchars($username))
- );
- break;
- case "u2f":
+ 'msg' => array('object_modified', htmlspecialchars($username))
+ );
+ break;
+ case "u2f":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
try {
$reg = $u2f->doRegister(json_decode($_SESSION['regReq']), json_decode($_data['token']));
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `authmech` != 'u2f'");
- $stmt->execute(array(':username' => $username));
+ $stmt->execute(array(':username' => $username));
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`) VALUES (?, ?, 'u2f', ?, ?, ?, ?, '1')");
$stmt->execute(array($username, $key_id, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter));
$_SESSION['return'][] = array(
@@ -971,8 +1218,8 @@
$_SESSION['regReq'] = null;
return false;
}
- break;
- case "totp":
+ break;
+ 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");
@@ -992,28 +1239,27 @@
'msg' => 'totp_verification_failed'
);
}
- break;
- case "none":
+ break;
+ case "none":
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
- $_SESSION['return'][] = array(
- 'type' => 'success',
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
- 'msg' => array('object_modified', htmlspecialchars($username))
- );
- break;
- }
+ 'msg' => array('object_modified', htmlspecialchars($username))
+ );
+ break;
+ }
}
function fido2($_data) {
- global $pdo;
+ global $pdo;
$_data_log = $_data;
// Not logging registration data, only actions
// Silent errors for "get" requests
- switch ($_data["action"]) {
- case "register":
+ switch ($_data["action"]) {
+ case "register":
$username = $_SESSION['mailcow_cc_username'];
- if ($_SESSION['mailcow_cc_role'] != "domainadmin" &&
- $_SESSION['mailcow_cc_role'] != "admin") {
+ if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data["action"]),
@@ -1040,13 +1286,12 @@
'log' => array(__FUNCTION__, $_data["action"]),
'msg' => array('object_modified', $username)
);
- break;
- case "get_user_cids":
+ break;
+ case "get_user_cids":
// Used to exclude existing CredentialIds while registering
$username = $_SESSION['mailcow_cc_username'];
- if ($_SESSION['mailcow_cc_role'] != "domainadmin" &&
- $_SESSION['mailcow_cc_role'] != "admin") {
- return false;
+ if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) {
+ return false;
}
$stmt = $pdo->prepare("SELECT `credentialId` FROM `fido2` WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
@@ -1055,8 +1300,8 @@
$cids[] = $row['credentialId'];
}
return $cids;
- break;
- case "get_all_cids":
+ break;
+ case "get_all_cids":
// Only needed when using fido2 with username
$stmt = $pdo->query("SELECT `credentialId` FROM `fido2`");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
@@ -1064,7 +1309,7 @@
$cids[] = $row['credentialId'];
}
return $cids;
- break;
+ break;
case "get_by_b64cid":
if (!isset($_data['cid']) || empty($_data['cid'])) {
return false;
@@ -1080,12 +1325,11 @@
$data['subject'] = $row['certificateSubject'];
$data['cid'] = $row['cid'];
return $data;
- break;
- case "get_friendly_names":
+ break;
+ case "get_friendly_names":
$username = $_SESSION['mailcow_cc_username'];
- if ($_SESSION['mailcow_cc_role'] != "domainadmin" &&
- $_SESSION['mailcow_cc_role'] != "admin") {
- return false;
+ if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) {
+ return false;
}
$stmt = $pdo->prepare("SELECT SHA2(`credentialId`, 256) AS `cid`, `created`, `certificateSubject`, `friendlyName` FROM `fido2` WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
@@ -1098,39 +1342,37 @@
);
}
return $fns;
- break;
- case "unset_fido2_key":
+ break;
+ case "unset_fido2_key":
$username = $_SESSION['mailcow_cc_username'];
- if ($_SESSION['mailcow_cc_role'] != "domainadmin" &&
- $_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $_data["action"]),
- 'msg' => 'access_denied'
- );
- return false;
+ if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_data["action"]),
+ 'msg' => 'access_denied'
+ );
+ return false;
}
$stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username AND SHA2(`credentialId`, 256) = :cid");
$stmt->execute(array(
':username' => $username,
':cid' => $_data['post_data']['unset_fido2_key']
));
- $_SESSION['return'][] = array(
- 'type' => 'success',
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
- 'msg' => array('object_modified', htmlspecialchars($username))
- );
- break;
+ 'msg' => array('object_modified', htmlspecialchars($username))
+ );
+ break;
case "edit_fn":
$username = $_SESSION['mailcow_cc_username'];
- if ($_SESSION['mailcow_cc_role'] != "domainadmin" &&
- $_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $_data["action"]),
- 'msg' => 'access_denied'
- );
- return false;
+ if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_data["action"]),
+ 'msg' => 'access_denied'
+ );
+ return false;
}
$stmt = $pdo->prepare("UPDATE `fido2` SET `friendlyName` = :friendlyName WHERE SHA2(`credentialId`, 256) = :cid AND `username` = :username");
$stmt->execute(array(
@@ -1138,13 +1380,13 @@
':friendlyName' => $_data['fido2_attrs']['fido2_fn'],
':cid' => $_data['fido2_attrs']['fido2_cid']
));
- $_SESSION['return'][] = array(
- 'type' => 'success',
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
- 'msg' => array('object_modified', htmlspecialchars($username))
- );
- break;
- }
+ 'msg' => array('object_modified', htmlspecialchars($username))
+ );
+ break;
+ }
}
function unset_tfa_key($_data) {
// Can only unset own keys
@@ -1154,14 +1396,13 @@
$_data_log = $_data;
$id = intval($_data['unset_tfa_key']);
$username = $_SESSION['mailcow_cc_username'];
- if ($_SESSION['mailcow_cc_role'] != "domainadmin" &&
- $_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $_data_log),
- 'msg' => 'access_denied'
- );
- return false;
+ if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_data_log),
+ 'msg' => 'access_denied'
+ );
+ return false;
}
try {
if (!is_numeric($id)) {
@@ -1202,7 +1443,7 @@
}
}
function get_tfa($username = null) {
- global $pdo;
+ global $pdo;
if (isset($_SESSION['mailcow_cc_username'])) {
$username = $_SESSION['mailcow_cc_username'];
}
@@ -1214,76 +1455,83 @@
$stmt->execute(array(':username' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
- switch ($row["authmech"]) {
- case "yubi_otp":
- $data['name'] = "yubi_otp";
- $data['pretty'] = "Yubico OTP";
- $stmt = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username");
- $stmt->execute(array(
- ':username' => $username,
- ));
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- while($row = array_shift($rows)) {
- $data['additional'][] = $row;
- }
- return $data;
- break;
- case "u2f":
- $data['name'] = "u2f";
- $data['pretty'] = "Fido U2F";
- $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username");
- $stmt->execute(array(
- ':username' => $username,
- ));
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- while($row = array_shift($rows)) {
- $data['additional'][] = $row;
- }
- return $data;
- break;
- case "hotp":
- $data['name'] = "hotp";
- $data['pretty'] = "HMAC-based OTP";
- return $data;
- break;
- case "totp":
- $data['name'] = "totp";
- $data['pretty'] = "Time-based OTP";
- $stmt = $pdo->prepare("SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' AND `username` = :username");
- $stmt->execute(array(
- ':username' => $username,
- ));
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- while($row = array_shift($rows)) {
- $data['additional'][] = $row;
- }
- return $data;
+ if (isset($row["authmech"])) {
+ switch ($row["authmech"]) {
+ case "yubi_otp":
+ $data['name'] = "yubi_otp";
+ $data['pretty'] = "Yubico OTP";
+ $stmt = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username");
+ $stmt->execute(array(
+ ':username' => $username,
+ ));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ while($row = array_shift($rows)) {
+ $data['additional'][] = $row;
+ }
+ return $data;
break;
- default:
- $data['name'] = 'none';
- $data['pretty'] = "-";
- return $data;
- break;
- }
+ case "u2f":
+ $data['name'] = "u2f";
+ $data['pretty'] = "Fido U2F";
+ $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username");
+ $stmt->execute(array(
+ ':username' => $username,
+ ));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ while($row = array_shift($rows)) {
+ $data['additional'][] = $row;
+ }
+ return $data;
+ break;
+ case "hotp":
+ $data['name'] = "hotp";
+ $data['pretty'] = "HMAC-based OTP";
+ return $data;
+ break;
+ case "totp":
+ $data['name'] = "totp";
+ $data['pretty'] = "Time-based OTP";
+ $stmt = $pdo->prepare("SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' AND `username` = :username");
+ $stmt->execute(array(
+ ':username' => $username,
+ ));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ while($row = array_shift($rows)) {
+ $data['additional'][] = $row;
+ }
+ return $data;
+ break;
+ default:
+ $data['name'] = 'none';
+ $data['pretty'] = "-";
+ return $data;
+ break;
+ }
+ }
+ else {
+ $data['name'] = 'none';
+ $data['pretty'] = "-";
+ return $data;
+ }
}
function verify_tfa_login($username, $token) {
- global $pdo;
- global $yubi;
- global $u2f;
- global $tfa;
+ global $pdo;
+ global $yubi;
+ global $u2f;
+ global $tfa;
$stmt = $pdo->prepare("SELECT `authmech` FROM `tfa`
WHERE `username` = :username AND `active` = '1'");
$stmt->execute(array(':username' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
- switch ($row["authmech"]) {
- case "yubi_otp":
- if (!ctype_alnum($token) || strlen($token) != 44) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
+ switch ($row["authmech"]) {
+ case "yubi_otp":
+ if (!ctype_alnum($token) || strlen($token) != 44) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
- 'msg' => array('yotp_verification_failed', 'token length error')
- );
+ 'msg' => array('yotp_verification_failed', 'token length error')
+ );
return false;
}
$yubico_modhex_id = substr($token, 0, 12);
@@ -1298,20 +1546,20 @@
$yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]);
$yauth = $yubi->verify($token);
if (PEAR::isError($yauth)) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
- 'msg' => array('yotp_verification_failed', $yauth->getMessage())
- );
- return false;
+ 'msg' => array('yotp_verification_failed', $yauth->getMessage())
+ );
+ return false;
}
else {
$_SESSION['tfa_id'] = $row['id'];
- $_SESSION['return'][] = array(
- 'type' => 'success',
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
'log' => array(__FUNCTION__, $username, '*'),
- 'msg' => 'verified_yotp_login'
- );
+ 'msg' => 'verified_yotp_login'
+ );
return true;
}
$_SESSION['return'][] = array(
@@ -1396,31 +1644,34 @@
);
return false;
break;
- }
+ }
return false;
}
function admin_api($access, $action, $data = null) {
- global $pdo;
- if ($_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
+ global $pdo;
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
'log' => array(__FUNCTION__),
- 'msg' => 'access_denied'
- );
- return false;
- }
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
if ($access !== "ro" && $access !== "rw") {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
'log' => array(__FUNCTION__),
- 'msg' => 'invalid access type'
- );
- return false;
+ 'msg' => 'invalid access type'
+ );
+ return false;
}
if ($action == "edit") {
$active = (!empty($data['active'])) ? 1 : 0;
$skip_ip_check = (isset($data['skip_ip_check'])) ? 1 : 0;
- $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $data['allow_from']));
+ $allow_from = array();
+ if (isset($data['allow_from'])) {
+ $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $data['allow_from']));
+ }
foreach ($allow_from as $key => $val) {
if (empty($val)) {
unset($allow_from[$key]);
@@ -1507,29 +1758,31 @@
elseif ($action == "get") {
$stmt = $pdo->query("SELECT * FROM `api` WHERE `access` = '" . $access . "'");
$apidata = $stmt->fetch(PDO::FETCH_ASSOC);
- $apidata['allow_from'] = str_replace(',', PHP_EOL, $apidata['allow_from']);
+ if ($apidata !== false) {
+ $apidata['allow_from'] = str_replace(',', PHP_EOL, $apidata['allow_from']);
+ }
return $apidata;
}
- $_SESSION['return'][] = array(
- 'type' => 'success',
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
'log' => array(__FUNCTION__, $data),
- 'msg' => 'admin_api_modified'
- );
+ 'msg' => 'admin_api_modified'
+ );
}
function license($action, $data = null) {
- global $pdo;
- global $redis;
- global $lang;
- if ($_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
+ global $pdo;
+ global $redis;
+ global $lang;
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
'log' => array(__FUNCTION__),
- 'msg' => 'access_denied'
- );
- return false;
- }
- switch ($action) {
- case "verify":
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
+ switch ($action) {
+ case "verify":
// Keep result until revalidate button is pressed or session expired
$stmt = $pdo->query("SELECT `version` FROM `versions` WHERE `application` = 'GUID'");
$versions = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -1588,16 +1841,16 @@
}
}
function rspamd_ui($action, $data = null) {
- if ($_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
'log' => array(__FUNCTION__),
- 'msg' => 'access_denied'
- );
- return false;
- }
- switch ($action) {
- case "edit":
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
+ switch ($action) {
+ case "edit":
$rspamd_ui_pass = $data['rspamd_ui_pass'];
$rspamd_ui_pass2 = $data['rspamd_ui_pass2'];
if (empty($rspamd_ui_pass) || empty($rspamd_ui_pass2)) {
@@ -1673,11 +1926,11 @@
$to = intval($to);
if ($from < 1 || $to < $from) { return false; }
}
- global $redis;
- global $pdo;
- if ($_SESSION['mailcow_cc_role'] != "admin") {
- return false;
- }
+ global $redis;
+ global $pdo;
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ return false;
+ }
// SQL
if ($application == "mailcow-ui") {
if (isset($from) && isset($to)) {
@@ -1699,6 +1952,26 @@
return $data;
}
}
+ if ($application == "sasl") {
+ if (isset($from) && isset($to)) {
+ $stmt = $pdo->prepare("SELECT * FROM `sasl_log` ORDER BY `datetime` 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_log` ORDER BY `datetime` 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)) {