Merge pull request #51 from andryyy/dev

Dev to master
master
André Peters 2017-02-15 21:25:36 +01:00 committed by GitHub
commit bdba65686b
25 changed files with 325 additions and 85 deletions

View File

@ -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

View File

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

View File

@ -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;
}
?>

View File

@ -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
@ -40,12 +54,12 @@ rspamd_config.ADD_DELIMITER_TAG = {
rspamd_logger.infox("User wants subject modified for tagged mail") rspamd_logger.infox("User wants subject modified for tagged mail")
local sbj = task:get_header('Subject') local sbj = task:get_header('Subject')
if tag then if tag then
rspamd_logger.infox("Found tag %1, will modify subject header", tag) rspamd_logger.infox("Found tag %1, will modify subject header", tag)
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?=' new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
task:set_rmilter_reply({ task:set_rmilter_reply({
remove_headers = {['Subject'] = 1}, remove_headers = {['Subject'] = 1},
add_headers = {['Subject'] = new_sbj} add_headers = {['Subject'] = new_sbj}
}) })
end end
else else
rspamd_logger.infox("Add X-Moo-Tag header") rspamd_logger.infox("Add X-Moo-Tag header")

View File

@ -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>

View File

@ -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,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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>

View File

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

View File

@ -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));
}
?> ?>

View File

@ -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>

View File

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

View File

@ -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>

View File

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

View File

@ -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'] = "";
?> ?>

View File

@ -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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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";

View File

@ -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";

View File

@ -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,15 +24,27 @@ 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> <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 class="row"> <div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['tfa']['set_tfa'];?></div> <div class="col-md-3 col-xs-5 text-right"><?=$lang['tfa']['set_tfa'];?></div>
<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>

View File

@ -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: