diff --git a/.github/workflows/rebuild_backup_image.yml b/.github/workflows/rebuild_backup_image.yml index 120d68d9..21c218a8 100644 --- a/.github/workflows/rebuild_backup_image.yml +++ b/.github/workflows/rebuild_backup_image.yml @@ -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 diff --git a/.github/workflows/tweet-trigger-publish-release.yml b/.github/workflows/tweet-trigger-publish-release.yml deleted file mode 100644 index 9aab121a..00000000 --- a/.github/workflows/tweet-trigger-publish-release.yml +++ /dev/null @@ -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' diff --git a/data/web/api/openapi.yaml b/data/web/api/openapi.yaml index 6310aa58..5e07c4b3 100644 --- a/data/web/api/openapi.yaml +++ b/data/web/api/openapi.yaml @@ -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 diff --git a/data/web/css/build/013-datatables.css b/data/web/css/build/013-datatables.css index e5518ff8..13378460 100644 --- a/data/web/css/build/013-datatables.css +++ b/data/web/css/build/013-datatables.css @@ -77,4 +77,22 @@ li .dtr-data { table.dataTable>tbody>tr.child span.dtr-title { width: 30%; max-width: 250px; -} \ No newline at end of file +} + + +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; +} diff --git a/data/web/css/build/014-mailcow.css b/data/web/css/build/014-mailcow.css index 3d0eeaee..374d484d 100644 --- a/data/web/css/build/014-mailcow.css +++ b/data/web/css/build/014-mailcow.css @@ -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; -} \ No newline at end of file diff --git a/data/web/css/build/015-responsive.css b/data/web/css/build/015-responsive.css index 47eadb53..a626a384 100644 --- a/data/web/css/build/015-responsive.css +++ b/data/web/css/build/015-responsive.css @@ -203,6 +203,9 @@ text-align: left; } + .senders-mw220 { + max-width: 100% !important; + } } @media (max-width: 350px) { diff --git a/data/web/css/site/quarantine.css b/data/web/css/site/quarantine.css index 98a74d66..0455b7c1 100644 --- a/data/web/css/site/quarantine.css +++ b/data/web/css/site/quarantine.css @@ -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; +} diff --git a/data/web/inc/functions.domain_admin.inc.php b/data/web/inc/functions.domain_admin.inc.php index 804c0f83..bb88ea34 100644 --- a/data/web/inc/functions.domain_admin.inc.php +++ b/data/web/inc/functions.domain_admin.inc.php @@ -1,407 +1,468 @@ - '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; - } -} + '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; + } +} diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 3bab56bb..de1855fa 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -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; diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index f96894ff..4529ee7b 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -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(); } } diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index e781f944..9fc9234e 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -1,230 +1,230 @@ -query("SHOW TABLES LIKE 'versions'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $stmt = $pdo->query("SELECT `version` FROM `versions` WHERE `application` = 'db_schema'"); - if ($stmt->fetch(PDO::FETCH_ASSOC)['version'] == $db_version) { - return true; - } - if (!preg_match('/y|yes/i', getenv('MASTER'))) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__), - 'msg' => 'Database not initialized: not running db_init on slave.' - ); - return true; - } - } - - $views = array( - "grouped_mail_aliases" => "CREATE VIEW grouped_mail_aliases (username, aliases) AS - SELECT goto, IFNULL(GROUP_CONCAT(address ORDER BY address SEPARATOR ' '), '') AS address FROM alias - WHERE address!=goto - AND active = '1' - AND sogo_visible = '1' - AND address NOT LIKE '@%' - GROUP BY goto;", - // START - // Unused at the moment - we cannot allow to show a foreign mailbox as sender address in SOGo, as SOGo does not like this - // We need to create delegation in SOGo AND set a sender_acl in mailcow to allow to send as user X - "grouped_sender_acl" => "CREATE VIEW grouped_sender_acl (username, send_as_acl) AS - SELECT logged_in_as, IFNULL(GROUP_CONCAT(send_as SEPARATOR ' '), '') AS send_as_acl FROM sender_acl - WHERE send_as NOT LIKE '@%' - GROUP BY logged_in_as;", - // END - "grouped_sender_acl_external" => "CREATE VIEW grouped_sender_acl_external (username, send_as_acl) AS - SELECT logged_in_as, IFNULL(GROUP_CONCAT(send_as SEPARATOR ' '), '') AS send_as_acl FROM sender_acl - WHERE send_as NOT LIKE '@%' AND external = '1' - GROUP BY logged_in_as;", - "grouped_domain_alias_address" => "CREATE VIEW grouped_domain_alias_address (username, ad_alias) AS - SELECT username, IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ' '), '') AS ad_alias FROM mailbox - LEFT OUTER JOIN alias_domain ON target_domain=domain - GROUP BY username;", - "sieve_before" => "CREATE VIEW sieve_before (id, username, script_name, script_data) AS - SELECT md5(script_data), username, script_name, script_data FROM sieve_filters - WHERE filter_type = 'prefilter';", - "sieve_after" => "CREATE VIEW sieve_after (id, username, script_name, script_data) AS - SELECT md5(script_data), username, script_name, script_data FROM sieve_filters - WHERE filter_type = 'postfilter';" - ); - - $tables = array( - "versions" => array( - "cols" => array( - "application" => "VARCHAR(255) NOT NULL", - "version" => "VARCHAR(100) NOT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - ), - "keys" => array( - "primary" => array( - "" => array("application") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "admin" => array( - "cols" => array( - "username" => "VARCHAR(255) NOT NULL", - "password" => "VARCHAR(255) NOT NULL", - "superadmin" => "TINYINT(1) NOT NULL DEFAULT '0'", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE NOW(0)", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("username") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "fido2" => array( - "cols" => array( - "username" => "VARCHAR(255) NOT NULL", - "friendlyName" => "VARCHAR(255)", - "rpId" => "VARCHAR(255) NOT NULL", - "credentialPublicKey" => "TEXT NOT NULL", - "certificateChain" => "TEXT", - // Can be null for format "none" - "certificate" => "TEXT", - "certificateIssuer" => "VARCHAR(255)", - "certificateSubject" => "VARCHAR(255)", - "signatureCounter" => "INT", - "AAGUID" => "BLOB", - "credentialId" => "BLOB NOT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE NOW(0)", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "_sogo_static_view" => array( - "cols" => array( - "c_uid" => "VARCHAR(255) NOT NULL", - "domain" => "VARCHAR(255) NOT NULL", - "c_name" => "VARCHAR(255) NOT NULL", - "c_password" => "VARCHAR(255) NOT NULL DEFAULT ''", - "c_cn" => "VARCHAR(255)", - "mail" => "VARCHAR(255) NOT NULL", - // TODO -> use TEXT and check if SOGo login breaks on empty aliases - "aliases" => "TEXT NOT NULL", - "ad_aliases" => "VARCHAR(6144) NOT NULL DEFAULT ''", - "ext_acl" => "VARCHAR(6144) NOT NULL DEFAULT ''", - "kind" => "VARCHAR(100) NOT NULL DEFAULT ''", - "multiple_bookings" => "INT NOT NULL DEFAULT -1" - ), - "keys" => array( - "primary" => array( - "" => array("c_uid") - ), - "key" => array( - "domain" => array("domain") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "relayhosts" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "hostname" => "VARCHAR(255) NOT NULL", - "username" => "VARCHAR(255) NOT NULL", - "password" => "VARCHAR(255) NOT NULL", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "key" => array( - "hostname" => array("hostname") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "transports" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "destination" => "VARCHAR(255) NOT NULL", - "nexthop" => "VARCHAR(255) NOT NULL", - "username" => "VARCHAR(255) NOT NULL DEFAULT ''", - "password" => "VARCHAR(255) NOT NULL DEFAULT ''", - "is_mx_based" => "TINYINT(1) NOT NULL DEFAULT '0'", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "key" => array( - "destination" => array("destination"), - "nexthop" => array("nexthop"), - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "alias" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "address" => "VARCHAR(255) NOT NULL", - "goto" => "TEXT NOT NULL", - "domain" => "VARCHAR(255) NOT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "private_comment" => "TEXT", - "public_comment" => "TEXT", - "sogo_visible" => "TINYINT(1) NOT NULL DEFAULT '1'", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "unique" => array( - "address" => array("address") - ), - "key" => array( - "domain" => array("domain") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "api" => array( - "cols" => array( - "api_key" => "VARCHAR(255) NOT NULL", - "allow_from" => "VARCHAR(512) NOT NULL", - "skip_ip_check" => "TINYINT(1) NOT NULL DEFAULT '0'", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE NOW(0)", - "access" => "ENUM('ro', 'rw') NOT NULL DEFAULT 'rw'", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("api_key") - ), - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sender_acl" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "logged_in_as" => "VARCHAR(255) NOT NULL", - "send_as" => "VARCHAR(255) NOT NULL", - "external" => "TINYINT(1) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), +query("SHOW TABLES LIKE 'versions'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $stmt = $pdo->query("SELECT `version` FROM `versions` WHERE `application` = 'db_schema'"); + if ($stmt->fetch(PDO::FETCH_ASSOC)['version'] == $db_version) { + return true; + } + if (!preg_match('/y|yes/i', getenv('MASTER'))) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__), + 'msg' => 'Database not initialized: not running db_init on slave.' + ); + return true; + } + } + + $views = array( + "grouped_mail_aliases" => "CREATE VIEW grouped_mail_aliases (username, aliases) AS + SELECT goto, IFNULL(GROUP_CONCAT(address ORDER BY address SEPARATOR ' '), '') AS address FROM alias + WHERE address!=goto + AND active = '1' + AND sogo_visible = '1' + AND address NOT LIKE '@%' + GROUP BY goto;", + // START + // Unused at the moment - we cannot allow to show a foreign mailbox as sender address in SOGo, as SOGo does not like this + // We need to create delegation in SOGo AND set a sender_acl in mailcow to allow to send as user X + "grouped_sender_acl" => "CREATE VIEW grouped_sender_acl (username, send_as_acl) AS + SELECT logged_in_as, IFNULL(GROUP_CONCAT(send_as SEPARATOR ' '), '') AS send_as_acl FROM sender_acl + WHERE send_as NOT LIKE '@%' + GROUP BY logged_in_as;", + // END + "grouped_sender_acl_external" => "CREATE VIEW grouped_sender_acl_external (username, send_as_acl) AS + SELECT logged_in_as, IFNULL(GROUP_CONCAT(send_as SEPARATOR ' '), '') AS send_as_acl FROM sender_acl + WHERE send_as NOT LIKE '@%' AND external = '1' + GROUP BY logged_in_as;", + "grouped_domain_alias_address" => "CREATE VIEW grouped_domain_alias_address (username, ad_alias) AS + SELECT username, IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ' '), '') AS ad_alias FROM mailbox + LEFT OUTER JOIN alias_domain ON target_domain=domain + GROUP BY username;", + "sieve_before" => "CREATE VIEW sieve_before (id, username, script_name, script_data) AS + SELECT md5(script_data), username, script_name, script_data FROM sieve_filters + WHERE filter_type = 'prefilter';", + "sieve_after" => "CREATE VIEW sieve_after (id, username, script_name, script_data) AS + SELECT md5(script_data), username, script_name, script_data FROM sieve_filters + WHERE filter_type = 'postfilter';" + ); + + $tables = array( + "versions" => array( + "cols" => array( + "application" => "VARCHAR(255) NOT NULL", + "version" => "VARCHAR(100) NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + ), + "keys" => array( + "primary" => array( + "" => array("application") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "admin" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "password" => "VARCHAR(255) NOT NULL", + "superadmin" => "TINYINT(1) NOT NULL DEFAULT '0'", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE NOW(0)", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("username") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "fido2" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "friendlyName" => "VARCHAR(255)", + "rpId" => "VARCHAR(255) NOT NULL", + "credentialPublicKey" => "TEXT NOT NULL", + "certificateChain" => "TEXT", + // Can be null for format "none" + "certificate" => "TEXT", + "certificateIssuer" => "VARCHAR(255)", + "certificateSubject" => "VARCHAR(255)", + "signatureCounter" => "INT", + "AAGUID" => "BLOB", + "credentialId" => "BLOB NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE NOW(0)", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "_sogo_static_view" => array( + "cols" => array( + "c_uid" => "VARCHAR(255) NOT NULL", + "domain" => "VARCHAR(255) NOT NULL", + "c_name" => "VARCHAR(255) NOT NULL", + "c_password" => "VARCHAR(255) NOT NULL DEFAULT ''", + "c_cn" => "VARCHAR(255)", + "mail" => "VARCHAR(255) NOT NULL", + // TODO -> use TEXT and check if SOGo login breaks on empty aliases + "aliases" => "TEXT NOT NULL", + "ad_aliases" => "VARCHAR(6144) NOT NULL DEFAULT ''", + "ext_acl" => "VARCHAR(6144) NOT NULL DEFAULT ''", + "kind" => "VARCHAR(100) NOT NULL DEFAULT ''", + "multiple_bookings" => "INT NOT NULL DEFAULT -1" + ), + "keys" => array( + "primary" => array( + "" => array("c_uid") + ), + "key" => array( + "domain" => array("domain") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "relayhosts" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "hostname" => "VARCHAR(255) NOT NULL", + "username" => "VARCHAR(255) NOT NULL", + "password" => "VARCHAR(255) NOT NULL", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "hostname" => array("hostname") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "transports" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "destination" => "VARCHAR(255) NOT NULL", + "nexthop" => "VARCHAR(255) NOT NULL", + "username" => "VARCHAR(255) NOT NULL DEFAULT ''", + "password" => "VARCHAR(255) NOT NULL DEFAULT ''", + "is_mx_based" => "TINYINT(1) NOT NULL DEFAULT '0'", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "destination" => array("destination"), + "nexthop" => array("nexthop"), + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "alias" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "address" => "VARCHAR(255) NOT NULL", + "goto" => "TEXT NOT NULL", + "domain" => "VARCHAR(255) NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "private_comment" => "TEXT", + "public_comment" => "TEXT", + "sogo_visible" => "TINYINT(1) NOT NULL DEFAULT '1'", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "unique" => array( + "address" => array("address") + ), + "key" => array( + "domain" => array("domain") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "api" => array( + "cols" => array( + "api_key" => "VARCHAR(255) NOT NULL", + "allow_from" => "VARCHAR(512) NOT NULL", + "skip_ip_check" => "TINYINT(1) NOT NULL DEFAULT '0'", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE NOW(0)", + "access" => "ENUM('ro', 'rw') NOT NULL DEFAULT 'rw'", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("api_key") + ), + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sender_acl" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "logged_in_as" => "VARCHAR(255) NOT NULL", + "send_as" => "VARCHAR(255) NOT NULL", + "external" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), "templates" => array( "cols" => array( "id" => "INT NOT NULL AUTO_INCREMENT", @@ -241,1074 +241,1087 @@ function init_db_schema() { ), "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" ), - "domain" => array( - // Todo: Move some attributes to json - "cols" => array( - "domain" => "VARCHAR(255) NOT NULL", - "description" => "VARCHAR(255)", - "aliases" => "INT(10) NOT NULL DEFAULT '0'", - "mailboxes" => "INT(10) NOT NULL DEFAULT '0'", - "defquota" => "BIGINT(20) NOT NULL DEFAULT '3072'", - "maxquota" => "BIGINT(20) NOT NULL DEFAULT '102400'", - "quota" => "BIGINT(20) NOT NULL DEFAULT '102400'", - "relayhost" => "VARCHAR(255) NOT NULL DEFAULT '0'", - "backupmx" => "TINYINT(1) NOT NULL DEFAULT '0'", - "gal" => "TINYINT(1) NOT NULL DEFAULT '1'", - "relay_all_recipients" => "TINYINT(1) NOT NULL DEFAULT '0'", - "relay_unknown_only" => "TINYINT(1) NOT NULL DEFAULT '0'", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("domain") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "tags_domain" => array( - "cols" => array( - "tag_name" => "VARCHAR(255) NOT NULL", - "domain" => "VARCHAR(255) NOT NULL" - ), - "keys" => array( - "fkey" => array( - "fk_tags_domain" => array( - "col" => "domain", - "ref" => "domain.domain", - "delete" => "CASCADE", - "update" => "NO ACTION" - ) - ), - "unique" => array( - "tag_name" => array("tag_name", "domain") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "tls_policy_override" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "dest" => "VARCHAR(255) NOT NULL", - "policy" => "ENUM('none', 'may', 'encrypt', 'dane', 'dane-only', 'fingerprint', 'verify', 'secure') NOT NULL", - "parameters" => "VARCHAR(255) DEFAULT ''", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "unique" => array( - "dest" => array("dest") - ), - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "quarantine" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "qid" => "VARCHAR(30) NOT NULL", - "subject" => "VARCHAR(500)", - "score" => "FLOAT(8,2)", - "ip" => "VARCHAR(50)", - "action" => "CHAR(20) NOT NULL DEFAULT 'unknown'", - "symbols" => "JSON", - "fuzzy_hashes" => "JSON", - "sender" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'", - "rcpt" => "VARCHAR(255)", - "msg" => "LONGTEXT", - "domain" => "VARCHAR(255)", - "notified" => "TINYINT(1) NOT NULL DEFAULT '0'", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "user" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'", - ), - "keys" => array( - "primary" => array( - "" => array("id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "mailbox" => array( - "cols" => array( - "username" => "VARCHAR(255) NOT NULL", - "password" => "VARCHAR(255) NOT NULL", - "name" => "VARCHAR(255)", - "description" => "VARCHAR(255)", - // mailbox_path_prefix is followed by domain/local_part/ - "mailbox_path_prefix" => "VARCHAR(150) DEFAULT '/var/vmail/'", - "quota" => "BIGINT(20) NOT NULL DEFAULT '102400'", - "local_part" => "VARCHAR(255) NOT NULL", - "domain" => "VARCHAR(255) NOT NULL", - "attributes" => "JSON", - "kind" => "VARCHAR(100) NOT NULL DEFAULT ''", - "multiple_bookings" => "INT NOT NULL DEFAULT -1", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("username") - ), - "key" => array( - "domain" => array("domain"), - "kind" => array("kind") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "tags_mailbox" => array( - "cols" => array( - "tag_name" => "VARCHAR(255) NOT NULL", - "username" => "VARCHAR(255) NOT NULL" - ), - "keys" => array( - "fkey" => array( - "fk_tags_mailbox" => array( - "col" => "username", - "ref" => "mailbox.username", - "delete" => "CASCADE", - "update" => "NO ACTION" - ) - ), - "unique" => array( - "tag_name" => array("tag_name", "username") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sieve_filters" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "username" => "VARCHAR(255) NOT NULL", - "script_desc" => "VARCHAR(255) NOT NULL", - "script_name" => "ENUM('active','inactive')", - "script_data" => "TEXT NOT NULL", - "filter_type" => "ENUM('postfilter','prefilter')", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "key" => array( - "username" => array("username"), - "script_desc" => array("script_desc") - ), - "fkey" => array( - "fk_username_sieve_global_before" => array( - "col" => "username", - "ref" => "mailbox.username", - "delete" => "CASCADE", - "update" => "NO ACTION" - ) - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "app_passwd" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "name" => "VARCHAR(255) NOT NULL", - "mailbox" => "VARCHAR(255) NOT NULL", - "domain" => "VARCHAR(255) NOT NULL", - "password" => "VARCHAR(255) NOT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "imap_access" => "TINYINT(1) NOT NULL DEFAULT '1'", - "smtp_access" => "TINYINT(1) NOT NULL DEFAULT '1'", - "dav_access" => "TINYINT(1) NOT NULL DEFAULT '1'", - "eas_access" => "TINYINT(1) NOT NULL DEFAULT '1'", - "pop3_access" => "TINYINT(1) NOT NULL DEFAULT '1'", - "sieve_access" => "TINYINT(1) NOT NULL DEFAULT '1'", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "key" => array( - "mailbox" => array("mailbox"), - "password" => array("password"), - "domain" => array("domain"), - ), - "fkey" => array( - "fk_username_app_passwd" => array( - "col" => "mailbox", - "ref" => "mailbox.username", - "delete" => "CASCADE", - "update" => "NO ACTION" - ) - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "user_acl" => array( - "cols" => array( - "username" => "VARCHAR(255) NOT NULL", - "spam_alias" => "TINYINT(1) NOT NULL DEFAULT '1'", - "tls_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", - "spam_score" => "TINYINT(1) NOT NULL DEFAULT '1'", - "spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", - "delimiter_action" => "TINYINT(1) NOT NULL DEFAULT '1'", - "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '0'", - "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'", - "sogo_profile_reset" => "TINYINT(1) NOT NULL DEFAULT '0'", - "pushover" => "TINYINT(1) NOT NULL DEFAULT '1'", - // quarantine is for quarantine actions, todo: rename - "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'", - "quarantine_attachments" => "TINYINT(1) NOT NULL DEFAULT '1'", - "quarantine_notification" => "TINYINT(1) NOT NULL DEFAULT '1'", - "quarantine_category" => "TINYINT(1) NOT NULL DEFAULT '1'", - "app_passwds" => "TINYINT(1) NOT NULL DEFAULT '1'", - ), - "keys" => array( - "primary" => array( - "" => array("username") - ), - "fkey" => array( - "fk_username" => array( - "col" => "username", - "ref" => "mailbox.username", - "delete" => "CASCADE", - "update" => "NO ACTION" - ) - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "alias_domain" => array( - "cols" => array( - "alias_domain" => "VARCHAR(255) NOT NULL", - "target_domain" => "VARCHAR(255) NOT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("alias_domain") - ), - "key" => array( - "active" => array("active"), - "target_domain" => array("target_domain") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "spamalias" => array( - "cols" => array( - "address" => "VARCHAR(255) NOT NULL", - "goto" => "TEXT NOT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "validity" => "INT(11)" - ), - "keys" => array( - "primary" => array( - "" => array("address") - ), - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "filterconf" => array( - "cols" => array( - "object" => "VARCHAR(255) NOT NULL DEFAULT ''", - "option" => "VARCHAR(50) NOT NULL DEFAULT ''", - "value" => "VARCHAR(100) NOT NULL DEFAULT ''", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "prefid" => "INT(11) NOT NULL AUTO_INCREMENT" - ), - "keys" => array( - "primary" => array( - "" => array("prefid") - ), - "key" => array( - "object" => array("object") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "settingsmap" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "desc" => "VARCHAR(255) NOT NULL", - "content" => "LONGTEXT NOT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "logs" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "task" => "CHAR(32) NOT NULL DEFAULT '000000'", - "type" => "VARCHAR(32) DEFAULT ''", - "msg" => "TEXT", - "call" => "TEXT", - "user" => "VARCHAR(64) NOT NULL", - "role" => "VARCHAR(32) NOT NULL", - "remote" => "VARCHAR(39) NOT NULL", - "time" => "INT(11) NOT NULL" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sasl_log" => array( - "cols" => array( - "service" => "VARCHAR(32) NOT NULL DEFAULT ''", - "app_password" => "INT", - "username" => "VARCHAR(255) NOT NULL", - "real_rip" => "VARCHAR(64) NOT NULL", - "datetime" => "DATETIME(0) NOT NULL DEFAULT NOW(0)" - ), - "keys" => array( - "primary" => array( - "" => array("service", "real_rip", "username") - ), - "key" => array( - "username" => array("username"), - "service" => array("service"), - "datetime" => array("datetime"), - "real_rip" => array("real_rip") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "quota2" => array( - "cols" => array( - "username" => "VARCHAR(255) NOT NULL", - "bytes" => "BIGINT(20) NOT NULL DEFAULT '0'", - "messages" => "BIGINT(20) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("username") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "quota2replica" => array( - "cols" => array( - "username" => "VARCHAR(255) NOT NULL", - "bytes" => "BIGINT(20) NOT NULL DEFAULT '0'", - "messages" => "BIGINT(20) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("username") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "domain_admins" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "username" => "VARCHAR(255) NOT NULL", - "domain" => "VARCHAR(255) NOT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "key" => array( - "username" => array("username") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "da_acl" => array( - "cols" => array( - "username" => "VARCHAR(255) NOT NULL", - "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'", - "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'", - "login_as" => "TINYINT(1) NOT NULL DEFAULT '1'", - "sogo_access" => "TINYINT(1) NOT NULL DEFAULT '1'", - "app_passwds" => "TINYINT(1) NOT NULL DEFAULT '1'", - "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'", - "pushover" => "TINYINT(1) NOT NULL DEFAULT '0'", - "filters" => "TINYINT(1) NOT NULL DEFAULT '1'", - "ratelimit" => "TINYINT(1) NOT NULL DEFAULT '1'", - "spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", - "extend_sender_acl" => "TINYINT(1) NOT NULL DEFAULT '0'", - "unlimited_quota" => "TINYINT(1) NOT NULL DEFAULT '0'", - "protocol_access" => "TINYINT(1) NOT NULL DEFAULT '1'", - "smtp_ip_access" => "TINYINT(1) NOT NULL DEFAULT '1'", - "alias_domains" => "TINYINT(1) NOT NULL DEFAULT '0'", - "mailbox_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'", - "domain_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'", - "domain_desc" => "TINYINT(1) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("username") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "imapsync" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "user2" => "VARCHAR(255) NOT NULL", - "host1" => "VARCHAR(255) NOT NULL", - "authmech1" => "ENUM('PLAIN','LOGIN','CRAM-MD5') DEFAULT 'PLAIN'", - "regextrans2" => "VARCHAR(255) DEFAULT ''", - "authmd51" => "TINYINT(1) NOT NULL DEFAULT 0", - "domain2" => "VARCHAR(255) NOT NULL DEFAULT ''", - "subfolder2" => "VARCHAR(255) NOT NULL DEFAULT ''", - "user1" => "VARCHAR(255) NOT NULL", - "password1" => "VARCHAR(255) NOT NULL", - "exclude" => "VARCHAR(500) NOT NULL DEFAULT ''", - "maxage" => "SMALLINT NOT NULL DEFAULT '0'", - "mins_interval" => "SMALLINT UNSIGNED NOT NULL DEFAULT '0'", - "maxbytespersecond" => "VARCHAR(50) NOT NULL DEFAULT '0'", - "port1" => "SMALLINT UNSIGNED NOT NULL", - "enc1" => "ENUM('TLS','SSL','PLAIN') DEFAULT 'TLS'", - "delete2duplicates" => "TINYINT(1) NOT NULL DEFAULT '1'", - "delete1" => "TINYINT(1) NOT NULL DEFAULT '0'", - "delete2" => "TINYINT(1) NOT NULL DEFAULT '0'", - "automap" => "TINYINT(1) NOT NULL DEFAULT '0'", - "skipcrossduplicates" => "TINYINT(1) NOT NULL DEFAULT '0'", - "custom_params" => "VARCHAR(512) NOT NULL DEFAULT ''", - "timeout1" => "SMALLINT NOT NULL DEFAULT '600'", - "timeout2" => "SMALLINT NOT NULL DEFAULT '600'", - "subscribeall" => "TINYINT(1) NOT NULL DEFAULT '1'", - "is_running" => "TINYINT(1) NOT NULL DEFAULT '0'", - "returned_text" => "LONGTEXT", - "last_run" => "TIMESTAMP NULL DEFAULT NULL", - "success" => "TINYINT(1) UNSIGNED DEFAULT NULL", - "exit_status" => "VARCHAR(50) DEFAULT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "bcc_maps" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "local_dest" => "VARCHAR(255) NOT NULL", - "bcc_dest" => "VARCHAR(255) NOT NULL", - "domain" => "VARCHAR(255) NOT NULL", - "type" => "ENUM('sender','rcpt')", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "key" => array( - "local_dest" => array("local_dest"), - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "recipient_maps" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "old_dest" => "VARCHAR(255) NOT NULL", - "new_dest" => "VARCHAR(255) NOT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "key" => array( - "local_dest" => array("old_dest"), - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "tfa" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "key_id" => "VARCHAR(255) NOT NULL", - "username" => "VARCHAR(255) NOT NULL", - "authmech" => "ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')", - "secret" => "VARCHAR(255) DEFAULT NULL", - "keyHandle" => "VARCHAR(1023) DEFAULT NULL", - "publicKey" => "VARCHAR(4096) DEFAULT NULL", - "counter" => "INT NOT NULL DEFAULT '0'", - "certificate" => "TEXT", - "active" => "TINYINT(1) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "forwarding_hosts" => array( - "cols" => array( - "host" => "VARCHAR(255) NOT NULL", - "source" => "VARCHAR(255) NOT NULL", - "filter_spam" => "TINYINT(1) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("host") - ), - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_acl" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "c_folder_id" => "INT NOT NULL", - "c_object" => "VARCHAR(255) NOT NULL", - "c_uid" => "VARCHAR(255) NOT NULL", - "c_role" => "VARCHAR(80) NOT NULL" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "key" => array( - "sogo_acl_c_folder_id_idx" => array("c_folder_id"), - "sogo_acl_c_uid_idx" => array("c_uid") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_alarms_folder" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "c_path" => "VARCHAR(255) NOT NULL", - "c_name" => "VARCHAR(255) NOT NULL", - "c_uid" => "VARCHAR(255) NOT NULL", - "c_recurrence_id" => "INT(11) DEFAULT NULL", - "c_alarm_number" => "INT(11) NOT NULL", - "c_alarm_date" => "INT(11) NOT NULL" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_cache_folder" => array( - "cols" => array( - "c_uid" => "VARCHAR(255) NOT NULL", - "c_path" => "VARCHAR(255) NOT NULL", - "c_parent_path" => "VARCHAR(255) DEFAULT NULL", - "c_type" => "TINYINT(3) unsigned NOT NULL", - "c_creationdate" => "INT(11) NOT NULL", - "c_lastmodified" => "INT(11) NOT NULL", - "c_version" => "INT(11) NOT NULL DEFAULT '0'", - "c_deleted" => "TINYINT(4) NOT NULL DEFAULT '0'", - "c_content" => "LONGTEXT" - ), - "keys" => array( - "primary" => array( - "" => array("c_uid", "c_path") - ), - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_folder_info" => array( - "cols" => array( - "c_folder_id" => "BIGINT(20) unsigned NOT NULL AUTO_INCREMENT", - "c_path" => "VARCHAR(255) NOT NULL", - "c_path1" => "VARCHAR(255) NOT NULL", - "c_path2" => "VARCHAR(255) DEFAULT NULL", - "c_path3" => "VARCHAR(255) DEFAULT NULL", - "c_path4" => "VARCHAR(255) DEFAULT NULL", - "c_foldername" => "VARCHAR(255) NOT NULL", - "c_location" => "VARCHAR(2048) DEFAULT NULL", - "c_quick_location" => "VARCHAR(2048) DEFAULT NULL", - "c_acl_location" => "VARCHAR(2048) DEFAULT NULL", - "c_folder_type" => "VARCHAR(255) NOT NULL" - ), - "keys" => array( - "primary" => array( - "" => array("c_path") - ), - "unique" => array( - "c_folder_id" => array("c_folder_id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_quick_appointment" => array( - "cols" => array( - "c_folder_id" => "INT NOT NULL", - "c_name" => "VARCHAR(255) NOT NULL", - "c_uid" => "VARCHAR(1000) NOT NULL", - "c_startdate" => "INT", - "c_enddate" => "INT", - "c_cycleenddate" => "INT", - "c_title" => "VARCHAR(1000) NOT NULL", - "c_participants" => "TEXT", - "c_isallday" => "INT", - "c_iscycle" => "INT", - "c_cycleinfo" => "TEXT", - "c_classification" => "INT NOT NULL", - "c_isopaque" => "INT NOT NULL", - "c_status" => "INT NOT NULL", - "c_priority" => "INT", - "c_location" => "VARCHAR(255)", - "c_orgmail" => "VARCHAR(255)", - "c_partmails" => "TEXT", - "c_partstates" => "TEXT", - "c_category" => "VARCHAR(255)", - "c_sequence" => "INT", - "c_component" => "VARCHAR(10) NOT NULL", - "c_nextalarm" => "INT", - "c_description" => "TEXT" - ), - "keys" => array( - "primary" => array( - "" => array("c_folder_id", "c_name") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_quick_contact" => array( - "cols" => array( - "c_folder_id" => "INT NOT NULL", - "c_name" => "VARCHAR(255) NOT NULL", - "c_givenname" => "VARCHAR(255)", - "c_cn" => "VARCHAR(255)", - "c_sn" => "VARCHAR(255)", - "c_screenname" => "VARCHAR(255)", - "c_l" => "VARCHAR(255)", - "c_mail" => "TEXT", - "c_o" => "VARCHAR(500)", - "c_ou" => "VARCHAR(255)", - "c_telephonenumber" => "VARCHAR(255)", - "c_categories" => "VARCHAR(255)", - "c_component" => "VARCHAR(10) NOT NULL", - "c_hascertificate" => "INT4 DEFAULT 0" - ), - "keys" => array( - "primary" => array( - "" => array("c_folder_id", "c_name") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_sessions_folder" => array( - "cols" => array( - "c_id" => "VARCHAR(255) NOT NULL", - "c_value" => "VARCHAR(4096) NOT NULL", - "c_creationdate" => "INT(11) NOT NULL", - "c_lastseen" => "INT(11) NOT NULL" - ), - "keys" => array( - "primary" => array( - "" => array("c_id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_store" => array( - "cols" => array( - "c_folder_id" => "INT NOT NULL", - "c_name" => "VARCHAR(255) NOT NULL", - "c_content" => "MEDIUMTEXT NOT NULL", - "c_creationdate" => "INT NOT NULL", - "c_lastmodified" => "INT NOT NULL", - "c_version" => "INT NOT NULL", - "c_deleted" => "INT" - ), - "keys" => array( - "primary" => array( - "" => array("c_folder_id", "c_name") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "pushover" => array( - "cols" => array( - "username" => "VARCHAR(255) NOT NULL", - "key" => "VARCHAR(255) NOT NULL", - "token" => "VARCHAR(255) NOT NULL", - "attributes" => "JSON", - "title" => "TEXT", - "text" => "TEXT", - "senders" => "TEXT", - "senders_regex" => "TEXT", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("username") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_user_profile" => array( - "cols" => array( - "c_uid" => "VARCHAR(255) NOT NULL", - "c_defaults" => "LONGTEXT", - "c_settings" => "LONGTEXT" - ), - "keys" => array( - "primary" => array( - "" => array("c_uid") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "oauth_clients" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "client_id" => "VARCHAR(80) NOT NULL", - "client_secret" => "VARCHAR(80)", - "redirect_uri" => "VARCHAR(2000)", - "grant_types" => "VARCHAR(80)", - "scope" => "VARCHAR(4000)", - "user_id" => "VARCHAR(80)" - ), - "keys" => array( - "primary" => array( - "" => array("client_id") - ), - "unique" => array( - "id" => array("id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "oauth_access_tokens" => array( - "cols" => array( - "access_token" => "VARCHAR(40) NOT NULL", - "client_id" => "VARCHAR(80) NOT NULL", - "user_id" => "VARCHAR(80)", - "expires" => "TIMESTAMP NOT NULL", - "scope" => "VARCHAR(4000)" - ), - "keys" => array( - "primary" => array( - "" => array("access_token") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "oauth_authorization_codes" => array( - "cols" => array( - "authorization_code" => "VARCHAR(40) NOT NULL", - "client_id" => "VARCHAR(80) NOT NULL", - "user_id" => "VARCHAR(80)", - "redirect_uri" => "VARCHAR(2000)", - "expires" => "TIMESTAMP NOT NULL", - "scope" => "VARCHAR(4000)", - "id_token" => "VARCHAR(1000)" - ), - "keys" => array( - "primary" => array( - "" => array("authorization_code") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "oauth_refresh_tokens" => array( - "cols" => array( - "refresh_token" => "VARCHAR(40) NOT NULL", - "client_id" => "VARCHAR(80) NOT NULL", - "user_id" => "VARCHAR(80)", - "expires" => "TIMESTAMP NOT NULL", - "scope" => "VARCHAR(4000)" - ), - "keys" => array( - "primary" => array( - "" => array("refresh_token") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ) - ); - - foreach ($tables as $table => $properties) { - // Migrate to quarantine - if ($table == 'quarantine') { - $stmt = $pdo->query("SHOW TABLES LIKE 'quarantaine'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $stmt = $pdo->query("SHOW TABLES LIKE 'quarantine'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results == 0) { - $pdo->query("RENAME TABLE `quarantaine` TO `quarantine`"); - } - } - } - + "domain" => array( + // Todo: Move some attributes to json + "cols" => array( + "domain" => "VARCHAR(255) NOT NULL", + "description" => "VARCHAR(255)", + "aliases" => "INT(10) NOT NULL DEFAULT '0'", + "mailboxes" => "INT(10) NOT NULL DEFAULT '0'", + "defquota" => "BIGINT(20) NOT NULL DEFAULT '3072'", + "maxquota" => "BIGINT(20) NOT NULL DEFAULT '102400'", + "quota" => "BIGINT(20) NOT NULL DEFAULT '102400'", + "relayhost" => "VARCHAR(255) NOT NULL DEFAULT '0'", + "backupmx" => "TINYINT(1) NOT NULL DEFAULT '0'", + "gal" => "TINYINT(1) NOT NULL DEFAULT '1'", + "relay_all_recipients" => "TINYINT(1) NOT NULL DEFAULT '0'", + "relay_unknown_only" => "TINYINT(1) NOT NULL DEFAULT '0'", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("domain") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "tags_domain" => array( + "cols" => array( + "tag_name" => "VARCHAR(255) NOT NULL", + "domain" => "VARCHAR(255) NOT NULL" + ), + "keys" => array( + "fkey" => array( + "fk_tags_domain" => array( + "col" => "domain", + "ref" => "domain.domain", + "delete" => "CASCADE", + "update" => "NO ACTION" + ) + ), + "unique" => array( + "tag_name" => array("tag_name", "domain") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "tls_policy_override" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "dest" => "VARCHAR(255) NOT NULL", + "policy" => "ENUM('none', 'may', 'encrypt', 'dane', 'dane-only', 'fingerprint', 'verify', 'secure') NOT NULL", + "parameters" => "VARCHAR(255) DEFAULT ''", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "unique" => array( + "dest" => array("dest") + ), + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "quarantine" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "qid" => "VARCHAR(30) NOT NULL", + "subject" => "VARCHAR(500)", + "score" => "FLOAT(8,2)", + "ip" => "VARCHAR(50)", + "action" => "CHAR(20) NOT NULL DEFAULT 'unknown'", + "symbols" => "JSON", + "fuzzy_hashes" => "JSON", + "sender" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'", + "rcpt" => "VARCHAR(255)", + "msg" => "LONGTEXT", + "domain" => "VARCHAR(255)", + "notified" => "TINYINT(1) NOT NULL DEFAULT '0'", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "user" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'", + ), + "keys" => array( + "primary" => array( + "" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "mailbox" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "password" => "VARCHAR(255) NOT NULL", + "name" => "VARCHAR(255)", + "description" => "VARCHAR(255)", + // mailbox_path_prefix is followed by domain/local_part/ + "mailbox_path_prefix" => "VARCHAR(150) DEFAULT '/var/vmail/'", + "quota" => "BIGINT(20) NOT NULL DEFAULT '102400'", + "local_part" => "VARCHAR(255) NOT NULL", + "domain" => "VARCHAR(255) NOT NULL", + "attributes" => "JSON", + "kind" => "VARCHAR(100) NOT NULL DEFAULT ''", + "multiple_bookings" => "INT NOT NULL DEFAULT -1", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("username") + ), + "key" => array( + "domain" => array("domain"), + "kind" => array("kind") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "tags_mailbox" => array( + "cols" => array( + "tag_name" => "VARCHAR(255) NOT NULL", + "username" => "VARCHAR(255) NOT NULL" + ), + "keys" => array( + "fkey" => array( + "fk_tags_mailbox" => array( + "col" => "username", + "ref" => "mailbox.username", + "delete" => "CASCADE", + "update" => "NO ACTION" + ) + ), + "unique" => array( + "tag_name" => array("tag_name", "username") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sieve_filters" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "username" => "VARCHAR(255) NOT NULL", + "script_desc" => "VARCHAR(255) NOT NULL", + "script_name" => "ENUM('active','inactive')", + "script_data" => "TEXT NOT NULL", + "filter_type" => "ENUM('postfilter','prefilter')", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "username" => array("username"), + "script_desc" => array("script_desc") + ), + "fkey" => array( + "fk_username_sieve_global_before" => array( + "col" => "username", + "ref" => "mailbox.username", + "delete" => "CASCADE", + "update" => "NO ACTION" + ) + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "app_passwd" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "name" => "VARCHAR(255) NOT NULL", + "mailbox" => "VARCHAR(255) NOT NULL", + "domain" => "VARCHAR(255) NOT NULL", + "password" => "VARCHAR(255) NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "imap_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "smtp_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "dav_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "eas_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "pop3_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "sieve_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "mailbox" => array("mailbox"), + "password" => array("password"), + "domain" => array("domain"), + ), + "fkey" => array( + "fk_username_app_passwd" => array( + "col" => "mailbox", + "ref" => "mailbox.username", + "delete" => "CASCADE", + "update" => "NO ACTION" + ) + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "user_acl" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "spam_alias" => "TINYINT(1) NOT NULL DEFAULT '1'", + "tls_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", + "spam_score" => "TINYINT(1) NOT NULL DEFAULT '1'", + "spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", + "delimiter_action" => "TINYINT(1) NOT NULL DEFAULT '1'", + "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '0'", + "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'", + "sogo_profile_reset" => "TINYINT(1) NOT NULL DEFAULT '0'", + "pushover" => "TINYINT(1) NOT NULL DEFAULT '1'", + // quarantine is for quarantine actions, todo: rename + "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'", + "quarantine_attachments" => "TINYINT(1) NOT NULL DEFAULT '1'", + "quarantine_notification" => "TINYINT(1) NOT NULL DEFAULT '1'", + "quarantine_category" => "TINYINT(1) NOT NULL DEFAULT '1'", + "app_passwds" => "TINYINT(1) NOT NULL DEFAULT '1'", + ), + "keys" => array( + "primary" => array( + "" => array("username") + ), + "fkey" => array( + "fk_username" => array( + "col" => "username", + "ref" => "mailbox.username", + "delete" => "CASCADE", + "update" => "NO ACTION" + ) + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "alias_domain" => array( + "cols" => array( + "alias_domain" => "VARCHAR(255) NOT NULL", + "target_domain" => "VARCHAR(255) NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("alias_domain") + ), + "key" => array( + "active" => array("active"), + "target_domain" => array("target_domain") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "spamalias" => array( + "cols" => array( + "address" => "VARCHAR(255) NOT NULL", + "goto" => "TEXT NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "validity" => "INT(11)" + ), + "keys" => array( + "primary" => array( + "" => array("address") + ), + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "filterconf" => array( + "cols" => array( + "object" => "VARCHAR(255) NOT NULL DEFAULT ''", + "option" => "VARCHAR(50) NOT NULL DEFAULT ''", + "value" => "VARCHAR(100) NOT NULL DEFAULT ''", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "prefid" => "INT(11) NOT NULL AUTO_INCREMENT" + ), + "keys" => array( + "primary" => array( + "" => array("prefid") + ), + "key" => array( + "object" => array("object") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "settingsmap" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "desc" => "VARCHAR(255) NOT NULL", + "content" => "LONGTEXT NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "logs" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "task" => "CHAR(32) NOT NULL DEFAULT '000000'", + "type" => "VARCHAR(32) DEFAULT ''", + "msg" => "TEXT", + "call" => "TEXT", + "user" => "VARCHAR(64) NOT NULL", + "role" => "VARCHAR(32) NOT NULL", + "remote" => "VARCHAR(39) NOT NULL", + "time" => "INT(11) NOT NULL" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sasl_log" => array( + "cols" => array( + "service" => "VARCHAR(32) NOT NULL DEFAULT ''", + "app_password" => "INT", + "username" => "VARCHAR(255) NOT NULL", + "real_rip" => "VARCHAR(64) NOT NULL", + "datetime" => "DATETIME(0) NOT NULL DEFAULT NOW(0)" + ), + "keys" => array( + "primary" => array( + "" => array("service", "real_rip", "username") + ), + "key" => array( + "username" => array("username"), + "service" => array("service"), + "datetime" => array("datetime"), + "real_rip" => array("real_rip") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "quota2" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "bytes" => "BIGINT(20) NOT NULL DEFAULT '0'", + "messages" => "BIGINT(20) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("username") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "quota2replica" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "bytes" => "BIGINT(20) NOT NULL DEFAULT '0'", + "messages" => "BIGINT(20) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("username") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "domain_admins" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "username" => "VARCHAR(255) NOT NULL", + "domain" => "VARCHAR(255) NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "username" => array("username") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "da_acl" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'", + "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'", + "login_as" => "TINYINT(1) NOT NULL DEFAULT '1'", + "sogo_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "app_passwds" => "TINYINT(1) NOT NULL DEFAULT '1'", + "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'", + "pushover" => "TINYINT(1) NOT NULL DEFAULT '0'", + "filters" => "TINYINT(1) NOT NULL DEFAULT '1'", + "ratelimit" => "TINYINT(1) NOT NULL DEFAULT '1'", + "spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", + "extend_sender_acl" => "TINYINT(1) NOT NULL DEFAULT '0'", + "unlimited_quota" => "TINYINT(1) NOT NULL DEFAULT '0'", + "protocol_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "smtp_ip_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "alias_domains" => "TINYINT(1) NOT NULL DEFAULT '0'", + "mailbox_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'", + "domain_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'", + "domain_desc" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("username") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "da_sso" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "token" => "VARCHAR(255) NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + ), + "keys" => array( + "primary" => array( + "" => array("token", "created") + ), + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "imapsync" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "user2" => "VARCHAR(255) NOT NULL", + "host1" => "VARCHAR(255) NOT NULL", + "authmech1" => "ENUM('PLAIN','LOGIN','CRAM-MD5') DEFAULT 'PLAIN'", + "regextrans2" => "VARCHAR(255) DEFAULT ''", + "authmd51" => "TINYINT(1) NOT NULL DEFAULT 0", + "domain2" => "VARCHAR(255) NOT NULL DEFAULT ''", + "subfolder2" => "VARCHAR(255) NOT NULL DEFAULT ''", + "user1" => "VARCHAR(255) NOT NULL", + "password1" => "VARCHAR(255) NOT NULL", + "exclude" => "VARCHAR(500) NOT NULL DEFAULT ''", + "maxage" => "SMALLINT NOT NULL DEFAULT '0'", + "mins_interval" => "SMALLINT UNSIGNED NOT NULL DEFAULT '0'", + "maxbytespersecond" => "VARCHAR(50) NOT NULL DEFAULT '0'", + "port1" => "SMALLINT UNSIGNED NOT NULL", + "enc1" => "ENUM('TLS','SSL','PLAIN') DEFAULT 'TLS'", + "delete2duplicates" => "TINYINT(1) NOT NULL DEFAULT '1'", + "delete1" => "TINYINT(1) NOT NULL DEFAULT '0'", + "delete2" => "TINYINT(1) NOT NULL DEFAULT '0'", + "automap" => "TINYINT(1) NOT NULL DEFAULT '0'", + "skipcrossduplicates" => "TINYINT(1) NOT NULL DEFAULT '0'", + "custom_params" => "VARCHAR(512) NOT NULL DEFAULT ''", + "timeout1" => "SMALLINT NOT NULL DEFAULT '600'", + "timeout2" => "SMALLINT NOT NULL DEFAULT '600'", + "subscribeall" => "TINYINT(1) NOT NULL DEFAULT '1'", + "is_running" => "TINYINT(1) NOT NULL DEFAULT '0'", + "returned_text" => "LONGTEXT", + "last_run" => "TIMESTAMP NULL DEFAULT NULL", + "success" => "TINYINT(1) UNSIGNED DEFAULT NULL", + "exit_status" => "VARCHAR(50) DEFAULT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "bcc_maps" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "local_dest" => "VARCHAR(255) NOT NULL", + "bcc_dest" => "VARCHAR(255) NOT NULL", + "domain" => "VARCHAR(255) NOT NULL", + "type" => "ENUM('sender','rcpt')", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "local_dest" => array("local_dest"), + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "recipient_maps" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "old_dest" => "VARCHAR(255) NOT NULL", + "new_dest" => "VARCHAR(255) NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "local_dest" => array("old_dest"), + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "tfa" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "key_id" => "VARCHAR(255) NOT NULL", + "username" => "VARCHAR(255) NOT NULL", + "authmech" => "ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')", + "secret" => "VARCHAR(255) DEFAULT NULL", + "keyHandle" => "VARCHAR(1023) DEFAULT NULL", + "publicKey" => "VARCHAR(4096) DEFAULT NULL", + "counter" => "INT NOT NULL DEFAULT '0'", + "certificate" => "TEXT", + "active" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "forwarding_hosts" => array( + "cols" => array( + "host" => "VARCHAR(255) NOT NULL", + "source" => "VARCHAR(255) NOT NULL", + "filter_spam" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("host") + ), + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_acl" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "c_folder_id" => "INT NOT NULL", + "c_object" => "VARCHAR(255) NOT NULL", + "c_uid" => "VARCHAR(255) NOT NULL", + "c_role" => "VARCHAR(80) NOT NULL" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "sogo_acl_c_folder_id_idx" => array("c_folder_id"), + "sogo_acl_c_uid_idx" => array("c_uid") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_alarms_folder" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "c_path" => "VARCHAR(255) NOT NULL", + "c_name" => "VARCHAR(255) NOT NULL", + "c_uid" => "VARCHAR(255) NOT NULL", + "c_recurrence_id" => "INT(11) DEFAULT NULL", + "c_alarm_number" => "INT(11) NOT NULL", + "c_alarm_date" => "INT(11) NOT NULL" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_cache_folder" => array( + "cols" => array( + "c_uid" => "VARCHAR(255) NOT NULL", + "c_path" => "VARCHAR(255) NOT NULL", + "c_parent_path" => "VARCHAR(255) DEFAULT NULL", + "c_type" => "TINYINT(3) unsigned NOT NULL", + "c_creationdate" => "INT(11) NOT NULL", + "c_lastmodified" => "INT(11) NOT NULL", + "c_version" => "INT(11) NOT NULL DEFAULT '0'", + "c_deleted" => "TINYINT(4) NOT NULL DEFAULT '0'", + "c_content" => "LONGTEXT" + ), + "keys" => array( + "primary" => array( + "" => array("c_uid", "c_path") + ), + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_folder_info" => array( + "cols" => array( + "c_folder_id" => "BIGINT(20) unsigned NOT NULL AUTO_INCREMENT", + "c_path" => "VARCHAR(255) NOT NULL", + "c_path1" => "VARCHAR(255) NOT NULL", + "c_path2" => "VARCHAR(255) DEFAULT NULL", + "c_path3" => "VARCHAR(255) DEFAULT NULL", + "c_path4" => "VARCHAR(255) DEFAULT NULL", + "c_foldername" => "VARCHAR(255) NOT NULL", + "c_location" => "VARCHAR(2048) DEFAULT NULL", + "c_quick_location" => "VARCHAR(2048) DEFAULT NULL", + "c_acl_location" => "VARCHAR(2048) DEFAULT NULL", + "c_folder_type" => "VARCHAR(255) NOT NULL" + ), + "keys" => array( + "primary" => array( + "" => array("c_path") + ), + "unique" => array( + "c_folder_id" => array("c_folder_id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_quick_appointment" => array( + "cols" => array( + "c_folder_id" => "INT NOT NULL", + "c_name" => "VARCHAR(255) NOT NULL", + "c_uid" => "VARCHAR(1000) NOT NULL", + "c_startdate" => "INT", + "c_enddate" => "INT", + "c_cycleenddate" => "INT", + "c_title" => "VARCHAR(1000) NOT NULL", + "c_participants" => "TEXT", + "c_isallday" => "INT", + "c_iscycle" => "INT", + "c_cycleinfo" => "TEXT", + "c_classification" => "INT NOT NULL", + "c_isopaque" => "INT NOT NULL", + "c_status" => "INT NOT NULL", + "c_priority" => "INT", + "c_location" => "VARCHAR(255)", + "c_orgmail" => "VARCHAR(255)", + "c_partmails" => "TEXT", + "c_partstates" => "TEXT", + "c_category" => "VARCHAR(255)", + "c_sequence" => "INT", + "c_component" => "VARCHAR(10) NOT NULL", + "c_nextalarm" => "INT", + "c_description" => "TEXT" + ), + "keys" => array( + "primary" => array( + "" => array("c_folder_id", "c_name") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_quick_contact" => array( + "cols" => array( + "c_folder_id" => "INT NOT NULL", + "c_name" => "VARCHAR(255) NOT NULL", + "c_givenname" => "VARCHAR(255)", + "c_cn" => "VARCHAR(255)", + "c_sn" => "VARCHAR(255)", + "c_screenname" => "VARCHAR(255)", + "c_l" => "VARCHAR(255)", + "c_mail" => "TEXT", + "c_o" => "VARCHAR(500)", + "c_ou" => "VARCHAR(255)", + "c_telephonenumber" => "VARCHAR(255)", + "c_categories" => "VARCHAR(255)", + "c_component" => "VARCHAR(10) NOT NULL", + "c_hascertificate" => "INT4 DEFAULT 0" + ), + "keys" => array( + "primary" => array( + "" => array("c_folder_id", "c_name") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_sessions_folder" => array( + "cols" => array( + "c_id" => "VARCHAR(255) NOT NULL", + "c_value" => "VARCHAR(4096) NOT NULL", + "c_creationdate" => "INT(11) NOT NULL", + "c_lastseen" => "INT(11) NOT NULL" + ), + "keys" => array( + "primary" => array( + "" => array("c_id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_store" => array( + "cols" => array( + "c_folder_id" => "INT NOT NULL", + "c_name" => "VARCHAR(255) NOT NULL", + "c_content" => "MEDIUMTEXT NOT NULL", + "c_creationdate" => "INT NOT NULL", + "c_lastmodified" => "INT NOT NULL", + "c_version" => "INT NOT NULL", + "c_deleted" => "INT" + ), + "keys" => array( + "primary" => array( + "" => array("c_folder_id", "c_name") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "pushover" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "key" => "VARCHAR(255) NOT NULL", + "token" => "VARCHAR(255) NOT NULL", + "attributes" => "JSON", + "title" => "TEXT", + "text" => "TEXT", + "senders" => "TEXT", + "senders_regex" => "TEXT", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("username") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_user_profile" => array( + "cols" => array( + "c_uid" => "VARCHAR(255) NOT NULL", + "c_defaults" => "LONGTEXT", + "c_settings" => "LONGTEXT" + ), + "keys" => array( + "primary" => array( + "" => array("c_uid") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "oauth_clients" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "client_id" => "VARCHAR(80) NOT NULL", + "client_secret" => "VARCHAR(80)", + "redirect_uri" => "VARCHAR(2000)", + "grant_types" => "VARCHAR(80)", + "scope" => "VARCHAR(4000)", + "user_id" => "VARCHAR(80)" + ), + "keys" => array( + "primary" => array( + "" => array("client_id") + ), + "unique" => array( + "id" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "oauth_access_tokens" => array( + "cols" => array( + "access_token" => "VARCHAR(40) NOT NULL", + "client_id" => "VARCHAR(80) NOT NULL", + "user_id" => "VARCHAR(80)", + "expires" => "TIMESTAMP NOT NULL", + "scope" => "VARCHAR(4000)" + ), + "keys" => array( + "primary" => array( + "" => array("access_token") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "oauth_authorization_codes" => array( + "cols" => array( + "authorization_code" => "VARCHAR(40) NOT NULL", + "client_id" => "VARCHAR(80) NOT NULL", + "user_id" => "VARCHAR(80)", + "redirect_uri" => "VARCHAR(2000)", + "expires" => "TIMESTAMP NOT NULL", + "scope" => "VARCHAR(4000)", + "id_token" => "VARCHAR(1000)" + ), + "keys" => array( + "primary" => array( + "" => array("authorization_code") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "oauth_refresh_tokens" => array( + "cols" => array( + "refresh_token" => "VARCHAR(40) NOT NULL", + "client_id" => "VARCHAR(80) NOT NULL", + "user_id" => "VARCHAR(80)", + "expires" => "TIMESTAMP NOT NULL", + "scope" => "VARCHAR(4000)" + ), + "keys" => array( + "primary" => array( + "" => array("refresh_token") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ) + ); + + foreach ($tables as $table => $properties) { + // Migrate to quarantine + if ($table == 'quarantine') { + $stmt = $pdo->query("SHOW TABLES LIKE 'quarantaine'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $stmt = $pdo->query("SHOW TABLES LIKE 'quarantine'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $pdo->query("RENAME TABLE `quarantaine` TO `quarantine`"); + } + } + } + // Migrate tls_enforce_* options - if ($table == 'mailbox') { - $stmt = $pdo->query("SHOW TABLES LIKE 'mailbox'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE '%tls_enforce%'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $stmt = $pdo->query("SELECT `username`, `tls_enforce_in`, `tls_enforce_out` FROM `mailbox`"); - $tls_options_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($tls_options_rows)) { - $tls_options[$row['username']] = array('tls_enforce_in' => $row['tls_enforce_in'], 'tls_enforce_out' => $row['tls_enforce_out']); - } - } - } - } - - $stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $stmt = $pdo->prepare("SELECT CONCAT('ALTER TABLE ', `table_schema`, '.', `table_name`, ' DROP FOREIGN KEY ', `constraint_name`, ';') AS `FKEY_DROP` FROM `information_schema`.`table_constraints` - WHERE `constraint_type` = 'FOREIGN KEY' AND `table_name` = :table;"); - $stmt->execute(array(':table' => $table)); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($rows)) { - $pdo->query($row['FKEY_DROP']); - } - foreach($properties['cols'] as $column => $type) { - $stmt = $pdo->query("SHOW COLUMNS FROM `" . $table . "` LIKE '" . $column . "'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results == 0) { - if (strpos($type, 'AUTO_INCREMENT') !== false) { - $type = $type . ' PRIMARY KEY '; - // Adding an AUTO_INCREMENT key, need to drop primary keys first, if exists - $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = 'PRIMARY'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $pdo->query("ALTER TABLE `" . $table . "` DROP PRIMARY KEY"); - } - } - $pdo->query("ALTER TABLE `" . $table . "` ADD `" . $column . "` " . $type); - } - else { - $pdo->query("ALTER TABLE `" . $table . "` MODIFY COLUMN `" . $column . "` " . $type); - } - } - foreach($properties['keys'] as $key_type => $key_content) { - if (strtolower($key_type) == 'primary') { - foreach ($key_content as $key_values) { - $fields = "`" . implode("`, `", $key_values) . "`"; - $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = 'PRIMARY'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - $is_drop = ($num_results != 0) ? "DROP PRIMARY KEY, " : ""; - $pdo->query("ALTER TABLE `" . $table . "` " . $is_drop . "ADD PRIMARY KEY (" . $fields . ")"); - } - } - if (strtolower($key_type) == 'key') { - foreach ($key_content as $key_name => $key_values) { - $fields = "`" . implode("`, `", $key_values) . "`"; - $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - $is_drop = ($num_results != 0) ? "DROP INDEX `" . $key_name . "`, " : ""; - $pdo->query("ALTER TABLE `" . $table . "` " . $is_drop . "ADD KEY `" . $key_name . "` (" . $fields . ")"); - } - } - if (strtolower($key_type) == 'unique') { - foreach ($key_content as $key_name => $key_values) { - $fields = "`" . implode("`, `", $key_values) . "`"; - $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - $is_drop = ($num_results != 0) ? "DROP INDEX `" . $key_name . "`, " : ""; - $pdo->query("ALTER TABLE `" . $table . "` " . $is_drop . "ADD UNIQUE KEY `" . $key_name . "` (" . $fields . ")"); - } - } - if (strtolower($key_type) == 'fkey') { - foreach ($key_content as $key_name => $key_values) { - $fields = "`" . implode("`, `", $key_values) . "`"; - $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $pdo->query("ALTER TABLE `" . $table . "` DROP INDEX `" . $key_name . "`"); - } - @list($table_ref, $field_ref) = explode('.', $key_values['ref']); - $pdo->query("ALTER TABLE `" . $table . "` ADD FOREIGN KEY `" . $key_name . "` (" . $key_values['col'] . ") REFERENCES `" . $table_ref . "` (`" . $field_ref . "`) - ON DELETE " . $key_values['delete'] . " ON UPDATE " . $key_values['update']); - } - } - } - // Drop all vanished columns - $stmt = $pdo->query("SHOW COLUMNS FROM `" . $table . "`"); - $cols_in_table = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($cols_in_table)) { - if (!array_key_exists($row['Field'], $properties['cols'])) { - $pdo->query("ALTER TABLE `" . $table . "` DROP COLUMN `" . $row['Field'] . "`;"); - } - } - - // Step 1: Get all non-primary keys, that currently exist and those that should exist - $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE `Key_name` != 'PRIMARY'"); - $keys_in_table = $stmt->fetchAll(PDO::FETCH_ASSOC); - $keys_to_exist = array(); - if (isset($properties['keys']['unique']) && is_array($properties['keys']['unique'])) { - foreach ($properties['keys']['unique'] as $key_name => $key_values) { - $keys_to_exist[] = $key_name; - } - } - if (isset($properties['keys']['key']) && is_array($properties['keys']['key'])) { - foreach ($properties['keys']['key'] as $key_name => $key_values) { - $keys_to_exist[] = $key_name; - } - } - // Index for foreign key must exist - if (isset($properties['keys']['fkey']) && is_array($properties['keys']['fkey'])) { - foreach ($properties['keys']['fkey'] as $key_name => $key_values) { - $keys_to_exist[] = $key_name; - } - } - // Step 2: Drop all vanished indexes - while ($row = array_shift($keys_in_table)) { - if (!in_array($row['Key_name'], $keys_to_exist)) { - $pdo->query("ALTER TABLE `" . $table . "` DROP INDEX `" . $row['Key_name'] . "`"); - } - } - // Step 3: Drop all vanished primary keys - if (!isset($properties['keys']['primary'])) { - $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = 'PRIMARY'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $pdo->query("ALTER TABLE `" . $table . "` DROP PRIMARY KEY"); - } - } - } - else { - // Create table if it is missing - $sql = "CREATE TABLE IF NOT EXISTS `" . $table . "` ("; - foreach($properties['cols'] as $column => $type) { - $sql .= "`" . $column . "` " . $type . ","; - } - foreach($properties['keys'] as $key_type => $key_content) { - if (strtolower($key_type) == 'primary') { - foreach ($key_content as $key_values) { - $fields = "`" . implode("`, `", $key_values) . "`"; - $sql .= "PRIMARY KEY (" . $fields . ")" . ","; - } - } - elseif (strtolower($key_type) == 'key') { - foreach ($key_content as $key_name => $key_values) { - $fields = "`" . implode("`, `", $key_values) . "`"; - $sql .= "KEY `" . $key_name . "` (" . $fields . ")" . ","; - } - } - elseif (strtolower($key_type) == 'unique') { - foreach ($key_content as $key_name => $key_values) { - $fields = "`" . implode("`, `", $key_values) . "`"; - $sql .= "UNIQUE KEY `" . $key_name . "` (" . $fields . ")" . ","; - } - } - elseif (strtolower($key_type) == 'fkey') { - foreach ($key_content as $key_name => $key_values) { - @list($table_ref, $field_ref) = explode('.', $key_values['ref']); - $sql .= "FOREIGN KEY `" . $key_name . "` (" . $key_values['col'] . ") REFERENCES `" . $table_ref . "` (`" . $field_ref . "`) - ON DELETE " . $key_values['delete'] . " ON UPDATE " . $key_values['update'] . ","; - } - } - } - $sql = rtrim($sql, ","); - $sql .= ") " . $properties['attr']; - $pdo->query($sql); - } - // Reset table attributes - $pdo->query("ALTER TABLE `" . $table . "` " . $properties['attr'] . ";"); - - } - - // Recreate SQL views - foreach ($views as $view => $create) { - $pdo->query("DROP VIEW IF EXISTS `" . $view . "`;"); - $pdo->query($create); - } - - // Mitigate imapsync argument injection issue - $pdo->query("UPDATE `imapsync` SET `custom_params` = '' - WHERE `custom_params` LIKE '%pipemess%' - OR custom_params LIKE '%skipmess%' - OR custom_params LIKE '%delete2foldersonly%' - OR custom_params LIKE '%delete2foldersbutnot%' - OR custom_params LIKE '%regexflag%' - OR custom_params LIKE '%pipemess%' - OR custom_params LIKE '%regextrans2%' - OR custom_params LIKE '%maxlinelengthcmd%';"); - - // Migrate webauthn tfa - $stmt = $pdo->query("ALTER TABLE `tfa` MODIFY COLUMN `authmech` ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')"); - - // Inject admin if not exists - $stmt = $pdo->query("SELECT NULL FROM `admin`"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results == 0) { - $pdo->query("INSERT INTO `admin` (`username`, `password`, `superadmin`, `created`, `modified`, `active`) - VALUES ('admin', '{SSHA256}K8eVJ6YsZbQCfuJvSUbaQRLr0HPLz5rC9IAp0PAFl0tmNDBkMDc0NDAyOTAxN2Rk', 1, NOW(), NOW(), 1)"); - $pdo->query("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) - SELECT `username`, 'ALL', NOW(), 1 FROM `admin` - WHERE superadmin='1' AND `username` NOT IN (SELECT `username` FROM `domain_admins`);"); - $pdo->query("DELETE FROM `admin` WHERE `username` NOT IN (SELECT `username` FROM `domain_admins`);"); - } - // Insert new DB schema version - $pdo->query("REPLACE INTO `versions` (`application`, `version`) VALUES ('db_schema', '" . $db_version . "');"); - - // Fix dangling domain admins - $pdo->query("DELETE FROM `admin` WHERE `superadmin` = 0 AND `username` NOT IN (SELECT `username`FROM `domain_admins`);"); - $pdo->query("DELETE FROM `da_acl` WHERE `username` NOT IN (SELECT `username`FROM `domain_admins`);"); - - // Migrate attributes - // pushover - $pdo->query("UPDATE `pushover` SET `attributes` = '{}' WHERE `attributes` = '' OR `attributes` IS NULL;"); - $pdo->query("UPDATE `pushover` SET `attributes` = JSON_SET(`attributes`, '$.evaluate_x_prio', \"0\") WHERE JSON_VALUE(`attributes`, '$.evaluate_x_prio') IS NULL;"); - $pdo->query("UPDATE `pushover` SET `attributes` = JSON_SET(`attributes`, '$.only_x_prio', \"0\") WHERE JSON_VALUE(`attributes`, '$.only_x_prio') IS NULL;"); - $pdo->query("UPDATE `pushover` SET `attributes` = JSON_SET(`attributes`, '$.sound', \"pushover\") WHERE JSON_VALUE(`attributes`, '$.sound') IS NULL;"); - // mailbox - $pdo->query("UPDATE `mailbox` SET `attributes` = '{}' WHERE `attributes` = '' OR `attributes` IS NULL;"); - $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.passwd_update', \"0\") WHERE JSON_VALUE(`attributes`, '$.passwd_update') IS NULL;"); - $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.relayhost', \"0\") WHERE JSON_VALUE(`attributes`, '$.relayhost') IS NULL;"); - $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.force_pw_update', \"0\") WHERE JSON_VALUE(`attributes`, '$.force_pw_update') IS NULL;"); - $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.sieve_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.sieve_access') IS NULL;"); - $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.sogo_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.sogo_access') IS NULL;"); - $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.imap_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.imap_access') IS NULL;"); - $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.pop3_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.pop3_access') IS NULL;"); - $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.smtp_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.smtp_access') IS NULL;"); - $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.mailbox_format', \"maildir:\") WHERE JSON_VALUE(`attributes`, '$.mailbox_format') IS NULL;"); - $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_notification', \"never\") WHERE JSON_VALUE(`attributes`, '$.quarantine_notification') IS NULL;"); - $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_category', \"reject\") WHERE JSON_VALUE(`attributes`, '$.quarantine_category') IS NULL;"); - foreach($tls_options as $tls_user => $tls_options) { - $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_enforce_in), - `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_enforce_out) - WHERE `username` = :username"); - $stmt->execute(array(':tls_enforce_in' => $tls_options['tls_enforce_in'], ':tls_enforce_out' => $tls_options['tls_enforce_out'], ':username' => $tls_user)); - } - // Set tls_enforce_* if still missing (due to deleted attrs for example) - $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', \"1\") WHERE JSON_VALUE(`attributes`, '$.tls_enforce_out') IS NULL;"); - $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', \"1\") WHERE JSON_VALUE(`attributes`, '$.tls_enforce_in') IS NULL;"); - // Fix ACL - $pdo->query("INSERT INTO `user_acl` (`username`) SELECT `username` FROM `mailbox` WHERE `kind` = '' AND NOT EXISTS (SELECT `username` FROM `user_acl`);"); - $pdo->query("INSERT INTO `da_acl` (`username`) SELECT DISTINCT `username` FROM `domain_admins` WHERE `username` != 'admin' AND NOT EXISTS (SELECT `username` FROM `da_acl`);"); - // Fix domain_admins - $pdo->query("DELETE FROM `domain_admins` WHERE `domain` = 'ALL';"); - + if ($table == 'mailbox') { + $stmt = $pdo->query("SHOW TABLES LIKE 'mailbox'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE '%tls_enforce%'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $stmt = $pdo->query("SELECT `username`, `tls_enforce_in`, `tls_enforce_out` FROM `mailbox`"); + $tls_options_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($tls_options_rows)) { + $tls_options[$row['username']] = array('tls_enforce_in' => $row['tls_enforce_in'], 'tls_enforce_out' => $row['tls_enforce_out']); + } + } + } + } + + $stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $stmt = $pdo->prepare("SELECT CONCAT('ALTER TABLE ', `table_schema`, '.', `table_name`, ' DROP FOREIGN KEY ', `constraint_name`, ';') AS `FKEY_DROP` FROM `information_schema`.`table_constraints` + WHERE `constraint_type` = 'FOREIGN KEY' AND `table_name` = :table;"); + $stmt->execute(array(':table' => $table)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($rows)) { + $pdo->query($row['FKEY_DROP']); + } + foreach($properties['cols'] as $column => $type) { + $stmt = $pdo->query("SHOW COLUMNS FROM `" . $table . "` LIKE '" . $column . "'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + if (strpos($type, 'AUTO_INCREMENT') !== false) { + $type = $type . ' PRIMARY KEY '; + // Adding an AUTO_INCREMENT key, need to drop primary keys first, if exists + $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = 'PRIMARY'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $pdo->query("ALTER TABLE `" . $table . "` DROP PRIMARY KEY"); + } + } + $pdo->query("ALTER TABLE `" . $table . "` ADD `" . $column . "` " . $type); + } + else { + $pdo->query("ALTER TABLE `" . $table . "` MODIFY COLUMN `" . $column . "` " . $type); + } + } + foreach($properties['keys'] as $key_type => $key_content) { + if (strtolower($key_type) == 'primary') { + foreach ($key_content as $key_values) { + $fields = "`" . implode("`, `", $key_values) . "`"; + $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = 'PRIMARY'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + $is_drop = ($num_results != 0) ? "DROP PRIMARY KEY, " : ""; + $pdo->query("ALTER TABLE `" . $table . "` " . $is_drop . "ADD PRIMARY KEY (" . $fields . ")"); + } + } + if (strtolower($key_type) == 'key') { + foreach ($key_content as $key_name => $key_values) { + $fields = "`" . implode("`, `", $key_values) . "`"; + $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + $is_drop = ($num_results != 0) ? "DROP INDEX `" . $key_name . "`, " : ""; + $pdo->query("ALTER TABLE `" . $table . "` " . $is_drop . "ADD KEY `" . $key_name . "` (" . $fields . ")"); + } + } + if (strtolower($key_type) == 'unique') { + foreach ($key_content as $key_name => $key_values) { + $fields = "`" . implode("`, `", $key_values) . "`"; + $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + $is_drop = ($num_results != 0) ? "DROP INDEX `" . $key_name . "`, " : ""; + $pdo->query("ALTER TABLE `" . $table . "` " . $is_drop . "ADD UNIQUE KEY `" . $key_name . "` (" . $fields . ")"); + } + } + if (strtolower($key_type) == 'fkey') { + foreach ($key_content as $key_name => $key_values) { + $fields = "`" . implode("`, `", $key_values) . "`"; + $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $pdo->query("ALTER TABLE `" . $table . "` DROP INDEX `" . $key_name . "`"); + } + @list($table_ref, $field_ref) = explode('.', $key_values['ref']); + $pdo->query("ALTER TABLE `" . $table . "` ADD FOREIGN KEY `" . $key_name . "` (" . $key_values['col'] . ") REFERENCES `" . $table_ref . "` (`" . $field_ref . "`) + ON DELETE " . $key_values['delete'] . " ON UPDATE " . $key_values['update']); + } + } + } + // Drop all vanished columns + $stmt = $pdo->query("SHOW COLUMNS FROM `" . $table . "`"); + $cols_in_table = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($cols_in_table)) { + if (!array_key_exists($row['Field'], $properties['cols'])) { + $pdo->query("ALTER TABLE `" . $table . "` DROP COLUMN `" . $row['Field'] . "`;"); + } + } + + // Step 1: Get all non-primary keys, that currently exist and those that should exist + $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE `Key_name` != 'PRIMARY'"); + $keys_in_table = $stmt->fetchAll(PDO::FETCH_ASSOC); + $keys_to_exist = array(); + if (isset($properties['keys']['unique']) && is_array($properties['keys']['unique'])) { + foreach ($properties['keys']['unique'] as $key_name => $key_values) { + $keys_to_exist[] = $key_name; + } + } + if (isset($properties['keys']['key']) && is_array($properties['keys']['key'])) { + foreach ($properties['keys']['key'] as $key_name => $key_values) { + $keys_to_exist[] = $key_name; + } + } + // Index for foreign key must exist + if (isset($properties['keys']['fkey']) && is_array($properties['keys']['fkey'])) { + foreach ($properties['keys']['fkey'] as $key_name => $key_values) { + $keys_to_exist[] = $key_name; + } + } + // Step 2: Drop all vanished indexes + while ($row = array_shift($keys_in_table)) { + if (!in_array($row['Key_name'], $keys_to_exist)) { + $pdo->query("ALTER TABLE `" . $table . "` DROP INDEX `" . $row['Key_name'] . "`"); + } + } + // Step 3: Drop all vanished primary keys + if (!isset($properties['keys']['primary'])) { + $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = 'PRIMARY'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $pdo->query("ALTER TABLE `" . $table . "` DROP PRIMARY KEY"); + } + } + } + else { + // Create table if it is missing + $sql = "CREATE TABLE IF NOT EXISTS `" . $table . "` ("; + foreach($properties['cols'] as $column => $type) { + $sql .= "`" . $column . "` " . $type . ","; + } + foreach($properties['keys'] as $key_type => $key_content) { + if (strtolower($key_type) == 'primary') { + foreach ($key_content as $key_values) { + $fields = "`" . implode("`, `", $key_values) . "`"; + $sql .= "PRIMARY KEY (" . $fields . ")" . ","; + } + } + elseif (strtolower($key_type) == 'key') { + foreach ($key_content as $key_name => $key_values) { + $fields = "`" . implode("`, `", $key_values) . "`"; + $sql .= "KEY `" . $key_name . "` (" . $fields . ")" . ","; + } + } + elseif (strtolower($key_type) == 'unique') { + foreach ($key_content as $key_name => $key_values) { + $fields = "`" . implode("`, `", $key_values) . "`"; + $sql .= "UNIQUE KEY `" . $key_name . "` (" . $fields . ")" . ","; + } + } + elseif (strtolower($key_type) == 'fkey') { + foreach ($key_content as $key_name => $key_values) { + @list($table_ref, $field_ref) = explode('.', $key_values['ref']); + $sql .= "FOREIGN KEY `" . $key_name . "` (" . $key_values['col'] . ") REFERENCES `" . $table_ref . "` (`" . $field_ref . "`) + ON DELETE " . $key_values['delete'] . " ON UPDATE " . $key_values['update'] . ","; + } + } + } + $sql = rtrim($sql, ","); + $sql .= ") " . $properties['attr']; + $pdo->query($sql); + } + // Reset table attributes + $pdo->query("ALTER TABLE `" . $table . "` " . $properties['attr'] . ";"); + + } + + // Recreate SQL views + foreach ($views as $view => $create) { + $pdo->query("DROP VIEW IF EXISTS `" . $view . "`;"); + $pdo->query($create); + } + + // Mitigate imapsync argument injection issue + $pdo->query("UPDATE `imapsync` SET `custom_params` = '' + WHERE `custom_params` LIKE '%pipemess%' + OR custom_params LIKE '%skipmess%' + OR custom_params LIKE '%delete2foldersonly%' + OR custom_params LIKE '%delete2foldersbutnot%' + OR custom_params LIKE '%regexflag%' + OR custom_params LIKE '%pipemess%' + OR custom_params LIKE '%regextrans2%' + OR custom_params LIKE '%maxlinelengthcmd%';"); + + // Migrate webauthn tfa + $stmt = $pdo->query("ALTER TABLE `tfa` MODIFY COLUMN `authmech` ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')"); + + // Inject admin if not exists + $stmt = $pdo->query("SELECT NULL FROM `admin`"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $pdo->query("INSERT INTO `admin` (`username`, `password`, `superadmin`, `created`, `modified`, `active`) + VALUES ('admin', '{SSHA256}K8eVJ6YsZbQCfuJvSUbaQRLr0HPLz5rC9IAp0PAFl0tmNDBkMDc0NDAyOTAxN2Rk', 1, NOW(), NOW(), 1)"); + $pdo->query("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) + SELECT `username`, 'ALL', NOW(), 1 FROM `admin` + WHERE superadmin='1' AND `username` NOT IN (SELECT `username` FROM `domain_admins`);"); + $pdo->query("DELETE FROM `admin` WHERE `username` NOT IN (SELECT `username` FROM `domain_admins`);"); + } + // Insert new DB schema version + $pdo->query("REPLACE INTO `versions` (`application`, `version`) VALUES ('db_schema', '" . $db_version . "');"); + + // Fix dangling domain admins + $pdo->query("DELETE FROM `admin` WHERE `superadmin` = 0 AND `username` NOT IN (SELECT `username`FROM `domain_admins`);"); + $pdo->query("DELETE FROM `da_acl` WHERE `username` NOT IN (SELECT `username`FROM `domain_admins`);"); + + // Migrate attributes + // pushover + $pdo->query("UPDATE `pushover` SET `attributes` = '{}' WHERE `attributes` = '' OR `attributes` IS NULL;"); + $pdo->query("UPDATE `pushover` SET `attributes` = JSON_SET(`attributes`, '$.evaluate_x_prio', \"0\") WHERE JSON_VALUE(`attributes`, '$.evaluate_x_prio') IS NULL;"); + $pdo->query("UPDATE `pushover` SET `attributes` = JSON_SET(`attributes`, '$.only_x_prio', \"0\") WHERE JSON_VALUE(`attributes`, '$.only_x_prio') IS NULL;"); + $pdo->query("UPDATE `pushover` SET `attributes` = JSON_SET(`attributes`, '$.sound', \"pushover\") WHERE JSON_VALUE(`attributes`, '$.sound') IS NULL;"); + // mailbox + $pdo->query("UPDATE `mailbox` SET `attributes` = '{}' WHERE `attributes` = '' OR `attributes` IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.passwd_update', \"0\") WHERE JSON_VALUE(`attributes`, '$.passwd_update') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.relayhost', \"0\") WHERE JSON_VALUE(`attributes`, '$.relayhost') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.force_pw_update', \"0\") WHERE JSON_VALUE(`attributes`, '$.force_pw_update') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.sieve_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.sieve_access') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.sogo_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.sogo_access') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.imap_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.imap_access') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.pop3_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.pop3_access') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.smtp_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.smtp_access') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.mailbox_format', \"maildir:\") WHERE JSON_VALUE(`attributes`, '$.mailbox_format') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_notification', \"never\") WHERE JSON_VALUE(`attributes`, '$.quarantine_notification') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_category', \"reject\") WHERE JSON_VALUE(`attributes`, '$.quarantine_category') IS NULL;"); + foreach($tls_options as $tls_user => $tls_options) { + $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_enforce_in), + `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_enforce_out) + WHERE `username` = :username"); + $stmt->execute(array(':tls_enforce_in' => $tls_options['tls_enforce_in'], ':tls_enforce_out' => $tls_options['tls_enforce_out'], ':username' => $tls_user)); + } + // Set tls_enforce_* if still missing (due to deleted attrs for example) + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', \"1\") WHERE JSON_VALUE(`attributes`, '$.tls_enforce_out') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', \"1\") WHERE JSON_VALUE(`attributes`, '$.tls_enforce_in') IS NULL;"); + // Fix ACL + $pdo->query("INSERT INTO `user_acl` (`username`) SELECT `username` FROM `mailbox` WHERE `kind` = '' AND NOT EXISTS (SELECT `username` FROM `user_acl`);"); + $pdo->query("INSERT INTO `da_acl` (`username`) SELECT DISTINCT `username` FROM `domain_admins` WHERE `username` != 'admin' AND NOT EXISTS (SELECT `username` FROM `da_acl`);"); + // Fix domain_admins + $pdo->query("DELETE FROM `domain_admins` WHERE `domain` = 'ALL';"); + // add default templates $default_domain_template = array( "template" => "Default", @@ -1398,68 +1411,68 @@ function init_db_schema() { )); } - if (php_sapi_name() == "cli") { - echo "DB initialization completed" . PHP_EOL; - } else { - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__), - 'msg' => 'db_init_complete' - ); - } - } - catch (PDOException $e) { - if (php_sapi_name() == "cli") { - echo "DB initialization failed: " . print_r($e, true) . PHP_EOL; - } else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => array('mysql_error', $e) - ); - } - } -} -if (php_sapi_name() == "cli") { - include '/web/inc/vars.inc.php'; - include '/web/inc/functions.docker.inc.php'; - // $now = new DateTime(); - // $mins = $now->getOffset() / 60; - // $sgn = ($mins < 0 ? -1 : 1); - // $mins = abs($mins); - // $hrs = floor($mins / 60); - // $mins -= $hrs * 60; - // $offset = sprintf('%+d:%02d', $hrs*$sgn, $mins); - $dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name; - $opt = [ - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_EMULATE_PREPARES => false, - //PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;", - ]; - $pdo = new PDO($dsn, $database_user, $database_pass, $opt); - $stmt = $pdo->query("SELECT COUNT('OK') AS OK_C FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view' OR TABLE_NAME = '_sogo_static_view';"); - $res = $stmt->fetch(PDO::FETCH_ASSOC); - if (intval($res['OK_C']) === 2) { - // Be more precise when replacing into _sogo_static_view, col orders may change - try { - $stmt = $pdo->query("REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`) - SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings` from sogo_view"); - $stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');"); - echo "Fixed _sogo_static_view" . PHP_EOL; - } - catch ( Exception $e ) { - // Dunno - } - } - try { - $m = new Memcached(); - $m->addServer('memcached', 11211); - $m->flush(); - echo "Cleaned up memcached". PHP_EOL; - } - catch ( Exception $e ) { - // Dunno - } - init_db_schema(); -} + if (php_sapi_name() == "cli") { + echo "DB initialization completed" . PHP_EOL; + } else { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__), + 'msg' => 'db_init_complete' + ); + } + } + catch (PDOException $e) { + if (php_sapi_name() == "cli") { + echo "DB initialization failed: " . print_r($e, true) . PHP_EOL; + } else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => array('mysql_error', $e) + ); + } + } +} +if (php_sapi_name() == "cli") { + include '/web/inc/vars.inc.php'; + include '/web/inc/functions.docker.inc.php'; + // $now = new DateTime(); + // $mins = $now->getOffset() / 60; + // $sgn = ($mins < 0 ? -1 : 1); + // $mins = abs($mins); + // $hrs = floor($mins / 60); + // $mins -= $hrs * 60; + // $offset = sprintf('%+d:%02d', $hrs*$sgn, $mins); + $dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name; + $opt = [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, + //PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;", + ]; + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); + $stmt = $pdo->query("SELECT COUNT('OK') AS OK_C FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view' OR TABLE_NAME = '_sogo_static_view';"); + $res = $stmt->fetch(PDO::FETCH_ASSOC); + if (intval($res['OK_C']) === 2) { + // Be more precise when replacing into _sogo_static_view, col orders may change + try { + $stmt = $pdo->query("REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`) + SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings` from sogo_view"); + $stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');"); + echo "Fixed _sogo_static_view" . PHP_EOL; + } + catch ( Exception $e ) { + // Dunno + } + } + try { + $m = new Memcached(); + $m->addServer('memcached', 11211); + $m->flush(); + echo "Cleaned up memcached". PHP_EOL; + } + catch ( Exception $e ) { + // Dunno + } + init_db_schema(); +} diff --git a/data/web/inc/sessions.inc.php b/data/web/inc/sessions.inc.php index 5c7ec710..1a33e760 100644 --- a/data/web/inc/sessions.inc.php +++ b/data/web/inc/sessions.inc.php @@ -1,140 +1,140 @@ - $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(); -} + $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(); +} diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php index aec043e9..fde1507f 100644 --- a/data/web/inc/triggers.inc.php +++ b/data/web/inc/triggers.inc.php @@ -1,4 +1,15 @@ >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>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;e127&&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;e191&&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($){ - // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery - var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}; - function jq(myid) {return "#" + myid.replace( /(:|\.|\[|\]|,|=|@)/g, "\\$1" );} - function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} - function validateRegex(e){var t=e.split("/"),n=e,r="";t.length>1&&(n=t[1],r=t[2]);try{return new RegExp(n,r),!0}catch(e){return!1}} - 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<'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/domain-admin/all", - dataSrc: function(data){ - return process_table_data(data, 'domainadminstable'); - } - }, - 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: lang.username, - data: 'username', - defaultContent: '' - }, - { - title: lang.admin_domains, - data: 'selected_domains', - defaultContent: '', - }, - { - title: "TFA", - data: 'tfa_active', - defaultContent: '', - render: function (data, type) { - if(data == 1) return ''; - else return '' - } - }, - { - title: lang.active, - data: 'active', - defaultContent: '', - render: function (data, type) { - if(data == 1) return ''; - else return '' - } - }, - { - title: lang.action, - data: 'action', - className: 'text-md-end dt-sm-head-hidden dt-body-right', - defaultContent: '' - }, - ], - initComplete: function(settings, json){ - } - }); - } - function draw_oauth2_clients() { - // just recalc width if instance already exists - if ($.fn.DataTable.isDataTable('#oauth2clientstable') ) { - $('#oauth2clientstable').DataTable().columns.adjust().responsive.recalc(); - return; - } - - $('#oauth2clientstable').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/oauth2-client/all", - dataSrc: function(data){ - return process_table_data(data, 'oauth2clientstable'); - } - }, - 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.oauth2_client_id, - data: 'client_id', - defaultContent: '' - }, - { - title: lang.oauth2_client_secret, - data: 'client_secret', - defaultContent: '' - }, - { - title: lang.oauth2_redirect_uri, - data: 'redirect_uri', - defaultContent: '' - }, - { - title: lang.action, - data: 'action', - className: 'text-md-end dt-sm-head-hidden dt-body-right', - defaultContent: '' - }, - ] - }); - } - function draw_admins() { - // just recalc width if instance already exists - if ($.fn.DataTable.isDataTable('#adminstable') ) { - $('#adminstable').DataTable().columns.adjust().responsive.recalc(); - return; - } - - $('#adminstable').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/admin/all", - dataSrc: function(data){ - return process_table_data(data, 'adminstable'); - } - }, - 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: lang.username, - data: 'username', - defaultContent: '' - }, - { - title: "TFA", - data: 'tfa_active', - defaultContent: '', - render: function (data, type) { - if(data == 1) return ''; - else return '' - } - }, - { - title: lang.active, - data: 'active', - defaultContent: '', - render: function (data, type) { - if(data == 1) return ''; - else return '' - } - }, - { - title: lang.action, - data: 'action', - defaultContent: '', - className: 'text-md-end dt-sm-head-hidden dt-body-right' - }, - ] - }); - } - function draw_fwd_hosts() { - // just recalc width if instance already exists - if ($.fn.DataTable.isDataTable('#forwardinghoststable') ) { - $('#forwardinghoststable').DataTable().columns.adjust().responsive.recalc(); - return; - } - - $('#forwardinghoststable').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/fwdhost/all", - dataSrc: function(data){ - return process_table_data(data, 'forwardinghoststable'); - } - }, - 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: lang.host, - data: 'host', - defaultContent: '' - }, - { - title: lang.source, - data: 'source', - defaultContent: '' - }, - { - title: lang.spamfilter, - data: 'keep_spam', - defaultContent: '', - render: function(data, type){ - return 'yes'==data?'':'no'==data&&''; - } - }, - { - title: lang.action, - data: 'action', - className: 'text-md-end dt-sm-head-hidden dt-body-right', - defaultContent: '' - }, - ] - }); - } - function draw_relayhosts() { - // just recalc width if instance already exists - if ($.fn.DataTable.isDataTable('#relayhoststable') ) { - $('#relayhoststable').DataTable().columns.adjust().responsive.recalc(); - return; - } - - $('#relayhoststable').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/relayhost/all", - dataSrc: function(data){ - return process_table_data(data, 'relayhoststable'); - } - }, - 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.host, - data: 'hostname', - defaultContent: '' - }, - { - title: lang.username, - data: 'username', - defaultContent: '' - }, - { - title: lang.in_use_by, - data: 'in_use_by', - defaultContent: '' - }, - { - title: lang.active, - data: 'active', - defaultContent: '', - render: function (data, type) { - if(data == 1) return ''; - else return '' - } - }, - { - title: lang.action, - data: 'action', - className: 'text-md-end dt-sm-head-hidden dt-body-right', - defaultContent: '' - }, - ] - }); - } - function draw_transport_maps() { - // just recalc width if instance already exists - if ($.fn.DataTable.isDataTable('#transportstable') ) { - $('#transportstable').DataTable().columns.adjust().responsive.recalc(); - return; - } - - $('#transportstable').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/transport/all", - dataSrc: function(data){ - return process_table_data(data, 'transportstable'); - } - }, - 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.destination, - data: 'destination', - defaultContent: '' - }, - { - title: lang.nexthop, - data: 'nexthop', - defaultContent: '' - }, - { - title: lang.username, - data: 'username', - defaultContent: '' - }, - { - title: lang.active, - data: 'active', - defaultContent: '', - render: function (data, type) { - if(data == 1) return ''; - else return '' - } - }, - { - title: lang.action, - data: 'action', - className: 'text-md-end dt-sm-head-hidden dt-body-right', - defaultContent: '' - }, - ] - }); - } - - function process_table_data(data, table) { - if (table == 'relayhoststable') { - $.each(data, function (i, item) { - item.action = ''; - if (item.used_by_mailboxes == '') { item.in_use_by = item.used_by_domains; } - else if (item.used_by_domains == '') { item.in_use_by = item.used_by_mailboxes; } - else { item.in_use_by = item.used_by_mailboxes + '
' + item.used_by_domains; } - item.chkbox = ''; - }); - } else if (table == 'transportstable') { - $.each(data, function (i, item) { - if (item.is_mx_based) { - item.destination = ' ' + item.destination + ''; - } - if (item.username) { - item.username = ' ' + item.username; - } - item.action = ''; - item.chkbox = ''; - }); - } else if (table == 'queuetable') { - $.each(data, function (i, item) { - item.chkbox = ''; - rcpts = $.map(item.recipients, function(i) { - return escapeHtml(i); - }); - item.recipients = rcpts.join('
'); - item.action = ''; - }); - } else if (table == 'forwardinghoststable') { - $.each(data, function (i, item) { - item.action = '
' + - ' ' + lang.remove + '' + - '
'; - item.chkbox = ''; - }); - } else if (table == 'oauth2clientstable') { - $.each(data, function (i, item) { - item.action = ''; - item.scope = "profile"; - item.grant_types = 'refresh_token password authorization_code'; - item.chkbox = ''; - }); - } else if (table == 'domainadminstable') { - $.each(data, function (i, item) { - item.selected_domains = escapeHtml(item.selected_domains); - item.selected_domains = item.selected_domains.toString().replace(/,/g, "
"); - item.chkbox = ''; - item.action = ''; - }); - } else if (table == 'adminstable') { - $.each(data, function (i, item) { - if (admin_username.toLowerCase() == item.username.toLowerCase()) { - item.usr = ' ' + item.username; - } else { - item.usr = item.username; - } - item.chkbox = ''; - item.action = ''; - }); - } - return data - }; - - // 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); - } - }); - }).observe(element_object); - }); - } - // Draw Table if tab is active - onVisible("[id^=adminstable]", () => draw_admins()); - onVisible("[id^=domainadminstable]", () => draw_domain_admins()); - onVisible("[id^=oauth2clientstable]", () => draw_oauth2_clients()); - onVisible("[id^=forwardinghoststable]", () => draw_fwd_hosts()); - onVisible("[id^=relayhoststable]", () => draw_relayhosts()); - onVisible("[id^=transportstable]", () => draw_transport_maps()); - - - $('body').on('click', 'span.footable-toggle', function () { - event.stopPropagation(); - }) - - // API IP check toggle - $("#skip_ip_check_ro").click(function( event ) { - $("#skip_ip_check_ro").not(this).prop('checked', false); - if ($("#skip_ip_check_ro:checked").length > 0) { - $('#allow_from_ro').prop('disabled', true); - } - else { - $("#allow_from_ro").removeAttr('disabled'); - } - }); - $("#skip_ip_check_rw").click(function( event ) { - $("#skip_ip_check_rw").not(this).prop('checked', false); - if ($("#skip_ip_check_rw:checked").length > 0) { - $('#allow_from_rw').prop('disabled', true); - } - else { - $("#allow_from_rw").removeAttr('disabled'); - } - }); - // Relayhost - $('#testRelayhostModal').on('show.bs.modal', function (e) { - $('#test_relayhost_result').text("-"); - button = $(e.relatedTarget) - if (button != null) { - $('#relayhost_id').val(button.data('relayhost-id')); - } - }) - $('#test_relayhost').on('click', function (e) { - e.preventDefault(); - prev = $('#test_relayhost').text(); - $(this).prop("disabled",true); - $(this).html(' '); - $.ajax({ - type: 'GET', - url: 'inc/ajax/relay_check.php', - dataType: 'text', - data: $('#test_relayhost_form').serialize(), - complete: function (data) { - $('#test_relayhost_result').html(data.responseText); - $('#test_relayhost').prop("disabled",false); - $('#test_relayhost').text(prev); - } - }); - }) - // Transport - $('#testTransportModal').on('show.bs.modal', function (e) { - $('#test_transport_result').text("-"); - button = $(e.relatedTarget) - if (button != null) { - $('#transport_id').val(button.data('transport-id')); - $('#transport_type').val(button.data('transport-type')); - } - }) - $('#test_transport').on('click', function (e) { - e.preventDefault(); - prev = $('#test_transport').text(); - $(this).prop("disabled",true); - $(this).html('
Loading...
'); - $.ajax({ - type: 'GET', - url: 'inc/ajax/transport_check.php', - dataType: 'text', - data: $('#test_transport_form').serialize(), - complete: function (data) { - $('#test_transport_result').html(data.responseText); - $('#test_transport').prop("disabled",false); - $('#test_transport').text(prev); - } - }); - }) - // DKIM private key modal - $('#showDKIMprivKey').on('show.bs.modal', function (e) { - $('#priv_key_pre').text("-"); - p_related = $(e.relatedTarget) - if (p_related != null) { - var decoded_key = Base64.decode((p_related.data('priv-key'))); - $('#priv_key_pre').text(decoded_key); - } - }) - // FIDO2 friendly name modal - $('#fido2ChangeFn').on('show.bs.modal', function (e) { - rename_link = $(e.relatedTarget) - if (rename_link != null) { - $('#fido2_cid').val(rename_link.data('cid')); - $('#fido2_subject_desc').text(Base64.decode(rename_link.data('subject'))); - } - }) - // App links - function add_table_row(table_id, type) { - var row = $(''); - if (type == "app_link") { - cols = ''; - cols += ''; - cols += '' + lang.remove_row + ''; - } else if (type == "f2b_regex") { - cols = ''; - cols += ''; - cols += '' + lang.remove_row + ''; - } - row.append(cols); - table_id.append(row); - } - $('#app_link_table').on('click', 'tr a', function (e) { - e.preventDefault(); - $(this).parents('tr').remove(); - }); - $('#f2b_regex_table').on('click', 'tr a', function (e) { - e.preventDefault(); - $(this).parents('tr').remove(); - }); - $('#add_app_link_row').click(function() { - add_table_row($('#app_link_table'), "app_link"); - }); - $('#add_f2b_regex_row').click(function() { - add_table_row($('#f2b_regex_table'), "f2b_regex"); - }); -}); +// 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>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>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;e127&&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;e191&&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($){ + // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery + var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}; + function jq(myid) {return "#" + myid.replace( /(:|\.|\[|\]|,|=|@)/g, "\\$1" );} + function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} + function validateRegex(e){var t=e.split("/"),n=e,r="";t.length>1&&(n=t[1],r=t[2]);try{return new RegExp(n,r),!0}catch(e){return!1}} + 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<'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/domain-admin/all", + dataSrc: function(data){ + return process_table_data(data, 'domainadminstable'); + } + }, + 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: lang.username, + data: 'username', + defaultContent: '' + }, + { + title: lang.admin_domains, + data: 'selected_domains', + defaultContent: '', + }, + { + title: "TFA", + data: 'tfa_active', + defaultContent: '', + render: function (data, type) { + if(data == 1) return ''; + else return ''; + } + }, + { + title: lang.active, + data: 'active', + defaultContent: '', + render: function (data, type) { + if(data == 1) return ''; + else return ''; + } + }, + { + title: lang.action, + data: 'action', + className: 'dt-sm-head-hidden dt-text-right', + defaultContent: '' + }, + ], + initComplete: function(settings, json){ + } + }); + } + function draw_oauth2_clients() { + // just recalc width if instance already exists + if ($.fn.DataTable.isDataTable('#oauth2clientstable') ) { + $('#oauth2clientstable').DataTable().columns.adjust().responsive.recalc(); + return; + } + + $('#oauth2clientstable').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/oauth2-client/all", + dataSrc: function(data){ + return process_table_data(data, 'oauth2clientstable'); + } + }, + 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.oauth2_client_id, + data: 'client_id', + defaultContent: '' + }, + { + title: lang.oauth2_client_secret, + data: 'client_secret', + defaultContent: '' + }, + { + title: lang.oauth2_redirect_uri, + data: 'redirect_uri', + defaultContent: '' + }, + { + title: lang.action, + data: 'action', + className: 'dt-sm-head-hidden dt-text-right', + defaultContent: '' + }, + ] + }); + } + function draw_admins() { + // just recalc width if instance already exists + if ($.fn.DataTable.isDataTable('#adminstable') ) { + $('#adminstable').DataTable().columns.adjust().responsive.recalc(); + return; + } + + $('#adminstable').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/admin/all", + dataSrc: function(data){ + return process_table_data(data, 'adminstable'); + } + }, + 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: lang.username, + data: 'username', + defaultContent: '' + }, + { + title: "TFA", + data: 'tfa_active', + defaultContent: '', + render: function (data, type) { + if(data == 1) return ''; + else return ''; + } + }, + { + title: lang.active, + data: 'active', + defaultContent: '', + render: function (data, type) { + if(data == 1) return ''; + else return ''; + } + }, + { + title: lang.action, + data: 'action', + defaultContent: '', + className: 'dt-sm-head-hidden dt-text-right' + }, + ] + }); + } + function draw_fwd_hosts() { + // just recalc width if instance already exists + if ($.fn.DataTable.isDataTable('#forwardinghoststable') ) { + $('#forwardinghoststable').DataTable().columns.adjust().responsive.recalc(); + return; + } + + $('#forwardinghoststable').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/fwdhost/all", + dataSrc: function(data){ + return process_table_data(data, 'forwardinghoststable'); + } + }, + 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: lang.host, + data: 'host', + defaultContent: '' + }, + { + title: lang.source, + data: 'source', + defaultContent: '' + }, + { + title: lang.spamfilter, + data: 'keep_spam', + defaultContent: '', + render: function(data, type){ + return 'yes'==data?'':'no'==data&&''; + } + }, + { + title: lang.action, + data: 'action', + className: 'dt-sm-head-hidden dt-text-right', + defaultContent: '' + }, + ] + }); + } + function draw_relayhosts() { + // just recalc width if instance already exists + if ($.fn.DataTable.isDataTable('#relayhoststable') ) { + $('#relayhoststable').DataTable().columns.adjust().responsive.recalc(); + return; + } + + $('#relayhoststable').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/relayhost/all", + dataSrc: function(data){ + return process_table_data(data, 'relayhoststable'); + } + }, + 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.host, + data: 'hostname', + defaultContent: '' + }, + { + title: lang.username, + data: 'username', + defaultContent: '' + }, + { + title: lang.in_use_by, + data: 'in_use_by', + defaultContent: '' + }, + { + title: lang.active, + data: 'active', + defaultContent: '', + render: function (data, type) { + if(data == 1) return ''; + else return ''; + } + }, + { + title: lang.action, + data: 'action', + className: 'dt-sm-head-hidden dt-text-right', + defaultContent: '' + }, + ] + }); + } + function draw_transport_maps() { + // just recalc width if instance already exists + if ($.fn.DataTable.isDataTable('#transportstable') ) { + $('#transportstable').DataTable().columns.adjust().responsive.recalc(); + return; + } + + $('#transportstable').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/transport/all", + dataSrc: function(data){ + return process_table_data(data, 'transportstable'); + } + }, + 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.destination, + data: 'destination', + defaultContent: '' + }, + { + title: lang.nexthop, + data: 'nexthop', + defaultContent: '' + }, + { + title: lang.username, + data: 'username', + defaultContent: '' + }, + { + title: lang.active, + data: 'active', + defaultContent: '', + render: function (data, type) { + if(data == 1) return ''; + else return ''; + } + }, + { + title: lang.action, + data: 'action', + className: 'dt-sm-head-hidden dt-text-right', + defaultContent: '' + }, + ] + }); + } + + function process_table_data(data, table) { + if (table == 'relayhoststable') { + $.each(data, function (i, item) { + item.action = ''; + if (item.used_by_mailboxes == '') { item.in_use_by = item.used_by_domains; } + else if (item.used_by_domains == '') { item.in_use_by = item.used_by_mailboxes; } + else { item.in_use_by = item.used_by_mailboxes + '
' + item.used_by_domains; } + item.chkbox = ''; + }); + } else if (table == 'transportstable') { + $.each(data, function (i, item) { + if (item.is_mx_based) { + item.destination = ' ' + item.destination + ''; + } + if (item.username) { + item.username = ' ' + item.username; + } + item.action = ''; + item.chkbox = ''; + }); + } else if (table == 'queuetable') { + $.each(data, function (i, item) { + item.chkbox = ''; + rcpts = $.map(item.recipients, function(i) { + return escapeHtml(i); + }); + item.recipients = rcpts.join('
'); + item.action = ''; + }); + } else if (table == 'forwardinghoststable') { + $.each(data, function (i, item) { + item.action = '
' + + ' ' + lang.remove + '' + + '
'; + item.chkbox = ''; + }); + } else if (table == 'oauth2clientstable') { + $.each(data, function (i, item) { + item.action = ''; + item.scope = "profile"; + item.grant_types = 'refresh_token password authorization_code'; + item.chkbox = ''; + }); + } else if (table == 'domainadminstable') { + $.each(data, function (i, item) { + item.selected_domains = escapeHtml(item.selected_domains); + item.selected_domains = item.selected_domains.toString().replace(/,/g, "
"); + item.chkbox = ''; + item.action = ''; + }); + } else if (table == 'adminstable') { + $.each(data, function (i, item) { + if (admin_username.toLowerCase() == item.username.toLowerCase()) { + item.usr = ' ' + item.username; + } else { + item.usr = item.username; + } + item.chkbox = ''; + item.action = ''; + }); + } + return data + }; + + // 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); + } + }); + }).observe(element_object); + }); + } + // Draw Table if tab is active + onVisible("[id^=adminstable]", () => draw_admins()); + onVisible("[id^=domainadminstable]", () => draw_domain_admins()); + onVisible("[id^=oauth2clientstable]", () => draw_oauth2_clients()); + onVisible("[id^=forwardinghoststable]", () => draw_fwd_hosts()); + onVisible("[id^=relayhoststable]", () => draw_relayhosts()); + onVisible("[id^=transportstable]", () => draw_transport_maps()); + + + $('body').on('click', 'span.footable-toggle', function () { + event.stopPropagation(); + }) + + // API IP check toggle + $("#skip_ip_check_ro").click(function( event ) { + $("#skip_ip_check_ro").not(this).prop('checked', false); + if ($("#skip_ip_check_ro:checked").length > 0) { + $('#allow_from_ro').prop('disabled', true); + } + else { + $("#allow_from_ro").removeAttr('disabled'); + } + }); + $("#skip_ip_check_rw").click(function( event ) { + $("#skip_ip_check_rw").not(this).prop('checked', false); + if ($("#skip_ip_check_rw:checked").length > 0) { + $('#allow_from_rw').prop('disabled', true); + } + else { + $("#allow_from_rw").removeAttr('disabled'); + } + }); + // Relayhost + $('#testRelayhostModal').on('show.bs.modal', function (e) { + $('#test_relayhost_result').text("-"); + button = $(e.relatedTarget) + if (button != null) { + $('#relayhost_id').val(button.data('relayhost-id')); + } + }) + $('#test_relayhost').on('click', function (e) { + e.preventDefault(); + prev = $('#test_relayhost').text(); + $(this).prop("disabled",true); + $(this).html(' '); + $.ajax({ + type: 'GET', + url: 'inc/ajax/relay_check.php', + dataType: 'text', + data: $('#test_relayhost_form').serialize(), + complete: function (data) { + $('#test_relayhost_result').html(data.responseText); + $('#test_relayhost').prop("disabled",false); + $('#test_relayhost').text(prev); + } + }); + }) + // Transport + $('#testTransportModal').on('show.bs.modal', function (e) { + $('#test_transport_result').text("-"); + button = $(e.relatedTarget) + if (button != null) { + $('#transport_id').val(button.data('transport-id')); + $('#transport_type').val(button.data('transport-type')); + } + }) + $('#test_transport').on('click', function (e) { + e.preventDefault(); + prev = $('#test_transport').text(); + $(this).prop("disabled",true); + $(this).html('
Loading...
'); + $.ajax({ + type: 'GET', + url: 'inc/ajax/transport_check.php', + dataType: 'text', + data: $('#test_transport_form').serialize(), + complete: function (data) { + $('#test_transport_result').html(data.responseText); + $('#test_transport').prop("disabled",false); + $('#test_transport').text(prev); + } + }); + }) + // DKIM private key modal + $('#showDKIMprivKey').on('show.bs.modal', function (e) { + $('#priv_key_pre').text("-"); + p_related = $(e.relatedTarget) + if (p_related != null) { + var decoded_key = Base64.decode((p_related.data('priv-key'))); + $('#priv_key_pre').text(decoded_key); + } + }) + // FIDO2 friendly name modal + $('#fido2ChangeFn').on('show.bs.modal', function (e) { + rename_link = $(e.relatedTarget) + if (rename_link != null) { + $('#fido2_cid').val(rename_link.data('cid')); + $('#fido2_subject_desc').text(Base64.decode(rename_link.data('subject'))); + } + }) + // App links + function add_table_row(table_id, type) { + var row = $(''); + if (type == "app_link") { + cols = ''; + cols += ''; + cols += '' + lang.remove_row + ''; + } else if (type == "f2b_regex") { + cols = ''; + cols += ''; + cols += '' + lang.remove_row + ''; + } + row.append(cols); + table_id.append(row); + } + $('#app_link_table').on('click', 'tr a', function (e) { + e.preventDefault(); + $(this).parents('tr').remove(); + }); + $('#f2b_regex_table').on('click', 'tr a', function (e) { + e.preventDefault(); + $(this).parents('tr').remove(); + }); + $('#add_app_link_row').click(function() { + add_table_row($('#app_link_table'), "app_link"); + }); + $('#add_f2b_regex_row').click(function() { + add_table_row($('#f2b_regex_table'), "f2b_regex"); + }); +}); diff --git a/data/web/js/site/debug.js b/data/web/js/site/debug.js index a7b06e5a..e0b9a5ab 100644 --- a/data/web/js/site/debug.js +++ b/data/web/js/site/debug.js @@ -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 = '(' + sym.score + ')' + sym.score_formatted = '(' + sym.score + ')'; } else if (sym.score === 0) { - sym.score_formatted = '(' + sym.score + ')' + sym.score_formatted = '(' + sym.score + ')'; } else { - sym.score_formatted = '(' + sym.score + ')' + sym.score_formatted = '(' + sym.score + ')'; } var str = '' + key + ' ' + sym.score_formatted; if (sym.options) { str += ' [' + escapeHtml(sym.options.join(", ")) + "]"; } - return str + return str; }).join('
\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 '' + last_uri_path + '
'; - }; + return '' + last_uri_path + '
'; + }; - // if it's not a github link, return complete link - return '' + matched + ''; + // if it's not a github link, return complete link + return '' + matched + ''; }); return replacedText; diff --git a/data/web/js/site/edit.js b/data/web/js/site/edit.js index 4c57b35e..4680bdfa 100644 --- a/data/web/js/site/edit.js +++ b/data/web/js/site/edit.js @@ -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 = ''; - } - else { - item.chkbox = ''; - } - }); - - 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 = ''; - } - else { - item.chkbox = ''; - } - }); - - 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 = ''; + } + else { + item.chkbox = ''; + } + }); + + 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 = ''; + } + else { + item.chkbox = ''; + } + }); + + 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()); +}); diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index b93b1819..2ef84688 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -77,7 +77,7 @@ $(document).ready(function() { $('.dns-modal-body').html(xhr.responseText); } }); - }); + }); // @Open Domain add modal $('#addDomainModal').on('show.bs.modal', function(e) { $.ajax({ @@ -85,24 +85,24 @@ $(document).ready(function() { data: {}, dataType: 'json', success: async function(data){ - $('#domain_templates').find('option').remove(); + $('#domain_templates').find('option').remove(); $('#domain_templates').selectpicker('destroy'); $('#domain_templates').selectpicker(); for (var i = 0; i < data.length; i++){ if (data[i].template === "Default"){ - $('#domain_templates').prepend($('