mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-10-23 17:53:24 +00:00
commit
bdba65686b
25 files changed
+317
-77
No files matched your search
@@ -8,10 +8,9 @@ while mysqladmin ping --host mysql --silent; do
|
|||||||
mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view"
|
mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view"
|
||||||
|
|
||||||
mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
|
mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
|
||||||
CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, senderacl, home, kind, multiple_bookings) AS
|
CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, home, kind, multiple_bookings) AS
|
||||||
SELECT mailbox.username, mailbox.domain, mailbox.username, mailbox.password, mailbox.name, mailbox.username, IFNULL(ga.aliases, ''), IFNULL(gda.ad_alias, ''), IFNULL(gs.send_as, ''), CONCAT('/var/vmail/', maildir), mailbox.kind, mailbox.multiple_bookings FROM mailbox
|
SELECT mailbox.username, mailbox.domain, mailbox.username, mailbox.password, mailbox.name, mailbox.username, IFNULL(ga.aliases, ''), IFNULL(gda.ad_alias, ''), CONCAT('/var/vmail/', maildir), mailbox.kind, mailbox.multiple_bookings FROM mailbox
|
||||||
LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username = mailbox.username
|
LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username = mailbox.username
|
||||||
LEFT OUTER JOIN grouped_sender_acl gs ON gs.username = mailbox.username
|
|
||||||
LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username
|
LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username
|
||||||
WHERE mailbox.active = '1';
|
WHERE mailbox.active = '1';
|
||||||
EOF
|
EOF
|
||||||
@@ -50,11 +49,10 @@ EOF
|
|||||||
# Generate multi-domain setup
|
# Generate multi-domain setup
|
||||||
while read line
|
while read line
|
||||||
do
|
do
|
||||||
DOMAIN_SANE=$(echo ${line} | tr '-' 'b' | tr '.' 'p' | tr -cd '[[:alnum:]]')
|
|
||||||
echo " <key>${line}</key>
|
echo " <key>${line}</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>SOGoMailDomain</key>
|
<key>SOGoMailDomain</key>
|
||||||
<string>${DOMAIN_SANE}</string>
|
<string>${line}</string>
|
||||||
<key>SOGoUserSources</key>
|
<key>SOGoUserSources</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
@@ -62,7 +60,6 @@ while read line
|
|||||||
<array>
|
<array>
|
||||||
<string>aliases</string>
|
<string>aliases</string>
|
||||||
<string>ad_aliases</string>
|
<string>ad_aliases</string>
|
||||||
<string>senderacl</string>
|
|
||||||
</array>
|
</array>
|
||||||
<key>KindFieldName</key>
|
<key>KindFieldName</key>
|
||||||
<string>kind</string>
|
<string>kind</string>
|
||||||
@@ -98,4 +95,4 @@ chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist
|
|||||||
|
|
||||||
sleep 99999
|
sleep 99999
|
||||||
|
|
||||||
done;
|
done
|
||||||
@@ -39,7 +39,7 @@ server {
|
|||||||
|
|
||||||
rewrite ^(/save.+)$ /rspamd$1 last;
|
rewrite ^(/save.+)$ /rspamd$1 last;
|
||||||
location /rspamd/ {
|
location /rspamd/ {
|
||||||
proxy_pass http://rspamd:11334/;
|
proxy_pass http://172.22.1.253:11334/;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
|
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
|
||||||
@@ -61,7 +61,7 @@ server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location ^~ /Microsoft-Server-ActiveSync {
|
location ^~ /Microsoft-Server-ActiveSync {
|
||||||
proxy_pass http://sogo:20000/SOGo/Microsoft-Server-ActiveSync;
|
proxy_pass http://172.22.1.252:20000/SOGo/Microsoft-Server-ActiveSync;
|
||||||
proxy_connect_timeout 1000;
|
proxy_connect_timeout 1000;
|
||||||
proxy_next_upstream timeout error;
|
proxy_next_upstream timeout error;
|
||||||
proxy_send_timeout 1000;
|
proxy_send_timeout 1000;
|
||||||
@@ -83,7 +83,7 @@ server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location ^~ /SOGo {
|
location ^~ /SOGo {
|
||||||
proxy_pass http://sogo:20000;
|
proxy_pass http://172.22.1.252:20000;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
@@ -105,7 +105,7 @@ server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location /SOGo.woa/WebServerResources/ {
|
location /SOGo.woa/WebServerResources/ {
|
||||||
proxy_pass http://sogo:9192/WebServerResources/;
|
proxy_pass http://172.22.1.252:9192/WebServerResources/;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_cache sogo;
|
proxy_cache sogo;
|
||||||
proxy_cache_valid 200 1d;
|
proxy_cache_valid 200 1d;
|
||||||
@@ -115,7 +115,7 @@ server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location /SOGo/WebServerResources/ {
|
location /SOGo/WebServerResources/ {
|
||||||
proxy_pass http://sogo:9192/WebServerResources/;
|
proxy_pass http://172.22.1.252:9192/WebServerResources/;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_cache sogo;
|
proxy_cache sogo;
|
||||||
proxy_cache_valid 200 1d;
|
proxy_cache_valid 200 1d;
|
||||||
@@ -125,7 +125,7 @@ server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$ {
|
location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$ {
|
||||||
proxy_pass http://sogo:9192/$1.SOGo/Resources/$2;
|
proxy_pass http://172.22.1.252:9192/$1.SOGo/Resources/$2;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_cache sogo;
|
proxy_cache sogo;
|
||||||
proxy_cache_valid 200 1d;
|
proxy_cache_valid 200 1d;
|
||||||
|
|||||||
@@ -14,4 +14,9 @@ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
while ($row = array_shift($rows)) {
|
while ($row = array_shift($rows)) {
|
||||||
echo strtolower(trim($row['username'])) . PHP_EOL;
|
echo strtolower(trim($row['username'])) . PHP_EOL;
|
||||||
}
|
}
|
||||||
|
$stmt = $pdo->query("SELECT CONCAT(mailbox.local_part, '@', alias_domain.alias_domain) as `tag_ad` FROM `mailbox` INNER JOIN `alias_domain` ON mailbox.domain = alias_domain.target_domain WHERE mailbox.wants_tagged_subject='1';");
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
while ($row = array_shift($rows)) {
|
||||||
|
echo strtolower(trim($row['tag_ad'])) . PHP_EOL;
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
@@ -27,11 +27,25 @@ rspamd_config.ADD_DELIMITER_TAG = {
|
|||||||
callback = function(task)
|
callback = function(task)
|
||||||
local util = require("rspamd_util")
|
local util = require("rspamd_util")
|
||||||
local rspamd_logger = require "rspamd_logger"
|
local rspamd_logger = require "rspamd_logger"
|
||||||
local user_tagged = task:get_recipients(1)[1]['user']
|
|
||||||
|
local user_env_tagged = task:get_recipients(1)[1]['user']
|
||||||
|
local user_to_tagged = task:get_recipients(2)[1]['user']
|
||||||
|
|
||||||
local domain = task:get_recipients(1)[1]['domain']
|
local domain = task:get_recipients(1)[1]['domain']
|
||||||
local user, tag = user_tagged:match("([^+]+)+(.*)")
|
|
||||||
|
local user_env, tag_env = user_env_tagged:match("([^+]+)+(.*)")
|
||||||
|
local user_to, tag_to = user_to_tagged:match("([^+]+)+(.*)")
|
||||||
|
|
||||||
local authdomain = auth_domain_map:get_key(domain)
|
local authdomain = auth_domain_map:get_key(domain)
|
||||||
|
|
||||||
|
if tag_env then
|
||||||
|
tag = tag_env
|
||||||
|
user = user_env
|
||||||
|
elseif tag_to then
|
||||||
|
tag = tag_to
|
||||||
|
user = user_env
|
||||||
|
end
|
||||||
|
|
||||||
if tag and authdomain then
|
if tag and authdomain then
|
||||||
rspamd_logger.infox("Domain %s is part of mailcow, start reading tag settings", domain)
|
rspamd_logger.infox("Domain %s is part of mailcow, start reading tag settings", domain)
|
||||||
local user_untagged = user .. '@' .. domain
|
local user_untagged = user .. '@' .. domain
|
||||||
|
|||||||
+18
-3
@@ -4,6 +4,7 @@ require_once("inc/prerequisites.inc.php");
|
|||||||
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
|
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
|
||||||
require_once("inc/header.inc.php");
|
require_once("inc/header.inc.php");
|
||||||
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
||||||
|
$tfa_data = get_tfa();
|
||||||
?>
|
?>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h4><span class="glyphicon glyphicon-user" aria-hidden="true"></span> <?=$lang['admin']['access'];?></h4>
|
<h4><span class="glyphicon glyphicon-user" aria-hidden="true"></span> <?=$lang['admin']['access'];?></h4>
|
||||||
@@ -43,12 +44,26 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-3 col-xs-5 text-right"><?=$lang['tfa']['tfa'];?>:</div>
|
<div class="col-sm-3 col-xs-5 text-right"><?=$lang['tfa']['tfa'];?>:</div>
|
||||||
<div class="col-sm-9 col-xs-7">
|
<div class="col-sm-9 col-xs-7">
|
||||||
<p><?=get_tfa()['pretty'];?></p>
|
<p id="tfa_pretty"><?=$tfa_data['pretty'];?></p>
|
||||||
|
<div id="tfa_additional">
|
||||||
|
<?php if($tfa_data['additional']):
|
||||||
|
foreach ($tfa_data['additional'] as $key_info): ?>
|
||||||
|
<form style="display:inline;" method="post">
|
||||||
|
<input type="hidden" name="unset_tfa_key" value="<?=$key_info['id'];?>" />
|
||||||
|
<div style="padding:4px;margin:4px" class="label label-<?=($_SESSION['tfa_id'] == $key_info['id']) ? 'success' : 'default'; ?>">
|
||||||
|
<?=$key_info['key_id'];?>
|
||||||
|
<a href="#" style="font-weight:bold;color:white" onClick="$(this).closest('form').submit()">[<?=strtolower($lang['admin']['remove']);?>]</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<?php endforeach;
|
||||||
|
endif;?>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-3 col-xs-5 text-right"><?=$lang['tfa']['set_tfa'];?>:</div>
|
<div class="col-sm-3 col-xs-5 text-right"><?=$lang['tfa']['set_tfa'];?>:</div>
|
||||||
<div class="col-md-9 col-xs-7">
|
<div class="col-sm-9 col-xs-7">
|
||||||
<select data-width="auto" id="selectTFA" class="selectpicker" title="<?=$lang['tfa']['select'];?>">
|
<select data-width="auto" id="selectTFA" class="selectpicker" title="<?=$lang['tfa']['select'];?>">
|
||||||
<option value="yubi_otp"><?=$lang['tfa']['yubi_otp'];?></option>
|
<option value="yubi_otp"><?=$lang['tfa']['yubi_otp'];?></option>
|
||||||
<option value="u2f"><?=$lang['tfa']['u2f'];?></option>
|
<option value="u2f"><?=$lang['tfa']['u2f'];?></option>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
<?php
|
|
||||||
require_once 'inc/vars.inc.php';
|
require_once 'inc/vars.inc.php';
|
||||||
|
|
||||||
ini_set('error_reporting', '0');
|
ini_set('error_reporting', '0');
|
||||||
$config = array(
|
$config = array(
|
||||||
'useEASforOutlook' => 'yes',
|
'useEASforOutlook' => 'yes',
|
||||||
@@ -31,7 +29,7 @@ if ($config['useEASforOutlook'] == 'no') {
|
|||||||
$config['autodiscoverType'] = 'imap';
|
$config['autodiscoverType'] = 'imap';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
require_once 'inc/functions.inc.php';
|
|
||||||
$dsn = "$database_type:host=$database_host;dbname=$database_name";
|
$dsn = "$database_type:host=$database_host;dbname=$database_name";
|
||||||
$opt = [
|
$opt = [
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
|||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap-select v1.12.2 (http://silviomoreto.github.io/bootstrap-select)
|
||||||
|
*
|
||||||
|
* Copyright 2013-2017 bootstrap-select
|
||||||
|
* Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE)
|
||||||
|
*/select.bs-select-hidden,select.selectpicker{display:none!important}.bootstrap-select{width:220px\9}.bootstrap-select>.dropdown-toggle{width:100%;padding-right:25px;z-index:1}.bootstrap-select>.dropdown-toggle.bs-placeholder,.bootstrap-select>.dropdown-toggle.bs-placeholder:active,.bootstrap-select>.dropdown-toggle.bs-placeholder:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder:hover{color:#999}.bootstrap-select>select{position:absolute!important;bottom:0;left:50%;display:block!important;width:.5px!important;height:100%!important;padding:0!important;opacity:0!important;border:none}.bootstrap-select>select.mobile-device{top:0;left:0;display:block!important;width:100%!important;z-index:2}.error .bootstrap-select .dropdown-toggle,.has-error .bootstrap-select .dropdown-toggle{border-color:#b94a48}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select:not([class*=col-]):not([class*=form-control]):not(.input-group-btn){width:220px}.bootstrap-select .dropdown-toggle:focus{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none}.bootstrap-select.form-control:not([class*=col-]){width:100%}.bootstrap-select.form-control.input-group-btn{z-index:auto}.bootstrap-select.form-control.input-group-btn:not(:first-child):not(:last-child)>.btn{border-radius:0}.bootstrap-select.btn-group:not(.input-group-btn),.bootstrap-select.btn-group[class*=col-]{float:none;display:inline-block;margin-left:0}.bootstrap-select.btn-group.dropdown-menu-right,.bootstrap-select.btn-group[class*=col-].dropdown-menu-right,.row .bootstrap-select.btn-group[class*=col-].dropdown-menu-right{float:right}.form-group .bootstrap-select.btn-group,.form-horizontal .bootstrap-select.btn-group,.form-inline .bootstrap-select.btn-group{margin-bottom:0}.form-group-lg .bootstrap-select.btn-group.form-control,.form-group-sm .bootstrap-select.btn-group.form-control{padding:0}.form-group-lg .bootstrap-select.btn-group.form-control .dropdown-toggle,.form-group-sm .bootstrap-select.btn-group.form-control .dropdown-toggle{height:100%;font-size:inherit;line-height:inherit;border-radius:inherit}.form-inline .bootstrap-select.btn-group .form-control{width:100%}.bootstrap-select.btn-group.disabled,.bootstrap-select.btn-group>.disabled{cursor:not-allowed}.bootstrap-select.btn-group.disabled:focus,.bootstrap-select.btn-group>.disabled:focus{outline:0!important}.bootstrap-select.btn-group.bs-container{position:absolute;height:0!important;padding:0!important}.bootstrap-select.btn-group.bs-container .dropdown-menu{z-index:1060}.bootstrap-select.btn-group .dropdown-toggle .filter-option{display:inline-block;overflow:hidden;width:100%;text-align:left}.bootstrap-select.btn-group .dropdown-toggle .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.bootstrap-select.btn-group[class*=col-] .dropdown-toggle{width:100%}.bootstrap-select.btn-group .dropdown-menu{min-width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .dropdown-menu.inner{position:static;float:none;border:0;padding:0;margin:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.bootstrap-select.btn-group .dropdown-menu li{position:relative}.bootstrap-select.btn-group .dropdown-menu li.active small{color:#fff}.bootstrap-select.btn-group .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select.btn-group .dropdown-menu li a{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bootstrap-select.btn-group .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select.btn-group .dropdown-menu li a span.check-mark{display:none}.bootstrap-select.btn-group .dropdown-menu li a span.text{display:inline-block}.bootstrap-select.btn-group .dropdown-menu li small{padding-left:.5em}.bootstrap-select.btn-group .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);pointer-events:none;opacity:.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .no-results{padding:3px;background:#f5f5f5;margin:0 5px;white-space:nowrap}.bootstrap-select.btn-group.fit-width .dropdown-toggle .filter-option{position:static}.bootstrap-select.btn-group.fit-width .dropdown-toggle .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a span.check-mark{position:absolute;display:inline-block;right:15px;margin-top:5px}.bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle{z-index:1061}.bootstrap-select.show-menu-arrow .dropdown-toggle:before{content:'';border-left:7px solidLine truncated
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/*! =======================================================
|
||||||
|
VERSION 9.7.2
|
||||||
|
========================================================= */
|
||||||
|
/*! =========================================================
|
||||||
|
* bootstrap-slider.js
|
||||||
|
*
|
||||||
|
* Maintainers:
|
||||||
|
* Kyle Kemp
|
||||||
|
* - Twitter: @seiyria
|
||||||
|
* - Github: seiyria
|
||||||
|
* Rohit Kalkur
|
||||||
|
* - Twitter: @Rovolutionary
|
||||||
|
* - Github: rovolution
|
||||||
|
*
|
||||||
|
* =========================================================
|
||||||
|
*
|
||||||
|
* bootstrap-slider is released under the MIT License
|
||||||
|
* Copyright (c) 2017 Kyle Kemp, Rohit Kalkur, and contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following
|
||||||
|
* conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* ========================================================= */.slider{display:inline-block;vertical-align:middle;position:relative}.slider.slider-horizontal{width:210px;height:20px}.slider.slider-horizontal .slider-track{height:10px;width:100%;margin-top:-5px;top:50%;left:0}.slider.slider-horizontal .slider-selection,.slider.slider-horizontal .slider-track-low,.slider.slider-horizontal .slider-track-high{height:100%;top:0;bottom:0}.slider.slider-horizontal .slider-tick,.slider.slider-horizontal .slider-handle{margin-left:-10px}.slider.slider-horizontal .slider-tick.triangle,.slider.slider-horizontal .slider-handle.triangle{position:relative;top:50%;transform:translateY(-50%);border-width:0 10px 10px 10px;width:0;height:0;border-bottom-color:#0480be;margin-top:0}.slider.slider-horizontal .slider-tick-container{white-space:nowrap;position:absolute;top:0;left:0;width:100%}.slider.slider-horizontal .slider-tick-label-container{white-space:nowrap;margin-top:20px}.slider.slider-horizontal .slider-tick-label-container .slider-tick-label{padding-top:4px;display:inline-block;text-align:center}.slider.slider-horizontal.slider-rtl .slider-track{left:initial;right:0}.slider.slider-horizontal.slider-rtl .slider-tick,.slider.slider-horizontal.slider-rtl .slider-handle{margin-left:initial;margin-right:-10px}.slider.slider-horizontal.slider-rtl .slider-tick-container{left:initial;right:0}.slider.slider-vertical{height:210px;width:20px}.slider.slider-vertical .slider-track{width:10px;height:100%;left:25%;top:0}.slider.slider-vertical .slider-selection{width:100%;left:0;top:0;bottom:0}.slider.slider-vertical .slider-track-low,.slider.slider-vertical .slider-track-high{width:100%;left:0;right:0}.slider.slider-vertical .slider-tick,.slider.slider-vertical .slider-handle{margin-top:-10px}.slider.slider-vertical .slider-tick.triangle,.slider.slider-vertical .slider-handle.triangle{border-width:10px 0 10px 10px;width:1px;height:1px;border-left-color:#0480be;border-right-color:#0480be;margin-left:0;margin-right:0}.slider.slider-vertical .slider-tick-label-container{white-space:nowrap}.slider.slider-vertical .slider-tick-label-container .slider-tick-label{padding-left:4px}.slider.slider-vertical.slider-rtl .slider-track{left:initial;right:25%}.slider.slider-vertical.slider-rtl .slider-selection{left:initial;right:0}.slider.slider-vertical.slider-rtl .slider-tick.triangle,.slider.slider-vertical.slider-rtl .slider-handle.triangle{border-width:10px 10px 10px 0}.slider.slider-vertical.slider-rtl .slider-tick-label-container .slider-tick-label{padding-left:initial;padding-right:4px}.slider.slider-disabled .slider-handle{background-image:-webkit-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:-o-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:linear-gradient(to bottom,#dfdfdf 0,#bebebe 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdfdfdf',endColorstr='#ffbebebe',GradientType=0)}.slider.slider-disabled .slider-track{background-image:-webkit-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:-o-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:linear-gradient(to bottom,#e5e5e5 0,#e9e9e9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe5e5e5',endColorstr='#ffe9e9e9',GradientType=0);cursor:not-allowed}.slider input{display:none}.slider .tooltip.top{margin-top:-36px}.slider .tooltip-inner{white-space:nowrap;max-width:none}.slider .hide{display:none}.slider-track{position:absolute;cursor:pointer;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#f9f9f9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);border-radius:4px}.slider-selection{position:absolute;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-selection.tick-slider-selection{background-image:-webkit-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:-o-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:linear-gradient(to bottom,#89cdef 0,#81bfde 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff89cdef',endColorstr='#ff81bfde',GradientType=0)}.slider-tracLine truncated
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* bootstrap-switch - Turn checkboxes and radio buttons into toggle switches.
|
||||||
|
*
|
||||||
|
* @version v3.3.3
|
||||||
|
* @homepage http://www.bootstrap-switch.org
|
||||||
|
* @author Mattia Larentis <mattia@larentis.eu> (http://larentis.eu)
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
.bootstrap-switch{display:inline-block;direction:ltr;cursor:pointer;border-radius:4px;border:1px solid #ccc;position:relative;text-align:left;overflow:hidden;line-height:8px;z-index:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.bootstrap-switch .bootstrap-switch-container{display:inline-block;top:0;border-radius:4px;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-handle-on,.bootstrap-switch .bootstrap-switch-label{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;cursor:pointer;display:inline-block!important;height:100%;padding:6px 12px;font-size:14px;line-height:20px}.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-handle-on{text-align:center;z-index:1}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary{color:#fff;background:#337ab7}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info{color:#fff;background:#5bc0de}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success{color:#fff;background:#5cb85c}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning{background:#f0ad4e;color:#fff}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger{color:#fff;background:#d9534f}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default{color:#000;background:#eee}.bootstrap-switch .bootstrap-switch-label{text-align:center;margin-top:-1px;margin-bottom:-1px;z-index:100;color:#333;background:#fff}.bootstrap-switch .bootstrap-switch-handle-on{border-bottom-left-radius:3px;border-top-left-radius:3px}.bootstrap-switch .bootstrap-switch-handle-off{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch input[type=radio],.bootstrap-switch input[type=checkbox]{position:absolute!important;top:0;left:0;margin:0;z-index:-1;opacity:0;filter:alpha(opacity=0)}.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label{padding:1px 5px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label{padding:5px 10px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label{padding:6px 16px;font-size:18px;line-height:1.3333333}.bootstrap-switch.bootstrap-switch-disabled,.bootstrap-switch.bootstrap-switch-indeterminate,.bootstrap-switch.bootstrap-switch-readonly{cursor:default!important}.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label{opacity:.5;filter:alpha(opacity=50);cursor:default!important}.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container{-webkit-transition:margin-left .5s;-o-transition:margin-left .5s;transition:margin-left .5s}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on{border-radius:0 3px 3px 0}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off{border-radius:3px 0 0 3px}.bootstrap-switch.bootstrap-switch-focused{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-on .booLine truncated
|
||||||
+1
-1
@@ -114,7 +114,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-offset-2 col-sm-10">
|
<div class="col-sm-offset-2 col-sm-10">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label><input type="checkbox" name="delete_tfa"> <?=$lang['tfa']['delete_tfa'];?></label>
|
<label><input type="checkbox" name="disable_tfa"> <?=$lang['tfa']['disable_tfa'];?></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ endif;
|
|||||||
?>
|
?>
|
||||||
<div style="margin-bottom:100px"></div>
|
<div style="margin-bottom:100px"></div>
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.min.js"></script>
|
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.min.js"></script>
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-switch/3.3.2/js/bootstrap-switch.min.js"></script>
|
<script src="/js/bootstrap-switch.min.js"></script>
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/7.0.2/bootstrap-slider.min.js"></script>
|
<script src="/js/bootstrap-slider.min.js"></script>
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.9.4/js/bootstrap-select.js"></script>
|
<script src="/js/bootstrap-select.min.js"></script>
|
||||||
<script src="/js/u2f-api.js"></script>
|
<script src="/js/u2f-api.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// Select language and reopen active URL without POST
|
// Select language and reopen active URL without POST
|
||||||
@@ -74,6 +74,7 @@ $(document).ready(function() {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
// Set TFA modals
|
// Set TFA modals
|
||||||
|
|
||||||
$('#selectTFA').change(function () {
|
$('#selectTFA').change(function () {
|
||||||
if ($(this).val() == "yubi_otp") {
|
if ($(this).val() == "yubi_otp") {
|
||||||
$('#YubiOTPModal').modal('show');
|
$('#YubiOTPModal').modal('show');
|
||||||
|
|||||||
+114
-32
@@ -63,6 +63,7 @@ function hasMailboxObjectAccess($username, $role, $object) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function init_db_schema() {
|
function init_db_schema() {
|
||||||
|
// This will be much better in future releases...
|
||||||
global $pdo;
|
global $pdo;
|
||||||
try {
|
try {
|
||||||
$stmt = $pdo->prepare("SELECT NULL FROM `admin`, `imapsync`, `tfa`");
|
$stmt = $pdo->prepare("SELECT NULL FROM `admin`, `imapsync`, `tfa`");
|
||||||
@@ -101,7 +102,7 @@ function init_db_schema() {
|
|||||||
$stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE 'kind'");
|
$stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE 'kind'");
|
||||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
if ($num_results == 0) {
|
if ($num_results == 0) {
|
||||||
$pdo->query("ALTER TABLE `mailbox` ADD `kind` varchar(100) NOT NULL DEFAULT ''");
|
$pdo->query("ALTER TABLE `mailbox` ADD `kind` VARCHAR(100) NOT NULL DEFAULT ''");
|
||||||
}
|
}
|
||||||
$stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE 'multiple_bookings'");
|
$stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE 'multiple_bookings'");
|
||||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
@@ -113,6 +114,11 @@ function init_db_schema() {
|
|||||||
if ($num_results == 0) {
|
if ($num_results == 0) {
|
||||||
$pdo->query("ALTER TABLE `mailbox` ADD `wants_tagged_subject` tinyint(1) NOT NULL DEFAULT '0'");
|
$pdo->query("ALTER TABLE `mailbox` ADD `wants_tagged_subject` tinyint(1) NOT NULL DEFAULT '0'");
|
||||||
}
|
}
|
||||||
|
$stmt = $pdo->query("SHOW COLUMNS FROM `tfa` LIKE 'key_id'");
|
||||||
|
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
|
if ($num_results == 0) {
|
||||||
|
$pdo->query("ALTER TABLE `tfa` ADD `key_id` VARCHAR(255) DEFAULT 'unidentified'");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function verify_ssha256($hash, $password) {
|
function verify_ssha256($hash, $password) {
|
||||||
// Remove tag if any
|
// Remove tag if any
|
||||||
@@ -198,6 +204,8 @@ function check_login($user, $pass) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
unset($_SESSION['ldelay']);
|
unset($_SESSION['ldelay']);
|
||||||
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
||||||
|
$stmt->execute(array(':user' => $user));
|
||||||
return "domainadmin";
|
return "domainadmin";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1806,6 +1814,10 @@ function set_tfa($postarray) {
|
|||||||
|
|
||||||
switch ($postarray["tfa_method"]) {
|
switch ($postarray["tfa_method"]) {
|
||||||
case "yubi_otp":
|
case "yubi_otp":
|
||||||
|
(!isset($postarray["key_id"])) ? $key_id = 'unidentified' : $key_id = $postarray["key_id"];
|
||||||
|
$yubico_id = $postarray['yubico_id'];
|
||||||
|
$yubico_key = $postarray['yubico_key'];
|
||||||
|
$yubi = new Auth_Yubico($yubico_id, $yubico_key);
|
||||||
if (!$yubi) {
|
if (!$yubi) {
|
||||||
$_SESSION['return'] = array(
|
$_SESSION['return'] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
@@ -1824,16 +1836,21 @@ function set_tfa($postarray) {
|
|||||||
if (PEAR::isError($yauth)) {
|
if (PEAR::isError($yauth)) {
|
||||||
$_SESSION['return'] = array(
|
$_SESSION['return'] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'msg' => 'Yubico Authentication error: ' . $yauth->getMessage()
|
'msg' => 'Yubico API: ' . $yauth->getMessage()
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username");
|
// We could also do a modhex translation here
|
||||||
$stmt->execute(array(':username' => $username));
|
$yubico_modhex_id = substr($postarray["otp_token"], 0, 12);
|
||||||
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `authmech`, `active`) VALUES
|
$stmt = $pdo->prepare("DELETE FROM `tfa`
|
||||||
(:username, 'yubi_otp', 1)");
|
WHERE `username` = :username
|
||||||
$stmt->execute(array(':username' => $username));
|
AND (`authmech` != 'yubi_otp')
|
||||||
|
OR (`authmech` = 'yubi_otp' AND `secret` LIKE :modhex)");
|
||||||
|
$stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id));
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO `tfa` (`key_id`, `username`, `authmech`, `active`, `secret`) VALUES
|
||||||
|
(:key_id, :username, 'yubi_otp', '1', :secret)");
|
||||||
|
$stmt->execute(array(':key_id' => $key_id, ':username' => $username, ':secret' => $yubico_id . ':' . $yubico_key . ':' . $yubico_modhex_id));
|
||||||
}
|
}
|
||||||
catch (PDOException $e) {
|
catch (PDOException $e) {
|
||||||
$_SESSION['return'] = array(
|
$_SESSION['return'] = array(
|
||||||
@@ -1850,9 +1867,12 @@ function set_tfa($postarray) {
|
|||||||
|
|
||||||
case "u2f":
|
case "u2f":
|
||||||
try {
|
try {
|
||||||
|
(!isset($postarray["key_id"])) ? $key_id = 'unidentified' : $key_id = $postarray["key_id"];
|
||||||
$reg = $u2f->doRegister(json_decode($_SESSION['regReq']), json_decode($postarray['token']));
|
$reg = $u2f->doRegister(json_decode($_SESSION['regReq']), json_decode($postarray['token']));
|
||||||
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`) VALUES (?, 'u2f', ?, ?, ?, ?)");
|
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `authmech` != 'u2f'");
|
||||||
$stmt->execute(array($username, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter));
|
$stmt->execute(array(':username' => $username));
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`) VALUES (?, ?, 'u2f', ?, ?, ?, ?, '1')");
|
||||||
|
$stmt->execute(array($username, $key_id, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter));
|
||||||
$_SESSION['return'] = array(
|
$_SESSION['return'] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'msg' => sprintf($lang['success']['object_modified'], $username)
|
'msg' => sprintf($lang['success']['object_modified'], $username)
|
||||||
@@ -1887,6 +1907,55 @@ function set_tfa($postarray) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function unset_tfa_key($postarray) {
|
||||||
|
// Can only unset own keys
|
||||||
|
// Needs at least one key left
|
||||||
|
global $pdo;
|
||||||
|
global $lang;
|
||||||
|
$id = intval($postarray['unset_tfa_key']);
|
||||||
|
if ($_SESSION['mailcow_cc_role'] != "domainadmin" &&
|
||||||
|
$_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
|
$_SESSION['return'] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'msg' => sprintf($lang['danger']['access_denied'])
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$username = $_SESSION['mailcow_cc_username'];
|
||||||
|
try {
|
||||||
|
if (!is_numeric($id)) {
|
||||||
|
$_SESSION['return'] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'msg' => sprintf($lang['danger']['access_denied'])
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa`
|
||||||
|
WHERE `username` = :username AND `active` = '1'");
|
||||||
|
$stmt->execute(array(':username' => $username));
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($row['keys'] == "1") {
|
||||||
|
$_SESSION['return'] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'msg' => sprintf($lang['danger']['last_key'])
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `id` = :id");
|
||||||
|
$stmt->execute(array(':username' => $username, ':id' => $id));
|
||||||
|
$_SESSION['return'] = array(
|
||||||
|
'type' => 'success',
|
||||||
|
'msg' => sprintf($lang['success']['object_modified'], $username)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (PDOException $e) {
|
||||||
|
$_SESSION['return'] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'msg' => 'MySQL: '.$e
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
function get_tfa($username = null) {
|
function get_tfa($username = null) {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
if (isset($_SESSION['mailcow_cc_username'])) {
|
if (isset($_SESSION['mailcow_cc_username'])) {
|
||||||
@@ -1896,8 +1965,8 @@ function get_tfa($username = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare("SELECT `authmech` FROM `tfa`
|
$stmt = $pdo->prepare("SELECT * FROM `tfa`
|
||||||
WHERE `username` = :username");
|
WHERE `username` = :username AND `active` = '1'");
|
||||||
$stmt->execute(array(':username' => $username));
|
$stmt->execute(array(':username' => $username));
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
@@ -1905,11 +1974,27 @@ function get_tfa($username = null) {
|
|||||||
case "yubi_otp":
|
case "yubi_otp":
|
||||||
$data['name'] = "yubi_otp";
|
$data['name'] = "yubi_otp";
|
||||||
$data['pretty'] = "Yubico OTP";
|
$data['pretty'] = "Yubico OTP";
|
||||||
|
$stmt = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':username' => $username,
|
||||||
|
));
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
while($row = array_shift($rows)) {
|
||||||
|
$data['additional'][] = $row;
|
||||||
|
}
|
||||||
return $data;
|
return $data;
|
||||||
break;
|
break;
|
||||||
case "u2f":
|
case "u2f":
|
||||||
$data['name'] = "u2f";
|
$data['name'] = "u2f";
|
||||||
$data['pretty'] = "Fido U2F";
|
$data['pretty'] = "Fido U2F";
|
||||||
|
$stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':username' => $username,
|
||||||
|
));
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
while($row = array_shift($rows)) {
|
||||||
|
$data['additional'][] = $row;
|
||||||
|
}
|
||||||
return $data;
|
return $data;
|
||||||
break;
|
break;
|
||||||
case "hotp":
|
case "hotp":
|
||||||
@@ -1935,7 +2020,7 @@ function verify_tfa_login($username, $token) {
|
|||||||
global $yubi;
|
global $yubi;
|
||||||
|
|
||||||
$stmt = $pdo->prepare("SELECT `authmech` FROM `tfa`
|
$stmt = $pdo->prepare("SELECT `authmech` FROM `tfa`
|
||||||
WHERE `username` = :username");
|
WHERE `username` = :username AND `active` = '1'");
|
||||||
$stmt->execute(array(':username' => $username));
|
$stmt->execute(array(':username' => $username));
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
@@ -1944,6 +2029,16 @@ function verify_tfa_login($username, $token) {
|
|||||||
if (!ctype_alnum($token) || strlen($token) != 44) {
|
if (!ctype_alnum($token) || strlen($token) != 44) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
$yubico_modhex_id = substr($token, 0, 12);
|
||||||
|
$stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
|
||||||
|
WHERE `username` = :username
|
||||||
|
AND `authmech` = 'yubi_otp'
|
||||||
|
AND `active`='1'
|
||||||
|
AND `secret` LIKE :modhex");
|
||||||
|
$stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id));
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
$yubico_auth = explode(':', $row['secret']);
|
||||||
|
$yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]);
|
||||||
$yauth = $yubi->verify($token);
|
$yauth = $yubi->verify($token);
|
||||||
if (PEAR::isError($yauth)) {
|
if (PEAR::isError($yauth)) {
|
||||||
$_SESSION['return'] = array(
|
$_SESSION['return'] = array(
|
||||||
@@ -1953,6 +2048,7 @@ function verify_tfa_login($username, $token) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
$_SESSION['tfa_id'] = $row['id'];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -1963,6 +2059,7 @@ function verify_tfa_login($username, $token) {
|
|||||||
$reg = $u2f->doAuthenticate(json_decode($_SESSION['authReq']), get_u2f_registrations($username), json_decode($token));
|
$reg = $u2f->doAuthenticate(json_decode($_SESSION['authReq']), get_u2f_registrations($username), json_decode($token));
|
||||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `counter` = ? WHERE `id` = ?");
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `counter` = ? WHERE `id` = ?");
|
||||||
$stmt->execute(array($reg->counter, $reg->id));
|
$stmt->execute(array($reg->counter, $reg->id));
|
||||||
|
$_SESSION['tfa_id'] = $reg->id;
|
||||||
$_SESSION['authReq'] = null;
|
$_SESSION['authReq'] = null;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2089,8 +2186,8 @@ function edit_domain_admin($postarray) {
|
|||||||
':modified' => date('Y-m-d H:i:s'),
|
':modified' => date('Y-m-d H:i:s'),
|
||||||
':active' => $active
|
':active' => $active
|
||||||
));
|
));
|
||||||
if (isset($postarray['delete_tfa'])) {
|
if (isset($postarray['disable_tfa'])) {
|
||||||
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
||||||
$stmt->execute(array(':username' => $username_now));
|
$stmt->execute(array(':username' => $username_now));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -2115,8 +2212,8 @@ function edit_domain_admin($postarray) {
|
|||||||
':modified' => date('Y-m-d H:i:s'),
|
':modified' => date('Y-m-d H:i:s'),
|
||||||
':active' => $active
|
':active' => $active
|
||||||
));
|
));
|
||||||
if (isset($postarray['delete_tfa'])) {
|
if (isset($postarray['disable_tfa'])) {
|
||||||
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
||||||
$stmt->execute(array(':username' => $username));
|
$stmt->execute(array(':username' => $username));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -4818,23 +4915,8 @@ function mailbox_get_sender_acl_handles($mailbox) {
|
|||||||
}
|
}
|
||||||
function get_u2f_registrations($username) {
|
function get_u2f_registrations($username) {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
$sel = $pdo->prepare("SELECT * FROM `tfa` WHERE `username` = ?");
|
$sel = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = ? AND `active` = '1'");
|
||||||
$sel->execute(array($username));
|
$sel->execute(array($username));
|
||||||
return $sel->fetchAll(PDO::FETCH_OBJ);
|
return $sel->fetchAll(PDO::FETCH_OBJ);
|
||||||
}
|
}
|
||||||
function add_u2f_registration($username, $reg) {
|
|
||||||
global $pdo;
|
|
||||||
global $lang;
|
|
||||||
$ins = $pdo->prepare("INSERT INTO `tfa` (`username`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`) VALUES (?, 'u2f', ?, ?, ?, ?)");
|
|
||||||
$ins->execute(array($username, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter));
|
|
||||||
$_SESSION['return'] = array(
|
|
||||||
'type' => 'success',
|
|
||||||
'msg' => sprintf($lang['success']['object_modified'], $username)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function edit_u2f_registration($reg) {
|
|
||||||
global $pdo;
|
|
||||||
$upd = $pdo->prepare("update tfa set counter = ? where id = ?");
|
|
||||||
$upd->execute(array($reg->counter, $reg->id));
|
|
||||||
}
|
|
||||||
?>
|
?>
|
||||||
@@ -12,9 +12,9 @@
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.0/jquery.min.js" integrity="sha384-XxcvoeNF5V0ZfksTnV+bejnCsJjOOIzN6UVwF85WBsAnU3zeYh5bloN+L4WLgeNE" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.0/jquery.min.js" integrity="sha384-XxcvoeNF5V0ZfksTnV+bejnCsJjOOIzN6UVwF85WBsAnU3zeYh5bloN+L4WLgeNE" crossorigin="anonymous"></script>
|
||||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css">
|
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.6/<?=strtolower(trim($DEFAULT_THEME));?>/bootstrap.min.css">
|
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.6/<?=strtolower(trim($DEFAULT_THEME));?>/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.9.4/css/bootstrap-select.min.css">
|
<link rel="stylesheet" href="/css/bootstrap-select.min.css">
|
||||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/7.0.2/css/bootstrap-slider.min.css">
|
<link rel="stylesheet" href="/css/bootstrap-slider.min.css">
|
||||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-switch/3.3.2/css/bootstrap3/bootstrap-switch.min.css">
|
<link rel="stylesheet" href="/css/bootstrap-switch.min.css">
|
||||||
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Source+Sans+Pro:400,600,700&subset=latin,latin-ext">
|
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Source+Sans+Pro:400,600,700&subset=latin,latin-ext">
|
||||||
<link rel="stylesheet" href="/inc/languages.min.css">
|
<link rel="stylesheet" href="/inc/languages.min.css">
|
||||||
<link rel="stylesheet" href="/css/mailcow.css">
|
<link rel="stylesheet" href="/css/mailcow.css">
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
<ul class="dropdown-menu" role="menu">
|
<ul class="dropdown-menu" role="menu">
|
||||||
<li <?=($_SESSION['mailcow_locale'] == 'de') ? 'class="active"' : ''?>> <a href="?<?= http_build_query(array_merge($_GET, array("lang" => "de"))) ?>"><span class="lang-xs lang-lbl-full" lang="de"></span></a></li>
|
<li <?=($_SESSION['mailcow_locale'] == 'de') ? 'class="active"' : ''?>> <a href="?<?= http_build_query(array_merge($_GET, array("lang" => "de"))) ?>"><span class="lang-xs lang-lbl-full" lang="de"></span></a></li>
|
||||||
<li <?=($_SESSION['mailcow_locale'] == 'en') ? 'class="active"' : ''?>> <a href="?<?= http_build_query(array_merge($_GET, array("lang" => "en"))) ?>"><span class="lang-xs lang-lbl-full" lang="en"></span></a></li>
|
<li <?=($_SESSION['mailcow_locale'] == 'en') ? 'class="active"' : ''?>> <a href="?<?= http_build_query(array_merge($_GET, array("lang" => "en"))) ?>"><span class="lang-xs lang-lbl-full" lang="en"></span></a></li>
|
||||||
|
<li <?=($_SESSION['mailcow_locale'] == 'es') ? 'class="active"' : ''?>> <a href="?<?= http_build_query(array_merge($_GET, array("lang" => "es"))) ?>"><span class="lang-xs lang-lbl-full" lang="es"></span></a></li>
|
||||||
<li <?=($_SESSION['mailcow_locale'] == 'nl') ? 'class="active"' : ''?>> <a href="?<?= http_build_query(array_merge($_GET, array("lang" => "nl"))) ?>"><span class="lang-xs lang-lbl-full" lang="nl"></span></a></li>
|
<li <?=($_SESSION['mailcow_locale'] == 'nl') ? 'class="active"' : ''?>> <a href="?<?= http_build_query(array_merge($_GET, array("lang" => "nl"))) ?>"><span class="lang-xs lang-lbl-full" lang="nl"></span></a></li>
|
||||||
<li <?=($_SESSION['mailcow_locale'] == 'pt') ? 'class="active"' : ''?>> <a href="?<?= http_build_query(array_merge($_GET, array("lang" => "pt"))) ?>"><span class="lang-xs lang-lbl-full" lang="pt"></span></a></li>
|
<li <?=($_SESSION['mailcow_locale'] == 'pt') ? 'class="active"' : ''?>> <a href="?<?= http_build_query(array_merge($_GET, array("lang" => "pt"))) ?>"><span class="lang-xs lang-lbl-full" lang="pt"></span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -22,10 +22,8 @@ if (file_exists('./inc/vars.local.inc.php')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Yubi OTP API
|
// Yubi OTP API
|
||||||
if (!empty($YUBI_API['ID']) && !empty($YUBI_API['KEY'])) {
|
require_once 'inc/lib/Yubico.php';
|
||||||
require_once 'inc/lib/Yubico.php';
|
|
||||||
$yubi = new Auth_Yubico($YUBI_API['ID'], $YUBI_API['KEY']);
|
|
||||||
}
|
|
||||||
// U2F API
|
// U2F API
|
||||||
require_once 'inc/lib/U2F.php';
|
require_once 'inc/lib/U2F.php';
|
||||||
$scheme = isset($_SERVER['HTTPS']) ? "https://" : "http://";
|
$scheme = isset($_SERVER['HTTPS']) ? "https://" : "http://";
|
||||||
@@ -59,6 +57,10 @@ if (isset($_COOKIE['language'])) {
|
|||||||
$_SESSION['mailcow_locale'] = 'en';
|
$_SESSION['mailcow_locale'] = 'en';
|
||||||
setcookie('language', 'en');
|
setcookie('language', 'en');
|
||||||
break;
|
break;
|
||||||
|
case "es":
|
||||||
|
$_SESSION['mailcow_locale'] = 'es';
|
||||||
|
setcookie('language', 'es');
|
||||||
|
break;
|
||||||
case "nl":
|
case "nl":
|
||||||
$_SESSION['mailcow_locale'] = 'nl';
|
$_SESSION['mailcow_locale'] = 'nl';
|
||||||
setcookie('language', 'nl');
|
setcookie('language', 'nl');
|
||||||
@@ -79,6 +81,10 @@ if (isset($_GET['lang'])) {
|
|||||||
$_SESSION['mailcow_locale'] = 'en';
|
$_SESSION['mailcow_locale'] = 'en';
|
||||||
setcookie('language', 'en');
|
setcookie('language', 'en');
|
||||||
break;
|
break;
|
||||||
|
case "es":
|
||||||
|
$_SESSION['mailcow_locale'] = 'es';
|
||||||
|
setcookie('language', 'es');
|
||||||
|
break;
|
||||||
case "nl":
|
case "nl":
|
||||||
$_SESSION['mailcow_locale'] = 'nl';
|
$_SESSION['mailcow_locale'] = 'nl';
|
||||||
setcookie('language', 'nl');
|
setcookie('language', 'nl');
|
||||||
|
|||||||
@@ -4,6 +4,18 @@
|
|||||||
<div class="modal-header"><b><?=$lang['tfa']['yubi_otp'];?></b></div>
|
<div class="modal-header"><b><?=$lang['tfa']['yubi_otp'];?></b></div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form role="form" method="post">
|
<form role="form" method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" class="form-control" name="key_id" id="key_id" placeholder="<?=$lang['tfa']['key_id'];?>" autocomplete="off" required>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<p class="help-block"><?=$lang['tfa']['api_register'];?></p>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" class="form-control" name="yubico_id" id="yubico_id" placeholder="Yubico API ID" autocomplete="off" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" class="form-control" name="yubico_key" id="yubico_key" placeholder="Yubico API Key" autocomplete="off" required>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="password" class="form-control" name="confirm_password" id="confirm_password" placeholder="<?=$lang['user']['password_now'];?>" autocomplete="off" required>
|
<input type="password" class="form-control" name="confirm_password" id="confirm_password" placeholder="<?=$lang['user']['password_now'];?>" autocomplete="off" required>
|
||||||
</div>
|
</div>
|
||||||
@@ -27,6 +39,9 @@
|
|||||||
<div class="modal-header"><b><?=$lang['tfa']['u2f'];?></b></div>
|
<div class="modal-header"><b><?=$lang['tfa']['u2f'];?></b></div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form role="form" method="post" id="u2f_reg_form">
|
<form role="form" method="post" id="u2f_reg_form">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" class="form-control" name="key_id" id="key_id" placeholder="<?=$lang['tfa']['key_id'];?>" autocomplete="off" required>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="password" class="form-control" name="confirm_password" id="confirm_password" placeholder="<?=$lang['user']['password_now'];?>" autocomplete="off" required>
|
<input type="password" class="form-control" name="confirm_password" id="confirm_password" placeholder="<?=$lang['user']['password_now'];?>" autocomplete="off" required>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -115,6 +115,9 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
|||||||
if (isset($_POST["set_tfa"])) {
|
if (isset($_POST["set_tfa"])) {
|
||||||
set_tfa($_POST);
|
set_tfa($_POST);
|
||||||
}
|
}
|
||||||
|
if (isset($_POST["unset_tfa_key"])) {
|
||||||
|
unset_tfa_key($_POST);
|
||||||
|
}
|
||||||
if (isset($_POST["add_policy_list_item"])) {
|
if (isset($_POST["add_policy_list_item"])) {
|
||||||
add_policy_list_item($_POST);
|
add_policy_list_item($_POST);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,4 @@ $DEFAULT_LANG = "en";
|
|||||||
// See https://bootswatch.com/
|
// See https://bootswatch.com/
|
||||||
$DEFAULT_THEME = "lumen";
|
$DEFAULT_THEME = "lumen";
|
||||||
|
|
||||||
// If you want to use Yubico TFA methods, setup an ID and a key here: https://upgrade.yubico.com/getapikey/
|
|
||||||
// Remember to override this value using vars.local.inc.php, do not change it here.
|
|
||||||
$YUBI_API['ID'] = "";
|
|
||||||
$YUBI_API['KEY'] = "";
|
|
||||||
?>
|
?>
|
||||||
@@ -48,6 +48,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
|||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li <?=($_SESSION['mailcow_locale'] == 'de') ? 'class="active"' : ''?>><a href="?<?= http_build_query(array_merge($_GET, array("lang" => "de"))) ?>"><span class="lang-xs lang-lbl-full" lang="de"></span></a></li>
|
<li <?=($_SESSION['mailcow_locale'] == 'de') ? 'class="active"' : ''?>><a href="?<?= http_build_query(array_merge($_GET, array("lang" => "de"))) ?>"><span class="lang-xs lang-lbl-full" lang="de"></span></a></li>
|
||||||
<li <?=($_SESSION['mailcow_locale'] == 'en') ? 'class="active"' : ''?>><a href="?<?= http_build_query(array_merge($_GET, array("lang" => "en"))) ?>"><span class="lang-xs lang-lbl-full" lang="en"></span></a></li>
|
<li <?=($_SESSION['mailcow_locale'] == 'en') ? 'class="active"' : ''?>><a href="?<?= http_build_query(array_merge($_GET, array("lang" => "en"))) ?>"><span class="lang-xs lang-lbl-full" lang="en"></span></a></li>
|
||||||
|
<li <?=($_SESSION['mailcow_locale'] == 'es') ? 'class="active"' : ''?>><a href="?<?= http_build_query(array_merge($_GET, array("lang" => "es"))) ?>"><span class="lang-xs lang-lbl-full" lang="es"></span></a></li>
|
||||||
<li <?=($_SESSION['mailcow_locale'] == 'nl') ? 'class="active"' : ''?>><a href="?<?= http_build_query(array_merge($_GET, array("lang" => "nl"))) ?>"><span class="lang-xs lang-lbl-full" lang="nl"></span></a></li>
|
<li <?=($_SESSION['mailcow_locale'] == 'nl') ? 'class="active"' : ''?>><a href="?<?= http_build_query(array_merge($_GET, array("lang" => "nl"))) ?>"><span class="lang-xs lang-lbl-full" lang="nl"></span></a></li>
|
||||||
<li <?=($_SESSION['mailcow_locale'] == 'pt') ? 'class="active"' : ''?>><a href="?<?= http_build_query(array_merge($_GET, array("lang" => "pt"))) ?>"><span class="lang-xs lang-lbl-full" lang="pt"></span></a></li>
|
<li <?=($_SESSION['mailcow_locale'] == 'pt') ? 'class="active"' : ''?>><a href="?<?= http_build_query(array_merge($_GET, array("lang" => "pt"))) ?>"><span class="lang-xs lang-lbl-full" lang="pt"></span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap-select v1.12.2 (http://silviomoreto.github.io/bootstrap-select)
|
||||||
|
*
|
||||||
|
* Copyright 2013-2017 bootstrap-select
|
||||||
|
* Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE)
|
||||||
|
*/
|
||||||
|
!function(a,b){"function"==typeof define&&define.amd?define(["jquery"],function(a){return b(a)}):"object"==typeof module&&module.exports?module.exports=b(require("jquery")):b(a.jQuery)}(this,function(a){!function(a){"use strict";function b(b){var c=[{re:/[\xC0-\xC6]/g,ch:"A"},{re:/[\xE0-\xE6]/g,ch:"a"},{re:/[\xC8-\xCB]/g,ch:"E"},{re:/[\xE8-\xEB]/g,ch:"e"},{re:/[\xCC-\xCF]/g,ch:"I"},{re:/[\xEC-\xEF]/g,ch:"i"},{re:/[\xD2-\xD6]/g,ch:"O"},{re:/[\xF2-\xF6]/g,ch:"o"},{re:/[\xD9-\xDC]/g,ch:"U"},{re:/[\xF9-\xFC]/g,ch:"u"},{re:/[\xC7-\xE7]/g,ch:"c"},{re:/[\xD1]/g,ch:"N"},{re:/[\xF1]/g,ch:"n"}];return a.each(c,function(){b=b?b.replace(this.re,this.ch):""}),b}function c(b){var c=arguments,d=b;[].shift.apply(c);var e,f=this.each(function(){var b=a(this);if(b.is("select")){var f=b.data("selectpicker"),g="object"==typeof d&&d;if(f){if(g)for(var h in g)g.hasOwnProperty(h)&&(f.options[h]=g[h])}else{var i=a.extend({},k.DEFAULTS,a.fn.selectpicker.defaults||{},b.data(),g);i.template=a.extend({},k.DEFAULTS.template,a.fn.selectpicker.defaults?a.fn.selectpicker.defaults.template:{},b.data().template,g.template),b.data("selectpicker",f=new k(this,i))}"string"==typeof d&&(e=f[d]instanceof Function?f[d].apply(f,c):f.options[d])}});return"undefined"!=typeof e?e:f}String.prototype.includes||!function(){var a={}.toString,b=function(){try{var a={},b=Object.defineProperty,c=b(a,a,a)&&b}catch(a){}return c}(),c="".indexOf,d=function(b){if(null==this)throw new TypeError;var d=String(this);if(b&&"[object RegExp]"==a.call(b))throw new TypeError;var e=d.length,f=String(b),g=f.length,h=arguments.length>1?arguments[1]:void 0,i=h?Number(h):0;i!=i&&(i=0);var j=Math.min(Math.max(i,0),e);return!(g+j>e)&&c.call(d,f,i)!=-1};b?b(String.prototype,"includes",{value:d,configurable:!0,writable:!0}):String.prototype.includes=d}(),String.prototype.startsWith||!function(){var a=function(){try{var a={},b=Object.defineProperty,c=b(a,a,a)&&b}catch(a){}return c}(),b={}.toString,c=function(a){if(null==this)throw new TypeError;var c=String(this);if(a&&"[object RegExp]"==b.call(a))throw new TypeError;var d=c.length,e=String(a),f=e.length,g=arguments.length>1?arguments[1]:void 0,h=g?Number(g):0;h!=h&&(h=0);var i=Math.min(Math.max(h,0),d);if(f+i>d)return!1;for(var j=-1;++j<f;)if(c.charCodeAt(i+j)!=e.charCodeAt(j))return!1;return!0};a?a(String.prototype,"startsWith",{value:c,configurable:!0,writable:!0}):String.prototype.startsWith=c}(),Object.keys||(Object.keys=function(a,b,c){c=[];for(b in a)c.hasOwnProperty.call(a,b)&&c.push(b);return c});var d={useDefault:!1,_set:a.valHooks.select.set};a.valHooks.select.set=function(b,c){return c&&!d.useDefault&&a(b).data("selected",!0),d._set.apply(this,arguments)};var e=null;a.fn.triggerNative=function(a){var b,c=this[0];c.dispatchEvent?("function"==typeof Event?b=new Event(a,{bubbles:!0}):(b=document.createEvent("Event"),b.initEvent(a,!0,!1)),c.dispatchEvent(b)):c.fireEvent?(b=document.createEventObject(),b.eventType=a,c.fireEvent("on"+a,b)):this.trigger(a)},a.expr.pseudos.icontains=function(b,c,d){var e=a(b),f=(e.data("tokens")||e.text()).toString().toUpperCase();return f.includes(d[3].toUpperCase())},a.expr.pseudos.ibegins=function(b,c,d){var e=a(b),f=(e.data("tokens")||e.text()).toString().toUpperCase();return f.startsWith(d[3].toUpperCase())},a.expr.pseudos.aicontains=function(b,c,d){var e=a(b),f=(e.data("tokens")||e.data("normalizedText")||e.text()).toString().toUpperCase();return f.includes(d[3].toUpperCase())},a.expr.pseudos.aibegins=function(b,c,d){var e=a(b),f=(e.data("tokens")||e.data("normalizedText")||e.text()).toString().toUpperCase();return f.startsWith(d[3].toUpperCase())};var f={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},g={"&":"&","<":"<",">":">",""":'"',"'":"'","`":"`"},h=function(a){var b=function(b){return a[b]},c="(?:"+Object.keys(a).join("|")+")",d=RegExp(c),e=RegExp(c,"g");return function(a){return a=null==a?"":""+a,d.test(a)?a.replace(e,b):a}},i=h(f),j=h(g),k=function(b,c){d.useDefault||(a.valHooks.select.set=d._set,d.useDefault=!0),this.$element=a(b),this.$newElement=null,this.$button=null,this.$menu=null,this.$lis=null,this.options=c,null===this.options.title&&(this.options.title=this.$element.attr("title"));var e=this.options.windowPadding;"number"==typeof e&&(this.options.windowPadding=[e,e,e,e]),this.val=k.prototype.val,this.render=k.prototype.render,this.refresh=k.prototype.refresh,this.setStyle=k.prototype.setStyle,this.selectAll=k.prototype.selectAll,this.deselectAll=k.prototype.deselectAll,this.destroy=k.prototype.destroy,this.remove=k.prototype.remove,this.show=k.prototype.show,this.hide=k.prototype.hide,this.init()};k.VERSION="1.12.2",k.DEFAULTS={noneSelectedText:"Nothing selected",noneResultsText:"No results matched {0}",countSelectedText:function(a,b){return 1==a?"{0} item selected":"{0} items selected"},maxOptionsText:function(a,b){return[1==a?"Limit reached ({n} item max)":"Limit reached ({n} items max)",1==b?"Group limit reached ({nLine truncated
|
||||||
|
m.focus());else{var u=a(":focus");u.click(),u.focus(),c.preventDefault(),a(document).data("spaceSelect",!0)}a(document).data("keycount",0)}(/(^9$|27)/.test(c.keyCode.toString(10))&&l&&(o.multiple||o.options.liveSearch)||/(27)/.test(c.keyCode.toString(10))&&!l)&&(o.$menu.parent().removeClass("open"),o.options.container&&o.$newElement.removeClass("open"),o.$button.focus())}},mobile:function(){this.$element.addClass("mobile-device")},refresh:function(){this.$lis=null,this.liObj={},this.reloadLi(),this.render(),this.checkDisabled(),this.liHeight(!0),this.setStyle(),this.setWidth(),this.$lis&&this.$searchbox.trigger("propertychange"),this.$element.trigger("refreshed.bs.select")},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()},destroy:function(){this.$newElement.before(this.$element).remove(),this.$bsContainer?this.$bsContainer.remove():this.$menu.remove(),this.$element.off(".bs.select").removeData("selectpicker").removeClass("bs-select-hidden selectpicker")}};var l=a.fn.selectpicker;a.fn.selectpicker=c,a.fn.selectpicker.Constructor=k,a.fn.selectpicker.noConflict=function(){return a.fn.selectpicker=l,this},a(document).data("keycount",0).on("keydown.bs.select",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input',k.prototype.keydown).on("focusin.modal",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input',function(a){a.stopPropagation()}),a(window).on("load.bs.select.data-api",function(){a(".selectpicker").each(function(){var b=a(this);c.call(b,b.data())})})}(a)});
|
||||||
|
//# sourceMappingURL=bootstrap-select.js.map
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
/*! =======================================================
|
||||||
|
VERSION 9.7.2
|
||||||
|
========================================================= */
|
||||||
|
"use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},windowIsDefined="object"===("undefined"==typeof window?"undefined":_typeof(window));!function(a){if("function"==typeof define&&define.amd)define(["jquery"],a);else if("object"===("undefined"==typeof module?"undefined":_typeof(module))&&module.exports){var b;try{b=require("jquery")}catch(c){b=null}module.exports=a(b)}else window&&(window.Slider=a(window.jQuery))}(function(a){var b="slider",c="bootstrapSlider";windowIsDefined&&!window.console&&(window.console={}),windowIsDefined&&!window.console.log&&(window.console.log=function(){}),windowIsDefined&&!window.console.warn&&(window.console.warn=function(){});var d;return function(a){function b(){}function c(a){function c(b){b.prototype.option||(b.prototype.option=function(b){a.isPlainObject(b)&&(this.options=a.extend(!0,this.options,b))})}function e(b,c){a.fn[b]=function(e){if("string"==typeof e){for(var g=d.call(arguments,1),h=0,i=this.length;i>h;h++){var j=this[h],k=a.data(j,b);if(k)if(a.isFunction(k[e])&&"_"!==e.charAt(0)){var l=k[e].apply(k,g);if(void 0!==l&&l!==k)return l}else f("no such method '"+e+"' for "+b+" instance");else f("cannot call methods on "+b+" prior to initialization; attempted to call '"+e+"'")}return this}var m=this.map(function(){var d=a.data(this,b);return d?(d.option(e),d._init()):(d=new c(this,e),a.data(this,b,d)),a(this)});return!m||m.length>1?m:m[0]}}if(a){var f="undefined"==typeof console?b:function(a){console.error(a)};return a.bridget=function(a,b){c(b),e(a,b)},a.bridget}}var d=Array.prototype.slice;c(a)}(a),function(a){function e(b,c){function d(a,b){var c="data-slider-"+b.replace(/_/g,"-"),d=a.getAttribute(c);try{return JSON.parse(d)}catch(e){return d}}this._state={value:null,enabled:null,offset:null,size:null,percentage:null,inDrag:!1,over:!1},this.ticksCallbackMap={},this.handleCallbackMap={},"string"==typeof b?this.element=document.querySelector(b):b instanceof HTMLElement&&(this.element=b),c=c?c:{};for(var e=Object.keys(this.defaultOptions),f=0;f<e.length;f++){var h=e[f],i=c[h];i="undefined"!=typeof i?i:d(this.element,h),i=null!==i?i:this.defaultOptions[h],this.options||(this.options={}),this.options[h]=i}"auto"===this.options.rtl&&(this.options.rtl="rtl"===window.getComputedStyle(this.element).direction),"vertical"!==this.options.orientation||"top"!==this.options.tooltip_position&&"bottom"!==this.options.tooltip_position?"horizontal"!==this.options.orientation||"left"!==this.options.tooltip_position&&"right"!==this.options.tooltip_position||(this.options.tooltip_position="top"):this.options.rtl?this.options.tooltip_position="left":this.options.tooltip_position="right";var j,k,l,m,n,o=this.element.style.width,p=!1,q=this.element.parentNode;if(this.sliderElem)p=!0;else{this.sliderElem=document.createElement("div"),this.sliderElem.className="slider";var r=document.createElement("div");if(r.className="slider-track",k=document.createElement("div"),k.className="slider-track-low",j=document.createElement("div"),j.className="slider-selection",l=document.createElement("div"),l.className="slider-track-high",m=document.createElement("div"),m.className="slider-handle min-slider-handle",m.setAttribute("role","slider"),m.setAttribute("aria-valuemin",this.options.min),m.setAttribute("aria-valuemax",this.options.max),n=document.createElement("div"),n.className="slider-handle max-slider-handle",n.setAttribute("role","slider"),n.setAttribute("aria-valuemin",this.options.min),n.setAttribute("aria-valuemax",this.options.max),r.appendChild(k),r.appendChild(j),r.appendChild(l),this.rangeHighlightElements=[],Array.isArray(this.options.rangeHighlights)&&this.options.rangeHighlights.length>0)for(var s=0;s<this.options.rangeHighlights.length;s++){var t=document.createElement("div");t.className="slider-rangeHighlight slider-selection",this.rangeHighlightElements.push(t),r.appendChild(t)}var u=Array.isArray(this.options.labelledby);if(u&&this.options.labelledby[0]&&m.setAttribute("aria-labelledby",this.options.labelledby[0]),u&&this.options.labelledby[1]&&n.setAttribute("aria-labelledby",this.options.labelledby[1]),!u&&this.options.labelledby&&(m.setAttribute("aria-labelledby",this.options.labelledby),n.setAttribute("aria-labelledby",this.options.labelledby)),this.ticks=[],Array.isArray(this.options.ticks)&&this.options.ticks.length>0){for(this.ticksContainer=document.createElement("div"),this.ticksContainer.className="slider-tick-container",f=0;f<this.options.ticks.length;f++){var v=document.createElement("div");if(v.className="slider-tick",this.options.ticks_tooltip){var w=this._addTickListener(),x=w.addMouseEnter(this,v,f),y=w.addMouseLeave(this,v);this.ticksCallbackMap[f]={mouseEnter:x,mouseLeave:y}}this.ticks.push(v),this.ticksContainer.appendChild(v)}j.className+=" tick-slider-selection"}if(this.tickLabels=[],Array.isArrLine truncated
|
||||||
|
var e=Math.abs(this.options.ticks[d]-b);e<=c[1]&&(c=[this.options.ticks[d],e])}if(c[1]<=this.options.ticks_snap_bounds)return c[0]}return b},_applyPrecision:function(a){var b=this.options.precision||this._getNumDigitsAfterDecimalPlace(this.options.step);return this._applyToFixedAndParseFloat(a,b)},_getNumDigitsAfterDecimalPlace:function(a){var b=(""+a).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);return b?Math.max(0,(b[1]?b[1].length:0)-(b[2]?+b[2]:0)):0},_applyToFixedAndParseFloat:function(a,b){var c=a.toFixed(b);return parseFloat(c)},_getPercentage:function(a){!this.touchCapable||"touchstart"!==a.type&&"touchmove"!==a.type||(a=a.touches[0]);var b=a[this.mousePos],c=this._state.offset[this.stylePos],d=b-c;"right"===this.stylePos&&(d=-d);var e=d/this._state.size*100;return e=Math.round(e/this._state.percentage[2])*this._state.percentage[2],this.options.reversed&&(e=100-e),Math.max(0,Math.min(100,e))},_validateInputValue:function(a){if(isNaN(+a)){if(Array.isArray(a))return this._validateArray(a),a;throw new Error(f.formatInvalidInputErrorMsg(a))}return+a},_validateArray:function(a){for(var b=0;b<a.length;b++){var c=a[b];if("number"!=typeof c)throw new Error(f.formatInvalidInputErrorMsg(c))}},_setDataVal:function(a){this.element.setAttribute("data-value",a),this.element.setAttribute("value",a),this.element.value=a},_trigger:function(b,c){c=c||0===c?c:void 0;var d=this.eventToCallbackMap[b];if(d&&d.length)for(var e=0;e<d.length;e++){var f=d[e];f(c)}a&&this._triggerJQueryEvent(b,c)},_triggerJQueryEvent:function(a,b){var c={type:a,value:b};this.$element.trigger(c),this.$sliderElem.trigger(c)},_unbindJQueryEventHandlers:function(){this.$element.off(),this.$sliderElem.off()},_setText:function(a,b){"undefined"!=typeof a.textContent?a.textContent=b:"undefined"!=typeof a.innerText&&(a.innerText=b)},_removeClass:function(a,b){for(var c=b.split(" "),d=a.className,e=0;e<c.length;e++){var f=c[e],g=new RegExp("(?:\\s|^)"+f+"(?:\\s|$)");d=d.replace(g," ")}a.className=d.trim()},_addClass:function(a,b){for(var c=b.split(" "),d=a.className,e=0;e<c.length;e++){var f=c[e],g=new RegExp("(?:\\s|^)"+f+"(?:\\s|$)"),h=g.test(d);h||(d+=" "+f)}a.className=d.trim()},_offsetLeft:function(a){return a.getBoundingClientRect().left},_offsetRight:function(a){return a.getBoundingClientRect().right},_offsetTop:function(a){for(var b=a.offsetTop;(a=a.offsetParent)&&!isNaN(a.offsetTop);)b+=a.offsetTop,"BODY"!==a.tagName&&(b-=a.scrollTop);return b},_offset:function(a){return{left:this._offsetLeft(a),right:this._offsetRight(a),top:this._offsetTop(a)}},_css:function(b,c,d){if(a)a.style(b,c,d);else{var e=c.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(a,b){return b.toUpperCase()});b.style[e]=d}},_toValue:function(a){return this.options.scale.toValue.apply(this,[a])},_toPercentage:function(a){return this.options.scale.toPercentage.apply(this,[a])},_setTooltipPosition:function(){var a=[this.tooltip,this.tooltip_min,this.tooltip_max];if("vertical"===this.options.orientation){var b;b=this.options.tooltip_position?this.options.tooltip_position:this.options.rtl?"left":"right";var c="left"===b?"right":"left";a.forEach(function(a){this._addClass(a,b),a.style[c]="100%"}.bind(this))}else"bottom"===this.options.tooltip_position?a.forEach(function(a){this._addClass(a,"bottom"),a.style.top="22px"}.bind(this)):a.forEach(function(a){this._addClass(a,"top"),a.style.top=-this.tooltip.outerHeight-14+"px"}.bind(this))}},a&&a.fn&&!function(){var e=void 0;a.fn.slider?(windowIsDefined&&window.console.warn("bootstrap-slider.js - WARNING: $.fn.slider namespace is already bound. Use the $.fn.bootstrapSlider namespace instead."),e=c):(a.bridget(b,d),e=b),a.bridget(c,d),a(function(){a("input[data-provide=slider]")[e]()})}()}(a),d});
|
||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* bootstrap-switch - Turn checkboxes and radio buttons into toggle switches.
|
||||||
|
*
|
||||||
|
* @version v3.3.3
|
||||||
|
* @homepage http://www.bootstrap-switch.org
|
||||||
|
* @author Mattia Larentis <mattia@larentis.eu> (http://larentis.eu)
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';var _createClass=function(){function a(b,c){for(var e,d=0;d<c.length;d++)e=c[d],e.enumerable=e.enumerable||!1,e.configurable=!0,'value'in e&&(e.writable=!0),Object.defineProperty(b,e.key,e)}return function(b,c,d){return c&&a(b.prototype,c),d&&a(b,d),b}}();function _classCallCheck(a,b){if(!(a instanceof b))throw new TypeError('Cannot call a class as a function')}(function(a,b){var c=function(){function d(e){var g=this,f=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{};_classCallCheck(this,d),this.$element=a(e),this.options=a.extend({},a.fn.bootstrapSwitch.defaults,this._getElementOptions(),f),this.prevOptions={},this.$wrapper=a('<div>',{class:function _class(){var h=[];return h.push(g.options.state?'on':'off'),g.options.size&&h.push(g.options.size),g.options.disabled&&h.push('disabled'),g.options.readonly&&h.push('readonly'),g.options.indeterminate&&h.push('indeterminate'),g.options.inverse&&h.push('inverse'),g.$element.attr('id')&&h.push('id-'+g.$element.attr('id')),h.map(g._getClass.bind(g)).concat([g.options.baseClass],g._getClasses(g.options.wrapperClass)).join(' ')}}),this.$container=a('<div>',{class:this._getClass('container')}),this.$on=a('<span>',{html:this.options.onText,class:this._getClass('handle-on')+' '+this._getClass(this.options.onColor)}),this.$off=a('<span>',{html:this.options.offText,class:this._getClass('handle-off')+' '+this._getClass(this.options.offColor)}),this.$label=a('<span>',{html:this.options.labelText,class:this._getClass('label')}),this.$element.on('init.bootstrapSwitch',this.options.onInit.bind(this,e)),this.$element.on('switchChange.bootstrapSwitch',function(){for(var j=arguments.length,h=Array(j),k=0;k<j;k++)h[k]=arguments[k];!1===g.options.onSwitchChange.apply(e,h)&&(g.$element.is(':radio')?a('[name="'+g.$element.attr('name')+'"]').trigger('previousState.bootstrapSwitch',!0):g.$element.trigger('previousState.bootstrapSwitch',!0))}),this.$container=this.$element.wrap(this.$container).parent(),this.$wrapper=this.$container.wrap(this.$wrapper).parent(),this.$element.before(this.options.inverse?this.$off:this.$on).before(this.$label).before(this.options.inverse?this.$on:this.$off),this.options.indeterminate&&this.$element.prop('indeterminate',!0),this._init(),this._elementHandlers(),this._handleHandlers(),this._labelHandlers(),this._formHandler(),this._externalLabelHandler(),this.$element.trigger('init.bootstrapSwitch',this.options.state)}return _createClass(d,[{key:'setPrevOptions',value:function setPrevOptions(){this.prevOptions=Object.assign({},this.options)}},{key:'state',value:function state(e,f){return'undefined'==typeof e?this.options.state:this.options.disabled||this.options.readonly||this.options.state&&!this.options.radioAllOff&&this.$element.is(':radio')?this.$element:(this.$element.is(':radio')?a('[name="'+this.$element.attr('name')+'"]').trigger('setPreviousOptions.bootstrapSwitch'):this.$element.trigger('setPreviousOptions.bootstrapSwitch'),this.options.indeterminate&&this.indeterminate(!1),this.$element.prop('checked',!!e).trigger('change.bootstrapSwitch',f),this.$element)}},{key:'toggleState',value:function toggleState(e){return this.options.disabled||this.options.readonly?this.$element:this.options.indeterminate?(this.indeterminate(!1),this.state(!0)):this.$element.prop('checked',!this.options.state).trigger('change.bootstrapSwitch',e)}},{key:'size',value:function size(e){return'undefined'==typeof e?this.options.size:(null!=this.options.size&&this.$wrapper.removeClass(this._getClass(this.options.size)),e&&this.$wrapper.addClass(this._getClass(e)),this._width(),this._containerPosition(),this.options.size=e,this.$element)}},{key:'animate',value:function animate(e){return'undefined'==typeof e?this.options.animate:this.options.animate===!!e?this.$element:this.toggleAnimate()}},{key:'toggleAnimate',value:function toggleAnimate(){return this.options.animate=!this.options.animate,this.$wrapper.toggleClass(this._getClass('animate')),this.$element}},{key:'disabled',value:function disabled(e){return'undefined'==typeof e?this.options.disabled:this.options.disabled===!!e?this.$element:this.toggleDisabled()}},{key:'toggleDisabled',value:function toggleDisabled(){return this.options.disabled=!this.options.disabled,this.$element.prop('disabled',this.options.disabled),this.$wrapper.toggleClass(this._getClass('disabled')),this.$element}},{key:'readonly',value:function readonly(e){return'undefined'==typeof e?this.options.readonly:this.options.readonly===!!e?this.$element:this.toggleReadonly()}},{key:'toggleReadonly',value:function toggleReadonly(){return this.options.readonly=!this.options.readonly,this.$element.prop('readonly',this.options.readonly),this.$wrapper.toggleClass(this._getClass('readonly')),this.$element}},{key:'indeterminate',value:function indeterminate(e){return'undefined'==typeof e?this.options.indeterminate:this.options.indeterminate===!!e?this.$element:this.toggleIndeterminate()}},{key:'toggleIndeterminate',value:function toggleIndetermiLine truncated
|
||||||
@@ -29,6 +29,7 @@ $lang['danger']['policy_list_from_exists'] = 'Ein Eintrag mit diesem Wert existi
|
|||||||
$lang['danger']['policy_list_from_invalid'] = 'Eintrag hat ungültiges Format';
|
$lang['danger']['policy_list_from_invalid'] = 'Eintrag hat ungültiges Format';
|
||||||
$lang['danger']['alias_invalid'] = 'Alias-Adrese ist ungültig';
|
$lang['danger']['alias_invalid'] = 'Alias-Adrese ist ungültig';
|
||||||
$lang['danger']['goto_invalid'] = 'Ziel-Adrese ist ungültig';
|
$lang['danger']['goto_invalid'] = 'Ziel-Adrese ist ungültig';
|
||||||
|
$lang['danger']['last_key'] = 'Letzter Key kann nicht gelöscht werden';
|
||||||
$lang['danger']['alias_domain_invalid'] = 'Alias-Domain ist ungültig';
|
$lang['danger']['alias_domain_invalid'] = 'Alias-Domain ist ungültig';
|
||||||
$lang['danger']['target_domain_invalid'] = 'Ziel-Domain ist ungültig';
|
$lang['danger']['target_domain_invalid'] = 'Ziel-Domain ist ungültig';
|
||||||
$lang['danger']['object_exists'] = 'Objekt %s existiert bereits';
|
$lang['danger']['object_exists'] = 'Objekt %s existiert bereits';
|
||||||
@@ -374,11 +375,14 @@ $lang['login']['delayed'] = 'Login wurde zur Sicherheit um %s Sekunde/n verzöge
|
|||||||
$lang['tfa']['tfa'] = "Two-Factor Authentication";
|
$lang['tfa']['tfa'] = "Two-Factor Authentication";
|
||||||
$lang['tfa']['set_tfa'] = "Konfiguriere Two-Factor Authentication Methode";
|
$lang['tfa']['set_tfa'] = "Konfiguriere Two-Factor Authentication Methode";
|
||||||
$lang['tfa']['yubi_otp'] = "Yubico OTP Authentifizierung";
|
$lang['tfa']['yubi_otp'] = "Yubico OTP Authentifizierung";
|
||||||
|
$lang['tfa']['key_id'] = "Ein Name für diesen YubiKey";
|
||||||
|
$lang['tfa']['api_register'] = 'mailcow verwendet die Yubico Cloud API. Ein API-Key für den Yubico Stick kann <a href="https://upgrade.yubico.com/getapikey/" target="_blank">hier</a> bezogen werden.';
|
||||||
$lang['tfa']['u2f'] = "U2F Authentifizierung";
|
$lang['tfa']['u2f'] = "U2F Authentifizierung";
|
||||||
$lang['tfa']['hotp'] = "HOTP Authentifizierung";
|
$lang['tfa']['hotp'] = "HOTP Authentifizierung";
|
||||||
$lang['tfa']['totp'] = "TOTP Authentifizierung";
|
$lang['tfa']['totp'] = "TOTP Authentifizierung";
|
||||||
$lang['tfa']['none'] = "Deaktiviert";
|
$lang['tfa']['none'] = "Deaktiviert";
|
||||||
$lang['tfa']['delete_tfa'] = "Deaktiviere TFA";
|
$lang['tfa']['delete_tfa'] = "Deaktiviere TFA";
|
||||||
|
$lang['tfa']['disable_tfa'] = "Deaktiviere TFA bis zur nächsten erfolgreichen Anmeldung";
|
||||||
$lang['tfa']['confirm_tfa'] = "Please confirm your one-time password in the below field";
|
$lang['tfa']['confirm_tfa'] = "Please confirm your one-time password in the below field";
|
||||||
$lang['tfa']['confirm'] = "Bestätigen";
|
$lang['tfa']['confirm'] = "Bestätigen";
|
||||||
$lang['tfa']['otp'] = "Einmalpasswort";
|
$lang['tfa']['otp'] = "Einmalpasswort";
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ $lang['danger']['mailbox_quota_exceeds_domain_quota'] = "Max. quota exceeds doma
|
|||||||
$lang['danger']['object_is_not_numeric'] = "Value %s is not numeric";
|
$lang['danger']['object_is_not_numeric'] = "Value %s is not numeric";
|
||||||
$lang['success']['domain_added'] = "Added domain %s";
|
$lang['success']['domain_added'] = "Added domain %s";
|
||||||
$lang['danger']['alias_empty'] = "Alias address must not be empty";
|
$lang['danger']['alias_empty'] = "Alias address must not be empty";
|
||||||
|
$lang['danger']['last_key'] = 'Last key cannot be deleted';
|
||||||
$lang['danger']['goto_empty'] = "Goto address must not be empty";
|
$lang['danger']['goto_empty'] = "Goto address must not be empty";
|
||||||
$lang['danger']['policy_list_from_exists'] = "A record with given name exists";
|
$lang['danger']['policy_list_from_exists'] = "A record with given name exists";
|
||||||
$lang['danger']['policy_list_from_invalid'] = "Record has invalid format";
|
$lang['danger']['policy_list_from_invalid'] = "Record has invalid format";
|
||||||
@@ -377,11 +378,14 @@ $lang['login']['delayed'] = 'Login was delayed by %s seconds.';
|
|||||||
$lang['tfa']['tfa'] = "Two-factor authentication";
|
$lang['tfa']['tfa'] = "Two-factor authentication";
|
||||||
$lang['tfa']['set_tfa'] = "Set two-factor authentication method";
|
$lang['tfa']['set_tfa'] = "Set two-factor authentication method";
|
||||||
$lang['tfa']['yubi_otp'] = "Yubico OTP authentication";
|
$lang['tfa']['yubi_otp'] = "Yubico OTP authentication";
|
||||||
|
$lang['tfa']['key_id'] = "An identifier for your YubiKey";
|
||||||
|
$lang['tfa']['api_register'] = 'mailcow uses the Yubico Cloud API. Please get an API key for your key <a href="https://upgrade.yubico.com/getapikey/" target="_blank">here</a>';
|
||||||
$lang['tfa']['u2f'] = "U2F authentication";
|
$lang['tfa']['u2f'] = "U2F authentication";
|
||||||
$lang['tfa']['hotp'] = "HOTP authentication";
|
$lang['tfa']['hotp'] = "HOTP authentication";
|
||||||
$lang['tfa']['totp'] = "TOTP authentication";
|
$lang['tfa']['totp'] = "TOTP authentication";
|
||||||
$lang['tfa']['none'] = "Deaktiviert";
|
$lang['tfa']['none'] = "Deaktiviert";
|
||||||
$lang['tfa']['delete_tfa'] = "Disable TFA";
|
$lang['tfa']['delete_tfa'] = "Disable TFA";
|
||||||
|
$lang['tfa']['disable_tfa'] = "Disable TFA until next successful login";
|
||||||
$lang['tfa']['confirm_tfa'] = "Please confirm your one-time password in the below field";
|
$lang['tfa']['confirm_tfa'] = "Please confirm your one-time password in the below field";
|
||||||
$lang['tfa']['confirm'] = "Confirm";
|
$lang['tfa']['confirm'] = "Confirm";
|
||||||
$lang['tfa']['otp'] = "One-time password";
|
$lang['tfa']['otp'] = "One-time password";
|
||||||
|
|||||||
+15
-2
@@ -8,6 +8,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma
|
|||||||
|
|
||||||
require_once("inc/header.inc.php");
|
require_once("inc/header.inc.php");
|
||||||
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
||||||
|
$tfa_data = get_tfa();
|
||||||
$username = $_SESSION['mailcow_cc_username'];
|
$username = $_SESSION['mailcow_cc_username'];
|
||||||
?>
|
?>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -23,8 +24,19 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma
|
|||||||
<hr>
|
<hr>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-3 col-xs-5 text-right"><?=$lang['tfa']['tfa'];?></div>
|
<div class="col-md-3 col-xs-5 text-right"><?=$lang['tfa']['tfa'];?></div>
|
||||||
<div class="col-md-9 col-xs-7">
|
<div class="col-sm-9 col-xs-7">
|
||||||
<p><?=get_tfa()['pretty'];?></p>
|
<p id="tfa_pretty"><?=$tfa_data['pretty'];?></p>
|
||||||
|
<div id="tfa_additional">
|
||||||
|
<?php if($tfa_data['additional']):
|
||||||
|
foreach ($tfa_data['additional'] as $key_info): ?>
|
||||||
|
<form style="display:inline;" method="post">
|
||||||
|
<input type="hidden" name="unset_tfa_key" value="<?=$key_info['id'];?>" />
|
||||||
|
<div class="label label-default">🔑 <?=$key_info['key_id'];?> <a href="#" style="font-weight:bold;color:white" onClick="$(this).closest('form').submit()">[<?=strtolower($lang['admin']['remove']);?>]</a></div>
|
||||||
|
</form>
|
||||||
|
<?php endforeach;
|
||||||
|
endif;?>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -32,6 +44,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma
|
|||||||
<div class="col-md-9 col-xs-7">
|
<div class="col-md-9 col-xs-7">
|
||||||
<select id="selectTFA" class="selectpicker" title="<?=$lang['tfa']['select'];?>">
|
<select id="selectTFA" class="selectpicker" title="<?=$lang['tfa']['select'];?>">
|
||||||
<option value="yubi_otp"><?=$lang['tfa']['yubi_otp'];?></option>
|
<option value="yubi_otp"><?=$lang['tfa']['yubi_otp'];?></option>
|
||||||
|
<option value="u2f"><?=$lang['tfa']['u2f'];?></option>
|
||||||
<option value="none"><?=$lang['tfa']['none'];?></option>
|
<option value="none"><?=$lang['tfa']['none'];?></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+11
-7
@@ -3,6 +3,9 @@ version: '2.1'
|
|||||||
services:
|
services:
|
||||||
pdns-mailcow:
|
pdns-mailcow:
|
||||||
image: andryyy/mailcow-dockerized:pdns
|
image: andryyy/mailcow-dockerized:pdns
|
||||||
|
depends_on:
|
||||||
|
mysql-mailcow:
|
||||||
|
condition: service_healthy
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/conf/pdns/:/etc/powerdns/
|
- ./data/conf/pdns/:/etc/powerdns/
|
||||||
restart: always
|
restart: always
|
||||||
@@ -14,9 +17,11 @@ services:
|
|||||||
|
|
||||||
mysql-mailcow:
|
mysql-mailcow:
|
||||||
image: mariadb:10.1
|
image: mariadb:10.1
|
||||||
depends_on:
|
healthcheck:
|
||||||
- pdns-mailcow
|
test: ["CMD", "mysqladmin", "ping", "--host", "localhost", "--silent"]
|
||||||
command: mysqld
|
interval: 10s
|
||||||
|
timeout: 30s
|
||||||
|
retries: 5
|
||||||
volumes:
|
volumes:
|
||||||
- mysql-vol-1:/var/lib/mysql/
|
- mysql-vol-1:/var/lib/mysql/
|
||||||
- ./data/conf/mysql/:/etc/mysql/conf.d/:ro
|
- ./data/conf/mysql/:/etc/mysql/conf.d/:ro
|
||||||
@@ -52,7 +57,7 @@ services:
|
|||||||
rspamd-mailcow:
|
rspamd-mailcow:
|
||||||
image: andryyy/mailcow-dockerized:rspamd
|
image: andryyy/mailcow-dockerized:rspamd
|
||||||
depends_on:
|
depends_on:
|
||||||
- pdns-mailcow
|
- nginx-mailcow
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/conf/rspamd/override.d/:/etc/rspamd/override.d:ro
|
- ./data/conf/rspamd/override.d/:/etc/rspamd/override.d:ro
|
||||||
- ./data/conf/rspamd/local.d/:/etc/rspamd/local.d:ro
|
- ./data/conf/rspamd/local.d/:/etc/rspamd/local.d:ro
|
||||||
@@ -65,6 +70,7 @@ services:
|
|||||||
dns_search: mailcow-network
|
dns_search: mailcow-network
|
||||||
networks:
|
networks:
|
||||||
mailcow-network:
|
mailcow-network:
|
||||||
|
ipv4_address: 172.22.1.253
|
||||||
aliases:
|
aliases:
|
||||||
- rspamd
|
- rspamd
|
||||||
|
|
||||||
@@ -95,7 +101,6 @@ services:
|
|||||||
image: andryyy/mailcow-dockerized:sogo
|
image: andryyy/mailcow-dockerized:sogo
|
||||||
depends_on:
|
depends_on:
|
||||||
- pdns-mailcow
|
- pdns-mailcow
|
||||||
- mysql-mailcow
|
|
||||||
environment:
|
environment:
|
||||||
- DBNAME=${DBNAME}
|
- DBNAME=${DBNAME}
|
||||||
- DBUSER=${DBUSER}
|
- DBUSER=${DBUSER}
|
||||||
@@ -110,6 +115,7 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
mailcow-network:
|
mailcow-network:
|
||||||
|
ipv4_address: 172.22.1.252
|
||||||
aliases:
|
aliases:
|
||||||
- sogo
|
- sogo
|
||||||
|
|
||||||
@@ -197,10 +203,8 @@ services:
|
|||||||
|
|
||||||
nginx-mailcow:
|
nginx-mailcow:
|
||||||
depends_on:
|
depends_on:
|
||||||
- mysql-mailcow
|
|
||||||
- sogo-mailcow
|
- sogo-mailcow
|
||||||
- php-fpm-mailcow
|
- php-fpm-mailcow
|
||||||
- rspamd-mailcow
|
|
||||||
image: nginx:mainline
|
image: nginx:mainline
|
||||||
command: /bin/bash -c "envsubst < /etc/nginx/conf.d/listen.template > /etc/nginx/conf.d/listen.active && nginx -g 'daemon off;'"
|
command: /bin/bash -c "envsubst < /etc/nginx/conf.d/listen.template > /etc/nginx/conf.d/listen.active && nginx -g 'daemon off;'"
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
Reference in new issue
Block a user