git subrepo commit mailcow/src/mailcow-dockerized
subrepo: subdir: "mailcow/src/mailcow-dockerized"
merged: "308860af"
upstream: origin: "https://github.com/mailcow/mailcow-dockerized.git"
branch: "master"
commit: "3f1a5af8"
git-subrepo: version: "0.4.5"
origin: "???"
commit: "???"
Change-Id: I5d51c14b45db54fe706be40a591ddbfcea50d4b0
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/functions.inc.php b/mailcow/src/mailcow-dockerized/data/web/inc/functions.inc.php
index 7ac0af5..3bab56b 100644
--- a/mailcow/src/mailcow-dockerized/data/web/inc/functions.inc.php
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/functions.inc.php
@@ -251,7 +251,7 @@
return true;
}
-function last_login($action, $username, $sasl_limit_days = 7) {
+function last_login($action, $username, $sasl_limit_days = 7, $ui_offset = 1) {
global $pdo;
global $redis;
$sasl_limit_days = intval($sasl_limit_days);
@@ -319,8 +319,11 @@
$stmt = $pdo->prepare('SELECT `remote`, `time` FROM `logs`
WHERE JSON_EXTRACT(`call`, "$[0]") = "check_login"
AND JSON_EXTRACT(`call`, "$[1]") = :username
- AND `type` = "success" ORDER BY `time` DESC LIMIT 1 OFFSET 1');
- $stmt->execute(array(':username' => $username));
+ AND `type` = "success" ORDER BY `time` DESC LIMIT 1 OFFSET :offset');
+ $stmt->execute(array(
+ ':username' => $username,
+ ':offset' => $ui_offset
+ ));
$ui = $stmt->fetch(PDO::FETCH_ASSOC);
}
else {
@@ -830,11 +833,15 @@
$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 +849,7 @@
'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 +872,14 @@
$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_methods'] = $authenticators['additional'];
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'info',
@@ -929,15 +938,37 @@
$stmt->execute(array(':user' => $user));
$rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC));
}
- foreach ($rows as $row) {
+ 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) {
+ if (!array_key_exists("app_passwd_id", $row)){
+ // password is not a app password
+ // check for tfa authenticators
+ $authenticators = get_tfa($user);
+ if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 &&
+ $app_passwd_data['eas'] !== true && $app_passwd_data['dav'] !== true) {
+ // authenticators found, init TFA flow
+ $_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 (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) {
+ // no authenticators found, login successfull
+ // 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) {
+ // password is a app password
$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(
@@ -946,8 +977,10 @@
':username' => $user,
':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'])
));
+
+ unset($_SESSION['ldelay']);
+ return "user";
}
- return "user";
}
}
@@ -1140,49 +1173,49 @@
function set_tfa($_data) {
global $pdo;
global $yubi;
- global $u2f;
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;
+
+ // 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;
}
}
- $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 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":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
@@ -1219,8 +1252,7 @@
$yubico_modhex_id = substr($_data["otp_token"], 0, 12);
$stmt = $pdo->prepare("DELETE FROM `tfa`
WHERE `username` = :username
- AND (`authmech` != 'yubi_otp')
- OR (`authmech` = 'yubi_otp' AND `secret` LIKE :modhex)");
+ AND (`authmech` = 'yubi_otp' AND `secret` LIKE :modhex)");
$stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id));
$stmt = $pdo->prepare("INSERT INTO `tfa` (`key_id`, `username`, `authmech`, `active`, `secret`) VALUES
(:key_id, :username, 'yubi_otp', '1', :secret)");
@@ -1240,31 +1272,6 @@
'msg' => array('object_modified', htmlspecialchars($username))
);
break;
- case "u2f":
- $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
- try {
- $reg = $u2f->doRegister(json_decode($_SESSION['regReq']), json_decode($_data['token']));
- $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `authmech` != 'u2f'");
- $stmt->execute(array(':username' => $username));
- $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`) VALUES (?, ?, 'u2f', ?, ?, ?, ?, '1')");
- $stmt->execute(array($username, $key_id, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter));
- $_SESSION['return'][] = array(
- 'type' => 'success',
- 'log' => array(__FUNCTION__, $_data_log),
- 'msg' => array('object_modified', $username)
- );
- $_SESSION['regReq'] = null;
- }
- catch (Exception $e) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $_data_log),
- 'msg' => array('u2f_verification_failed', $e->getMessage())
- );
- $_SESSION['regReq'] = null;
- return false;
- }
- break;
case "totp":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
if ($tfa->verifyCode($_POST['totp_secret'], $_POST['totp_confirm_token']) === true) {
@@ -1286,6 +1293,26 @@
);
}
break;
+ case "webauthn":
+ $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
+
+ $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`)
+ VALUES (?, ?, 'webauthn', ?, ?, ?, ?, '1')");
+ $stmt->execute(array(
+ $username,
+ $key_id,
+ base64_encode($_data['registration']->credentialId),
+ $_data['registration']->credentialPublicKey,
+ $_data['registration']->certificate,
+ 0
+ ));
+
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array(__FUNCTION__, $_data_log),
+ 'msg' => array('object_modified', $username)
+ );
+ break;
case "none":
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
@@ -1360,8 +1387,8 @@
if (!isset($_data['cid']) || empty($_data['cid'])) {
return false;
}
- $stmt = $pdo->prepare("SELECT `certificateSubject`, `username`, `credentialPublicKey`, SHA2(`credentialId`, 256) AS `cid` FROM `fido2` WHERE TO_BASE64(`credentialId`) = :cid");
- $stmt->execute(array(':cid' => $_data['cid']));
+ $stmt = $pdo->prepare("SELECT `certificateSubject`, `username`, `credentialPublicKey`, SHA2(`credentialId`, 256) AS `cid` FROM `fido2` WHERE `credentialId` = :cid");
+ $stmt->execute(array(':cid' => base64_decode($_data['cid'])));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row) || empty($row['credentialPublicKey']) || empty($row['username'])) {
return false;
@@ -1440,25 +1467,27 @@
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));
@@ -1471,6 +1500,8 @@
);
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(
@@ -1488,7 +1519,7 @@
return false;
}
}
-function get_tfa($username = null) {
+function get_tfa($username = null, $id = null) {
global $pdo;
if (isset($_SESSION['mailcow_cc_username'])) {
$username = $_SESSION['mailcow_cc_username'];
@@ -1496,204 +1527,311 @@
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($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 id
+ $stmt = $pdo->prepare("SELECT * FROM `tfa`
+ 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"])) {
+ 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 AND `id` = :id");
+ $stmt->execute(array(
+ ':username' => $username,
+ ':id' => $id
+ ));
+ $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 AND `id` = :id");
+ $stmt->execute(array(
+ ':username' => $username,
+ ':id' => $id
+ ));
+ $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 AND `id` = :id");
+ $stmt->execute(array(
+ ':username' => $username,
+ ':id' => $id
+ ));
+ $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 AND `id` = :id");
+ $stmt->execute(array(
+ ':username' => $username,
+ ':id' => $id
+ ));
+ $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;
- case "u2f":
- $data['name'] = "u2f";
- $data['pretty'] = "Fido U2F";
- $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username");
- $stmt->execute(array(
- ':username' => $username,
- ));
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- while($row = array_shift($rows)) {
- $data['additional'][] = $row;
- }
- return $data;
- break;
- case "hotp":
- $data['name'] = "hotp";
- $data['pretty'] = "HMAC-based OTP";
- return $data;
- break;
- case "totp":
- $data['name'] = "totp";
- $data['pretty'] = "Time-based OTP";
- $stmt = $pdo->prepare("SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' AND `username` = :username");
- $stmt->execute(array(
- ':username' => $username,
- ));
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- while($row = array_shift($rows)) {
- $data['additional'][] = $row;
- }
- return $data;
- break;
- default:
+ }
+ else {
$data['name'] = 'none';
$data['pretty'] = "-";
return $data;
- break;
+ }
}
- }
- else {
- $data['name'] = 'none';
- $data['pretty'] = "-";
- return $data;
- }
}
-function verify_tfa_login($username, $token) {
+function verify_tfa_login($username, $_data) {
global $pdo;
global $yubi;
global $u2f;
global $tfa;
- $stmt = $pdo->prepare("SELECT `authmech` FROM `tfa`
- WHERE `username` = :username AND `active` = '1'");
- $stmt->execute(array(':username' => $username));
- $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ global $WebAuthn;
- switch ($row["authmech"]) {
- case "yubi_otp":
- if (!ctype_alnum($token) || strlen($token) != 44) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $username, '*'),
- 'msg' => array('yotp_verification_failed', 'token length error')
- );
- return false;
- }
- $yubico_modhex_id = substr($token, 0, 12);
- $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
- WHERE `username` = :username
- AND `authmech` = 'yubi_otp'
- AND `active`='1'
- AND `secret` LIKE :modhex");
- $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id));
- $row = $stmt->fetch(PDO::FETCH_ASSOC);
- $yubico_auth = explode(':', $row['secret']);
- $yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]);
- $yauth = $yubi->verify($token);
- if (PEAR::isError($yauth)) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $username, '*'),
- 'msg' => array('yotp_verification_failed', $yauth->getMessage())
- );
- return false;
- }
- else {
- $_SESSION['tfa_id'] = $row['id'];
- $_SESSION['return'][] = array(
- 'type' => 'success',
- 'log' => array(__FUNCTION__, $username, '*'),
- 'msg' => 'verified_yotp_login'
- );
- return true;
- }
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $username, '*'),
- 'msg' => array('yotp_verification_failed', 'unknown')
- );
- return false;
- break;
- case "u2f":
- try {
- $reg = $u2f->doAuthenticate(json_decode($_SESSION['authReq']), get_u2f_registrations($username), json_decode($token));
- $stmt = $pdo->prepare("SELECT `id` FROM `tfa` WHERE `keyHandle` = ?");
- $stmt->execute(array($reg->keyHandle));
- $row_key_id = $stmt->fetch(PDO::FETCH_ASSOC);
- $_SESSION['tfa_id'] = $row_key_id['id'];
- $_SESSION['authReq'] = null;
- $_SESSION['return'][] = array(
- 'type' => 'success',
- 'log' => array(__FUNCTION__, $username, '*'),
- 'msg' => 'verified_u2f_login'
- );
- return true;
- }
- catch (Exception $e) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $username, '*'),
- 'msg' => array('u2f_verification_failed', $e->getMessage())
- );
- $_SESSION['regReq'] = null;
- return false;
- }
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $username, '*'),
- 'msg' => array('u2f_verification_failed', 'unknown')
- );
- return false;
- break;
- case "hotp":
- return false;
- break;
- case "totp":
- try {
- $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
- WHERE `username` = :username
- AND `authmech` = 'totp'
- AND `active`='1'");
- $stmt->execute(array(':username' => $username));
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- foreach ($rows as $row) {
- if ($tfa->verifyCode($row['secret'], $_POST['token']) === true) {
- $_SESSION['tfa_id'] = $row['id'];
- $_SESSION['return'][] = array(
- 'type' => 'success',
+ if ($_data['tfa_method'] != 'u2f'){
+
+ switch ($_data["tfa_method"]) {
+ case "yubi_otp":
+ if (!ctype_alnum($_data['token']) || strlen($_data['token']) != 44) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $username, '*'),
+ 'msg' => array('yotp_verification_failed', 'token length error')
+ );
+ return false;
+ }
+ $yubico_modhex_id = substr($_data['token'], 0, 12);
+ $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
+ WHERE `username` = :username
+ AND `authmech` = 'yubi_otp'
+ AND `active` = '1'
+ AND `secret` LIKE :modhex");
+ $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ $yubico_auth = explode(':', $row['secret']);
+ $yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]);
+ $yauth = $yubi->verify($_data['token']);
+ if (PEAR::isError($yauth)) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $username, '*'),
+ 'msg' => array('yotp_verification_failed', $yauth->getMessage())
+ );
+ return false;
+ }
+ else {
+ $_SESSION['tfa_id'] = $row['id'];
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array(__FUNCTION__, $username, '*'),
+ 'msg' => 'verified_yotp_login'
+ );
+ return true;
+ }
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $username, '*'),
+ 'msg' => array('yotp_verification_failed', 'unknown')
+ );
+ return false;
+ break;
+ case "hotp":
+ return false;
+ break;
+ case "totp":
+ try {
+ $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
+ WHERE `username` = :username
+ AND `authmech` = 'totp'
+ AND `id` = :id
+ AND `active`='1'");
+ $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) {
+ $_SESSION['tfa_id'] = $row['id'];
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array(__FUNCTION__, $username, '*'),
+ 'msg' => 'verified_totp_login'
+ );
+ return true;
+ }
+ }
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $username, '*'),
+ 'msg' => 'totp_verification_failed'
+ );
+ return false;
+ }
+ 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']);
+ $clientDataJSON = base64_decode($tokenData->clientDataJSON);
+ $authenticatorData = base64_decode($tokenData->authenticatorData);
+ $signature = base64_decode($tokenData->signature);
+ $id = base64_decode($tokenData->id);
+ $challenge = $_SESSION['challenge'];
+
+ $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);
+
+ if (empty($process_webauthn)){
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $username, '*'),
+ 'msg' => array('webauthn_verification_failed', 'authenticator not found')
+ );
+ return false;
+ }
+
+ if (empty($process_webauthn['publicKey']) || $process_webauthn['publicKey'] === false) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $username, '*'),
+ 'msg' => array('webauthn_verification_failed', 'publicKey not found')
+ );
+ return false;
+ }
+
+ try {
+ $WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $process_webauthn['publicKey'], $challenge, null, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN'], $GLOBALS['WEBAUTHN_USER_PRESENT_FLAG']);
+ }
+ catch (Throwable $ex) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $username, '*'),
+ 'msg' => array('webauthn_verification_failed', $ex->getMessage())
+ );
+ return false;
+ }
+
+ $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";
+ }
+ elseif ($obj_props['superadmin'] === 0) {
+ $_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 (!empty($row['username'])) {
+ $_SESSION["mailcow_cc_role"] = "user";
+ } else {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $username, '*'),
+ 'msg' => array('webauthn_verification_failed', 'could not determine user role')
+ );
+ return false;
+ }
+ }
+
+ if ($process_webauthn['username'] != $_SESSION['pending_mailcow_cc_username']){
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $username, '*'),
+ 'msg' => array('webauthn_verification_failed', 'user who requests does not match with sql entry')
+ );
+ return false;
+ }
+
+ $_SESSION["mailcow_cc_username"] = $process_webauthn['username'];
+ $_SESSION['tfa_id'] = $process_webauthn['id'];
+ $_SESSION['authReq'] = null;
+ unset($_SESSION["challenge"]);
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array("webauthn_login"),
+ 'msg' => array('logged_in_as', $process_webauthn['username'])
+ );
+ return true;
+ break;
+ default:
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
- 'msg' => 'verified_totp_login'
- );
- return true;
- }
- }
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $username, '*'),
- 'msg' => 'totp_verification_failed'
- );
- return false;
+ 'msg' => 'unknown_tfa_method'
+ );
+ return false;
+ break;
}
- catch (PDOException $e) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $username, '*'),
- 'msg' => array('mysql_error', $e)
- );
- return false;
- }
- break;
- default:
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $username, '*'),
- 'msg' => 'unknown_tfa_method'
- );
+
return false;
- break;
+ } 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;
}
- return false;
}
function admin_api($access, $action, $data = null) {
global $pdo;
@@ -1955,12 +2093,7 @@
break;
}
}
-function get_u2f_registrations($username) {
- global $pdo;
- $sel = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = ? AND `active` = '1'");
- $sel->execute(array($username));
- return $sel->fetchAll(PDO::FETCH_OBJ);
-}
+
function get_logs($application, $lines = false) {
if ($lines === false) {
$lines = $GLOBALS['LOG_LINES'] - 1;