From ea1a02bd7d0bbc84291cf3a647fa13bc5b599522 Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Sun, 26 Dec 2021 17:11:06 +0100 Subject: [PATCH 001/115] Fix "The operation is insecure." when trying to register fido2 device. navigator.credentials.create(); Doesn't accept a port in the "id" parameter. So, when trying to register a fido2 device via WebAuthn throws: "The operation is insecure." on firefox and "The relying party ID is not a registrable domain suffix of, nor equal to the current domain." on Chrome or Edge. This commit replaces `$_SERVER['HTTP_HOST']` with `$_SERVER['SERVER_NAME']` when initializing `$WebAuthn` which excludes the port to formulate correct requests. Now Mailcow allows the registration of fido2 devices when running in a non-standard port(eg. 443). --- data/web/inc/prerequisites.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index a5eb2c80..95a9c63b 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -60,7 +60,7 @@ $tfa = new RobThree\Auth\TwoFactorAuth($OTP_LABEL, 6, 30, 'sha1', $qrprovider); // FIDO2 $formats = $GLOBALS['FIDO2_FORMATS']; -$WebAuthn = new \WebAuthn\WebAuthn('WebAuthn Library', $_SERVER['HTTP_HOST'], $formats); +$WebAuthn = new \WebAuthn\WebAuthn('WebAuthn Library', $_SERVER['SERVER_NAME'], $formats); $WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/solo.pem'); $WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/apple.pem'); $WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/nitro.pem'); From ea1a412749ed6a1cb9da75f33be93e4f5f4d3ead Mon Sep 17 00:00:00 2001 From: El-Virus <36414402+El-Virus@users.noreply.github.com> Date: Fri, 21 Jan 2022 15:46:44 +0100 Subject: [PATCH 002/115] Fix missing "lbuchs", after resolving last conflict It seems that when solving the conflict in my pr when the latest staging branch was merged to master, I accidentally deleted "lbuchs", I added it back --- data/web/inc/prerequisites.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index ac86016a..8e8f5e8f 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -61,7 +61,7 @@ $tfa = new RobThree\Auth\TwoFactorAuth($OTP_LABEL, 6, 30, 'sha1', $qrprovider); // FIDO2 $formats = $GLOBALS['FIDO2_FORMATS']; -$WebAuthn = new \WebAuthn\WebAuthn('WebAuthn Library', $_SERVER['SERVER_NAME'], $formats); +$WebAuthn = new lbuchs\WebAuthn\WebAuthn('WebAuthn Library', $_SERVER['SERVER_NAME'], $formats); // only include root ca's when needed if (getenv('WEBAUTHN_ONLY_TRUSTED_VENDORS') == 'y') $WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates'); From f09a3df870df0e2b66732e720e719d54559df209 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 21 Feb 2022 10:46:24 +0100 Subject: [PATCH 003/115] [Web] add verify selected tfa --- data/web/css/build/008-mailcow.css | 10 + data/web/inc/ajax/destroy_tfa_auth.php | 2 +- data/web/inc/footer.inc.php | 24 +- data/web/inc/functions.inc.php | 328 ++++++++++++++----------- data/web/inc/triggers.inc.php | 35 +-- data/web/json_api.php | 10 +- data/web/templates/base.twig | 100 ++++++-- data/web/templates/modals/footer.twig | 216 +++++++++++----- 8 files changed, 473 insertions(+), 252 deletions(-) diff --git a/data/web/css/build/008-mailcow.css b/data/web/css/build/008-mailcow.css index d7533424..5a965537 100644 --- a/data/web/css/build/008-mailcow.css +++ b/data/web/css/build/008-mailcow.css @@ -255,3 +255,13 @@ code { .flag-icon { margin-right: 5px; } +.list-group-item.webauthn-authenticator-selection, +.list-group-item.totp-authenticator-selection, +.list-group-item.yubi_otp-authenticator-selection { + border-radius: 0px !important; +} +.pending-tfa-collapse { + padding: 10px; + background: #fbfbfb; + border: 1px solid #ededed; +} diff --git a/data/web/inc/ajax/destroy_tfa_auth.php b/data/web/inc/ajax/destroy_tfa_auth.php index 72c7f1e3..07873b55 100644 --- a/data/web/inc/ajax/destroy_tfa_auth.php +++ b/data/web/inc/ajax/destroy_tfa_auth.php @@ -2,5 +2,5 @@ session_start(); unset($_SESSION['pending_mailcow_cc_username']); unset($_SESSION['pending_mailcow_cc_role']); -unset($_SESSION['pending_tfa_method']); +unset($_SESSION['pending_tfa_methods']); ?> diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php index 72482707..b2f1d4d5 100644 --- a/data/web/inc/footer.inc.php +++ b/data/web/inc/footer.inc.php @@ -23,6 +23,27 @@ if (is_array($alertbox_log_parser)) { unset($_SESSION['return']); } +// map tfa details for twig +$pending_tfa_authmechs = []; +foreach($_SESSION['pending_tfa_methods'] as $authdata){ + $pending_tfa_authmechs[$authdata['authmech']] = false; +} +if (isset($pending_tfa_authmechs['webauthn'])) { + $pending_tfa_authmechs['webauthn'] = true; +} +if (!isset($pending_tfa_authmechs['webauthn']) + && isset($pending_tfa_authmechs['yubi_otp'])) { + $pending_tfa_authmechs['yubi_otp'] = true; +} +if (!isset($pending_tfa_authmechs['webauthn']) + && !isset($pending_tfa_authmechs['yubi_otp']) + && isset($pending_tfa_authmechs['totp'])) { + $pending_tfa_authmechs['totp'] = true; +} +if (isset($pending_tfa_authmechs['u2f'])) { + $pending_tfa_authmechs['u2f'] = true; +} + // globals $globalVariables = [ 'mailcow_info' => array( @@ -30,7 +51,8 @@ $globalVariables = [ 'git_project_url' => $GLOBALS['MAILCOW_GIT_URL'] ), 'js_path' => '/cache/'.basename($JSPath), - 'pending_tfa_method' => @$_SESSION['pending_tfa_method'], + 'pending_tfa_methods' => @$_SESSION['pending_tfa_methods'], + 'pending_tfa_authmechs' => $pending_tfa_authmechs, 'pending_mailcow_cc_username' => @$_SESSION['pending_mailcow_cc_username'], 'lang_footer' => json_encode($lang['footer']), 'lang_acl' => json_encode($lang['acl']), diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 21a0d8ce..0b485c6e 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -830,11 +830,15 @@ function check_login($user, $pass, $app_passwd_data = false) { $stmt->execute(array(':user' => $user)); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { + // verify password if (verify_hash($row['password'], $pass)) { - if (get_tfa($user)['name'] != "none") { + // check for tfa authenticators + $authenticators = get_tfa($user); + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { + // active tfa authenticators found, set pending user login $_SESSION['pending_mailcow_cc_username'] = $user; $_SESSION['pending_mailcow_cc_role'] = "admin"; - $_SESSION['pending_tfa_method'] = get_tfa($user)['name']; + $_SESSION['pending_tfa_methods'] = $authenticators['additional']; unset($_SESSION['ldelay']); $_SESSION['return'][] = array( 'type' => 'info', @@ -842,8 +846,7 @@ function check_login($user, $pass, $app_passwd_data = false) { 'msg' => 'awaiting_tfa_confirmation' ); return "pending"; - } - else { + } else { unset($_SESSION['ldelay']); // Reactivate TFA if it was set to "deactivate TFA for next login" $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); @@ -866,11 +869,14 @@ function check_login($user, $pass, $app_passwd_data = false) { $stmt->execute(array(':user' => $user)); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { + // verify password if (verify_hash($row['password'], $pass) !== false) { - if (get_tfa($user)['name'] != "none") { + // check for tfa authenticators + $authenticators = get_tfa($user); + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { $_SESSION['pending_mailcow_cc_username'] = $user; $_SESSION['pending_mailcow_cc_role'] = "domainadmin"; - $_SESSION['pending_tfa_method'] = get_tfa($user)['name']; + $_SESSION['pending_tfa_method'] = $authenticators['additional']; unset($_SESSION['ldelay']); $_SESSION['return'][] = array( 'type' => 'info', @@ -1142,47 +1148,46 @@ function set_tfa($_data) { global $yubi; global $tfa; $_data_log = $_data; + $access_denied = null; !isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*'; $username = $_SESSION['mailcow_cc_username']; - if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $stmt = $pdo->prepare("SELECT `password` FROM `admin` - 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; - } - } - $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; + + // check for empty user and role + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) $access_denied = true; + + // check admin confirm password + if ($access_denied === null) { + $stmt = $pdo->prepare("SELECT `password` FROM `admin` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row) { + if (!verify_hash($row['password'], $_data["confirm_password"])) $access_denied = true; + else $access_denied = false; } } + // check mailbox confirm password + if ($access_denied === null) { + $stmt = $pdo->prepare("SELECT `password` FROM `mailbox` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row) { + if (!verify_hash($row['password'], $_data["confirm_password"])) $access_denied = true; + else $access_denied = false; + } + } + + // set access_denied error + if ($access_denied){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } switch ($_data["tfa_method"]) { case "yubi_otp": @@ -1265,9 +1270,6 @@ function set_tfa($_data) { case "webauthn": $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"]; - $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `authmech` != 'webauthn'"); - $stmt->execute(array(':username' => $username)); - $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`) VALUES (?, ?, 'webauthn', ?, ?, ?, ?, '1')"); $stmt->execute(array( @@ -1439,25 +1441,27 @@ function unset_tfa_key($_data) { global $pdo; global $lang; $_data_log = $_data; + $access_denied = null; $id = intval($_data['unset_tfa_key']); $username = $_SESSION['mailcow_cc_username']; - if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } + + // check for empty user and role + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) $access_denied = true; + try { - if (!is_numeric($id)) { - $_SESSION['return'][] = array( + if (!is_numeric($id)) $access_denied = true; + + // set access_denied error + if ($access_denied){ + $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_data_log), 'msg' => 'access_denied' ); return false; - } + } + + // check if it's last key $stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa` WHERE `username` = :username AND `active` = '1'"); $stmt->execute(array(':username' => $username)); @@ -1470,6 +1474,8 @@ function unset_tfa_key($_data) { ); return false; } + + // delete key $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `id` = :id"); $stmt->execute(array(':username' => $username, ':id' => $id)); $_SESSION['return'][] = array( @@ -1487,7 +1493,7 @@ function unset_tfa_key($_data) { return false; } } -function get_tfa($username = null) { +function get_tfa($username = null, $key_id = null) { global $pdo; if (isset($_SESSION['mailcow_cc_username'])) { $username = $_SESSION['mailcow_cc_username']; @@ -1495,92 +1501,116 @@ function get_tfa($username = null) { elseif (empty($username)) { return false; } - $stmt = $pdo->prepare("SELECT * FROM `tfa` - WHERE `username` = :username AND `active` = '1'"); - $stmt->execute(array(':username' => $username)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - 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; + if (!isset($key_id)){ + // fetch all tfa methods - just get information about possible authenticators + $stmt = $pdo->prepare("SELECT `id`, `key_id`, `authmech` FROM `tfa` + WHERE `username` = :username AND `active` = '1'"); + $stmt->execute(array(':username' => $username)); + $results = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // no tfa methods found + if (count($results) == 0) { + $data['name'] = 'none'; + $data['pretty'] = "-"; + $data['additional'] = array(); + return $data; + } + + $data['additional'] = $results; + return $data; + } else { + // fetch specific authenticator details by key_id + $stmt = $pdo->prepare("SELECT * FROM `tfa` + WHERE `username` = :username AND `active` = '1'"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + 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; + // u2f - deprecated, should be removed + 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; + case "webauthn": + $data['name'] = "webauthn"; + $data['pretty'] = "WebAuthn"; + $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' 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; } - return $data; - break; - // u2f - deprecated, should be removed - 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; - case "webauthn": - $data['name'] = "webauthn"; - $data['pretty'] = "WebAuthn"; - $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' 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: + } + else { $data['name'] = 'none'; $data['pretty'] = "-"; return $data; - break; + } } - } - else { - $data['name'] = 'none'; - $data['pretty'] = "-"; - return $data; - } } -function verify_tfa_login($username, $_data, $WebAuthn) { - global $pdo; - global $yubi; - global $u2f; - global $tfa; +function verify_tfa_login($username, $_data) { + global $pdo; + global $yubi; + global $u2f; + global $tfa; + global $WebAuthn; + + if ($_data['tfa_method'] != 'u2f'){ $stmt = $pdo->prepare("SELECT `authmech` FROM `tfa` - WHERE `username` = :username AND `active` = '1'"); - $stmt->execute(array(':username' => $username)); + WHERE `username` = :username AND `key_id` = :key_id AND `active` = '1'"); + $stmt->execute(array(':username' => $username, ':key_id' => $_data['key_id'])); $row = $stmt->fetch(PDO::FETCH_ASSOC); switch ($row["authmech"]) { @@ -1597,9 +1627,10 @@ function verify_tfa_login($username, $_data, $WebAuthn) { $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` WHERE `username` = :username AND `authmech` = 'yubi_otp' + AND `key_id` = ':key_id' AND `active`='1' AND `secret` LIKE :modhex"); - $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id)); + $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id, ':key_id' => $_data['key_id'])); $row = $stmt->fetch(PDO::FETCH_ASSOC); $yubico_auth = explode(':', $row['secret']); $yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]); @@ -1636,8 +1667,9 @@ function verify_tfa_login($username, $_data, $WebAuthn) { $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` WHERE `username` = :username AND `authmech` = 'totp' + AND `key_id` = :key_id AND `active`='1'"); - $stmt->execute(array(':username' => $username)); + $stmt->execute(array(':username' => $username, ':key_id' => $_data['key_id'])); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { if ($tfa->verifyCode($row['secret'], $_data['token']) === true) { @@ -1666,13 +1698,6 @@ function verify_tfa_login($username, $_data, $WebAuthn) { return false; } break; - // u2f - deprecated, should be removed - case "u2f": - // delete old keys that used u2f - $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `authmech` = :authmech AND `username` = :username"); - $stmt->execute(array(':authmech' => 'u2f', ':username' => $username)); - - return true; case "webauthn": $tokenData = json_decode($_data['token']); $clientDataJSON = base64_decode($tokenData->clientDataJSON); @@ -1681,7 +1706,7 @@ function verify_tfa_login($username, $_data, $WebAuthn) { $id = base64_decode($tokenData->id); $challenge = $_SESSION['challenge']; - $stmt = $pdo->prepare("SELECT `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `keyHandle` = :tokenId"); + $stmt = $pdo->prepare("SELECT `id`, `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `keyHandle` = :tokenId"); $stmt->execute(array(':tokenId' => $tokenData->id)); $process_webauthn = $stmt->fetch(PDO::FETCH_ASSOC); @@ -1738,7 +1763,7 @@ function verify_tfa_login($username, $_data, $WebAuthn) { $_SESSION["mailcow_cc_username"] = $process_webauthn['username']; - $_SESSION['tfa_id'] = $process_webauthn['key_id']; + $_SESSION['tfa_id'] = $process_webauthn['id']; $_SESSION['authReq'] = null; unset($_SESSION["challenge"]); $_SESSION['return'][] = array( @@ -1759,6 +1784,17 @@ function verify_tfa_login($username, $_data, $WebAuthn) { } return false; + } else { + // delete old keys that used u2f + $stmt = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username"); + $stmt->execute(array(':username' => $username)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + if (count($rows) == 0) return false; + + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username"); + $stmt->execute(array(':username' => $username)); + return true; + } } function admin_api($access, $action, $data = null) { global $pdo; diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php index cb3a3771..1e2bdb42 100644 --- a/data/web/inc/triggers.inc.php +++ b/data/web/inc/triggers.inc.php @@ -1,24 +1,24 @@ tfa_method; $_data['key_id'] = $post->key_id; + $_data['confirm_password'] = $post->confirm_password; $_data['registration'] = $data; set_tfa($_data); @@ -450,14 +451,15 @@ if (isset($_GET['query'])) { $stmt = $pdo->prepare("SELECT `keyHandle` FROM `tfa` WHERE username = :username"); $stmt->execute(array(':username' => $_SESSION['pending_mailcow_cc_username'])); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $cids[] = base64_decode($row['keyHandle']); - } - if (count($cids) == 0) { + if (count($rows) == 0) { print(json_encode(array( 'type' => 'error', 'msg' => 'Cannot find matching credentialIds' ))); + exit; + } + while($row = array_shift($rows)) { + $cids[] = base64_decode($row['keyHandle']); } $getArgs = $WebAuthn->getGetArgs($cids, 30, true, true, true, true, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN']); diff --git a/data/web/templates/base.twig b/data/web/templates/base.twig index 08376e71..2691718b 100644 --- a/data/web/templates/base.twig +++ b/data/web/templates/base.twig @@ -176,15 +176,62 @@ function recursiveBase64StrToArrayBuffer(obj) { {% endfor %} // Confirm TFA modal - {% if pending_tfa_method %} + {% if pending_tfa_methods %} $('#ConfirmTFAModal').modal({ backdrop: 'static', keyboard: false }); + // validate Yubi OTP tfa + $("#pending_tfa_tab_yubi_otp").click(function(){ + $(".totp-authenticator-selection").removeClass("active"); + $(".webauthn-authenticator-selection").removeClass("active"); + + $("#collapseTotpTFA").collapse('hide'); + $("#collapseWebAuthnTFA").collapse('hide'); + }); + $(".yubi-authenticator-selection").click(function(){ + $(".yubi-authenticator-selection").removeClass("active"); + $(this).addClass("active"); + + var key_id = $(this).children('span').first().text(); + $("#yubi_selected_key_id").val(key_id); + + $("#collapseYubiTFA").collapse('show'); + }); + // validate Time based OTP tfa + $("#pending_tfa_tab_totp").click(function(){ + $(".yubi-authenticator-selection").removeClass("active"); + $(".webauthn-authenticator-selection").removeClass("active"); + + $("#collapseYubiTFA").collapse('hide'); + $("#collapseWebAuthnTFA").collapse('hide'); + }); + $(".totp-authenticator-selection").click(function(){ + $(".totp-authenticator-selection").removeClass("active"); + $(this).addClass("active"); + + var key_id = $(this).children('span').first().text(); + $("#totp_selected_key_id").val(key_id); + + $("#collapseTotpTFA").collapse('show'); + }); // validate WebAuthn tfa - $('#start_webauthn_confirmation').click(function(){ - $('#webauthn_status_auth').html('

' + lang_tfa.init_webauthn + '

