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