2
									
								
								.github/workflows/rebuild_backup_image.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/rebuild_backup_image.yml
									
									
									
									
										vendored
									
									
								
							| @@ -26,7 +26,7 @@ jobs: | ||||
|           password: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_TOKEN }} | ||||
|  | ||||
|       - name: Build and push | ||||
|         uses: docker/build-push-action@v3 | ||||
|         uses: docker/build-push-action@v4 | ||||
|         with: | ||||
|           context: . | ||||
|           file: data/Dockerfiles/backup/Dockerfile | ||||
|   | ||||
| @@ -1,20 +0,0 @@ | ||||
| name: "Tweet trigger release" | ||||
| on: | ||||
|   release: | ||||
|     types: [published] | ||||
|  | ||||
| jobs: | ||||
|   tweet: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: "Get Release Tag" | ||||
|         run:  | | ||||
|           RELEASE_TAG=$(curl https://api.github.com/repos/mailcow/mailcow-dockerized/releases/latest | jq -r '.tag_name') | ||||
|       - name: Tweet-trigger-publish-release | ||||
|         uses: mugi111/tweet-trigger-release@v1.2 | ||||
|         with: | ||||
|           consumer_key: ${{ secrets.CONSUMER_KEY }} | ||||
|           consumer_secret: ${{ secrets.CONSUMER_SECRET }} | ||||
|           access_token_key: ${{ secrets.ACCESS_TOKEN_KEY }} | ||||
|           access_token_secret: ${{ secrets.ACCESS_TOKEN_SECRET }} | ||||
|           tweet_body: 'A new mailcow update has just been released! Checkout the GitHub Page for changelog and more informations: https://github.com/mailcow/mailcow-dockerized/releases/latest' | ||||
| @@ -699,6 +699,38 @@ paths: | ||||
|                   type: string | ||||
|               type: object | ||||
|       summary: Create Domain Admin user | ||||
|   /api/v1/add/sso/domain-admin: | ||||
|     post: | ||||
|       responses: | ||||
|         "401": | ||||
|           $ref: "#/components/responses/Unauthorized" | ||||
|         "200": | ||||
|           content: | ||||
|             application/json: | ||||
|               examples: | ||||
|                 response: | ||||
|                   value: | ||||
|                     token: "591F6D-5C3DD2-7455CD-DAF1C1-AA4FCC" | ||||
|           description: OK | ||||
|           headers: { } | ||||
|       tags: | ||||
|         - Single Sign-On | ||||
|       description: >- | ||||
|         Using this endpoint you can issue a token for Domain Admin user. This token can be used for | ||||
|         autologin Domain Admin user by using query_string var sso_token={token}. Token expiration time is 30s | ||||
|       operationId: Issue Domain Admin SSO token | ||||
|       requestBody: | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               example: | ||||
|                 username: testadmin | ||||
|               properties: | ||||
|                 username: | ||||
|                   description: the username for the admin user | ||||
|                   type: object | ||||
|               type: object | ||||
|       summary: Issue Domain Admin SSO token | ||||
|   /api/v1/edit/da-acl: | ||||
|     post: | ||||
|       responses: | ||||
| @@ -1999,7 +2031,7 @@ paths: | ||||
|                 - domain.tld | ||||
|                 - domain2.tld | ||||
|               properties: | ||||
|                 items:  | ||||
|                 items: | ||||
|                   type: array | ||||
|                   items: | ||||
|                     type: string | ||||
| @@ -2993,7 +3025,7 @@ paths: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items:  | ||||
|                 items: | ||||
|                   type: object | ||||
|                   properties: | ||||
|                     log: | ||||
| @@ -5586,6 +5618,8 @@ tags: | ||||
|     description: Manage DKIM keys | ||||
|   - name: Domain admin | ||||
|     description: Create or udpdate domain admin users | ||||
|   - name: Single Sign-On | ||||
|     description: Issue tokens for users | ||||
|   - name: Address Rewriting | ||||
|     description: Create BCC maps or recipient maps | ||||
|   - name: Outgoing TLS Policy Map Overrides | ||||
|   | ||||
| @@ -77,4 +77,22 @@ li .dtr-data { | ||||
| table.dataTable>tbody>tr.child span.dtr-title { | ||||
|     width: 30%; | ||||
|     max-width: 250px; | ||||
| } | ||||
| } | ||||
|  | ||||
|  | ||||
| div.dataTables_wrapper div.dataTables_filter { | ||||
|     text-align: left; | ||||
| } | ||||
| div.dataTables_wrapper div.dataTables_length { | ||||
|     text-align: right; | ||||
| } | ||||
| .dataTables_paginate, .dataTables_length, .dataTables_filter { | ||||
|     margin: 10px 0!important; | ||||
| } | ||||
|  | ||||
| td.dt-text-right { | ||||
|     text-align: end !important; | ||||
| } | ||||
| th.dt-text-right { | ||||
|     text-align: end !important; | ||||
| } | ||||
|   | ||||
| @@ -370,14 +370,3 @@ button[aria-expanded='true'] > .caret { | ||||
| .btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show { | ||||
|     background-color: #f0f0f0 !important; | ||||
| } | ||||
|  | ||||
|  | ||||
| div.dataTables_wrapper div.dataTables_filter { | ||||
|   text-align: left; | ||||
| } | ||||
| div.dataTables_wrapper div.dataTables_length { | ||||
|   text-align: right; | ||||
| } | ||||
| .dataTables_paginate, .dataTables_length, .dataTables_filter { | ||||
|   margin: 10px 0!important; | ||||
| } | ||||
| @@ -203,6 +203,9 @@ | ||||
|     text-align: left; | ||||
|   } | ||||
|  | ||||
|   .senders-mw220 { | ||||
|     max-width: 100% !important; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @media (max-width: 350px) { | ||||
|   | ||||
| @@ -1,102 +1,104 @@ | ||||
| .pagination a { | ||||
|   text-decoration: none !important; | ||||
| } | ||||
|  | ||||
| .panel.panel-default { | ||||
|   overflow: visible !important; | ||||
| } | ||||
|  | ||||
| .table-responsive { | ||||
|   overflow: visible !important; | ||||
| } | ||||
|  | ||||
| .table-responsive { | ||||
|   overflow-x: scroll !important; | ||||
| } | ||||
|  | ||||
| .footer-add-item { | ||||
|   display: block; | ||||
|   text-align: center; | ||||
|   font-style: italic; | ||||
|   padding: 10px; | ||||
|   background: #F5F5F5; | ||||
| } | ||||
|  | ||||
| @media (min-width: 992px) { | ||||
|   .container { | ||||
|     width: 100%; | ||||
|   } | ||||
| } | ||||
| @media (min-width: 1920px) { | ||||
|   .container { | ||||
|       width: 80%; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .mass-actions-quarantine { | ||||
|   user-select: none; | ||||
| } | ||||
|  | ||||
| .inputMissingAttr { | ||||
|   border-color: #FF4136; | ||||
| } | ||||
|  | ||||
| .modal#qidDetailModal p { | ||||
|   word-break: break-all; | ||||
| } | ||||
|  | ||||
| span#qid_detail_score { | ||||
|   font-weight: 700; | ||||
|   margin-left: 5px; | ||||
| } | ||||
|  | ||||
| span.rspamd-symbol { | ||||
|   display: inline-block; | ||||
|   margin: 2px 6px 2px 0; | ||||
|   border-radius: 4px; | ||||
|   padding: 0 7px; | ||||
| } | ||||
|  | ||||
| span.rspamd-symbol.positive { | ||||
|   background: #4CAF50; | ||||
|   border: 1px solid #4CAF50; | ||||
|   color: white; | ||||
| } | ||||
|  | ||||
| span.rspamd-symbol.negative { | ||||
|   background: #ff4136; | ||||
|   border: 1px solid #ff4136; | ||||
|   color: white; | ||||
| } | ||||
|  | ||||
| span.rspamd-symbol.neutral { | ||||
|   background: #f5f5f5; | ||||
|   color: #333; | ||||
|   border: 1px solid #ccc; | ||||
| } | ||||
|  | ||||
| span.rspamd-symbol span.score { | ||||
|   font-weight: 700; | ||||
| } | ||||
|  | ||||
| span.mail-address-item { | ||||
|   background-color: #f5f5f5; | ||||
|   border-radius: 4px; | ||||
|   border: 1px solid #ccc; | ||||
|   padding: 2px 7px; | ||||
|   display: inline-block; | ||||
|   margin: 2px 6px 2px 0; | ||||
| } | ||||
|  | ||||
| table tbody tr { | ||||
|   cursor: pointer; | ||||
| } | ||||
|  | ||||
| table tbody tr td input[type="checkbox"] { | ||||
|   cursor: pointer; | ||||
| } | ||||
| .label-rspamd-action { | ||||
|   font-size:110%; | ||||
|   margin:20px; | ||||
| } | ||||
|  | ||||
| .pagination a { | ||||
|   text-decoration: none !important; | ||||
| } | ||||
|  | ||||
| .panel.panel-default { | ||||
|   overflow: visible !important; | ||||
| } | ||||
|  | ||||
| .table-responsive { | ||||
|   overflow: visible !important; | ||||
| } | ||||
|  | ||||
| .table-responsive { | ||||
|   overflow-x: scroll !important; | ||||
| } | ||||
|  | ||||
| .footer-add-item { | ||||
|   display: block; | ||||
|   text-align: center; | ||||
|   font-style: italic; | ||||
|   padding: 10px; | ||||
|   background: #F5F5F5; | ||||
| } | ||||
|  | ||||
| @media (min-width: 992px) { | ||||
|   .container { | ||||
|     width: 100%; | ||||
|   } | ||||
| } | ||||
| @media (min-width: 1920px) { | ||||
|   .container { | ||||
|       width: 80%; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .mass-actions-quarantine { | ||||
|   user-select: none; | ||||
| } | ||||
|  | ||||
| .inputMissingAttr { | ||||
|   border-color: #FF4136; | ||||
| } | ||||
|  | ||||
| .modal#qidDetailModal p { | ||||
|   word-break: break-all; | ||||
| } | ||||
|  | ||||
| span#qid_detail_score { | ||||
|   font-weight: 700; | ||||
|   margin-left: 5px; | ||||
| } | ||||
|  | ||||
| span.rspamd-symbol { | ||||
|   display: inline-block; | ||||
|   margin: 2px 6px 2px 0; | ||||
|   border-radius: 4px; | ||||
|   padding: 0 7px; | ||||
| } | ||||
|  | ||||
| span.rspamd-symbol.positive { | ||||
|   background: #4CAF50; | ||||
|   border: 1px solid #4CAF50; | ||||
|   color: white; | ||||
| } | ||||
|  | ||||
| span.rspamd-symbol.negative { | ||||
|   background: #ff4136; | ||||
|   border: 1px solid #ff4136; | ||||
|   color: white; | ||||
| } | ||||
|  | ||||
| span.rspamd-symbol.neutral { | ||||
|   background: #f5f5f5; | ||||
|   color: #333; | ||||
|   border: 1px solid #ccc; | ||||
| } | ||||
|  | ||||
| span.rspamd-symbol span.score { | ||||
|   font-weight: 700; | ||||
| } | ||||
|  | ||||
| span.mail-address-item { | ||||
|   background-color: #f5f5f5; | ||||
|   border-radius: 4px; | ||||
|   border: 1px solid #ccc; | ||||
|   padding: 2px 7px; | ||||
|   display: inline-block; | ||||
|   margin: 2px 6px 2px 0; | ||||
| } | ||||
|  | ||||
| table tbody tr { | ||||
|   cursor: pointer; | ||||
| } | ||||
|  | ||||
| table tbody tr td input[type="checkbox"] { | ||||
|   cursor: pointer; | ||||
| } | ||||
| .label-rspamd-action { | ||||
|   font-size:110%; | ||||
|   margin:20px; | ||||
| } | ||||
| .senders-mw220 { | ||||
|   max-width: 220px; | ||||
| } | ||||
|   | ||||
| @@ -1,407 +1,468 @@ | ||||
| <?php | ||||
| function domain_admin($_action, $_data = null) { | ||||
|   global $pdo; | ||||
|   global $lang; | ||||
|   $_data_log = $_data; | ||||
|   !isset($_data_log['password']) ?: $_data_log['password'] = '*'; | ||||
|   !isset($_data_log['password2']) ?: $_data_log['password2'] = '*'; | ||||
|   !isset($_data_log['user_old_pass']) ?: $_data_log['user_old_pass'] = '*'; | ||||
|   !isset($_data_log['user_new_pass']) ?: $_data_log['user_new_pass'] = '*'; | ||||
|   !isset($_data_log['user_new_pass2']) ?: $_data_log['user_new_pass2'] = '*'; | ||||
|   switch ($_action) { | ||||
|     case 'add': | ||||
|       $username		= strtolower(trim($_data['username'])); | ||||
|       $password		= $_data['password']; | ||||
|       $password2  = $_data['password2']; | ||||
|       $domains    = (array)$_data['domains']; | ||||
|       $active     = intval($_data['active']); | ||||
|       if ($_SESSION['mailcow_cc_role'] != "admin") { | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'danger', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|           'msg' => 'access_denied' | ||||
|         ); | ||||
|         return false; | ||||
|       } | ||||
|       if (empty($domains)) { | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'danger', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|           'msg' => 'domain_invalid' | ||||
|         ); | ||||
|         return false; | ||||
|       } | ||||
|       if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username) || $username == 'API') { | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'danger', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|           'msg' => array('username_invalid', $username) | ||||
|         ); | ||||
|         return false; | ||||
|       } | ||||
|  | ||||
|       $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` | ||||
|         WHERE `username` = :username"); | ||||
|       $stmt->execute(array(':username' => $username)); | ||||
|       $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); | ||||
|  | ||||
|       $stmt = $pdo->prepare("SELECT `username` FROM `admin` | ||||
|         WHERE `username` = :username"); | ||||
|       $stmt->execute(array(':username' => $username)); | ||||
|       $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); | ||||
|  | ||||
|       $stmt = $pdo->prepare("SELECT `username` FROM `domain_admins` | ||||
|         WHERE `username` = :username"); | ||||
|       $stmt->execute(array(':username' => $username)); | ||||
|       $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); | ||||
|  | ||||
|       foreach ($num_results as $num_results_each) { | ||||
|         if ($num_results_each != 0) { | ||||
|           $_SESSION['return'][] = array( | ||||
|             'type' => 'danger', | ||||
|             'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|             'msg' => array('object_exists', htmlspecialchars($username)) | ||||
|           ); | ||||
|           return false; | ||||
|         } | ||||
|       } | ||||
|       if (password_check($password, $password2) !== true) { | ||||
|         continue; | ||||
|       } | ||||
|       $password_hashed = hash_password($password); | ||||
|       $valid_domains = 0; | ||||
|       foreach ($domains as $domain) { | ||||
|         if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) { | ||||
|           $_SESSION['return'][] = array( | ||||
|             'type' => 'danger', | ||||
|             'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|             'msg' => array('domain_invalid', htmlspecialchars($domain)) | ||||
|           ); | ||||
|           continue; | ||||
|         } | ||||
|         $valid_domains++; | ||||
|         $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) | ||||
|             VALUES (:username, :domain, :created, :active)"); | ||||
|         $stmt->execute(array( | ||||
|           ':username' => $username, | ||||
|           ':domain' => $domain, | ||||
|           ':created' => date('Y-m-d H:i:s'), | ||||
|           ':active' => $active | ||||
|         )); | ||||
|       } | ||||
|       if ($valid_domains != 0) { | ||||
|         $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`) | ||||
|           VALUES (:username, :password_hashed, '0', :active)"); | ||||
|         $stmt->execute(array( | ||||
|           ':username' => $username, | ||||
|           ':password_hashed' => $password_hashed, | ||||
|           ':active' => $active | ||||
|         )); | ||||
|       } | ||||
|       $stmt = $pdo->prepare("INSERT INTO `da_acl` (`username`) VALUES (:username)"); | ||||
|       $stmt->execute(array( | ||||
|         ':username' => $username | ||||
|       )); | ||||
|       $_SESSION['return'][] = array( | ||||
|         'type' => 'success', | ||||
|         'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|         'msg' => array('domain_admin_added', htmlspecialchars($username)) | ||||
|       ); | ||||
|     break; | ||||
|     case 'edit': | ||||
|       if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'danger', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|           'msg' => 'access_denied' | ||||
|         ); | ||||
|         return false; | ||||
|       } | ||||
|       // Administrator | ||||
|       if ($_SESSION['mailcow_cc_role'] == "admin") { | ||||
|         if (!is_array($_data['username'])) { | ||||
|           $usernames = array(); | ||||
|           $usernames[] = $_data['username']; | ||||
|         } | ||||
|         else { | ||||
|           $usernames = $_data['username']; | ||||
|         } | ||||
|         foreach ($usernames as $username) { | ||||
|           $is_now = domain_admin('details', $username); | ||||
|           $domains = (isset($_data['domains'])) ? (array)$_data['domains'] : null; | ||||
|           if (!empty($is_now)) { | ||||
|             $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; | ||||
|             $domains = (!empty($domains)) ? $domains : $is_now['selected_domains']; | ||||
|             $username_new = (!empty($_data['username_new'])) ? $_data['username_new'] : $is_now['username']; | ||||
|           } | ||||
|           else { | ||||
|             $_SESSION['return'][] = array( | ||||
|               'type' => 'danger', | ||||
|               'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|               'msg' => 'access_denied' | ||||
|             ); | ||||
|             continue; | ||||
|           } | ||||
|           $password     = $_data['password']; | ||||
|           $password2    = $_data['password2']; | ||||
|           if (!empty($domains)) { | ||||
|             foreach ($domains as $domain) { | ||||
|               if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) { | ||||
|                 $_SESSION['return'][] = array( | ||||
|                   'type' => 'danger', | ||||
|                   'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|                   'msg' => array('domain_invalid', htmlspecialchars($domain)) | ||||
|                 ); | ||||
|                 continue 2; | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|           if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new))) { | ||||
|             $_SESSION['return'][] = array( | ||||
|               'type' => 'danger', | ||||
|               'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|               'msg' => array('username_invalid', $username_new) | ||||
|             ); | ||||
|             continue; | ||||
|           } | ||||
|           if ($username_new != $username) { | ||||
|             if (!empty(domain_admin('details', $username_new)['username'])) { | ||||
|               $_SESSION['return'][] = array( | ||||
|                 'type' => 'danger', | ||||
|                 'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|                 'msg' => array('username_invalid', $username_new) | ||||
|               ); | ||||
|               continue; | ||||
|             } | ||||
|           } | ||||
|           $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); | ||||
|           $stmt->execute(array( | ||||
|             ':username' => $username, | ||||
|           )); | ||||
|           $stmt = $pdo->prepare("UPDATE `da_acl` SET `username` = :username_new WHERE `username` = :username"); | ||||
|           $stmt->execute(array( | ||||
|             ':username_new' => $username_new, | ||||
|             ':username' => $username | ||||
|           )); | ||||
|           if (!empty($domains)) { | ||||
|             foreach ($domains as $domain) { | ||||
|               $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) | ||||
|                 VALUES (:username_new, :domain, :created, :active)"); | ||||
|               $stmt->execute(array( | ||||
|                 ':username_new' => $username_new, | ||||
|                 ':domain' => $domain, | ||||
|                 ':created' => date('Y-m-d H:i:s'), | ||||
|                 ':active' => $active | ||||
|               )); | ||||
|             } | ||||
|           } | ||||
|           if (!empty($password)) { | ||||
|             if (password_check($password, $password2) !== true) { | ||||
|               return false; | ||||
|             } | ||||
|             $password_hashed = hash_password($password); | ||||
|             $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username"); | ||||
|             $stmt->execute(array( | ||||
|               ':password_hashed' => $password_hashed, | ||||
|               ':username_new' => $username_new, | ||||
|               ':username' => $username, | ||||
|               ':active' => $active | ||||
|             )); | ||||
|             if (isset($_data['disable_tfa'])) { | ||||
|               $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); | ||||
|               $stmt->execute(array(':username' => $username)); | ||||
|             } | ||||
|             else { | ||||
|               $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); | ||||
|               $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); | ||||
|             } | ||||
|           } | ||||
|           else { | ||||
|             $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username"); | ||||
|             $stmt->execute(array( | ||||
|               ':username_new' => $username_new, | ||||
|               ':username' => $username, | ||||
|               ':active' => $active | ||||
|             )); | ||||
|             if (isset($_data['disable_tfa'])) { | ||||
|               $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); | ||||
|               $stmt->execute(array(':username' => $username)); | ||||
|             } | ||||
|             else { | ||||
|               $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); | ||||
|               $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); | ||||
|             } | ||||
|           } | ||||
|           $_SESSION['return'][] = array( | ||||
|             'type' => 'success', | ||||
|             'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|             'msg' => array('domain_admin_modified', htmlspecialchars($username)) | ||||
|           ); | ||||
|         } | ||||
|         return true; | ||||
|       } | ||||
|       // Domain administrator | ||||
|       // Can only edit itself | ||||
|       elseif ($_SESSION['mailcow_cc_role'] == "domainadmin") { | ||||
|         $username = $_SESSION['mailcow_cc_username']; | ||||
|         $password_old		= $_data['user_old_pass']; | ||||
|         $password_new	= $_data['user_new_pass']; | ||||
|         $password_new2	= $_data['user_new_pass2']; | ||||
|  | ||||
|         $stmt = $pdo->prepare("SELECT `password` FROM `admin` | ||||
|             WHERE `username` = :user"); | ||||
|         $stmt->execute(array(':user' => $username)); | ||||
|         $row = $stmt->fetch(PDO::FETCH_ASSOC); | ||||
|         if (!verify_hash($row['password'], $password_old)) { | ||||
|           $_SESSION['return'][] = array( | ||||
|             'type' => 'danger', | ||||
|             'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|             'msg' => 'access_denied' | ||||
|           ); | ||||
|           return false; | ||||
|         } | ||||
|         if (password_check($password_new, $password_new2) !== true) { | ||||
|           return false; | ||||
|         } | ||||
|         $password_hashed = hash_password($password_new); | ||||
|         $stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username"); | ||||
|         $stmt->execute(array( | ||||
|           ':password_hashed' => $password_hashed, | ||||
|           ':username' => $username | ||||
|         )); | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'success', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|           'msg' => array('domain_admin_modified', htmlspecialchars($username)) | ||||
|         ); | ||||
|       } | ||||
|     break; | ||||
|     case 'delete': | ||||
|       if ($_SESSION['mailcow_cc_role'] != "admin") { | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'danger', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|           'msg' => 'access_denied' | ||||
|         ); | ||||
|         return false; | ||||
|       } | ||||
|       $usernames = (array)$_data['username']; | ||||
|       foreach ($usernames as $username) { | ||||
|         if (empty(domain_admin('details', $username))) { | ||||
|           $_SESSION['return'][] = array( | ||||
|             'type' => 'danger', | ||||
|             'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|             'msg' => array('username_invalid', $username) | ||||
|           ); | ||||
|           continue; | ||||
|         } | ||||
|         $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); | ||||
|         $stmt->execute(array( | ||||
|           ':username' => $username, | ||||
|         )); | ||||
|         $stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username"); | ||||
|         $stmt->execute(array( | ||||
|           ':username' => $username, | ||||
|         )); | ||||
|         $stmt = $pdo->prepare("DELETE FROM `da_acl` WHERE `username` = :username"); | ||||
|         $stmt->execute(array( | ||||
|           ':username' => $username, | ||||
|         )); | ||||
|         $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); | ||||
|         $stmt->execute(array( | ||||
|           ':username' => $username, | ||||
|         )); | ||||
|         $stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username"); | ||||
|         $stmt->execute(array( | ||||
|           ':username' => $username, | ||||
|         )); | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'success', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|           'msg' => array('domain_admin_removed', htmlspecialchars($username)) | ||||
|         ); | ||||
|       } | ||||
|     break; | ||||
|     case 'get': | ||||
|       $domainadmins = array(); | ||||
|       if ($_SESSION['mailcow_cc_role'] != "admin") { | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'danger', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|           'msg' => 'access_denied' | ||||
|         ); | ||||
|         return false; | ||||
|       } | ||||
|       $stmt = $pdo->query("SELECT DISTINCT | ||||
|         `username` | ||||
|           FROM `domain_admins` | ||||
|             WHERE `username` IN ( | ||||
|               SELECT `username` FROM `admin` | ||||
|                 WHERE `superadmin`!='1' | ||||
|             )"); | ||||
|       $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); | ||||
|       while ($row = array_shift($rows)) { | ||||
|         $domainadmins[] = $row['username']; | ||||
|       } | ||||
|       return $domainadmins; | ||||
|     break; | ||||
|     case 'details': | ||||
|       $domainadmindata = array(); | ||||
|       if ($_SESSION['mailcow_cc_role'] == "domainadmin" && $_data != $_SESSION['mailcow_cc_username']) { | ||||
|         return false; | ||||
|       } | ||||
|       elseif ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { | ||||
|         return false; | ||||
|       } | ||||
|       if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $_data))) { | ||||
|         return false; | ||||
|       } | ||||
|       $stmt = $pdo->prepare("SELECT | ||||
|         `tfa`.`active` AS `tfa_active`, | ||||
|         `domain_admins`.`username`, | ||||
|         `domain_admins`.`created`, | ||||
|         `domain_admins`.`active` AS `active` | ||||
|           FROM `domain_admins` | ||||
|           LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`domain_admins`.`username` | ||||
|             WHERE `domain_admins`.`username`= :domain_admin"); | ||||
|       $stmt->execute(array( | ||||
|         ':domain_admin' => $_data | ||||
|       )); | ||||
|       $row = $stmt->fetch(PDO::FETCH_ASSOC); | ||||
|       if (empty($row)) { | ||||
|         return false; | ||||
|       } | ||||
|       $domainadmindata['username'] = $row['username']; | ||||
|       $domainadmindata['tfa_active'] = (is_null($row['tfa_active'])) ? 0 : $row['tfa_active']; | ||||
|       $domainadmindata['tfa_active_int'] = (is_null($row['tfa_active'])) ? 0 : $row['tfa_active']; | ||||
|       $domainadmindata['active'] = $row['active']; | ||||
|       $domainadmindata['active_int'] = $row['active']; | ||||
|       $domainadmindata['created'] = $row['created']; | ||||
|       // GET SELECTED | ||||
|       $stmt = $pdo->prepare("SELECT `domain` FROM `domain` | ||||
|         WHERE `domain` IN ( | ||||
|           SELECT `domain` FROM `domain_admins` | ||||
|             WHERE `username`= :domain_admin)"); | ||||
|       $stmt->execute(array(':domain_admin' => $_data)); | ||||
|       $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); | ||||
|       while($row = array_shift($rows)) { | ||||
|         $domainadmindata['selected_domains'][] = $row['domain']; | ||||
|       } | ||||
|       // GET UNSELECTED | ||||
|       $stmt = $pdo->prepare("SELECT `domain` FROM `domain` | ||||
|         WHERE `domain` NOT IN ( | ||||
|           SELECT `domain` FROM `domain_admins` | ||||
|             WHERE `username`= :domain_admin)"); | ||||
|       $stmt->execute(array(':domain_admin' => $_data)); | ||||
|       $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); | ||||
|       while($row = array_shift($rows)) { | ||||
|         $domainadmindata['unselected_domains'][] = $row['domain']; | ||||
|       } | ||||
|       if (!isset($domainadmindata['unselected_domains'])) { | ||||
|         $domainadmindata['unselected_domains'] = ""; | ||||
|       } | ||||
|  | ||||
|       return $domainadmindata; | ||||
|     break; | ||||
|   } | ||||
| } | ||||
| <?php | ||||
| function domain_admin($_action, $_data = null) { | ||||
|   global $pdo; | ||||
|   global $lang; | ||||
|   $_data_log = $_data; | ||||
|   !isset($_data_log['password']) ?: $_data_log['password'] = '*'; | ||||
|   !isset($_data_log['password2']) ?: $_data_log['password2'] = '*'; | ||||
|   !isset($_data_log['user_old_pass']) ?: $_data_log['user_old_pass'] = '*'; | ||||
|   !isset($_data_log['user_new_pass']) ?: $_data_log['user_new_pass'] = '*'; | ||||
|   !isset($_data_log['user_new_pass2']) ?: $_data_log['user_new_pass2'] = '*'; | ||||
|   switch ($_action) { | ||||
|     case 'add': | ||||
|       $username		= strtolower(trim($_data['username'])); | ||||
|       $password		= $_data['password']; | ||||
|       $password2  = $_data['password2']; | ||||
|       $domains    = (array)$_data['domains']; | ||||
|       $active     = intval($_data['active']); | ||||
|       if ($_SESSION['mailcow_cc_role'] != "admin") { | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'danger', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|           'msg' => 'access_denied' | ||||
|         ); | ||||
|         return false; | ||||
|       } | ||||
|       if (empty($domains)) { | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'danger', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|           'msg' => 'domain_invalid' | ||||
|         ); | ||||
|         return false; | ||||
|       } | ||||
|       if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username) || $username == 'API') { | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'danger', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|           'msg' => array('username_invalid', $username) | ||||
|         ); | ||||
|         return false; | ||||
|       } | ||||
|  | ||||
|       $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` | ||||
|         WHERE `username` = :username"); | ||||
|       $stmt->execute(array(':username' => $username)); | ||||
|       $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); | ||||
|  | ||||
|       $stmt = $pdo->prepare("SELECT `username` FROM `admin` | ||||
|         WHERE `username` = :username"); | ||||
|       $stmt->execute(array(':username' => $username)); | ||||
|       $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); | ||||
|  | ||||
|       $stmt = $pdo->prepare("SELECT `username` FROM `domain_admins` | ||||
|         WHERE `username` = :username"); | ||||
|       $stmt->execute(array(':username' => $username)); | ||||
|       $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); | ||||
|  | ||||
|       foreach ($num_results as $num_results_each) { | ||||
|         if ($num_results_each != 0) { | ||||
|           $_SESSION['return'][] = array( | ||||
|             'type' => 'danger', | ||||
|             'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|             'msg' => array('object_exists', htmlspecialchars($username)) | ||||
|           ); | ||||
|           return false; | ||||
|         } | ||||
|       } | ||||
|       if (password_check($password, $password2) !== true) { | ||||
|         continue; | ||||
|       } | ||||
|       $password_hashed = hash_password($password); | ||||
|       $valid_domains = 0; | ||||
|       foreach ($domains as $domain) { | ||||
|         if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) { | ||||
|           $_SESSION['return'][] = array( | ||||
|             'type' => 'danger', | ||||
|             'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|             'msg' => array('domain_invalid', htmlspecialchars($domain)) | ||||
|           ); | ||||
|           continue; | ||||
|         } | ||||
|         $valid_domains++; | ||||
|         $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) | ||||
|             VALUES (:username, :domain, :created, :active)"); | ||||
|         $stmt->execute(array( | ||||
|           ':username' => $username, | ||||
|           ':domain' => $domain, | ||||
|           ':created' => date('Y-m-d H:i:s'), | ||||
|           ':active' => $active | ||||
|         )); | ||||
|       } | ||||
|       if ($valid_domains != 0) { | ||||
|         $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`) | ||||
|           VALUES (:username, :password_hashed, '0', :active)"); | ||||
|         $stmt->execute(array( | ||||
|           ':username' => $username, | ||||
|           ':password_hashed' => $password_hashed, | ||||
|           ':active' => $active | ||||
|         )); | ||||
|       } | ||||
|       $stmt = $pdo->prepare("INSERT INTO `da_acl` (`username`) VALUES (:username)"); | ||||
|       $stmt->execute(array( | ||||
|         ':username' => $username | ||||
|       )); | ||||
|       $_SESSION['return'][] = array( | ||||
|         'type' => 'success', | ||||
|         'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|         'msg' => array('domain_admin_added', htmlspecialchars($username)) | ||||
|       ); | ||||
|     break; | ||||
|     case 'edit': | ||||
|       if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'danger', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|           'msg' => 'access_denied' | ||||
|         ); | ||||
|         return false; | ||||
|       } | ||||
|       // Administrator | ||||
|       if ($_SESSION['mailcow_cc_role'] == "admin") { | ||||
|         if (!is_array($_data['username'])) { | ||||
|           $usernames = array(); | ||||
|           $usernames[] = $_data['username']; | ||||
|         } | ||||
|         else { | ||||
|           $usernames = $_data['username']; | ||||
|         } | ||||
|         foreach ($usernames as $username) { | ||||
|           $is_now = domain_admin('details', $username); | ||||
|           $domains = (isset($_data['domains'])) ? (array)$_data['domains'] : null; | ||||
|           if (!empty($is_now)) { | ||||
|             $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; | ||||
|             $domains = (!empty($domains)) ? $domains : $is_now['selected_domains']; | ||||
|             $username_new = (!empty($_data['username_new'])) ? $_data['username_new'] : $is_now['username']; | ||||
|           } | ||||
|           else { | ||||
|             $_SESSION['return'][] = array( | ||||
|               'type' => 'danger', | ||||
|               'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|               'msg' => 'access_denied' | ||||
|             ); | ||||
|             continue; | ||||
|           } | ||||
|           $password     = $_data['password']; | ||||
|           $password2    = $_data['password2']; | ||||
|           if (!empty($domains)) { | ||||
|             foreach ($domains as $domain) { | ||||
|               if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) { | ||||
|                 $_SESSION['return'][] = array( | ||||
|                   'type' => 'danger', | ||||
|                   'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|                   'msg' => array('domain_invalid', htmlspecialchars($domain)) | ||||
|                 ); | ||||
|                 continue 2; | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|           if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new))) { | ||||
|             $_SESSION['return'][] = array( | ||||
|               'type' => 'danger', | ||||
|               'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|               'msg' => array('username_invalid', $username_new) | ||||
|             ); | ||||
|             continue; | ||||
|           } | ||||
|           if ($username_new != $username) { | ||||
|             if (!empty(domain_admin('details', $username_new)['username'])) { | ||||
|               $_SESSION['return'][] = array( | ||||
|                 'type' => 'danger', | ||||
|                 'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|                 'msg' => array('username_invalid', $username_new) | ||||
|               ); | ||||
|               continue; | ||||
|             } | ||||
|           } | ||||
|           $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); | ||||
|           $stmt->execute(array( | ||||
|             ':username' => $username, | ||||
|           )); | ||||
|           $stmt = $pdo->prepare("UPDATE `da_acl` SET `username` = :username_new WHERE `username` = :username"); | ||||
|           $stmt->execute(array( | ||||
|             ':username_new' => $username_new, | ||||
|             ':username' => $username | ||||
|           )); | ||||
|           if (!empty($domains)) { | ||||
|             foreach ($domains as $domain) { | ||||
|               $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) | ||||
|                 VALUES (:username_new, :domain, :created, :active)"); | ||||
|               $stmt->execute(array( | ||||
|                 ':username_new' => $username_new, | ||||
|                 ':domain' => $domain, | ||||
|                 ':created' => date('Y-m-d H:i:s'), | ||||
|                 ':active' => $active | ||||
|               )); | ||||
|             } | ||||
|           } | ||||
|           if (!empty($password)) { | ||||
|             if (password_check($password, $password2) !== true) { | ||||
|               return false; | ||||
|             } | ||||
|             $password_hashed = hash_password($password); | ||||
|             $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username"); | ||||
|             $stmt->execute(array( | ||||
|               ':password_hashed' => $password_hashed, | ||||
|               ':username_new' => $username_new, | ||||
|               ':username' => $username, | ||||
|               ':active' => $active | ||||
|             )); | ||||
|             if (isset($_data['disable_tfa'])) { | ||||
|               $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); | ||||
|               $stmt->execute(array(':username' => $username)); | ||||
|             } | ||||
|             else { | ||||
|               $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); | ||||
|               $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); | ||||
|             } | ||||
|           } | ||||
|           else { | ||||
|             $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username"); | ||||
|             $stmt->execute(array( | ||||
|               ':username_new' => $username_new, | ||||
|               ':username' => $username, | ||||
|               ':active' => $active | ||||
|             )); | ||||
|             if (isset($_data['disable_tfa'])) { | ||||
|               $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); | ||||
|               $stmt->execute(array(':username' => $username)); | ||||
|             } | ||||
|             else { | ||||
|               $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); | ||||
|               $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); | ||||
|             } | ||||
|           } | ||||
|           $_SESSION['return'][] = array( | ||||
|             'type' => 'success', | ||||
|             'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|             'msg' => array('domain_admin_modified', htmlspecialchars($username)) | ||||
|           ); | ||||
|         } | ||||
|         return true; | ||||
|       } | ||||
|       // Domain administrator | ||||
|       // Can only edit itself | ||||
|       elseif ($_SESSION['mailcow_cc_role'] == "domainadmin") { | ||||
|         $username = $_SESSION['mailcow_cc_username']; | ||||
|         $password_old		= $_data['user_old_pass']; | ||||
|         $password_new	= $_data['user_new_pass']; | ||||
|         $password_new2	= $_data['user_new_pass2']; | ||||
|  | ||||
|         $stmt = $pdo->prepare("SELECT `password` FROM `admin` | ||||
|             WHERE `username` = :user"); | ||||
|         $stmt->execute(array(':user' => $username)); | ||||
|         $row = $stmt->fetch(PDO::FETCH_ASSOC); | ||||
|         if (!verify_hash($row['password'], $password_old)) { | ||||
|           $_SESSION['return'][] = array( | ||||
|             'type' => 'danger', | ||||
|             'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|             'msg' => 'access_denied' | ||||
|           ); | ||||
|           return false; | ||||
|         } | ||||
|         if (password_check($password_new, $password_new2) !== true) { | ||||
|           return false; | ||||
|         } | ||||
|         $password_hashed = hash_password($password_new); | ||||
|         $stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username"); | ||||
|         $stmt->execute(array( | ||||
|           ':password_hashed' => $password_hashed, | ||||
|           ':username' => $username | ||||
|         )); | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'success', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|           'msg' => array('domain_admin_modified', htmlspecialchars($username)) | ||||
|         ); | ||||
|       } | ||||
|     break; | ||||
|     case 'delete': | ||||
|       if ($_SESSION['mailcow_cc_role'] != "admin") { | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'danger', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|           'msg' => 'access_denied' | ||||
|         ); | ||||
|         return false; | ||||
|       } | ||||
|       $usernames = (array)$_data['username']; | ||||
|       foreach ($usernames as $username) { | ||||
|         if (empty(domain_admin('details', $username))) { | ||||
|           $_SESSION['return'][] = array( | ||||
|             'type' => 'danger', | ||||
|             'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|             'msg' => array('username_invalid', $username) | ||||
|           ); | ||||
|           continue; | ||||
|         } | ||||
|         $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); | ||||
|         $stmt->execute(array( | ||||
|           ':username' => $username, | ||||
|         )); | ||||
|         $stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username"); | ||||
|         $stmt->execute(array( | ||||
|           ':username' => $username, | ||||
|         )); | ||||
|         $stmt = $pdo->prepare("DELETE FROM `da_acl` WHERE `username` = :username"); | ||||
|         $stmt->execute(array( | ||||
|           ':username' => $username, | ||||
|         )); | ||||
|         $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); | ||||
|         $stmt->execute(array( | ||||
|           ':username' => $username, | ||||
|         )); | ||||
|         $stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username"); | ||||
|         $stmt->execute(array( | ||||
|           ':username' => $username, | ||||
|         )); | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'success', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|           'msg' => array('domain_admin_removed', htmlspecialchars($username)) | ||||
|         ); | ||||
|       } | ||||
|     break; | ||||
|     case 'get': | ||||
|       $domainadmins = array(); | ||||
|       if ($_SESSION['mailcow_cc_role'] != "admin") { | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'danger', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data_log), | ||||
|           'msg' => 'access_denied' | ||||
|         ); | ||||
|         return false; | ||||
|       } | ||||
|       $stmt = $pdo->query("SELECT DISTINCT | ||||
|         `username` | ||||
|           FROM `domain_admins` | ||||
|             WHERE `username` IN ( | ||||
|               SELECT `username` FROM `admin` | ||||
|                 WHERE `superadmin`!='1' | ||||
|             )"); | ||||
|       $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); | ||||
|       while ($row = array_shift($rows)) { | ||||
|         $domainadmins[] = $row['username']; | ||||
|       } | ||||
|       return $domainadmins; | ||||
|     break; | ||||
|     case 'details': | ||||
|       $domainadmindata = array(); | ||||
|       if ($_SESSION['mailcow_cc_role'] == "domainadmin" && $_data != $_SESSION['mailcow_cc_username']) { | ||||
|         return false; | ||||
|       } | ||||
|       elseif ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { | ||||
|         return false; | ||||
|       } | ||||
|       if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $_data))) { | ||||
|         return false; | ||||
|       } | ||||
|       $stmt = $pdo->prepare("SELECT | ||||
|         `tfa`.`active` AS `tfa_active`, | ||||
|         `domain_admins`.`username`, | ||||
|         `domain_admins`.`created`, | ||||
|         `domain_admins`.`active` AS `active` | ||||
|           FROM `domain_admins` | ||||
|           LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`domain_admins`.`username` | ||||
|             WHERE `domain_admins`.`username`= :domain_admin"); | ||||
|       $stmt->execute(array( | ||||
|         ':domain_admin' => $_data | ||||
|       )); | ||||
|       $row = $stmt->fetch(PDO::FETCH_ASSOC); | ||||
|       if (empty($row)) { | ||||
|         return false; | ||||
|       } | ||||
|       $domainadmindata['username'] = $row['username']; | ||||
|       $domainadmindata['tfa_active'] = (is_null($row['tfa_active'])) ? 0 : $row['tfa_active']; | ||||
|       $domainadmindata['tfa_active_int'] = (is_null($row['tfa_active'])) ? 0 : $row['tfa_active']; | ||||
|       $domainadmindata['active'] = $row['active']; | ||||
|       $domainadmindata['active_int'] = $row['active']; | ||||
|       $domainadmindata['created'] = $row['created']; | ||||
|       // GET SELECTED | ||||
|       $stmt = $pdo->prepare("SELECT `domain` FROM `domain` | ||||
|         WHERE `domain` IN ( | ||||
|           SELECT `domain` FROM `domain_admins` | ||||
|             WHERE `username`= :domain_admin)"); | ||||
|       $stmt->execute(array(':domain_admin' => $_data)); | ||||
|       $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); | ||||
|       while($row = array_shift($rows)) { | ||||
|         $domainadmindata['selected_domains'][] = $row['domain']; | ||||
|       } | ||||
|       // GET UNSELECTED | ||||
|       $stmt = $pdo->prepare("SELECT `domain` FROM `domain` | ||||
|         WHERE `domain` NOT IN ( | ||||
|           SELECT `domain` FROM `domain_admins` | ||||
|             WHERE `username`= :domain_admin)"); | ||||
|       $stmt->execute(array(':domain_admin' => $_data)); | ||||
|       $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); | ||||
|       while($row = array_shift($rows)) { | ||||
|         $domainadmindata['unselected_domains'][] = $row['domain']; | ||||
|       } | ||||
|       if (!isset($domainadmindata['unselected_domains'])) { | ||||
|         $domainadmindata['unselected_domains'] = ""; | ||||
|       } | ||||
|  | ||||
|       return $domainadmindata; | ||||
|     break; | ||||
|   } | ||||
| } | ||||
| function domain_admin_sso($_action, $_data) { | ||||
|   global $pdo; | ||||
|  | ||||
|   switch ($_action) { | ||||
|     case 'check': | ||||
|       $token = $_data; | ||||
|  | ||||
|       $stmt = $pdo->prepare("SELECT `t1`.`username` FROM `da_sso` AS `t1` JOIN `admin` AS `t2` ON `t1`.`username` = `t2`.`username` WHERE `t1`.`token` = :token AND `t1`.`created` > DATE_SUB(NOW(), INTERVAL '30' SECOND) AND `t2`.`active` = 1 AND `t2`.`superadmin` = 0;"); | ||||
|       $stmt->execute(array( | ||||
|         ':token' => preg_replace('/[^a-zA-Z0-9-]/', '', $token) | ||||
|       )); | ||||
|       $return = $stmt->fetch(PDO::FETCH_ASSOC); | ||||
|       return empty($return['username']) ? false : $return['username']; | ||||
|     case 'issue': | ||||
|       if ($_SESSION['mailcow_cc_role'] != "admin") { | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'danger', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data), | ||||
|           'msg' => 'access_denied' | ||||
|         ); | ||||
|         return false; | ||||
|       } | ||||
|  | ||||
|       $username = $_data['username']; | ||||
|  | ||||
|       $stmt = $pdo->prepare("SELECT `username` FROM `domain_admins` | ||||
|         WHERE `username` = :username"); | ||||
|       $stmt->execute(array(':username' => $username)); | ||||
|       $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); | ||||
|  | ||||
|       if ($num_results < 1) { | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'danger', | ||||
|           'log' => array(__FUNCTION__, $_action, $_data), | ||||
|           'msg' => array('object_doesnt_exist', htmlspecialchars($username)) | ||||
|         ); | ||||
|         return false; | ||||
|       } | ||||
|  | ||||
|       $token = implode('-', array( | ||||
|         strtoupper(bin2hex(random_bytes(3))), | ||||
|         strtoupper(bin2hex(random_bytes(3))), | ||||
|         strtoupper(bin2hex(random_bytes(3))), | ||||
|         strtoupper(bin2hex(random_bytes(3))), | ||||
|         strtoupper(bin2hex(random_bytes(3))) | ||||
|       )); | ||||
|  | ||||
|       $stmt = $pdo->prepare("INSERT INTO `da_sso` (`username`, `token`) | ||||
|             VALUES (:username, :token)"); | ||||
|       $stmt->execute(array( | ||||
|         ':username' => $username, | ||||
|         ':token' => $token | ||||
|       )); | ||||
|  | ||||
|       // perform cleanup | ||||
|       $pdo->query("DELETE FROM `da_sso` WHERE created < DATE_SUB(NOW(), INTERVAL '30' SECOND);"); | ||||
|  | ||||
|       return ['token' => $token]; | ||||
|     break; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1739,7 +1739,7 @@ function verify_tfa_login($username, $_data) { | ||||
|               $_SESSION['return'][] =  array( | ||||
|                   'type' => 'danger', | ||||
|                   'log' => array(__FUNCTION__, $username, '*'), | ||||
|                   'msg' => array('webauthn_verification_failed', 'authenticator not found') | ||||
|                   'msg' => array('webauthn_authenticator_failed') | ||||
|               ); | ||||
|               return false; | ||||
|             }  | ||||
| @@ -1748,11 +1748,20 @@ function verify_tfa_login($username, $_data) { | ||||
|                 $_SESSION['return'][] =  array( | ||||
|                     'type' => 'danger', | ||||
|                     'log' => array(__FUNCTION__, $username, '*'), | ||||
|                     'msg' => array('webauthn_verification_failed', 'publicKey not found') | ||||
|                     'msg' => array('webauthn_publickey_failed') | ||||
|                 ); | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             if ($process_webauthn['username'] != $_SESSION['pending_mailcow_cc_username']){ | ||||
|               $_SESSION['return'][] =  array( | ||||
|                   'type' => 'danger', | ||||
|                   'log' => array(__FUNCTION__, $username, '*'), | ||||
|                   'msg' => array('webauthn_username_failed') | ||||
|               ); | ||||
|               return false; | ||||
|             } | ||||
|  | ||||
|             try { | ||||
|                 $WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $process_webauthn['publicKey'], $challenge, null, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN'], $GLOBALS['WEBAUTHN_USER_PRESENT_FLAG']); | ||||
|             } | ||||
| @@ -1784,21 +1793,12 @@ function verify_tfa_login($username, $_data) { | ||||
|                 $_SESSION['return'][] =  array( | ||||
|                   'type' => 'danger', | ||||
|                   'log' => array(__FUNCTION__, $username, '*'), | ||||
|                   'msg' => array('webauthn_verification_failed', 'could not determine user role') | ||||
|                   'msg' => array('webauthn_role_failed') | ||||
|                 ); | ||||
|                 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; | ||||
|   | ||||
| @@ -5264,7 +5264,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { | ||||
|       } | ||||
|     break; | ||||
|   } | ||||
|   if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox', 'resource'))) { | ||||
|   if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox', 'resource')) && getenv('SKIP_SOGO') != "y") { | ||||
|     update_sogo_static_view(); | ||||
|   } | ||||
| } | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,140 +1,140 @@ | ||||
| <?php | ||||
| // Start session | ||||
| if (session_status() !== PHP_SESSION_ACTIVE) { | ||||
|   ini_set("session.cookie_httponly", 1); | ||||
|   ini_set('session.gc_maxlifetime', $SESSION_LIFETIME); | ||||
| } | ||||
|  | ||||
| if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&  | ||||
|   strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == "https") { | ||||
|   if (session_status() !== PHP_SESSION_ACTIVE) { | ||||
|     ini_set("session.cookie_secure", 1); | ||||
|   } | ||||
|   $IS_HTTPS = true; | ||||
| } | ||||
| elseif (isset($_SERVER['HTTPS'])) { | ||||
|   if (session_status() !== PHP_SESSION_ACTIVE) { | ||||
|     ini_set("session.cookie_secure", 1); | ||||
|   } | ||||
|   $IS_HTTPS = true; | ||||
| } | ||||
| else { | ||||
|   $IS_HTTPS = false; | ||||
| } | ||||
|  | ||||
| if (session_status() !== PHP_SESSION_ACTIVE) { | ||||
|   session_start(); | ||||
| } | ||||
|  | ||||
| if (!isset($_SESSION['CSRF']['TOKEN'])) { | ||||
|   $_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32)); | ||||
| } | ||||
|  | ||||
| // Set session UA | ||||
| if (!isset($_SESSION['SESS_REMOTE_UA'])) { | ||||
|   $_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT']; | ||||
| } | ||||
|  | ||||
| // Keep session active | ||||
| if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $SESSION_LIFETIME)) { | ||||
|   session_unset(); | ||||
|   session_destroy(); | ||||
| } | ||||
| $_SESSION['LAST_ACTIVITY'] = time(); | ||||
|  | ||||
| // API | ||||
| if (!empty($_SERVER['HTTP_X_API_KEY'])) { | ||||
|   $stmt = $pdo->prepare("SELECT * FROM `api` WHERE `api_key` = :api_key AND `active` = '1';"); | ||||
|   $stmt->execute(array( | ||||
|     ':api_key' => preg_replace('/[^a-zA-Z0-9-]/', '', $_SERVER['HTTP_X_API_KEY']) | ||||
|   )); | ||||
|   $api_return = $stmt->fetch(PDO::FETCH_ASSOC); | ||||
|   if (!empty($api_return['api_key'])) { | ||||
|     $skip_ip_check = ($api_return['skip_ip_check'] == 1); | ||||
|     $remote = get_remote_ip(false); | ||||
|     $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $api_return['allow_from'])); | ||||
|     if ($skip_ip_check === true || ip_acl($remote, $allow_from)) { | ||||
|       $_SESSION['mailcow_cc_username'] = 'API'; | ||||
|       $_SESSION['mailcow_cc_role'] = 'admin'; | ||||
|       $_SESSION['mailcow_cc_api'] = true; | ||||
|       if ($api_return['access'] == 'rw') { | ||||
|         $_SESSION['mailcow_cc_api_access'] = 'rw'; | ||||
|       } | ||||
|       else { | ||||
|         $_SESSION['mailcow_cc_api_access'] = 'ro'; | ||||
|       } | ||||
|     } | ||||
|     else { | ||||
|       $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']); | ||||
|       error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); | ||||
|       http_response_code(401); | ||||
|       echo json_encode(array( | ||||
|         'type' => 'error', | ||||
|         'msg' => 'api access denied for ip ' . $_SERVER['REMOTE_ADDR'] | ||||
|       )); | ||||
|       unset($_POST); | ||||
|       exit(); | ||||
|     } | ||||
|   } | ||||
|   else { | ||||
|     $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']); | ||||
|     error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); | ||||
|     http_response_code(401); | ||||
|     echo json_encode(array( | ||||
|       'type' => 'error', | ||||
|       'msg' => 'authentication failed' | ||||
|     )); | ||||
|     unset($_POST); | ||||
|     exit(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Handle logouts | ||||
| if (isset($_POST["logout"])) { | ||||
|   if (isset($_SESSION["dual-login"])) { | ||||
|     $_SESSION["mailcow_cc_username"] = $_SESSION["dual-login"]["username"]; | ||||
|     $_SESSION["mailcow_cc_role"] = $_SESSION["dual-login"]["role"]; | ||||
|     unset($_SESSION["dual-login"]); | ||||
|     header("Location: /mailbox"); | ||||
|     exit(); | ||||
|   } | ||||
|   else { | ||||
|     session_regenerate_id(true); | ||||
|     session_unset(); | ||||
|     session_destroy(); | ||||
|     session_write_close(); | ||||
|     header("Location: /"); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Check session | ||||
| function session_check() { | ||||
|   if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) { | ||||
|     return true; | ||||
|   } | ||||
|   if (!isset($_SESSION['SESS_REMOTE_UA']) || ($_SESSION['SESS_REMOTE_UA'] != $_SERVER['HTTP_USER_AGENT'])) { | ||||
|     $_SESSION['return'][] = array( | ||||
|       'type' => 'warning', | ||||
|       'msg' => 'session_ua' | ||||
|     ); | ||||
|     return false; | ||||
|   } | ||||
|   if (!empty($_POST)) { | ||||
|     if ($_SESSION['CSRF']['TOKEN'] != $_POST['csrf_token']) { | ||||
|       $_SESSION['return'][] = array( | ||||
|         'type' => 'warning', | ||||
|         'msg' => 'session_token' | ||||
|       ); | ||||
|       return false; | ||||
|     } | ||||
|     unset($_POST['csrf_token']); | ||||
|     $_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32)); | ||||
|     $_SESSION['CSRF']['TIME'] = time(); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| if (isset($_SESSION['mailcow_cc_role']) && session_check() === false) { | ||||
|   $_POST = array(); | ||||
|   $_FILES = array(); | ||||
| } | ||||
| <?php | ||||
| // Start session | ||||
| if (session_status() !== PHP_SESSION_ACTIVE) { | ||||
|   ini_set("session.cookie_httponly", 1); | ||||
|   ini_set('session.gc_maxlifetime', $SESSION_LIFETIME); | ||||
| } | ||||
|  | ||||
| if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && | ||||
|   strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == "https") { | ||||
|   if (session_status() !== PHP_SESSION_ACTIVE) { | ||||
|     ini_set("session.cookie_secure", 1); | ||||
|   } | ||||
|   $IS_HTTPS = true; | ||||
| } | ||||
| elseif (isset($_SERVER['HTTPS'])) { | ||||
|   if (session_status() !== PHP_SESSION_ACTIVE) { | ||||
|     ini_set("session.cookie_secure", 1); | ||||
|   } | ||||
|   $IS_HTTPS = true; | ||||
| } | ||||
| else { | ||||
|   $IS_HTTPS = false; | ||||
| } | ||||
|  | ||||
| if (session_status() !== PHP_SESSION_ACTIVE) { | ||||
|   session_start(); | ||||
| } | ||||
|  | ||||
| if (!isset($_SESSION['CSRF']['TOKEN'])) { | ||||
|   $_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32)); | ||||
| } | ||||
|  | ||||
| // Set session UA | ||||
| if (!isset($_SESSION['SESS_REMOTE_UA'])) { | ||||
|   $_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT']; | ||||
| } | ||||
|  | ||||
| // Keep session active | ||||
| if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $SESSION_LIFETIME)) { | ||||
|   session_unset(); | ||||
|   session_destroy(); | ||||
| } | ||||
| $_SESSION['LAST_ACTIVITY'] = time(); | ||||
|  | ||||
| // API | ||||
| if (!empty($_SERVER['HTTP_X_API_KEY'])) { | ||||
|   $stmt = $pdo->prepare("SELECT * FROM `api` WHERE `api_key` = :api_key AND `active` = '1';"); | ||||
|   $stmt->execute(array( | ||||
|     ':api_key' => preg_replace('/[^a-zA-Z0-9-]/', '', $_SERVER['HTTP_X_API_KEY']) | ||||
|   )); | ||||
|   $api_return = $stmt->fetch(PDO::FETCH_ASSOC); | ||||
|   if (!empty($api_return['api_key'])) { | ||||
|     $skip_ip_check = ($api_return['skip_ip_check'] == 1); | ||||
|     $remote = get_remote_ip(false); | ||||
|     $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $api_return['allow_from'])); | ||||
|     if ($skip_ip_check === true || ip_acl($remote, $allow_from)) { | ||||
|       $_SESSION['mailcow_cc_username'] = 'API'; | ||||
|       $_SESSION['mailcow_cc_role'] = 'admin'; | ||||
|       $_SESSION['mailcow_cc_api'] = true; | ||||
|       if ($api_return['access'] == 'rw') { | ||||
|         $_SESSION['mailcow_cc_api_access'] = 'rw'; | ||||
|       } | ||||
|       else { | ||||
|         $_SESSION['mailcow_cc_api_access'] = 'ro'; | ||||
|       } | ||||
|     } | ||||
|     else { | ||||
|       $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']); | ||||
|       error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); | ||||
|       http_response_code(401); | ||||
|       echo json_encode(array( | ||||
|         'type' => 'error', | ||||
|         'msg' => 'api access denied for ip ' . $_SERVER['REMOTE_ADDR'] | ||||
|       )); | ||||
|       unset($_POST); | ||||
|       exit(); | ||||
|     } | ||||
|   } | ||||
|   else { | ||||
|     $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']); | ||||
|     error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); | ||||
|     http_response_code(401); | ||||
|     echo json_encode(array( | ||||
|       'type' => 'error', | ||||
|       'msg' => 'authentication failed' | ||||
|     )); | ||||
|     unset($_POST); | ||||
|     exit(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Handle logouts | ||||
| if (isset($_POST["logout"])) { | ||||
|   if (isset($_SESSION["dual-login"])) { | ||||
|     $_SESSION["mailcow_cc_username"] = $_SESSION["dual-login"]["username"]; | ||||
|     $_SESSION["mailcow_cc_role"] = $_SESSION["dual-login"]["role"]; | ||||
|     unset($_SESSION["dual-login"]); | ||||
|     header("Location: /mailbox"); | ||||
|     exit(); | ||||
|   } | ||||
|   else { | ||||
|     session_regenerate_id(true); | ||||
|     session_unset(); | ||||
|     session_destroy(); | ||||
|     session_write_close(); | ||||
|     header("Location: /"); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Check session | ||||
| function session_check() { | ||||
|   if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) { | ||||
|     return true; | ||||
|   } | ||||
|   if (!isset($_SESSION['SESS_REMOTE_UA']) || ($_SESSION['SESS_REMOTE_UA'] != $_SERVER['HTTP_USER_AGENT'])) { | ||||
|     $_SESSION['return'][] = array( | ||||
|       'type' => 'warning', | ||||
|       'msg' => 'session_ua' | ||||
|     ); | ||||
|     return false; | ||||
|   } | ||||
|   if (!empty($_POST)) { | ||||
|     if ($_SESSION['CSRF']['TOKEN'] != $_POST['csrf_token']) { | ||||
|       $_SESSION['return'][] = array( | ||||
|         'type' => 'warning', | ||||
|         'msg' => 'session_token' | ||||
|       ); | ||||
|       return false; | ||||
|     } | ||||
|     unset($_POST['csrf_token']); | ||||
|     $_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32)); | ||||
|     $_SESSION['CSRF']['TIME'] = time(); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| if (isset($_SESSION['mailcow_cc_role']) && session_check() === false) { | ||||
|   $_POST = array(); | ||||
|   $_FILES = array(); | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,15 @@ | ||||
| <?php | ||||
| // SSO Domain Admin | ||||
| if (!empty($_GET['sso_token'])) { | ||||
|   $username = domain_admin_sso('check', $_GET['sso_token']); | ||||
|  | ||||
|   if ($username !== false) { | ||||
|     $_SESSION['mailcow_cc_username'] = $username; | ||||
|     $_SESSION['mailcow_cc_role'] = 'domainadmin'; | ||||
|     header('Location: /mailbox'); | ||||
|   } | ||||
| } | ||||
|  | ||||
| if (isset($_POST["verify_tfa_login"])) { | ||||
|   if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST)) { | ||||
|     $_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username']; | ||||
| @@ -6,7 +17,7 @@ if (isset($_POST["verify_tfa_login"])) { | ||||
|     unset($_SESSION['pending_mailcow_cc_username']); | ||||
|     unset($_SESSION['pending_mailcow_cc_role']); | ||||
|     unset($_SESSION['pending_tfa_methods']); | ||||
| 	 | ||||
|  | ||||
|     header("Location: /user"); | ||||
|   } else { | ||||
|     unset($_SESSION['pending_mailcow_cc_username']); | ||||
| @@ -34,7 +45,7 @@ if (isset($_POST["quick_delete"])) { | ||||
| if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { | ||||
| 	$login_user = strtolower(trim($_POST["login_user"])); | ||||
| 	$as = check_login($login_user, $_POST["pass_user"]); | ||||
|    | ||||
|  | ||||
| 	if ($as == "admin") { | ||||
| 		$_SESSION['mailcow_cc_username'] = $login_user; | ||||
| 		$_SESSION['mailcow_cc_role'] = "admin"; | ||||
|   | ||||
| @@ -124,7 +124,7 @@ $MAILCOW_APPS = array( | ||||
| ); | ||||
|  | ||||
| // Rows until pagination begins | ||||
| $PAGINATION_SIZE = 20; | ||||
| $PAGINATION_SIZE = 25; | ||||
|  | ||||
| // Default number of rows/lines to display (log table) | ||||
| $LOG_LINES = 1000; | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -34,7 +34,7 @@ $(document).ready(function() { | ||||
|   }); | ||||
|  | ||||
|   // set update loop container list | ||||
|   containersToUpdate = {} | ||||
|   containersToUpdate = {}; | ||||
|   // set default ChartJs Font Color | ||||
|   Chart.defaults.color = '#999'; | ||||
|   // create host cpu and mem charts | ||||
| @@ -44,14 +44,13 @@ $(document).ready(function() { | ||||
|     check_update(mailcow_info.version_tag, mailcow_info.project_url); | ||||
|   } | ||||
|   $("#maiclow_version").click(function(){ | ||||
|     if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin" || | ||||
|        mailcow_info.branch !== "master") | ||||
|     if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin" || mailcow_info.branch !== "master") | ||||
|       return; | ||||
|  | ||||
|     showVersionModal("Version " + mailcow_info.version_tag, mailcow_info.version_tag); | ||||
|   }) | ||||
|   // get public ips | ||||
|   $("#host_show_ip").click(function(){   | ||||
|   $("#host_show_ip").click(function(){ | ||||
|     $("#host_show_ip").find(".text").addClass("d-none"); | ||||
|     $("#host_show_ip").find(".spinner-border").removeClass("d-none"); | ||||
|  | ||||
| @@ -76,7 +75,7 @@ $(document).ready(function() { | ||||
|       $("#host_ipv6").addClass("d-block"); | ||||
|     }).catch(function(error){ | ||||
|       console.log(error); | ||||
|        | ||||
|  | ||||
|       $("#host_ipv6").removeClass("d-none"); | ||||
|       $("#host_ipv6").addClass("d-block"); | ||||
|       $("#host_ipv6").addClass("text-danger"); | ||||
| @@ -119,10 +118,11 @@ jQuery(function($){ | ||||
|     } | ||||
|  | ||||
|     var table = $('#autodiscover_log').DataTable({ | ||||
| 			responsive: true, | ||||
|       responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       pageLength: log_pagination_size, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
| @@ -188,10 +188,11 @@ jQuery(function($){ | ||||
|     } | ||||
|  | ||||
|     var table = $('#postfix_log').DataTable({ | ||||
| 			responsive: true, | ||||
|       responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       pageLength: log_pagination_size, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
| @@ -242,10 +243,11 @@ jQuery(function($){ | ||||
|     } | ||||
|  | ||||
|     var table = $('#watchdog_log').DataTable({ | ||||
| 			responsive: true, | ||||
|       responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       pageLength: log_pagination_size, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
| @@ -300,10 +302,11 @@ jQuery(function($){ | ||||
|     } | ||||
|  | ||||
|     var table =  $('#api_log').DataTable({ | ||||
| 			responsive: true, | ||||
|       responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       pageLength: log_pagination_size, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
| @@ -352,7 +355,7 @@ jQuery(function($){ | ||||
|         } | ||||
|       ] | ||||
|     }); | ||||
|      | ||||
|  | ||||
|     table.on('responsive-resize', function (e, datatable, columns){ | ||||
|       hideTableExpandCollapseBtn('#tab-api-logs', '#api_log'); | ||||
|     }); | ||||
| @@ -365,10 +368,11 @@ jQuery(function($){ | ||||
|     } | ||||
|  | ||||
|     var table = $('#rl_log').DataTable({ | ||||
| 			responsive: true, | ||||
|       responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       pageLength: log_pagination_size, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
| @@ -455,7 +459,7 @@ jQuery(function($){ | ||||
|         } | ||||
|       ] | ||||
|     }); | ||||
|      | ||||
|  | ||||
|     table.on('responsive-resize', function (e, datatable, columns){ | ||||
|       hideTableExpandCollapseBtn('#tab-rl-logs', '#rl_log'); | ||||
|     }); | ||||
| @@ -468,10 +472,11 @@ jQuery(function($){ | ||||
|     } | ||||
|  | ||||
|     var table = $('#ui_logs').DataTable({ | ||||
| 			responsive: true, | ||||
|       responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       pageLength: log_pagination_size, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
| @@ -538,7 +543,7 @@ jQuery(function($){ | ||||
|         } | ||||
|       ] | ||||
|     }); | ||||
|      | ||||
|  | ||||
|     table.on('responsive-resize', function (e, datatable, columns){ | ||||
|       hideTableExpandCollapseBtn('#tab-ui-logs', '#ui_log'); | ||||
|     }); | ||||
| @@ -551,10 +556,11 @@ jQuery(function($){ | ||||
|     } | ||||
|  | ||||
|     var table = $('#sasl_logs').DataTable({ | ||||
| 			responsive: true, | ||||
|       responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       pageLength: log_pagination_size, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
| @@ -598,7 +604,7 @@ jQuery(function($){ | ||||
|         } | ||||
|       ] | ||||
|     }); | ||||
|      | ||||
|  | ||||
|     table.on('responsive-resize', function (e, datatable, columns){ | ||||
|       hideTableExpandCollapseBtn('#tab-sasl-logs', '#sasl_logs'); | ||||
|     }); | ||||
| @@ -611,10 +617,11 @@ jQuery(function($){ | ||||
|     } | ||||
|  | ||||
|     var table = $('#acme_log').DataTable({ | ||||
| 			responsive: true, | ||||
|       responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       pageLength: log_pagination_size, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
| @@ -647,7 +654,7 @@ jQuery(function($){ | ||||
|         } | ||||
|       ] | ||||
|     }); | ||||
|      | ||||
|  | ||||
|     table.on('responsive-resize', function (e, datatable, columns){ | ||||
|       hideTableExpandCollapseBtn('#tab-acme-logs', '#acme_log'); | ||||
|     }); | ||||
| @@ -660,10 +667,11 @@ jQuery(function($){ | ||||
|     } | ||||
|  | ||||
|     var table = $('#netfilter_log').DataTable({ | ||||
| 			responsive: true, | ||||
|       responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       pageLength: log_pagination_size, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
| @@ -701,7 +709,7 @@ jQuery(function($){ | ||||
|         } | ||||
|       ] | ||||
|     }); | ||||
|      | ||||
|  | ||||
|     table.on('responsive-resize', function (e, datatable, columns){ | ||||
|       hideTableExpandCollapseBtn('#tab-netfilter-logs', '#netfilter_log'); | ||||
|     }); | ||||
| @@ -714,10 +722,11 @@ jQuery(function($){ | ||||
|     } | ||||
|  | ||||
|     var table = $('#sogo_log').DataTable({ | ||||
| 			responsive: true, | ||||
|       responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       pageLength: log_pagination_size, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
| @@ -755,7 +764,7 @@ jQuery(function($){ | ||||
|         } | ||||
|       ] | ||||
|     }); | ||||
|      | ||||
|  | ||||
|     table.on('responsive-resize', function (e, datatable, columns){ | ||||
|       hideTableExpandCollapseBtn('#tab-sogo-logs', '#sogo_log'); | ||||
|     }); | ||||
| @@ -768,10 +777,11 @@ jQuery(function($){ | ||||
|     } | ||||
|  | ||||
|     var table = $('#dovecot_log').DataTable({ | ||||
| 			responsive: true, | ||||
|       responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       pageLength: log_pagination_size, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
| @@ -883,10 +893,11 @@ jQuery(function($){ | ||||
|     } | ||||
|  | ||||
|     var table = $('#rspamd_history').DataTable({ | ||||
| 			responsive: true, | ||||
|       responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       pageLength: log_pagination_size, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
| @@ -983,7 +994,7 @@ jQuery(function($){ | ||||
|         } | ||||
|       ] | ||||
|     }); | ||||
|      | ||||
|  | ||||
|     table.on('responsive-resize', function (e, datatable, columns){ | ||||
|       hideTableExpandCollapseBtn('#tab-rspamd-history', '#rspamd_history'); | ||||
|     }); | ||||
| @@ -998,31 +1009,31 @@ jQuery(function($){ | ||||
|         item.rcpt = escapeHtml(item.rcpt_smtp.join(", ")); | ||||
|       } | ||||
|       item.symbols = Object.keys(item.symbols).sort(function (a, b) { | ||||
|         if (item.symbols[a].score === 0) return 1 | ||||
|         if (item.symbols[b].score === 0) return -1 | ||||
|         if (item.symbols[a].score === 0) return 1; | ||||
|         if (item.symbols[b].score === 0) return -1; | ||||
|         if (item.symbols[b].score < 0 && item.symbols[a].score < 0) { | ||||
|           return item.symbols[a].score - item.symbols[b].score | ||||
|           return item.symbols[a].score - item.symbols[b].score; | ||||
|         } | ||||
|         if (item.symbols[b].score > 0 && item.symbols[a].score > 0) { | ||||
|           return item.symbols[b].score - item.symbols[a].score | ||||
|           return item.symbols[b].score - item.symbols[a].score; | ||||
|         } | ||||
|         return item.symbols[b].score - item.symbols[a].score | ||||
|         return item.symbols[b].score - item.symbols[a].score; | ||||
|       }).map(function(key) { | ||||
|         var sym = item.symbols[key]; | ||||
|         if (sym.score < 0) { | ||||
|           sym.score_formatted = '(<span class="text-success"><b>' + sym.score + '</b></span>)' | ||||
|           sym.score_formatted = '(<span class="text-success"><b>' + sym.score + '</b></span>)'; | ||||
|         } | ||||
|         else if (sym.score === 0) { | ||||
|           sym.score_formatted = '(<span><b>' + sym.score + '</b></span>)' | ||||
|           sym.score_formatted = '(<span><b>' + sym.score + '</b></span>)'; | ||||
|         } | ||||
|         else { | ||||
|           sym.score_formatted = '(<span class="text-danger"><b>' + sym.score + '</b></span>)' | ||||
|           sym.score_formatted = '(<span class="text-danger"><b>' + sym.score + '</b></span>)'; | ||||
|         } | ||||
|         var str = '<strong>' + key + '</strong> ' + sym.score_formatted; | ||||
|         if (sym.options) { | ||||
|           str += ' [' + escapeHtml(sym.options.join(", ")) + "]"; | ||||
|         } | ||||
|         return str | ||||
|         return str; | ||||
|       }).join('<br>\n'); | ||||
|       item.subject = escapeHtml(item.subject); | ||||
|       var scan_time = item.time_real.toFixed(3); | ||||
| @@ -1155,14 +1166,14 @@ jQuery(function($){ | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|     return data | ||||
|     return data; | ||||
|   }; | ||||
|   $('.add_log_lines').on('click', function (e) { | ||||
|     e.preventDefault(); | ||||
|     var log_table= $(this).data("table") | ||||
|     var new_nrows = $(this).data("nrows") | ||||
|     var post_process = $(this).data("post-process") | ||||
|     var log_url = $(this).data("log-url") | ||||
|     var log_table= $(this).data("table"); | ||||
|     var new_nrows = $(this).data("nrows"); | ||||
|     var post_process = $(this).data("post-process"); | ||||
|     var log_url = $(this).data("log-url"); | ||||
|     if (log_table === undefined || new_nrows === undefined || post_process === undefined || log_url === undefined) { | ||||
|       console.log("no data-table or data-nrows or log_url or data-post-process attr found"); | ||||
|       return; | ||||
| @@ -1184,9 +1195,9 @@ jQuery(function($){ | ||||
|   }) | ||||
|   function hideTableExpandCollapseBtn(tab, table){ | ||||
|     if ($(table).hasClass('collapsed')) | ||||
|       $(tab).find(".table_collapse_option").show();  | ||||
|       $(tab).find(".table_collapse_option").show(); | ||||
|     else | ||||
|       $(tab).find(".table_collapse_option").hide();  | ||||
|       $(tab).find(".table_collapse_option").hide(); | ||||
|   } | ||||
|  | ||||
|   // detect element visibility changes | ||||
| @@ -1220,7 +1231,6 @@ jQuery(function($){ | ||||
|   onVisible("[id^=rspamd_donut]", () => rspamd_pie_graph()); | ||||
|  | ||||
|  | ||||
|  | ||||
|   // start polling host stats if tab is active | ||||
|   onVisible("[id^=tab-containers]", () => update_stats()); | ||||
|   // start polling container stats if collapse is active | ||||
| @@ -1303,9 +1313,9 @@ function update_stats(timeout=5){ | ||||
|       if (mem_chart.data.labels.length > 30) mem_chart.data.labels.shift(); | ||||
|  | ||||
|       cpu_chart.data.datasets[0].data.push(data.cpu.usage); | ||||
|       if (cpu_chart.data.datasets[0].data.length > 30)  cpu_chart.data.datasets[0].data.shift(); | ||||
|       if (cpu_chart.data.datasets[0].data.length > 30) cpu_chart.data.datasets[0].data.shift(); | ||||
|       mem_chart.data.datasets[0].data.push(data.memory.usage); | ||||
|       if (mem_chart.data.datasets[0].data.length > 30)  mem_chart.data.datasets[0].data.shift(); | ||||
|       if (mem_chart.data.datasets[0].data.length > 30) mem_chart.data.datasets[0].data.shift(); | ||||
|  | ||||
|       cpu_chart.update(); | ||||
|       mem_chart.update(); | ||||
| @@ -1464,23 +1474,23 @@ function createReadWriteChart(chart_id, read_lable, write_lable){ | ||||
|   }; | ||||
|   var optionsNet = { | ||||
|     interaction: { | ||||
|         mode: 'index' | ||||
|       mode: 'index' | ||||
|     }, | ||||
|     scales: { | ||||
|       yAxis: { | ||||
|         min: 0, | ||||
|         grid: { | ||||
|             display: false | ||||
|           display: false | ||||
|         }, | ||||
|         ticks: { | ||||
|           callback: function(i, index, ticks) { | ||||
|              return formatBytes(i); | ||||
|             return formatBytes(i); | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       xAxis: { | ||||
|         grid: { | ||||
|             display: false | ||||
|           display: false | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| @@ -1528,13 +1538,13 @@ function createHostCpuAndMemChart(){ | ||||
|   }; | ||||
|   var optionsCpu = { | ||||
|     interaction: { | ||||
|         mode: 'index' | ||||
|       mode: 'index' | ||||
|     }, | ||||
|     scales: { | ||||
|       yAxis: { | ||||
|         min: 0, | ||||
|         grid: { | ||||
|             display: false | ||||
|           display: false | ||||
|         }, | ||||
|         ticks: { | ||||
|           callback: function(i, index, ticks) { | ||||
| @@ -1544,7 +1554,7 @@ function createHostCpuAndMemChart(){ | ||||
|       }, | ||||
|       xAxis: { | ||||
|         grid: { | ||||
|             display: false | ||||
|           display: false | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| @@ -1566,13 +1576,13 @@ function createHostCpuAndMemChart(){ | ||||
|   }; | ||||
|   var optionsMem = { | ||||
|     interaction: { | ||||
|         mode: 'index' | ||||
|       mode: 'index' | ||||
|     }, | ||||
|     scales: { | ||||
|       yAxis: { | ||||
|         min: 0, | ||||
|         grid: { | ||||
|             display: false | ||||
|           display: false | ||||
|         }, | ||||
|         ticks: { | ||||
|           callback: function(i, index, ticks) { | ||||
| @@ -1582,7 +1592,7 @@ function createHostCpuAndMemChart(){ | ||||
|       }, | ||||
|       xAxis: { | ||||
|         grid: { | ||||
|             display: false | ||||
|           display: false | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| @@ -1678,22 +1688,22 @@ function parseGithubMarkdownLinks(inputText) { | ||||
|  | ||||
|   replacePattern1 = /(\b(https?):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; | ||||
|   replacedText = inputText.replace(replacePattern1, (matched, index, original, input_string) => { | ||||
|       if (matched.includes('github.com')){ | ||||
|         // return short link if it's github link | ||||
|         last_uri_path = matched.split('/'); | ||||
|         last_uri_path = last_uri_path[last_uri_path.length - 1]; | ||||
|     if (matched.includes('github.com')){ | ||||
|       // return short link if it's github link | ||||
|       last_uri_path = matched.split('/'); | ||||
|       last_uri_path = last_uri_path[last_uri_path.length - 1]; | ||||
|  | ||||
|         // adjust Full Changelog link to match last git version and new git version, if link is a compare link | ||||
|         if (matched.includes('/compare/') && mailcow_info.last_version_tag !== ''){ | ||||
|           matched = matched.replace(last_uri_path,  mailcow_info.last_version_tag + '...' + mailcow_info.version_tag); | ||||
|           last_uri_path = mailcow_info.last_version_tag + '...' + mailcow_info.version_tag; | ||||
|         } | ||||
|       // adjust Full Changelog link to match last git version and new git version, if link is a compare link | ||||
|       if (matched.includes('/compare/') && mailcow_info.last_version_tag !== ''){ | ||||
|         matched = matched.replace(last_uri_path,  mailcow_info.last_version_tag + '...' + mailcow_info.version_tag); | ||||
|         last_uri_path = mailcow_info.last_version_tag + '...' + mailcow_info.version_tag; | ||||
|       } | ||||
|  | ||||
|         return '<a href="' + matched + '" target="_blank">' + last_uri_path + '</a><br>'; | ||||
|       }; | ||||
|       return '<a href="' + matched + '" target="_blank">' + last_uri_path + '</a><br>'; | ||||
|     }; | ||||
|  | ||||
|       // if it's not a github link, return complete link | ||||
|       return '<a href="' + matched + '" target="_blank">' + matched + '</a>'; | ||||
|     // if it's not a github link, return complete link | ||||
|     return '<a href="' + matched + '" target="_blank">' + matched + '</a>'; | ||||
|   }); | ||||
|  | ||||
|   return replacedText; | ||||
|   | ||||
| @@ -1,220 +1,222 @@ | ||||
| $(document).ready(function() { | ||||
|   $(".arrow-toggle").on('click', function(e) { e.preventDefault(); $(this).find('.arrow').toggleClass("animation"); }); | ||||
|   $("#pushover_delete").click(function() { return confirm(lang.delete_ays); }); | ||||
|   $(".goto_checkbox").click(function( event ) { | ||||
|    $("form[data-id='editalias'] .goto_checkbox").not(this).prop('checked', false); | ||||
|     if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) { | ||||
|       $('#textarea_alias_goto').prop('disabled', true); | ||||
|     } | ||||
|     else { | ||||
|       $("#textarea_alias_goto").removeAttr('disabled'); | ||||
|     } | ||||
|   }); | ||||
|   $("#disable_sender_check").click(function( event ) { | ||||
|     if ($("form[data-id='editmailbox'] #disable_sender_check:checked").length > 0) { | ||||
|       $('#editSelectSenderACL').prop('disabled', true); | ||||
|       $('#editSelectSenderACL').selectpicker('refresh'); | ||||
|     } | ||||
|     else { | ||||
|       $('#editSelectSenderACL').prop('disabled', false); | ||||
|       $('#editSelectSenderACL').selectpicker('refresh'); | ||||
|     } | ||||
|   }); | ||||
|   if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) { | ||||
|     $('#textarea_alias_goto').prop('disabled', true); | ||||
|   } | ||||
|  | ||||
|   $("#mailbox-password-warning-close").click(function( event ) { | ||||
|     $('#mailbox-passwd-hidden-info').addClass('hidden'); | ||||
|     $('#mailbox-passwd-form-groups').removeClass('hidden'); | ||||
|   }); | ||||
|   // Sender ACL | ||||
|   if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){ | ||||
|     $("#sender_acl_disabled").show(); | ||||
|   } | ||||
|   $('#editSelectSenderACL').change(function() { | ||||
|     if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){ | ||||
|       $("#sender_acl_disabled").show(); | ||||
|     } | ||||
|     else { | ||||
|       $("#sender_acl_disabled").hide(); | ||||
|     } | ||||
|   }); | ||||
|   // Resources | ||||
|   if ($("#editSelectMultipleBookings").val() == "custom") { | ||||
|     $("#multiple_bookings_custom_div").show(); | ||||
|     $('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val()); | ||||
|   } | ||||
|   $("#editSelectMultipleBookings").change(function() { | ||||
|     $('input[name=multiple_bookings]').val($("#editSelectMultipleBookings").val()); | ||||
|     if ($('input[name=multiple_bookings]').val() == "custom") { | ||||
|       $("#multiple_bookings_custom_div").show(); | ||||
|     } | ||||
|     else { | ||||
|       $("#multiple_bookings_custom_div").hide(); | ||||
|     } | ||||
|   }); | ||||
|   $("#multiple_bookings_custom").bind("change keypress keyup blur", function() { | ||||
|     $('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val()); | ||||
|   }); | ||||
|  | ||||
|   // load tags | ||||
|   if ($('#tags').length){ | ||||
|     var tagsEl = $('#tags').parent().find('.tag-values')[0]; | ||||
|     console.log($(tagsEl).val()) | ||||
|     var tags = JSON.parse($(tagsEl).val()); | ||||
|     $(tagsEl).val(""); | ||||
|      | ||||
|     for (var i = 0; i < tags.length; i++) | ||||
|       addTag($('#tags'), tags[i]); | ||||
|   } | ||||
| }); | ||||
|  | ||||
| jQuery(function($){ | ||||
|   // http://stackoverflow.com/questions/46155/validate-email-address-in-javascript | ||||
|   function validateEmail(email) { | ||||
|     var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; | ||||
|     return re.test(email); | ||||
|   } | ||||
|   function draw_wl_policy_domain_table() { | ||||
|     $('#wl_policy_domain_table').DataTable({ | ||||
| 			responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
|       language: lang_datatables, | ||||
|       ajax: { | ||||
|         type: "GET", | ||||
|         url: '/api/v1/get/policy_wl_domain/' + table_for_domain, | ||||
|         dataSrc: function(data){ | ||||
|           $.each(data, function (i, item) { | ||||
|             if (!validateEmail(item.object)) { | ||||
|               item.chkbox = '<input type="checkbox" data-id="policy_wl_domain" name="multi_select" value="' + item.prefid + '" />'; | ||||
|             } | ||||
|             else { | ||||
|               item.chkbox = '<input type="checkbox" disabled title="' + lang_user.spamfilter_table_domain_policy + '" />'; | ||||
|             } | ||||
|           }); | ||||
|  | ||||
|           return data; | ||||
|         } | ||||
|       }, | ||||
|       columns: [ | ||||
|           { | ||||
|             // placeholder, so checkbox will not block child row toggle | ||||
|             title: '', | ||||
|             data: null, | ||||
|             searchable: false, | ||||
|             orderable: false, | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: '', | ||||
|             data: 'chkbox', | ||||
|             searchable: false, | ||||
|             orderable: false, | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: 'ID', | ||||
|             data: 'prefid', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: lang_user.spamfilter_table_rule, | ||||
|             data: 'value', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: 'Scope', | ||||
|             data: 'object', | ||||
|             defaultContent: '' | ||||
|           } | ||||
|       ] | ||||
|     }); | ||||
|   } | ||||
|   function draw_bl_policy_domain_table() { | ||||
|     $('#bl_policy_domain_table').DataTable({ | ||||
| 			responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
|       language: lang_datatables, | ||||
|       ajax: { | ||||
|         type: "GET", | ||||
|         url: '/api/v1/get/policy_bl_domain/' + table_for_domain, | ||||
|         dataSrc: function(data){ | ||||
|           $.each(data, function (i, item) { | ||||
|             if (!validateEmail(item.object)) { | ||||
|               item.chkbox = '<input type="checkbox" data-id="policy_bl_domain" name="multi_select" value="' + item.prefid + '" />'; | ||||
|             } | ||||
|             else { | ||||
|               item.chkbox = '<input type="checkbox" disabled tooltip="' + lang_user.spamfilter_table_domain_policy + '" />'; | ||||
|             } | ||||
|           }); | ||||
|  | ||||
|           return data; | ||||
|         } | ||||
|       }, | ||||
|       columns: [ | ||||
|           { | ||||
|             // placeholder, so checkbox will not block child row toggle | ||||
|             title: '', | ||||
|             data: null, | ||||
|             searchable: false, | ||||
|             orderable: false, | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: '', | ||||
|             data: 'chkbox', | ||||
|             searchable: false, | ||||
|             orderable: false, | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: 'ID', | ||||
|             data: 'prefid', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: lang_user.spamfilter_table_rule, | ||||
|             data: 'value', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: 'Scope', | ||||
|             data: 'object', | ||||
|             defaultContent: '' | ||||
|           } | ||||
|       ] | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|    | ||||
|   // detect element visibility changes | ||||
|   function onVisible(element, callback) { | ||||
|     $(document).ready(function() { | ||||
|       element_object = document.querySelector(element); | ||||
|       if (element_object === null) return; | ||||
|  | ||||
|       new IntersectionObserver((entries, observer) => { | ||||
|         entries.forEach(entry => { | ||||
|           if(entry.intersectionRatio > 0) { | ||||
|             callback(element_object); | ||||
|             observer.disconnect(); | ||||
|           } | ||||
|         }); | ||||
|       }).observe(element_object); | ||||
|     }); | ||||
|   } | ||||
|   // Draw Table if tab is active | ||||
|   onVisible("[id^=wl_policy_domain_table]", () => draw_wl_policy_domain_table()); | ||||
|   onVisible("[id^=bl_policy_domain_table]", () => draw_bl_policy_domain_table()); | ||||
| }); | ||||
| $(document).ready(function() { | ||||
|   $(".arrow-toggle").on('click', function(e) { e.preventDefault(); $(this).find('.arrow').toggleClass("animation"); }); | ||||
|   $("#pushover_delete").click(function() { return confirm(lang.delete_ays); }); | ||||
|   $(".goto_checkbox").click(function( event ) { | ||||
|     $("form[data-id='editalias'] .goto_checkbox").not(this).prop('checked', false); | ||||
|     if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) { | ||||
|       $('#textarea_alias_goto').prop('disabled', true); | ||||
|     } | ||||
|     else { | ||||
|       $("#textarea_alias_goto").removeAttr('disabled'); | ||||
|     } | ||||
|   }); | ||||
|   $("#disable_sender_check").click(function( event ) { | ||||
|     if ($("form[data-id='editmailbox'] #disable_sender_check:checked").length > 0) { | ||||
|       $('#editSelectSenderACL').prop('disabled', true); | ||||
|       $('#editSelectSenderACL').selectpicker('refresh'); | ||||
|     } | ||||
|     else { | ||||
|       $('#editSelectSenderACL').prop('disabled', false); | ||||
|       $('#editSelectSenderACL').selectpicker('refresh'); | ||||
|     } | ||||
|   }); | ||||
|   if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) { | ||||
|     $('#textarea_alias_goto').prop('disabled', true); | ||||
|   } | ||||
|  | ||||
|   $("#mailbox-password-warning-close").click(function( event ) { | ||||
|     $('#mailbox-passwd-hidden-info').addClass('hidden'); | ||||
|     $('#mailbox-passwd-form-groups').removeClass('hidden'); | ||||
|   }); | ||||
|   // Sender ACL | ||||
|   if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){ | ||||
|     $("#sender_acl_disabled").show(); | ||||
|   } | ||||
|   $('#editSelectSenderACL').change(function() { | ||||
|     if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){ | ||||
|       $("#sender_acl_disabled").show(); | ||||
|     } | ||||
|     else { | ||||
|       $("#sender_acl_disabled").hide(); | ||||
|     } | ||||
|   }); | ||||
|   // Resources | ||||
|   if ($("#editSelectMultipleBookings").val() == "custom") { | ||||
|     $("#multiple_bookings_custom_div").show(); | ||||
|     $('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val()); | ||||
|   } | ||||
|   $("#editSelectMultipleBookings").change(function() { | ||||
|     $('input[name=multiple_bookings]').val($("#editSelectMultipleBookings").val()); | ||||
|     if ($('input[name=multiple_bookings]').val() == "custom") { | ||||
|       $("#multiple_bookings_custom_div").show(); | ||||
|     } | ||||
|     else { | ||||
|       $("#multiple_bookings_custom_div").hide(); | ||||
|     } | ||||
|   }); | ||||
|   $("#multiple_bookings_custom").bind("change keypress keyup blur", function() { | ||||
|     $('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val()); | ||||
|   }); | ||||
|  | ||||
|   // load tags | ||||
|   if ($('#tags').length){ | ||||
|     var tagsEl = $('#tags').parent().find('.tag-values')[0]; | ||||
|     console.log($(tagsEl).val()) | ||||
|     var tags = JSON.parse($(tagsEl).val()); | ||||
|     $(tagsEl).val(""); | ||||
|  | ||||
|     for (var i = 0; i < tags.length; i++) | ||||
|       addTag($('#tags'), tags[i]); | ||||
|   } | ||||
| }); | ||||
|  | ||||
| jQuery(function($){ | ||||
|   // http://stackoverflow.com/questions/46155/validate-email-address-in-javascript | ||||
|   function validateEmail(email) { | ||||
|     var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; | ||||
|     return re.test(email); | ||||
|   } | ||||
|   function draw_wl_policy_domain_table() { | ||||
|     $('#wl_policy_domain_table').DataTable({ | ||||
|       responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       pageLength: pagination_size, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
|       language: lang_datatables, | ||||
|       ajax: { | ||||
|         type: "GET", | ||||
|         url: '/api/v1/get/policy_wl_domain/' + table_for_domain, | ||||
|         dataSrc: function(data){ | ||||
|           $.each(data, function (i, item) { | ||||
|             if (!validateEmail(item.object)) { | ||||
|               item.chkbox = '<input type="checkbox" data-id="policy_wl_domain" name="multi_select" value="' + item.prefid + '" />'; | ||||
|             } | ||||
|             else { | ||||
|               item.chkbox = '<input type="checkbox" disabled title="' + lang_user.spamfilter_table_domain_policy + '" />'; | ||||
|             } | ||||
|           }); | ||||
|  | ||||
|           return data; | ||||
|         } | ||||
|       }, | ||||
|       columns: [ | ||||
|         { | ||||
|           // placeholder, so checkbox will not block child row toggle | ||||
|           title: '', | ||||
|           data: null, | ||||
|           searchable: false, | ||||
|           orderable: false, | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: '', | ||||
|           data: 'chkbox', | ||||
|           searchable: false, | ||||
|           orderable: false, | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: 'ID', | ||||
|           data: 'prefid', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: lang_user.spamfilter_table_rule, | ||||
|           data: 'value', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: 'Scope', | ||||
|           data: 'object', | ||||
|           defaultContent: '' | ||||
|         } | ||||
|       ] | ||||
|     }); | ||||
|   } | ||||
|   function draw_bl_policy_domain_table() { | ||||
|     $('#bl_policy_domain_table').DataTable({ | ||||
|       responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       pageLength: pagination_size, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
|       language: lang_datatables, | ||||
|       ajax: { | ||||
|         type: "GET", | ||||
|         url: '/api/v1/get/policy_bl_domain/' + table_for_domain, | ||||
|         dataSrc: function(data){ | ||||
|           $.each(data, function (i, item) { | ||||
|             if (!validateEmail(item.object)) { | ||||
|               item.chkbox = '<input type="checkbox" data-id="policy_bl_domain" name="multi_select" value="' + item.prefid + '" />'; | ||||
|             } | ||||
|             else { | ||||
|               item.chkbox = '<input type="checkbox" disabled tooltip="' + lang_user.spamfilter_table_domain_policy + '" />'; | ||||
|             } | ||||
|           }); | ||||
|  | ||||
|           return data; | ||||
|         } | ||||
|       }, | ||||
|       columns: [ | ||||
|         { | ||||
|           // placeholder, so checkbox will not block child row toggle | ||||
|           title: '', | ||||
|           data: null, | ||||
|           searchable: false, | ||||
|           orderable: false, | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: '', | ||||
|           data: 'chkbox', | ||||
|           searchable: false, | ||||
|           orderable: false, | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: 'ID', | ||||
|           data: 'prefid', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: lang_user.spamfilter_table_rule, | ||||
|           data: 'value', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: 'Scope', | ||||
|           data: 'object', | ||||
|           defaultContent: '' | ||||
|         } | ||||
|       ] | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|  | ||||
|   // detect element visibility changes | ||||
|   function onVisible(element, callback) { | ||||
|     $(document).ready(function() { | ||||
|       element_object = document.querySelector(element); | ||||
|       if (element_object === null) return; | ||||
|  | ||||
|       new IntersectionObserver((entries, observer) => { | ||||
|         entries.forEach(entry => { | ||||
|           if(entry.intersectionRatio > 0) { | ||||
|             callback(element_object); | ||||
|             observer.disconnect(); | ||||
|           } | ||||
|         }); | ||||
|       }).observe(element_object); | ||||
|     }); | ||||
|   } | ||||
|   // Draw Table if tab is active | ||||
|   onVisible("[id^=wl_policy_domain_table]", () => draw_wl_policy_domain_table()); | ||||
|   onVisible("[id^=bl_policy_domain_table]", () => draw_bl_policy_domain_table()); | ||||
| }); | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,71 +1,71 @@ | ||||
| jQuery(function($){ | ||||
|   var qitem = $('legend').data('hash'); | ||||
|   var qError = $("#qid_error"); | ||||
|   $.ajax({ | ||||
|     url: '/inc/ajax/qitem_details.php', | ||||
|     data: { hash: qitem }, | ||||
|     dataType: 'json', | ||||
|     success: function(data){ | ||||
|       $('[data-id="qitems_single"]').each(function(index) { | ||||
|         $(this).attr("data-item", qitem); | ||||
|       }); | ||||
|       $('#qid_detail_subj').text(data.subject); | ||||
|       $('#qid_detail_hfrom').text(data.header_from); | ||||
|       $('#qid_detail_efrom').text(data.env_from); | ||||
|       $('#qid_detail_score').html(''); | ||||
|       $('#qid_detail_symbols').html(''); | ||||
|       $('#qid_detail_recipients').html(''); | ||||
|       $('#qid_detail_fuzzy').html(''); | ||||
|       if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) { | ||||
|         $.each(data.fuzzy_hashes, function (index, value) { | ||||
|           $('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + value + '</p>'); | ||||
|         }); | ||||
|       } else { | ||||
|         $('#qid_detail_fuzzy').append('-'); | ||||
|       } | ||||
|       if (typeof data.symbols !== 'undefined') { | ||||
|         data.symbols.sort(function (a, b) { | ||||
|           if (a.score === 0) return 1 | ||||
|           if (b.score === 0) return -1 | ||||
|           if (b.score < 0 && a.score < 0) { | ||||
|             return a.score - b.score | ||||
|           } | ||||
|           if (b.score > 0 && a.score > 0) { | ||||
|             return b.score - a.score | ||||
|           } | ||||
|           return b.score - a.score | ||||
|         }) | ||||
|         $.each(data.symbols, function (index, value) { | ||||
|           var highlightClass = '' | ||||
|           if (value.score > 0) highlightClass = 'negative' | ||||
|           else if (value.score < 0) highlightClass = 'positive' | ||||
|           else highlightClass = 'neutral' | ||||
|           $('#qid_detail_symbols').append('<span data-bs-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>'); | ||||
|         }); | ||||
|         $('[data-bs-toggle="tooltip"]').tooltip() | ||||
|       } | ||||
|       if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') { | ||||
|         if (data.action === "add header") { | ||||
|           $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>'); | ||||
|         } else if (data.action === "reject") { | ||||
|           $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-danger"><b>' + data.score + '</b> - ' + lang.rejected + '</span>'); | ||||
|         } else if (data.action === "rewrite subject") { | ||||
|           $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.rewrite_subject + '</span>'); | ||||
|         } | ||||
|       } | ||||
|       if (typeof data.recipients !== 'undefined') { | ||||
|         $.each(data.recipients, function(index, value) { | ||||
|           var elem = $('<span class="mail-address-item"></span>'); | ||||
|           elem.text(value.address + ' (' + value.type.toUpperCase() + ')'); | ||||
|           $('#qid_detail_recipients').append(elem); | ||||
|         }); | ||||
|       } | ||||
|     }, | ||||
|     error: function(data){ | ||||
|       if (typeof data.error !== 'undefined') { | ||||
|         qError.text("Error loading quarantine item"); | ||||
|         qError.show(); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
| }); | ||||
| jQuery(function($){ | ||||
|   var qitem = $('legend').data('hash'); | ||||
|   var qError = $("#qid_error"); | ||||
|   $.ajax({ | ||||
|     url: '/inc/ajax/qitem_details.php', | ||||
|     data: { hash: qitem }, | ||||
|     dataType: 'json', | ||||
|     success: function(data){ | ||||
|       $('[data-id="qitems_single"]').each(function(index) { | ||||
|         $(this).attr("data-item", qitem); | ||||
|       }); | ||||
|       $('#qid_detail_subj').text(data.subject); | ||||
|       $('#qid_detail_hfrom').text(data.header_from); | ||||
|       $('#qid_detail_efrom').text(data.env_from); | ||||
|       $('#qid_detail_score').html(''); | ||||
|       $('#qid_detail_symbols').html(''); | ||||
|       $('#qid_detail_recipients').html(''); | ||||
|       $('#qid_detail_fuzzy').html(''); | ||||
|       if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) { | ||||
|         $.each(data.fuzzy_hashes, function (index, value) { | ||||
|           $('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + value + '</p>'); | ||||
|         }); | ||||
|       } else { | ||||
|         $('#qid_detail_fuzzy').append('-'); | ||||
|       } | ||||
|       if (typeof data.symbols !== 'undefined') { | ||||
|         data.symbols.sort(function (a, b) { | ||||
|           if (a.score === 0) return 1; | ||||
|           if (b.score === 0) return -1; | ||||
|           if (b.score < 0 && a.score < 0) { | ||||
|             return a.score - b.score; | ||||
|           } | ||||
|           if (b.score > 0 && a.score > 0) { | ||||
|             return b.score - a.score; | ||||
|           } | ||||
|           return b.score - a.score; | ||||
|         }) | ||||
|         $.each(data.symbols, function (index, value) { | ||||
|           var highlightClass = ''; | ||||
|           if (value.score > 0) highlightClass = 'negative'; | ||||
|           else if (value.score < 0) highlightClass = 'positive'; | ||||
|           else highlightClass = 'neutral'; | ||||
|           $('#qid_detail_symbols').append('<span data-bs-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>'); | ||||
|         }); | ||||
|         $('[data-bs-toggle="tooltip"]').tooltip(); | ||||
|       } | ||||
|       if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') { | ||||
|         if (data.action === "add header") { | ||||
|           $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>'); | ||||
|         } else if (data.action === "reject") { | ||||
|           $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-danger"><b>' + data.score + '</b> - ' + lang.rejected + '</span>'); | ||||
|         } else if (data.action === "rewrite subject") { | ||||
|           $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.rewrite_subject + '</span>'); | ||||
|         } | ||||
|       } | ||||
|       if (typeof data.recipients !== 'undefined') { | ||||
|         $.each(data.recipients, function(index, value) { | ||||
|           var elem = $('<span class="mail-address-item"></span>'); | ||||
|           elem.text(value.address + ' (' + value.type.toUpperCase() + ')'); | ||||
|           $('#qid_detail_recipients').append(elem); | ||||
|         }); | ||||
|       } | ||||
|     }, | ||||
|     error: function(data){ | ||||
|       if (typeof data.error !== 'undefined') { | ||||
|         qError.text("Error loading quarantine item"); | ||||
|         qError.show(); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
| }); | ||||
|   | ||||
| @@ -1,286 +1,297 @@ | ||||
| // Base64 functions | ||||
| var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C<r.length;)a=(t=r.charCodeAt(C++))>>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d<r.length;)t=this._keyStr.indexOf(r.charAt(d++))<<2|(a=this._keyStr.indexOf(r.charAt(d++)))>>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e<r.length;e++){var o=r.charCodeAt(e);o<128?t+=String.fromCharCode(o):o>127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e<r.length;)(o=r.charCodeAt(e))<128?(t+=String.fromCharCode(o),e++):o>191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}}; | ||||
|  | ||||
| jQuery(function($){ | ||||
|   acl_data = JSON.parse(acl); | ||||
|   // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery | ||||
|   var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}; | ||||
|   function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} | ||||
|   function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]} | ||||
|   $(".refresh_table").on('click', function(e) { | ||||
|     e.preventDefault(); | ||||
|     var table_name = $(this).data('table'); | ||||
|     $('#' + table_name).DataTable().ajax.reload(); | ||||
|   }); | ||||
|   function draw_quarantine_table() { | ||||
|     var table = $('#quarantinetable').DataTable({ | ||||
| 			responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
|       language: lang_datatables, | ||||
|       initComplete: function(){ | ||||
|         hideTableExpandCollapseBtn('#quarantinetable'); | ||||
|       }, | ||||
|       ajax: { | ||||
|         type: "GET", | ||||
|         url: "/api/v1/get/quarantine/all", | ||||
|         dataSrc: function(data){ | ||||
|           $.each(data, function (i, item) { | ||||
|             if (item.subject === null) { | ||||
|               item.subject = ''; | ||||
|             } else { | ||||
|               item.subject = escapeHtml(item.subject); | ||||
|             } | ||||
|             if (item.score === null) { | ||||
|               item.score = '-'; | ||||
|             } | ||||
|             if (item.virus_flag > 0) { | ||||
|               item.virus = '<span class="badge fs-6 bg-danger">' + lang.high_danger + '</span>'; | ||||
|             } else { | ||||
|               item.virus = '<span class="badge fs-6 bg-secondary">' + lang.neutral_danger + '</span>'; | ||||
|             } | ||||
|             if (item.action === "reject") { | ||||
|               item.rspamdaction = '<span class="badge fs-6 bg-danger">' + lang.rejected + '</span>'; | ||||
|             } else if (item.action === "add header") { | ||||
|               item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.junk_folder + '</span>'; | ||||
|             } else if (item.action === "rewrite subject") { | ||||
|               item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.rewrite_subject + '</span>'; | ||||
|             } | ||||
|             if(item.notified > 0) { | ||||
|               item.notified = '✔'; | ||||
|             } else { | ||||
|               item.notified = '✖'; | ||||
|             } | ||||
|             if (acl_data.login_as === 1) { | ||||
|             item.action = '<div class="btn-group">' + | ||||
|               '<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-half btn-info show_qid_info"><i class="bi bi-box-arrow-up-right"></i> ' + lang.show_item + '</a>' + | ||||
|               '<a href="#" data-action="delete_selected" data-id="del-single-qitem" data-api-url="delete/qitem" data-item="' + encodeURI(item.id) + '" class="btn btn-xs  btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' + | ||||
|               '</div>'; | ||||
|             } | ||||
|             else { | ||||
|             item.action = '<div class="btn-group">' + | ||||
|               '<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-info show_qid_info"><i class="bi bi-file-earmark-text"></i> ' + lang.show_item + '</a>' + | ||||
|               '</div>'; | ||||
|             } | ||||
|             item.chkbox = '<input type="checkbox" data-id="qitems" name="multi_select" value="' + item.id + '" />'; | ||||
|           }); | ||||
|  | ||||
|           return data; | ||||
|         } | ||||
|       }, | ||||
|       columns: [ | ||||
|           { | ||||
|             // placeholder, so checkbox will not block child row toggle | ||||
|             title: '', | ||||
|             data: null, | ||||
|             searchable: false, | ||||
|             orderable: false, | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: '', | ||||
|             data: 'chkbox', | ||||
|             searchable: false, | ||||
|             orderable: false, | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: 'ID', | ||||
|             data: 'id', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: lang.qid, | ||||
|             data: 'qid', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: lang.sender, | ||||
|             data: 'sender', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: lang.subj, | ||||
|             data: 'subject', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: lang.rspamd_result, | ||||
|             data: 'rspamdaction', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: lang.rcpt, | ||||
|             data: 'rcpt', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: lang.danger, | ||||
|             data: 'virus', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: lang.spam_score, | ||||
|             data: 'score', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: lang.notified, | ||||
|             data: 'notified', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: lang.received, | ||||
|             data: 'created', | ||||
|             defaultContent: '', | ||||
|             createdCell: function(td, cellData) {     | ||||
|               $(td).attr({ | ||||
|                 "data-order": cellData, | ||||
|                 "data-sort": cellData | ||||
|               }); | ||||
|                | ||||
|               var date = new Date(cellData ? cellData * 1000 : 0);  | ||||
|               var dateString = date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}); | ||||
|               $(td).html(dateString); | ||||
|             } | ||||
|           }, | ||||
|           { | ||||
|             title: lang.action, | ||||
|             data: 'action', | ||||
|             className: 'text-md-end dt-sm-head-hidden dt-body-right', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|       ] | ||||
|     }); | ||||
|  | ||||
|     table.on('responsive-resize', function (e, datatable, columns){ | ||||
|       hideTableExpandCollapseBtn('#quarantinetable'); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   $('body').on('click', '.show_qid_info', function (e) { | ||||
|     e.preventDefault(); | ||||
|     var qitem = $(this).attr('data-item'); | ||||
|     var qError = $("#qid_error"); | ||||
|  | ||||
|     $('#qidDetailModal').modal('show'); | ||||
|     qError.hide(); | ||||
|  | ||||
|     $.ajax({ | ||||
|       url: '/inc/ajax/qitem_details.php', | ||||
|       data: { id: qitem }, | ||||
|       dataType: 'json', | ||||
|       success: function(data){ | ||||
|  | ||||
|         $('[data-id="qitems_single"]').each(function(index) { | ||||
|           $(this).attr("data-item", qitem); | ||||
|         }); | ||||
|  | ||||
|         $("#quick_download_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&eml', '_blank')"); | ||||
|         $("#quick_release_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&quick_release', '_blank')"); | ||||
|         $("#quick_delete_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&quick_delete', '_blank')"); | ||||
|  | ||||
|         $('#qid_detail_subj').text(data.subject); | ||||
|         $('#qid_detail_hfrom').text(data.header_from); | ||||
|         $('#qid_detail_efrom').text(data.env_from); | ||||
|         $('#qid_detail_score').html(''); | ||||
|         $('#qid_detail_recipients').html(''); | ||||
|         $('#qid_detail_symbols').html(''); | ||||
|         $('#qid_detail_fuzzy').html(''); | ||||
|         if (typeof data.symbols !== 'undefined') { | ||||
|           data.symbols.sort(function (a, b) { | ||||
|             if (a.score === 0) return 1 | ||||
|             if (b.score === 0) return -1 | ||||
|             if (b.score < 0 && a.score < 0) { | ||||
|               return a.score - b.score | ||||
|             } | ||||
|             if (b.score > 0 && a.score > 0) { | ||||
|               return b.score - a.score | ||||
|             } | ||||
|             return b.score - a.score | ||||
|           }) | ||||
|           $.each(data.symbols, function (index, value) { | ||||
|             var highlightClass = '' | ||||
|             if (value.score > 0) highlightClass = 'negative' | ||||
|             else if (value.score < 0) highlightClass = 'positive' | ||||
|             else highlightClass = 'neutral' | ||||
|             $('#qid_detail_symbols').append('<span data-bs-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>'); | ||||
|           }); | ||||
|           $('[data-bs-toggle="tooltip"]').tooltip() | ||||
|         } | ||||
|         if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) { | ||||
|           $.each(data.fuzzy_hashes, function (index, value) { | ||||
|             $('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + value + '</p>'); | ||||
|           }); | ||||
|         } else { | ||||
|           $('#qid_detail_fuzzy').append('-'); | ||||
|         } | ||||
|         if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') { | ||||
|           if (data.action == "add header") { | ||||
|             $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>'); | ||||
|           } else if (data.action == "reject") { | ||||
|             $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-danger"><b>' + data.score + '</b> - ' + lang.rejected + '</span>'); | ||||
|           } else if (data.action == "rewrite subject") { | ||||
|             $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.rewrite_subject + '</span>'); | ||||
|           } | ||||
|         } | ||||
|         if (typeof data.recipients !== 'undefined') { | ||||
|           $.each(data.recipients, function(index, value) { | ||||
|             var elem = $('<span class="mail-address-item"></span>'); | ||||
|             elem.text(value.address + ' (' + value.type.toUpperCase() + ')'); | ||||
|             $('#qid_detail_recipients').append(elem); | ||||
|           }); | ||||
|         } | ||||
|         $('#qid_detail_text').text(data.text_plain); | ||||
|         $('#qid_detail_text_from_html').text(data.text_html); | ||||
|         var qAtts = $("#qid_detail_atts"); | ||||
|         if (typeof data.attachments !== 'undefined') { | ||||
|           qAtts.text(''); | ||||
|           $.each(data.attachments, function(index, value) { | ||||
|             qAtts.append( | ||||
|               '<p><a href="/inc/ajax/qitem_details.php?id=' + qitem + '&att=' + index + '" target="_blank">' + value[0] + '</a> (' + value[1] + ')' + | ||||
|               ' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>' | ||||
|             ); | ||||
|           }); | ||||
|         } | ||||
|         else { | ||||
|           qAtts.text('-'); | ||||
|         } | ||||
|       }, | ||||
|       error: function(data){ | ||||
|         if (typeof data.error !== 'undefined') { | ||||
|           $('#qid_detail_subj').text('-'); | ||||
|           $('#qid_detail_hfrom').text('-'); | ||||
|           $('#qid_detail_efrom').text('-'); | ||||
|           $('#qid_detail_score').html('-'); | ||||
|           $('#qid_detail_recipients').html('-'); | ||||
|           $('#qid_detail_symbols').html('-'); | ||||
|           $('#qid_detail_fuzzy').html('-'); | ||||
|           $('#qid_detail_text').text('-'); | ||||
|           $('#qid_detail_text_from_html').text('-'); | ||||
|           qError.text("Error loading quarantine item"); | ||||
|           qError.show(); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   $('body').on('click', 'span.footable-toggle', function () { | ||||
|     event.stopPropagation(); | ||||
|   }) | ||||
|  | ||||
|   // Initial table drawings | ||||
|   draw_quarantine_table(); | ||||
|  | ||||
|    | ||||
|   function hideTableExpandCollapseBtn(table){ | ||||
|     if ($(table).hasClass('collapsed')) | ||||
|       $(".table_collapse_option").show();  | ||||
|     else | ||||
|       $(".table_collapse_option").hide();  | ||||
|   } | ||||
| }); | ||||
| // Base64 functions | ||||
| var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C<r.length;)a=(t=r.charCodeAt(C++))>>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d<r.length;)t=this._keyStr.indexOf(r.charAt(d++))<<2|(a=this._keyStr.indexOf(r.charAt(d++)))>>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e<r.length;e++){var o=r.charCodeAt(e);o<128?t+=String.fromCharCode(o):o>127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e<r.length;)(o=r.charCodeAt(e))<128?(t+=String.fromCharCode(o),e++):o>191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}}; | ||||
|  | ||||
| jQuery(function($){ | ||||
|   acl_data = JSON.parse(acl); | ||||
|   // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery | ||||
|   var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}; | ||||
|   function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} | ||||
|   function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]} | ||||
|   $(".refresh_table").on('click', function(e) { | ||||
|     e.preventDefault(); | ||||
|     var table_name = $(this).data('table'); | ||||
|     $('#' + table_name).DataTable().ajax.reload(); | ||||
|   }); | ||||
|   function draw_quarantine_table() { | ||||
|     var table = $('#quarantinetable').DataTable({ | ||||
|       responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       pageLength: pagination_size, | ||||
|       order: [[2, 'desc']], | ||||
|       lengthMenu: [ | ||||
|         [10, 25, 50, 100, -1], | ||||
|         [10, 25, 50, 100, 'all'] | ||||
|       ], | ||||
|       pagingType: 'first_last_numbers', | ||||
|       aColumns: [ | ||||
|         { sWidth: '8.25%' }, | ||||
|         { sClass: 'classDataTable' } | ||||
|       ], | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
|       language: lang_datatables, | ||||
|       initComplete: function(){ | ||||
|         hideTableExpandCollapseBtn('#quarantinetable'); | ||||
|       }, | ||||
|       ajax: { | ||||
|         type: "GET", | ||||
|         url: "/api/v1/get/quarantine/all", | ||||
|         dataSrc: function(data){ | ||||
|           $.each(data, function (i, item) { | ||||
|             if (item.subject === null) { | ||||
|               item.subject = ''; | ||||
|             } else { | ||||
|               item.subject = escapeHtml(item.subject); | ||||
|             } | ||||
|             if (item.score === null) { | ||||
|               item.score = '-'; | ||||
|             } | ||||
|             if (item.virus_flag > 0) { | ||||
|               item.virus = '<span class="badge fs-6 bg-danger">' + lang.high_danger + '</span>'; | ||||
|             } else { | ||||
|               item.virus = '<span class="badge fs-6 bg-secondary">' + lang.neutral_danger + '</span>'; | ||||
|             } | ||||
|             if (item.action === "reject") { | ||||
|               item.rspamdaction = '<span class="badge fs-6 bg-danger">' + lang.rejected + '</span>'; | ||||
|             } else if (item.action === "add header") { | ||||
|               item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.junk_folder + '</span>'; | ||||
|             } else if (item.action === "rewrite subject") { | ||||
|               item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.rewrite_subject + '</span>'; | ||||
|             } | ||||
|             if(item.notified > 0) { | ||||
|               item.notified = '✔'; | ||||
|             } else { | ||||
|               item.notified = '✖'; | ||||
|             } | ||||
|             if (acl_data.login_as === 1) { | ||||
|             item.action = '<div class="btn-group">' + | ||||
|               '<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-half btn-info show_qid_info"><i class="bi bi-box-arrow-up-right"></i> ' + lang.show_item + '</a>' + | ||||
|               '<a href="#" data-action="delete_selected" data-id="del-single-qitem" data-api-url="delete/qitem" data-item="' + encodeURI(item.id) + '" class="btn btn-xs  btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' + | ||||
|               '</div>'; | ||||
|             } | ||||
|             else { | ||||
|             item.action = '<div class="btn-group">' + | ||||
|               '<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-info show_qid_info"><i class="bi bi-file-earmark-text"></i> ' + lang.show_item + '</a>' + | ||||
|               '</div>'; | ||||
|             } | ||||
|             item.chkbox = '<input type="checkbox" data-id="qitems" name="multi_select" value="' + item.id + '" />'; | ||||
|           }); | ||||
|  | ||||
|           return data; | ||||
|         } | ||||
|       }, | ||||
|       columns: [ | ||||
|         { | ||||
|           // placeholder, so checkbox will not block child row toggle | ||||
|           title: '', | ||||
|           data: null, | ||||
|           searchable: false, | ||||
|           orderable: false, | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: '', | ||||
|           data: 'chkbox', | ||||
|           searchable: false, | ||||
|           orderable: false, | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: 'ID', | ||||
|           data: 'id', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: lang.qid, | ||||
|           data: 'qid', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: lang.sender, | ||||
|           data: 'sender', | ||||
|           className: 'senders-mw220', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: lang.subj, | ||||
|           data: 'subject', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: lang.rspamd_result, | ||||
|           data: 'rspamdaction', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: lang.rcpt, | ||||
|           data: 'rcpt', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: lang.danger, | ||||
|           data: 'virus', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: lang.spam_score, | ||||
|           data: 'score', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: lang.notified, | ||||
|           data: 'notified', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: lang.received, | ||||
|           data: 'created', | ||||
|           defaultContent: '', | ||||
|           createdCell: function(td, cellData) { | ||||
|             $(td).attr({ | ||||
|               "data-order": cellData, | ||||
|               "data-sort": cellData | ||||
|             }); | ||||
|  | ||||
|             var date = new Date(cellData ? cellData * 1000 : 0); | ||||
|             var dateString = date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}); | ||||
|             $(td).html(dateString); | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           title: lang.action, | ||||
|           data: 'action', | ||||
|           className: 'dt-text-right dt-sm-head-hidden', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|       ] | ||||
|     }); | ||||
|  | ||||
|     table.on('responsive-resize', function (e, datatable, columns){ | ||||
|       hideTableExpandCollapseBtn('#quarantinetable'); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   $('body').on('click', '.show_qid_info', function (e) { | ||||
|     e.preventDefault(); | ||||
|     var qitem = $(this).attr('data-item'); | ||||
|     var qError = $("#qid_error"); | ||||
|  | ||||
|     $('#qidDetailModal').modal('show'); | ||||
|     qError.hide(); | ||||
|  | ||||
|     $.ajax({ | ||||
|       url: '/inc/ajax/qitem_details.php', | ||||
|       data: { id: qitem }, | ||||
|       dataType: 'json', | ||||
|       success: function(data){ | ||||
|  | ||||
|         $('[data-id="qitems_single"]').each(function(index) { | ||||
|           $(this).attr("data-item", qitem); | ||||
|         }); | ||||
|  | ||||
|         $("#quick_download_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&eml', '_blank')"); | ||||
|         $("#quick_release_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&quick_release', '_blank')"); | ||||
|         $("#quick_delete_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&quick_delete', '_blank')"); | ||||
|  | ||||
|         $('#qid_detail_subj').text(data.subject); | ||||
|         $('#qid_detail_hfrom').text(data.header_from); | ||||
|         $('#qid_detail_efrom').text(data.env_from); | ||||
|         $('#qid_detail_score').html(''); | ||||
|         $('#qid_detail_recipients').html(''); | ||||
|         $('#qid_detail_symbols').html(''); | ||||
|         $('#qid_detail_fuzzy').html(''); | ||||
|         if (typeof data.symbols !== 'undefined') { | ||||
|           data.symbols.sort(function (a, b) { | ||||
|             if (a.score === 0) return 1; | ||||
|             if (b.score === 0) return -1; | ||||
|             if (b.score < 0 && a.score < 0) { | ||||
|               return a.score - b.score; | ||||
|             } | ||||
|             if (b.score > 0 && a.score > 0) { | ||||
|               return b.score - a.score; | ||||
|             } | ||||
|             return b.score - a.score; | ||||
|           }) | ||||
|           $.each(data.symbols, function (index, value) { | ||||
|             var highlightClass = ''; | ||||
|             if (value.score > 0) highlightClass = 'negative'; | ||||
|             else if (value.score < 0) highlightClass = 'positive'; | ||||
|             else highlightClass = 'neutral'; | ||||
|             $('#qid_detail_symbols').append('<span data-bs-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>'); | ||||
|           }); | ||||
|           $('[data-bs-toggle="tooltip"]').tooltip(); | ||||
|         } | ||||
|         if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) { | ||||
|           $.each(data.fuzzy_hashes, function (index, value) { | ||||
|             $('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + value + '</p>'); | ||||
|           }); | ||||
|         } else { | ||||
|           $('#qid_detail_fuzzy').append('-'); | ||||
|         } | ||||
|         if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') { | ||||
|           if (data.action == "add header") { | ||||
|             $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>'); | ||||
|           } else if (data.action == "reject") { | ||||
|             $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-danger"><b>' + data.score + '</b> - ' + lang.rejected + '</span>'); | ||||
|           } else if (data.action == "rewrite subject") { | ||||
|             $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.rewrite_subject + '</span>'); | ||||
|           } | ||||
|         } | ||||
|         if (typeof data.recipients !== 'undefined') { | ||||
|           $.each(data.recipients, function(index, value) { | ||||
|             var elem = $('<span class="mail-address-item"></span>'); | ||||
|             elem.text(value.address + ' (' + value.type.toUpperCase() + ')'); | ||||
|             $('#qid_detail_recipients').append(elem); | ||||
|           }); | ||||
|         } | ||||
|         $('#qid_detail_text').text(data.text_plain); | ||||
|         $('#qid_detail_text_from_html').text(data.text_html); | ||||
|         var qAtts = $("#qid_detail_atts"); | ||||
|         if (typeof data.attachments !== 'undefined') { | ||||
|           qAtts.text(''); | ||||
|           $.each(data.attachments, function(index, value) { | ||||
|             qAtts.append( | ||||
|               '<p><a href="/inc/ajax/qitem_details.php?id=' + qitem + '&att=' + index + '" target="_blank">' + value[0] + '</a> (' + value[1] + ')' + | ||||
|               ' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>' | ||||
|             ); | ||||
|           }); | ||||
|         } | ||||
|         else { | ||||
|           qAtts.text('-'); | ||||
|         } | ||||
|       }, | ||||
|       error: function(data){ | ||||
|         if (typeof data.error !== 'undefined') { | ||||
|           $('#qid_detail_subj').text('-'); | ||||
|           $('#qid_detail_hfrom').text('-'); | ||||
|           $('#qid_detail_efrom').text('-'); | ||||
|           $('#qid_detail_score').html('-'); | ||||
|           $('#qid_detail_recipients').html('-'); | ||||
|           $('#qid_detail_symbols').html('-'); | ||||
|           $('#qid_detail_fuzzy').html('-'); | ||||
|           $('#qid_detail_text').text('-'); | ||||
|           $('#qid_detail_text_from_html').text('-'); | ||||
|           qError.text("Error loading quarantine item"); | ||||
|           qError.show(); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   $('body').on('click', 'span.footable-toggle', function () { | ||||
|     event.stopPropagation(); | ||||
|   }) | ||||
|  | ||||
|   // Initial table drawings | ||||
|   draw_quarantine_table(); | ||||
|  | ||||
|   function hideTableExpandCollapseBtn(table){ | ||||
|     if ($(table).hasClass('collapsed')) | ||||
|       $(".table_collapse_option").show(); | ||||
|     else | ||||
|       $(".table_collapse_option").hide(); | ||||
|   } | ||||
| }); | ||||
|   | ||||
| @@ -1,127 +1,128 @@ | ||||
| jQuery(function($){ | ||||
|  | ||||
|     $(".refresh_table").on('click', function(e) { | ||||
|       e.preventDefault(); | ||||
|       var table_name = $(this).data('table'); | ||||
|       $('#' + table_name).DataTable().ajax.reload(); | ||||
|     }); | ||||
|   $(".refresh_table").on('click', function(e) { | ||||
|     e.preventDefault(); | ||||
|     var table_name = $(this).data('table'); | ||||
|     $('#' + table_name).DataTable().ajax.reload(); | ||||
|   }); | ||||
|  | ||||
|  | ||||
|     function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]} | ||||
|   function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]} | ||||
|  | ||||
|     // Queue item | ||||
|     $('#showQueuedMsg').on('show.bs.modal', function (e) { | ||||
|       $('#queue_msg_content').text(lang.loading); | ||||
|       button = $(e.relatedTarget) | ||||
|       if (button != null) { | ||||
|         $('#queue_id').text(button.data('queue-id')); | ||||
|       } | ||||
|       $.ajax({ | ||||
|           type: 'GET', | ||||
|           url: '/api/v1/get/postcat/' + button.data('queue-id'), | ||||
|           dataType: 'text', | ||||
|           complete: function (data) { | ||||
|             $('#queue_msg_content').text(data.responseText); | ||||
|           } | ||||
|       }); | ||||
|     }) | ||||
|  | ||||
|     function draw_queue() { | ||||
|     // just recalc width if instance already exists | ||||
|     if ($.fn.DataTable.isDataTable('#queuetable') ) { | ||||
|       $('#queuetable').DataTable().columns.adjust().responsive.recalc(); | ||||
|       return; | ||||
|   // Queue item | ||||
|   $('#showQueuedMsg').on('show.bs.modal', function (e) { | ||||
|     $('#queue_msg_content').text(lang.loading); | ||||
|     button = $(e.relatedTarget) | ||||
|     if (button != null) { | ||||
|       $('#queue_id').text(button.data('queue-id')); | ||||
|     } | ||||
|     $.ajax({ | ||||
|       type: 'GET', | ||||
|       url: '/api/v1/get/postcat/' + button.data('queue-id'), | ||||
|       dataType: 'text', | ||||
|       complete: function (data) { | ||||
|         $('#queue_msg_content').text(data.responseText); | ||||
|       } | ||||
|     }); | ||||
|   }) | ||||
|  | ||||
|     $('#queuetable').DataTable({ | ||||
| 			responsive: true, | ||||
|       processing: true, | ||||
|       serverSide: false, | ||||
|       stateSave: true, | ||||
|       dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|            "tr" + | ||||
|            "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
|       language: lang_datatables, | ||||
|       ajax: { | ||||
|         type: "GET", | ||||
|         url: "/api/v1/get/mailq/all", | ||||
|         dataSrc: function(data){ | ||||
|           $.each(data, function (i, item) { | ||||
|             item.chkbox = '<input type="checkbox" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />'; | ||||
|             rcpts = $.map(item.recipients, function(i) { | ||||
|               return escapeHtml(i); | ||||
|             }); | ||||
|             item.recipients = rcpts.join('<hr style="margin:1px!important">'); | ||||
|             item.action = '<div class="btn-group">' + | ||||
|               '<a href="#" data-bs-toggle="modal" data-bs-target="#showQueuedMsg" data-queue-id="' + encodeURI(item.queue_id) + '" class="btn btn-xs btn-secondary">' + lang.show_message + '</a>' + | ||||
|   function draw_queue() { | ||||
|   // just recalc width if instance already exists | ||||
|   if ($.fn.DataTable.isDataTable('#queuetable') ) { | ||||
|     $('#queuetable').DataTable().columns.adjust().responsive.recalc(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   $('#queuetable').DataTable({ | ||||
|     responsive: true, | ||||
|     processing: true, | ||||
|     serverSide: false, | ||||
|     stateSave: true, | ||||
|     pageLength: pagination_size, | ||||
|     dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + | ||||
|          "tr" + | ||||
|          "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", | ||||
|     language: lang_datatables, | ||||
|     ajax: { | ||||
|       type: "GET", | ||||
|       url: "/api/v1/get/mailq/all", | ||||
|       dataSrc: function(data){ | ||||
|         $.each(data, function (i, item) { | ||||
|           item.chkbox = '<input type="checkbox" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />'; | ||||
|           rcpts = $.map(item.recipients, function(i) { | ||||
|             return escapeHtml(i); | ||||
|           }); | ||||
|           item.recipients = rcpts.join('<hr style="margin:1px!important">'); | ||||
|           item.action = '<div class="btn-group">' + | ||||
|             '<a href="#" data-bs-toggle="modal" data-bs-target="#showQueuedMsg" data-queue-id="' + encodeURI(item.queue_id) + '" class="btn btn-xs btn-secondary">' + lang.show_message + '</a>' + | ||||
|             '</div>'; | ||||
|           }); | ||||
|           return data; | ||||
|         } | ||||
|       }, | ||||
|       columns: [ | ||||
|           { | ||||
|             // placeholder, so checkbox will not block child row toggle | ||||
|             title: '', | ||||
|             data: null, | ||||
|             searchable: false, | ||||
|             orderable: false, | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: '', | ||||
|             data: 'chkbox', | ||||
|             searchable: false, | ||||
|             orderable: false, | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: 'QID', | ||||
|             data: 'queue_id', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: 'Queue', | ||||
|             data: 'queue_name', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: lang_admin.arrival_time, | ||||
|             data: 'arrival_time', | ||||
|             defaultContent: '', | ||||
|             render: function (data, type){ | ||||
|               var date = new Date(data ? data * 1000 : 0);  | ||||
|               return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}); | ||||
|             } | ||||
|           }, | ||||
|           { | ||||
|             title: lang_admin.message_size, | ||||
|             data: 'message_size', | ||||
|             defaultContent: '', | ||||
|             render: function (data, type){ | ||||
|               return humanFileSize(data); | ||||
|             } | ||||
|           }, | ||||
|           { | ||||
|             title: lang_admin.sender, | ||||
|             data: 'sender', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: lang_admin.recipients, | ||||
|             data: 'recipients', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|           { | ||||
|             title: lang_admin.action, | ||||
|             data: 'action', | ||||
|             className: 'text-md-end dt-sm-head-hidden dt-body-right', | ||||
|             defaultContent: '' | ||||
|           }, | ||||
|         { | ||||
|           // placeholder, so checkbox will not block child row toggle | ||||
|           title: '', | ||||
|           data: null, | ||||
|           searchable: false, | ||||
|           orderable: false, | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: '', | ||||
|           data: 'chkbox', | ||||
|           searchable: false, | ||||
|           orderable: false, | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: 'QID', | ||||
|           data: 'queue_id', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: 'Queue', | ||||
|           data: 'queue_name', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: lang_admin.arrival_time, | ||||
|           data: 'arrival_time', | ||||
|           defaultContent: '', | ||||
|           render: function (data, type){ | ||||
|             var date = new Date(data ? data * 1000 : 0); | ||||
|             return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}); | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           title: lang_admin.message_size, | ||||
|           data: 'message_size', | ||||
|           defaultContent: '', | ||||
|           render: function (data, type){ | ||||
|             return humanFileSize(data); | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           title: lang_admin.sender, | ||||
|           data: 'sender', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: lang_admin.recipients, | ||||
|           data: 'recipients', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|         { | ||||
|           title: lang_admin.action, | ||||
|           data: 'action', | ||||
|           className: 'dt-sm-head-hidden dt-text-right', | ||||
|           defaultContent: '' | ||||
|         }, | ||||
|       ] | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   draw_queue(); | ||||
|  | ||||
| }) | ||||
| }) | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -288,6 +288,18 @@ if (isset($_GET['query'])) { | ||||
|         case "domain-admin": | ||||
|           process_add_return(domain_admin('add', $attr)); | ||||
|         break; | ||||
|         case "sso": | ||||
|           switch ($object) { | ||||
|             case "domain-admin": | ||||
|               $data = domain_admin_sso('issue', $attr); | ||||
|               if($data) { | ||||
|                 echo json_encode($data); | ||||
|                 exit(0); | ||||
|               } | ||||
|               process_add_return($data); | ||||
|             break; | ||||
|           } | ||||
|         break; | ||||
|         case "admin": | ||||
|           process_add_return(admin('add', $attr)); | ||||
|         break; | ||||
|   | ||||
| @@ -339,7 +339,8 @@ | ||||
|         "oauth2_add_client": "Füge OAuth2 Client hinzu", | ||||
|         "api_read_only": "Schreibgeschützter Zugriff", | ||||
|         "api_read_write": "Lese-Schreib-Zugriff", | ||||
|         "oauth2_apps": "OAuth2 Apps" | ||||
|         "oauth2_apps": "OAuth2 Apps", | ||||
|         "queue_unban": "entsperren" | ||||
|     }, | ||||
|     "danger": { | ||||
|         "access_denied": "Zugriff verweigert oder unvollständige/ungültige Daten", | ||||
| @@ -366,7 +367,7 @@ | ||||
|         "domain_not_empty": "Domain %s ist nicht leer", | ||||
|         "domain_not_found": "Domain %s nicht gefunden", | ||||
|         "domain_quota_m_in_use": "Domain-Speicherplatzlimit muss größer oder gleich %d MiB sein", | ||||
|         "extended_sender_acl_denied": "Keine Rechte zum setzen von externen Absenderadressen", | ||||
|         "extended_sender_acl_denied": "Keine Rechte zum Setzen von externen Absenderadressen", | ||||
|         "extra_acl_invalid": "Externe Absenderadresse \"%s\" ist ungültig", | ||||
|         "extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain", | ||||
|         "fido2_verification_failed": "FIDO2-Verifizierung fehlgeschlagen: %s", | ||||
| @@ -454,17 +455,23 @@ | ||||
|         "totp_verification_failed": "TOTP-Verifizierung fehlgeschlagen", | ||||
|         "transport_dest_exists": "Transport-Maps-Ziel \"%s\" existiert bereits", | ||||
|         "webauthn_verification_failed": "WebAuthn-Verifizierung fehlgeschlagen: %s", | ||||
|         "webauthn_authenticator_failed": "Der ausgewählte Authenticator wurde nicht gefunden", | ||||
|         "webauthn_publickey_failed": "Zu dem ausgewählten Authenticator wurde kein Publickey hinterlegt", | ||||
|         "webauthn_username_failed": "Der ausgewählte Authenticator gehört zu einem anderen Konto", | ||||
|         "unknown": "Ein unbekannter Fehler trat auf", | ||||
|         "unknown_tfa_method": "Unbekannte TFA-Methode", | ||||
|         "unlimited_quota_acl": "Unendliche Quota untersagt durch ACL", | ||||
|         "username_invalid": "Benutzername %s kann nicht verwendet werden", | ||||
|         "validity_missing": "Bitte geben Sie eine Gültigkeitsdauer an", | ||||
|         "value_missing": "Bitte alle Felder ausfüllen", | ||||
|         "yotp_verification_failed": "Yubico OTP-Verifizierung fehlgeschlagen: %s" | ||||
|         "yotp_verification_failed": "Yubico OTP-Verifizierung fehlgeschlagen: %s", | ||||
|         "template_exists": "Vorlage %s existiert bereits", | ||||
|         "template_id_invalid": "Vorlagen-ID %s ungültig", | ||||
|         "template_name_invalid": "Name der Vorlage ungültig" | ||||
|     }, | ||||
|     "datatables": { | ||||
|         "collapse_all": "Alle Einklappen", | ||||
|         "decimal": "", | ||||
|         "decimal": ",", | ||||
|         "emptyTable": "Keine Daten in der Tabelle vorhanden", | ||||
|         "expand_all": "Alle Ausklappen", | ||||
|         "info": "_START_ bis _END_ von _TOTAL_ Einträgen", | ||||
| @@ -498,7 +505,7 @@ | ||||
|         "current_time": "Systemzeit", | ||||
|         "disk_usage": "Festplattennutzung", | ||||
|         "docs": "Dokumente", | ||||
|         "error_show_ip": "konnte die öffentlichen IP Adressen nicht auflösen", | ||||
|         "error_show_ip": "Konnte die öffentlichen IP Adressen nicht auflösen", | ||||
|         "external_logs": "Externe Logs", | ||||
|         "history_all_servers": "History (alle Server)", | ||||
|         "in_memory_logs": "In-memory Logs", | ||||
| @@ -651,7 +658,8 @@ | ||||
|         "title": "Objekt bearbeiten", | ||||
|         "unchanged_if_empty": "Unverändert, wenn leer", | ||||
|         "username": "Benutzername", | ||||
|         "validate_save": "Validieren und speichern" | ||||
|         "validate_save": "Validieren und speichern", | ||||
|         "pushover_sound": "Ton" | ||||
|     }, | ||||
|     "fido2": { | ||||
|         "confirm": "Bestätigen", | ||||
| @@ -692,7 +700,8 @@ | ||||
|         "quarantine": "Quarantäne", | ||||
|         "restart_netfilter": "Netfilter neustarten", | ||||
|         "restart_sogo": "SOGo neustarten", | ||||
|         "user_settings": "Benutzereinstellungen" | ||||
|         "user_settings": "Benutzereinstellungen", | ||||
|         "mailcow_system": "System" | ||||
|     }, | ||||
|     "info": { | ||||
|         "awaiting_tfa_confirmation": "Warte auf TFA-Verifizierung", | ||||
| @@ -1236,7 +1245,8 @@ | ||||
|         "syncjob_EXIT_CONNECTION_FAILURE": "Verbindungsproblem", | ||||
|         "syncjob_EXIT_TLS_FAILURE": "Problem mit verschlüsselter Verbindung", | ||||
|         "syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentifizierungsproblem", | ||||
|         "syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Falscher Benutzername oder Passwort" | ||||
|         "syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Falscher Benutzername oder Passwort", | ||||
|         "pushover_sound": "Ton" | ||||
|     }, | ||||
|     "warning": { | ||||
|         "cannot_delete_self": "Kann derzeit eingeloggten Benutzer nicht entfernen", | ||||
|   | ||||
| @@ -458,6 +458,9 @@ | ||||
|         "totp_verification_failed": "TOTP verification failed", | ||||
|         "transport_dest_exists": "Transport destination \"%s\" exists", | ||||
|         "webauthn_verification_failed": "WebAuthn verification failed: %s", | ||||
|         "webauthn_authenticator_failed": "The selected authenticator was not found", | ||||
|         "webauthn_publickey_failed": "No public key was stored for the selected authenticator", | ||||
|         "webauthn_username_failed": "The selected authenticator belongs to another account", | ||||
|         "unknown": "An unknown error occurred", | ||||
|         "unknown_tfa_method": "Unknown TFA method", | ||||
|         "unlimited_quota_acl": "Unlimited quota prohibited by ACL", | ||||
| @@ -468,7 +471,7 @@ | ||||
|     }, | ||||
|     "datatables": { | ||||
|         "collapse_all": "Collapse All", | ||||
|         "decimal": "", | ||||
|         "decimal": ".", | ||||
|         "emptyTable": "No data available in table", | ||||
|         "expand_all": "Expand All", | ||||
|         "info": "Showing _START_ to _END_ of _TOTAL_ entries", | ||||
|   | ||||
| @@ -106,7 +106,8 @@ | ||||
|         "username": "Používateľské meno", | ||||
|         "validate": "Overiť", | ||||
|         "validation_success": "Úspešne overené", | ||||
|         "app_passwd_protocols": "Povolené protokoly k heslu aplikácie" | ||||
|         "app_passwd_protocols": "Povolené protokoly k heslu aplikácie", | ||||
|         "tags": "Štítky" | ||||
|     }, | ||||
|     "admin": { | ||||
|         "access": "Prístup", | ||||
|   | ||||
| @@ -57,7 +57,7 @@ | ||||
|       </div> | ||||
|     </div> <!-- /col-md-12 --> | ||||
|   </div> <!-- /row --> | ||||
| </div>  | ||||
| </div> | ||||
|  | ||||
| {% include 'modals/admin.twig' %} | ||||
|  | ||||
| @@ -66,7 +66,7 @@ var lang = {{ lang_admin|raw }}; | ||||
| var lang_datatables = {{ lang_datatables|raw }}; | ||||
| var admin_username = '{{ mailcow_cc_username }}'; | ||||
| var csrf_token = '{{ csrf_token }}'; | ||||
| var pagination_size = '{{ pagination_size }}'; | ||||
| var log_pagination_size = '{{ log_pagination_size }}'; | ||||
| var pagination_size = Math.trunc('{{ pagination_size }}'); | ||||
| var log_pagination_size = Math.trunc('{{ log_pagination_size }}'); | ||||
| </script> | ||||
| {% endblock %} | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -46,7 +46,7 @@ | ||||
|       <div class="col-sm-3 col-5 text-end">{{ lang.fido2.known_ids }}:</div> | ||||
|       <div class="col-sm-9 col-7"> | ||||
|         <div class="table-responsive"> | ||||
|           <table class="table table-striped table-hover table-condensed" id="fido2_keys"> | ||||
|           <table class="table table-striped table-hover table-condensed w-100" id="fido2_keys"> | ||||
|             <tr> | ||||
|               <th>ID</th> | ||||
|               <th style="min-width:240px;text-align: right">{{ lang.admin.action }}</th> | ||||
|   | ||||
| @@ -26,7 +26,7 @@ | ||||
|   var lang_user = {{ lang_user|raw }}; | ||||
|   var lang_datatables = {{ lang_datatables|raw }}; | ||||
|   var csrf_token = '{{ csrf_token }}'; | ||||
|   var pagination_size = '{{ pagination_size }}'; | ||||
|   var pagination_size = Math.trunc('{{ pagination_size }}'); | ||||
|   var table_for_domain = '{{ domain }}'; | ||||
| </script> | ||||
| {% endblock %} | ||||
|   | ||||
| @@ -58,7 +58,7 @@ | ||||
|   var lang_rl = {{ lang_rl|raw }}; | ||||
|   var lang_datatables = {{ lang_datatables|raw }}; | ||||
|   var csrf_token = '{{ csrf_token }}'; | ||||
|   var pagination_size = '{{ pagination_size }}'; | ||||
|   var pagination_size = Math.trunc('{{ pagination_size }}'); | ||||
|   var role = '{{ role }}'; | ||||
|   var is_dual = {{ is_dual }}; | ||||
|   var ALLOW_ADMIN_EMAIL_LOGIN = {{ allow_admin_email_login }}; | ||||
|   | ||||
| @@ -37,7 +37,7 @@ | ||||
|           </p> | ||||
|           {% endif %} | ||||
|         </p> | ||||
|         <table id="quarantinetable" class="table table-striped"></table> | ||||
|         <table id="quarantinetable" class="table table-striped w-100"></table> | ||||
|         <div class="mass-actions-quarantine mt-4"> | ||||
|           <div class="btn-group" data-acl="{{ acl.quarantine }}"> | ||||
|             <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="qitems" href="#"><i class="bi bi-check-all"></i> {{ lang.quarantine.toggle_all }}</a> | ||||
| @@ -66,7 +66,7 @@ var acl = '{{ acl_json|raw }}'; | ||||
| var lang = {{ lang_quarantine|raw }}; | ||||
| var lang_datatables = {{ lang_datatables|raw }}; | ||||
| var csrf_token = '{{ csrf_token }}'; | ||||
| var pagination_size = '{{ pagination_size }}'; | ||||
| var pagination_size = Math.trunc('{{ pagination_size }}'); | ||||
| var role = '{{ role }}'; | ||||
| </script> | ||||
| {% endblock %} | ||||
|   | ||||
| @@ -55,7 +55,7 @@ | ||||
|   var lang = {{ lang_queue|raw }}; | ||||
|   var lang_datatables = {{ lang_datatables|raw }}; | ||||
|   var csrf_token = '{{ csrf_token }}'; | ||||
|   var pagination_size = '{{ pagination_size }}'; | ||||
|   var pagination_size = Math.trunc('{{ pagination_size }}'); | ||||
|   var table_for_domain = '{{ domain }}'; | ||||
| </script> | ||||
| {% endblock %} | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|   var acl = '{{ acl_json|raw }}'; | ||||
|   var lang = {{ lang_user|raw }}; | ||||
|   var csrf_token = '{{ csrf_token }}'; | ||||
|   var pagination_size = '{{ pagination_size }}'; | ||||
|   var pagination_size = Math.trunc('{{ pagination_size }}'); | ||||
|   var mailcow_cc_username = '{{ mailcow_cc_username }}'; | ||||
|   var user_spam_score = [{{ user_spam_score }}]; | ||||
|   var lang_datatables = {{ lang_datatables|raw }}; | ||||
|   | ||||
| @@ -169,7 +169,7 @@ services: | ||||
|             - phpfpm | ||||
|  | ||||
|     sogo-mailcow: | ||||
|       image: mailcow/sogo:1.114 | ||||
|       image: mailcow/sogo:1.115 | ||||
|       environment: | ||||
|         - DBNAME=${DBNAME} | ||||
|         - DBUSER=${DBUSER} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user