'); + $("#pending_tfa_tab_webauthn").click(function(){ + $(".totp-authenticator-selection").removeClass("active"); + $(".yubi-authenticator-selection").removeClass("active"); + + $("#collapseTotpTFA").collapse('hide'); + $("#collapseYubiTFA").collapse('hide'); + }); + $(".webauthn-authenticator-selection").click(function(){ + $(".webauthn-authenticator-selection").removeClass("active"); + $(this).addClass("active"); + + var key_id = $(this).children('span').first().text(); + $("#webauthn_selected_key_id").val(key_id); + + $("#collapseWebAuthnTFA").collapse('show'); $(this).find('input[name=token]').focus(); if(document.getElementById("webauthn_auth_data") !== null) { @@ -198,30 +245,31 @@ function recursiveBase64StrToArrayBuffer(obj) { window.fetch("/api/v1/get/webauthn-tfa-get-args", {method:'GET',cache:'no-cache'}).then(response => { return response.json(); }).then(json => { - if (json.success === false) throw new Error(); + if (json.success === false) throw new Error(); + if (json.type === "error") throw new Error(json.msg); - recursiveBase64StrToArrayBuffer(json); - return json; + recursiveBase64StrToArrayBuffer(json); + return json; }).then(getCredentialArgs => { - // get credentials - return navigator.credentials.get(getCredentialArgs); + // get credentials + return navigator.credentials.get(getCredentialArgs); }).then(cred => { - return { - id: cred.rawId ? arrayBufferToBase64(cred.rawId) : null, - clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null, - authenticatorData: cred.response.authenticatorData ? arrayBufferToBase64(cred.response.authenticatorData) : null, - signature : cred.response.signature ? arrayBufferToBase64(cred.response.signature) : null - }; + return { + id: cred.rawId ? arrayBufferToBase64(cred.rawId) : null, + clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null, + authenticatorData: cred.response.authenticatorData ? arrayBufferToBase64(cred.response.authenticatorData) : null, + signature : cred.response.signature ? arrayBufferToBase64(cred.response.signature) : null + }; }).then(JSON.stringify).then(function(AuthenticatorAttestationResponse) { - // send request by submit - var form = document.getElementById('webauthn_auth_form'); - var auth = document.getElementById('webauthn_auth_data'); - auth.value = AuthenticatorAttestationResponse; - form.submit(); + // send request by submit + var form = document.getElementById('webauthn_auth_form'); + var auth = document.getElementById('webauthn_auth_data'); + auth.value = AuthenticatorAttestationResponse; + form.submit(); }).catch(function(err) { - var webauthn_return_code = document.getElementById('webauthn_return_code'); - webauthn_return_code.style.display = webauthn_return_code.style.display === 'none' ? '' : null; - webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry; + var webauthn_return_code = document.getElementById('webauthn_return_code'); + webauthn_return_code.style.display = webauthn_return_code.style.display === 'none' ? '' : null; + webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry; }); } }); @@ -237,7 +285,9 @@ function recursiveBase64StrToArrayBuffer(obj) { } }); }); - {% endif %} + {% endif %} + + // Validate FIDO2 $("#fido2-login").click(function(){ $('#fido2-alerts').html(); @@ -358,6 +408,7 @@ function recursiveBase64StrToArrayBuffer(obj) { $("#start_webauthn_register").click(() => { var key_id = document.getElementsByName('key_id')[1].value; + var confirm_password = document.getElementsByName('confirm_password')[1].value; // fetch WebAuthn create args window.fetch("/api/v1/get/webauthn-tfa-registration/{{ mailcow_cc_username|url_encode(true)|default('null') }}", {method:'GET',cache:'no-cache'}).then(response => { @@ -375,7 +426,8 @@ function recursiveBase64StrToArrayBuffer(obj) { clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null, attestationObject: cred.response.attestationObject ? arrayBufferToBase64(cred.response.attestationObject) : null, key_id: key_id, - tfa_method: "webauthn" + tfa_method: "webauthn", + confirm_password: confirm_password }; }).then(JSON.stringify).then(AuthenticatorAttestationResponse => { // send request diff --git a/data/web/templates/modals/footer.twig b/data/web/templates/modals/footer.twig index 690b9de0..6df4a10d 100644 --- a/data/web/templates/modals/footer.twig +++ b/data/web/templates/modals/footer.twig @@ -133,73 +133,171 @@ {% endif %} -{% if pending_tfa_method %} +{% if pending_tfa_methods %} From 4c6a2055c2997396c6b3bc35da5ed79b4c43f97d Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 21 Feb 2022 14:10:12 +0100 Subject: [PATCH 004/115] [Web] add verify selected tfa --- data/web/inc/functions.inc.php | 61 ++++++++++++++++----------- data/web/templates/base.twig | 12 +++--- data/web/templates/modals/footer.twig | 17 +++++--- 3 files changed, 52 insertions(+), 38 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 0b485c6e..f93e7eca 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -1493,7 +1493,7 @@ function unset_tfa_key($_data) { return false; } } -function get_tfa($username = null, $key_id = null) { +function get_tfa($username = null, $id = null) { global $pdo; if (isset($_SESSION['mailcow_cc_username'])) { $username = $_SESSION['mailcow_cc_username']; @@ -1502,7 +1502,7 @@ function get_tfa($username = null, $key_id = null) { return false; } - if (!isset($key_id)){ + if (!isset($id)){ // fetch all tfa methods - just get information about possible authenticators $stmt = $pdo->prepare("SELECT `id`, `key_id`, `authmech` FROM `tfa` WHERE `username` = :username AND `active` = '1'"); @@ -1520,10 +1520,10 @@ function get_tfa($username = null, $key_id = null) { $data['additional'] = $results; return $data; } else { - // fetch specific authenticator details by key_id + // fetch specific authenticator details by id $stmt = $pdo->prepare("SELECT * FROM `tfa` - WHERE `username` = :username AND `active` = '1'"); - $stmt->execute(array(':username' => $username)); + WHERE `username` = :username AND `id` = :id AND `active` = '1'"); + $stmt->execute(array(':username' => $username, ':id' => $id)); $row = $stmt->fetch(PDO::FETCH_ASSOC); if (isset($row["authmech"])) { @@ -1531,9 +1531,10 @@ function get_tfa($username = null, $key_id = null) { 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 = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username AND `id` = :id"); $stmt->execute(array( ':username' => $username, + ':id' => $id )); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { @@ -1545,9 +1546,10 @@ function get_tfa($username = null, $key_id = null) { 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 = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username AND `id` = :id"); $stmt->execute(array( ':username' => $username, + ':id' => $id )); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { @@ -1563,9 +1565,10 @@ function get_tfa($username = null, $key_id = null) { 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 = $pdo->prepare("SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' AND `username` = :username AND `id` = :id"); $stmt->execute(array( ':username' => $username, + ':id' => $id )); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { @@ -1576,9 +1579,10 @@ function get_tfa($username = null, $key_id = null) { case "webauthn": $data['name'] = "webauthn"; $data['pretty'] = "WebAuthn"; - $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' AND `username` = :username"); + $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' AND `username` = :username AND `id` = :id"); $stmt->execute(array( ':username' => $username, + ':id' => $id )); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { @@ -1609,8 +1613,8 @@ function verify_tfa_login($username, $_data) { if ($_data['tfa_method'] != 'u2f'){ $stmt = $pdo->prepare("SELECT `authmech` FROM `tfa` - WHERE `username` = :username AND `key_id` = :key_id AND `active` = '1'"); - $stmt->execute(array(':username' => $username, ':key_id' => $_data['key_id'])); + WHERE `username` = :username AND `id` = :id AND `active` = '1'"); + $stmt->execute(array(':username' => $username, ':id' => $_data['id'])); $row = $stmt->fetch(PDO::FETCH_ASSOC); switch ($row["authmech"]) { @@ -1627,10 +1631,10 @@ function verify_tfa_login($username, $_data) { $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` WHERE `username` = :username AND `authmech` = 'yubi_otp' - AND `key_id` = ':key_id' + AND `id` = ':id' AND `active`='1' AND `secret` LIKE :modhex"); - $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id, ':key_id' => $_data['key_id'])); + $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id, ':id' => $_data['id'])); $row = $stmt->fetch(PDO::FETCH_ASSOC); $yubico_auth = explode(':', $row['secret']); $yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]); @@ -1663,16 +1667,16 @@ function verify_tfa_login($username, $_data) { return false; break; case "totp": - try { + try { $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` WHERE `username` = :username AND `authmech` = 'totp' - AND `key_id` = :key_id + AND `id` = :id AND `active`='1'"); - $stmt->execute(array(':username' => $username, ':key_id' => $_data['key_id'])); + $stmt->execute(array(':username' => $username, ':id' => $_data['id'])); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { - if ($tfa->verifyCode($row['secret'], $_data['token']) === true) { + if ($tfa->verifyCode($row['secret'], $_data['token']) === true) { $_SESSION['tfa_id'] = $row['id']; $_SESSION['return'][] = array( 'type' => 'success', @@ -1680,7 +1684,7 @@ function verify_tfa_login($username, $_data) { 'msg' => 'verified_totp_login' ); return true; - } + } } $_SESSION['return'][] = array( 'type' => 'danger', @@ -1688,15 +1692,15 @@ function verify_tfa_login($username, $_data) { 'msg' => 'totp_verification_failed' ); return false; - } - catch (PDOException $e) { + } + catch (PDOException $e) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $username, '*'), 'msg' => array('mysql_error', $e) ); return false; - } + } break; case "webauthn": $tokenData = json_decode($_data['token']); @@ -1706,13 +1710,20 @@ function verify_tfa_login($username, $_data) { $id = base64_decode($tokenData->id); $challenge = $_SESSION['challenge']; - $stmt = $pdo->prepare("SELECT `id`, `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `keyHandle` = :tokenId"); - $stmt->execute(array(':tokenId' => $tokenData->id)); + $stmt = $pdo->prepare("SELECT `id`, `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data['id'])); $process_webauthn = $stmt->fetch(PDO::FETCH_ASSOC); - if (empty($process_webauthn) || empty($process_webauthn['publicKey']) || empty($process_webauthn['username'])) return false; + if (empty($process_webauthn)){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('webauthn_verification_failed', 'authenticator not found') + ); + return false; + } - if ($process_webauthn['publicKey'] === false) { + if (empty($process_webauthn['publicKey']) || $process_webauthn['publicKey'] === false) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $username, '*'), diff --git a/data/web/templates/base.twig b/data/web/templates/base.twig index 2691718b..b51dcde2 100644 --- a/data/web/templates/base.twig +++ b/data/web/templates/base.twig @@ -194,8 +194,8 @@ function recursiveBase64StrToArrayBuffer(obj) { $(".yubi-authenticator-selection").removeClass("active"); $(this).addClass("active"); - var key_id = $(this).children('span').first().text(); - $("#yubi_selected_key_id").val(key_id); + var id = $(this).children('input').first().val(); + $("#yubi_selected_id").val(id); $("#collapseYubiTFA").collapse('show'); }); @@ -211,8 +211,8 @@ function recursiveBase64StrToArrayBuffer(obj) { $(".totp-authenticator-selection").removeClass("active"); $(this).addClass("active"); - var key_id = $(this).children('span').first().text(); - $("#totp_selected_key_id").val(key_id); + var id = $(this).children('input').first().val(); + $("#totp_selected_id").val(id); $("#collapseTotpTFA").collapse('show'); }); @@ -228,8 +228,8 @@ function recursiveBase64StrToArrayBuffer(obj) { $(".webauthn-authenticator-selection").removeClass("active"); $(this).addClass("active"); - var key_id = $(this).children('span').first().text(); - $("#webauthn_selected_key_id").val(key_id); + var id = $(this).children('input').first().val(); + $("#webauthn_selected_id").val(id); $("#collapseWebAuthnTFA").collapse('show'); diff --git a/data/web/templates/modals/footer.twig b/data/web/templates/modals/footer.twig index 6df4a10d..67cc3482 100644 --- a/data/web/templates/modals/footer.twig +++ b/data/web/templates/modals/footer.twig @@ -139,7 +139,7 @@ @@ -205,7 +206,7 @@
- Available Authenticators + Authenticators
{% for authenticator in pending_tfa_methods %} @@ -213,6 +214,7 @@ {{ authenticator["key_id"] }} + {% endif %} {% endfor %} @@ -223,7 +225,7 @@ Yubicon Icon - +
@@ -240,7 +242,7 @@ - Available Authenticators + Authenticators
{% for authenticator in pending_tfa_methods %} @@ -248,6 +250,7 @@ {{ authenticator["key_id"] }} + {% endif %} {% endfor %} @@ -258,7 +261,7 @@ -
+
From df33f1a1303d893e16a3724434eeff93ee0b528e Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Tue, 22 Feb 2022 09:38:06 +0100 Subject: [PATCH 005/115] [Web] multiple tfa - domainadmin support --- data/web/inc/functions.inc.php | 2 +- data/web/templates/domainadmin.twig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index f93e7eca..384905d4 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -876,7 +876,7 @@ function check_login($user, $pass, $app_passwd_data = false) { if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { $_SESSION['pending_mailcow_cc_username'] = $user; $_SESSION['pending_mailcow_cc_role'] = "domainadmin"; - $_SESSION['pending_tfa_method'] = $authenticators['additional']; + $_SESSION['pending_tfa_methods'] = $authenticators['additional']; unset($_SESSION['ldelay']); $_SESSION['return'][] = array( 'type' => 'info', diff --git a/data/web/templates/domainadmin.twig b/data/web/templates/domainadmin.twig index 6aae54ba..3bc7ab40 100644 --- a/data/web/templates/domainadmin.twig +++ b/data/web/templates/domainadmin.twig @@ -28,7 +28,7 @@
From a2d57d43d1f61435b865d08ccd0225c06963d6ff Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 7 Mar 2022 11:41:13 +0100 Subject: [PATCH 006/115] [Web] multiple tfa - user support --- data/web/inc/functions.inc.php | 47 ++++++++++++++-------- data/web/inc/triggers.inc.php | 6 +-- data/web/templates/user/tab-user-auth.twig | 27 ++++++++++++- data/web/user.php | 1 + 4 files changed, 60 insertions(+), 21 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 384905d4..12efd60b 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -936,24 +936,39 @@ function check_login($user, $pass, $app_passwd_data = false) { $rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC)); } foreach ($rows as $row) { + // verify password 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) - ); - if ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) { - $service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV'; - $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)"); - $stmt->execute(array( - ':service' => $service, - ':app_id' => $row['app_passwd_id'], - ':username' => $user, - ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']) - )); + // check for tfa authenticators + $authenticators = get_tfa($user); + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { + $_SESSION['pending_mailcow_cc_username'] = $user; + $_SESSION['pending_mailcow_cc_role'] = "user"; + $_SESSION['pending_tfa_methods'] = $authenticators['additional']; + unset($_SESSION['ldelay']); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => array('logged_in_as', $user) + ); + return "pending"; + } else { + if ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) { + $service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV'; + $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)"); + $stmt->execute(array( + ':service' => $service, + ':app_id' => $row['app_passwd_id'], + ':username' => $user, + ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']) + )); + } + + unset($_SESSION['ldelay']); + // Reactivate TFA if it was set to "deactivate TFA for next login" + $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); + $stmt->execute(array(':user' => $user)); + return "user"; } - return "user"; } } diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php index 1e2bdb42..aec043e9 100644 --- a/data/web/inc/triggers.inc.php +++ b/data/web/inc/triggers.inc.php @@ -61,9 +61,9 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { header("Location: /user"); } elseif ($as != "pending") { - unset($_SESSION['pending_mailcow_cc_username']); - unset($_SESSION['pending_mailcow_cc_role']); - unset($_SESSION['pending_tfa_methods']); + unset($_SESSION['pending_mailcow_cc_username']); + unset($_SESSION['pending_mailcow_cc_role']); + unset($_SESSION['pending_tfa_methods']); unset($_SESSION['mailcow_cc_username']); unset($_SESSION['mailcow_cc_role']); } diff --git a/data/web/templates/user/tab-user-auth.twig b/data/web/templates/user/tab-user-auth.twig index c35eee39..cbf5f83d 100644 --- a/data/web/templates/user/tab-user-auth.twig +++ b/data/web/templates/user/tab-user-auth.twig @@ -15,6 +15,10 @@ {{ lang.user.open_webmail_sso }} {% endif %} +

@@ -40,8 +44,27 @@

{{ mailboxdata.quota_used|formatBytes(2) }} / {% if mailboxdata.quota == 0 %}∞{% else %}{{ mailboxdata.quota|formatBytes(2) }}{% endif %}
{{ mailboxdata.messages }} {{ lang.user.messages }}

-
-

{{ lang.user.change_password }}

+ + +
+ {# TFA #} +
+
{{ lang.tfa.tfa }}:
+
+

{{ tfa_data.pretty }}

+ {% include 'tfa_keys.twig' %} +
+
+
+
+
{{ lang.tfa.set_tfa }}:
+
+

diff --git a/data/web/user.php b/data/web/user.php index 5bf60917..d7faf791 100644 --- a/data/web/user.php +++ b/data/web/user.php @@ -76,6 +76,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' 'acl_json' => json_encode($_SESSION['acl']), 'user_spam_score' => mailbox('get', 'spam_score', $username), 'tfa_data' => $tfa_data, + 'tfa_id' => @$_SESSION['tfa_id'], 'fido2_data' => $fido2_data, 'mailboxdata' => $mailboxdata, 'clientconfigstr' => $clientconfigstr, From 84b4269c75d21deb4c7b03bbc14b528ed5f27232 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 14 Mar 2022 09:29:07 +0100 Subject: [PATCH 007/115] [Web] increase mysql publicKey field length --- data/web/inc/init_db.inc.php | 4 ++-- data/web/json_api.php | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 3cab461e..5705379d 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -3,7 +3,7 @@ function init_db_schema() { try { global $pdo; - $db_version = "18012022_1020"; + $db_version = "14032022_0921"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -699,7 +699,7 @@ function init_db_schema() { "authmech" => "ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')", "secret" => "VARCHAR(255) DEFAULT NULL", "keyHandle" => "VARCHAR(255) DEFAULT NULL", - "publicKey" => "VARCHAR(255) DEFAULT NULL", + "publicKey" => "VARCHAR(4096) DEFAULT NULL", "counter" => "INT NOT NULL DEFAULT '0'", "certificate" => "TEXT", "active" => "TINYINT(1) NOT NULL DEFAULT '0'" diff --git a/data/web/json_api.php b/data/web/json_api.php index 9a557e7b..79056bc6 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -175,15 +175,22 @@ if (isset($_GET['query'])) { // parse post data $post = trim(file_get_contents('php://input')); if ($post) $post = json_decode($post); - - // decode base64 strings - $clientDataJSON = base64_decode($post->clientDataJSON); - $attestationObject = base64_decode($post->attestationObject); // process registration data from authenticator try { + // decode base64 strings + $clientDataJSON = base64_decode($post->clientDataJSON); + $attestationObject = base64_decode($post->attestationObject); + // processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true) $data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $_SESSION['challenge'], false, true); + + // safe authenticator in mysql `tfa` table + $_data['tfa_method'] = $post->tfa_method; + $_data['key_id'] = $post->key_id; + $_data['confirm_password'] = $post->confirm_password; + $_data['registration'] = $data; + set_tfa($_data); } catch (Throwable $ex) { // err @@ -194,12 +201,6 @@ if (isset($_GET['query'])) { exit; } - // safe authenticator in mysql `tfa` table - $_data['tfa_method'] = $post->tfa_method; - $_data['key_id'] = $post->key_id; - $_data['confirm_password'] = $post->confirm_password; - $_data['registration'] = $data; - set_tfa($_data); // send response $return = new stdClass(); From 3ef2b6cfa2a99c1adbd2e9e5e74b278274fa4ade Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 21 Feb 2022 10:46:24 +0100 Subject: [PATCH 008/115] [Web] add verify selected tfa --- data/web/css/build/008-mailcow.css | 10 + data/web/inc/ajax/destroy_tfa_auth.php | 2 +- data/web/inc/footer.inc.php | 24 +- data/web/inc/functions.inc.php | 328 ++++++++++++++----------- data/web/inc/triggers.inc.php | 35 +-- data/web/json_api.php | 10 +- data/web/templates/base.twig | 100 ++++++-- data/web/templates/modals/footer.twig | 216 +++++++++++----- 8 files changed, 473 insertions(+), 252 deletions(-) diff --git a/data/web/css/build/008-mailcow.css b/data/web/css/build/008-mailcow.css index 4d45a75e..5ce2afda 100644 --- a/data/web/css/build/008-mailcow.css +++ b/data/web/css/build/008-mailcow.css @@ -257,3 +257,13 @@ code { .flag-icon { margin-right: 5px; } +.list-group-item.webauthn-authenticator-selection, +.list-group-item.totp-authenticator-selection, +.list-group-item.yubi_otp-authenticator-selection { + border-radius: 0px !important; +} +.pending-tfa-collapse { + padding: 10px; + background: #fbfbfb; + border: 1px solid #ededed; +} diff --git a/data/web/inc/ajax/destroy_tfa_auth.php b/data/web/inc/ajax/destroy_tfa_auth.php index 72c7f1e3..07873b55 100644 --- a/data/web/inc/ajax/destroy_tfa_auth.php +++ b/data/web/inc/ajax/destroy_tfa_auth.php @@ -2,5 +2,5 @@ session_start(); unset($_SESSION['pending_mailcow_cc_username']); unset($_SESSION['pending_mailcow_cc_role']); -unset($_SESSION['pending_tfa_method']); +unset($_SESSION['pending_tfa_methods']); ?> diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php index 72482707..b2f1d4d5 100644 --- a/data/web/inc/footer.inc.php +++ b/data/web/inc/footer.inc.php @@ -23,6 +23,27 @@ if (is_array($alertbox_log_parser)) { unset($_SESSION['return']); } +// map tfa details for twig +$pending_tfa_authmechs = []; +foreach($_SESSION['pending_tfa_methods'] as $authdata){ + $pending_tfa_authmechs[$authdata['authmech']] = false; +} +if (isset($pending_tfa_authmechs['webauthn'])) { + $pending_tfa_authmechs['webauthn'] = true; +} +if (!isset($pending_tfa_authmechs['webauthn']) + && isset($pending_tfa_authmechs['yubi_otp'])) { + $pending_tfa_authmechs['yubi_otp'] = true; +} +if (!isset($pending_tfa_authmechs['webauthn']) + && !isset($pending_tfa_authmechs['yubi_otp']) + && isset($pending_tfa_authmechs['totp'])) { + $pending_tfa_authmechs['totp'] = true; +} +if (isset($pending_tfa_authmechs['u2f'])) { + $pending_tfa_authmechs['u2f'] = true; +} + // globals $globalVariables = [ 'mailcow_info' => array( @@ -30,7 +51,8 @@ $globalVariables = [ 'git_project_url' => $GLOBALS['MAILCOW_GIT_URL'] ), 'js_path' => '/cache/'.basename($JSPath), - 'pending_tfa_method' => @$_SESSION['pending_tfa_method'], + 'pending_tfa_methods' => @$_SESSION['pending_tfa_methods'], + 'pending_tfa_authmechs' => $pending_tfa_authmechs, 'pending_mailcow_cc_username' => @$_SESSION['pending_mailcow_cc_username'], 'lang_footer' => json_encode($lang['footer']), 'lang_acl' => json_encode($lang['acl']), diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 21a0d8ce..0b485c6e 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -830,11 +830,15 @@ function check_login($user, $pass, $app_passwd_data = false) { $stmt->execute(array(':user' => $user)); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { + // verify password if (verify_hash($row['password'], $pass)) { - if (get_tfa($user)['name'] != "none") { + // check for tfa authenticators + $authenticators = get_tfa($user); + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { + // active tfa authenticators found, set pending user login $_SESSION['pending_mailcow_cc_username'] = $user; $_SESSION['pending_mailcow_cc_role'] = "admin"; - $_SESSION['pending_tfa_method'] = get_tfa($user)['name']; + $_SESSION['pending_tfa_methods'] = $authenticators['additional']; unset($_SESSION['ldelay']); $_SESSION['return'][] = array( 'type' => 'info', @@ -842,8 +846,7 @@ function check_login($user, $pass, $app_passwd_data = false) { 'msg' => 'awaiting_tfa_confirmation' ); return "pending"; - } - else { + } else { unset($_SESSION['ldelay']); // Reactivate TFA if it was set to "deactivate TFA for next login" $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); @@ -866,11 +869,14 @@ function check_login($user, $pass, $app_passwd_data = false) { $stmt->execute(array(':user' => $user)); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { + // verify password if (verify_hash($row['password'], $pass) !== false) { - if (get_tfa($user)['name'] != "none") { + // check for tfa authenticators + $authenticators = get_tfa($user); + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { $_SESSION['pending_mailcow_cc_username'] = $user; $_SESSION['pending_mailcow_cc_role'] = "domainadmin"; - $_SESSION['pending_tfa_method'] = get_tfa($user)['name']; + $_SESSION['pending_tfa_method'] = $authenticators['additional']; unset($_SESSION['ldelay']); $_SESSION['return'][] = array( 'type' => 'info', @@ -1142,47 +1148,46 @@ function set_tfa($_data) { global $yubi; global $tfa; $_data_log = $_data; + $access_denied = null; !isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*'; $username = $_SESSION['mailcow_cc_username']; - if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $stmt = $pdo->prepare("SELECT `password` FROM `admin` - 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; - } - } - $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; + + // check for empty user and role + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) $access_denied = true; + + // check admin confirm password + if ($access_denied === null) { + $stmt = $pdo->prepare("SELECT `password` FROM `admin` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row) { + if (!verify_hash($row['password'], $_data["confirm_password"])) $access_denied = true; + else $access_denied = false; } } + // check mailbox confirm password + if ($access_denied === null) { + $stmt = $pdo->prepare("SELECT `password` FROM `mailbox` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row) { + if (!verify_hash($row['password'], $_data["confirm_password"])) $access_denied = true; + else $access_denied = false; + } + } + + // set access_denied error + if ($access_denied){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } switch ($_data["tfa_method"]) { case "yubi_otp": @@ -1265,9 +1270,6 @@ function set_tfa($_data) { case "webauthn": $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"]; - $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `authmech` != 'webauthn'"); - $stmt->execute(array(':username' => $username)); - $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`) VALUES (?, ?, 'webauthn', ?, ?, ?, ?, '1')"); $stmt->execute(array( @@ -1439,25 +1441,27 @@ function unset_tfa_key($_data) { global $pdo; global $lang; $_data_log = $_data; + $access_denied = null; $id = intval($_data['unset_tfa_key']); $username = $_SESSION['mailcow_cc_username']; - if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } + + // check for empty user and role + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) $access_denied = true; + try { - if (!is_numeric($id)) { - $_SESSION['return'][] = array( + if (!is_numeric($id)) $access_denied = true; + + // set access_denied error + if ($access_denied){ + $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_data_log), 'msg' => 'access_denied' ); return false; - } + } + + // check if it's last key $stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa` WHERE `username` = :username AND `active` = '1'"); $stmt->execute(array(':username' => $username)); @@ -1470,6 +1474,8 @@ function unset_tfa_key($_data) { ); return false; } + + // delete key $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `id` = :id"); $stmt->execute(array(':username' => $username, ':id' => $id)); $_SESSION['return'][] = array( @@ -1487,7 +1493,7 @@ function unset_tfa_key($_data) { return false; } } -function get_tfa($username = null) { +function get_tfa($username = null, $key_id = null) { global $pdo; if (isset($_SESSION['mailcow_cc_username'])) { $username = $_SESSION['mailcow_cc_username']; @@ -1495,92 +1501,116 @@ function get_tfa($username = null) { elseif (empty($username)) { return false; } - $stmt = $pdo->prepare("SELECT * FROM `tfa` - WHERE `username` = :username AND `active` = '1'"); - $stmt->execute(array(':username' => $username)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - 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; + if (!isset($key_id)){ + // fetch all tfa methods - just get information about possible authenticators + $stmt = $pdo->prepare("SELECT `id`, `key_id`, `authmech` FROM `tfa` + WHERE `username` = :username AND `active` = '1'"); + $stmt->execute(array(':username' => $username)); + $results = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // no tfa methods found + if (count($results) == 0) { + $data['name'] = 'none'; + $data['pretty'] = "-"; + $data['additional'] = array(); + return $data; + } + + $data['additional'] = $results; + return $data; + } else { + // fetch specific authenticator details by key_id + $stmt = $pdo->prepare("SELECT * FROM `tfa` + WHERE `username` = :username AND `active` = '1'"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + 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; + // u2f - deprecated, should be removed + 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; + case "webauthn": + $data['name'] = "webauthn"; + $data['pretty'] = "WebAuthn"; + $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' 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; } - return $data; - break; - // u2f - deprecated, should be removed - 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; - case "webauthn": - $data['name'] = "webauthn"; - $data['pretty'] = "WebAuthn"; - $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' 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: + } + else { $data['name'] = 'none'; $data['pretty'] = "-"; return $data; - break; + } } - } - else { - $data['name'] = 'none'; - $data['pretty'] = "-"; - return $data; - } } -function verify_tfa_login($username, $_data, $WebAuthn) { - global $pdo; - global $yubi; - global $u2f; - global $tfa; +function verify_tfa_login($username, $_data) { + global $pdo; + global $yubi; + global $u2f; + global $tfa; + global $WebAuthn; + + if ($_data['tfa_method'] != 'u2f'){ $stmt = $pdo->prepare("SELECT `authmech` FROM `tfa` - WHERE `username` = :username AND `active` = '1'"); - $stmt->execute(array(':username' => $username)); + WHERE `username` = :username AND `key_id` = :key_id AND `active` = '1'"); + $stmt->execute(array(':username' => $username, ':key_id' => $_data['key_id'])); $row = $stmt->fetch(PDO::FETCH_ASSOC); switch ($row["authmech"]) { @@ -1597,9 +1627,10 @@ function verify_tfa_login($username, $_data, $WebAuthn) { $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` WHERE `username` = :username AND `authmech` = 'yubi_otp' + AND `key_id` = ':key_id' AND `active`='1' AND `secret` LIKE :modhex"); - $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id)); + $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id, ':key_id' => $_data['key_id'])); $row = $stmt->fetch(PDO::FETCH_ASSOC); $yubico_auth = explode(':', $row['secret']); $yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]); @@ -1636,8 +1667,9 @@ function verify_tfa_login($username, $_data, $WebAuthn) { $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` WHERE `username` = :username AND `authmech` = 'totp' + AND `key_id` = :key_id AND `active`='1'"); - $stmt->execute(array(':username' => $username)); + $stmt->execute(array(':username' => $username, ':key_id' => $_data['key_id'])); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { if ($tfa->verifyCode($row['secret'], $_data['token']) === true) { @@ -1666,13 +1698,6 @@ function verify_tfa_login($username, $_data, $WebAuthn) { return false; } break; - // u2f - deprecated, should be removed - case "u2f": - // delete old keys that used u2f - $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `authmech` = :authmech AND `username` = :username"); - $stmt->execute(array(':authmech' => 'u2f', ':username' => $username)); - - return true; case "webauthn": $tokenData = json_decode($_data['token']); $clientDataJSON = base64_decode($tokenData->clientDataJSON); @@ -1681,7 +1706,7 @@ function verify_tfa_login($username, $_data, $WebAuthn) { $id = base64_decode($tokenData->id); $challenge = $_SESSION['challenge']; - $stmt = $pdo->prepare("SELECT `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `keyHandle` = :tokenId"); + $stmt = $pdo->prepare("SELECT `id`, `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `keyHandle` = :tokenId"); $stmt->execute(array(':tokenId' => $tokenData->id)); $process_webauthn = $stmt->fetch(PDO::FETCH_ASSOC); @@ -1738,7 +1763,7 @@ function verify_tfa_login($username, $_data, $WebAuthn) { $_SESSION["mailcow_cc_username"] = $process_webauthn['username']; - $_SESSION['tfa_id'] = $process_webauthn['key_id']; + $_SESSION['tfa_id'] = $process_webauthn['id']; $_SESSION['authReq'] = null; unset($_SESSION["challenge"]); $_SESSION['return'][] = array( @@ -1759,6 +1784,17 @@ function verify_tfa_login($username, $_data, $WebAuthn) { } return false; + } else { + // delete old keys that used u2f + $stmt = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username"); + $stmt->execute(array(':username' => $username)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + if (count($rows) == 0) return false; + + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username"); + $stmt->execute(array(':username' => $username)); + return true; + } } function admin_api($access, $action, $data = null) { global $pdo; diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php index cb3a3771..1e2bdb42 100644 --- a/data/web/inc/triggers.inc.php +++ b/data/web/inc/triggers.inc.php @@ -1,24 +1,24 @@ tfa_method; $_data['key_id'] = $post->key_id; + $_data['confirm_password'] = $post->confirm_password; $_data['registration'] = $data; set_tfa($_data); @@ -450,14 +451,15 @@ if (isset($_GET['query'])) { $stmt = $pdo->prepare("SELECT `keyHandle` FROM `tfa` WHERE username = :username"); $stmt->execute(array(':username' => $_SESSION['pending_mailcow_cc_username'])); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $cids[] = base64_decode($row['keyHandle']); - } - if (count($cids) == 0) { + if (count($rows) == 0) { print(json_encode(array( 'type' => 'error', 'msg' => 'Cannot find matching credentialIds' ))); + exit; + } + while($row = array_shift($rows)) { + $cids[] = base64_decode($row['keyHandle']); } $getArgs = $WebAuthn->getGetArgs($cids, 30, true, true, true, true, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN']); diff --git a/data/web/templates/base.twig b/data/web/templates/base.twig index 08376e71..2691718b 100644 --- a/data/web/templates/base.twig +++ b/data/web/templates/base.twig @@ -176,15 +176,62 @@ function recursiveBase64StrToArrayBuffer(obj) { {% endfor %} // Confirm TFA modal - {% if pending_tfa_method %} + {% if pending_tfa_methods %} $('#ConfirmTFAModal').modal({ backdrop: 'static', keyboard: false }); + // validate Yubi OTP tfa + $("#pending_tfa_tab_yubi_otp").click(function(){ + $(".totp-authenticator-selection").removeClass("active"); + $(".webauthn-authenticator-selection").removeClass("active"); + + $("#collapseTotpTFA").collapse('hide'); + $("#collapseWebAuthnTFA").collapse('hide'); + }); + $(".yubi-authenticator-selection").click(function(){ + $(".yubi-authenticator-selection").removeClass("active"); + $(this).addClass("active"); + + var key_id = $(this).children('span').first().text(); + $("#yubi_selected_key_id").val(key_id); + + $("#collapseYubiTFA").collapse('show'); + }); + // validate Time based OTP tfa + $("#pending_tfa_tab_totp").click(function(){ + $(".yubi-authenticator-selection").removeClass("active"); + $(".webauthn-authenticator-selection").removeClass("active"); + + $("#collapseYubiTFA").collapse('hide'); + $("#collapseWebAuthnTFA").collapse('hide'); + }); + $(".totp-authenticator-selection").click(function(){ + $(".totp-authenticator-selection").removeClass("active"); + $(this).addClass("active"); + + var key_id = $(this).children('span').first().text(); + $("#totp_selected_key_id").val(key_id); + + $("#collapseTotpTFA").collapse('show'); + }); // validate WebAuthn tfa - $('#start_webauthn_confirmation').click(function(){ - $('#webauthn_status_auth').html('

' + lang_tfa.init_webauthn + '

'); + $("#pending_tfa_tab_webauthn").click(function(){ + $(".totp-authenticator-selection").removeClass("active"); + $(".yubi-authenticator-selection").removeClass("active"); + + $("#collapseTotpTFA").collapse('hide'); + $("#collapseYubiTFA").collapse('hide'); + }); + $(".webauthn-authenticator-selection").click(function(){ + $(".webauthn-authenticator-selection").removeClass("active"); + $(this).addClass("active"); + + var key_id = $(this).children('span').first().text(); + $("#webauthn_selected_key_id").val(key_id); + + $("#collapseWebAuthnTFA").collapse('show'); $(this).find('input[name=token]').focus(); if(document.getElementById("webauthn_auth_data") !== null) { @@ -198,30 +245,31 @@ function recursiveBase64StrToArrayBuffer(obj) { window.fetch("/api/v1/get/webauthn-tfa-get-args", {method:'GET',cache:'no-cache'}).then(response => { return response.json(); }).then(json => { - if (json.success === false) throw new Error(); + if (json.success === false) throw new Error(); + if (json.type === "error") throw new Error(json.msg); - recursiveBase64StrToArrayBuffer(json); - return json; + recursiveBase64StrToArrayBuffer(json); + return json; }).then(getCredentialArgs => { - // get credentials - return navigator.credentials.get(getCredentialArgs); + // get credentials + return navigator.credentials.get(getCredentialArgs); }).then(cred => { - return { - id: cred.rawId ? arrayBufferToBase64(cred.rawId) : null, - clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null, - authenticatorData: cred.response.authenticatorData ? arrayBufferToBase64(cred.response.authenticatorData) : null, - signature : cred.response.signature ? arrayBufferToBase64(cred.response.signature) : null - }; + return { + id: cred.rawId ? arrayBufferToBase64(cred.rawId) : null, + clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null, + authenticatorData: cred.response.authenticatorData ? arrayBufferToBase64(cred.response.authenticatorData) : null, + signature : cred.response.signature ? arrayBufferToBase64(cred.response.signature) : null + }; }).then(JSON.stringify).then(function(AuthenticatorAttestationResponse) { - // send request by submit - var form = document.getElementById('webauthn_auth_form'); - var auth = document.getElementById('webauthn_auth_data'); - auth.value = AuthenticatorAttestationResponse; - form.submit(); + // send request by submit + var form = document.getElementById('webauthn_auth_form'); + var auth = document.getElementById('webauthn_auth_data'); + auth.value = AuthenticatorAttestationResponse; + form.submit(); }).catch(function(err) { - var webauthn_return_code = document.getElementById('webauthn_return_code'); - webauthn_return_code.style.display = webauthn_return_code.style.display === 'none' ? '' : null; - webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry; + var webauthn_return_code = document.getElementById('webauthn_return_code'); + webauthn_return_code.style.display = webauthn_return_code.style.display === 'none' ? '' : null; + webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry; }); } }); @@ -237,7 +285,9 @@ function recursiveBase64StrToArrayBuffer(obj) { } }); }); - {% endif %} + {% endif %} + + // Validate FIDO2 $("#fido2-login").click(function(){ $('#fido2-alerts').html(); @@ -358,6 +408,7 @@ function recursiveBase64StrToArrayBuffer(obj) { $("#start_webauthn_register").click(() => { var key_id = document.getElementsByName('key_id')[1].value; + var confirm_password = document.getElementsByName('confirm_password')[1].value; // fetch WebAuthn create args window.fetch("/api/v1/get/webauthn-tfa-registration/{{ mailcow_cc_username|url_encode(true)|default('null') }}", {method:'GET',cache:'no-cache'}).then(response => { @@ -375,7 +426,8 @@ function recursiveBase64StrToArrayBuffer(obj) { clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null, attestationObject: cred.response.attestationObject ? arrayBufferToBase64(cred.response.attestationObject) : null, key_id: key_id, - tfa_method: "webauthn" + tfa_method: "webauthn", + confirm_password: confirm_password }; }).then(JSON.stringify).then(AuthenticatorAttestationResponse => { // send request diff --git a/data/web/templates/modals/footer.twig b/data/web/templates/modals/footer.twig index 690b9de0..6df4a10d 100644 --- a/data/web/templates/modals/footer.twig +++ b/data/web/templates/modals/footer.twig @@ -133,73 +133,171 @@ {% endif %} -{% if pending_tfa_method %} +{% if pending_tfa_methods %} From 5fcccbc97d02c4b0cdd0c479bd15d6293b83dbb0 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 21 Feb 2022 14:10:12 +0100 Subject: [PATCH 009/115] [Web] add verify selected tfa --- data/web/inc/functions.inc.php | 61 ++++++++++++++++----------- data/web/templates/base.twig | 12 +++--- data/web/templates/modals/footer.twig | 17 +++++--- 3 files changed, 52 insertions(+), 38 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 0b485c6e..f93e7eca 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -1493,7 +1493,7 @@ function unset_tfa_key($_data) { return false; } } -function get_tfa($username = null, $key_id = null) { +function get_tfa($username = null, $id = null) { global $pdo; if (isset($_SESSION['mailcow_cc_username'])) { $username = $_SESSION['mailcow_cc_username']; @@ -1502,7 +1502,7 @@ function get_tfa($username = null, $key_id = null) { return false; } - if (!isset($key_id)){ + if (!isset($id)){ // fetch all tfa methods - just get information about possible authenticators $stmt = $pdo->prepare("SELECT `id`, `key_id`, `authmech` FROM `tfa` WHERE `username` = :username AND `active` = '1'"); @@ -1520,10 +1520,10 @@ function get_tfa($username = null, $key_id = null) { $data['additional'] = $results; return $data; } else { - // fetch specific authenticator details by key_id + // fetch specific authenticator details by id $stmt = $pdo->prepare("SELECT * FROM `tfa` - WHERE `username` = :username AND `active` = '1'"); - $stmt->execute(array(':username' => $username)); + WHERE `username` = :username AND `id` = :id AND `active` = '1'"); + $stmt->execute(array(':username' => $username, ':id' => $id)); $row = $stmt->fetch(PDO::FETCH_ASSOC); if (isset($row["authmech"])) { @@ -1531,9 +1531,10 @@ function get_tfa($username = null, $key_id = null) { 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 = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username AND `id` = :id"); $stmt->execute(array( ':username' => $username, + ':id' => $id )); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { @@ -1545,9 +1546,10 @@ function get_tfa($username = null, $key_id = null) { 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 = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username AND `id` = :id"); $stmt->execute(array( ':username' => $username, + ':id' => $id )); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { @@ -1563,9 +1565,10 @@ function get_tfa($username = null, $key_id = null) { 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 = $pdo->prepare("SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' AND `username` = :username AND `id` = :id"); $stmt->execute(array( ':username' => $username, + ':id' => $id )); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { @@ -1576,9 +1579,10 @@ function get_tfa($username = null, $key_id = null) { case "webauthn": $data['name'] = "webauthn"; $data['pretty'] = "WebAuthn"; - $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' AND `username` = :username"); + $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' AND `username` = :username AND `id` = :id"); $stmt->execute(array( ':username' => $username, + ':id' => $id )); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { @@ -1609,8 +1613,8 @@ function verify_tfa_login($username, $_data) { if ($_data['tfa_method'] != 'u2f'){ $stmt = $pdo->prepare("SELECT `authmech` FROM `tfa` - WHERE `username` = :username AND `key_id` = :key_id AND `active` = '1'"); - $stmt->execute(array(':username' => $username, ':key_id' => $_data['key_id'])); + WHERE `username` = :username AND `id` = :id AND `active` = '1'"); + $stmt->execute(array(':username' => $username, ':id' => $_data['id'])); $row = $stmt->fetch(PDO::FETCH_ASSOC); switch ($row["authmech"]) { @@ -1627,10 +1631,10 @@ function verify_tfa_login($username, $_data) { $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` WHERE `username` = :username AND `authmech` = 'yubi_otp' - AND `key_id` = ':key_id' + AND `id` = ':id' AND `active`='1' AND `secret` LIKE :modhex"); - $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id, ':key_id' => $_data['key_id'])); + $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id, ':id' => $_data['id'])); $row = $stmt->fetch(PDO::FETCH_ASSOC); $yubico_auth = explode(':', $row['secret']); $yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]); @@ -1663,16 +1667,16 @@ function verify_tfa_login($username, $_data) { return false; break; case "totp": - try { + try { $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` WHERE `username` = :username AND `authmech` = 'totp' - AND `key_id` = :key_id + AND `id` = :id AND `active`='1'"); - $stmt->execute(array(':username' => $username, ':key_id' => $_data['key_id'])); + $stmt->execute(array(':username' => $username, ':id' => $_data['id'])); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { - if ($tfa->verifyCode($row['secret'], $_data['token']) === true) { + if ($tfa->verifyCode($row['secret'], $_data['token']) === true) { $_SESSION['tfa_id'] = $row['id']; $_SESSION['return'][] = array( 'type' => 'success', @@ -1680,7 +1684,7 @@ function verify_tfa_login($username, $_data) { 'msg' => 'verified_totp_login' ); return true; - } + } } $_SESSION['return'][] = array( 'type' => 'danger', @@ -1688,15 +1692,15 @@ function verify_tfa_login($username, $_data) { 'msg' => 'totp_verification_failed' ); return false; - } - catch (PDOException $e) { + } + catch (PDOException $e) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $username, '*'), 'msg' => array('mysql_error', $e) ); return false; - } + } break; case "webauthn": $tokenData = json_decode($_data['token']); @@ -1706,13 +1710,20 @@ function verify_tfa_login($username, $_data) { $id = base64_decode($tokenData->id); $challenge = $_SESSION['challenge']; - $stmt = $pdo->prepare("SELECT `id`, `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `keyHandle` = :tokenId"); - $stmt->execute(array(':tokenId' => $tokenData->id)); + $stmt = $pdo->prepare("SELECT `id`, `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data['id'])); $process_webauthn = $stmt->fetch(PDO::FETCH_ASSOC); - if (empty($process_webauthn) || empty($process_webauthn['publicKey']) || empty($process_webauthn['username'])) return false; + if (empty($process_webauthn)){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('webauthn_verification_failed', 'authenticator not found') + ); + return false; + } - if ($process_webauthn['publicKey'] === false) { + if (empty($process_webauthn['publicKey']) || $process_webauthn['publicKey'] === false) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $username, '*'), diff --git a/data/web/templates/base.twig b/data/web/templates/base.twig index 2691718b..b51dcde2 100644 --- a/data/web/templates/base.twig +++ b/data/web/templates/base.twig @@ -194,8 +194,8 @@ function recursiveBase64StrToArrayBuffer(obj) { $(".yubi-authenticator-selection").removeClass("active"); $(this).addClass("active"); - var key_id = $(this).children('span').first().text(); - $("#yubi_selected_key_id").val(key_id); + var id = $(this).children('input').first().val(); + $("#yubi_selected_id").val(id); $("#collapseYubiTFA").collapse('show'); }); @@ -211,8 +211,8 @@ function recursiveBase64StrToArrayBuffer(obj) { $(".totp-authenticator-selection").removeClass("active"); $(this).addClass("active"); - var key_id = $(this).children('span').first().text(); - $("#totp_selected_key_id").val(key_id); + var id = $(this).children('input').first().val(); + $("#totp_selected_id").val(id); $("#collapseTotpTFA").collapse('show'); }); @@ -228,8 +228,8 @@ function recursiveBase64StrToArrayBuffer(obj) { $(".webauthn-authenticator-selection").removeClass("active"); $(this).addClass("active"); - var key_id = $(this).children('span').first().text(); - $("#webauthn_selected_key_id").val(key_id); + var id = $(this).children('input').first().val(); + $("#webauthn_selected_id").val(id); $("#collapseWebAuthnTFA").collapse('show'); diff --git a/data/web/templates/modals/footer.twig b/data/web/templates/modals/footer.twig index 6df4a10d..67cc3482 100644 --- a/data/web/templates/modals/footer.twig +++ b/data/web/templates/modals/footer.twig @@ -139,7 +139,7 @@ @@ -205,7 +206,7 @@
- Available Authenticators + Authenticators
{% for authenticator in pending_tfa_methods %} @@ -213,6 +214,7 @@ {{ authenticator["key_id"] }} + {% endif %} {% endfor %} @@ -223,7 +225,7 @@ Yubicon Icon - +
@@ -240,7 +242,7 @@ - Available Authenticators + Authenticators
{% for authenticator in pending_tfa_methods %} @@ -248,6 +250,7 @@ {{ authenticator["key_id"] }} + {% endif %} {% endfor %} @@ -258,7 +261,7 @@ -
+
From 21fadf6df2bad0a7d4edf18a1506303b1728edb0 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Tue, 22 Feb 2022 09:38:06 +0100 Subject: [PATCH 010/115] [Web] multiple tfa - domainadmin support --- data/web/inc/functions.inc.php | 2 +- data/web/templates/domainadmin.twig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index f93e7eca..384905d4 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -876,7 +876,7 @@ function check_login($user, $pass, $app_passwd_data = false) { if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { $_SESSION['pending_mailcow_cc_username'] = $user; $_SESSION['pending_mailcow_cc_role'] = "domainadmin"; - $_SESSION['pending_tfa_method'] = $authenticators['additional']; + $_SESSION['pending_tfa_methods'] = $authenticators['additional']; unset($_SESSION['ldelay']); $_SESSION['return'][] = array( 'type' => 'info', diff --git a/data/web/templates/domainadmin.twig b/data/web/templates/domainadmin.twig index 6aae54ba..3bc7ab40 100644 --- a/data/web/templates/domainadmin.twig +++ b/data/web/templates/domainadmin.twig @@ -28,7 +28,7 @@
From 49c506eed956fdc985f3b64e82336c27fca68e52 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 7 Mar 2022 11:41:13 +0100 Subject: [PATCH 011/115] [Web] multiple tfa - user support --- data/web/inc/functions.inc.php | 47 ++++++++++++++-------- data/web/inc/triggers.inc.php | 6 +-- data/web/templates/user/tab-user-auth.twig | 27 ++++++++++++- data/web/user.php | 1 + 4 files changed, 60 insertions(+), 21 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 384905d4..12efd60b 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -936,24 +936,39 @@ function check_login($user, $pass, $app_passwd_data = false) { $rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC)); } foreach ($rows as $row) { + // verify password 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) - ); - if ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) { - $service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV'; - $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)"); - $stmt->execute(array( - ':service' => $service, - ':app_id' => $row['app_passwd_id'], - ':username' => $user, - ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']) - )); + // check for tfa authenticators + $authenticators = get_tfa($user); + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { + $_SESSION['pending_mailcow_cc_username'] = $user; + $_SESSION['pending_mailcow_cc_role'] = "user"; + $_SESSION['pending_tfa_methods'] = $authenticators['additional']; + unset($_SESSION['ldelay']); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => array('logged_in_as', $user) + ); + return "pending"; + } else { + if ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) { + $service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV'; + $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)"); + $stmt->execute(array( + ':service' => $service, + ':app_id' => $row['app_passwd_id'], + ':username' => $user, + ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']) + )); + } + + unset($_SESSION['ldelay']); + // Reactivate TFA if it was set to "deactivate TFA for next login" + $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); + $stmt->execute(array(':user' => $user)); + return "user"; } - return "user"; } } diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php index 1e2bdb42..aec043e9 100644 --- a/data/web/inc/triggers.inc.php +++ b/data/web/inc/triggers.inc.php @@ -61,9 +61,9 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { header("Location: /user"); } elseif ($as != "pending") { - unset($_SESSION['pending_mailcow_cc_username']); - unset($_SESSION['pending_mailcow_cc_role']); - unset($_SESSION['pending_tfa_methods']); + unset($_SESSION['pending_mailcow_cc_username']); + unset($_SESSION['pending_mailcow_cc_role']); + unset($_SESSION['pending_tfa_methods']); unset($_SESSION['mailcow_cc_username']); unset($_SESSION['mailcow_cc_role']); } diff --git a/data/web/templates/user/tab-user-auth.twig b/data/web/templates/user/tab-user-auth.twig index e1f84fff..0d4c6f32 100644 --- a/data/web/templates/user/tab-user-auth.twig +++ b/data/web/templates/user/tab-user-auth.twig @@ -15,6 +15,10 @@ {{ lang.user.open_webmail_sso }} {% endif %} +

@@ -40,8 +44,27 @@

{{ mailboxdata.quota_used|formatBytes(2) }} / {% if mailboxdata.quota == 0 %}∞{% else %}{{ mailboxdata.quota|formatBytes(2) }}{% endif %}
{{ mailboxdata.messages }} {{ lang.user.messages }}

-
-

{{ lang.user.change_password }}

+ + +
+ {# TFA #} +
+
{{ lang.tfa.tfa }}:
+
+

{{ tfa_data.pretty }}

+ {% include 'tfa_keys.twig' %} +
+
+
+
+
{{ lang.tfa.set_tfa }}:
+
+

diff --git a/data/web/user.php b/data/web/user.php index 5bf60917..d7faf791 100644 --- a/data/web/user.php +++ b/data/web/user.php @@ -76,6 +76,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' 'acl_json' => json_encode($_SESSION['acl']), 'user_spam_score' => mailbox('get', 'spam_score', $username), 'tfa_data' => $tfa_data, + 'tfa_id' => @$_SESSION['tfa_id'], 'fido2_data' => $fido2_data, 'mailboxdata' => $mailboxdata, 'clientconfigstr' => $clientconfigstr, From e7fe52a62522ef3f0f51d79e964c8209834073cc Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 14 Mar 2022 09:29:07 +0100 Subject: [PATCH 012/115] [Web] increase mysql publicKey field length --- data/web/inc/init_db.inc.php | 4 ++-- data/web/json_api.php | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 3cab461e..5705379d 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -3,7 +3,7 @@ function init_db_schema() { try { global $pdo; - $db_version = "18012022_1020"; + $db_version = "14032022_0921"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -699,7 +699,7 @@ function init_db_schema() { "authmech" => "ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')", "secret" => "VARCHAR(255) DEFAULT NULL", "keyHandle" => "VARCHAR(255) DEFAULT NULL", - "publicKey" => "VARCHAR(255) DEFAULT NULL", + "publicKey" => "VARCHAR(4096) DEFAULT NULL", "counter" => "INT NOT NULL DEFAULT '0'", "certificate" => "TEXT", "active" => "TINYINT(1) NOT NULL DEFAULT '0'" diff --git a/data/web/json_api.php b/data/web/json_api.php index 9a557e7b..79056bc6 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -175,15 +175,22 @@ if (isset($_GET['query'])) { // parse post data $post = trim(file_get_contents('php://input')); if ($post) $post = json_decode($post); - - // decode base64 strings - $clientDataJSON = base64_decode($post->clientDataJSON); - $attestationObject = base64_decode($post->attestationObject); // process registration data from authenticator try { + // decode base64 strings + $clientDataJSON = base64_decode($post->clientDataJSON); + $attestationObject = base64_decode($post->attestationObject); + // processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true) $data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $_SESSION['challenge'], false, true); + + // safe authenticator in mysql `tfa` table + $_data['tfa_method'] = $post->tfa_method; + $_data['key_id'] = $post->key_id; + $_data['confirm_password'] = $post->confirm_password; + $_data['registration'] = $data; + set_tfa($_data); } catch (Throwable $ex) { // err @@ -194,12 +201,6 @@ if (isset($_GET['query'])) { exit; } - // safe authenticator in mysql `tfa` table - $_data['tfa_method'] = $post->tfa_method; - $_data['key_id'] = $post->key_id; - $_data['confirm_password'] = $post->confirm_password; - $_data['registration'] = $data; - set_tfa($_data); // send response $return = new stdClass(); From b185f83fc3e38ae44d87161e4f9d229bcc8f4bec Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Fri, 18 Mar 2022 08:37:22 +0100 Subject: [PATCH 013/115] [Web] tfa extra debugging --- data/web/inc/functions.inc.php | 94 ++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 10 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 12efd60b..c58662fd 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -1626,12 +1626,28 @@ function verify_tfa_login($username, $_data) { global $tfa; global $WebAuthn; + // just for debugging + // remove later + $_SESSION['return'][] = array( + 'type' => 'info', + 'log' => array(__FUNCTION__, 'tfa_post_data_log'), + 'msg' => $_data + ); + if ($_data['tfa_method'] != 'u2f'){ $stmt = $pdo->prepare("SELECT `authmech` FROM `tfa` WHERE `username` = :username AND `id` = :id AND `active` = '1'"); $stmt->execute(array(':username' => $username, ':id' => $_data['id'])); $row = $stmt->fetch(PDO::FETCH_ASSOC); + // just for debugging + // remove later + $_SESSION['return'][] = array( + 'type' => 'info', + 'log' => array(__FUNCTION__, 'tfa_verify_authmech'), + 'msg' => $row + ); + switch ($row["authmech"]) { case "yubi_otp": if (!ctype_alnum($_data['token']) || strlen($_data['token']) != 44) { @@ -1718,6 +1734,7 @@ function verify_tfa_login($username, $_data) { } break; case "webauthn": + // prepare authenticator data $tokenData = json_decode($_data['token']); $clientDataJSON = base64_decode($tokenData->clientDataJSON); $authenticatorData = base64_decode($tokenData->authenticatorData); @@ -1725,10 +1742,28 @@ function verify_tfa_login($username, $_data) { $id = base64_decode($tokenData->id); $challenge = $_SESSION['challenge']; - $stmt = $pdo->prepare("SELECT `id`, `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `id` = :id"); + // just for debugging + // remove later + $_SESSION['return'][] = array( + 'type' => 'info', + 'log' => array(__FUNCTION__, 'tfa_try_verify'), + 'msg' => 'try grab authenticator' + ); + + // fetch authenticator + $stmt = $pdo->prepare("SELECT `id`, `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `id` = :id AND `active`='1'"); $stmt->execute(array(':id' => $_data['id'])); $process_webauthn = $stmt->fetch(PDO::FETCH_ASSOC); + // just for debugging + // remove later + $_SESSION['return'][] = array( + 'type' => 'info', + 'log' => array(__FUNCTION__, 'tfa_authenticator_grabbed'), + 'msg' => 'grabbed authenticator from db' + ); + + // return err if no authenticator was found if (empty($process_webauthn)){ $_SESSION['return'][] = array( 'type' => 'danger', @@ -1738,6 +1773,7 @@ function verify_tfa_login($username, $_data) { return false; } + // return err if authenticator has no publicKey if (empty($process_webauthn['publicKey']) || $process_webauthn['publicKey'] === false) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -1746,6 +1782,8 @@ function verify_tfa_login($username, $_data) { ); return false; } + + // try verify authenticator try { $WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $process_webauthn['publicKey'], $challenge, null, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN'], $GLOBALS['WEBAUTHN_USER_PRESENT_FLAG']); } @@ -1758,26 +1796,54 @@ function verify_tfa_login($username, $_data) { return false; } - + // just for debugging + // remove later + $_SESSION['return'][] = array( + 'type' => 'info', + 'log' => array(__FUNCTION__, 'tfa_progress'), + 'msg' => 'authenticator verified, check user role' + ); + + // if verified, check user role $stmt = $pdo->prepare("SELECT `superadmin` FROM `admin` WHERE `username` = :username"); $stmt->execute(array(':username' => $process_webauthn['username'])); $obj_props = $stmt->fetch(PDO::FETCH_ASSOC); if ($obj_props['superadmin'] === 1) { - $_SESSION["mailcow_cc_role"] = "admin"; + // is admin + $_SESSION["mailcow_cc_role"] = "admin"; } elseif ($obj_props['superadmin'] === 0) { - $_SESSION["mailcow_cc_role"] = "domainadmin"; + // is domainadmin + $_SESSION["mailcow_cc_role"] = "domainadmin"; } else { - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :username"); - $stmt->execute(array(':username' => $process_webauthn['username'])); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if ($row['username'] == $process_webauthn['username']) { + // just for debugging + // remove later + $_SESSION['return'][] = array( + 'type' => 'info', + 'log' => array(__FUNCTION__, 'tfa_progress'), + 'msg' => 'no admin or domainadmin role, check if normal user' + ); + + // no admin, check if normal user + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array(':username' => $process_webauthn['username'])); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($row['username']) { + // is user $_SESSION["mailcow_cc_role"] = "user"; - } + } else { + // err, no specific role found + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('webauthn_verification_failed', 'could not determine user role') + ); + return false; + } } - + // check if fetched user and pendig_user matches if ($process_webauthn['username'] != $_SESSION['pending_mailcow_cc_username']){ $_SESSION['return'][] = array( 'type' => 'danger', @@ -1787,7 +1853,15 @@ function verify_tfa_login($username, $_data) { return false; } + // just for debugging + // remove later + $_SESSION['return'][] = array( + 'type' => 'info', + 'log' => array(__FUNCTION__, 'tfa_success'), + 'msg' => 'tfa flow success' + ); + // set user session data and delete WebAuthn challenge session $_SESSION["mailcow_cc_username"] = $process_webauthn['username']; $_SESSION['tfa_id'] = $process_webauthn['id']; $_SESSION['authReq'] = null; From 70921b8d154d365f13b50b91a68ae5cc484a79a9 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Fri, 18 Mar 2022 08:45:02 +0100 Subject: [PATCH 014/115] [Web] tfa extra debugging --- data/web/inc/functions.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index c58662fd..e43024f1 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -1829,7 +1829,7 @@ function verify_tfa_login($username, $_data) { $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :username"); $stmt->execute(array(':username' => $process_webauthn['username'])); $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!empty($row['username']) { + if (!empty($row['username'])) { // is user $_SESSION["mailcow_cc_role"] = "user"; } else { From 6d3798ad08176cf542bb2fdea858be99cccbf190 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Sat, 19 Mar 2022 20:18:31 +0100 Subject: [PATCH 015/115] [Web] fix yubi otp --- data/web/inc/functions.inc.php | 73 +--------------------------------- 1 file changed, 2 insertions(+), 71 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index e43024f1..9c73a475 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -1626,28 +1626,12 @@ function verify_tfa_login($username, $_data) { global $tfa; global $WebAuthn; - // just for debugging - // remove later - $_SESSION['return'][] = array( - 'type' => 'info', - 'log' => array(__FUNCTION__, 'tfa_post_data_log'), - 'msg' => $_data - ); - if ($_data['tfa_method'] != 'u2f'){ $stmt = $pdo->prepare("SELECT `authmech` FROM `tfa` WHERE `username` = :username AND `id` = :id AND `active` = '1'"); $stmt->execute(array(':username' => $username, ':id' => $_data['id'])); $row = $stmt->fetch(PDO::FETCH_ASSOC); - // just for debugging - // remove later - $_SESSION['return'][] = array( - 'type' => 'info', - 'log' => array(__FUNCTION__, 'tfa_verify_authmech'), - 'msg' => $row - ); - switch ($row["authmech"]) { case "yubi_otp": if (!ctype_alnum($_data['token']) || strlen($_data['token']) != 44) { @@ -1662,8 +1646,8 @@ function verify_tfa_login($username, $_data) { $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` WHERE `username` = :username AND `authmech` = 'yubi_otp' - AND `id` = ':id' - AND `active`='1' + AND `id` = :id + AND `active` = '1' AND `secret` LIKE :modhex"); $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id, ':id' => $_data['id'])); $row = $stmt->fetch(PDO::FETCH_ASSOC); @@ -1734,7 +1718,6 @@ function verify_tfa_login($username, $_data) { } break; case "webauthn": - // prepare authenticator data $tokenData = json_decode($_data['token']); $clientDataJSON = base64_decode($tokenData->clientDataJSON); $authenticatorData = base64_decode($tokenData->authenticatorData); @@ -1742,28 +1725,10 @@ function verify_tfa_login($username, $_data) { $id = base64_decode($tokenData->id); $challenge = $_SESSION['challenge']; - // just for debugging - // remove later - $_SESSION['return'][] = array( - 'type' => 'info', - 'log' => array(__FUNCTION__, 'tfa_try_verify'), - 'msg' => 'try grab authenticator' - ); - - // fetch authenticator $stmt = $pdo->prepare("SELECT `id`, `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `id` = :id AND `active`='1'"); $stmt->execute(array(':id' => $_data['id'])); $process_webauthn = $stmt->fetch(PDO::FETCH_ASSOC); - // just for debugging - // remove later - $_SESSION['return'][] = array( - 'type' => 'info', - 'log' => array(__FUNCTION__, 'tfa_authenticator_grabbed'), - 'msg' => 'grabbed authenticator from db' - ); - - // return err if no authenticator was found if (empty($process_webauthn)){ $_SESSION['return'][] = array( 'type' => 'danger', @@ -1773,7 +1738,6 @@ function verify_tfa_login($username, $_data) { return false; } - // return err if authenticator has no publicKey if (empty($process_webauthn['publicKey']) || $process_webauthn['publicKey'] === false) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -1783,7 +1747,6 @@ function verify_tfa_login($username, $_data) { return false; } - // try verify authenticator try { $WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $process_webauthn['publicKey'], $challenge, null, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN'], $GLOBALS['WEBAUTHN_USER_PRESENT_FLAG']); } @@ -1796,44 +1759,22 @@ function verify_tfa_login($username, $_data) { return false; } - // just for debugging - // remove later - $_SESSION['return'][] = array( - 'type' => 'info', - 'log' => array(__FUNCTION__, 'tfa_progress'), - 'msg' => 'authenticator verified, check user role' - ); - - // if verified, check user role $stmt = $pdo->prepare("SELECT `superadmin` FROM `admin` WHERE `username` = :username"); $stmt->execute(array(':username' => $process_webauthn['username'])); $obj_props = $stmt->fetch(PDO::FETCH_ASSOC); if ($obj_props['superadmin'] === 1) { - // is admin $_SESSION["mailcow_cc_role"] = "admin"; } elseif ($obj_props['superadmin'] === 0) { - // is domainadmin $_SESSION["mailcow_cc_role"] = "domainadmin"; } else { - // just for debugging - // remove later - $_SESSION['return'][] = array( - 'type' => 'info', - 'log' => array(__FUNCTION__, 'tfa_progress'), - 'msg' => 'no admin or domainadmin role, check if normal user' - ); - - // no admin, check if normal user $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :username"); $stmt->execute(array(':username' => $process_webauthn['username'])); $row = $stmt->fetch(PDO::FETCH_ASSOC); if (!empty($row['username'])) { - // is user $_SESSION["mailcow_cc_role"] = "user"; } else { - // err, no specific role found $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $username, '*'), @@ -1843,7 +1784,6 @@ function verify_tfa_login($username, $_data) { } } - // check if fetched user and pendig_user matches if ($process_webauthn['username'] != $_SESSION['pending_mailcow_cc_username']){ $_SESSION['return'][] = array( 'type' => 'danger', @@ -1853,15 +1793,6 @@ function verify_tfa_login($username, $_data) { return false; } - // just for debugging - // remove later - $_SESSION['return'][] = array( - 'type' => 'info', - 'log' => array(__FUNCTION__, 'tfa_success'), - 'msg' => 'tfa flow success' - ); - - // set user session data and delete WebAuthn challenge session $_SESSION["mailcow_cc_username"] = $process_webauthn['username']; $_SESSION['tfa_id'] = $process_webauthn['id']; $_SESSION['authReq'] = null; From 3029a2d33d4b67ccabac8a7e6f96fba9ced05fff Mon Sep 17 00:00:00 2001 From: Niklas Meyer <62480600+DerLinkman@users.noreply.github.com> Date: Tue, 17 May 2022 15:26:01 +0200 Subject: [PATCH 016/115] Change DB Date to newer Date than staging --- data/web/inc/init_db.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 1e6b8133..be9078ff 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -3,7 +3,7 @@ function init_db_schema() { try { global $pdo; - $db_version = "02052022_1500"; + $db_version = "17052022_1525"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); From 3c9502f24114545b494fce65657f428dc7ea3772 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Tue, 17 May 2022 19:02:52 +0200 Subject: [PATCH 017/115] add webauthn console log --- data/web/templates/base.twig | 1 + 1 file changed, 1 insertion(+) diff --git a/data/web/templates/base.twig b/data/web/templates/base.twig index b51dcde2..945d4501 100644 --- a/data/web/templates/base.twig +++ b/data/web/templates/base.twig @@ -245,6 +245,7 @@ function recursiveBase64StrToArrayBuffer(obj) { window.fetch("/api/v1/get/webauthn-tfa-get-args", {method:'GET',cache:'no-cache'}).then(response => { return response.json(); }).then(json => { + console.log(json); if (json.success === false) throw new Error(); if (json.type === "error") throw new Error(json.msg); From 4ec982163edc014ec04c63927bebd6cc3e9c8bcf Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Wed, 18 May 2022 09:39:50 +0200 Subject: [PATCH 018/115] restrict webauthn-tfa-get-args sql query --- data/web/json_api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/json_api.php b/data/web/json_api.php index 4b0e294c..53e47af6 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -452,7 +452,7 @@ if (isset($_GET['query'])) { } break; case "webauthn-tfa-get-args": - $stmt = $pdo->prepare("SELECT `keyHandle` FROM `tfa` WHERE username = :username"); + $stmt = $pdo->prepare("SELECT `keyHandle` FROM `tfa` WHERE username = :username AND authmech = `webauthn`"); $stmt->execute(array(':username' => $_SESSION['pending_mailcow_cc_username'])); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); if (count($rows) == 0) { From 7d5990bf0fff79411d9e783cb5182705d367f5cc Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Wed, 18 May 2022 10:03:10 +0200 Subject: [PATCH 019/115] restrict webauthn-tfa-get-args sql query --- data/web/json_api.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/data/web/json_api.php b/data/web/json_api.php index 53e47af6..2c8f13fb 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -452,8 +452,11 @@ if (isset($_GET['query'])) { } break; case "webauthn-tfa-get-args": - $stmt = $pdo->prepare("SELECT `keyHandle` FROM `tfa` WHERE username = :username AND authmech = `webauthn`"); - $stmt->execute(array(':username' => $_SESSION['pending_mailcow_cc_username'])); + $stmt = $pdo->prepare("SELECT `keyHandle` FROM `tfa` WHERE username = :username AND authmech = :authmech"); + $stmt->execute(array( + ':username' => $_SESSION['pending_mailcow_cc_username'], + ':authmech' => 'webauthn' + )); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); if (count($rows) == 0) { print(json_encode(array( From 0eb254577374963160770d9ab08759c6e76ac04d Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Tue, 7 Jun 2022 09:01:04 +0200 Subject: [PATCH 020/115] [WebAuthn] send empty transports array to fix android bug --- data/web/json_api.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/json_api.php b/data/web/json_api.php index 2c8f13fb..0ebc95bc 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -421,7 +421,7 @@ if (isset($_GET['query'])) { // } $ids = NULL; - $getArgs = $WebAuthn->getGetArgs($ids, 30, true, true, true, true, $GLOBALS['FIDO2_UV_FLAG_LOGIN']); + $getArgs = $WebAuthn->getGetArgs($ids, 30, false, false, false, false, $GLOBALS['FIDO2_UV_FLAG_LOGIN']); print(json_encode($getArgs)); $_SESSION['challenge'] = $WebAuthn->getChallenge(); return; @@ -469,7 +469,7 @@ if (isset($_GET['query'])) { $cids[] = base64_decode($row['keyHandle']); } - $getArgs = $WebAuthn->getGetArgs($cids, 30, true, true, true, true, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN']); + $getArgs = $WebAuthn->getGetArgs($cids, 30, false, false, false, false, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN']); $getArgs->publicKey->extensions = array('appid' => "https://".$getArgs->publicKey->rpId); print(json_encode($getArgs)); $_SESSION['challenge'] = $WebAuthn->getChallenge(); From 71db83efce069c8f241bdfd4ecac56babc9b2610 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 13 Jun 2022 12:46:39 +0200 Subject: [PATCH 021/115] hotfix imapsync --- data/web/inc/functions.mailbox.inc.php | 61 +++++++++++- data/web/inc/vars.inc.php | 132 +++++++++++++++++++++++++ 2 files changed, 188 insertions(+), 5 deletions(-) diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 7f8ff3ac..d6a07eab 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -336,9 +336,34 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $mins_interval = $_data['mins_interval']; $enc1 = $_data['enc1']; $custom_params = (empty(trim($_data['custom_params']))) ? '' : trim($_data['custom_params']); - // Workaround, fixme - if (stripos($custom_params, 'pipemess') || stripos($custom_params, 'pipemes')) { - $custom_params = ''; + + // validate custom params + foreach (explode(' -', $custom_params) as $param){ + if (str_contains($param, ' ')) { + // bad char + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'bad character SPACE' + ); + return false; + } + + // extract option + if (str_contains($param, '=')) $param = explode('=', $param)[0]; + // remove first char if first char is - + if ($param[0] == '-') $param = ltrim($param, $param[0]); + + // check if param is whitelisted + if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){ + // bad option + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'bad option '. $param + ); + return false; + } } if (empty($subfolder2)) { $subfolder2 = ""; @@ -1764,8 +1789,34 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - if (stripos($custom_params, 'pipemess') || stripos($custom_params, 'pipemes')) { - $custom_params = ''; + + // validate custom params + foreach (explode(' -', $custom_params) as $param){ + if (str_contains($param, ' ')) { + // bad char + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'bad character SPACE' + ); + return false; + } + + // extract option + if (str_contains($param, '=')) $param = explode('=', $param)[0]; + // remove first char if first char is - + if ($param[0] == '-') $param = ltrim($param, $param[0]); + + // check if param is whitelisted + if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){ + // bad option + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'bad option '. $param + ); + return false; + } } if (empty($subfolder2)) { $subfolder2 = ""; diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php index c8e79bc4..6e08be13 100644 --- a/data/web/inc/vars.inc.php +++ b/data/web/inc/vars.inc.php @@ -228,3 +228,135 @@ $RSPAMD_MAPS = array( 'Monitoring Hosts' => 'monitoring_nolog.map' ) ); + + +$IMAPSYNC_OPTIONS = array( + 'whitelist' => array( + 'log', + 'showpasswords', + 'nossl1', + 'nossl2', + 'ssl2', + 'notls1', + 'notls2', + 'tls2', + 'debugssl', + 'sslargs1', + 'sslargs2', + 'authmech1', + 'authmech2', + 'authuser1', + 'authuser2', + 'proxyauth1', + 'proxyauth2', + 'authmd51', + 'authmd52', + 'domain1', + 'domain2', + 'oauthaccesstoken1', + 'oauthaccesstoken2', + 'oauthdirect1', + 'oauthdirect2', + 'folder', + 'folder', + 'folderrec', + 'folderrec', + 'folderfirst', + 'folderfirst', + 'folderlast', + 'folderlast', + 'nomixfolders', + 'skipemptyfolders', + 'include', + 'include', + 'subfolder1', + 'subscribed', + 'subscribe', + 'prefix1', + 'prefix2', + 'sep1', + 'sep2', + 'nofoldersizesatend', + 'justfoldersizes', + 'pidfile', + 'pidfilelocking', + 'nolog', + 'logfile', + 'logdir', + 'debugcrossduplicates', + 'disarmreadreceipts', + 'truncmess', + 'synclabels', + 'resynclabels', + 'resyncflags', + 'noresyncflags', + 'filterbuggyflags', + 'expunge1', + 'noexpunge1', + 'delete1emptyfolders', + 'delete2folders', + 'noexpunge2', + 'nouidexpunge2', + 'syncinternaldates', + 'idatefromheader', + 'maxsize', + 'minsize', + 'minage', + 'search', + 'search1', + 'search2', + 'noabletosearch', + 'noabletosearch1', + 'noabletosearch2', + 'maxlinelength', + 'useheader', + 'useheader', + 'syncduplicates', + 'usecache', + 'nousecache', + 'useuid', + 'syncacls', + 'nosyncacls', + 'debug', + 'debugfolders', + 'debugcontent', + 'debugflags', + 'debugimap1', + 'debugimap2', + 'debugimap', + 'debugmemory', + 'errorsmax', + 'tests', + 'testslive', + 'testslive6', + 'gmail1', + 'gmail2', + 'office1', + 'office2', + 'exchange1', + 'exchange2', + 'domino1', + 'domino2', + 'keepalive1', + 'keepalive2', + 'maxmessagespersecond', + 'maxbytesafter', + 'maxsleep', + 'abort', + 'exitwhenover', + 'noid', + 'justconnect', + 'justlogin', + 'justfolders' + ), + 'blacklist' => array( + 'skipmess', + 'delete2foldersonly', + 'delete2foldersbutnot', + 'regexflag', + 'regexmess', + 'pipemess', + 'regextrans2', + 'maxlinelengthcmd' + ) +); From 581be02e530496e17065d40550868d1e82e46c8f Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Tue, 14 Jun 2022 15:02:40 +0200 Subject: [PATCH 022/115] [Dovecot] Update to 2.3.19.1 --- data/Dockerfiles/dovecot/Dockerfile | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index 4a914904..4e90052b 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -2,7 +2,7 @@ FROM debian:bullseye-slim LABEL maintainer "Andre Peters " ARG DEBIAN_FRONTEND=noninteractive -ARG DOVECOT=2.3.18 +ARG DOVECOT=2.3.19.1 ENV LC_ALL C ENV GOSU_VERSION 1.14 diff --git a/docker-compose.yml b/docker-compose.yml index e3b08637..09951cb3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -215,7 +215,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.162 + image: mailcow/dovecot:1.17 depends_on: - mysql-mailcow dns: From 3fe776ee6947401b6f42fd3678369b3f75a9ee50 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 14 Jun 2022 18:55:26 +0200 Subject: [PATCH 023/115] Update SOGo to 5.7.0 --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index e3b08637..3208e88b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -168,7 +168,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.108 + image: mailcow/sogo:1.109 environment: - DBNAME=${DBNAME} - DBUSER=${DBUSER} From 537a7908f1a1a70c3ddadc4c4804a26b8e9bbfb4 Mon Sep 17 00:00:00 2001 From: Markus Ritzmann Date: Wed, 15 Jun 2022 15:49:29 +0200 Subject: [PATCH 024/115] Clamd: Fix Docker Healthcheck --- data/Dockerfiles/clamd/Dockerfile | 8 +++++++- data/Dockerfiles/clamd/healthcheck.sh | 9 +++++++++ docker-compose.yml | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100755 data/Dockerfiles/clamd/healthcheck.sh diff --git a/data/Dockerfiles/clamd/Dockerfile b/data/Dockerfiles/clamd/Dockerfile index a4971133..cba56e03 100644 --- a/data/Dockerfiles/clamd/Dockerfile +++ b/data/Dockerfiles/clamd/Dockerfile @@ -8,8 +8,14 @@ RUN apk upgrade --no-cache \ bind-tools \ bash -COPY clamd.sh ./ +# init +COPY clamd.sh /clamd.sh RUN chmod +x /sbin/tini +# healthcheck +COPY healthcheck.sh /healthcheck.sh +RUN chmod +x /healthcheck.sh +HEALTHCHECK --start-period=6m CMD "/healthcheck.sh" + ENTRYPOINT [] CMD ["/sbin/tini", "-g", "--", "/clamd.sh"] \ No newline at end of file diff --git a/data/Dockerfiles/clamd/healthcheck.sh b/data/Dockerfiles/clamd/healthcheck.sh new file mode 100755 index 00000000..6c18ac06 --- /dev/null +++ b/data/Dockerfiles/clamd/healthcheck.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +if [[ "${SKIP_CLAMD}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + echo "SKIP_CLAMD=y, skipping ClamAV..." + exit 0 +fi + +# run clamd healthcheck +/usr/local/bin/clamdcheck.sh diff --git a/docker-compose.yml b/docker-compose.yml index e3b08637..b8276411 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -58,7 +58,7 @@ services: - redis clamd-mailcow: - image: mailcow/clamd:1.52 + image: mailcow/clamd:1.53 restart: always depends_on: - unbound-mailcow From 7166696aa2682617836c254e9243a3120a9243d0 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 16 Jun 2022 12:28:04 +0200 Subject: [PATCH 025/115] Readded Compose Standalone Download --- update.sh | 103 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 65 insertions(+), 38 deletions(-) diff --git a/update.sh b/update.sh index 0b9e12d2..26a09785 100755 --- a/update.sh +++ b/update.sh @@ -44,27 +44,6 @@ for bin in curl docker git awk sha1sum; do if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi done - -echo "checking docker compose version..."; -if docker compose >/dev/null 2>&1; then - echo -e "\e[32mFound Compose v2!\e[0m" - COMPOSE_COMMAND="docker compose" -elif docker-compose version --short | grep -m1 "^2" > /dev/null 2>&1; then - echo -e "\e[32mFound Compose v2!\e[0m" - COMPOSE_COMMAND="docker-compose" -elif docker-compose version --short | grep -m1 "^1" > /dev/null 2>&1; then - echo -e "\e[33mWARN: Your machine is using Docker-Compose v1!\e[0m" - echo -e "\e[33mmailcow will drop the Docker-Compose v1 Support in December 2022\e[0m" - echo -e "\e[33mPlease consider a upgrade to Docker-Compose v2.\e[0m" - echo - echo - echo -e "\e[33mContinuing...\e[0m" - sleep 3 - COMPOSE_COMMAND="docker-compose" -else - echo -e "\e[31mCannot find Docker-Compose v1 or v2 on your System. Please install Docker-Compose v2 and re-run the Script.\e[0m" - exit 1 -fi export LC_ALL=C DATE=$(date +%Y-%m-%d_%H_%M_%S) BRANCH=$(cd ${SCRIPT_DIR}; git rev-parse --abbrev-ref HEAD) @@ -218,6 +197,50 @@ migrate_docker_nat() { fi } +update_compose(){ +if [[ ${NO_UPDATE_COMPOSE} == "y" ]]; then + echo -e "\e[33mNot fetching latest docker-compose, please check for updates manually!\e[0m" + return 0 +elif [[ -e /etc/alpine-release ]]; then + echo -e "\e[33mNot fetching latest docker-compose, because you are using Alpine Linux without glibc support. Please update docker-compose via apk!\e[0m" + return 0 +else + echo -e "\e[32mFetching new docker-compose version...\e[0m" + echo -e "\e[32mTrying to determine GLIBC version...\e[0m" + if ldd --version > /dev/null; then + GLIBC_V=$(ldd --version | grep -E '(GLIBC|GNU libc)' | rev | cut -d ' ' -f1 | rev | cut -d '.' -f2) + if [ ! -z "${GLIBC_V}" ] && [ ${GLIBC_V} -gt 27 ]; then + DC_DL_SUFFIX= + else + DC_DL_SUFFIX=legacy + fi + else + DC_DL_SUFFIX=legacy + fi + sleep 1 + if [[ ! -z $(which pip) && $(pip list --local 2>&1 | grep -v DEPRECATION | grep -c docker-compose) == 1 ]]; then + true + #prevent breaking a working docker-compose installed with pip + elif [[ $(curl -sL -w "%{http_code}" https://www.servercow.de/docker-compose/latest.php?vers=${DC_DL_SUFFIX} -o /dev/null) == "200" ]]; then + LATEST_COMPOSE=$(curl -#L https://www.servercow.de/docker-compose/latest.php) + COMPOSE_VERSION=$(docker-compose version --short) + if [[ "$LATEST_COMPOSE" != "$COMPOSE_VERSION" ]]; then + COMPOSE_PATH=$(which docker-compose) + if [[ -w ${COMPOSE_PATH} ]]; then + curl -#L https://github.com/docker/compose/releases/download/v${LATEST_COMPOSE}/docker-compose-$(uname -s)-$(uname -m) > $COMPOSE_PATH + chmod +x $COMPOSE_PATH + else + echo -e "\e[33mWARNING: $COMPOSE_PATH is not writable, but new version $LATEST_COMPOSE is available (installed: $COMPOSE_VERSION)\e[0m" + return 1 + fi + fi + else + echo -e "\e[33mCannot determine latest docker-compose version, skipping...\e[0m" + return 1 + fi +fi +} + while (($#)); do case "${1}" in --check|-c) @@ -256,6 +279,9 @@ while (($#)); do echo -e "\e[32mRunning in forced mode...\e[0m" FORCE=y ;; + --no-update-compose) + NO_UPDATE_COMPOSE=y + ;; --skip-ping-check) SKIP_PING_CHECK=y ;; @@ -265,6 +291,7 @@ while (($#)); do -c|--check - Check for updates and exit (exit codes => 0: update available, 3: no updates) --ours - Use merge strategy option "ours" to solve conflicts in favor of non-mailcow code (local changes over remote changes), not recommended! --gc - Run garbage collector to delete old image tags + --no-update-compose - Do not update docker-compose --prefetch - Only prefetch new images and exit (useful to prepare updates) --skip-start - Do not start mailcow after update --skip-ping-check - Skip ICMP Check to public DNS resolvers (Use it only if you´ve blocked any ICMP Connections to your mailcow machine). @@ -281,7 +308,7 @@ source mailcow.conf DOTS=${MAILCOW_HOSTNAME//[^.]}; if [ ${#DOTS} -lt 2 ]; then echo "MAILCOW_HOSTNAME (${MAILCOW_HOSTNAME}) is not a FQDN!" - echo "Please change it to a FQDN and run ${COMPOSE_COMMAND} down followed by ${COMPOSE_COMMAND} up -d" + echo "Please change it to a FQDN and run docker-compose down followed by docker-compose up -d" exit 1 fi @@ -571,6 +598,7 @@ echo -e "\e[32mChecking for newer update script...\e[0m" SHA1_1=$(sha1sum update.sh) git fetch origin #${BRANCH} git checkout origin/${BRANCH} update.sh +git checkout origin/${BRANCH} docker-compose.yml SHA1_2=$(sha1sum update.sh) if [[ ${SHA1_1} != ${SHA1_2} ]]; then echo "update.sh changed, please run this script again, exiting." @@ -594,14 +622,16 @@ if [ ! $FORCE ]; then migrate_docker_nat fi +update_compose + echo -e "\e[32mValidating docker-compose stack configuration...\e[0m" -if ! ${COMPOSE_COMMAND} config -q; then +if ! docker-compose config -q; then echo -e "\e[31m\nOh no, something went wrong. Please check the error message above.\e[0m" exit 1 fi echo -e "\e[32mChecking for conflicting bridges...\e[0m" -MAILCOW_BRIDGE=$(${COMPOSE_COMMAND} config | grep -i com.docker.network.bridge.name | cut -d':' -f2) +MAILCOW_BRIDGE=$(docker-compose config | grep -i com.docker.network.bridge.name | cut -d':' -f2) while read NAT_ID; do iptables -t nat -D POSTROUTING $NAT_ID done < <(iptables -L -vn -t nat --line-numbers | grep $IPV4_NETWORK | grep -E 'MASQUERADE.*all' | grep -v ${MAILCOW_BRIDGE} | cut -d' ' -f1) @@ -621,8 +651,8 @@ prefetch_images echo -e "\e[32mStopping mailcow...\e[0m" sleep 2 -MAILCOW_CONTAINERS=($(${COMPOSE_COMMAND} ps -q)) -${COMPOSE_COMMAND} down +MAILCOW_CONTAINERS=($(docker-compose ps -q)) +docker-compose down echo -e "\e[32mChecking for remaining containers...\e[0m" sleep 2 for container in "${MAILCOW_CONTAINERS[@]}"; do @@ -659,16 +689,13 @@ elif [[ ${MERGE_RETURN} == 1 ]]; then elif [[ ${MERGE_RETURN} != 0 ]]; then echo -e "\e[31m\nOh no, something went wrong. Please check the error message above.\e[0m" echo - echo "Run ${COMPOSE_COMMAND} up -d to restart your stack without updates or try again after fixing the mentioned errors." + echo "Run docker-compose up -d to restart your stack without updates or try again after fixing the mentioned errors." exit 1 fi -echo -e "\e[33mNot fetching latest docker-compose, please check for updates manually!\e[0m" -sleep 3 - echo -e "\e[32mFetching new images, if any...\e[0m" sleep 2 -${COMPOSE_COMMAND} pull +docker-compose pull # Fix missing SSL, does not overwrite existing files [[ ! -d data/assets/ssl ]] && mkdir -p data/assets/ssl @@ -723,11 +750,11 @@ else fi if [[ ${SKIP_START} == "y" ]]; then - echo -e "\e[33mNot starting mailcow, please run \"${COMPOSE_COMMAND} up -d --remove-orphans\" to start mailcow.\e[0m" + echo -e "\e[33mNot starting mailcow, please run \"docker-compose up -d --remove-orphans\" to start mailcow.\e[0m" else echo -e "\e[32mStarting mailcow...\e[0m" sleep 2 - ${COMPOSE_COMMAND} up -d --remove-orphans + docker-compose up -d --remove-orphans fi echo -e "\e[32mCollecting garbage...\e[0m" @@ -738,8 +765,8 @@ if [ -f "${SCRIPT_DIR}/post_update_hook.sh" ]; then bash "${SCRIPT_DIR}/post_update_hook.sh" fi -#echo "In case you encounter any problem, hard-reset to a state before updating mailcow:" -#echo -#git reflog --color=always | grep "Before update on " -#echo -#echo "Use \"git reset --hard hash-on-the-left\" and run ${COMPOSE_COMMAND} up -d afterwards." \ No newline at end of file +# echo "In case you encounter any problem, hard-reset to a state before updating mailcow:" +# echo +# git reflog --color=always | grep "Before update on " +# echo +# echo "Use \"git reset --hard hash-on-the-left\" and run docker-compose up -d afterwards." \ No newline at end of file From a139eb9bce925e66a99639efd4b1027e020ac26a Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 16 Jun 2022 12:38:06 +0200 Subject: [PATCH 026/115] Readded Dual Stack availability of WebUI (default) --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e3b08637..a563df91 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -377,8 +377,8 @@ services: - ./data/conf/rspamd/meta_exporter:/meta_exporter:ro,z - sogo-web-vol-1:/usr/lib/GNUstep/SOGo/ ports: - - "${HTTPS_BIND:-0.0.0.0}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}" - - "${HTTP_BIND:-0.0.0.0}:${HTTP_PORT:-80}:${HTTP_PORT:-80}" + - "${HTTPS_BIND:-}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}" + - "${HTTP_BIND:-}:${HTTP_PORT:-80}:${HTTP_PORT:-80}" restart: always networks: mailcow-network: From 36e4ee7738570f7598f227a864bf852e58ac789b Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 16 Jun 2022 12:41:49 +0200 Subject: [PATCH 027/115] Before update on 2022-06-16_12_41_05 --- docker-compose.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a563df91..753901aa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -580,36 +580,6 @@ services: aliases: - ofelia - ipv6nat-mailcow: - depends_on: - - unbound-mailcow - - mysql-mailcow - - redis-mailcow - - clamd-mailcow - - rspamd-mailcow - - php-fpm-mailcow - - sogo-mailcow - - dovecot-mailcow - - postfix-mailcow - - memcached-mailcow - - nginx-mailcow - - acme-mailcow - - netfilter-mailcow - - watchdog-mailcow - - dockerapi-mailcow - - solr-mailcow - environment: - - TZ=${TZ} - image: robbertkl/ipv6nat - security_opt: - - label=disable - restart: always - privileged: true - network_mode: "host" - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - /lib/modules:/lib/modules:ro - networks: mailcow-network: driver: bridge From 872fa072131873a8482bbedf049caf186f3a0ea1 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 16 Jun 2022 12:49:17 +0200 Subject: [PATCH 028/115] Restore docker-compose check in scripts --- generate_config.sh | 21 +--------- helper-scripts/_cold-standby.sh | 57 +--------------------------- helper-scripts/backup_and_restore.sh | 26 +++---------- 3 files changed, 9 insertions(+), 95 deletions(-) diff --git a/generate_config.sh b/generate_config.sh index 800f0b53..de285178 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -25,29 +25,10 @@ if cp --help 2>&1 | grep -q -i "busybox"; then exit 1 fi -for bin in openssl curl docker git awk sha1sum; do +for bin in openssl curl docker docker-compose git awk sha1sum; do if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi done -echo "checking docker compose version..."; -if docker compose >/dev/null 2>&1; then - echo -e "\e[32mFound Compose v2!\e[0m" -elif docker-compose version --short | grep -m1 "^2" > /dev/null 2>&1; then - echo -e "\e[32mFound Compose v2!\e[0m" - COMPOSE_COMMAND="docker-compose" -elif docker-compose version --short | grep -m1 "^1" > /dev/null 2>&1; then - echo -e "\e[33mWARN: Your machine is using Docker-Compose v1!\e[0m" - echo -e "\e[33mmailcow will drop the Docker-Compose v1 Support in December 2022\e[0m" - echo -e "\e[33mPlease consider a upgrade to Docker-Compose v2.\e[0m" - echo - echo - echo -e "\e[33mContinuing...\e[0m" - sleep 3 -else - echo -e "\e[31mCannot find Docker-Compose v1 or v2 on your System. Please install Docker-Compose v2 and re-run the Script.\e[0m" - exit 1 -fi - if [ -f mailcow.conf ]; then read -r -p "A config file exists and will be overwritten, are you sure you want to continue? [y/N] " response case $response in diff --git a/helper-scripts/_cold-standby.sh b/helper-scripts/_cold-standby.sh index 14200954..b71d85cd 100755 --- a/helper-scripts/_cold-standby.sh +++ b/helper-scripts/_cold-standby.sh @@ -77,33 +77,13 @@ function preflight_local_checks() { exit 1 fi - for bin in rsync docker grep cut; do + for bin in rsync docker docker-compose grep cut; do if [[ -z $(which ${bin}) ]]; then >&2 echo -e "\e[31mCannot find ${bin} in local PATH, exiting...\e[0m" exit 1 fi done - - echo "checking docker compose version..."; - if docker compose >/dev/null 2>&1; then - echo -e "\e[32mFound Compose v2 on local machine!\e[0m" - elif docker-compose version --short | grep -m1 "^2" > /dev/null 2>&1; then - echo -e "\e[32mFound Compose v2!\e[0m" - COMPOSE_COMMAND="docker-compose" - elif docker-compose version --short | grep -m1 "^1" > /dev/null 2>&1; then - echo -e "\e[33mWARN: Your machine is using Docker-Compose v1!\e[0m" - echo -e "\e[33mmailcow will drop the Docker-Compose v1 Support in December 2022\e[0m" - echo -e "\e[33mPlease consider a upgrade to Docker-Compose v2.\e[0m" - echo - echo - echo -e "\e[33mContinuing...\e[0m" - sleep 3 - else - echo -e "\e[31mCannot find Docker-Compose v1 or v2 on your System. Please install Docker-Compose v2 and re-run the Script.\e[0m" - exit 1 - fi - if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "\e[31mBusyBox grep detected on local system, please install GNU grep\e[0m" exit 1 @@ -131,7 +111,7 @@ function preflight_remote_checks() { exit 1 fi - for bin in rsync docker; do + for bin in rsync docker docker-compose; do if ! ssh -o StrictHostKeyChecking=no \ -i "${REMOTE_SSH_KEY}" \ ${REMOTE_SSH_HOST} \ @@ -141,39 +121,6 @@ function preflight_remote_checks() { exit 1 fi done - - echo "checking docker compose version on remote..."; - if ssh -q -o StrictHostKeyChecking=no \ - -i "${REMOTE_SSH_KEY}" \ - ${REMOTE_SSH_HOST} \ - -p ${REMOTE_SSH_PORT} \ - -t 'docker compose' >/dev/null 2>&1; then - echo -e "\e[32mFound Compose v2 on remote!\e[0m" - COMPOSE_COMMAND="docker compose" - elif ssh -q -o StrictHostKeyChecking=no \ - -i "${REMOTE_SSH_KEY}" \ - ${REMOTE_SSH_HOST} \ - -p ${REMOTE_SSH_PORT} \ - -t 'docker-compose version --short' | grep -m1 "^2" > /dev/null 2>&1; then - echo -e "\e[32mFound Compose v2!\e[0m" - COMPOSE_COMMAND="docker-compose" - elif ssh -q -o StrictHostKeyChecking=no \ - -i "${REMOTE_SSH_KEY}" \ - ${REMOTE_SSH_HOST} \ - -p ${REMOTE_SSH_PORT} \ - -t 'docker-compose version --short' | grep -m1 "^1" > /dev/null 2>&1; then - echo -e "\e[33mWARN: The remote is using Docker-Compose v1!\e[0m" - echo -e "\e[33mmailcow will drop the Docker-Compose v1 Support in December 2022\e[0m" - echo -e "\e[33mPlease consider a upgrade to Docker-Compose v2 on remote.\e[0m" - echo - echo - echo -e "\e[33mContinuing...\e[0m" - sleep 3 - COMPOSE_COMMAND="docker-compose" - else - echo -e "\e[31mCannot find Docker-Compose v1 or v2 on the Remote Machine! Please install Docker-Compose v2 on that and re-run the script.\e[0m" - exit 1 - fi } preflight_local_checks diff --git a/helper-scripts/backup_and_restore.sh b/helper-scripts/backup_and_restore.sh index 8136def3..b45fcb97 100755 --- a/helper-scripts/backup_and_restore.sh +++ b/helper-scripts/backup_and_restore.sh @@ -76,26 +76,12 @@ else CMPS_PRJ=$(echo ${COMPOSE_PROJECT_NAME} | tr -cd "[0-9A-Za-z-_]") fi -echo "checking docker compose version..."; -if docker compose >/dev/null 2>&1; then - echo -e "\e[32mFound Compose v2!\e[0m" - COMPOSE_COMMAND="docker compose" -elif docker-compose version --short | grep -m1 "^2" > /dev/null 2>&1; then - echo -e "\e[32mFound Compose v2!\e[0m" - COMPOSE_COMMAND="docker-compose" -elif docker-compose version --short | grep -m1 "^1" > /dev/null 2>&1; then - echo -e "\e[33mWARN: Your machine is using Docker-Compose v1!\e[0m" - echo -e "\e[33mmailcow will drop the Docker-Compose v1 Support in December 2022\e[0m" - echo -e "\e[33mPlease consider a upgrade to Docker-Compose v2.\e[0m" - echo - echo - echo -e "\e[33mContinuing...\e[0m" - sleep 3 - COMPOSE_COMMAND="docker-compose" -else - echo -e "\e[31mCannot find Docker-Compose v1 or v2 on your System. Please install Docker-Compose v2 and re-run the Script.\e[0m" - exit 1 -fi +for bin in docker docker-compose; do + if [[ -z $(which ${bin}) ]]; then + >&2 echo -e "\e[31mCannot find ${bin} in local PATH, exiting...\e[0m" + exit 1 + fi +done if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then >&2 echo -e "\e[31mBusyBox grep detected on local system, please install GNU grep\e[0m" From 89cea31475720a19c366fc31e620ff11525ec47b Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 16 Jun 2022 12:51:51 +0200 Subject: [PATCH 029/115] Revert "Before update on 2022-06-16_12_41_05" This reverts commit 36e4ee7738570f7598f227a864bf852e58ac789b. --- docker-compose.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 753901aa..a563df91 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -580,6 +580,36 @@ services: aliases: - ofelia + ipv6nat-mailcow: + depends_on: + - unbound-mailcow + - mysql-mailcow + - redis-mailcow + - clamd-mailcow + - rspamd-mailcow + - php-fpm-mailcow + - sogo-mailcow + - dovecot-mailcow + - postfix-mailcow + - memcached-mailcow + - nginx-mailcow + - acme-mailcow + - netfilter-mailcow + - watchdog-mailcow + - dockerapi-mailcow + - solr-mailcow + environment: + - TZ=${TZ} + image: robbertkl/ipv6nat + security_opt: + - label=disable + restart: always + privileged: true + network_mode: "host" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - /lib/modules:/lib/modules:ro + networks: mailcow-network: driver: bridge From 6612b892b77c8a255580c3554eaca671dc169604 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 16 Jun 2022 12:56:54 +0200 Subject: [PATCH 030/115] Removed extra checkout line --- update.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/update.sh b/update.sh index 26a09785..c46751a5 100755 --- a/update.sh +++ b/update.sh @@ -597,8 +597,7 @@ fi echo -e "\e[32mChecking for newer update script...\e[0m" SHA1_1=$(sha1sum update.sh) git fetch origin #${BRANCH} -git checkout origin/${BRANCH} update.sh -git checkout origin/${BRANCH} docker-compose.yml +git checkout origin/${BRANCH} update.sh docker-compose.yml SHA1_2=$(sha1sum update.sh) if [[ ${SHA1_1} != ${SHA1_2} ]]; then echo "update.sh changed, please run this script again, exiting." From 499273dbb757a6420f8d352c8f1ac0a8323f454b Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 16 Jun 2022 13:50:44 +0200 Subject: [PATCH 031/115] Readded docker-compose check --- update.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update.sh b/update.sh index c46751a5..3de7e561 100755 --- a/update.sh +++ b/update.sh @@ -40,7 +40,7 @@ PATH=$PATH:/opt/bin umask 0022 -for bin in curl docker git awk sha1sum; do +for bin in curl docker docker-compose git awk sha1sum; do if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi done From dd6b8c44a4e71b0d59e2d90573a2b8c667cbb403 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 16 Jun 2022 14:13:08 +0200 Subject: [PATCH 032/115] Added automatic docker-compose standalone installation --- update.sh | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/update.sh b/update.sh index 3de7e561..dc47c3de 100755 --- a/update.sh +++ b/update.sh @@ -40,8 +40,20 @@ PATH=$PATH:/opt/bin umask 0022 -for bin in curl docker docker-compose git awk sha1sum; do - if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi +for bin in curl docker git awk sha1sum; do + if [[ -z $(which ${bin}) ]]; then + echo "Cannot find ${bin}, exiting..." + exit 1; + elif [[ -z $(which docker-compose) ]]; then + echo "Cannot find docker-compose Standalone. Installing..." + sleep 3 + if [[ -e /etc/alpine-release ]]; then + echo -e "\e[33mNot installing latest docker-compose, because you are using Alpine Linux without glibc support. Install docker-compose via apk!\e[0m" + exit 1 + fi + curl -#L https://github.com/docker/compose/releases/download/v$(curl -Ls https://www.servercow.de/docker-compose/latest.php)/docker-compose-$(uname -s)-$(uname -m) > /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose + fi done export LC_ALL=C From af9c3a8565a40ade25909e86a5da09c7ab34fd31 Mon Sep 17 00:00:00 2001 From: milkmaker Date: Sun, 19 Jun 2022 22:23:35 +0200 Subject: [PATCH 033/115] [Web] Updated lang.es.json [CI SKIP] (#4638) Co-authored-by: Daniel Castellanos Co-authored-by: Daniel Castellanos --- data/web/lang/lang.es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/web/lang/lang.es.json b/data/web/lang/lang.es.json index 9a67c3c6..ba12202f 100644 --- a/data/web/lang/lang.es.json +++ b/data/web/lang/lang.es.json @@ -19,7 +19,8 @@ "syncjobs": "Trabajos de sincronización", "tls_policy": "Póliza de TLS", "unlimited_quota": "Cuota ilimitada para buzones", - "app_passwds": "Gestionar las contraseñas de aplicaciones" + "app_passwds": "Gestionar las contraseñas de aplicaciones", + "domain_desc": "Cambiar descripción del dominio" }, "add": { "activate_filter_warn": "Todos los demás filtros se desactivarán cuando este filtro se active.", From cd7715fa0e2779fecbf82a985c25e8e74603be25 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 20 Jun 2022 10:32:14 +0200 Subject: [PATCH 034/115] Restore docker-compose check in scripts --- helper-scripts/_cold-standby.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper-scripts/_cold-standby.sh b/helper-scripts/_cold-standby.sh index b71d85cd..7fc5a495 100755 --- a/helper-scripts/_cold-standby.sh +++ b/helper-scripts/_cold-standby.sh @@ -258,7 +258,7 @@ echo "OK" -i "${REMOTE_SSH_KEY}" \ ${REMOTE_SSH_HOST} \ -p ${REMOTE_SSH_PORT} \ - $COMPOSE_COMMAND -f "${SCRIPT_DIR}/../docker-compose.yml" pull --no-parallel --quiet 2>&1 ; then + docker-compose -f "${SCRIPT_DIR}/../docker-compose.yml" pull --no-parallel --quiet 2>&1 ; then >&2 echo -e "\e[31m[ERR]\e[0m - Could not pull images on remote" fi From d373164e13a14e058f82c9f1918a5612f375a9f9 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 20 Jun 2022 21:18:57 +0200 Subject: [PATCH 035/115] hotfix imapsync --- data/web/inc/init_db.inc.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 88be5bca..1e53d4b8 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -3,7 +3,7 @@ function init_db_schema() { try { global $pdo; - $db_version = "20052022_0938"; + $db_version = "18062022_1153"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -440,7 +440,7 @@ function init_db_schema() { "spam_score" => "TINYINT(1) NOT NULL DEFAULT '1'", "spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", "delimiter_action" => "TINYINT(1) NOT NULL DEFAULT '1'", - "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'", + "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '0'", "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'", "sogo_profile_reset" => "TINYINT(1) NOT NULL DEFAULT '0'", "pushover" => "TINYINT(1) NOT NULL DEFAULT '1'", @@ -1228,8 +1228,17 @@ function init_db_schema() { } // Mitigate imapsync pipemess issue - $pdo->query("UPDATE `imapsync` SET `custom_params` = '' WHERE `custom_params` LIKE '%pipemess%' OR `custom_params` LIKE '%pipemes%';"); - + $pdo->query("UPDATE `imapsync` SET `custom_params` = '' + WHERE `custom_params` LIKE '%pipemess%' + OR custom_params LIKE '%skipmess%' + OR custom_params LIKE '%delete2foldersonly%' + OR custom_params LIKE '%delete2foldersbutnot%' + OR custom_params LIKE '%regexflag%' + OR custom_params LIKE '%pipemess%' + OR custom_params LIKE '%regextrans2%' + OR custom_params LIKE '%maxlinelengthcmd%';"); + + // Migrate webauthn tfa $stmt = $pdo->query("ALTER TABLE `tfa` MODIFY COLUMN `authmech` ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')"); From 452daf5d5eca1ba95d7e5befadf0ca8ab0dcab12 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 23 Jun 2022 08:55:06 +0200 Subject: [PATCH 036/115] Restored docker-compose Command --- helper-scripts/backup_and_restore.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helper-scripts/backup_and_restore.sh b/helper-scripts/backup_and_restore.sh index b45fcb97..d6f11ca8 100755 --- a/helper-scripts/backup_and_restore.sh +++ b/helper-scripts/backup_and_restore.sh @@ -239,7 +239,7 @@ function restore() { continue else echo "Stopping mailcow..." - ${COMPOSE_COMMAND} -f ${COMPOSE_FILE} --env-file ${ENV_FILE} down + docker-compose -f ${COMPOSE_FILE} --env-file ${ENV_FILE} down fi #docker stop $(docker ps -qf name=mysql-mailcow) if [[ -d "${RESTORE_LOCATION}/mysql" ]]; then @@ -277,7 +277,7 @@ function restore() { sed -i --follow-symlinks "/DBROOT/c\DBROOT=${DBROOT}" ${SCRIPT_DIR}/../mailcow.conf source ${SCRIPT_DIR}/../mailcow.conf echo "Starting mailcow..." - ${COMPOSE_COMMAND} -f ${COMPOSE_FILE} --env-file ${ENV_FILE} up -d + docker-compose -f ${COMPOSE_FILE} --env-file ${ENV_FILE} up -d #docker start $(docker ps -aqf name=mysql-mailcow) fi ;; From db7d7ea288ca1ea85959890616dd1a694452acd2 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 23 Jun 2022 10:05:09 +0200 Subject: [PATCH 037/115] Added override NGINX Port Removal --- update.sh | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/update.sh b/update.sh index dc47c3de..104ef38e 100755 --- a/update.sh +++ b/update.sh @@ -209,6 +209,26 @@ migrate_docker_nat() { fi } +remove_obsolete_nginx_ports() { + # Removing obsolete docker-compose.override.yml + if [ -s docker-compose.override.* ]; then + if cat docker-compose.override.* | grep nginx-mailcow > /dev/null 2>&1; then + if cat docker-compose.override.* | grep -w [::] > /dev/null 2>&1; then + if cat docker-compose.override.* | grep -w 80:80 > /dev/null 2>&1 && cat docker-compose.override.* | grep -w 443:443 > /dev/null 2>&1 ; then + sed -i '/nginx-mailcow:$/,/^$/d' docker-compose.override.* + if [[ "$(cat docker-compose.override.yml | sed '/^\s*$/d' | wc -l)" == "2" ]]; then + mv docker-compose.override.yml docker-compose.override.yml_backup + echo -e "\e[31mRemoved obsolete NGINX IPv6 Bind from override File.\e[0m" + elif [[ "$(cat docker-compose.override.yaml | sed '/^\s*$/d' | wc -l)" == "2" ]]; then + mv docker-compose.override.yaml docker-compose.override.yaml_backup + echo -e "\e[31mRemoved obsolete NGINX IPv6 Bind from override File.\e[0m" + fi + fi + fi + fi + fi +} + update_compose(){ if [[ ${NO_UPDATE_COMPOSE} == "y" ]]; then echo -e "\e[33mNot fetching latest docker-compose, please check for updates manually!\e[0m" @@ -635,6 +655,8 @@ fi update_compose +remove_obsolete_nginx_ports + echo -e "\e[32mValidating docker-compose stack configuration...\e[0m" if ! docker-compose config -q; then echo -e "\e[31m\nOh no, something went wrong. Please check the error message above.\e[0m" From 092890b6abca7e195d0742fbf644119c57439a70 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 23 Jun 2022 10:23:48 +0200 Subject: [PATCH 038/115] Changed message in nginx-port removal function --- update.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/update.sh b/update.sh index 104ef38e..9836b927 100755 --- a/update.sh +++ b/update.sh @@ -216,12 +216,13 @@ remove_obsolete_nginx_ports() { if cat docker-compose.override.* | grep -w [::] > /dev/null 2>&1; then if cat docker-compose.override.* | grep -w 80:80 > /dev/null 2>&1 && cat docker-compose.override.* | grep -w 443:443 > /dev/null 2>&1 ; then sed -i '/nginx-mailcow:$/,/^$/d' docker-compose.override.* + echo -e "\e[32mRemoved obsolete NGINX IPv6 Bind from override File.\e[0m" if [[ "$(cat docker-compose.override.yml | sed '/^\s*$/d' | wc -l)" == "2" ]]; then mv docker-compose.override.yml docker-compose.override.yml_backup - echo -e "\e[31mRemoved obsolete NGINX IPv6 Bind from override File.\e[0m" + echo -e "\e[31mdocker-compose.override.yml is empty. Renamed it to ensure mailcow is startable.\e[0m" elif [[ "$(cat docker-compose.override.yaml | sed '/^\s*$/d' | wc -l)" == "2" ]]; then mv docker-compose.override.yaml docker-compose.override.yaml_backup - echo -e "\e[31mRemoved obsolete NGINX IPv6 Bind from override File.\e[0m" + echo -e "\e[31mdocker-compose.override.yml is empty. Renamed it to ensure mailcow is startable.\e[0m" fi fi fi From 263baa81c07b2a3546ee6301d58eba511a8747fc Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 23 Jun 2022 10:55:12 +0200 Subject: [PATCH 039/115] Improved .yml and .yaml check for Port Removal --- update.sh | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/update.sh b/update.sh index 9836b927..714541aa 100755 --- a/update.sh +++ b/update.sh @@ -211,23 +211,22 @@ migrate_docker_nat() { remove_obsolete_nginx_ports() { # Removing obsolete docker-compose.override.yml - if [ -s docker-compose.override.* ]; then - if cat docker-compose.override.* | grep nginx-mailcow > /dev/null 2>&1; then - if cat docker-compose.override.* | grep -w [::] > /dev/null 2>&1; then - if cat docker-compose.override.* | grep -w 80:80 > /dev/null 2>&1 && cat docker-compose.override.* | grep -w 443:443 > /dev/null 2>&1 ; then - sed -i '/nginx-mailcow:$/,/^$/d' docker-compose.override.* - echo -e "\e[32mRemoved obsolete NGINX IPv6 Bind from override File.\e[0m" - if [[ "$(cat docker-compose.override.yml | sed '/^\s*$/d' | wc -l)" == "2" ]]; then - mv docker-compose.override.yml docker-compose.override.yml_backup - echo -e "\e[31mdocker-compose.override.yml is empty. Renamed it to ensure mailcow is startable.\e[0m" - elif [[ "$(cat docker-compose.override.yaml | sed '/^\s*$/d' | wc -l)" == "2" ]]; then - mv docker-compose.override.yaml docker-compose.override.yaml_backup - echo -e "\e[31mdocker-compose.override.yml is empty. Renamed it to ensure mailcow is startable.\e[0m" + for override in docker-compose.override.yml docker-compose.override.yaml; do + if [ -s $override ] ; then + if cat $override | grep nginx-mailcow > /dev/null 2>&1; then + if cat $override | grep -w [::] > /dev/null 2>&1; then + if cat $override | grep -w 80:80 > /dev/null 2>&1 && cat $override | grep -w 443:443 > /dev/null 2>&1 ; then + sed -i '/nginx-mailcow:$/,/^$/d' $override + echo -e "\e[33mRemoved obsolete NGINX IPv6 Bind from override File.\e[0m" + if [[ "$(cat $override | sed '/^\s*$/d' | wc -l)" == "2" ]]; then + mv $override ${override}_backup + echo -e "\e[31m${override} is empty. Renamed it to ensure mailcow is startable.\e[0m" fi fi fi fi - fi + fi + done } update_compose(){ From c1c7167acef67aed73b7af388a88cb75868dd500 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 23 Jun 2022 15:41:48 +0200 Subject: [PATCH 040/115] Added auto correction of composev1 Binds in compose.yml --- update.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/update.sh b/update.sh index 714541aa..6d91becc 100755 --- a/update.sh +++ b/update.sh @@ -629,7 +629,7 @@ fi echo -e "\e[32mChecking for newer update script...\e[0m" SHA1_1=$(sha1sum update.sh) git fetch origin #${BRANCH} -git checkout origin/${BRANCH} update.sh docker-compose.yml +git checkout origin/${BRANCH} update.sh SHA1_2=$(sha1sum update.sh) if [[ ${SHA1_1} != ${SHA1_2} ]]; then echo "update.sh changed, please run this script again, exiting." @@ -658,6 +658,8 @@ update_compose remove_obsolete_nginx_ports echo -e "\e[32mValidating docker-compose stack configuration...\e[0m" +sed -i 's/HTTPS_BIND:-:/HTTPS_BIND:-/g' docker-compose.yml +sed -i 's/HTTP_BIND:-:/HTTP_BIND:-/g' docker-compose.yml if ! docker-compose config -q; then echo -e "\e[31m\nOh no, something went wrong. Please check the error message above.\e[0m" exit 1 From a835419168394bcbefdafb03de50fa9e6187717a Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Thu, 23 Jun 2022 18:36:54 +0200 Subject: [PATCH 041/115] fix imapsync --- data/web/inc/functions.mailbox.inc.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index d6a07eab..788b207f 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -339,6 +339,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { // validate custom params foreach (explode(' -', $custom_params) as $param){ + if(empty($param)) continue; + if (str_contains($param, ' ')) { // bad char $_SESSION['return'][] = array( @@ -1792,6 +1794,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { // validate custom params foreach (explode(' -', $custom_params) as $param){ + if(empty($param)) continue; + if (str_contains($param, ' ')) { // bad char $_SESSION['return'][] = array( From 33eb2c8801c86952ec8e89a4c041c55849ae2485 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Fri, 24 Jun 2022 23:10:00 +0200 Subject: [PATCH 042/115] Improved [::] Section check + included prior override backup --- update.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/update.sh b/update.sh index 6d91becc..ea283eb2 100755 --- a/update.sh +++ b/update.sh @@ -214,10 +214,14 @@ remove_obsolete_nginx_ports() { for override in docker-compose.override.yml docker-compose.override.yaml; do if [ -s $override ] ; then if cat $override | grep nginx-mailcow > /dev/null 2>&1; then - if cat $override | grep -w [::] > /dev/null 2>&1; then + if cat $override | grep -E '(\[::])' > /dev/null 2>&1; then if cat $override | grep -w 80:80 > /dev/null 2>&1 && cat $override | grep -w 443:443 > /dev/null 2>&1 ; then + echo -e "\e[33mBacking up ${override} to preserve custom changes...\e[0m" + echo -e "\e[33m!!! Manual Merge needed (if other overrides are set) !!!\e[0m" + sleep 3 + cp $override ${override}_backup sed -i '/nginx-mailcow:$/,/^$/d' $override - echo -e "\e[33mRemoved obsolete NGINX IPv6 Bind from override File.\e[0m" + echo -e "\e[33mRemoved obsolete NGINX IPv6 Bind from original override File.\e[0m" if [[ "$(cat $override | sed '/^\s*$/d' | wc -l)" == "2" ]]; then mv $override ${override}_backup echo -e "\e[31m${override} is empty. Renamed it to ensure mailcow is startable.\e[0m" From 6fb967cf79209ab4e143fe24e9e5461993ba68be Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 4 Jul 2022 17:01:35 +0200 Subject: [PATCH 043/115] extra tfa register debugging --- data/web/templates/base.twig | 1 + 1 file changed, 1 insertion(+) diff --git a/data/web/templates/base.twig b/data/web/templates/base.twig index 945d4501..37c27e21 100644 --- a/data/web/templates/base.twig +++ b/data/web/templates/base.twig @@ -415,6 +415,7 @@ function recursiveBase64StrToArrayBuffer(obj) { window.fetch("/api/v1/get/webauthn-tfa-registration/{{ mailcow_cc_username|url_encode(true)|default('null') }}", {method:'GET',cache:'no-cache'}).then(response => { return response.json(); }).then(json => { + console.log(json); if (json.success === false) throw new Error(json.msg); recursiveBase64StrToArrayBuffer(json); From 52e92cc0db159f6e576401c4959a1240a7d945f8 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 4 Jul 2022 17:17:31 +0200 Subject: [PATCH 044/115] fix sql query for tfa registration --- data/web/json_api.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/data/web/json_api.php b/data/web/json_api.php index 0ebc95bc..22b747bc 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -430,8 +430,11 @@ if (isset($_GET['query'])) { case "webauthn-tfa-registration": if (isset($_SESSION["mailcow_cc_role"])) { // Exclude existing CredentialIds, if any - $stmt = $pdo->prepare("SELECT `keyHandle` FROM `tfa` WHERE username = :username"); - $stmt->execute(array(':username' => $_SESSION['mailcow_cc_username'])); + $stmt = $pdo->prepare("SELECT `keyHandle` FROM `tfa` WHERE username = :username AND authmech = :authmech"); + $stmt->execute(array( + ':username' => $_SESSION['mailcow_cc_username'], + ':authmech' => 'webauthn' + )); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { $excludeCredentialIds[] = base64_decode($row['keyHandle']); From 14bc105d4386e87ce40763a4d55ffd427b744239 Mon Sep 17 00:00:00 2001 From: Rafael Kraut Date: Tue, 5 Jul 2022 11:51:05 +0200 Subject: [PATCH 045/115] [Web] Remove default selection for sync job target mailbox (#4661) + Don't cache that form, closes #4642 --- data/web/templates/modals/mailbox.twig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/templates/modals/mailbox.twig b/data/web/templates/modals/mailbox.twig index 4097145f..17e06ed2 100644 --- a/data/web/templates/modals/mailbox.twig +++ b/data/web/templates/modals/mailbox.twig @@ -435,11 +435,11 @@ diff --git a/update.sh b/update.sh index 7d47b3cc..2d8ed95a 100755 --- a/update.sh +++ b/update.sh @@ -873,11 +873,12 @@ fi # Set app_info.inc.php if [ ${BRANCH} == "master" ]; then mailcow_git_version=$(git describe --tags `git rev-list --tags --max-count=1`) - mailcow_last_git_version="" elif [ ${BRANCH} == "nightly" ]; then mailcow_git_version=$(git rev-parse --short HEAD) + mailcow_last_git_version="" else mailcow_git_version=$(git rev-parse --short HEAD) + mailcow_last_git_version="" fi mailcow_git_commit=$(git rev-parse HEAD) From 77f9947613fa3aac9ac772574495a0ee50a3c91f Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Wed, 24 Aug 2022 14:37:00 +0200 Subject: [PATCH 099/115] Readded footer + vars. --- data/web/inc/footer.inc.php | 9 ++++++++- data/web/templates/base.twig | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php index b2f1d4d5..61d81dff 100644 --- a/data/web/inc/footer.inc.php +++ b/data/web/inc/footer.inc.php @@ -48,7 +48,14 @@ if (isset($pending_tfa_authmechs['u2f'])) { $globalVariables = [ 'mailcow_info' => array( 'version_tag' => $GLOBALS['MAILCOW_GIT_VERSION'], - 'git_project_url' => $GLOBALS['MAILCOW_GIT_URL'] + 'last_version_tag' => $GLOBALS['MAILCOW_LAST_GIT_VERSION'], + 'git_owner' => $GLOBALS['MAILCOW_GIT_OWNER'], + 'git_repo' => $GLOBALS['MAILCOW_GIT_REPO'], + 'git_project_url' => $GLOBALS['MAILCOW_GIT_URL'], + 'git_commit' => $GLOBALS['MAILCOW_GIT_COMMIT'], + 'git_commit_date' => $GLOBALS['MAILCOW_GIT_COMMIT_DATE'], + 'mailcow_branch' => $GLOBALS['MAILCOW_BRANCH'], + 'updated_at' => $GLOBALS['MAILCOW_UPDATEDAT'] ), 'js_path' => '/cache/'.basename($JSPath), 'pending_tfa_methods' => @$_SESSION['pending_tfa_methods'], diff --git a/data/web/templates/base.twig b/data/web/templates/base.twig index ba0810a1..e97199b3 100644 --- a/data/web/templates/base.twig +++ b/data/web/templates/base.twig @@ -477,14 +477,14 @@ function recursiveBase64StrToArrayBuffer(obj) { {% if ui_texts.ui_footer %}
{{ ui_texts.ui_footer|rot13|raw }} {% endif %} - {% if mailcow_cc_username and mailcow_info.mailcow_build|lower == "stable" and mailcow_info.version_tag|default %} + {% if mailcow_cc_username and mailcow_info.mailcow_branch|lower == "master" and mailcow_info.version_tag|default %} 🐮 + 🐋 = 💕 Version: {{ mailcow_info.version_tag }} {% endif %} - {% if mailcow_cc_username and mailcow_info.mailcow_build|lower == "nightly" and mailcow_info.version_tag|default %} + {% if mailcow_cc_username and mailcow_info.mailcow_branch|lower == "origin/feature/nightly-editions" and mailcow_info.version_tag|default %} 🛠️🐮 + 🐋 = 💕 Nightly: {{ mailcow_info.version_tag }} From 4f7ee669d3c3fc13829f629a392215abd899c35f Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Wed, 24 Aug 2022 14:42:31 +0200 Subject: [PATCH 100/115] Added missing ;then in update.sh --- update.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/update.sh b/update.sh index 2d8ed95a..4e277e26 100755 --- a/update.sh +++ b/update.sh @@ -658,7 +658,7 @@ else fi fi -if [ $NEW_BRANCH != "master" ] || [ $NEW_BRANCH != "nightly"] +if [ $NEW_BRANCH != "master" ] || [ $NEW_BRANCH != "nightly"]; then echo -e "\e[33mDetecting which build your mailcow runs on...\e[0m" sleep 1 if [ ${BRANCH} == "master" ]; then @@ -676,7 +676,7 @@ if [ $NEW_BRANCH != "master" ] || [ $NEW_BRANCH != "nightly"] echo -e "\e[33mThe mailcow stack might still work but it is recommended to switch to the master branch (stable builds).\e[0m" echo -e "\e[33mTo change that run the update.sh Script one time with the --stable parameter to switch to stable builds.\e[0m" fi -elif [ $NEW_BRANCH == "master" ] && [ $CURRENT_BRANCH != "master" ] +elif [ $NEW_BRANCH == "master" ] && [ $CURRENT_BRANCH != "master" ]; then echo -e "\e[33mYou are about to switch your mailcow Updates to the stable (master) branch.\e[0m" sleep 1 echo -e "\e[33mBefore you do: Please take a backup of all components to ensure that no Data is lost...\e[0m" @@ -704,7 +704,7 @@ elif [ $NEW_BRANCH == "master" ] && [ $CURRENT_BRANCH != "master" ] git fetch origin --all git checkout -f origin/${BRANCH} -elif [ $NEW_BRANCH == "nightly" ] && [ $CURRENT_BRANCH != "nightly" ] +elif [ $NEW_BRANCH == "nightly" ] && [ $CURRENT_BRANCH != "nightly" ]; then echo -e "\e[33mYou are about to switch your mailcow Updates to the unstable (nightly) branch.\e[0m" sleep 1 echo -e "\e[33mBefore you do: Please take a backup of all components to ensure that no Data is lost...\e[0m" From 9db9818edea9ba7680aef32267d91e57a6ff0895 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Wed, 24 Aug 2022 15:00:39 +0200 Subject: [PATCH 101/115] Moved Force Mode check in prio --- update.sh | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/update.sh b/update.sh index 4e277e26..6b19dd1d 100755 --- a/update.sh +++ b/update.sh @@ -658,7 +658,7 @@ else fi fi -if [ $NEW_BRANCH != "master" ] || [ $NEW_BRANCH != "nightly"]; then +if ! [ $NEW_BRANCH ]; then echo -e "\e[33mDetecting which build your mailcow runs on...\e[0m" sleep 1 if [ ${BRANCH} == "master" ]; then @@ -676,6 +676,11 @@ if [ $NEW_BRANCH != "master" ] || [ $NEW_BRANCH != "nightly"]; then echo -e "\e[33mThe mailcow stack might still work but it is recommended to switch to the master branch (stable builds).\e[0m" echo -e "\e[33mTo change that run the update.sh Script one time with the --stable parameter to switch to stable builds.\e[0m" fi +elif [ $FORCE = "y" ]; then + echo -e "\e[31mYou are running in forced mode!\e[0m" + echo -e "\e[31mA Branch Switch can only be performed manually (monitored).\e[0m" + echo -e "\e[31mPlease rerun the update.sh Script without the --force/-f parameter.\e[0m" + sleep 1 elif [ $NEW_BRANCH == "master" ] && [ $CURRENT_BRANCH != "master" ]; then echo -e "\e[33mYou are about to switch your mailcow Updates to the stable (master) branch.\e[0m" sleep 1 @@ -683,7 +688,8 @@ elif [ $NEW_BRANCH == "master" ] && [ $CURRENT_BRANCH != "master" ]; then sleep 1 echo -e "\e[31mWARNING: Please see on GitHub or ask in the communitys if a switch to master is stable or not. In some rear cases a Update back to master can destroy your mailcow configuration in case of Database Upgrades etc. - Normally a upgrade back to master should be safe during each full release. Check GitHub for Database Changes and Update only if there similar to the full release!\e[0m" + Normally a upgrade back to master should be safe during each full release. + Check GitHub for Database Changes and Update only if there similar to the full release!\e[0m" read -r -p "Are you sure you that want to continue upgrading to the stable (master) branch? [y/N] " response if [[ ! "${response}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then echo "OK. If you prepared yourself for that please run the update.sh Script with the --stable parameter again to trigger this process here." @@ -709,7 +715,7 @@ elif [ $NEW_BRANCH == "nightly" ] && [ $CURRENT_BRANCH != "nightly" ]; then sleep 1 echo -e "\e[33mBefore you do: Please take a backup of all components to ensure that no Data is lost...\e[0m" sleep 1 - echo -e "\e[31mWARNING: A switch to nightly is possible any time. But a switch back (to master) not.\e[0m" + echo -e "\e[31mWARNING: A switch to nightly is possible any time. But a switch back (to master) isn't.\e[0m" read -r -p "Are you sure you that want to continue upgrading to the unstable (nightly) branch? [y/N] " response if [[ ! "${response}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then echo "OK. If you prepared yourself for that please run the update.sh Script with the --nightly parameter again to trigger this process here." @@ -730,10 +736,6 @@ elif [ $NEW_BRANCH == "nightly" ] && [ $CURRENT_BRANCH != "nightly" ]; then git fetch origin --all git checkout -f origin/${BRANCH} -elif [ $FORCE ]; then - echo -e "\e[31mYou are running in forced mode!\e[0m" - echo -e "\e[31mA Branch Switch can only be performed manually (monitored).\e[0m" - echo -e "\e[31mPlease rerun the update.sh Script without the --force/-f parameter.\e[0m" fi echo -e "\e[32mChecking for newer update script...\e[0m" From cdc8f63b4b2ec1622c4f62106a4e3ea2b57bc7ba Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Wed, 24 Aug 2022 15:05:14 +0200 Subject: [PATCH 102/115] Fixed Force flag --- update.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update.sh b/update.sh index 6b19dd1d..93f8f365 100755 --- a/update.sh +++ b/update.sh @@ -676,7 +676,7 @@ if ! [ $NEW_BRANCH ]; then echo -e "\e[33mThe mailcow stack might still work but it is recommended to switch to the master branch (stable builds).\e[0m" echo -e "\e[33mTo change that run the update.sh Script one time with the --stable parameter to switch to stable builds.\e[0m" fi -elif [ $FORCE = "y" ]; then +elif [ $FORCE ]; then echo -e "\e[31mYou are running in forced mode!\e[0m" echo -e "\e[31mA Branch Switch can only be performed manually (monitored).\e[0m" echo -e "\e[31mPlease rerun the update.sh Script without the --force/-f parameter.\e[0m" From 825c8a6abee977529b3b29dd3e167d5d42b3fb47 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Wed, 24 Aug 2022 15:56:02 +0200 Subject: [PATCH 103/115] Changed Git Checkout form --- update.sh | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/update.sh b/update.sh index 93f8f365..e8141b41 100755 --- a/update.sh +++ b/update.sh @@ -695,8 +695,7 @@ elif [ $NEW_BRANCH == "master" ] && [ $CURRENT_BRANCH != "master" ]; then echo "OK. If you prepared yourself for that please run the update.sh Script with the --stable parameter again to trigger this process here." exit 0 fi - - BRANCH = NEW_BRANCH + BRANCH=$NEW_BRANCH DIFF_DIRECTORY=update_diffs DIFF_FILE=${DIFF_DIRECTORY}/diff_before_upgrade_to_master_$(date +"%Y-%m-%d-%H-%M-%S") mv diff_before_upgrade* ${DIFF_DIRECTORY}/ 2> /dev/null @@ -708,7 +707,7 @@ elif [ $NEW_BRANCH == "master" ] && [ $CURRENT_BRANCH != "master" ]; then fi echo -e "\e[32mSwitching Branch to ${BRANCH}...\e[0m" git fetch origin --all - git checkout -f origin/${BRANCH} + git checkout -f ${BRANCH} elif [ $NEW_BRANCH == "nightly" ] && [ $CURRENT_BRANCH != "nightly" ]; then echo -e "\e[33mYou are about to switch your mailcow Updates to the unstable (nightly) branch.\e[0m" @@ -721,8 +720,7 @@ elif [ $NEW_BRANCH == "nightly" ] && [ $CURRENT_BRANCH != "nightly" ]; then echo "OK. If you prepared yourself for that please run the update.sh Script with the --nightly parameter again to trigger this process here." exit 0 fi - - BRANCH = NEW_BRANCH + BRANCH=$NEW_BRANCH DIFF_DIRECTORY=update_diffs DIFF_FILE=${DIFF_DIRECTORY}/diff_before_upgrade_to_nightly_$(date +"%Y-%m-%d-%H-%M-%S") mv diff_before_upgrade* ${DIFF_DIRECTORY}/ 2> /dev/null @@ -732,10 +730,8 @@ elif [ $NEW_BRANCH == "nightly" ] && [ $CURRENT_BRANCH != "nightly" ]; then git diff ${BRANCH} --stat > ${DIFF_FILE} git diff ${BRANCH} >> ${DIFF_FILE} fi - git fetch origin --all - git checkout -f origin/${BRANCH} - + git checkout -f ${BRANCH} fi echo -e "\e[32mChecking for newer update script...\e[0m" @@ -932,8 +928,8 @@ if [ -f "${SCRIPT_DIR}/post_update_hook.sh" ]; then bash "${SCRIPT_DIR}/post_update_hook.sh" fi -# echo "In case you encounter any problem, hard-reset to a state before updating mailcow:" -# echo -# git reflog --color=always | grep "Before update on " -# echo -# echo "Use \"git reset --hard hash-on-the-left\" and run $COMPOSE_COMMAND up -d afterwards." \ No newline at end of file +echo "In case you encounter any problem, hard-reset to a state before updating mailcow:" +echo +git reflog --color=always | grep "Before update on " +echo +echo "Use \"git reset --hard hash-on-the-left\" and run $COMPOSE_COMMAND up -d afterwards." \ No newline at end of file From 85deeaf80645fc78adadfbe5b514651096f09382 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Wed, 24 Aug 2022 16:00:57 +0200 Subject: [PATCH 104/115] Corrected origin fetch --- update.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/update.sh b/update.sh index e8141b41..b5bd13c5 100755 --- a/update.sh +++ b/update.sh @@ -730,7 +730,7 @@ elif [ $NEW_BRANCH == "nightly" ] && [ $CURRENT_BRANCH != "nightly" ]; then git diff ${BRANCH} --stat > ${DIFF_FILE} git diff ${BRANCH} >> ${DIFF_FILE} fi - git fetch origin --all + git fetch origin git checkout -f ${BRANCH} fi @@ -928,8 +928,8 @@ if [ -f "${SCRIPT_DIR}/post_update_hook.sh" ]; then bash "${SCRIPT_DIR}/post_update_hook.sh" fi -echo "In case you encounter any problem, hard-reset to a state before updating mailcow:" -echo -git reflog --color=always | grep "Before update on " -echo -echo "Use \"git reset --hard hash-on-the-left\" and run $COMPOSE_COMMAND up -d afterwards." \ No newline at end of file +# echo "In case you encounter any problem, hard-reset to a state before updating mailcow:" +# echo +# git reflog --color=always | grep "Before update on " +# echo +# echo "Use \"git reset --hard hash-on-the-left\" and run $COMPOSE_COMMAND up -d afterwards." \ No newline at end of file From e202530afb57ee33c7cce6f9e2dbb3594e6df30f Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Wed, 24 Aug 2022 16:12:36 +0200 Subject: [PATCH 105/115] Set correct Commit ID from origin instead of local --- update.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/update.sh b/update.sh index b5bd13c5..c27fcd43 100755 --- a/update.sh +++ b/update.sh @@ -730,7 +730,7 @@ elif [ $NEW_BRANCH == "nightly" ] && [ $CURRENT_BRANCH != "nightly" ]; then git diff ${BRANCH} --stat > ${DIFF_FILE} git diff ${BRANCH} >> ${DIFF_FILE} fi - git fetch origin + git fetch origin --all git checkout -f ${BRANCH} fi @@ -872,14 +872,14 @@ fi if [ ${BRANCH} == "master" ]; then mailcow_git_version=$(git describe --tags `git rev-list --tags --max-count=1`) elif [ ${BRANCH} == "nightly" ]; then - mailcow_git_version=$(git rev-parse --short HEAD) + mailcow_git_version=$(git rev-parse origin/${BRANCH} --short HEAD | head -2 | tail -1) mailcow_last_git_version="" else mailcow_git_version=$(git rev-parse --short HEAD) mailcow_last_git_version="" fi -mailcow_git_commit=$(git rev-parse HEAD) +mailcow_git_commit=$(git rev-parse origin/${BRANCH}) mailcow_git_commit_date=$(git show -s --format=%cd --date=format:'%Y-%m-%d %H:%M') if [ $? -eq 0 ]; then @@ -928,8 +928,8 @@ if [ -f "${SCRIPT_DIR}/post_update_hook.sh" ]; then bash "${SCRIPT_DIR}/post_update_hook.sh" fi -# echo "In case you encounter any problem, hard-reset to a state before updating mailcow:" -# echo -# git reflog --color=always | grep "Before update on " -# echo -# echo "Use \"git reset --hard hash-on-the-left\" and run $COMPOSE_COMMAND up -d afterwards." \ No newline at end of file +echo "In case you encounter any problem, hard-reset to a state before updating mailcow:" +echo +git reflog --color=always | grep "Before update on " +echo +echo "Use \"git reset --hard hash-on-the-left\" and run $COMPOSE_COMMAND up -d afterwards." \ No newline at end of file From ef311f22bf5b188837909427438ee29423c2ac56 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Wed, 24 Aug 2022 16:16:38 +0200 Subject: [PATCH 106/115] Corrected Twig Footer --- data/web/templates/base.twig | 2 +- update.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/web/templates/base.twig b/data/web/templates/base.twig index e97199b3..be23c19d 100644 --- a/data/web/templates/base.twig +++ b/data/web/templates/base.twig @@ -484,7 +484,7 @@ function recursiveBase64StrToArrayBuffer(obj) { {% endif %} - {% if mailcow_cc_username and mailcow_info.mailcow_branch|lower == "origin/feature/nightly-editions" and mailcow_info.version_tag|default %} + {% if mailcow_cc_username and mailcow_info.mailcow_branch|lower == "nightly" and mailcow_info.version_tag|default %} 🛠️🐮 + 🐋 = 💕 Nightly: {{ mailcow_info.version_tag }} diff --git a/update.sh b/update.sh index c27fcd43..d0e4c4ba 100755 --- a/update.sh +++ b/update.sh @@ -706,7 +706,7 @@ elif [ $NEW_BRANCH == "master" ] && [ $CURRENT_BRANCH != "master" ]; then git diff ${BRANCH} >> ${DIFF_FILE} fi echo -e "\e[32mSwitching Branch to ${BRANCH}...\e[0m" - git fetch origin --all + git fetch origin git checkout -f ${BRANCH} elif [ $NEW_BRANCH == "nightly" ] && [ $CURRENT_BRANCH != "nightly" ]; then @@ -730,7 +730,7 @@ elif [ $NEW_BRANCH == "nightly" ] && [ $CURRENT_BRANCH != "nightly" ]; then git diff ${BRANCH} --stat > ${DIFF_FILE} git diff ${BRANCH} >> ${DIFF_FILE} fi - git fetch origin --all + git fetch origin git checkout -f ${BRANCH} fi From 5ea43051856104e9ea5d01bde800b0180e6edd04 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Wed, 24 Aug 2022 16:26:07 +0200 Subject: [PATCH 107/115] Fix Upstream Commit ID grep --- update.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/update.sh b/update.sh index d0e4c4ba..4efbc6a5 100755 --- a/update.sh +++ b/update.sh @@ -872,7 +872,7 @@ fi if [ ${BRANCH} == "master" ]; then mailcow_git_version=$(git describe --tags `git rev-list --tags --max-count=1`) elif [ ${BRANCH} == "nightly" ]; then - mailcow_git_version=$(git rev-parse origin/${BRANCH} --short HEAD | head -2 | tail -1) + mailcow_git_version=$(git rev-parse --short $(git rev-parse @{upstream})) mailcow_last_git_version="" else mailcow_git_version=$(git rev-parse --short HEAD) @@ -928,8 +928,8 @@ if [ -f "${SCRIPT_DIR}/post_update_hook.sh" ]; then bash "${SCRIPT_DIR}/post_update_hook.sh" fi -echo "In case you encounter any problem, hard-reset to a state before updating mailcow:" -echo -git reflog --color=always | grep "Before update on " -echo -echo "Use \"git reset --hard hash-on-the-left\" and run $COMPOSE_COMMAND up -d afterwards." \ No newline at end of file +# echo "In case you encounter any problem, hard-reset to a state before updating mailcow:" +# echo +# git reflog --color=always | grep "Before update on " +# echo +# echo "Use \"git reset --hard hash-on-the-left\" and run $COMPOSE_COMMAND up -d afterwards." \ No newline at end of file From 778a3ed551c270d0bb104eddd26c3587fcc25df4 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 25 Aug 2022 10:07:42 +0200 Subject: [PATCH 108/115] Use universal Git Commit Date Command --- update.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update.sh b/update.sh index 4efbc6a5..169923b9 100755 --- a/update.sh +++ b/update.sh @@ -880,7 +880,7 @@ else fi mailcow_git_commit=$(git rev-parse origin/${BRANCH}) -mailcow_git_commit_date=$(git show -s --format=%cd --date=format:'%Y-%m-%d %H:%M') +mailcow_git_commit_date=$(git log -1 --format=%ci @{upstream} ) if [ $? -eq 0 ]; then echo ' data/web/inc/app_info.inc.php From 1f9f4157a674850962b06fe68553ff040e6c9a5e Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 25 Aug 2022 10:27:46 +0200 Subject: [PATCH 109/115] Corrected detect docker compose command position --- update.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/update.sh b/update.sh index 169923b9..7ebb0f49 100755 --- a/update.sh +++ b/update.sh @@ -344,6 +344,8 @@ while (($#)); do shift done +detect_docker_compose_command + [[ ! -f mailcow.conf ]] && { echo "mailcow.conf is missing! Is mailcow installed?"; exit 1;} chmod 600 mailcow.conf source mailcow.conf @@ -354,8 +356,6 @@ if [ ${#DOTS} -lt 2 ]; then exit 1 fi -detect_docker_compose_command - if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo "BusyBox grep detected, please install gnu grep, \"apk add --no-cache --upgrade grep\""; exit 1; fi # This will also cover sort if cp --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo "BusyBox cp detected, please install coreutils, \"apk add --no-cache --upgrade coreutils\""; exit 1; fi From bc9141753fab8fb4b591461d92703c345ebbef0c Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 25 Aug 2022 10:32:33 +0200 Subject: [PATCH 110/115] Re-arranged position of source mailcow.conf --- update.sh | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/update.sh b/update.sh index 7ebb0f49..a7664c67 100755 --- a/update.sh +++ b/update.sh @@ -344,11 +344,12 @@ while (($#)); do shift done +chmod 600 mailcow.conf +source mailcow.conf + detect_docker_compose_command [[ ! -f mailcow.conf ]] && { echo "mailcow.conf is missing! Is mailcow installed?"; exit 1;} -chmod 600 mailcow.conf -source mailcow.conf DOTS=${MAILCOW_HOSTNAME//[^.]}; if [ ${#DOTS} -lt 2 ]; then echo "MAILCOW_HOSTNAME (${MAILCOW_HOSTNAME}) is not a FQDN!" @@ -734,16 +735,16 @@ elif [ $NEW_BRANCH == "nightly" ] && [ $CURRENT_BRANCH != "nightly" ]; then git checkout -f ${BRANCH} fi -echo -e "\e[32mChecking for newer update script...\e[0m" -SHA1_1=$(sha1sum update.sh) -git fetch origin #${BRANCH} -git checkout origin/${BRANCH} update.sh -SHA1_2=$(sha1sum update.sh) -if [[ ${SHA1_1} != ${SHA1_2} ]]; then - echo "update.sh changed, please run this script again, exiting." - chmod +x update.sh - exit 2 -fi +# echo -e "\e[32mChecking for newer update script...\e[0m" +# SHA1_1=$(sha1sum update.sh) +# git fetch origin #${BRANCH} +# git checkout origin/${BRANCH} update.sh +# SHA1_2=$(sha1sum update.sh) +# if [[ ${SHA1_1} != ${SHA1_2} ]]; then +# echo "update.sh changed, please run this script again, exiting." +# chmod +x update.sh +# exit 2 +# fi if [ ! $FORCE ]; then read -r -p "Are you sure you want to update mailcow: dockerized? All containers will be stopped. [y/N] " response From e98a9844179e0808106d5a7f28c1eca800f2bf40 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 25 Aug 2022 11:16:55 +0200 Subject: [PATCH 111/115] Implemented correct app_info.php set in generate_config --- generate_config.sh | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/generate_config.sh b/generate_config.sh index 1ae76b21..a7f4a5a0 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -420,16 +420,42 @@ echo "Copying snake-oil certificate..." cp -n -d data/assets/ssl-example/*.pem data/assets/ssl/ # Set app_info.inc.php -mailcow_git_version=$(git describe --tags `git rev-list --tags --max-count=1`) +if [ ${git_branch} == "master" ]; then + mailcow_git_version=$(git describe --tags `git rev-list --tags --max-count=1`) +elif [ ${git_branch} == "nightly" ]; then + mailcow_git_version=$(git rev-parse --short $(git rev-parse @{upstream})) + mailcow_last_git_version="" +else + mailcow_git_version=$(git rev-parse --short HEAD) + mailcow_last_git_version="" +fi + +mailcow_git_commit=$(git rev-parse origin/${git_branch}) +mailcow_git_commit_date=$(git log -1 --format=%ci @{upstream} ) + if [ $? -eq 0 ]; then echo ' data/web/inc/app_info.inc.php echo ' $MAILCOW_GIT_VERSION="'$mailcow_git_version'";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_LAST_GIT_VERSION="";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_GIT_OWNER="mailcow";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_GIT_REPO="mailcow-dockerized";' >> data/web/inc/app_info.inc.php echo ' $MAILCOW_GIT_URL="https://github.com/mailcow/mailcow-dockerized";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_GIT_COMMIT="'$mailcow_git_commit'";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_GIT_COMMIT_DATE="'$mailcow_git_commit_date'";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_BRANCH="'$BRANCH'";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_UPDATEDAT='$(date +%s)';' >> data/web/inc/app_info.inc.php echo '?>' >> data/web/inc/app_info.inc.php else echo ' data/web/inc/app_info.inc.php - echo ' $MAILCOW_GIT_VERSION="";' >> data/web/inc/app_info.inc.php - echo ' $MAILCOW_GIT_URL="";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_GIT_VERSION="'$mailcow_git_version'";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_LAST_GIT_VERSION="";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_GIT_OWNER="mailcow";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_GIT_REPO="mailcow-dockerized";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_GIT_URL="https://github.com/mailcow/mailcow-dockerized";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_GIT_COMMIT="";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_GIT_COMMIT_DATE="";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_BRANCH="'$BRANCH'";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_UPDATEDAT='$(date +%s)';' >> data/web/inc/app_info.inc.php echo '?>' >> data/web/inc/app_info.inc.php echo -e "\e[33mCannot determine current git repository version...\e[0m" fi \ No newline at end of file From 3633766544d37ee74a96fc34d763885be5504f2e Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 25 Aug 2022 11:21:12 +0200 Subject: [PATCH 112/115] Fixed missing branch variable in app info.php (gen-config) --- generate_config.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generate_config.sh b/generate_config.sh index a7f4a5a0..6af24f28 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -442,7 +442,7 @@ if [ $? -eq 0 ]; then echo ' $MAILCOW_GIT_URL="https://github.com/mailcow/mailcow-dockerized";' >> data/web/inc/app_info.inc.php echo ' $MAILCOW_GIT_COMMIT="'$mailcow_git_commit'";' >> data/web/inc/app_info.inc.php echo ' $MAILCOW_GIT_COMMIT_DATE="'$mailcow_git_commit_date'";' >> data/web/inc/app_info.inc.php - echo ' $MAILCOW_BRANCH="'$BRANCH'";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_BRANCH="'$git_branch'";' >> data/web/inc/app_info.inc.php echo ' $MAILCOW_UPDATEDAT='$(date +%s)';' >> data/web/inc/app_info.inc.php echo '?>' >> data/web/inc/app_info.inc.php else @@ -454,7 +454,7 @@ else echo ' $MAILCOW_GIT_URL="https://github.com/mailcow/mailcow-dockerized";' >> data/web/inc/app_info.inc.php echo ' $MAILCOW_GIT_COMMIT="";' >> data/web/inc/app_info.inc.php echo ' $MAILCOW_GIT_COMMIT_DATE="";' >> data/web/inc/app_info.inc.php - echo ' $MAILCOW_BRANCH="'$BRANCH'";' >> data/web/inc/app_info.inc.php + echo ' $MAILCOW_BRANCH="'$git_branch'";' >> data/web/inc/app_info.inc.php echo ' $MAILCOW_UPDATEDAT='$(date +%s)';' >> data/web/inc/app_info.inc.php echo '?>' >> data/web/inc/app_info.inc.php echo -e "\e[33mCannot determine current git repository version...\e[0m" From 555f4a8a6d300361985c8d4cb9aada97ea339483 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Thu, 25 Aug 2022 14:26:45 +0200 Subject: [PATCH 113/115] [Web] Mailbox TFA fix --- data/web/inc/functions.inc.php | 48 ++++++++++++++++-------------- data/web/inc/prerequisites.inc.php | 2 +- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index ca371303..4f70b4c2 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -807,7 +807,7 @@ function verify_hash($hash, $password) { } return false; } -function check_login($user, $pass, $app_passwd_data = false, $skip_tfa = false) { +function check_login($user, $pass, $app_passwd_data = false) { global $pdo; global $redis; global $imap_server; @@ -834,7 +834,7 @@ function check_login($user, $pass, $app_passwd_data = false, $skip_tfa = false) if (verify_hash($row['password'], $pass)) { // check for tfa authenticators $authenticators = get_tfa($user); - if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$skip_tfa) { + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { // active tfa authenticators found, set pending user login $_SESSION['pending_mailcow_cc_username'] = $user; $_SESSION['pending_mailcow_cc_role'] = "admin"; @@ -873,7 +873,7 @@ function check_login($user, $pass, $app_passwd_data = false, $skip_tfa = false) if (verify_hash($row['password'], $pass) !== false) { // check for tfa authenticators $authenticators = get_tfa($user); - if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$skip_tfa) { + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { $_SESSION['pending_mailcow_cc_username'] = $user; $_SESSION['pending_mailcow_cc_role'] = "domainadmin"; $_SESSION['pending_tfa_methods'] = $authenticators['additional']; @@ -937,9 +937,8 @@ function check_login($user, $pass, $app_passwd_data = false, $skip_tfa = false) } foreach ($rows as $row) { // verify password - if (verify_hash($row['password'], $pass) !== false) { - - if ($app_passwd_data['eas'] !== true && $app_passwd_data['dav'] !== true && !$skip_tfa){ + if ($app_passwd_data['eas'] !== true && $app_passwd_data['dav'] !== true){ + if (verify_hash($row['password'], $pass) !== false) { // check for tfa authenticators $authenticators = get_tfa($user); if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { @@ -953,26 +952,31 @@ function check_login($user, $pass, $app_passwd_data = false, $skip_tfa = false) 'msg' => array('logged_in_as', $user) ); return "pending"; + } else { + // Reactivate TFA if it was set to "deactivate TFA for next login" + $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); + $stmt->execute(array(':user' => $user)); + + unset($_SESSION['ldelay']); + return "user"; } } + } elseif ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) { + if (array_key_exists("app_passwd_id", $row)){ + if (verify_hash($row['password'], $pass) !== false) { + $service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV'; + $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)"); + $stmt->execute(array( + ':service' => $service, + ':app_id' => $row['app_passwd_id'], + ':username' => $user, + ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']) + )); - if ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) { - $service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV'; - $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)"); - $stmt->execute(array( - ':service' => $service, - ':app_id' => $row['app_passwd_id'], - ':username' => $user, - ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']) - )); - } elseif (!$skip_tfa) { - // Reactivate TFA if it was set to "deactivate TFA for next login" - $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); - $stmt->execute(array(':user' => $user)); + unset($_SESSION['ldelay']); + return "user"; + } } - - unset($_SESSION['ldelay']); - return "user"; } } diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index cb02ba16..2897444b 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -131,7 +131,7 @@ class mailcowPdo extends OAuth2\Storage\Pdo { $this->config['user_table'] = 'mailbox'; } public function checkUserCredentials($username, $password) { - if (check_login($username, $password, false, true) == 'user') { + if (check_login($username, $password) == 'user') { return true; } return false; From 57cd5ec818b5872e91327990f92e9f69a96a2e19 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 25 Aug 2022 14:48:38 +0200 Subject: [PATCH 114/115] Readded update.sh new Version check :P --- update.sh | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/update.sh b/update.sh index a7664c67..a0ba0729 100755 --- a/update.sh +++ b/update.sh @@ -184,10 +184,10 @@ if ! [ "${DOCKER_COMPOSE_VERSION}" == "native" ] && ! [ "${DOCKER_COMPOSE_VERSIO echo -e "\e[31mFound Docker Compose Plugin (native).\e[0m" echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m" sleep 2 - echo -e "\e[33mNotice: You´ll have to update this Compose Version via your Package Manager manually!\e[0m" + echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m" else echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" - echo -e "\e[31mPlease update it manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" exit 1 fi elif docker-compose > /dev/null 2>&1; then @@ -198,10 +198,10 @@ if ! [ "${DOCKER_COMPOSE_VERSION}" == "native" ] && ! [ "${DOCKER_COMPOSE_VERSIO echo -e "\e[31mFound Docker Compose Standalone.\e[0m" echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m" sleep 2 - echo -e "\e[33mNotice: You´ll have to update this Compose Version manually! Please see: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.[0m" else echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" - echo -e "\e[31mPlease update manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease update/install regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" exit 1 fi fi @@ -735,16 +735,16 @@ elif [ $NEW_BRANCH == "nightly" ] && [ $CURRENT_BRANCH != "nightly" ]; then git checkout -f ${BRANCH} fi -# echo -e "\e[32mChecking for newer update script...\e[0m" -# SHA1_1=$(sha1sum update.sh) -# git fetch origin #${BRANCH} -# git checkout origin/${BRANCH} update.sh -# SHA1_2=$(sha1sum update.sh) -# if [[ ${SHA1_1} != ${SHA1_2} ]]; then -# echo "update.sh changed, please run this script again, exiting." -# chmod +x update.sh -# exit 2 -# fi +echo -e "\e[32mChecking for newer update script...\e[0m" +SHA1_1=$(sha1sum update.sh) +git fetch origin #${BRANCH} +git checkout origin/${BRANCH} update.sh +SHA1_2=$(sha1sum update.sh) +if [[ ${SHA1_1} != ${SHA1_2} ]]; then + echo "update.sh changed, please run this script again, exiting." + chmod +x update.sh + exit 2 +fi if [ ! $FORCE ]; then read -r -p "Are you sure you want to update mailcow: dockerized? All containers will be stopped. [y/N] " response From fee6ff43bf1d65846cea58c727cd707905d6ae65 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 25 Aug 2022 14:51:49 +0200 Subject: [PATCH 115/115] Corrected compose standalone update message in generate config --- generate_config.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/generate_config.sh b/generate_config.sh index 6af24f28..afd1d892 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -34,7 +34,7 @@ if docker compose > /dev/null 2>&1; then echo -e "\e[33mNotice: You´ll have to update this Compose Version via your Package Manager manually!\e[0m" else echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" - echo -e "\e[31mPlease update it manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" exit 1 fi elif docker-compose > /dev/null 2>&1; then @@ -44,10 +44,10 @@ elif docker-compose > /dev/null 2>&1; then echo -e "\e[31mFound Docker Compose Standalone.\e[0m" echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m" sleep 2 - echo -e "\e[33mNotice: You´ll have to update this Compose Version manually! Please see: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m" else echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" - echo -e "\e[31mPlease update manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease update/install manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" exit 1 fi fi