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/json_api.php b/mailcow/src/mailcow-dockerized/data/web/json_api.php
index 595bd8f..79b6bfd 100644
--- a/mailcow/src/mailcow-dockerized/data/web/json_api.php
+++ b/mailcow/src/mailcow-dockerized/data/web/json_api.php
@@ -14,17 +14,20 @@
if ($data == 'csrf_token') {
continue;
}
- if ($value = json_decode($value, true)) {
- unset($value["csrf_token"]);
+
+ $value = json_decode($value, true);
+ if ($value) {
+ if (is_array($value)) unset($value["csrf_token"]);
foreach ($value as $key => &$val) {
if(preg_match("/pass/i", $key)) {
$val = '*';
}
}
- $value = json_encode($value);
+ $value = json_encode($value);
}
$data_var[] = $data . "='" . $value . "'";
}
+
try {
$log_line = array(
'time' => time(),
@@ -41,7 +44,7 @@
'msg' => 'Redis: '.$e
);
return false;
- }
+ }
}
if (isset($_GET['query'])) {
@@ -82,10 +85,10 @@
if ($action == 'delete') {
$_POST['items'] = $request;
}
-
}
api_log($_POST);
+
$request_incomplete = json_encode(array(
'type' => 'error',
'msg' => 'Cannot find attributes in post data'
@@ -117,12 +120,12 @@
echo isset($_SESSION['return']) ? json_encode($_SESSION['return']) : $generic_success;
}
}
- if (!isset($_POST['attr']) && $category != "fido2-registration") {
+ if (!isset($_POST['attr']) && $category != "fido2-registration" && $category != "webauthn-tfa-registration") {
echo $request_incomplete;
exit;
}
else {
- if ($category != "fido2-registration") {
+ if ($category != "fido2-registration" && $category != "webauthn-tfa-registration") {
$attr = (array)json_decode($_POST['attr'], true);
}
unset($attr['csrf_token']);
@@ -170,6 +173,50 @@
exit;
}
break;
+ case "webauthn-tfa-registration":
+ if (isset($_SESSION["mailcow_cc_role"])) {
+ // parse post data
+ $post = trim(file_get_contents('php://input'));
+ if ($post) $post = json_decode($post);
+
+ // 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
+ $return = new stdClass();
+ $return->success = false;
+ $return->msg = $ex->getMessage();
+ echo json_encode($return);
+ exit;
+ }
+
+
+ // send response
+ $return = new stdClass();
+ $return->success = true;
+ echo json_encode($return);
+ exit;
+ }
+ else {
+ // err - request incomplete
+ echo $request_incomplete;
+ exit;
+ }
+ break;
case "time_limited_alias":
process_add_return(mailbox('add', 'time_limited_alias', $attr));
break;
@@ -183,13 +230,27 @@
process_add_return(rsettings('add', $attr));
break;
case "mailbox":
- process_add_return(mailbox('add', 'mailbox', $attr));
+ switch ($object) {
+ case "template":
+ process_add_return(mailbox('add', 'mailbox_templates', $attr));
+ break;
+ default:
+ process_add_return(mailbox('add', 'mailbox', $attr));
+ break;
+ }
break;
case "oauth2-client":
process_add_return(oauth2('add', 'client', $attr));
break;
case "domain":
- process_add_return(mailbox('add', 'domain', $attr));
+ switch ($object) {
+ case "template":
+ process_add_return(mailbox('add', 'domain_templates', $attr));
+ break;
+ default:
+ process_add_return(mailbox('add', 'domain', $attr));
+ break;
+ }
break;
case "resource":
process_add_return(mailbox('add', 'resource', $attr));
@@ -350,29 +411,13 @@
exit();
}
switch ($category) {
- case "u2f-registration":
- header('Content-Type: application/javascript');
- if (isset($_SESSION["mailcow_cc_role"]) && $_SESSION["mailcow_cc_username"] == $object) {
- list($req, $sigs) = $u2f->getRegisterData(get_u2f_registrations($object));
- $_SESSION['regReq'] = json_encode($req);
- $_SESSION['regSigs'] = json_encode($sigs);
- echo 'var req = ' . json_encode($req) . ';';
- echo 'var registeredKeys = ' . json_encode($sigs) . ';';
- echo 'var appId = req.appId;';
- echo 'var registerRequests = [{version: req.version, challenge: req.challenge}];';
- return;
- }
- else {
- return;
- }
- break;
- // fido2-registration via GET
+ // fido2
case "fido2-registration":
header('Content-Type: application/json');
if (isset($_SESSION["mailcow_cc_role"])) {
// Exclude existing CredentialIds, if any
$excludeCredentialIds = fido2(array("action" => "get_user_cids"));
- $createArgs = $WebAuthn->getCreateArgs($_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], 30, true, $GLOBALS['FIDO2_UV_FLAG_REGISTER'], $excludeCredentialIds);
+ $createArgs = $WebAuthn->getCreateArgs($_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], 30, true, $GLOBALS['FIDO2_UV_FLAG_REGISTER'], null, $excludeCredentialIds);
print(json_encode($createArgs));
$_SESSION['challenge'] = $WebAuthn->getChallenge();
return;
@@ -381,28 +426,6 @@
return;
}
break;
- case "u2f-authentication":
- header('Content-Type: application/javascript');
- if (isset($_SESSION['pending_mailcow_cc_username']) && $_SESSION['pending_mailcow_cc_username'] == $object) {
- $auth_data = $u2f->getAuthenticateData(get_u2f_registrations($object));
- $challenge = $auth_data[0]->challenge;
- $appId = $auth_data[0]->appId;
- foreach ($auth_data as $each) {
- $key = array(); // Empty array
- $key['version'] = $each->version;
- $key['keyHandle'] = $each->keyHandle;
- $registeredKey[] = $key;
- }
- $_SESSION['authReq'] = json_encode($auth_data);
- echo 'var appId = "' . $appId . '";';
- echo 'var challenge = ' . json_encode($challenge) . ';';
- echo 'var registeredKeys = ' . json_encode($registeredKey) . ';';
- return;
- }
- else {
- return;
- }
- break;
case "fido2-get-args":
header('Content-Type: application/json');
// Login without username, no ids!
@@ -411,7 +434,60 @@
// return;
// }
$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;
+ break;
+ // webauthn two factor authentication
+ 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 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']);
+ }
+ // getCreateArgs($userId, $userName, $userDisplayName, $timeout=20, $requireResidentKey=false, $requireUserVerification=false, $crossPlatformAttachment=null, $excludeCredentialIds=array())
+ // cross-platform: true, if type internal is not allowed
+ // false, if only internal is allowed
+ // null, if internal and cross-platform is allowed
+ $createArgs = $WebAuthn->getCreateArgs($_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], 30, false, $GLOBALS['WEBAUTHN_UV_FLAG_REGISTER'], null, $excludeCredentialIds);
+
+ print(json_encode($createArgs));
+ $_SESSION['challenge'] = $WebAuthn->getChallenge();
+ return;
+
+ }
+ else {
+ return;
+ }
+ break;
+ case "webauthn-tfa-get-args":
+ $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(
+ 'type' => 'error',
+ 'msg' => 'Cannot find matching credentialIds'
+ )));
+ exit;
+ }
+ while($row = array_shift($rows)) {
+ $cids[] = base64_decode($row['keyHandle']);
+ }
+
+ $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();
return;
@@ -436,7 +512,12 @@
case "domain":
switch ($object) {
case "all":
- $domains = mailbox('get', 'domains');
+ $tags = null;
+ if (isset($_GET['tags']) && $_GET['tags'] != '')
+ $tags = explode(',', $_GET['tags']);
+
+ $domains = mailbox('get', 'domains', null, $tags);
+
if (!empty($domains)) {
foreach ($domains as $domain) {
if ($details = mailbox('get', 'domain_details', $domain)) {
@@ -452,7 +533,16 @@
echo '{}';
}
break;
-
+ case "template":
+ switch ($extra){
+ case "all":
+ process_get_return(mailbox('get', 'domain_templates'));
+ break;
+ default:
+ process_get_return(mailbox('get', 'domain_templates', $extra));
+ break;
+ }
+ break;
default:
$data = mailbox('get', 'domain_details', $object);
process_get_return($data);
@@ -902,23 +992,20 @@
switch ($object) {
case "all":
case "reduced":
- if (empty($extra)) {
- $domains = mailbox('get', 'domains');
- }
- else {
- $domains = explode(',', $extra);
- }
+ $tags = null;
+ if (isset($_GET['tags']) && $_GET['tags'] != '')
+ $tags = explode(',', $_GET['tags']);
+
+ if (empty($extra)) $domains = mailbox('get', 'domains');
+ else $domains = explode(',', $extra);
+
if (!empty($domains)) {
foreach ($domains as $domain) {
- $mailboxes = mailbox('get', 'mailboxes', $domain);
+ $mailboxes = mailbox('get', 'mailboxes', $domain, $tags);
if (!empty($mailboxes)) {
foreach ($mailboxes as $mailbox) {
- if ($details = mailbox('get', 'mailbox_details', $mailbox, $object)) {
- $data[] = $details;
- }
- else {
- continue;
- }
+ if ($details = mailbox('get', 'mailbox_details', $mailbox, $object)) $data[] = $details;
+ else continue;
}
}
}
@@ -928,10 +1015,34 @@
echo '{}';
}
break;
-
+ case "template":
+ switch ($extra){
+ case "all":
+ process_get_return(mailbox('get', 'mailbox_templates'));
+ break;
+ default:
+ process_get_return(mailbox('get', 'mailbox_templates', $extra));
+ break;
+ }
+ break;
default:
- $data = mailbox('get', 'mailbox_details', $object);
- process_get_return($data);
+ $tags = null;
+ if (isset($_GET['tags']) && $_GET['tags'] != '')
+ $tags = explode(',', $_GET['tags']);
+
+ if ($tags === null) {
+ $data = mailbox('get', 'mailbox_details', $object);
+ process_get_return($data);
+ } else {
+ $mailboxes = mailbox('get', 'mailboxes', $object, $tags);
+ if (is_array($mailboxes)) {
+ foreach ($mailboxes as $mailbox) {
+ if ($details = mailbox('get', 'mailbox_details', $mailbox))
+ $data[] = $details;
+ }
+ }
+ process_get_return($data, false);
+ }
break;
}
break;
@@ -1393,6 +1504,10 @@
}
echo json_encode($temp, JSON_UNESCAPED_SLASHES);
break;
+ case "container":
+ $container_stats = docker('container_stats', $extra);
+ echo json_encode($container_stats);
+ break;
case "vmail":
$exec_fields_vmail = array('cmd' => 'system', 'task' => 'df', 'dir' => '/var/vmail');
$vmail_df = explode(',', json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields_vmail), true));
@@ -1404,24 +1519,53 @@
'used_percent' => $vmail_df[4]
);
echo json_encode($temp, JSON_UNESCAPED_SLASHES);
- break;
- case "solr":
- $solr_status = solr_status();
- $solr_size = ($solr_status['status']['dovecot-fts']['index']['size']);
- $solr_documents = ($solr_status['status']['dovecot-fts']['index']['numDocs']);
- if (strtolower(getenv('SKIP_SOLR')) != 'n') {
- $solr_enabled = false;
- }
- else {
- $solr_enabled = true;
- }
- echo json_encode(array(
- 'type' => 'info',
- 'solr_enabled' => $solr_enabled,
- 'solr_size' => $solr_size,
- 'solr_documents' => $solr_documents
- ));
- break;
+ break;
+ case "solr":
+ $solr_status = solr_status();
+ $solr_size = ($solr_status['status']['dovecot-fts']['index']['size']);
+ $solr_documents = ($solr_status['status']['dovecot-fts']['index']['numDocs']);
+ if (strtolower(getenv('SKIP_SOLR')) != 'n') {
+ $solr_enabled = false;
+ }
+ else {
+ $solr_enabled = true;
+ }
+ echo json_encode(array(
+ 'type' => 'info',
+ 'solr_enabled' => $solr_enabled,
+ 'solr_size' => $solr_size,
+ 'solr_documents' => $solr_documents
+ ));
+ break;
+ case "host":
+ if (!$extra){
+ $stats = docker("host_stats");
+ echo json_encode($stats);
+ }
+ else if ($extra == "ip") {
+ // get public ips
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_URL, 'http://ipv4.mailcow.email');
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($curl, CURLOPT_POST, 0);
+ $ipv4 = curl_exec($curl);
+ curl_setopt($curl, CURLOPT_URL, 'http://ipv6.mailcow.email');
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($curl, CURLOPT_POST, 0);
+ $ipv6 = curl_exec($curl);
+ $ips = array(
+ "ipv4" => $ipv4,
+ "ipv6" => $ipv6
+ );
+ curl_close($curl);
+ echo json_encode($ips);
+ }
+ break;
+ case "version":
+ echo json_encode(array(
+ 'version' => $GLOBALS['MAILCOW_GIT_VERSION']
+ ));
+ break;
}
}
break;
@@ -1525,13 +1669,31 @@
process_delete_return(dkim('delete', array('domains' => $items)));
break;
case "domain":
- process_delete_return(mailbox('delete', 'domain', array('domain' => $items)));
+ switch ($object){
+ case "tag":
+ process_delete_return(mailbox('delete', 'tags_domain', array('tags' => $items, 'domain' => $extra)));
+ break;
+ case "template":
+ process_delete_return(mailbox('delete', 'domain_templates', array('ids' => $items)));
+ break;
+ default:
+ process_delete_return(mailbox('delete', 'domain', array('domain' => $items)));
+ }
break;
case "alias-domain":
process_delete_return(mailbox('delete', 'alias_domain', array('alias_domain' => $items)));
break;
case "mailbox":
- process_delete_return(mailbox('delete', 'mailbox', array('username' => $items)));
+ switch ($object){
+ case "tag":
+ process_delete_return(mailbox('delete', 'tags_mailbox', array('tags' => $items, 'username' => $extra)));
+ break;
+ case "template":
+ process_delete_return(mailbox('delete', 'mailbox_templates', array('ids' => $items)));
+ break;
+ default:
+ process_delete_return(mailbox('delete', 'mailbox', array('username' => $items)));
+ }
break;
case "resource":
process_delete_return(mailbox('delete', 'resource', array('name' => $items)));
@@ -1690,7 +1852,14 @@
process_edit_return(mailbox('edit', 'time_limited_alias', array_merge(array('address' => $items), $attr)));
break;
case "mailbox":
- process_edit_return(mailbox('edit', 'mailbox', array_merge(array('username' => $items), $attr)));
+ switch ($object) {
+ case "template":
+ process_edit_return(mailbox('edit', 'mailbox_templates', array_merge(array('ids' => $items), $attr)));
+ break;
+ default:
+ process_edit_return(mailbox('edit', 'mailbox', array_merge(array('username' => $items), $attr)));
+ break;
+ }
break;
case "syncjob":
process_edit_return(mailbox('edit', 'syncjob', array_merge(array('id' => $items), $attr)));
@@ -1702,7 +1871,14 @@
process_edit_return(mailbox('edit', 'resource', array_merge(array('name' => $items), $attr)));
break;
case "domain":
- process_edit_return(mailbox('edit', 'domain', array_merge(array('domain' => $items), $attr)));
+ switch ($object) {
+ case "template":
+ process_edit_return(mailbox('edit', 'domain_templates', array_merge(array('ids' => $items), $attr)));
+ break;
+ default:
+ process_edit_return(mailbox('edit', 'domain', array_merge(array('domain' => $items), $attr)));
+ break;
+ }
break;
case "rl-domain":
process_edit_return(ratelimit('edit', 'domain', array_merge(array('object' => $items), $attr)));